├── .clang-format ├── .gitignore ├── .travis.yml ├── APNSubGroupOperationQueue.podspec ├── APNSubGroupOperationQueue.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── APNSubGroupOperationQueue iOS.xcscheme │ ├── APNSubGroupOperationQueue macOS.xcscheme │ ├── APNSubGroupOperationQueue tvOS.xcscheme │ └── APNSubGroupOperationQueue watchOS.xcscheme ├── APNSubGroupOperationQueue.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Sources ├── APNSubGroupOperationQueue.h ├── CompletionOperation.swift ├── DynamicSubGroupOperationQueue.swift ├── Info-iOS.plist ├── Info-macOS.plist ├── Info-tvOS.plist ├── Info-watchOS.plist ├── OperationSubGroupMap.swift ├── SubGroupOperationQueue.swift └── UnfairLock.swift └── Tests ├── APNSubGroupOperationQueueTests.m ├── DynamicSubGroupOperationQueueTests.swift ├── Info-OSX.plist ├── Info-iOS.plist ├── Info-tvOS.plist ├── SubGroupOperationQueueTests.swift └── TestUtils.swift /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: Google 3 | AccessModifierOffset: -1 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortIfStatementsOnASingleLine: true 9 | AllowShortLoopsOnASingleLine: true 10 | AlwaysBreakTemplateDeclarations: true 11 | AlwaysBreakBeforeMultilineStrings: true 12 | BreakBeforeBinaryOperators: false 13 | BreakBeforeTernaryOperators: true 14 | BreakConstructorInitializersBeforeComma: false 15 | BinPackParameters: true 16 | ColumnLimit: 120 17 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 18 | DerivePointerBinding: true 19 | ExperimentalAutoDetectBinPacking: false 20 | IndentCaseLabels: true 21 | MaxEmptyLinesToKeep: 1 22 | NamespaceIndentation: None 23 | ObjCSpaceBeforeProtocolList: false 24 | PenaltyBreakBeforeFirstCallParameter: 1 25 | PenaltyBreakComment: 60 26 | PenaltyBreakString: 1000 27 | PenaltyBreakFirstLessLess: 120 28 | PenaltyExcessCharacter: 1000000 29 | PenaltyReturnTypeOnItsOwnLine: 200 30 | PointerBindsToType: true 31 | SpacesBeforeTrailingComments: 2 32 | Cpp11BracedListStyle: true 33 | Standard: Auto 34 | IndentWidth: 4 35 | TabWidth: 4 36 | UseTab: Never 37 | BreakBeforeBraces: Attach 38 | IndentFunctionDeclarationAfterType: true 39 | SpacesInParentheses: false 40 | SpacesInAngles: false 41 | SpaceInEmptyParentheses: false 42 | SpacesInCStyleCastParentheses: false 43 | SpaceAfterControlStatementKeyword: true 44 | SpaceBeforeAssignmentOperators: true 45 | ContinuationIndentWidth: 4 46 | ObjCBlockIndentWidth: 4 47 | ... 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,xcode,osx,carthage,objective-c 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | 69 | 70 | ### Xcode ### 71 | # Xcode 72 | # 73 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 74 | 75 | ## Build generated 76 | build/ 77 | DerivedData/ 78 | 79 | ## Various settings 80 | *.pbxuser 81 | !default.pbxuser 82 | *.mode1v3 83 | !default.mode1v3 84 | *.mode2v3 85 | !default.mode2v3 86 | *.perspectivev3 87 | !default.perspectivev3 88 | xcuserdata/ 89 | 90 | ## Other 91 | *.moved-aside 92 | *.xccheckout 93 | *.xcscmblueprint 94 | 95 | 96 | ### OSX ### 97 | .DS_Store 98 | .AppleDouble 99 | .LSOverride 100 | 101 | # Icon must end with two \r 102 | Icon 103 | 104 | 105 | # Thumbnails 106 | ._* 107 | 108 | # Files that might appear in the root of a volume 109 | .DocumentRevisions-V100 110 | .fseventsd 111 | .Spotlight-V100 112 | .TemporaryItems 113 | .Trashes 114 | .VolumeIcon.icns 115 | 116 | # Directories potentially created on remote AFP share 117 | .AppleDB 118 | .AppleDesktop 119 | Network Trash Folder 120 | Temporary Items 121 | .apdisk 122 | 123 | 124 | ### Carthage ### 125 | # Carthage - A simple, decentralized dependency manager for Cocoa 126 | Carthage.checkout 127 | Carthage.build 128 | 129 | ### Objective-C ### 130 | # Xcode 131 | # 132 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 133 | 134 | ## Build generated 135 | build/ 136 | DerivedData/ 137 | 138 | ## Various settings 139 | *.pbxuser 140 | !default.pbxuser 141 | *.mode1v3 142 | !default.mode1v3 143 | *.mode2v3 144 | !default.mode2v3 145 | *.perspectivev3 146 | !default.perspectivev3 147 | xcuserdata/ 148 | 149 | ## Other 150 | *.moved-aside 151 | *.xcuserstate 152 | 153 | ## Obj-C/Swift specific 154 | *.hmap 155 | *.ipa 156 | 157 | # CocoaPods 158 | # 159 | # We recommend against adding the Pods directory to your .gitignore. However 160 | # you should judge for yourself, the pros and cons are mentioned at: 161 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 162 | # 163 | # Pods/ 164 | 165 | # Carthage 166 | # 167 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 168 | # Carthage/Checkouts 169 | 170 | Carthage/Build 171 | 172 | # fastlane 173 | # 174 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 175 | # screenshots whenever they are needed. 176 | # For more information about the recommended setup visit: 177 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 178 | 179 | fastlane/report.xml 180 | fastlane/screenshots 181 | 182 | ### Objective-C Patch ### 183 | *.xcscmblueprint 184 | 185 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode10.2 3 | 4 | before_install: 5 | - gem install cocoapods 6 | 7 | install: true 8 | 9 | stage: Build + Test 10 | env: 11 | - PLATFORM="iOS" 12 | - PLATFORM="macOS" 13 | - PLATFORM="tvOS" 14 | - PLATFORM="watchOS" 15 | script: 16 | - make $PLATFORM 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) 19 | 20 | jobs: 21 | include: 22 | - stage: Pod lib lint 23 | script: 24 | - pod lib lint --verbose 25 | - stage: Deploy Github 26 | script: skip 27 | before_deploy: 28 | - carthage build --no-skip-current --cache-builds 29 | - carthage archive APNSubGroupOperationQueue 30 | deploy: 31 | - provider: releases 32 | api_key: $GITHUB_OAUTH_TOKEN 33 | file: APNSubGroupOperationQueue.framework.zip 34 | skip_cleanup: true 35 | overwrite: true 36 | on: 37 | repo: p4checo/APNSubGroupOperationQueue 38 | branch: master 39 | tags: true 40 | - stage: Deploy Cocoapods 41 | script: skip 42 | deploy: 43 | - provider: script 44 | script: pod trunk push --allow-warnings 45 | skip_cleanup: true 46 | on: 47 | repo: p4checo/APNSubGroupOperationQueue 48 | branch: master 49 | tags: true 50 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "APNSubGroupOperationQueue" 3 | s.version = "4.0.0" 4 | s.summary = "Serial processing sub groups inside your concurrent NSOperationQueue." 5 | s.description = "APNSubGroupOperationQueue is a µFramework consisting of `NSOperationQueue` subclasses (Swift & Obj-C) which allow scheduling operations in serial subgroups inside a concurrent queue" 6 | 7 | s.homepage = "https://github.com/p4checo/APNSubGroupOperationQueue" 8 | s.license = { :type => 'MIT', :file => 'LICENSE' } 9 | 10 | s.author = { 'André Pacheco Neves' => 'p4checo + @ + gmail + . + com' } 11 | s.social_media_url = 'https://twitter.com/p4checo' 12 | 13 | s.module_name = 'APNSubGroupOperationQueue' 14 | s.swift_version = '5.0' 15 | 16 | s.ios.deployment_target = '10.0' 17 | s.osx.deployment_target = '10.12' 18 | s.tvos.deployment_target = '10.0' 19 | s.watchos.deployment_target = '3.0' 20 | 21 | s.source = { :git => "https://github.com/p4checo/APNSubGroupOperationQueue.git", :tag => s.version } 22 | 23 | s.source_files = "Sources/**/*.swift" 24 | 25 | s.framework = "Foundation" 26 | end 27 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A5DB88C1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */; }; 11 | 0A5DB88D1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */; }; 12 | 0A5DB88E1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */; }; 13 | 0A5DB88F1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */; }; 14 | 0A5DB8911E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */; }; 15 | 0A5DB8921E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */; }; 16 | 0A5DB8931E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */; }; 17 | 0A5DB8941E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */; }; 18 | 0A5DB8961E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */; }; 19 | 0A5DB8971E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */; }; 20 | 0A5DB8981E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */; }; 21 | 0A5DB8991E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */; }; 22 | 0A5DB89B1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89A1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift */; }; 23 | 0A5DB89C1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89A1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift */; }; 24 | 0A5DB89D1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89A1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift */; }; 25 | 0A5DB89F1E50CC3D00DD3AD9 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89E1E50CC3D00DD3AD9 /* TestUtils.swift */; }; 26 | 0A5DB8A01E50CC3D00DD3AD9 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89E1E50CC3D00DD3AD9 /* TestUtils.swift */; }; 27 | 0A5DB8A11E50CC3D00DD3AD9 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5DB89E1E50CC3D00DD3AD9 /* TestUtils.swift */; }; 28 | 0A752A5B214DB11700320E76 /* UnfairLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A752A5A214DB11700320E76 /* UnfairLock.swift */; }; 29 | 0A752A5C214DB27B00320E76 /* UnfairLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A752A5A214DB11700320E76 /* UnfairLock.swift */; }; 30 | 0A752A5D214DB27C00320E76 /* UnfairLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A752A5A214DB11700320E76 /* UnfairLock.swift */; }; 31 | 0A752A5E214DB27C00320E76 /* UnfairLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A752A5A214DB11700320E76 /* UnfairLock.swift */; }; 32 | 0A9DEDA51CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 33 | 0A9DEDA61CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34 | 0A9DEDA71CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 35 | 0A9DEDB61CA754B200E81AEA /* APNSubGroupOperationQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36 | 0A9DEDC11CA834E900E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A9DED791CA7513A00E81AEA /* APNSubGroupOperationQueue.framework */; }; 37 | 0A9DEDD01CA8350500E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A9DED931CA7516700E81AEA /* APNSubGroupOperationQueue.framework */; }; 38 | 0A9DEDDF1CA8353300E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A9DEDAD1CA7540A00E81AEA /* APNSubGroupOperationQueue.framework */; }; 39 | 0A9DEDEC1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */; }; 40 | 0A9DEDED1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */; }; 41 | 0A9DEDEE1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */; }; 42 | 0A9DEDEF1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */; }; 43 | 0A9DEDF21CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDF11CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift */; }; 44 | 0A9DEDF31CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDF11CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift */; }; 45 | 0A9DEDF41CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEDF11CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift */; }; 46 | 0A9DEE3D1CB15FB700E81AEA /* APNSubGroupOperationQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEE381CB15E7D00E81AEA /* APNSubGroupOperationQueueTests.m */; }; 47 | 0A9DEE3E1CB15FB700E81AEA /* APNSubGroupOperationQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEE381CB15E7D00E81AEA /* APNSubGroupOperationQueueTests.m */; }; 48 | 0A9DEE3F1CB15FB800E81AEA /* APNSubGroupOperationQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A9DEE381CB15E7D00E81AEA /* APNSubGroupOperationQueueTests.m */; }; 49 | /* End PBXBuildFile section */ 50 | 51 | /* Begin PBXContainerItemProxy section */ 52 | 0A9DEDC21CA834E900E81AEA /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = 0A9DED601CA7509100E81AEA /* Project object */; 55 | proxyType = 1; 56 | remoteGlobalIDString = 0A9DED781CA7513A00E81AEA; 57 | remoteInfo = "APNSubGroupOperationQueue iOS"; 58 | }; 59 | 0A9DEDD11CA8350500E81AEA /* PBXContainerItemProxy */ = { 60 | isa = PBXContainerItemProxy; 61 | containerPortal = 0A9DED601CA7509100E81AEA /* Project object */; 62 | proxyType = 1; 63 | remoteGlobalIDString = 0A9DED921CA7516700E81AEA; 64 | remoteInfo = "APNSubGroupOperationQueue tvOS"; 65 | }; 66 | 0A9DEDE01CA8353300E81AEA /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = 0A9DED601CA7509100E81AEA /* Project object */; 69 | proxyType = 1; 70 | remoteGlobalIDString = 0A9DEDAC1CA7540A00E81AEA; 71 | remoteInfo = "APNSubGroupOperationQueue OS X"; 72 | }; 73 | /* End PBXContainerItemProxy section */ 74 | 75 | /* Begin PBXFileReference section */ 76 | 0A15BE5F1CBAD28C00D6E3DB /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 77 | 0A1FE5C01CBA9C2600FFAA96 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 78 | 0A1FE5C11CBAA4E600FFAA96 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 79 | 0A1FE5C21CBAA4F200FFAA96 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; 80 | 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicSubGroupOperationQueue.swift; sourceTree = ""; }; 81 | 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompletionOperation.swift; sourceTree = ""; }; 82 | 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationSubGroupMap.swift; sourceTree = ""; }; 83 | 0A5DB89A1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicSubGroupOperationQueueTests.swift; sourceTree = ""; }; 84 | 0A5DB89E1E50CC3D00DD3AD9 /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; 85 | 0A6F1AC31CB98DE7003C74FC /* APNSubGroupOperationQueue.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = APNSubGroupOperationQueue.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 86 | 0A752A5A214DB11700320E76 /* UnfairLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfairLock.swift; sourceTree = ""; }; 87 | 0A9DED791CA7513A00E81AEA /* APNSubGroupOperationQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APNSubGroupOperationQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 0A9DED861CA7515800E81AEA /* APNSubGroupOperationQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APNSubGroupOperationQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 0A9DED931CA7516700E81AEA /* APNSubGroupOperationQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APNSubGroupOperationQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 90 | 0A9DED9C1CA751D800E81AEA /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 91 | 0A9DED9D1CA751D800E81AEA /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 92 | 0A9DED9E1CA751D800E81AEA /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = ""; }; 93 | 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APNSubGroupOperationQueue.h; sourceTree = ""; }; 94 | 0A9DEDAD1CA7540A00E81AEA /* APNSubGroupOperationQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APNSubGroupOperationQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | 0A9DEDB51CA7542F00E81AEA /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; 96 | 0A9DEDBC1CA834E900E81AEA /* APNSubGroupOperationQueue iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "APNSubGroupOperationQueue iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 97 | 0A9DEDCB1CA8350500E81AEA /* APNSubGroupOperationQueue tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "APNSubGroupOperationQueue tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | 0A9DEDDA1CA8353200E81AEA /* APNSubGroupOperationQueue macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "APNSubGroupOperationQueue macOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 0A9DEDEA1CA83B7400E81AEA /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 100 | 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubGroupOperationQueue.swift; sourceTree = ""; }; 101 | 0A9DEDF11CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubGroupOperationQueueTests.swift; sourceTree = ""; }; 102 | 0A9DEDF71CA88BFD00E81AEA /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 103 | 0A9DEDF81CA88BFD00E81AEA /* Info-OSX.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-OSX.plist"; sourceTree = ""; }; 104 | 0A9DEDF91CA88BFD00E81AEA /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 105 | 0A9DEE381CB15E7D00E81AEA /* APNSubGroupOperationQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APNSubGroupOperationQueueTests.m; sourceTree = ""; }; 106 | /* End PBXFileReference section */ 107 | 108 | /* Begin PBXFrameworksBuildPhase section */ 109 | 0A9DED751CA7513A00E81AEA /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | 0A9DED821CA7515800E81AEA /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | 0A9DED8F1CA7516700E81AEA /* Frameworks */ = { 124 | isa = PBXFrameworksBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | 0A9DEDA91CA7540A00E81AEA /* Frameworks */ = { 131 | isa = PBXFrameworksBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | 0A9DEDB91CA834E900E81AEA /* Frameworks */ = { 138 | isa = PBXFrameworksBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | 0A9DEDC11CA834E900E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | 0A9DEDC81CA8350500E81AEA /* Frameworks */ = { 146 | isa = PBXFrameworksBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 0A9DEDD01CA8350500E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | 0A9DEDD71CA8353200E81AEA /* Frameworks */ = { 154 | isa = PBXFrameworksBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 0A9DEDDF1CA8353300E81AEA /* APNSubGroupOperationQueue.framework in Frameworks */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXFrameworksBuildPhase section */ 162 | 163 | /* Begin PBXGroup section */ 164 | 0A9DED5F1CA7509100E81AEA = { 165 | isa = PBXGroup; 166 | children = ( 167 | 0A9DEDE81CA83B6500E81AEA /* Metadata */, 168 | 0A9DED9B1CA751D800E81AEA /* Sources */, 169 | 0A9DEDF01CA88B5A00E81AEA /* Tests */, 170 | 0A9DED6C1CA750D800E81AEA /* Products */, 171 | ); 172 | sourceTree = ""; 173 | }; 174 | 0A9DED6C1CA750D800E81AEA /* Products */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 0A9DED791CA7513A00E81AEA /* APNSubGroupOperationQueue.framework */, 178 | 0A9DED861CA7515800E81AEA /* APNSubGroupOperationQueue.framework */, 179 | 0A9DED931CA7516700E81AEA /* APNSubGroupOperationQueue.framework */, 180 | 0A9DEDAD1CA7540A00E81AEA /* APNSubGroupOperationQueue.framework */, 181 | 0A9DEDBC1CA834E900E81AEA /* APNSubGroupOperationQueue iOSTests.xctest */, 182 | 0A9DEDCB1CA8350500E81AEA /* APNSubGroupOperationQueue tvOSTests.xctest */, 183 | 0A9DEDDA1CA8353200E81AEA /* APNSubGroupOperationQueue macOSTests.xctest */, 184 | ); 185 | name = Products; 186 | sourceTree = ""; 187 | }; 188 | 0A9DED9B1CA751D800E81AEA /* Sources */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 0A9DEDA21CA7527900E81AEA /* APNSubGroupOperationQueue.h */, 192 | 0A9DEDEB1CA83BC400E81AEA /* SubGroupOperationQueue.swift */, 193 | 0A5DB88B1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift */, 194 | 0A5DB8951E50B6BA00DD3AD9 /* OperationSubGroupMap.swift */, 195 | 0A5DB8901E509E5E00DD3AD9 /* CompletionOperation.swift */, 196 | 0A752A5A214DB11700320E76 /* UnfairLock.swift */, 197 | 0A9DED9C1CA751D800E81AEA /* Info-iOS.plist */, 198 | 0A9DEDB51CA7542F00E81AEA /* Info-macOS.plist */, 199 | 0A9DED9D1CA751D800E81AEA /* Info-tvOS.plist */, 200 | 0A9DED9E1CA751D800E81AEA /* Info-watchOS.plist */, 201 | ); 202 | path = Sources; 203 | sourceTree = ""; 204 | }; 205 | 0A9DEDE81CA83B6500E81AEA /* Metadata */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 0A1FE5C01CBA9C2600FFAA96 /* README.md */, 209 | 0A1FE5C21CBAA4F200FFAA96 /* .travis.yml */, 210 | 0A1FE5C11CBAA4E600FFAA96 /* Makefile */, 211 | 0A15BE5F1CBAD28C00D6E3DB /* Package.swift */, 212 | 0A6F1AC31CB98DE7003C74FC /* APNSubGroupOperationQueue.podspec */, 213 | 0A9DEDEA1CA83B7400E81AEA /* LICENSE */, 214 | ); 215 | name = Metadata; 216 | sourceTree = ""; 217 | }; 218 | 0A9DEDF01CA88B5A00E81AEA /* Tests */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 0A9DEDF11CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift */, 222 | 0A5DB89A1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift */, 223 | 0A9DEE381CB15E7D00E81AEA /* APNSubGroupOperationQueueTests.m */, 224 | 0A5DB89E1E50CC3D00DD3AD9 /* TestUtils.swift */, 225 | 0A9DEDF71CA88BFD00E81AEA /* Info-iOS.plist */, 226 | 0A9DEDF81CA88BFD00E81AEA /* Info-OSX.plist */, 227 | 0A9DEDF91CA88BFD00E81AEA /* Info-tvOS.plist */, 228 | ); 229 | path = Tests; 230 | sourceTree = ""; 231 | }; 232 | /* End PBXGroup section */ 233 | 234 | /* Begin PBXHeadersBuildPhase section */ 235 | 0A9DED761CA7513A00E81AEA /* Headers */ = { 236 | isa = PBXHeadersBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | 0A9DEDA51CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | 0A9DED831CA7515800E81AEA /* Headers */ = { 244 | isa = PBXHeadersBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 0A9DEDA61CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | 0A9DED901CA7516700E81AEA /* Headers */ = { 252 | isa = PBXHeadersBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | 0A9DEDA71CA7531900E81AEA /* APNSubGroupOperationQueue.h in Headers */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 0A9DEDAA1CA7540A00E81AEA /* Headers */ = { 260 | isa = PBXHeadersBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 0A9DEDB61CA754B200E81AEA /* APNSubGroupOperationQueue.h in Headers */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXHeadersBuildPhase section */ 268 | 269 | /* Begin PBXNativeTarget section */ 270 | 0A9DED781CA7513A00E81AEA /* APNSubGroupOperationQueue iOS */ = { 271 | isa = PBXNativeTarget; 272 | buildConfigurationList = 0A9DED7E1CA7513A00E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue iOS" */; 273 | buildPhases = ( 274 | 0A9DED741CA7513A00E81AEA /* Sources */, 275 | 0A9DED751CA7513A00E81AEA /* Frameworks */, 276 | 0A9DED761CA7513A00E81AEA /* Headers */, 277 | 0A9DED771CA7513A00E81AEA /* Resources */, 278 | ); 279 | buildRules = ( 280 | ); 281 | dependencies = ( 282 | ); 283 | name = "APNSubGroupOperationQueue iOS"; 284 | productName = "APNSubGroupOperationQueue iOS"; 285 | productReference = 0A9DED791CA7513A00E81AEA /* APNSubGroupOperationQueue.framework */; 286 | productType = "com.apple.product-type.framework"; 287 | }; 288 | 0A9DED851CA7515800E81AEA /* APNSubGroupOperationQueue watchOS */ = { 289 | isa = PBXNativeTarget; 290 | buildConfigurationList = 0A9DED8B1CA7515800E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue watchOS" */; 291 | buildPhases = ( 292 | 0A9DED811CA7515800E81AEA /* Sources */, 293 | 0A9DED821CA7515800E81AEA /* Frameworks */, 294 | 0A9DED831CA7515800E81AEA /* Headers */, 295 | 0A9DED841CA7515800E81AEA /* Resources */, 296 | ); 297 | buildRules = ( 298 | ); 299 | dependencies = ( 300 | ); 301 | name = "APNSubGroupOperationQueue watchOS"; 302 | productName = "APNSubGroupOperationQueue watchOS"; 303 | productReference = 0A9DED861CA7515800E81AEA /* APNSubGroupOperationQueue.framework */; 304 | productType = "com.apple.product-type.framework"; 305 | }; 306 | 0A9DED921CA7516700E81AEA /* APNSubGroupOperationQueue tvOS */ = { 307 | isa = PBXNativeTarget; 308 | buildConfigurationList = 0A9DED981CA7516700E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue tvOS" */; 309 | buildPhases = ( 310 | 0A9DED8E1CA7516700E81AEA /* Sources */, 311 | 0A9DED8F1CA7516700E81AEA /* Frameworks */, 312 | 0A9DED901CA7516700E81AEA /* Headers */, 313 | 0A9DED911CA7516700E81AEA /* Resources */, 314 | ); 315 | buildRules = ( 316 | ); 317 | dependencies = ( 318 | ); 319 | name = "APNSubGroupOperationQueue tvOS"; 320 | productName = "APNSubGroupOperationQueue tvOS"; 321 | productReference = 0A9DED931CA7516700E81AEA /* APNSubGroupOperationQueue.framework */; 322 | productType = "com.apple.product-type.framework"; 323 | }; 324 | 0A9DEDAC1CA7540A00E81AEA /* APNSubGroupOperationQueue macOS */ = { 325 | isa = PBXNativeTarget; 326 | buildConfigurationList = 0A9DEDB21CA7540B00E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue macOS" */; 327 | buildPhases = ( 328 | 0A9DEDA81CA7540A00E81AEA /* Sources */, 329 | 0A9DEDA91CA7540A00E81AEA /* Frameworks */, 330 | 0A9DEDAA1CA7540A00E81AEA /* Headers */, 331 | 0A9DEDAB1CA7540A00E81AEA /* Resources */, 332 | ); 333 | buildRules = ( 334 | ); 335 | dependencies = ( 336 | ); 337 | name = "APNSubGroupOperationQueue macOS"; 338 | productName = "APNSubGroupOperationQueue OS X"; 339 | productReference = 0A9DEDAD1CA7540A00E81AEA /* APNSubGroupOperationQueue.framework */; 340 | productType = "com.apple.product-type.framework"; 341 | }; 342 | 0A9DEDBB1CA834E900E81AEA /* APNSubGroupOperationQueue iOSTests */ = { 343 | isa = PBXNativeTarget; 344 | buildConfigurationList = 0A9DEDC61CA834E900E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue iOSTests" */; 345 | buildPhases = ( 346 | 0A9DEDB81CA834E900E81AEA /* Sources */, 347 | 0A9DEDB91CA834E900E81AEA /* Frameworks */, 348 | 0A9DEDBA1CA834E900E81AEA /* Resources */, 349 | ); 350 | buildRules = ( 351 | ); 352 | dependencies = ( 353 | 0A9DEDC31CA834E900E81AEA /* PBXTargetDependency */, 354 | ); 355 | name = "APNSubGroupOperationQueue iOSTests"; 356 | productName = "APNSubGroupOperationQueue iOSTests"; 357 | productReference = 0A9DEDBC1CA834E900E81AEA /* APNSubGroupOperationQueue iOSTests.xctest */; 358 | productType = "com.apple.product-type.bundle.unit-test"; 359 | }; 360 | 0A9DEDCA1CA8350500E81AEA /* APNSubGroupOperationQueue tvOSTests */ = { 361 | isa = PBXNativeTarget; 362 | buildConfigurationList = 0A9DEDD31CA8350500E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue tvOSTests" */; 363 | buildPhases = ( 364 | 0A9DEDC71CA8350500E81AEA /* Sources */, 365 | 0A9DEDC81CA8350500E81AEA /* Frameworks */, 366 | 0A9DEDC91CA8350500E81AEA /* Resources */, 367 | ); 368 | buildRules = ( 369 | ); 370 | dependencies = ( 371 | 0A9DEDD21CA8350500E81AEA /* PBXTargetDependency */, 372 | ); 373 | name = "APNSubGroupOperationQueue tvOSTests"; 374 | productName = "APNSubGroupOperationQueue tvOSTests"; 375 | productReference = 0A9DEDCB1CA8350500E81AEA /* APNSubGroupOperationQueue tvOSTests.xctest */; 376 | productType = "com.apple.product-type.bundle.unit-test"; 377 | }; 378 | 0A9DEDD91CA8353200E81AEA /* APNSubGroupOperationQueue macOSTests */ = { 379 | isa = PBXNativeTarget; 380 | buildConfigurationList = 0A9DEDE21CA8353300E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue macOSTests" */; 381 | buildPhases = ( 382 | 0A9DEDD61CA8353200E81AEA /* Sources */, 383 | 0A9DEDD71CA8353200E81AEA /* Frameworks */, 384 | 0A9DEDD81CA8353200E81AEA /* Resources */, 385 | ); 386 | buildRules = ( 387 | ); 388 | dependencies = ( 389 | 0A9DEDE11CA8353300E81AEA /* PBXTargetDependency */, 390 | ); 391 | name = "APNSubGroupOperationQueue macOSTests"; 392 | productName = "APNSubGroupOperationQueue OSXTests"; 393 | productReference = 0A9DEDDA1CA8353200E81AEA /* APNSubGroupOperationQueue macOSTests.xctest */; 394 | productType = "com.apple.product-type.bundle.unit-test"; 395 | }; 396 | /* End PBXNativeTarget section */ 397 | 398 | /* Begin PBXProject section */ 399 | 0A9DED601CA7509100E81AEA /* Project object */ = { 400 | isa = PBXProject; 401 | attributes = { 402 | CLASSPREFIX = APN; 403 | LastSwiftUpdateCheck = 0730; 404 | LastUpgradeCheck = 1020; 405 | ORGANIZATIONNAME = "André Pacheco Neves"; 406 | TargetAttributes = { 407 | 0A9DED781CA7513A00E81AEA = { 408 | CreatedOnToolsVersion = 7.3; 409 | LastSwiftMigration = 1020; 410 | }; 411 | 0A9DED851CA7515800E81AEA = { 412 | CreatedOnToolsVersion = 7.3; 413 | LastSwiftMigration = 0910; 414 | }; 415 | 0A9DED921CA7516700E81AEA = { 416 | CreatedOnToolsVersion = 7.3; 417 | LastSwiftMigration = 0910; 418 | }; 419 | 0A9DEDAC1CA7540A00E81AEA = { 420 | CreatedOnToolsVersion = 7.3; 421 | LastSwiftMigration = ""; 422 | }; 423 | 0A9DEDBB1CA834E900E81AEA = { 424 | CreatedOnToolsVersion = 7.3; 425 | LastSwiftMigration = 1020; 426 | }; 427 | 0A9DEDCA1CA8350500E81AEA = { 428 | CreatedOnToolsVersion = 7.3; 429 | LastSwiftMigration = 0910; 430 | }; 431 | 0A9DEDD91CA8353200E81AEA = { 432 | CreatedOnToolsVersion = 7.3; 433 | LastSwiftMigration = ""; 434 | }; 435 | }; 436 | }; 437 | buildConfigurationList = 0A9DED631CA7509100E81AEA /* Build configuration list for PBXProject "APNSubGroupOperationQueue" */; 438 | compatibilityVersion = "Xcode 3.2"; 439 | developmentRegion = en; 440 | hasScannedForEncodings = 0; 441 | knownRegions = ( 442 | en, 443 | Base, 444 | ); 445 | mainGroup = 0A9DED5F1CA7509100E81AEA; 446 | productRefGroup = 0A9DED6C1CA750D800E81AEA /* Products */; 447 | projectDirPath = ""; 448 | projectRoot = ""; 449 | targets = ( 450 | 0A9DED781CA7513A00E81AEA /* APNSubGroupOperationQueue iOS */, 451 | 0A9DED851CA7515800E81AEA /* APNSubGroupOperationQueue watchOS */, 452 | 0A9DED921CA7516700E81AEA /* APNSubGroupOperationQueue tvOS */, 453 | 0A9DEDAC1CA7540A00E81AEA /* APNSubGroupOperationQueue macOS */, 454 | 0A9DEDBB1CA834E900E81AEA /* APNSubGroupOperationQueue iOSTests */, 455 | 0A9DEDCA1CA8350500E81AEA /* APNSubGroupOperationQueue tvOSTests */, 456 | 0A9DEDD91CA8353200E81AEA /* APNSubGroupOperationQueue macOSTests */, 457 | ); 458 | }; 459 | /* End PBXProject section */ 460 | 461 | /* Begin PBXResourcesBuildPhase section */ 462 | 0A9DED771CA7513A00E81AEA /* Resources */ = { 463 | isa = PBXResourcesBuildPhase; 464 | buildActionMask = 2147483647; 465 | files = ( 466 | ); 467 | runOnlyForDeploymentPostprocessing = 0; 468 | }; 469 | 0A9DED841CA7515800E81AEA /* Resources */ = { 470 | isa = PBXResourcesBuildPhase; 471 | buildActionMask = 2147483647; 472 | files = ( 473 | ); 474 | runOnlyForDeploymentPostprocessing = 0; 475 | }; 476 | 0A9DED911CA7516700E81AEA /* Resources */ = { 477 | isa = PBXResourcesBuildPhase; 478 | buildActionMask = 2147483647; 479 | files = ( 480 | ); 481 | runOnlyForDeploymentPostprocessing = 0; 482 | }; 483 | 0A9DEDAB1CA7540A00E81AEA /* Resources */ = { 484 | isa = PBXResourcesBuildPhase; 485 | buildActionMask = 2147483647; 486 | files = ( 487 | ); 488 | runOnlyForDeploymentPostprocessing = 0; 489 | }; 490 | 0A9DEDBA1CA834E900E81AEA /* Resources */ = { 491 | isa = PBXResourcesBuildPhase; 492 | buildActionMask = 2147483647; 493 | files = ( 494 | ); 495 | runOnlyForDeploymentPostprocessing = 0; 496 | }; 497 | 0A9DEDC91CA8350500E81AEA /* Resources */ = { 498 | isa = PBXResourcesBuildPhase; 499 | buildActionMask = 2147483647; 500 | files = ( 501 | ); 502 | runOnlyForDeploymentPostprocessing = 0; 503 | }; 504 | 0A9DEDD81CA8353200E81AEA /* Resources */ = { 505 | isa = PBXResourcesBuildPhase; 506 | buildActionMask = 2147483647; 507 | files = ( 508 | ); 509 | runOnlyForDeploymentPostprocessing = 0; 510 | }; 511 | /* End PBXResourcesBuildPhase section */ 512 | 513 | /* Begin PBXSourcesBuildPhase section */ 514 | 0A9DED741CA7513A00E81AEA /* Sources */ = { 515 | isa = PBXSourcesBuildPhase; 516 | buildActionMask = 2147483647; 517 | files = ( 518 | 0A5DB88C1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */, 519 | 0A9DEDEC1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */, 520 | 0A5DB8911E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */, 521 | 0A752A5B214DB11700320E76 /* UnfairLock.swift in Sources */, 522 | 0A5DB8961E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */, 523 | ); 524 | runOnlyForDeploymentPostprocessing = 0; 525 | }; 526 | 0A9DED811CA7515800E81AEA /* Sources */ = { 527 | isa = PBXSourcesBuildPhase; 528 | buildActionMask = 2147483647; 529 | files = ( 530 | 0A5DB88D1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */, 531 | 0A9DEDED1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */, 532 | 0A5DB8921E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */, 533 | 0A752A5C214DB27B00320E76 /* UnfairLock.swift in Sources */, 534 | 0A5DB8971E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */, 535 | ); 536 | runOnlyForDeploymentPostprocessing = 0; 537 | }; 538 | 0A9DED8E1CA7516700E81AEA /* Sources */ = { 539 | isa = PBXSourcesBuildPhase; 540 | buildActionMask = 2147483647; 541 | files = ( 542 | 0A5DB88E1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */, 543 | 0A9DEDEE1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */, 544 | 0A5DB8931E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */, 545 | 0A752A5E214DB27C00320E76 /* UnfairLock.swift in Sources */, 546 | 0A5DB8981E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */, 547 | ); 548 | runOnlyForDeploymentPostprocessing = 0; 549 | }; 550 | 0A9DEDA81CA7540A00E81AEA /* Sources */ = { 551 | isa = PBXSourcesBuildPhase; 552 | buildActionMask = 2147483647; 553 | files = ( 554 | 0A5DB88F1E509E4500DD3AD9 /* DynamicSubGroupOperationQueue.swift in Sources */, 555 | 0A9DEDEF1CA83BC400E81AEA /* SubGroupOperationQueue.swift in Sources */, 556 | 0A5DB8941E509E5E00DD3AD9 /* CompletionOperation.swift in Sources */, 557 | 0A752A5D214DB27C00320E76 /* UnfairLock.swift in Sources */, 558 | 0A5DB8991E50B6BA00DD3AD9 /* OperationSubGroupMap.swift in Sources */, 559 | ); 560 | runOnlyForDeploymentPostprocessing = 0; 561 | }; 562 | 0A9DEDB81CA834E900E81AEA /* Sources */ = { 563 | isa = PBXSourcesBuildPhase; 564 | buildActionMask = 2147483647; 565 | files = ( 566 | 0A5DB89B1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */, 567 | 0A9DEE3D1CB15FB700E81AEA /* APNSubGroupOperationQueueTests.m in Sources */, 568 | 0A9DEDF21CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */, 569 | 0A5DB89F1E50CC3D00DD3AD9 /* TestUtils.swift in Sources */, 570 | ); 571 | runOnlyForDeploymentPostprocessing = 0; 572 | }; 573 | 0A9DEDC71CA8350500E81AEA /* Sources */ = { 574 | isa = PBXSourcesBuildPhase; 575 | buildActionMask = 2147483647; 576 | files = ( 577 | 0A5DB89C1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */, 578 | 0A9DEE3E1CB15FB700E81AEA /* APNSubGroupOperationQueueTests.m in Sources */, 579 | 0A9DEDF31CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */, 580 | 0A5DB8A01E50CC3D00DD3AD9 /* TestUtils.swift in Sources */, 581 | ); 582 | runOnlyForDeploymentPostprocessing = 0; 583 | }; 584 | 0A9DEDD61CA8353200E81AEA /* Sources */ = { 585 | isa = PBXSourcesBuildPhase; 586 | buildActionMask = 2147483647; 587 | files = ( 588 | 0A5DB89D1E50CBA400DD3AD9 /* DynamicSubGroupOperationQueueTests.swift in Sources */, 589 | 0A9DEE3F1CB15FB800E81AEA /* APNSubGroupOperationQueueTests.m in Sources */, 590 | 0A9DEDF41CA88B9F00E81AEA /* SubGroupOperationQueueTests.swift in Sources */, 591 | 0A5DB8A11E50CC3D00DD3AD9 /* TestUtils.swift in Sources */, 592 | ); 593 | runOnlyForDeploymentPostprocessing = 0; 594 | }; 595 | /* End PBXSourcesBuildPhase section */ 596 | 597 | /* Begin PBXTargetDependency section */ 598 | 0A9DEDC31CA834E900E81AEA /* PBXTargetDependency */ = { 599 | isa = PBXTargetDependency; 600 | target = 0A9DED781CA7513A00E81AEA /* APNSubGroupOperationQueue iOS */; 601 | targetProxy = 0A9DEDC21CA834E900E81AEA /* PBXContainerItemProxy */; 602 | }; 603 | 0A9DEDD21CA8350500E81AEA /* PBXTargetDependency */ = { 604 | isa = PBXTargetDependency; 605 | target = 0A9DED921CA7516700E81AEA /* APNSubGroupOperationQueue tvOS */; 606 | targetProxy = 0A9DEDD11CA8350500E81AEA /* PBXContainerItemProxy */; 607 | }; 608 | 0A9DEDE11CA8353300E81AEA /* PBXTargetDependency */ = { 609 | isa = PBXTargetDependency; 610 | target = 0A9DEDAC1CA7540A00E81AEA /* APNSubGroupOperationQueue macOS */; 611 | targetProxy = 0A9DEDE01CA8353300E81AEA /* PBXContainerItemProxy */; 612 | }; 613 | /* End PBXTargetDependency section */ 614 | 615 | /* Begin XCBuildConfiguration section */ 616 | 0A9DED641CA7509100E81AEA /* Debug */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | APPLICATION_EXTENSION_API_ONLY = NO; 620 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 621 | CLANG_WARN_BOOL_CONVERSION = YES; 622 | CLANG_WARN_COMMA = YES; 623 | CLANG_WARN_CONSTANT_CONVERSION = YES; 624 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 625 | CLANG_WARN_EMPTY_BODY = YES; 626 | CLANG_WARN_ENUM_CONVERSION = YES; 627 | CLANG_WARN_INFINITE_RECURSION = YES; 628 | CLANG_WARN_INT_CONVERSION = YES; 629 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 630 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 631 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 632 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 633 | CLANG_WARN_STRICT_PROTOTYPES = YES; 634 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 635 | CLANG_WARN_UNREACHABLE_CODE = YES; 636 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 637 | ENABLE_STRICT_OBJC_MSGSEND = YES; 638 | ENABLE_TESTABILITY = YES; 639 | GCC_NO_COMMON_BLOCKS = YES; 640 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 641 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 642 | GCC_WARN_UNDECLARED_SELECTOR = YES; 643 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 644 | GCC_WARN_UNUSED_FUNCTION = YES; 645 | GCC_WARN_UNUSED_VARIABLE = YES; 646 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 647 | MACOSX_DEPLOYMENT_TARGET = 10.12; 648 | ONLY_ACTIVE_ARCH = YES; 649 | SWIFT_VERSION = 4.0; 650 | TVOS_DEPLOYMENT_TARGET = 10.0; 651 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 652 | }; 653 | name = Debug; 654 | }; 655 | 0A9DED651CA7509100E81AEA /* Release */ = { 656 | isa = XCBuildConfiguration; 657 | buildSettings = { 658 | APPLICATION_EXTENSION_API_ONLY = NO; 659 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 660 | CLANG_WARN_BOOL_CONVERSION = YES; 661 | CLANG_WARN_COMMA = YES; 662 | CLANG_WARN_CONSTANT_CONVERSION = YES; 663 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 664 | CLANG_WARN_EMPTY_BODY = YES; 665 | CLANG_WARN_ENUM_CONVERSION = YES; 666 | CLANG_WARN_INFINITE_RECURSION = YES; 667 | CLANG_WARN_INT_CONVERSION = YES; 668 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 669 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 670 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 671 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 672 | CLANG_WARN_STRICT_PROTOTYPES = YES; 673 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 674 | CLANG_WARN_UNREACHABLE_CODE = YES; 675 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 676 | ENABLE_STRICT_OBJC_MSGSEND = YES; 677 | GCC_NO_COMMON_BLOCKS = YES; 678 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 679 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 680 | GCC_WARN_UNDECLARED_SELECTOR = YES; 681 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 682 | GCC_WARN_UNUSED_FUNCTION = YES; 683 | GCC_WARN_UNUSED_VARIABLE = YES; 684 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 685 | MACOSX_DEPLOYMENT_TARGET = 10.12; 686 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 687 | SWIFT_VERSION = 4.0; 688 | TVOS_DEPLOYMENT_TARGET = 10.0; 689 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 690 | }; 691 | name = Release; 692 | }; 693 | 0A9DED7F1CA7513A00E81AEA /* Debug */ = { 694 | isa = XCBuildConfiguration; 695 | buildSettings = { 696 | ALWAYS_SEARCH_USER_PATHS = NO; 697 | APPLICATION_EXTENSION_API_ONLY = YES; 698 | CLANG_ANALYZER_NONNULL = YES; 699 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 700 | CLANG_CXX_LIBRARY = "libc++"; 701 | CLANG_ENABLE_MODULES = YES; 702 | CLANG_ENABLE_OBJC_ARC = YES; 703 | CLANG_WARN_BOOL_CONVERSION = YES; 704 | CLANG_WARN_CONSTANT_CONVERSION = YES; 705 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 706 | CLANG_WARN_EMPTY_BODY = YES; 707 | CLANG_WARN_ENUM_CONVERSION = YES; 708 | CLANG_WARN_INT_CONVERSION = YES; 709 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 710 | CLANG_WARN_UNREACHABLE_CODE = YES; 711 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 712 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 713 | COPY_PHASE_STRIP = NO; 714 | CURRENT_PROJECT_VERSION = 1; 715 | DEBUG_INFORMATION_FORMAT = dwarf; 716 | DEFINES_MODULE = YES; 717 | DYLIB_COMPATIBILITY_VERSION = 1; 718 | DYLIB_CURRENT_VERSION = 1; 719 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 720 | ENABLE_STRICT_OBJC_MSGSEND = YES; 721 | ENABLE_TESTABILITY = YES; 722 | GCC_C_LANGUAGE_STANDARD = gnu99; 723 | GCC_DYNAMIC_NO_PIC = NO; 724 | GCC_NO_COMMON_BLOCKS = YES; 725 | GCC_OPTIMIZATION_LEVEL = 0; 726 | GCC_PREPROCESSOR_DEFINITIONS = ( 727 | "DEBUG=1", 728 | "$(inherited)", 729 | ); 730 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 731 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 732 | GCC_WARN_UNDECLARED_SELECTOR = YES; 733 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 734 | GCC_WARN_UNUSED_FUNCTION = YES; 735 | GCC_WARN_UNUSED_VARIABLE = YES; 736 | INFOPLIST_FILE = "Sources/Info-iOS.plist"; 737 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 738 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 739 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 740 | MTL_ENABLE_DEBUG_INFO = YES; 741 | ONLY_ACTIVE_ARCH = YES; 742 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-iOS"; 743 | PRODUCT_NAME = APNSubGroupOperationQueue; 744 | SDKROOT = iphoneos; 745 | SKIP_INSTALL = YES; 746 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 747 | SWIFT_VERSION = 5.0; 748 | TARGETED_DEVICE_FAMILY = "1,2"; 749 | VERSIONING_SYSTEM = "apple-generic"; 750 | VERSION_INFO_PREFIX = ""; 751 | }; 752 | name = Debug; 753 | }; 754 | 0A9DED801CA7513A00E81AEA /* Release */ = { 755 | isa = XCBuildConfiguration; 756 | buildSettings = { 757 | ALWAYS_SEARCH_USER_PATHS = NO; 758 | APPLICATION_EXTENSION_API_ONLY = YES; 759 | CLANG_ANALYZER_NONNULL = YES; 760 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 761 | CLANG_CXX_LIBRARY = "libc++"; 762 | CLANG_ENABLE_MODULES = YES; 763 | CLANG_ENABLE_OBJC_ARC = YES; 764 | CLANG_WARN_BOOL_CONVERSION = YES; 765 | CLANG_WARN_CONSTANT_CONVERSION = YES; 766 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 767 | CLANG_WARN_EMPTY_BODY = YES; 768 | CLANG_WARN_ENUM_CONVERSION = YES; 769 | CLANG_WARN_INT_CONVERSION = YES; 770 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 771 | CLANG_WARN_UNREACHABLE_CODE = YES; 772 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 773 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 774 | COPY_PHASE_STRIP = NO; 775 | CURRENT_PROJECT_VERSION = 1; 776 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 777 | DEFINES_MODULE = YES; 778 | DYLIB_COMPATIBILITY_VERSION = 1; 779 | DYLIB_CURRENT_VERSION = 1; 780 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 781 | ENABLE_NS_ASSERTIONS = NO; 782 | ENABLE_STRICT_OBJC_MSGSEND = YES; 783 | GCC_C_LANGUAGE_STANDARD = gnu99; 784 | GCC_NO_COMMON_BLOCKS = YES; 785 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 786 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 787 | GCC_WARN_UNDECLARED_SELECTOR = YES; 788 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 789 | GCC_WARN_UNUSED_FUNCTION = YES; 790 | GCC_WARN_UNUSED_VARIABLE = YES; 791 | INFOPLIST_FILE = "Sources/Info-iOS.plist"; 792 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 793 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 794 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 795 | MTL_ENABLE_DEBUG_INFO = NO; 796 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-iOS"; 797 | PRODUCT_NAME = APNSubGroupOperationQueue; 798 | SDKROOT = iphoneos; 799 | SKIP_INSTALL = YES; 800 | SWIFT_VERSION = 5.0; 801 | TARGETED_DEVICE_FAMILY = "1,2"; 802 | VALIDATE_PRODUCT = YES; 803 | VERSIONING_SYSTEM = "apple-generic"; 804 | VERSION_INFO_PREFIX = ""; 805 | }; 806 | name = Release; 807 | }; 808 | 0A9DED8C1CA7515800E81AEA /* Debug */ = { 809 | isa = XCBuildConfiguration; 810 | buildSettings = { 811 | ALWAYS_SEARCH_USER_PATHS = NO; 812 | APPLICATION_EXTENSION_API_ONLY = YES; 813 | CLANG_ANALYZER_NONNULL = YES; 814 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 815 | CLANG_CXX_LIBRARY = "libc++"; 816 | CLANG_ENABLE_MODULES = YES; 817 | CLANG_ENABLE_OBJC_ARC = YES; 818 | CLANG_WARN_BOOL_CONVERSION = YES; 819 | CLANG_WARN_CONSTANT_CONVERSION = YES; 820 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 821 | CLANG_WARN_EMPTY_BODY = YES; 822 | CLANG_WARN_ENUM_CONVERSION = YES; 823 | CLANG_WARN_INT_CONVERSION = YES; 824 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 825 | CLANG_WARN_UNREACHABLE_CODE = YES; 826 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 827 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 828 | COPY_PHASE_STRIP = NO; 829 | CURRENT_PROJECT_VERSION = 1; 830 | DEBUG_INFORMATION_FORMAT = dwarf; 831 | DEFINES_MODULE = YES; 832 | DYLIB_COMPATIBILITY_VERSION = 1; 833 | DYLIB_CURRENT_VERSION = 1; 834 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 835 | ENABLE_STRICT_OBJC_MSGSEND = YES; 836 | ENABLE_TESTABILITY = YES; 837 | GCC_C_LANGUAGE_STANDARD = gnu99; 838 | GCC_DYNAMIC_NO_PIC = NO; 839 | GCC_NO_COMMON_BLOCKS = YES; 840 | GCC_OPTIMIZATION_LEVEL = 0; 841 | GCC_PREPROCESSOR_DEFINITIONS = ( 842 | "DEBUG=1", 843 | "$(inherited)", 844 | ); 845 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 846 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 847 | GCC_WARN_UNDECLARED_SELECTOR = YES; 848 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 849 | GCC_WARN_UNUSED_FUNCTION = YES; 850 | GCC_WARN_UNUSED_VARIABLE = YES; 851 | INFOPLIST_FILE = "Sources/Info-watchOS.plist"; 852 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 853 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 854 | MTL_ENABLE_DEBUG_INFO = YES; 855 | ONLY_ACTIVE_ARCH = YES; 856 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-watchOS"; 857 | PRODUCT_NAME = APNSubGroupOperationQueue; 858 | SDKROOT = watchos; 859 | SKIP_INSTALL = YES; 860 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 861 | SWIFT_VERSION = 4.0; 862 | TARGETED_DEVICE_FAMILY = 4; 863 | VERSIONING_SYSTEM = "apple-generic"; 864 | VERSION_INFO_PREFIX = ""; 865 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 866 | }; 867 | name = Debug; 868 | }; 869 | 0A9DED8D1CA7515800E81AEA /* Release */ = { 870 | isa = XCBuildConfiguration; 871 | buildSettings = { 872 | ALWAYS_SEARCH_USER_PATHS = NO; 873 | APPLICATION_EXTENSION_API_ONLY = YES; 874 | CLANG_ANALYZER_NONNULL = YES; 875 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 876 | CLANG_CXX_LIBRARY = "libc++"; 877 | CLANG_ENABLE_MODULES = YES; 878 | CLANG_ENABLE_OBJC_ARC = YES; 879 | CLANG_WARN_BOOL_CONVERSION = YES; 880 | CLANG_WARN_CONSTANT_CONVERSION = YES; 881 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 882 | CLANG_WARN_EMPTY_BODY = YES; 883 | CLANG_WARN_ENUM_CONVERSION = YES; 884 | CLANG_WARN_INT_CONVERSION = YES; 885 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 886 | CLANG_WARN_UNREACHABLE_CODE = YES; 887 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 888 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 889 | COPY_PHASE_STRIP = NO; 890 | CURRENT_PROJECT_VERSION = 1; 891 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 892 | DEFINES_MODULE = YES; 893 | DYLIB_COMPATIBILITY_VERSION = 1; 894 | DYLIB_CURRENT_VERSION = 1; 895 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 896 | ENABLE_NS_ASSERTIONS = NO; 897 | ENABLE_STRICT_OBJC_MSGSEND = YES; 898 | GCC_C_LANGUAGE_STANDARD = gnu99; 899 | GCC_NO_COMMON_BLOCKS = YES; 900 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 901 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 902 | GCC_WARN_UNDECLARED_SELECTOR = YES; 903 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 904 | GCC_WARN_UNUSED_FUNCTION = YES; 905 | GCC_WARN_UNUSED_VARIABLE = YES; 906 | INFOPLIST_FILE = "Sources/Info-watchOS.plist"; 907 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 908 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 909 | MTL_ENABLE_DEBUG_INFO = NO; 910 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-watchOS"; 911 | PRODUCT_NAME = APNSubGroupOperationQueue; 912 | SDKROOT = watchos; 913 | SKIP_INSTALL = YES; 914 | SWIFT_VERSION = 4.0; 915 | TARGETED_DEVICE_FAMILY = 4; 916 | VALIDATE_PRODUCT = YES; 917 | VERSIONING_SYSTEM = "apple-generic"; 918 | VERSION_INFO_PREFIX = ""; 919 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 920 | }; 921 | name = Release; 922 | }; 923 | 0A9DED991CA7516700E81AEA /* Debug */ = { 924 | isa = XCBuildConfiguration; 925 | buildSettings = { 926 | ALWAYS_SEARCH_USER_PATHS = NO; 927 | APPLICATION_EXTENSION_API_ONLY = YES; 928 | CLANG_ANALYZER_NONNULL = YES; 929 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 930 | CLANG_CXX_LIBRARY = "libc++"; 931 | CLANG_ENABLE_MODULES = YES; 932 | CLANG_ENABLE_OBJC_ARC = YES; 933 | CLANG_WARN_BOOL_CONVERSION = YES; 934 | CLANG_WARN_CONSTANT_CONVERSION = YES; 935 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 936 | CLANG_WARN_EMPTY_BODY = YES; 937 | CLANG_WARN_ENUM_CONVERSION = YES; 938 | CLANG_WARN_INT_CONVERSION = YES; 939 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 940 | CLANG_WARN_UNREACHABLE_CODE = YES; 941 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 942 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 943 | COPY_PHASE_STRIP = NO; 944 | CURRENT_PROJECT_VERSION = 1; 945 | DEBUG_INFORMATION_FORMAT = dwarf; 946 | DEFINES_MODULE = YES; 947 | DYLIB_COMPATIBILITY_VERSION = 1; 948 | DYLIB_CURRENT_VERSION = 1; 949 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 950 | ENABLE_STRICT_OBJC_MSGSEND = YES; 951 | ENABLE_TESTABILITY = YES; 952 | GCC_C_LANGUAGE_STANDARD = gnu99; 953 | GCC_DYNAMIC_NO_PIC = NO; 954 | GCC_NO_COMMON_BLOCKS = YES; 955 | GCC_OPTIMIZATION_LEVEL = 0; 956 | GCC_PREPROCESSOR_DEFINITIONS = ( 957 | "DEBUG=1", 958 | "$(inherited)", 959 | ); 960 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 961 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 962 | GCC_WARN_UNDECLARED_SELECTOR = YES; 963 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 964 | GCC_WARN_UNUSED_FUNCTION = YES; 965 | GCC_WARN_UNUSED_VARIABLE = YES; 966 | INFOPLIST_FILE = "Sources/Info-tvOS.plist"; 967 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 968 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 969 | MTL_ENABLE_DEBUG_INFO = YES; 970 | ONLY_ACTIVE_ARCH = YES; 971 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-tvOS"; 972 | PRODUCT_NAME = APNSubGroupOperationQueue; 973 | SDKROOT = appletvos; 974 | SKIP_INSTALL = YES; 975 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 976 | SWIFT_VERSION = 4.0; 977 | TARGETED_DEVICE_FAMILY = 3; 978 | TVOS_DEPLOYMENT_TARGET = 10.0; 979 | VERSIONING_SYSTEM = "apple-generic"; 980 | VERSION_INFO_PREFIX = ""; 981 | }; 982 | name = Debug; 983 | }; 984 | 0A9DED9A1CA7516700E81AEA /* Release */ = { 985 | isa = XCBuildConfiguration; 986 | buildSettings = { 987 | ALWAYS_SEARCH_USER_PATHS = NO; 988 | APPLICATION_EXTENSION_API_ONLY = YES; 989 | CLANG_ANALYZER_NONNULL = YES; 990 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 991 | CLANG_CXX_LIBRARY = "libc++"; 992 | CLANG_ENABLE_MODULES = YES; 993 | CLANG_ENABLE_OBJC_ARC = YES; 994 | CLANG_WARN_BOOL_CONVERSION = YES; 995 | CLANG_WARN_CONSTANT_CONVERSION = YES; 996 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 997 | CLANG_WARN_EMPTY_BODY = YES; 998 | CLANG_WARN_ENUM_CONVERSION = YES; 999 | CLANG_WARN_INT_CONVERSION = YES; 1000 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1001 | CLANG_WARN_UNREACHABLE_CODE = YES; 1002 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1003 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 1004 | COPY_PHASE_STRIP = NO; 1005 | CURRENT_PROJECT_VERSION = 1; 1006 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1007 | DEFINES_MODULE = YES; 1008 | DYLIB_COMPATIBILITY_VERSION = 1; 1009 | DYLIB_CURRENT_VERSION = 1; 1010 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1011 | ENABLE_NS_ASSERTIONS = NO; 1012 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1013 | GCC_C_LANGUAGE_STANDARD = gnu99; 1014 | GCC_NO_COMMON_BLOCKS = YES; 1015 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1016 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1017 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1018 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1019 | GCC_WARN_UNUSED_FUNCTION = YES; 1020 | GCC_WARN_UNUSED_VARIABLE = YES; 1021 | INFOPLIST_FILE = "Sources/Info-tvOS.plist"; 1022 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1023 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1024 | MTL_ENABLE_DEBUG_INFO = NO; 1025 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-tvOS"; 1026 | PRODUCT_NAME = APNSubGroupOperationQueue; 1027 | SDKROOT = appletvos; 1028 | SKIP_INSTALL = YES; 1029 | SWIFT_VERSION = 4.0; 1030 | TARGETED_DEVICE_FAMILY = 3; 1031 | TVOS_DEPLOYMENT_TARGET = 10.0; 1032 | VALIDATE_PRODUCT = YES; 1033 | VERSIONING_SYSTEM = "apple-generic"; 1034 | VERSION_INFO_PREFIX = ""; 1035 | }; 1036 | name = Release; 1037 | }; 1038 | 0A9DEDB31CA7540B00E81AEA /* Debug */ = { 1039 | isa = XCBuildConfiguration; 1040 | buildSettings = { 1041 | ALWAYS_SEARCH_USER_PATHS = NO; 1042 | APPLICATION_EXTENSION_API_ONLY = YES; 1043 | CLANG_ANALYZER_NONNULL = YES; 1044 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1045 | CLANG_CXX_LIBRARY = "libc++"; 1046 | CLANG_ENABLE_MODULES = YES; 1047 | CLANG_ENABLE_OBJC_ARC = YES; 1048 | CLANG_WARN_BOOL_CONVERSION = YES; 1049 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1050 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1051 | CLANG_WARN_EMPTY_BODY = YES; 1052 | CLANG_WARN_ENUM_CONVERSION = YES; 1053 | CLANG_WARN_INT_CONVERSION = YES; 1054 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1055 | CLANG_WARN_UNREACHABLE_CODE = YES; 1056 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1057 | CODE_SIGN_IDENTITY = ""; 1058 | COMBINE_HIDPI_IMAGES = YES; 1059 | COPY_PHASE_STRIP = NO; 1060 | CURRENT_PROJECT_VERSION = 1; 1061 | DEBUG_INFORMATION_FORMAT = dwarf; 1062 | DEFINES_MODULE = YES; 1063 | DYLIB_COMPATIBILITY_VERSION = 1; 1064 | DYLIB_CURRENT_VERSION = 1; 1065 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1066 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1067 | ENABLE_TESTABILITY = YES; 1068 | FRAMEWORK_VERSION = A; 1069 | GCC_C_LANGUAGE_STANDARD = gnu99; 1070 | GCC_DYNAMIC_NO_PIC = NO; 1071 | GCC_NO_COMMON_BLOCKS = YES; 1072 | GCC_OPTIMIZATION_LEVEL = 0; 1073 | GCC_PREPROCESSOR_DEFINITIONS = ( 1074 | "DEBUG=1", 1075 | "$(inherited)", 1076 | ); 1077 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1078 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1079 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1080 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1081 | GCC_WARN_UNUSED_FUNCTION = YES; 1082 | GCC_WARN_UNUSED_VARIABLE = YES; 1083 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-macOS.plist"; 1084 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1085 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1086 | MACOSX_DEPLOYMENT_TARGET = 10.12; 1087 | MTL_ENABLE_DEBUG_INFO = YES; 1088 | ONLY_ACTIVE_ARCH = YES; 1089 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-macOS"; 1090 | PRODUCT_NAME = APNSubGroupOperationQueue; 1091 | SDKROOT = macosx; 1092 | SKIP_INSTALL = YES; 1093 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1094 | SWIFT_VERSION = 4.2; 1095 | VERSIONING_SYSTEM = "apple-generic"; 1096 | VERSION_INFO_PREFIX = ""; 1097 | }; 1098 | name = Debug; 1099 | }; 1100 | 0A9DEDB41CA7540B00E81AEA /* Release */ = { 1101 | isa = XCBuildConfiguration; 1102 | buildSettings = { 1103 | ALWAYS_SEARCH_USER_PATHS = NO; 1104 | APPLICATION_EXTENSION_API_ONLY = YES; 1105 | CLANG_ANALYZER_NONNULL = YES; 1106 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1107 | CLANG_CXX_LIBRARY = "libc++"; 1108 | CLANG_ENABLE_MODULES = YES; 1109 | CLANG_ENABLE_OBJC_ARC = YES; 1110 | CLANG_WARN_BOOL_CONVERSION = YES; 1111 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1112 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1113 | CLANG_WARN_EMPTY_BODY = YES; 1114 | CLANG_WARN_ENUM_CONVERSION = YES; 1115 | CLANG_WARN_INT_CONVERSION = YES; 1116 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1117 | CLANG_WARN_UNREACHABLE_CODE = YES; 1118 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1119 | CODE_SIGN_IDENTITY = ""; 1120 | COMBINE_HIDPI_IMAGES = YES; 1121 | COPY_PHASE_STRIP = NO; 1122 | CURRENT_PROJECT_VERSION = 1; 1123 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1124 | DEFINES_MODULE = YES; 1125 | DYLIB_COMPATIBILITY_VERSION = 1; 1126 | DYLIB_CURRENT_VERSION = 1; 1127 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1128 | ENABLE_NS_ASSERTIONS = NO; 1129 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1130 | FRAMEWORK_VERSION = A; 1131 | GCC_C_LANGUAGE_STANDARD = gnu99; 1132 | GCC_NO_COMMON_BLOCKS = YES; 1133 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1134 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1135 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1136 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1137 | GCC_WARN_UNUSED_FUNCTION = YES; 1138 | GCC_WARN_UNUSED_VARIABLE = YES; 1139 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-macOS.plist"; 1140 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1141 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1142 | MACOSX_DEPLOYMENT_TARGET = 10.12; 1143 | MTL_ENABLE_DEBUG_INFO = NO; 1144 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-macOS"; 1145 | PRODUCT_NAME = APNSubGroupOperationQueue; 1146 | SDKROOT = macosx; 1147 | SKIP_INSTALL = YES; 1148 | SWIFT_VERSION = 4.2; 1149 | VERSIONING_SYSTEM = "apple-generic"; 1150 | VERSION_INFO_PREFIX = ""; 1151 | }; 1152 | name = Release; 1153 | }; 1154 | 0A9DEDC41CA834E900E81AEA /* Debug */ = { 1155 | isa = XCBuildConfiguration; 1156 | buildSettings = { 1157 | ALWAYS_SEARCH_USER_PATHS = NO; 1158 | CLANG_ANALYZER_NONNULL = YES; 1159 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1160 | CLANG_CXX_LIBRARY = "libc++"; 1161 | CLANG_ENABLE_MODULES = YES; 1162 | CLANG_ENABLE_OBJC_ARC = YES; 1163 | CLANG_WARN_BOOL_CONVERSION = YES; 1164 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1165 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1166 | CLANG_WARN_EMPTY_BODY = YES; 1167 | CLANG_WARN_ENUM_CONVERSION = YES; 1168 | CLANG_WARN_INT_CONVERSION = YES; 1169 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1170 | CLANG_WARN_UNREACHABLE_CODE = YES; 1171 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1172 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1173 | COPY_PHASE_STRIP = NO; 1174 | DEBUG_INFORMATION_FORMAT = dwarf; 1175 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1176 | ENABLE_TESTABILITY = YES; 1177 | GCC_C_LANGUAGE_STANDARD = gnu99; 1178 | GCC_DYNAMIC_NO_PIC = NO; 1179 | GCC_NO_COMMON_BLOCKS = YES; 1180 | GCC_OPTIMIZATION_LEVEL = 0; 1181 | GCC_PREPROCESSOR_DEFINITIONS = ( 1182 | "DEBUG=1", 1183 | "$(inherited)", 1184 | ); 1185 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1186 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1187 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1188 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1189 | GCC_WARN_UNUSED_FUNCTION = YES; 1190 | GCC_WARN_UNUSED_VARIABLE = YES; 1191 | INFOPLIST_FILE = "Tests/Info-iOS.plist"; 1192 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1193 | MTL_ENABLE_DEBUG_INFO = YES; 1194 | ONLY_ACTIVE_ARCH = YES; 1195 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-iOSTests"; 1196 | PRODUCT_NAME = "$(TARGET_NAME)"; 1197 | SDKROOT = iphoneos; 1198 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1199 | SWIFT_VERSION = 5.0; 1200 | }; 1201 | name = Debug; 1202 | }; 1203 | 0A9DEDC51CA834E900E81AEA /* Release */ = { 1204 | isa = XCBuildConfiguration; 1205 | buildSettings = { 1206 | ALWAYS_SEARCH_USER_PATHS = NO; 1207 | CLANG_ANALYZER_NONNULL = YES; 1208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1209 | CLANG_CXX_LIBRARY = "libc++"; 1210 | CLANG_ENABLE_MODULES = YES; 1211 | CLANG_ENABLE_OBJC_ARC = YES; 1212 | CLANG_WARN_BOOL_CONVERSION = YES; 1213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1215 | CLANG_WARN_EMPTY_BODY = YES; 1216 | CLANG_WARN_ENUM_CONVERSION = YES; 1217 | CLANG_WARN_INT_CONVERSION = YES; 1218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1219 | CLANG_WARN_UNREACHABLE_CODE = YES; 1220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1221 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1222 | COPY_PHASE_STRIP = NO; 1223 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1224 | ENABLE_NS_ASSERTIONS = NO; 1225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1226 | GCC_C_LANGUAGE_STANDARD = gnu99; 1227 | GCC_NO_COMMON_BLOCKS = YES; 1228 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1229 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1230 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1231 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1232 | GCC_WARN_UNUSED_FUNCTION = YES; 1233 | GCC_WARN_UNUSED_VARIABLE = YES; 1234 | INFOPLIST_FILE = "Tests/Info-iOS.plist"; 1235 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1236 | MTL_ENABLE_DEBUG_INFO = NO; 1237 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-iOSTests"; 1238 | PRODUCT_NAME = "$(TARGET_NAME)"; 1239 | SDKROOT = iphoneos; 1240 | SWIFT_VERSION = 5.0; 1241 | VALIDATE_PRODUCT = YES; 1242 | }; 1243 | name = Release; 1244 | }; 1245 | 0A9DEDD41CA8350500E81AEA /* Debug */ = { 1246 | isa = XCBuildConfiguration; 1247 | buildSettings = { 1248 | ALWAYS_SEARCH_USER_PATHS = NO; 1249 | CLANG_ANALYZER_NONNULL = YES; 1250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1251 | CLANG_CXX_LIBRARY = "libc++"; 1252 | CLANG_ENABLE_MODULES = YES; 1253 | CLANG_ENABLE_OBJC_ARC = YES; 1254 | CLANG_WARN_BOOL_CONVERSION = YES; 1255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1257 | CLANG_WARN_EMPTY_BODY = YES; 1258 | CLANG_WARN_ENUM_CONVERSION = YES; 1259 | CLANG_WARN_INT_CONVERSION = YES; 1260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1261 | CLANG_WARN_UNREACHABLE_CODE = YES; 1262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1263 | COPY_PHASE_STRIP = NO; 1264 | DEBUG_INFORMATION_FORMAT = dwarf; 1265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1266 | ENABLE_TESTABILITY = YES; 1267 | GCC_C_LANGUAGE_STANDARD = gnu99; 1268 | GCC_DYNAMIC_NO_PIC = NO; 1269 | GCC_NO_COMMON_BLOCKS = YES; 1270 | GCC_OPTIMIZATION_LEVEL = 0; 1271 | GCC_PREPROCESSOR_DEFINITIONS = ( 1272 | "DEBUG=1", 1273 | "$(inherited)", 1274 | ); 1275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1276 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1277 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1278 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1279 | GCC_WARN_UNUSED_FUNCTION = YES; 1280 | GCC_WARN_UNUSED_VARIABLE = YES; 1281 | INFOPLIST_FILE = "Tests/Info-tvOS.plist"; 1282 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1283 | MTL_ENABLE_DEBUG_INFO = YES; 1284 | ONLY_ACTIVE_ARCH = YES; 1285 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-tvOSTests"; 1286 | PRODUCT_NAME = "$(TARGET_NAME)"; 1287 | SDKROOT = appletvos; 1288 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1289 | SWIFT_VERSION = 4.0; 1290 | }; 1291 | name = Debug; 1292 | }; 1293 | 0A9DEDD51CA8350500E81AEA /* Release */ = { 1294 | isa = XCBuildConfiguration; 1295 | buildSettings = { 1296 | ALWAYS_SEARCH_USER_PATHS = NO; 1297 | CLANG_ANALYZER_NONNULL = YES; 1298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1299 | CLANG_CXX_LIBRARY = "libc++"; 1300 | CLANG_ENABLE_MODULES = YES; 1301 | CLANG_ENABLE_OBJC_ARC = YES; 1302 | CLANG_WARN_BOOL_CONVERSION = YES; 1303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1304 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1305 | CLANG_WARN_EMPTY_BODY = YES; 1306 | CLANG_WARN_ENUM_CONVERSION = YES; 1307 | CLANG_WARN_INT_CONVERSION = YES; 1308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1309 | CLANG_WARN_UNREACHABLE_CODE = YES; 1310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1311 | COPY_PHASE_STRIP = NO; 1312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1313 | ENABLE_NS_ASSERTIONS = NO; 1314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1315 | GCC_C_LANGUAGE_STANDARD = gnu99; 1316 | GCC_NO_COMMON_BLOCKS = YES; 1317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1321 | GCC_WARN_UNUSED_FUNCTION = YES; 1322 | GCC_WARN_UNUSED_VARIABLE = YES; 1323 | INFOPLIST_FILE = "Tests/Info-tvOS.plist"; 1324 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1325 | MTL_ENABLE_DEBUG_INFO = NO; 1326 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-tvOSTests"; 1327 | PRODUCT_NAME = "$(TARGET_NAME)"; 1328 | SDKROOT = appletvos; 1329 | SWIFT_VERSION = 4.0; 1330 | VALIDATE_PRODUCT = YES; 1331 | }; 1332 | name = Release; 1333 | }; 1334 | 0A9DEDE31CA8353300E81AEA /* Debug */ = { 1335 | isa = XCBuildConfiguration; 1336 | buildSettings = { 1337 | ALWAYS_SEARCH_USER_PATHS = NO; 1338 | CLANG_ANALYZER_NONNULL = YES; 1339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1340 | CLANG_CXX_LIBRARY = "libc++"; 1341 | CLANG_ENABLE_MODULES = YES; 1342 | CLANG_ENABLE_OBJC_ARC = YES; 1343 | CLANG_WARN_BOOL_CONVERSION = YES; 1344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1346 | CLANG_WARN_EMPTY_BODY = YES; 1347 | CLANG_WARN_ENUM_CONVERSION = YES; 1348 | CLANG_WARN_INT_CONVERSION = YES; 1349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1350 | CLANG_WARN_UNREACHABLE_CODE = YES; 1351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1352 | CODE_SIGN_IDENTITY = "-"; 1353 | COMBINE_HIDPI_IMAGES = YES; 1354 | COPY_PHASE_STRIP = NO; 1355 | DEBUG_INFORMATION_FORMAT = dwarf; 1356 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1357 | ENABLE_TESTABILITY = YES; 1358 | GCC_C_LANGUAGE_STANDARD = gnu99; 1359 | GCC_DYNAMIC_NO_PIC = NO; 1360 | GCC_NO_COMMON_BLOCKS = YES; 1361 | GCC_OPTIMIZATION_LEVEL = 0; 1362 | GCC_PREPROCESSOR_DEFINITIONS = ( 1363 | "DEBUG=1", 1364 | "$(inherited)", 1365 | ); 1366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1370 | GCC_WARN_UNUSED_FUNCTION = YES; 1371 | GCC_WARN_UNUSED_VARIABLE = YES; 1372 | INFOPLIST_FILE = "Tests/Info-OSX.plist"; 1373 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1374 | MTL_ENABLE_DEBUG_INFO = YES; 1375 | ONLY_ACTIVE_ARCH = YES; 1376 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-OSXTests"; 1377 | PRODUCT_NAME = "$(TARGET_NAME)"; 1378 | SDKROOT = macosx; 1379 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1380 | SWIFT_VERSION = 4.2; 1381 | }; 1382 | name = Debug; 1383 | }; 1384 | 0A9DEDE41CA8353300E81AEA /* Release */ = { 1385 | isa = XCBuildConfiguration; 1386 | buildSettings = { 1387 | ALWAYS_SEARCH_USER_PATHS = NO; 1388 | CLANG_ANALYZER_NONNULL = YES; 1389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1390 | CLANG_CXX_LIBRARY = "libc++"; 1391 | CLANG_ENABLE_MODULES = YES; 1392 | CLANG_ENABLE_OBJC_ARC = YES; 1393 | CLANG_WARN_BOOL_CONVERSION = YES; 1394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1396 | CLANG_WARN_EMPTY_BODY = YES; 1397 | CLANG_WARN_ENUM_CONVERSION = YES; 1398 | CLANG_WARN_INT_CONVERSION = YES; 1399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1400 | CLANG_WARN_UNREACHABLE_CODE = YES; 1401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1402 | CODE_SIGN_IDENTITY = "-"; 1403 | COMBINE_HIDPI_IMAGES = YES; 1404 | COPY_PHASE_STRIP = NO; 1405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1406 | ENABLE_NS_ASSERTIONS = NO; 1407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1408 | GCC_C_LANGUAGE_STANDARD = gnu99; 1409 | GCC_NO_COMMON_BLOCKS = YES; 1410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1414 | GCC_WARN_UNUSED_FUNCTION = YES; 1415 | GCC_WARN_UNUSED_VARIABLE = YES; 1416 | INFOPLIST_FILE = "Tests/Info-OSX.plist"; 1417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1418 | MTL_ENABLE_DEBUG_INFO = NO; 1419 | PRODUCT_BUNDLE_IDENTIFIER = "com.p4checo.APNSubGroupOperationQueue-OSXTests"; 1420 | PRODUCT_NAME = "$(TARGET_NAME)"; 1421 | SDKROOT = macosx; 1422 | SWIFT_VERSION = 4.2; 1423 | }; 1424 | name = Release; 1425 | }; 1426 | /* End XCBuildConfiguration section */ 1427 | 1428 | /* Begin XCConfigurationList section */ 1429 | 0A9DED631CA7509100E81AEA /* Build configuration list for PBXProject "APNSubGroupOperationQueue" */ = { 1430 | isa = XCConfigurationList; 1431 | buildConfigurations = ( 1432 | 0A9DED641CA7509100E81AEA /* Debug */, 1433 | 0A9DED651CA7509100E81AEA /* Release */, 1434 | ); 1435 | defaultConfigurationIsVisible = 0; 1436 | defaultConfigurationName = Release; 1437 | }; 1438 | 0A9DED7E1CA7513A00E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue iOS" */ = { 1439 | isa = XCConfigurationList; 1440 | buildConfigurations = ( 1441 | 0A9DED7F1CA7513A00E81AEA /* Debug */, 1442 | 0A9DED801CA7513A00E81AEA /* Release */, 1443 | ); 1444 | defaultConfigurationIsVisible = 0; 1445 | defaultConfigurationName = Release; 1446 | }; 1447 | 0A9DED8B1CA7515800E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue watchOS" */ = { 1448 | isa = XCConfigurationList; 1449 | buildConfigurations = ( 1450 | 0A9DED8C1CA7515800E81AEA /* Debug */, 1451 | 0A9DED8D1CA7515800E81AEA /* Release */, 1452 | ); 1453 | defaultConfigurationIsVisible = 0; 1454 | defaultConfigurationName = Release; 1455 | }; 1456 | 0A9DED981CA7516700E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue tvOS" */ = { 1457 | isa = XCConfigurationList; 1458 | buildConfigurations = ( 1459 | 0A9DED991CA7516700E81AEA /* Debug */, 1460 | 0A9DED9A1CA7516700E81AEA /* Release */, 1461 | ); 1462 | defaultConfigurationIsVisible = 0; 1463 | defaultConfigurationName = Release; 1464 | }; 1465 | 0A9DEDB21CA7540B00E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue macOS" */ = { 1466 | isa = XCConfigurationList; 1467 | buildConfigurations = ( 1468 | 0A9DEDB31CA7540B00E81AEA /* Debug */, 1469 | 0A9DEDB41CA7540B00E81AEA /* Release */, 1470 | ); 1471 | defaultConfigurationIsVisible = 0; 1472 | defaultConfigurationName = Release; 1473 | }; 1474 | 0A9DEDC61CA834E900E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue iOSTests" */ = { 1475 | isa = XCConfigurationList; 1476 | buildConfigurations = ( 1477 | 0A9DEDC41CA834E900E81AEA /* Debug */, 1478 | 0A9DEDC51CA834E900E81AEA /* Release */, 1479 | ); 1480 | defaultConfigurationIsVisible = 0; 1481 | defaultConfigurationName = Release; 1482 | }; 1483 | 0A9DEDD31CA8350500E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue tvOSTests" */ = { 1484 | isa = XCConfigurationList; 1485 | buildConfigurations = ( 1486 | 0A9DEDD41CA8350500E81AEA /* Debug */, 1487 | 0A9DEDD51CA8350500E81AEA /* Release */, 1488 | ); 1489 | defaultConfigurationIsVisible = 0; 1490 | defaultConfigurationName = Release; 1491 | }; 1492 | 0A9DEDE21CA8353300E81AEA /* Build configuration list for PBXNativeTarget "APNSubGroupOperationQueue macOSTests" */ = { 1493 | isa = XCConfigurationList; 1494 | buildConfigurations = ( 1495 | 0A9DEDE31CA8353300E81AEA /* Debug */, 1496 | 0A9DEDE41CA8353300E81AEA /* Release */, 1497 | ); 1498 | defaultConfigurationIsVisible = 0; 1499 | defaultConfigurationName = Release; 1500 | }; 1501 | /* End XCConfigurationList section */ 1502 | }; 1503 | rootObject = 0A9DED601CA7509100E81AEA /* Project object */; 1504 | } 1505 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/xcshareddata/xcschemes/APNSubGroupOperationQueue iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/xcshareddata/xcschemes/APNSubGroupOperationQueue macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/xcshareddata/xcschemes/APNSubGroupOperationQueue tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcodeproj/xcshareddata/xcschemes/APNSubGroupOperationQueue watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /APNSubGroupOperationQueue.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributions are welcomed for this project! Nothing is off limits. 4 | 5 | If you found a problem, [file an issue](https://github.com/p4checo/APNSubGroupOperationQueue/issues/new). 6 | 7 | If you just want to contribute, feel free to look through the [issues 8 | list](https://github.com/p4checo/APNSubGroupOperationQueue/issues), or 9 | simply submit a PR with your idea! 10 | 11 | ## Reporting Bugs 12 | 13 | As swift evolves, this project may become out date. Feel free to file 14 | issues to report problems. Be sure to include: 15 | 16 | - What platform are you building for? (eg - iOS 9.3, Xcode 7.3) 17 | - What package manager are you using? (eg - Cocoapods 0.39.0) 18 | - Swift Package Manager based on what version of swift you're using 19 | - Cocoapods version can be retrieved via: `pod --version` 20 | - Carthage version can be retrieved via: `carthage version` 21 | - What is your target configuration? (iOS App, OS X Framework, etc.) 22 | 23 | ## Pull Requests 24 | 25 | Please try to minimize the amount of commits in your pull request. 26 | The goal is to keep the history readable. 27 | 28 | Always rebase your changes onto master before submitting your PR's. 29 | 30 | Please write [good](http://chris.beams.io/posts/git-commit/) commit messages: 31 | 32 | - Separate subject from body with a blank line 33 | - Limit the subject line to 50 characters 34 | - Capitalize the subject line 35 | - Do not end the subject line with a period 36 | - Use the imperative mood in the subject line 37 | - Wrap the body at 72 characters 38 | - Use the body to explain what and why vs. how 39 | 40 | If you need to modify/squash existing commits you've made, use [rebase 41 | interactive](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History). 42 | 43 | If your PR includes Objective-C code, please apply [`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) to it. 44 | You can use the awesome [ClangFormat-Xcode](https://github.com/travisjeffery/ClangFormat-Xcode) plugin to make it easier. 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 André Pacheco Neves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: iOS macOS tvOS watchOS 2 | 3 | iOS: 4 | set -o pipefail && xcodebuild test -project APNSubGroupOperationQueue.xcodeproj -scheme 'APNSubGroupOperationQueue iOS' -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 6s" -enableCodeCoverage YES | xcpretty 5 | 6 | macOS: 7 | set -o pipefail && xcodebuild test -project APNSubGroupOperationQueue.xcodeproj -scheme 'APNSubGroupOperationQueue macOS' | xcpretty 8 | 9 | tvOS: 10 | set -o pipefail && xcodebuild test -project APNSubGroupOperationQueue.xcodeproj -scheme 'APNSubGroupOperationQueue tvOS' -sdk appletvsimulator -destination "platform=tvOS Simulator,name=Apple TV 4K" | xcpretty 11 | 12 | watchOS: 13 | set -o pipefail && xcodebuild -project APNSubGroupOperationQueue.xcodeproj -scheme 'APNSubGroupOperationQueue watchOS' -sdk watchsimulator -destination "platform=watchOS Simulator,name=Apple Watch - 42mm" | xcpretty 14 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "APNSubGroupOperationQueue" 5 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # APNSubGroupOperationQueue 2 | [![license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/p4checo/APNSubGroupOperationQueue/master/LICENSE) 3 | [![release](https://img.shields.io/github/release/p4checo/APNSubGroupOperationQueue.svg)](https://github.com/p4checo/APNSubGroupOperationQueue/releases) 4 | ![platforms](https://img.shields.io/badge/platform-iOS%20%7C%20OS%20X%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg) 5 | [![Build Status](https://travis-ci.org/p4checo/APNSubGroupOperationQueue.svg?branch=master)](https://travis-ci.org/p4checo/APNSubGroupOperationQueue) 6 | [![codecov.io](https://codecov.io/github/p4checo/APNSubGroupOperationQueue/coverage.svg?branch=master)](https://codecov.io/github/p4checo/APNSubGroupOperationQueue?branch=master) 7 | [![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/APNSubGroupOperationQueue.svg)](http://cocoadocs.org/docsets/APNSubGroupOperationQueue) 8 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 9 | [![CocoaPods](https://img.shields.io/cocoapods/v/APNSubGroupOperationQueue.svg)](https://cocoapods.org/) 10 | [![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://developer.apple.com/swift/) 11 | 12 | Swift & Obj-C µFramework consisting of `NSOperationQueue` subclasses which allow scheduling operations in serial subgroups inside concurrent queues. 13 | 14 | In some scenarios, operations scheduled in an `NSOperationQueue` depend on a subset of other operations and should be processed in order, but don't depend on the remaining operations in the queue. 15 | 16 | So far, this could be solved by: 17 | - using separate queues for each subset of dependent operations (can become unmanageable, wasteful) 18 | - defining the queue as serial (sub-optimal performance) 19 | 20 | With this µFramework, a single operation queue can be used to schedule all operations and obtain the best of both worlds. 21 | 22 | Dependent operations are grouped into "subgroups" which are guaranteed to be processed in a serial fashion inside each subgroup, while operations from other subgroups are processed concurrently (while serial inside their own subgroup) and regular operations (i.e. without defined subgroup) are processed concurrently with all others. This is done by leveraging `NSOperation`s dependencies and using an auxiliary data structure to store all subgroups' operations. 23 | 24 | ## Use 25 | 26 | ### Swift 27 | 28 | #### Single SubGroup Key type 29 | ```swift 30 | @import APNSubGroupOperationQueue 31 | 32 | let subGroupQueue = SubGroupOperationQueue() 33 | 34 | // schedule operations in subgroups "A", "B" and "C" 35 | // these will run serially inside each subgroup, but concurrently with other subgroups' operations 36 | 37 | subGroupQueue.addOperation(opA1, withKey: "A") 38 | subGroupQueue.addOperation(opA2, withKey: "A") 39 | subGroupQueue.addOperation(opA3, withKey: "A") 40 | 41 | subGroupQueue.addOperations([opB1, opB2, opB3], withKey: "B") 42 | 43 | subGroupQueue.addOperation({ /* opC1 */ }, withKey: "C") 44 | subGroupQueue.addOperation({ /* opC2 */ }, withKey: "C") 45 | subGroupQueue.addOperation({ /* opC3 */ }, withKey: "C") 46 | 47 | // query current subgroup's operations (a snapshot) 48 | let aOps = subGroupQueue["A"] 49 | let bOps = subGroupQueue["B"] 50 | let cOps = subGroupQueue.subGroupOperations(forKey: "C") 51 | ``` 52 | 53 | #### Multiple SubGroup Key types (via `AnyHashable`) 54 | ```swift 55 | @import APNSubGroupOperationQueue 56 | 57 | let dynamicSubGroupQueue = SubGroupQueue // or simply a `DynamicSubGroupOperationQueue` 58 | 59 | dynamicSubGroupQueue.addOperation(opX1, withKey: "X") 60 | dynamicSubGroupQueue.addOperation(opX2, withKey: "X") 61 | dynamicSubGroupQueue.addOperation(opX3, withKey: "X") 62 | 63 | dynamicSubGroupQueue.addOperations([opN1, opN2, opN3], withKey: 1337) 64 | 65 | let date = Date() 66 | dynamicSubGroupQueue.addOperation({ /* opD1 */ }, withKey: date) 67 | dynamicSubGroupQueue.addOperation({ /* opD2 */ }, withKey: date) 68 | dynamicSubGroupQueue.addOperation({ /* opD3 */ }, withKey: date) 69 | 70 | // query current subgroup's operations (a snapshot) 71 | let xOps = subGroupQueue["X"] 72 | let nOps = subGroupQueue[1337] 73 | let dOps = subGroupQueue.subGroupOperations(forKey: date) 74 | ``` 75 | 76 | ### Objective-C 77 | ```objc 78 | @import APNSubGroupOperationQueue; 79 | 80 | APNSubGroupOperationQueue *subGroupQueue = [APNSubGroupOperationQueue new]; 81 | 82 | // schedule operations in subgroups "A", "B" and "C" 83 | // these will run serially inside each subgroup, but concurrently with other subgroups' operations 84 | [subGroupQueue addOperation:opA1 withKey:@"A"]; 85 | [subGroupQueue addOperation:opA2 withKey:@"A"]; 86 | [subGroupQueue addOperation:opA2 withKey:@"A"]; 87 | 88 | [subGroupQueue addOperations::@[opB1, opB2, opB3] withKey:@"B" waitUntilFinished:false]; 89 | 90 | [subGroupQueue addOperationWithBlock:^{ /* opC1 */ } andKey:@"C"]; 91 | [subGroupQueue addOperationWithBlock:^{ /* opC2 */ } andKey:@"C"]; 92 | [subGroupQueue addOperationWithBlock:^{ /* opC3 */ } andKey:@"C"]; 93 | 94 | // query current subgroup's operations (a snapshot) 95 | NSArray *aOps = [subGroupQueue subGroupOperationsForKey:@"A"]; 96 | NSArray *bOps = [subGroupQueue subGroupOperationsForKey:@"B"]; 97 | NSArray *cOps = [subGroupQueue subGroupOperationsForKey:@"C"]; 98 | 99 | // Objective-C uses a `DynamicSubGroupOperationQueue` which allows a more flexible usage, since keys only need to be `NSObject`'s (`AnyHashable`) 100 | [subGroupQueue addOperations:@[opN1, opN2, opN3] withKey:@1337 waitUntilFinished:false]; 101 | 102 | NSDate *date = [NSDate date]; 103 | [subGroupQueue addOperationWithBlock:^{ /* opD1 */ } andKey:date]; 104 | [subGroupQueue addOperationWithBlock:^{ /* opD2 */ } andKey:date]; 105 | ``` 106 | ## Compatibility 107 | 108 | ### `4.x` (current) 109 | 110 | - iOS 10.0+, macOS 10.12, tvOS 10.0+, watchOS 3.0+ 111 | - Xcode 10.2+ 112 | - Swift 5.0 113 | 114 | ### `3.x` 115 | 116 | - iOS 10.0+, macOS 10.12, tvOS 10.0+, watchOS 3.0+ 117 | - Xcode 10 118 | - Swift 4.2 119 | 120 | ### `2.x` 121 | 122 | - iOS 8.0+, macOS 10.9+, tvOS 9.0+, watchOS 2.0+ 123 | - `2.3.0` 124 | + Xcode 9.4 125 | + Swift 4.1 126 | - `2.2.0` 127 | + Xcode 9 128 | + Swift 4.0 129 | - `2.1.0` 130 | + Xcode 8 131 | + Swift 3 132 | 133 | 134 | ## Integration 135 | 136 | ### CocoaPods 137 | Add APNSubGroupOperationQueue to your `Podfile` and run `pod install`: 138 | 139 | ```ruby 140 | # CocoaPods 141 | pod 'APNSubGroupOperationQueue', '~> 4.0' 142 | ``` 143 | 144 | ### Carthage 145 | 146 | Add APNSubGroupOperationQueue to your `Cartfile` (package dependency) or `Cartfile.private` 147 | (development dependency): 148 | 149 | ``` 150 | github "p4checo/APNSubGroupOperationQueue" ~> 4.0 151 | ``` 152 | 153 | ### Swift Package Manager 154 | 155 | Add APNSubGroupOperationQueue to your `Package.swift`: 156 | 157 | ```swift 158 | import PackageDescription 159 | 160 | let package = Package( 161 | name: "HelloWorld", 162 | dependencies: [ 163 | .package(url: "https://github.com/p4checo/APNSubGroupOperationQueue.git", from: "4.0.0"), 164 | ] 165 | ) 166 | ``` 167 | 168 | ### git Submodule 169 | 170 | 1. Add this repository as a submodule. 171 | 2. Drag APNSubGroupOperationQueue.xcodeproj into your project or workspace. 172 | 3. Link your target against APNSubGroupOperationQueue.framework of your platform. 173 | 4. If linking againts an Application target, ensure the framework gets copied into the bundle. If linking against a Framework target, the application linking to it should also include APNSubGroupOperationQueue. 174 | 175 | ## Contributing 176 | 177 | See [CONTRIBUTING](https://github.com/p4checo/APNSubGroupOperationQueue/blob/master/CONTRIBUTING.md). 178 | -------------------------------------------------------------------------------- /Sources/APNSubGroupOperationQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // APNSubGroupOperationQueue.h 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 26/03/16. 6 | // Copyright © 2016 André Pacheco Neves. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | #import 27 | 28 | //! Project version number for APNSubGroupOperationQueue. 29 | FOUNDATION_EXPORT double APNSubGroupOperationQueue_VersionNumber; 30 | 31 | //! Project version string for APNSubGroupOperationQueue. 32 | FOUNDATION_EXPORT const unsigned char APNSubGroupOperationQueue_VersionString[]; 33 | 34 | // In this header, you should import all the public headers of your framework using statements like: 35 | // #import 36 | -------------------------------------------------------------------------------- /Sources/CompletionOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionOperation.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 12/02/2017. 6 | // Copyright © 2017 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// `CompletionOperation` is a `BlockOperation` subclass which is used internally by the `SubGroupOperationQueue` to 12 | /// remove completed operations from the subgroup dictionary. 13 | /// 14 | /// For each operation submitted by the user to the queue, a `CompletionOperation` which depends on it is created 15 | /// containing the logic to cleanup the subgroup dictionary. By doing this, the operation's `completionBlock` doesn't 16 | /// have to be used, allowing the user full control over the operation, without introducing possible side-effects. 17 | /// 18 | /// These operations aren't returned by either `subscript` or `subGroupOperations` even though they technically are in 19 | /// the subgroup. 20 | final class CompletionOperation: BlockOperation {} 21 | -------------------------------------------------------------------------------- /Sources/DynamicSubGroupOperationQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicSubGroupOperationQueue.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 12/02/2017. 6 | // Copyright © 2017 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// `DynamicSubGroupOperationQueue` is an `OperationQueue` subclass which allows scheduling operations in serial 12 | /// subgroups inside a concurrent queue. 13 | /// 14 | /// The subgroups are stored as a `[AnyHashable : [Operation]]`, and each subgroup array contains all the scheduled 15 | /// subgroup's operations which are pending and executing. Finished `Operation`s are automatically removed from the 16 | /// subgroup after completion. 17 | @objc(APNSubGroupOperationQueue) 18 | public final class DynamicSubGroupOperationQueue: OperationQueue { 19 | 20 | /// The queue's operation subgroups map. 21 | fileprivate let subGroups = OperationSubGroupMap() 22 | 23 | /// The maximum number of queued operations that can execute at the same time. 24 | /// 25 | /// - Warning: This value should be `!= 1` (serial queue), otherwise this class provides no benefit. 26 | override public var maxConcurrentOperationCount: Int { 27 | get { return super.maxConcurrentOperationCount } 28 | set { 29 | assert(newValue != 1, "`\(type(of:self))` must be concurrent to provide any benefit over a serial queue! 🙃") 30 | super.maxConcurrentOperationCount = newValue 31 | } 32 | } 33 | 34 | // MARK: - Public 35 | 36 | /// Adds the specified operation to the queue, and registers it the subgroup identified by `key`. 37 | /// 38 | /// Once added, the operation will only be executed after all currently existing operations in the same subgroup 39 | /// finish executing (serial processing), but can be executed concurrently with other subgroup's operations. 40 | /// 41 | /// - Parameters: 42 | /// - op: The `NSOperation` to be added to the queue. 43 | /// - key: The subgroup's identifier key. 44 | @objc 45 | public func addOperation(_ op: Operation, withKey key: AnyHashable) { 46 | addOperations(subGroups.register(op, withKey: key), waitUntilFinished: false) 47 | } 48 | 49 | /// Adds the specified operations to the queue, and registers them the subgroup identified by `key`. 50 | /// The order in which the operations are processed is the same as the array's. 51 | /// 52 | /// Once added, the operations will be executed in order after all currently existing operations in the same subgroup 53 | /// finish executing (serial processing), but can be executed concurrently with other subgroup's operations. 54 | /// 55 | /// - Parameters: 56 | /// - ops: The `[NSOperation]` to be added to the queue 57 | /// - key: The subgroup's identifier key 58 | /// - wait: If `true`, the current thread is blocked until all of the specified operations finish executing. 59 | /// If `false`, the operations are added to the queue and control returns immediately to the caller. 60 | @objc 61 | public func addOperations(_ ops: [Operation], withKey key: AnyHashable, waitUntilFinished wait: Bool) { 62 | addOperations(subGroups.register(ops, withKey: key), waitUntilFinished: wait) 63 | } 64 | 65 | /// Wraps the specified block in an operation object, adds it to the queue and and registers it the subgroup 66 | /// identified by `key`. 67 | /// 68 | /// Once added, the operation will only be executed after all currently existing operations in the same subgroup 69 | /// finish executing (serial processing), but can be executed concurrently with other subgroup's operations. 70 | /// 71 | /// - Parameters: 72 | /// - block: The block to execute from the operation. 73 | /// - key: The subgroup's identifier key. 74 | @objc(addOperationWithBlock:andKey:) 75 | public func addOperation(_ block: @escaping () -> Void, withKey key: AnyHashable) { 76 | addOperations(subGroups.register(block, withKey: key), waitUntilFinished: false) 77 | } 78 | 79 | // MARK: SubGroup querying 80 | 81 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 82 | /// 83 | /// - Parameter key: The subgroup's identifier key. 84 | /// - Returns: An `[NSOperation]` containing a snapshot of all currently scheduled (non-finished) subgroup 85 | /// operations. 86 | public subscript(key: AnyHashable) -> [Operation] { 87 | return subGroups[key] 88 | } 89 | 90 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 91 | /// 92 | /// - Parameter key: The subgroup's identifier key. 93 | /// - Returns: An `[NSOperation]` containing a snapshot of all currently scheduled (non-finished) subgroup 94 | /// operations. 95 | @objc 96 | public func subGroupOperations(forKey key: AnyHashable) -> [Operation] { 97 | return subGroups[key] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Info-macOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2016 André Pacheco Neves. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Sources/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Info-watchOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/OperationSubGroupMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperationSubGroupMap.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 12/02/2017. 6 | // Copyright © 2017 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// `OperationSubGroupMap` is a class which contains an `OperationQueue`'s serial subgroups and synchronizes access to 12 | /// them. 13 | /// 14 | /// The subgroups are stored as a `[Key : [Operation]]`, and each subgroup array contains all the scheduled subgroup's 15 | /// operations which are pending and executing. Finished `Operation`s are automatically removed from the subgroup after 16 | /// completion. 17 | final class OperationSubGroupMap { 18 | 19 | /// The lock which syncronizes access to the subgroup map. 20 | fileprivate let lock: UnfairLock 21 | 22 | /// The operation subgroup map. 23 | fileprivate var subGroups: [Key : [Operation]] 24 | 25 | /// Instantiates a new `OperationSubGroupMap`. 26 | init() { 27 | lock = UnfairLock() 28 | subGroups = [:] 29 | } 30 | 31 | // MARK: - Public 32 | 33 | /// Register the specified operation in the subgroup identified by `key`, and create a `CompletionOperation` to ensure 34 | /// the operation is removed from the subgroup on completion. 35 | /// 36 | /// Once added to the `OperationQueue`, the operation will only be executed after all currently existing operations 37 | /// in the same subgroup finish executing (serial processing), but can be executed concurrently with other 38 | /// subgroups' operations. 39 | /// 40 | /// - Parameters: 41 | /// - op: The `Operation` to be added to the queue. 42 | /// - key: The subgroup's identifier key. 43 | /// - Returns: An `[Operation]` containing the registered operation `op` and it's associated `CompletionOperation`, 44 | /// which *must* both be added to the `OperationQueue`. 45 | func register(_ op: Operation, withKey key: Key) -> [Operation] { 46 | return register([op], withKey: key) 47 | } 48 | 49 | /// Wrap the specified block in a `BlockOperation`, register it in the subgroup identified by `key`, and create a 50 | /// `CompletionOperation` to ensure the operation is removed from the subgroup on completion. 51 | /// 52 | /// Once added to the `OperationQueue`, the operation will only be executed after all currently existing operations 53 | /// in the same subgroup finish executing (serial processing), but can be executed concurrently with other 54 | /// subgroups' operations. 55 | /// 56 | /// - Parameters: 57 | /// - block: The `Operation` to be added to the queue. 58 | /// - key: The subgroup's identifier key. 59 | /// - Returns: An `[Operation]` containing the registered operation `op` and it's associated `CompletionOperation`, 60 | /// which both *must* be added to the `OperationQueue`. 61 | func register(_ block: @escaping () -> Void, withKey key: Key) -> [Operation] { 62 | return register([BlockOperation(block: block)], withKey: key) 63 | } 64 | 65 | /// 66 | /// - Parameters: 67 | /// - ops: The `[Operation]` to be added to the queue. 68 | /// - key: The subgroup's identifier key. 69 | /// - Returns: An `[Operation]` containing the registered operation `ops` and their associated 70 | /// `CompletionOperation`, which *must* all be added to the `OperationQueue`. 71 | func register(_ ops: [Operation], withKey key: Key) -> [Operation] { 72 | lock.lock() 73 | defer { lock.unlock() } 74 | 75 | var newOps = [Operation]() 76 | var subGroup = subGroups[key] ?? [] 77 | 78 | ops.forEach { op in 79 | let completionOp = createCompletionOperation(for: op, withKey: key) 80 | setupDependencies(for: op, completionOp: completionOp, subGroup: subGroup) 81 | 82 | let opPair = [op, completionOp] 83 | newOps.append(contentsOf: opPair) 84 | subGroup.append(contentsOf: opPair) 85 | } 86 | 87 | subGroups[key] = subGroup 88 | 89 | return newOps 90 | } 91 | 92 | // MARK: SubGroup querying 93 | 94 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 95 | /// 96 | /// - Parameter key: The subgroup's identifier key. 97 | /// - Returns: An `[Operation]` containing a snapshot of all currently scheduled (non-finished) subgroup operations. 98 | public subscript(key: Key) -> [Operation] { 99 | return operations(forKey: key) 100 | } 101 | 102 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 103 | /// 104 | /// - Parameter key: The subgroup's identifier key. 105 | /// - Returns: An `[Operation]` containing a snapshot of all currently scheduled (non-finished) subgroup operations. 106 | public func operations(forKey key: Key) -> [Operation] { 107 | lock.lock() 108 | defer { lock.unlock() } 109 | 110 | return subGroups[key]?.filter { !($0 is CompletionOperation) } ?? [] 111 | } 112 | 113 | // MARK: - Private 114 | 115 | /// Set up dependencies for an operation being registered in a subgroup. 116 | /// 117 | /// This consists of: 118 | /// 1. Add dependency from the `completionOp` to the registered `op` 119 | /// 2. Add dependency from `op` to the last operation in the subgroup, *if the subgroup isn't empty*. 120 | /// 121 | /// - Parameters: 122 | /// - op: The operation being registered. 123 | /// - completionOp: The completion operation 124 | /// - subGroup: The subgroup where the operation is being registered. 125 | private func setupDependencies(for op: Operation, completionOp: CompletionOperation, subGroup: [Operation]) { 126 | completionOp.addDependency(op) 127 | 128 | // new operations only need to depend on the group's last operation 129 | if let lastOp = subGroup.last { 130 | op.addDependency(lastOp) 131 | } 132 | } 133 | 134 | /// Create a completion operation for an operation being registered in a subgroup. The produced 135 | /// `CompletionOperation` is responsible for removing the operation being registered (and itself) from the subgroup. 136 | /// 137 | /// - Parameters: 138 | /// - op: The operation being registered. 139 | /// - key: The subgroup's identifier key. 140 | /// - Returns: A `CompletionOperation` containing the removal of `op` from the subgroup identified by `key` 141 | private func createCompletionOperation(for op: Operation, withKey key: Key) -> CompletionOperation { 142 | let completionOp = CompletionOperation() 143 | 144 | completionOp.addExecutionBlock { [unowned self, weak weakCompletionOp = completionOp] in 145 | self.lock.lock() 146 | defer { self.lock.unlock() } 147 | 148 | guard let completionOp = weakCompletionOp else { 149 | assertionFailure("💥: The completion operation must not be nil") 150 | return 151 | } 152 | 153 | guard var subGroup = self.subGroups[key] else { 154 | assertionFailure("💥: A group must exist in the dicionary for the finished operation's key!") 155 | return 156 | } 157 | 158 | assert([op, completionOp] == subGroup[0...1], 159 | "💥: op and completionOp must be the first 2 elements in the subgroup's array") 160 | 161 | self.subGroups[key] = subGroup.count == 2 ? nil : { 162 | subGroup.removeFirst(2) 163 | return subGroup 164 | }() 165 | } 166 | 167 | return completionOp 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Sources/SubGroupOperationQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubGroupOperationQueue.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 27/03/16. 6 | // Copyright © 2016 André Pacheco Neves. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | import Foundation 27 | 28 | /// `SubGroupOperationQueue` is an `OperationQueue` subclass which allows scheduling operations in serial subgroups 29 | /// inside a concurrent queue. 30 | /// 31 | /// The subgroups are stored as a `[Key : [Operation]]`, and each subgroup array contains all the scheduled subgroup's 32 | /// operations which are pending and executing. Finished `Operation`s are automatically removed from the subgroup after 33 | /// completion. 34 | public class SubGroupOperationQueue: OperationQueue { 35 | 36 | /// The queue's operation subgroups map. 37 | private let subGroups = OperationSubGroupMap() 38 | 39 | /// The maximum number of queued operations that can execute at the same time. 40 | /// 41 | /// - Warning: This value should be `!= 1` (serial queue), otherwise this class provides no benefit. 42 | override public var maxConcurrentOperationCount: Int { 43 | get { return super.maxConcurrentOperationCount } 44 | set { 45 | assert(newValue != 1, "`SubGroupQueue` must be concurrent to provide any benefit over a serial queue! 🙃") 46 | super.maxConcurrentOperationCount = newValue 47 | } 48 | } 49 | 50 | // MARK: - Public 51 | 52 | /// Adds the specified operation to the queue, and registers it the subgroup identified by `key`. 53 | /// 54 | /// Once added, the operation will only be executed after all currently existing operations in the same subgroup 55 | /// finish executing (serial processing), but can be executed concurrently with other subgroup's operations. 56 | /// 57 | /// - Parameters: 58 | /// - op: The `Operation` to be added to the queue. 59 | /// - key: The subgroup's identifier key. 60 | public func addOperation(_ op: Operation, withKey key: Key) { 61 | addOperations(subGroups.register(op, withKey: key), waitUntilFinished: false) 62 | } 63 | 64 | /// Adds the specified operations to the queue, and registers them the subgroup identified by `key`. 65 | /// The order in which the operations are processed is the same as the array's. 66 | /// 67 | /// Once added, the operations will be executed in order after all currently existing operations in the same 68 | /// subgroup finish executing (serial processing), but can be executed concurrently with other subgroup's 69 | /// operations. 70 | /// 71 | /// - Parameters: 72 | /// - ops: The `[Operation]` to be added to the queue 73 | /// - key: The subgroup's identifier key 74 | /// - wait: If `true`, the current thread is blocked until all of the specified operations finish executing. 75 | /// If `false`, the operations are added to the queue and control returns immediately to the caller. 76 | public func addOperations(_ ops: [Operation], withKey key: Key, waitUntilFinished wait: Bool) { 77 | addOperations(subGroups.register(ops, withKey: key), waitUntilFinished: wait) 78 | } 79 | 80 | /// Wraps the specified block in an operation object, adds it to the queue and and registers it the subgroup 81 | /// identified by `key`. 82 | /// 83 | /// Once added, the operation will only be executed after all currently existing operations in the same subgroup 84 | /// finish executing (serial processing), but can be executed concurrently with other subgroup's operations. 85 | /// 86 | /// - Parameters: 87 | /// - block: The block to execute from the operation. 88 | /// - key: The subgroup's identifier key. 89 | public func addOperation(_ block: @escaping () -> Void, withKey key: Key) { 90 | addOperations(subGroups.register(block, withKey: key), waitUntilFinished: false) 91 | } 92 | 93 | // MARK: SubGroup querying 94 | 95 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 96 | /// 97 | /// - Parameter key: The subgroup's identifier key. 98 | /// - Returns: An `[Operation]` containing a snapshot of all currently scheduled (non-finished) subgroup operations. 99 | public subscript(key: Key) -> [Operation] { 100 | return subGroups[key] 101 | } 102 | 103 | /// Return a snapshot of currently scheduled (i.e. non-finished) operations of the subgroup identified by `key`. 104 | /// 105 | /// - Parameter key: The subgroup's identifier key. 106 | /// - Returns: An `[Operation]` containing a snapshot of all currently scheduled (non-finished) subgroup operations. 107 | public func subGroupOperations(forKey key: Key) -> [Operation] { 108 | return subGroups[key] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/UnfairLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnfairLock.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 15/09/2018. 6 | // Copyright © 2018 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import os 11 | 12 | /// Wrapper class around an `os_unfair_lock_t`. 13 | final class UnfairLock { 14 | 15 | /// The wrapped unfair lock. 16 | private let _lock: os_unfair_lock_t 17 | 18 | /// Instantiates a new `UnfairLock`. 19 | init() { 20 | _lock = .allocate(capacity: 1) 21 | _lock.initialize(to: os_unfair_lock()) 22 | } 23 | 24 | /// Locks the lock. 25 | func lock() { 26 | os_unfair_lock_lock(_lock) 27 | } 28 | 29 | /// Unlocks the lock. 30 | func unlock() { 31 | os_unfair_lock_unlock(_lock) 32 | } 33 | 34 | deinit { 35 | _lock.deinitialize(count: 1) 36 | _lock.deallocate() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/APNSubGroupOperationQueueTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // APNSubGroupOperationQueueTests.m 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 03/04/16. 6 | // Copyright © 2016 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import APNSubGroupOperationQueue; 12 | 13 | typedef void (^APNAppendingBlock)(void); 14 | 15 | @interface APNSubGroupOperationQueueTests : XCTestCase 16 | 17 | @property(nonatomic, strong) APNSubGroupOperationQueue *subGroupQueue; 18 | 19 | @end 20 | 21 | @implementation APNSubGroupOperationQueueTests 22 | 23 | - (void)setUp { 24 | [super setUp]; 25 | 26 | self.subGroupQueue = [APNSubGroupOperationQueue new]; 27 | } 28 | 29 | - (void)tearDown { 30 | [super tearDown]; 31 | } 32 | 33 | #pragma mark - 34 | #pragma mark addOperation 35 | 36 | - (void)testAddOperation_withSingleGroup_mustExecuteSerially { 37 | NSString *key = @"key"; 38 | NSString *string = @"123456"; 39 | NSArray *strings = [self splitString:string]; 40 | NSMutableString *result = [NSMutableString string]; 41 | 42 | NSArray *ops = [self stringAppendingBlockOperationsForStrings:strings result:result]; 43 | 44 | self.subGroupQueue.suspended = YES; 45 | 46 | for (NSBlockOperation *op in ops) { 47 | [self.subGroupQueue addOperation:op withKey:key]; 48 | } 49 | 50 | self.subGroupQueue.suspended = NO; 51 | 52 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 53 | 54 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:key].count == 0); 55 | XCTAssert([result isEqualToString:string]); 56 | } 57 | 58 | - (void)testAddOperation_withMutipleGroups_mustExecuteEachGroupSerially { 59 | NSObject *keyA = @"A"; 60 | NSObject *keyB = @1337; 61 | NSObject *keyC = [NSDate date]; 62 | 63 | NSString *stringA = @"123456"; 64 | NSString *stringB = @"abcdef"; 65 | NSString *stringC = @"ABCDEF"; 66 | 67 | NSArray *stringsA = [self splitString:stringA]; 68 | NSArray *stringsB = [self splitString:stringB]; 69 | NSArray *stringsC = [self splitString:stringC]; 70 | 71 | NSMutableString *resultA = [NSMutableString string]; 72 | NSMutableString *resultB = [NSMutableString string]; 73 | NSMutableString *resultC = [NSMutableString string]; 74 | 75 | NSArray *opsA = [self stringAppendingBlockOperationsForStrings:stringsA result:resultA]; 76 | NSArray *opsB = [self stringAppendingBlockOperationsForStrings:stringsB result:resultB]; 77 | NSArray *opsC = [self stringAppendingBlockOperationsForStrings:stringsC result:resultC]; 78 | 79 | self.subGroupQueue.suspended = YES; 80 | 81 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 82 | [self.subGroupQueue addOperation:opsA[0] withKey:keyA]; 83 | [self.subGroupQueue addOperation:opsB[0] withKey:keyB]; 84 | [self.subGroupQueue addOperation:opsC[0] withKey:keyC]; 85 | [self.subGroupQueue addOperation:opsA[1] withKey:keyA]; 86 | [self.subGroupQueue addOperation:opsB[1] withKey:keyB]; 87 | [self.subGroupQueue addOperation:opsB[2] withKey:keyB]; 88 | [self.subGroupQueue addOperation:opsA[2] withKey:keyA]; 89 | [self.subGroupQueue addOperation:opsC[1] withKey:keyC]; 90 | [self.subGroupQueue addOperation:opsC[2] withKey:keyC]; 91 | [self.subGroupQueue addOperation:opsA[3] withKey:keyA]; 92 | [self.subGroupQueue addOperation:opsB[3] withKey:keyB]; 93 | [self.subGroupQueue addOperation:opsA[4] withKey:keyA]; 94 | [self.subGroupQueue addOperation:opsC[3] withKey:keyC]; 95 | [self.subGroupQueue addOperation:opsB[4] withKey:keyB]; 96 | [self.subGroupQueue addOperation:opsC[4] withKey:keyC]; 97 | [self.subGroupQueue addOperation:opsA[5] withKey:keyA]; 98 | [self.subGroupQueue addOperation:opsB[5] withKey:keyB]; 99 | [self.subGroupQueue addOperation:opsC[5] withKey:keyC]; 100 | 101 | self.subGroupQueue.suspended = NO; 102 | 103 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 104 | 105 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyA].count == 0); 106 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyB].count == 0); 107 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyC].count == 0); 108 | 109 | XCTAssert([resultA isEqualToString:stringA]); 110 | XCTAssert([resultB isEqualToString:stringB]); 111 | XCTAssert([resultC isEqualToString:stringC]); 112 | } 113 | 114 | #pragma mark - 115 | #pragma mark addOperations 116 | 117 | - (void)testAddOperations_withSingleGroup_mustExecuteSerially { 118 | NSString *key = @"key"; 119 | NSString *string = @"123456"; 120 | NSArray *strings = [self splitString:string]; 121 | NSMutableString *result = [NSMutableString string]; 122 | NSArray *ops = [self stringAppendingBlockOperationsForStrings:strings result:result]; 123 | 124 | [self.subGroupQueue addOperations:ops withKey:key waitUntilFinished:true]; 125 | 126 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:key].count == 0); 127 | XCTAssert([result isEqualToString:string], @"%@ didn't match expected value %@", result, string); 128 | } 129 | 130 | - (void)testAddOperations_withMutipleGroups_mustExecuteEachGroupSerially { 131 | NSObject *keyA = @"A"; 132 | NSObject *keyB = @1337; 133 | NSObject *keyC = [NSDate date]; 134 | 135 | NSString *stringA = @"123456"; 136 | NSString *stringB = @"abcdef"; 137 | NSString *stringC = @"ABCDEF"; 138 | 139 | NSArray *stringsA = [self splitString:stringA]; 140 | NSArray *stringsB = [self splitString:stringB]; 141 | NSArray *stringsC = [self splitString:stringC]; 142 | 143 | NSMutableString *resultA = [NSMutableString string]; 144 | NSMutableString *resultB = [NSMutableString string]; 145 | NSMutableString *resultC = [NSMutableString string]; 146 | 147 | NSArray *opsA = [self stringAppendingBlockOperationsForStrings:stringsA result:resultA]; 148 | NSArray *opsB = [self stringAppendingBlockOperationsForStrings:stringsB result:resultB]; 149 | NSArray *opsC = [self stringAppendingBlockOperationsForStrings:stringsC result:resultC]; 150 | 151 | self.subGroupQueue.suspended = YES; 152 | 153 | [self.subGroupQueue addOperations:opsA withKey:keyA waitUntilFinished:false]; 154 | [self.subGroupQueue addOperations:opsB withKey:keyB waitUntilFinished:false]; 155 | [self.subGroupQueue addOperations:opsC withKey:keyC waitUntilFinished:false]; 156 | 157 | self.subGroupQueue.suspended = NO; 158 | 159 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 160 | 161 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyA].count == 0); 162 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyB].count == 0); 163 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyC].count == 0); 164 | 165 | XCTAssert([resultA isEqualToString:stringA], @"%@ didn't match expected value %@", resultA, stringA); 166 | XCTAssert([resultB isEqualToString:stringB], @"%@ didn't match expected value %@", resultB, stringB); 167 | XCTAssert([resultC isEqualToString:stringC], @"%@ didn't match expected value %@", resultC, stringC); 168 | } 169 | 170 | #pragma mark - 171 | #pragma mark addOperationWithBlock 172 | 173 | - (void)testAddOperationWithBlock_withSingleGroup_mustExecuteSerially { 174 | NSString *key = @"key"; 175 | NSString *string = @"123456"; 176 | NSArray *strings = [self splitString:string]; 177 | NSMutableString *result = [NSMutableString string]; 178 | 179 | NSArray *blocks = [self stringAppendingBlocksForStrings:strings result:result]; 180 | 181 | self.subGroupQueue.suspended = YES; 182 | 183 | for (APNAppendingBlock block in blocks) { 184 | [self.subGroupQueue addOperationWithBlock:block andKey:key]; 185 | } 186 | 187 | self.subGroupQueue.suspended = NO; 188 | 189 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 190 | 191 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:key].count == 0); 192 | XCTAssert([result isEqualToString:string], @"%@ didn't match expected value %@", result, string); 193 | } 194 | 195 | - (void)testAddOperationWithBlock_withMutipleGroups_mustExecuteEachGroupSerially { 196 | NSObject *keyA = @"A"; 197 | NSObject *keyB = @1337; 198 | NSObject *keyC = [NSDate date]; 199 | 200 | NSString *stringA = @"123456"; 201 | NSString *stringB = @"abcdef"; 202 | NSString *stringC = @"ABCDEF"; 203 | 204 | NSArray *stringsA = [self splitString:stringA]; 205 | NSArray *stringsB = [self splitString:stringB]; 206 | NSArray *stringsC = [self splitString:stringC]; 207 | 208 | NSMutableString *resultA = [NSMutableString string]; 209 | NSMutableString *resultB = [NSMutableString string]; 210 | NSMutableString *resultC = [NSMutableString string]; 211 | 212 | NSArray *blocksA = [self stringAppendingBlocksForStrings:stringsA result:resultA]; 213 | NSArray *blocksB = [self stringAppendingBlocksForStrings:stringsB result:resultB]; 214 | NSArray *blocksC = [self stringAppendingBlocksForStrings:stringsC result:resultC]; 215 | 216 | self.subGroupQueue.suspended = YES; 217 | 218 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 219 | [self.subGroupQueue addOperationWithBlock:blocksA[0] andKey:keyA]; 220 | [self.subGroupQueue addOperationWithBlock:blocksB[0] andKey:keyB]; 221 | [self.subGroupQueue addOperationWithBlock:blocksC[0] andKey:keyC]; 222 | [self.subGroupQueue addOperationWithBlock:blocksA[1] andKey:keyA]; 223 | [self.subGroupQueue addOperationWithBlock:blocksB[1] andKey:keyB]; 224 | [self.subGroupQueue addOperationWithBlock:blocksB[2] andKey:keyB]; 225 | [self.subGroupQueue addOperationWithBlock:blocksA[2] andKey:keyA]; 226 | [self.subGroupQueue addOperationWithBlock:blocksC[1] andKey:keyC]; 227 | [self.subGroupQueue addOperationWithBlock:blocksC[2] andKey:keyC]; 228 | [self.subGroupQueue addOperationWithBlock:blocksA[3] andKey:keyA]; 229 | [self.subGroupQueue addOperationWithBlock:blocksB[3] andKey:keyB]; 230 | [self.subGroupQueue addOperationWithBlock:blocksA[4] andKey:keyA]; 231 | [self.subGroupQueue addOperationWithBlock:blocksC[3] andKey:keyC]; 232 | [self.subGroupQueue addOperationWithBlock:blocksB[4] andKey:keyB]; 233 | [self.subGroupQueue addOperationWithBlock:blocksC[4] andKey:keyC]; 234 | [self.subGroupQueue addOperationWithBlock:blocksA[5] andKey:keyA]; 235 | [self.subGroupQueue addOperationWithBlock:blocksB[5] andKey:keyB]; 236 | [self.subGroupQueue addOperationWithBlock:blocksC[5] andKey:keyC]; 237 | 238 | self.subGroupQueue.suspended = NO; 239 | 240 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 241 | 242 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyA].count == 0); 243 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyB].count == 0); 244 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyC].count == 0); 245 | 246 | XCTAssert([resultA isEqualToString:stringA], @"%@ didn't match expected value %@", resultA, stringA); 247 | XCTAssert([resultB isEqualToString:stringB], @"%@ didn't match expected value %@", resultB, stringB); 248 | XCTAssert([resultC isEqualToString:stringC], @"%@ didn't match expected value %@", resultC, stringC); 249 | } 250 | 251 | #pragma mark - 252 | #pragma mark mixed 253 | 254 | - (void)testMixedAddOperations_withSingleGroup_mustExecuteEachGroupSerially { 255 | NSString *key = @"key"; 256 | NSString *string = @"123456"; 257 | NSArray *strings = [self splitString:string]; 258 | NSMutableString *result = [NSMutableString string]; 259 | 260 | NSArray *blocks = [self stringAppendingBlocksForStrings:strings result:result]; 261 | 262 | NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:blocks[0]]; 263 | NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:blocks[1]]; 264 | NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:blocks[4]]; 265 | NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:blocks[5]]; 266 | 267 | self.subGroupQueue.suspended = YES; 268 | 269 | [self.subGroupQueue addOperation:op1 withKey:key]; 270 | [self.subGroupQueue addOperation:op2 withKey:key]; 271 | 272 | [self.subGroupQueue addOperationWithBlock:blocks[2] andKey:key]; 273 | [self.subGroupQueue addOperationWithBlock:blocks[3] andKey:key]; 274 | 275 | [self.subGroupQueue addOperations:@[ op5, op6 ] withKey:key waitUntilFinished:false]; 276 | 277 | self.subGroupQueue.suspended = NO; 278 | 279 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 280 | 281 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:key].count == 0); 282 | XCTAssert([result isEqualToString:string], @"%@ didn't match expected value %@", result, string); 283 | } 284 | 285 | - (void)testMixedAddOperations_withMutipleGroups_mustExecuteEachGroupSerially { 286 | NSObject *keyA = @"A"; 287 | NSObject *keyB = @1337; 288 | NSObject *keyC = [NSDate date]; 289 | 290 | NSString *stringA = @"123456"; 291 | NSString *stringB = @"abcdef"; 292 | NSString *stringC = @"ABCDEF"; 293 | 294 | NSArray *stringsA = [self splitString:stringA]; 295 | NSArray *stringsB = [self splitString:stringB]; 296 | NSArray *stringsC = [self splitString:stringC]; 297 | 298 | NSMutableString *resultA = [NSMutableString string]; 299 | NSMutableString *resultB = [NSMutableString string]; 300 | NSMutableString *resultC = [NSMutableString string]; 301 | 302 | NSArray *blocksA = [self stringAppendingBlocksForStrings:stringsA result:resultA]; 303 | NSArray *blocksB = [self stringAppendingBlocksForStrings:stringsB result:resultB]; 304 | NSArray *blocksC = [self stringAppendingBlocksForStrings:stringsC result:resultC]; 305 | 306 | NSBlockOperation *opA1 = [NSBlockOperation blockOperationWithBlock:blocksA[0]]; 307 | NSBlockOperation *opA2 = [NSBlockOperation blockOperationWithBlock:blocksA[1]]; 308 | NSBlockOperation *opA5 = [NSBlockOperation blockOperationWithBlock:blocksA[4]]; 309 | NSBlockOperation *opA6 = [NSBlockOperation blockOperationWithBlock:blocksA[5]]; 310 | 311 | NSBlockOperation *opB3 = [NSBlockOperation blockOperationWithBlock:blocksB[2]]; 312 | NSBlockOperation *opB4 = [NSBlockOperation blockOperationWithBlock:blocksB[3]]; 313 | NSBlockOperation *opB5 = [NSBlockOperation blockOperationWithBlock:blocksB[4]]; 314 | NSBlockOperation *opB6 = [NSBlockOperation blockOperationWithBlock:blocksB[5]]; 315 | 316 | NSBlockOperation *opC1 = [NSBlockOperation blockOperationWithBlock:blocksC[0]]; 317 | NSBlockOperation *opC2 = [NSBlockOperation blockOperationWithBlock:blocksC[1]]; 318 | NSBlockOperation *opC3 = [NSBlockOperation blockOperationWithBlock:blocksC[2]]; 319 | NSBlockOperation *opC4 = [NSBlockOperation blockOperationWithBlock:blocksC[3]]; 320 | 321 | self.subGroupQueue.suspended = YES; 322 | 323 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 324 | [self.subGroupQueue addOperation:opA1 withKey:keyA]; 325 | [self.subGroupQueue addOperationWithBlock:blocksB[0] andKey:keyB]; 326 | [self.subGroupQueue addOperations:@[ opC1, opC2 ] withKey:keyC waitUntilFinished:false]; 327 | [self.subGroupQueue addOperation:opA2 withKey:keyA]; 328 | [self.subGroupQueue addOperationWithBlock:blocksB[1] andKey:keyB]; 329 | [self.subGroupQueue addOperations:@[ opB3, opB4 ] withKey:keyB waitUntilFinished:false]; 330 | [self.subGroupQueue addOperationWithBlock:blocksA[2] andKey:keyA]; 331 | [self.subGroupQueue addOperation:opC3 withKey:keyC]; 332 | [self.subGroupQueue addOperationWithBlock:blocksA[3] andKey:keyA]; 333 | [self.subGroupQueue addOperations:@[ opA5, opA6 ] withKey:keyA waitUntilFinished:false]; 334 | [self.subGroupQueue addOperation:opC4 withKey:keyC]; 335 | [self.subGroupQueue addOperation:opB5 withKey:keyB]; 336 | [self.subGroupQueue addOperationWithBlock:blocksC[4] andKey:keyC]; 337 | [self.subGroupQueue addOperation:opB6 withKey:keyB]; 338 | [self.subGroupQueue addOperationWithBlock:blocksC[5] andKey:keyC]; 339 | 340 | self.subGroupQueue.suspended = NO; 341 | 342 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 343 | 344 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyA].count == 0); 345 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyB].count == 0); 346 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:keyC].count == 0); 347 | 348 | XCTAssert([resultA isEqualToString:stringA], @"%@ didn't match expected value %@", resultA, stringA); 349 | XCTAssert([resultB isEqualToString:stringB], @"%@ didn't match expected value %@", resultB, stringB); 350 | XCTAssert([resultC isEqualToString:stringC], @"%@ didn't match expected value %@", resultC, stringC); 351 | } 352 | 353 | #pragma mark - 354 | #pragma mark subGroupOperationsForKey 355 | 356 | - (void)testSubGroupOperations_withExistingSubGroupOperations_shouldReturnOperations { 357 | NSString *key = @"key"; 358 | 359 | NSBlockOperation *op1 = [NSBlockOperation new]; 360 | NSBlockOperation *op2 = [NSBlockOperation new]; 361 | NSBlockOperation *op3 = [NSBlockOperation new]; 362 | NSBlockOperation *op4 = [NSBlockOperation new]; 363 | NSBlockOperation *op5 = [NSBlockOperation new]; 364 | NSBlockOperation *op6 = [NSBlockOperation new]; 365 | 366 | NSArray *expected = @[ op1 ]; // Xcode complains with array literals with more than 1 element 😓 367 | 368 | self.subGroupQueue.suspended = YES; 369 | 370 | [self.subGroupQueue addOperation:op1 withKey:key]; 371 | XCTAssert([[self.subGroupQueue subGroupOperationsForKey:key] isEqualToArray:expected]); 372 | 373 | [self.subGroupQueue addOperation:op2 withKey:key]; 374 | expected = @[ op1, op2 ]; 375 | XCTAssert([[self.subGroupQueue subGroupOperationsForKey:key] isEqualToArray:expected]); 376 | 377 | [self.subGroupQueue addOperation:op3 withKey:key]; 378 | expected = @[ op1, op2, op3 ]; 379 | XCTAssert([[self.subGroupQueue subGroupOperationsForKey:key] isEqualToArray:expected]); 380 | 381 | [self.subGroupQueue addOperations:@[ op4, op5, op6 ] withKey:key waitUntilFinished:false]; 382 | expected = @[ op1, op2, op3, op4, op5, op6 ]; 383 | XCTAssert([[self.subGroupQueue subGroupOperationsForKey:key] isEqualToArray:expected]); 384 | 385 | self.subGroupQueue.suspended = NO; 386 | 387 | [self.subGroupQueue waitUntilAllOperationsAreFinished]; 388 | 389 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:key].count == 0); 390 | } 391 | 392 | - (void)testSubGroupOperations_withNonExistingSubGroupOperations_shouldReturnEmptyArray { 393 | XCTAssert([self.subGroupQueue subGroupOperationsForKey:@"key"].count == 0); 394 | } 395 | 396 | #pragma mark - 397 | #pragma mark Auxiliary 398 | 399 | - (NSArray *)splitString:(NSString *)string { 400 | NSParameterAssert(string); 401 | 402 | NSMutableArray *chars = [NSMutableArray array]; 403 | 404 | for (int i = 0; i < string.length; i++) { 405 | [chars addObject:[string substringWithRange:NSMakeRange(i, 1)]]; 406 | } 407 | return [chars copy]; 408 | } 409 | 410 | - (NSArray *)stringAppendingBlocksForStrings:(NSArray *)strings 411 | result:(NSMutableString *)result { 412 | NSParameterAssert(strings.count); 413 | NSParameterAssert(result); 414 | 415 | NSMutableArray *blocks = [NSMutableArray array]; 416 | 417 | for (NSString *s in strings) { 418 | [blocks addObject:^{ 419 | [result appendString:s]; 420 | }]; 421 | } 422 | 423 | return [blocks copy]; 424 | } 425 | 426 | - (NSArray *)stringAppendingBlockOperationsForStrings:(NSArray *)strings 427 | result:(NSMutableString *)result { 428 | NSParameterAssert(strings.count); 429 | NSParameterAssert(result); 430 | 431 | NSMutableArray *ops = [NSMutableArray array]; 432 | 433 | for (NSString *s in strings) { 434 | [ops addObject:[NSBlockOperation blockOperationWithBlock:^{ 435 | [result appendString:s]; 436 | }]]; 437 | } 438 | 439 | return [ops copy]; 440 | } 441 | 442 | @end 443 | -------------------------------------------------------------------------------- /Tests/DynamicSubGroupOperationQueueTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicSubGroupOperationQueueTests.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 12/02/2017. 6 | // Copyright © 2017 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import APNSubGroupOperationQueue 11 | 12 | class DynamicSubGroupOperationQueueTests: XCTestCase { 13 | 14 | var subGroupQueue: DynamicSubGroupOperationQueue! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | subGroupQueue = DynamicSubGroupOperationQueue() 20 | subGroupQueue.maxConcurrentOperationCount = OperationQueue.defaultMaxConcurrentOperationCount 21 | } 22 | 23 | override func tearDown() { 24 | subGroupQueue = nil 25 | 26 | super.tearDown() 27 | } 28 | 29 | // MARK: - addOperation 30 | 31 | func testAddOperation_withSingleGroup_mustExecuteSerially() { 32 | let (key, value, result) = ("key", "123456", Box("")) 33 | 34 | let ops = stringAppendingBlockOperations(for: splitString(value), sharedBox: result) 35 | 36 | subGroupQueue.isSuspended = true 37 | 38 | ops.forEach { subGroupQueue.addOperation($0, withKey: key) } 39 | 40 | subGroupQueue.isSuspended = false 41 | 42 | subGroupQueue.waitUntilAllOperationsAreFinished() 43 | 44 | XCTAssert(subGroupQueue[key].count == 0) 45 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 46 | } 47 | 48 | func testAddOperation_withMutipleGroups_mustExecuteEachGroupSerially() { 49 | let (keyA, valueA, resultA) = ("A", "123456", Box("")) 50 | let (keyB, valueB, resultB) = (1337, "abcdef", Box("")) 51 | let (keyC, valueC, resultC) = (Date(), "ABCDEF", Box("")) 52 | 53 | let opsA = stringAppendingBlockOperations(for: splitString(valueA), sharedBox: resultA) 54 | let opsB = stringAppendingBlockOperations(for: splitString(valueB), sharedBox: resultB) 55 | let opsC = stringAppendingBlockOperations(for: splitString(valueC), sharedBox: resultC) 56 | 57 | subGroupQueue.isSuspended = true 58 | 59 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 60 | subGroupQueue.addOperation(opsA[0], withKey: keyA) 61 | subGroupQueue.addOperation(opsB[0], withKey: keyB) 62 | subGroupQueue.addOperation(opsC[0], withKey: keyC) 63 | subGroupQueue.addOperation(opsA[1], withKey: keyA) 64 | subGroupQueue.addOperation(opsB[1], withKey: keyB) 65 | subGroupQueue.addOperation(opsB[2], withKey: keyB) 66 | subGroupQueue.addOperation(opsA[2], withKey: keyA) 67 | subGroupQueue.addOperation(opsC[1], withKey: keyC) 68 | subGroupQueue.addOperation(opsC[2], withKey: keyC) 69 | subGroupQueue.addOperation(opsA[3], withKey: keyA) 70 | subGroupQueue.addOperation(opsB[3], withKey: keyB) 71 | subGroupQueue.addOperation(opsA[4], withKey: keyA) 72 | subGroupQueue.addOperation(opsC[3], withKey: keyC) 73 | subGroupQueue.addOperation(opsB[4], withKey: keyB) 74 | subGroupQueue.addOperation(opsC[4], withKey: keyC) 75 | subGroupQueue.addOperation(opsA[5], withKey: keyA) 76 | subGroupQueue.addOperation(opsB[5], withKey: keyB) 77 | subGroupQueue.addOperation(opsC[5], withKey: keyC) 78 | 79 | subGroupQueue.isSuspended = false 80 | 81 | subGroupQueue.waitUntilAllOperationsAreFinished() 82 | 83 | XCTAssert(subGroupQueue[keyA].count == 0) 84 | XCTAssert(subGroupQueue[keyB].count == 0) 85 | XCTAssert(subGroupQueue[keyC].count == 0) 86 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 87 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 88 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 89 | } 90 | 91 | // MARK: - addOperations 92 | 93 | func testAddOperations_withSingleGroup_mustExecuteSerially() { 94 | let (key, value, result) = ("key", "123456", Box("")) 95 | 96 | let ops = stringAppendingBlockOperations(for: splitString(value), sharedBox: result) 97 | 98 | subGroupQueue.addOperations(ops, withKey: key, waitUntilFinished: true) 99 | 100 | XCTAssert(subGroupQueue[key].count == 0) 101 | XCTAssert(result.value == value, "\(result) didn't match expected value \(value)") 102 | } 103 | 104 | func testAddOperations_withMutipleGroups_mustExecuteEachGroupSerially() { 105 | let (keyA, valueA, resultA) = ("A", "123456", Box("")) 106 | let (keyB, valueB, resultB) = (1337, "abcdef", Box("")) 107 | let (keyC, valueC, resultC) = (Date(), "ABCDEF", Box("")) 108 | 109 | let opsA = stringAppendingBlockOperations(for: splitString(valueA), sharedBox: resultA) 110 | let opsB = stringAppendingBlockOperations(for: splitString(valueB), sharedBox: resultB) 111 | let opsC = stringAppendingBlockOperations(for: splitString(valueC), sharedBox: resultC) 112 | 113 | subGroupQueue.isSuspended = true 114 | 115 | subGroupQueue.addOperations(opsA, withKey: keyA, waitUntilFinished: false) 116 | subGroupQueue.addOperations(opsB, withKey: keyB, waitUntilFinished: false) 117 | subGroupQueue.addOperations(opsC, withKey: keyC, waitUntilFinished: false) 118 | 119 | subGroupQueue.isSuspended = false 120 | 121 | subGroupQueue.waitUntilAllOperationsAreFinished() 122 | 123 | XCTAssert(subGroupQueue[keyA].count == 0) 124 | XCTAssert(subGroupQueue[keyB].count == 0) 125 | XCTAssert(subGroupQueue[keyC].count == 0) 126 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 127 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 128 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 129 | } 130 | 131 | // MARK: - addOperation 132 | 133 | func testAddOperationWithBlock_withSingleGroup_mustExecuteSerially() { 134 | let (key, value, result) = ("key", "123456", Box("")) 135 | 136 | let blocks = stringAppendingBlocks(for: splitString(value), sharedBox: result) 137 | 138 | subGroupQueue.isSuspended = true 139 | 140 | blocks.forEach { subGroupQueue.addOperation($0, withKey: key) } 141 | 142 | subGroupQueue.isSuspended = false 143 | 144 | subGroupQueue.waitUntilAllOperationsAreFinished() 145 | 146 | XCTAssert(subGroupQueue[key].count == 0) 147 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 148 | } 149 | 150 | func testAddOperationWithBlock_withMutipleGroups_mustExecuteEachGroupSerially() { 151 | let (keyA, valueA, resultA) = ("A", "123456", Box("")) 152 | let (keyB, valueB, resultB) = (1337, "abcdef", Box("")) 153 | let (keyC, valueC, resultC) = (Date(), "ABCDEF", Box("")) 154 | 155 | let blocksA = stringAppendingBlocks(for: splitString(valueA), sharedBox: resultA) 156 | let blocksB = stringAppendingBlocks(for: splitString(valueB), sharedBox: resultB) 157 | let blocksC = stringAppendingBlocks(for: splitString(valueC), sharedBox: resultC) 158 | 159 | subGroupQueue.isSuspended = true 160 | 161 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 162 | subGroupQueue.addOperation(blocksA[0], withKey: keyA) 163 | subGroupQueue.addOperation(blocksB[0], withKey: keyB) 164 | subGroupQueue.addOperation(blocksC[0], withKey: keyC) 165 | subGroupQueue.addOperation(blocksA[1], withKey: keyA) 166 | subGroupQueue.addOperation(blocksB[1], withKey: keyB) 167 | subGroupQueue.addOperation(blocksB[2], withKey: keyB) 168 | subGroupQueue.addOperation(blocksA[2], withKey: keyA) 169 | subGroupQueue.addOperation(blocksC[1], withKey: keyC) 170 | subGroupQueue.addOperation(blocksC[2], withKey: keyC) 171 | subGroupQueue.addOperation(blocksA[3], withKey: keyA) 172 | subGroupQueue.addOperation(blocksB[3], withKey: keyB) 173 | subGroupQueue.addOperation(blocksA[4], withKey: keyA) 174 | subGroupQueue.addOperation(blocksC[3], withKey: keyC) 175 | subGroupQueue.addOperation(blocksB[4], withKey: keyB) 176 | subGroupQueue.addOperation(blocksC[4], withKey: keyC) 177 | subGroupQueue.addOperation(blocksA[5], withKey: keyA) 178 | subGroupQueue.addOperation(blocksB[5], withKey: keyB) 179 | subGroupQueue.addOperation(blocksC[5], withKey: keyC) 180 | 181 | subGroupQueue.isSuspended = false 182 | 183 | subGroupQueue.waitUntilAllOperationsAreFinished() 184 | 185 | XCTAssert(subGroupQueue[keyA].count == 0) 186 | XCTAssert(subGroupQueue[keyB].count == 0) 187 | XCTAssert(subGroupQueue[keyC].count == 0) 188 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 189 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 190 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 191 | } 192 | 193 | // MARK: - mixed 194 | 195 | func testMixedAddOperations_withSingleGroup_mustExecuteEachGroupSerially() { 196 | let (key, value, result) = ("key", "123456", Box("")) 197 | 198 | let blocks = stringAppendingBlocks(for: splitString(value), sharedBox: result) 199 | 200 | subGroupQueue.isSuspended = true 201 | 202 | subGroupQueue.addOperation(BlockOperation(block: blocks[0]), withKey: key) 203 | subGroupQueue.addOperation(BlockOperation(block: blocks[1]), withKey: key) 204 | 205 | subGroupQueue.addOperation(blocks[2], withKey: key) 206 | subGroupQueue.addOperation(blocks[3], withKey: key) 207 | 208 | let op5 = BlockOperation(block: blocks[4]) 209 | let op6 = BlockOperation(block: blocks[5]) 210 | subGroupQueue.addOperations([op5, op6], withKey: key, waitUntilFinished: false) 211 | 212 | subGroupQueue.isSuspended = false 213 | 214 | subGroupQueue.waitUntilAllOperationsAreFinished() 215 | 216 | XCTAssert(subGroupQueue[key].count == 0) 217 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 218 | } 219 | 220 | func testMixedAddOperations_withMutipleGroups_mustExecuteEachGroupSerially() { 221 | let (keyA, valueA, resultA) = ("A", "123456", Box("")) 222 | let (keyB, valueB, resultB) = (1337, "abcdef", Box("")) 223 | let (keyC, valueC, resultC) = (Date(), "ABCDEF", Box("")) 224 | 225 | let blocksA = stringAppendingBlocks(for: splitString(valueA), sharedBox: resultA) 226 | let blocksB = stringAppendingBlocks(for: splitString(valueB), sharedBox: resultB) 227 | let blocksC = stringAppendingBlocks(for: splitString(valueC), sharedBox: resultC) 228 | 229 | let opA5 = BlockOperation(block: blocksA[4]) 230 | let opA6 = BlockOperation(block: blocksA[5]) 231 | 232 | let opB3 = BlockOperation(block: blocksB[2]) 233 | let opB4 = BlockOperation(block: blocksB[3]) 234 | 235 | let opC1 = BlockOperation(block: blocksC[0]) 236 | let opC2 = BlockOperation(block: blocksC[1]) 237 | 238 | subGroupQueue.isSuspended = true 239 | 240 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 241 | subGroupQueue.addOperation(BlockOperation(block: blocksA[0]), withKey: keyA) 242 | subGroupQueue.addOperation(blocksB[0], withKey: keyB) 243 | subGroupQueue.addOperations([opC1, opC2], withKey: keyC, waitUntilFinished: false) 244 | subGroupQueue.addOperation(BlockOperation(block: blocksA[1]), withKey: keyA) 245 | subGroupQueue.addOperation(blocksB[1], withKey: keyB) 246 | subGroupQueue.addOperations([opB3, opB4], withKey: keyB, waitUntilFinished: false) 247 | subGroupQueue.addOperation(blocksA[2], withKey: keyA) 248 | subGroupQueue.addOperation(BlockOperation(block: blocksC[2]), withKey: keyC) 249 | subGroupQueue.addOperation(blocksA[3], withKey: keyA) 250 | subGroupQueue.addOperations([opA5, opA6], withKey: keyA, waitUntilFinished: false) 251 | subGroupQueue.addOperation(BlockOperation(block: blocksC[3]), withKey: keyC) 252 | subGroupQueue.addOperation(BlockOperation(block: blocksB[4]), withKey: keyB) 253 | subGroupQueue.addOperation(blocksC[4], withKey: keyC) 254 | subGroupQueue.addOperation(BlockOperation(block: blocksB[5]), withKey: keyB) 255 | subGroupQueue.addOperation(blocksC[5], withKey: keyC) 256 | 257 | subGroupQueue.isSuspended = false 258 | 259 | subGroupQueue.waitUntilAllOperationsAreFinished() 260 | 261 | XCTAssert(subGroupQueue[keyA].count == 0) 262 | XCTAssert(subGroupQueue[keyB].count == 0) 263 | XCTAssert(subGroupQueue[keyC].count == 0) 264 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 265 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 266 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 267 | } 268 | 269 | // MARK: - subGroupOperations 270 | 271 | func testSubGroupOperations_withExistingSubGroupOperations_shouldReturnOperations() { 272 | let key = "key" 273 | 274 | let ops = stringAppendingBlockOperations(for: splitString("123456"), sharedBox: Box("")) 275 | 276 | subGroupQueue.isSuspended = true 277 | 278 | subGroupQueue.addOperation(ops[0], withKey: key) 279 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<1])) 280 | 281 | subGroupQueue.addOperation(ops[1], withKey: key) 282 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<2])) 283 | 284 | subGroupQueue.addOperation(ops[2], withKey: key) 285 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<3])) 286 | 287 | subGroupQueue.addOperations(Array(ops[3...5]), withKey: key, waitUntilFinished: false) 288 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == ops) 289 | 290 | XCTAssert(subGroupQueue[key].count == 6) 291 | } 292 | 293 | func testSubGroupOperations_withNonExistingSubGroupOperations_shouldReturnEmptyArray() { 294 | let key = "key" 295 | 296 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == []) 297 | XCTAssert(subGroupQueue[key].count == 0) 298 | } 299 | 300 | } 301 | 302 | -------------------------------------------------------------------------------- /Tests/Info-OSX.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/SubGroupOperationQueueTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubGroupOperationQueueTests.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 27/03/16. 6 | // Copyright © 2016 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import APNSubGroupOperationQueue 11 | 12 | class SubGroupOperationQueueTestsTests: XCTestCase { 13 | 14 | var subGroupQueue: SubGroupOperationQueue! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | subGroupQueue = SubGroupOperationQueue() 20 | subGroupQueue.maxConcurrentOperationCount = OperationQueue.defaultMaxConcurrentOperationCount 21 | } 22 | 23 | override func tearDown() { 24 | subGroupQueue = nil 25 | 26 | super.tearDown() 27 | } 28 | 29 | // MARK: - addOperation 30 | 31 | func testAddOperation_withSingleGroup_mustExecuteSerially() { 32 | let (key, value, result) = ("key", "123456", Box("")) 33 | 34 | let ops = stringAppendingBlockOperations(for: splitString(value), sharedBox: result) 35 | 36 | subGroupQueue.isSuspended = true 37 | 38 | ops.forEach { subGroupQueue.addOperation($0, withKey: key) } 39 | 40 | subGroupQueue.isSuspended = false 41 | 42 | subGroupQueue.waitUntilAllOperationsAreFinished() 43 | 44 | XCTAssert(subGroupQueue[key].count == 0) 45 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 46 | } 47 | 48 | func testAddOperation_withMutipleGroups_mustExecuteEachGroupSerially() { 49 | let (keyA, valueA, resultA) = ("keyA", "123456", Box("")) 50 | let (keyB, valueB, resultB) = ("keyB", "abcdef", Box("")) 51 | let (keyC, valueC, resultC) = ("keyC", "ABCDEF", Box("")) 52 | 53 | let opsA = stringAppendingBlockOperations(for: splitString(valueA), sharedBox: resultA) 54 | let opsB = stringAppendingBlockOperations(for: splitString(valueB), sharedBox: resultB) 55 | let opsC = stringAppendingBlockOperations(for: splitString(valueC), sharedBox: resultC) 56 | 57 | subGroupQueue.isSuspended = true 58 | 59 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 60 | subGroupQueue.addOperation(opsA[0], withKey: keyA) 61 | subGroupQueue.addOperation(opsB[0], withKey: keyB) 62 | subGroupQueue.addOperation(opsC[0], withKey: keyC) 63 | subGroupQueue.addOperation(opsA[1], withKey: keyA) 64 | subGroupQueue.addOperation(opsB[1], withKey: keyB) 65 | subGroupQueue.addOperation(opsB[2], withKey: keyB) 66 | subGroupQueue.addOperation(opsA[2], withKey: keyA) 67 | subGroupQueue.addOperation(opsC[1], withKey: keyC) 68 | subGroupQueue.addOperation(opsC[2], withKey: keyC) 69 | subGroupQueue.addOperation(opsA[3], withKey: keyA) 70 | subGroupQueue.addOperation(opsB[3], withKey: keyB) 71 | subGroupQueue.addOperation(opsA[4], withKey: keyA) 72 | subGroupQueue.addOperation(opsC[3], withKey: keyC) 73 | subGroupQueue.addOperation(opsB[4], withKey: keyB) 74 | subGroupQueue.addOperation(opsC[4], withKey: keyC) 75 | subGroupQueue.addOperation(opsA[5], withKey: keyA) 76 | subGroupQueue.addOperation(opsB[5], withKey: keyB) 77 | subGroupQueue.addOperation(opsC[5], withKey: keyC) 78 | 79 | subGroupQueue.isSuspended = false 80 | 81 | subGroupQueue.waitUntilAllOperationsAreFinished() 82 | 83 | XCTAssert(subGroupQueue[keyA].count == 0) 84 | XCTAssert(subGroupQueue[keyB].count == 0) 85 | XCTAssert(subGroupQueue[keyC].count == 0) 86 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 87 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 88 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 89 | } 90 | 91 | // MARK: - addOperations 92 | 93 | func testAddOperations_withSingleGroup_mustExecuteSerially() { 94 | let (key, value, result) = ("key", "123456", Box("")) 95 | 96 | let ops = stringAppendingBlockOperations(for: splitString(value), sharedBox: result) 97 | 98 | subGroupQueue.addOperations(ops, withKey: key, waitUntilFinished: true) 99 | 100 | XCTAssert(subGroupQueue[key].count == 0) 101 | XCTAssert(result.value == value, "\(result) didn't match expected value \(value)") 102 | } 103 | 104 | func testAddOperations_withMutipleGroups_mustExecuteEachGroupSerially() { 105 | let (keyA, valueA, resultA) = ("keyA", "123456", Box("")) 106 | let (keyB, valueB, resultB) = ("keyB", "abcdef", Box("")) 107 | let (keyC, valueC, resultC) = ("keyC", "ABCDEF", Box("")) 108 | 109 | let opsA = stringAppendingBlockOperations(for: splitString(valueA), sharedBox: resultA) 110 | let opsB = stringAppendingBlockOperations(for: splitString(valueB), sharedBox: resultB) 111 | let opsC = stringAppendingBlockOperations(for: splitString(valueC), sharedBox: resultC) 112 | 113 | subGroupQueue.isSuspended = true 114 | 115 | subGroupQueue.addOperations(opsA, withKey: keyA, waitUntilFinished: false) 116 | subGroupQueue.addOperations(opsB, withKey: keyB, waitUntilFinished: false) 117 | subGroupQueue.addOperations(opsC, withKey: keyC, waitUntilFinished: false) 118 | 119 | subGroupQueue.isSuspended = false 120 | 121 | subGroupQueue.waitUntilAllOperationsAreFinished() 122 | 123 | XCTAssert(subGroupQueue[keyA].count == 0) 124 | XCTAssert(subGroupQueue[keyB].count == 0) 125 | XCTAssert(subGroupQueue[keyC].count == 0) 126 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 127 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 128 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 129 | } 130 | 131 | // MARK: - addOperation 132 | 133 | func testAddOperationWithBlock_withSingleGroup_mustExecuteSerially() { 134 | let (key, value, result) = ("key", "123456", Box("")) 135 | 136 | let blocks = stringAppendingBlocks(for: splitString(value), sharedBox: result) 137 | 138 | subGroupQueue.isSuspended = true 139 | 140 | blocks.forEach { subGroupQueue.addOperation($0, withKey: key) } 141 | 142 | subGroupQueue.isSuspended = false 143 | 144 | subGroupQueue.waitUntilAllOperationsAreFinished() 145 | 146 | XCTAssert(subGroupQueue[key].count == 0) 147 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 148 | } 149 | 150 | func testAddOperationWithBlock_withMutipleGroups_mustExecuteEachGroupSerially() { 151 | let (keyA, valueA, resultA) = ("keyA", "123456", Box("")) 152 | let (keyB, valueB, resultB) = ("keyB", "abcdef", Box("")) 153 | let (keyC, valueC, resultC) = ("keyC", "ABCDEF", Box("")) 154 | 155 | let blocksA = stringAppendingBlocks(for: splitString(valueA), sharedBox: resultA) 156 | let blocksB = stringAppendingBlocks(for: splitString(valueB), sharedBox: resultB) 157 | let blocksC = stringAppendingBlocks(for: splitString(valueC), sharedBox: resultC) 158 | 159 | subGroupQueue.isSuspended = true 160 | 161 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 162 | subGroupQueue.addOperation(blocksA[0], withKey: keyA) 163 | subGroupQueue.addOperation(blocksB[0], withKey: keyB) 164 | subGroupQueue.addOperation(blocksC[0], withKey: keyC) 165 | subGroupQueue.addOperation(blocksA[1], withKey: keyA) 166 | subGroupQueue.addOperation(blocksB[1], withKey: keyB) 167 | subGroupQueue.addOperation(blocksB[2], withKey: keyB) 168 | subGroupQueue.addOperation(blocksA[2], withKey: keyA) 169 | subGroupQueue.addOperation(blocksC[1], withKey: keyC) 170 | subGroupQueue.addOperation(blocksC[2], withKey: keyC) 171 | subGroupQueue.addOperation(blocksA[3], withKey: keyA) 172 | subGroupQueue.addOperation(blocksB[3], withKey: keyB) 173 | subGroupQueue.addOperation(blocksA[4], withKey: keyA) 174 | subGroupQueue.addOperation(blocksC[3], withKey: keyC) 175 | subGroupQueue.addOperation(blocksB[4], withKey: keyB) 176 | subGroupQueue.addOperation(blocksC[4], withKey: keyC) 177 | subGroupQueue.addOperation(blocksA[5], withKey: keyA) 178 | subGroupQueue.addOperation(blocksB[5], withKey: keyB) 179 | subGroupQueue.addOperation(blocksC[5], withKey: keyC) 180 | 181 | subGroupQueue.isSuspended = false 182 | 183 | subGroupQueue.waitUntilAllOperationsAreFinished() 184 | 185 | XCTAssert(subGroupQueue[keyA].count == 0) 186 | XCTAssert(subGroupQueue[keyB].count == 0) 187 | XCTAssert(subGroupQueue[keyC].count == 0) 188 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 189 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 190 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 191 | } 192 | 193 | // MARK: - mixed 194 | 195 | func testMixedAddOperations_withSingleGroup_mustExecuteEachGroupSerially() { 196 | let (key, value, result) = ("key", "123456", Box("")) 197 | 198 | let blocks = stringAppendingBlocks(for: splitString(value), sharedBox: result) 199 | 200 | subGroupQueue.isSuspended = true 201 | 202 | subGroupQueue.addOperation(BlockOperation(block: blocks[0]), withKey: key) 203 | subGroupQueue.addOperation(BlockOperation(block: blocks[1]), withKey: key) 204 | 205 | subGroupQueue.addOperation(blocks[2], withKey: key) 206 | subGroupQueue.addOperation(blocks[3], withKey: key) 207 | 208 | let op5 = BlockOperation(block: blocks[4]) 209 | let op6 = BlockOperation(block: blocks[5]) 210 | subGroupQueue.addOperations([op5, op6], withKey: key, waitUntilFinished: false) 211 | 212 | subGroupQueue.isSuspended = false 213 | 214 | subGroupQueue.waitUntilAllOperationsAreFinished() 215 | 216 | XCTAssert(subGroupQueue[key].count == 0) 217 | XCTAssert(result.value == value, "\(result.value) didn't match expected value \(value)") 218 | } 219 | 220 | func testMixedAddOperations_withMutipleGroups_mustExecuteEachGroupSerially() { 221 | let (keyA, valueA, resultA) = ("keyA", "123456", Box("")) 222 | let (keyB, valueB, resultB) = ("keyB", "abcdef", Box("")) 223 | let (keyC, valueC, resultC) = ("keyC", "ABCDEF", Box("")) 224 | 225 | let blocksA = stringAppendingBlocks(for: splitString(valueA), sharedBox: resultA) 226 | let blocksB = stringAppendingBlocks(for: splitString(valueB), sharedBox: resultB) 227 | let blocksC = stringAppendingBlocks(for: splitString(valueC), sharedBox: resultC) 228 | 229 | let opA5 = BlockOperation(block: blocksA[4]) 230 | let opA6 = BlockOperation(block: blocksA[5]) 231 | 232 | let opB3 = BlockOperation(block: blocksB[2]) 233 | let opB4 = BlockOperation(block: blocksB[3]) 234 | 235 | let opC1 = BlockOperation(block: blocksC[0]) 236 | let opC2 = BlockOperation(block: blocksC[1]) 237 | 238 | subGroupQueue.isSuspended = true 239 | 240 | // schedule them in order *inside* each subgroup, but *shuffled* between subgroups 241 | subGroupQueue.addOperation(BlockOperation(block: blocksA[0]), withKey: keyA) 242 | subGroupQueue.addOperation(blocksB[0], withKey: keyB) 243 | subGroupQueue.addOperations([opC1, opC2], withKey: keyC, waitUntilFinished: false) 244 | subGroupQueue.addOperation(BlockOperation(block: blocksA[1]), withKey: keyA) 245 | subGroupQueue.addOperation(blocksB[1], withKey: keyB) 246 | subGroupQueue.addOperations([opB3, opB4], withKey: keyB, waitUntilFinished: false) 247 | subGroupQueue.addOperation(blocksA[2], withKey: keyA) 248 | subGroupQueue.addOperation(BlockOperation(block: blocksC[2]), withKey: keyC) 249 | subGroupQueue.addOperation(blocksA[3], withKey: keyA) 250 | subGroupQueue.addOperations([opA5, opA6], withKey: keyA, waitUntilFinished: false) 251 | subGroupQueue.addOperation(BlockOperation(block: blocksC[3]), withKey: keyC) 252 | subGroupQueue.addOperation(BlockOperation(block: blocksB[4]), withKey: keyB) 253 | subGroupQueue.addOperation(blocksC[4], withKey: keyC) 254 | subGroupQueue.addOperation(BlockOperation(block: blocksB[5]), withKey: keyB) 255 | subGroupQueue.addOperation(blocksC[5], withKey: keyC) 256 | 257 | subGroupQueue.isSuspended = false 258 | 259 | subGroupQueue.waitUntilAllOperationsAreFinished() 260 | 261 | XCTAssert(subGroupQueue[keyA].count == 0) 262 | XCTAssert(subGroupQueue[keyB].count == 0) 263 | XCTAssert(subGroupQueue[keyC].count == 0) 264 | XCTAssert(resultA.value == valueA, "\(resultA.value) didn't match expected value \(valueA)") 265 | XCTAssert(resultB.value == valueB, "\(resultB.value) didn't match expected value \(valueB)") 266 | XCTAssert(resultC.value == valueC, "\(resultC.value) didn't match expected value \(valueC)") 267 | } 268 | 269 | // MARK: - subGroupOperations 270 | 271 | func testSubGroupOperations_withExistingSubGroupOperations_shouldReturnOperations() { 272 | let key = "key" 273 | 274 | let ops = stringAppendingBlockOperations(for: splitString("123456"), sharedBox: Box("")) 275 | 276 | subGroupQueue.isSuspended = true 277 | 278 | subGroupQueue.addOperation(ops[0], withKey: key) 279 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<1])) 280 | 281 | subGroupQueue.addOperation(ops[1], withKey: key) 282 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<2])) 283 | 284 | subGroupQueue.addOperation(ops[2], withKey: key) 285 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == Array(ops[0..<3])) 286 | 287 | subGroupQueue.addOperations(Array(ops[3...5]), withKey: key, waitUntilFinished: false) 288 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == ops) 289 | 290 | XCTAssert(subGroupQueue[key].count == 6) 291 | } 292 | 293 | func testSubGroupOperations_withNonExistingSubGroupOperations_shouldReturnEmptyArray() { 294 | let key = "key" 295 | 296 | XCTAssert(subGroupQueue.subGroupOperations(forKey: key) == []) 297 | XCTAssert(subGroupQueue[key].count == 0) 298 | } 299 | 300 | } 301 | 302 | -------------------------------------------------------------------------------- /Tests/TestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestUtils.swift 3 | // APNSubGroupOperationQueue 4 | // 5 | // Created by André Pacheco Neves on 12/02/2017. 6 | // Copyright © 2017 André Pacheco Neves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Box { 12 | var value: T 13 | init(_ v: T) { value = v } 14 | } 15 | 16 | func splitString(_ string: String) -> [String] { 17 | return string.map { String($0) } 18 | } 19 | 20 | func stringAppendingBlocks(for strings: [String], sharedBox: Box) -> [() -> Void] { 21 | return strings.map { s in { sharedBox.value += s } } 22 | } 23 | 24 | func stringAppendingBlockOperations(for strings: [String], sharedBox: Box) -> [BlockOperation] { 25 | return strings.map { s in BlockOperation(block: { sharedBox.value += s }) } 26 | } 27 | --------------------------------------------------------------------------------