├── .gitignore ├── .travis.yml ├── DRBOperationTree.podspec ├── DRBOperationTree.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── Tests-OSX.xcscheme │ └── Tests-iOS.xcscheme ├── DRBOperationTree.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── DRBOperationTree.xccheckout ├── DRBOperationTree ├── DRBOperationTree.h └── DRBOperationTree.m ├── Example ├── .gitignore ├── CREDITS ├── Example.xcodeproj │ └── project.pbxproj ├── Example │ ├── DRBAppDelegate.h │ ├── DRBAppDelegate.m │ ├── DRBCookbook.h │ ├── DRBCookbook.m │ ├── DRBCookbookProvider.h │ ├── DRBCookbookProvider.m │ ├── DRBDetailViewController.h │ ├── DRBDetailViewController.m │ ├── DRBIngredient.h │ ├── DRBIngredient.m │ ├── DRBIngredientImageProvider.h │ ├── DRBIngredientImageProvider.m │ ├── DRBIngredientProvider.h │ ├── DRBIngredientProvider.m │ ├── DRBMasterViewController.h │ ├── DRBMasterViewController.m │ ├── DRBRecipe.h │ ├── DRBRecipe.m │ ├── DRBRecipeImageProvider.h │ ├── DRBRecipeImageProvider.m │ ├── DRBRecipeProvider.h │ ├── DRBRecipeProvider.m │ ├── Example-Info.plist │ ├── Example-Prefix.pch │ ├── Example.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── Example.xcdatamodel │ │ │ └── contents │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── ExampleTests │ ├── ExampleTests-Info.plist │ ├── ExampleTests.m │ └── en.lproj │ │ └── InfoPlist.strings ├── Podfile └── cassette.json ├── LICENSE ├── Makefile ├── README.md ├── Tests-OSX └── Tests-OSX-Info.plist ├── Tests-iOS ├── DRBOperationTreeTests.m └── Tests-iOS-Info.plist ├── Tests └── DRBOperationTreeTests.m ├── Vendor ├── XCTestCase+SRTAdditions.h └── XCTestCase+SRTAdditions.m ├── cookbook.png └── cookbook.viz /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | script: brew update; brew upgrade xctool; make test_ios 3 | -------------------------------------------------------------------------------- /DRBOperationTree.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "DRBOperationTree" 3 | s.version = "0.0.1" 4 | s.summary = "DRBOperationTree is an iOS and OSX API to organize NSOperations into a tree" 5 | s.description = <<-DESC 6 | DRBOperationTree is an iOS and OSX API to organize work (NSOperations) into a tree such that the output of each parent is passed to it's children for further processing. 7 | DESC 8 | 9 | s.homepage = "https://github.com/dstnbrkr/DRBOperationTree" 10 | s.license = { :type => 'MIT', :file => 'LICENSE' } 11 | s.author = { "Dustin Barker" => "dustin.barker@gmail.com" } 12 | 13 | s.ios.deployment_target = '5.0' 14 | s.osx.deployment_target = '10.7' 15 | 16 | s.source = { :git => "https://github.com/dstnbrkr/DRBOperationTree.git", :tag => "0.0.1" } 17 | 18 | s.source_files = 'DRBOperationTree', 'DRBOperationTree/**/*.{h,m}' 19 | s.requires_arc = true 20 | end 21 | -------------------------------------------------------------------------------- /DRBOperationTree.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B30DA5F7185BF83A00D02C80 /* DRBOperationTree.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5F5185BF83A00D02C80 /* DRBOperationTree.m */; }; 11 | B30DA5FA185BF85C00D02C80 /* DRBOperationTreeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5F9185BF85C00D02C80 /* DRBOperationTreeTests.m */; }; 12 | B30DA5FE185BF87200D02C80 /* XCTestCase+SRTAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5FD185BF87200D02C80 /* XCTestCase+SRTAdditions.m */; }; 13 | B30DA604185BF98E00D02C80 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3B72C10185BF3B4005406E9 /* XCTest.framework */; }; 14 | B30DA611185BF9A000D02C80 /* DRBOperationTreeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5F9185BF85C00D02C80 /* DRBOperationTreeTests.m */; }; 15 | B30DA612185BF9A300D02C80 /* DRBOperationTree.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5F5185BF83A00D02C80 /* DRBOperationTree.m */; }; 16 | B3B72C11185BF3B4005406E9 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3B72C10185BF3B4005406E9 /* XCTest.framework */; }; 17 | B3B72C13185BF3B4005406E9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3B72C12185BF3B4005406E9 /* Foundation.framework */; }; 18 | B3C3E807185BFB7000C1FC7E /* XCTestCase+SRTAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B30DA5FD185BF87200D02C80 /* XCTestCase+SRTAdditions.m */; }; 19 | B3C3E809185C0A0100C1FC7E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E808185C0A0100C1FC7E /* Foundation.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | B30DA5F5185BF83A00D02C80 /* DRBOperationTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DRBOperationTree.m; path = DRBOperationTree/DRBOperationTree.m; sourceTree = ""; }; 24 | B30DA5F6185BF83A00D02C80 /* DRBOperationTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DRBOperationTree.h; path = DRBOperationTree/DRBOperationTree.h; sourceTree = ""; }; 25 | B30DA5F9185BF85C00D02C80 /* DRBOperationTreeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DRBOperationTreeTests.m; path = Tests/DRBOperationTreeTests.m; sourceTree = ""; }; 26 | B30DA5FC185BF87200D02C80 /* XCTestCase+SRTAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "XCTestCase+SRTAdditions.h"; path = "Vendor/XCTestCase+SRTAdditions.h"; sourceTree = ""; }; 27 | B30DA5FD185BF87200D02C80 /* XCTestCase+SRTAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "XCTestCase+SRTAdditions.m"; path = "Vendor/XCTestCase+SRTAdditions.m"; sourceTree = ""; }; 28 | B30DA603185BF98E00D02C80 /* Tests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | B30DA607185BF98E00D02C80 /* Tests-OSX-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Tests-OSX-Info.plist"; path = "Tests-OSX/Tests-OSX-Info.plist"; sourceTree = ""; }; 30 | B3B72C0D185BF3B4005406E9 /* Tests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | B3B72C10185BF3B4005406E9 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 32 | B3B72C12185BF3B4005406E9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 33 | B3B72C18185BF3B4005406E9 /* Tests-iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Tests-iOS-Info.plist"; path = "Tests-iOS/Tests-iOS-Info.plist"; sourceTree = ""; }; 34 | B3C3E808185C0A0100C1FC7E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | B30DA600185BF98E00D02C80 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | B3C3E809185C0A0100C1FC7E /* Foundation.framework in Frameworks */, 43 | B30DA604185BF98E00D02C80 /* XCTest.framework in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | B3B72C0A185BF3B4005406E9 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | B3B72C11185BF3B4005406E9 /* XCTest.framework in Frameworks */, 52 | B3B72C13185BF3B4005406E9 /* Foundation.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | B30DA5F4185BF82600D02C80 /* DRBOperationTree */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | B30DA5F6185BF83A00D02C80 /* DRBOperationTree.h */, 63 | B30DA5F5185BF83A00D02C80 /* DRBOperationTree.m */, 64 | ); 65 | name = DRBOperationTree; 66 | sourceTree = ""; 67 | }; 68 | B30DA5F8185BF85200D02C80 /* Tests */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | B30DA5F9185BF85C00D02C80 /* DRBOperationTreeTests.m */, 72 | B3B72C18185BF3B4005406E9 /* Tests-iOS-Info.plist */, 73 | B30DA607185BF98E00D02C80 /* Tests-OSX-Info.plist */, 74 | ); 75 | name = Tests; 76 | sourceTree = ""; 77 | }; 78 | B30DA5FB185BF86600D02C80 /* Vendor */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | B30DA5FC185BF87200D02C80 /* XCTestCase+SRTAdditions.h */, 82 | B30DA5FD185BF87200D02C80 /* XCTestCase+SRTAdditions.m */, 83 | ); 84 | name = Vendor; 85 | sourceTree = ""; 86 | }; 87 | B3B72C02185BF3A5005406E9 = { 88 | isa = PBXGroup; 89 | children = ( 90 | B30DA5F4185BF82600D02C80 /* DRBOperationTree */, 91 | B30DA5F8185BF85200D02C80 /* Tests */, 92 | B3B72C0F185BF3B4005406E9 /* Frameworks */, 93 | B3B72C0E185BF3B4005406E9 /* Products */, 94 | B30DA5FB185BF86600D02C80 /* Vendor */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | B3B72C0E185BF3B4005406E9 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | B3B72C0D185BF3B4005406E9 /* Tests-iOS.xctest */, 102 | B30DA603185BF98E00D02C80 /* Tests-OSX.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | B3B72C0F185BF3B4005406E9 /* Frameworks */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | B3C3E808185C0A0100C1FC7E /* Foundation.framework */, 111 | B3B72C10185BF3B4005406E9 /* XCTest.framework */, 112 | B3B72C12185BF3B4005406E9 /* Foundation.framework */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | B30DA602185BF98E00D02C80 /* Tests-OSX */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = B30DA60E185BF98F00D02C80 /* Build configuration list for PBXNativeTarget "Tests-OSX" */; 123 | buildPhases = ( 124 | B30DA5FF185BF98E00D02C80 /* Sources */, 125 | B30DA600185BF98E00D02C80 /* Frameworks */, 126 | B30DA601185BF98E00D02C80 /* Resources */, 127 | ); 128 | buildRules = ( 129 | ); 130 | dependencies = ( 131 | ); 132 | name = "Tests-OSX"; 133 | productName = "Tests-OSX"; 134 | productReference = B30DA603185BF98E00D02C80 /* Tests-OSX.xctest */; 135 | productType = "com.apple.product-type.bundle.unit-test"; 136 | }; 137 | B3B72C0C185BF3B4005406E9 /* Tests-iOS */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = B3B72C1F185BF3B4005406E9 /* Build configuration list for PBXNativeTarget "Tests-iOS" */; 140 | buildPhases = ( 141 | B3B72C09185BF3B4005406E9 /* Sources */, 142 | B3B72C0A185BF3B4005406E9 /* Frameworks */, 143 | B3B72C0B185BF3B4005406E9 /* Resources */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = "Tests-iOS"; 150 | productName = "Tests-iOS"; 151 | productReference = B3B72C0D185BF3B4005406E9 /* Tests-iOS.xctest */; 152 | productType = "com.apple.product-type.bundle.unit-test"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | B3B72C03185BF3A5005406E9 /* Project object */ = { 158 | isa = PBXProject; 159 | attributes = { 160 | LastUpgradeCheck = 0500; 161 | }; 162 | buildConfigurationList = B3B72C06185BF3A5005406E9 /* Build configuration list for PBXProject "DRBOperationTree" */; 163 | compatibilityVersion = "Xcode 3.2"; 164 | developmentRegion = English; 165 | hasScannedForEncodings = 0; 166 | knownRegions = ( 167 | en, 168 | ); 169 | mainGroup = B3B72C02185BF3A5005406E9; 170 | productRefGroup = B3B72C0E185BF3B4005406E9 /* Products */; 171 | projectDirPath = ""; 172 | projectRoot = ""; 173 | targets = ( 174 | B3B72C0C185BF3B4005406E9 /* Tests-iOS */, 175 | B30DA602185BF98E00D02C80 /* Tests-OSX */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | B30DA601185BF98E00D02C80 /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | B3B72C0B185BF3B4005406E9 /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXResourcesBuildPhase section */ 196 | 197 | /* Begin PBXSourcesBuildPhase section */ 198 | B30DA5FF185BF98E00D02C80 /* Sources */ = { 199 | isa = PBXSourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | B3C3E807185BFB7000C1FC7E /* XCTestCase+SRTAdditions.m in Sources */, 203 | B30DA612185BF9A300D02C80 /* DRBOperationTree.m in Sources */, 204 | B30DA611185BF9A000D02C80 /* DRBOperationTreeTests.m in Sources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | B3B72C09185BF3B4005406E9 /* Sources */ = { 209 | isa = PBXSourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | B30DA5FE185BF87200D02C80 /* XCTestCase+SRTAdditions.m in Sources */, 213 | B30DA5FA185BF85C00D02C80 /* DRBOperationTreeTests.m in Sources */, 214 | B30DA5F7185BF83A00D02C80 /* DRBOperationTree.m in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | B30DA60F185BF98F00D02C80 /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 226 | CLANG_CXX_LIBRARY = "libc++"; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 231 | CLANG_WARN_EMPTY_BODY = YES; 232 | CLANG_WARN_ENUM_CONVERSION = YES; 233 | CLANG_WARN_INT_CONVERSION = YES; 234 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 235 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 236 | COPY_PHASE_STRIP = NO; 237 | FRAMEWORK_SEARCH_PATHS = ( 238 | "$(DEVELOPER_FRAMEWORKS_DIR)", 239 | "$(inherited)", 240 | ); 241 | GCC_C_LANGUAGE_STANDARD = gnu99; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 244 | GCC_OPTIMIZATION_LEVEL = 0; 245 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 246 | GCC_PREFIX_HEADER = ""; 247 | GCC_PREPROCESSOR_DEFINITIONS = ( 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | INFOPLIST_FILE = "Tests-OSX/Tests-OSX-Info.plist"; 259 | MACOSX_DEPLOYMENT_TARGET = 10.8; 260 | ONLY_ACTIVE_ARCH = YES; 261 | OTHER_LDFLAGS = "$(inherited)"; 262 | PRODUCT_NAME = "$(TARGET_NAME)"; 263 | SDKROOT = macosx; 264 | WRAPPER_EXTENSION = xctest; 265 | }; 266 | name = Debug; 267 | }; 268 | B30DA610185BF98F00D02C80 /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ALWAYS_SEARCH_USER_PATHS = NO; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_OBJC_ARC = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = YES; 284 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 285 | ENABLE_NS_ASSERTIONS = NO; 286 | FRAMEWORK_SEARCH_PATHS = ( 287 | "$(DEVELOPER_FRAMEWORKS_DIR)", 288 | "$(inherited)", 289 | ); 290 | GCC_C_LANGUAGE_STANDARD = gnu99; 291 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 292 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 293 | GCC_PREFIX_HEADER = ""; 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | INFOPLIST_FILE = "Tests-OSX/Tests-OSX-Info.plist"; 301 | MACOSX_DEPLOYMENT_TARGET = 10.8; 302 | OTHER_LDFLAGS = "$(inherited)"; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SDKROOT = macosx; 305 | WRAPPER_EXTENSION = xctest; 306 | }; 307 | name = Release; 308 | }; 309 | B3B72C07185BF3A5005406E9 /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | }; 313 | name = Debug; 314 | }; 315 | B3B72C08185BF3A5005406E9 /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | }; 319 | name = Release; 320 | }; 321 | B3B72C20185BF3B4005406E9 /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | COPY_PHASE_STRIP = NO; 339 | FRAMEWORK_SEARCH_PATHS = ( 340 | "$(SDKROOT)/Developer/Library/Frameworks", 341 | "$(inherited)", 342 | "$(DEVELOPER_FRAMEWORKS_DIR)", 343 | ); 344 | GCC_C_LANGUAGE_STANDARD = gnu99; 345 | GCC_DYNAMIC_NO_PIC = NO; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 348 | GCC_PREFIX_HEADER = ""; 349 | GCC_PREPROCESSOR_DEFINITIONS = ( 350 | "DEBUG=1", 351 | "$(inherited)", 352 | ); 353 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | INFOPLIST_FILE = "Tests-iOS/Tests-iOS-Info.plist"; 361 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 362 | ONLY_ACTIVE_ARCH = YES; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SDKROOT = iphoneos; 365 | WRAPPER_EXTENSION = xctest; 366 | }; 367 | name = Debug; 368 | }; 369 | B3B72C21185BF3B4005406E9 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 374 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 375 | CLANG_CXX_LIBRARY = "libc++"; 376 | CLANG_ENABLE_MODULES = YES; 377 | CLANG_ENABLE_OBJC_ARC = YES; 378 | CLANG_WARN_BOOL_CONVERSION = YES; 379 | CLANG_WARN_CONSTANT_CONVERSION = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INT_CONVERSION = YES; 384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | COPY_PHASE_STRIP = YES; 387 | ENABLE_NS_ASSERTIONS = NO; 388 | FRAMEWORK_SEARCH_PATHS = ( 389 | "$(SDKROOT)/Developer/Library/Frameworks", 390 | "$(inherited)", 391 | "$(DEVELOPER_FRAMEWORKS_DIR)", 392 | ); 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 395 | GCC_PREFIX_HEADER = ""; 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | INFOPLIST_FILE = "Tests-iOS/Tests-iOS-Info.plist"; 403 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SDKROOT = iphoneos; 406 | VALIDATE_PRODUCT = YES; 407 | WRAPPER_EXTENSION = xctest; 408 | }; 409 | name = Release; 410 | }; 411 | /* End XCBuildConfiguration section */ 412 | 413 | /* Begin XCConfigurationList section */ 414 | B30DA60E185BF98F00D02C80 /* Build configuration list for PBXNativeTarget "Tests-OSX" */ = { 415 | isa = XCConfigurationList; 416 | buildConfigurations = ( 417 | B30DA60F185BF98F00D02C80 /* Debug */, 418 | B30DA610185BF98F00D02C80 /* Release */, 419 | ); 420 | defaultConfigurationIsVisible = 0; 421 | defaultConfigurationName = Release; 422 | }; 423 | B3B72C06185BF3A5005406E9 /* Build configuration list for PBXProject "DRBOperationTree" */ = { 424 | isa = XCConfigurationList; 425 | buildConfigurations = ( 426 | B3B72C07185BF3A5005406E9 /* Debug */, 427 | B3B72C08185BF3A5005406E9 /* Release */, 428 | ); 429 | defaultConfigurationIsVisible = 0; 430 | defaultConfigurationName = Release; 431 | }; 432 | B3B72C1F185BF3B4005406E9 /* Build configuration list for PBXNativeTarget "Tests-iOS" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | B3B72C20185BF3B4005406E9 /* Debug */, 436 | B3B72C21185BF3B4005406E9 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | /* End XCConfigurationList section */ 442 | }; 443 | rootObject = B3B72C03185BF3A5005406E9 /* Project object */; 444 | } 445 | -------------------------------------------------------------------------------- /DRBOperationTree.xcodeproj/xcshareddata/xcschemes/Tests-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /DRBOperationTree.xcodeproj/xcshareddata/xcschemes/Tests-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /DRBOperationTree.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DRBOperationTree.xcworkspace/xcshareddata/DRBOperationTree.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | DE886364-C0CB-467E-A627-DF1BC9662B29 9 | IDESourceControlProjectName 10 | DRBOperationTree 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 9574439A-FA79-4EC0-A864-7F674FEAAFB2 14 | ssh://github.com/dstnbrkr/DRBOperationTree.git 15 | 16 | IDESourceControlProjectPath 17 | DRBOperationTree.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 9574439A-FA79-4EC0-A864-7F674FEAAFB2 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/dstnbrkr/DRBOperationTree.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 9574439A-FA79-4EC0-A864-7F674FEAAFB2 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 9574439A-FA79-4EC0-A864-7F674FEAAFB2 36 | IDESourceControlWCCName 37 | DRBOperationTree 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DRBOperationTree/DRBOperationTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBOperationTree.h 3 | // 4 | // Created by Dustin Barker on 12/5/13. 5 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import 26 | 27 | @class DRBOperationTree; 28 | 29 | /** 30 | `DRBOperationProvider` is the interface `DRBOperationTree` interacts with to determine what work needs to be performed in a node. 31 | 32 | An object that implements `DRBOperationProvider` is responsible for: 33 | - mapping an input object to one or more output objects 34 | - mapping each output object to an NSOperation 35 | */ 36 | @protocol DRBOperationProvider 37 | 38 | /** 39 | Maps an input object to one or more output objects. 40 | */ 41 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(id)object completion:(void(^)(NSArray *objects))completion; 42 | 43 | /** 44 | Maps an output object to an operation. 45 | 46 | The operation is responsible for calling `continuation`, which will allow tree proccessing to continue. 47 | */ 48 | - (NSOperation *)operationTree:(DRBOperationTree *)node 49 | operationForObject:(id)object 50 | continuation:(void(^)(id object, void(^completion)()))continuation 51 | failure:(void(^)())failure; 52 | 53 | @end 54 | 55 | /** 56 | `DRBOperationTree` is an iOS and OSX API to organize NSOperations into a tree. 57 | 58 | Each node's output becomes the input for its child nodes. 59 | */ 60 | @interface DRBOperationTree : NSObject 61 | 62 | /** 63 | Convenience constructor to return a `DRBOperationTree` initialized with the default NSOperationQueue. 64 | */ 65 | + (DRBOperationTree *)tree; 66 | 67 | /** 68 | The NSOperationQueue that DRBOperationTree instances will use by default. 69 | */ 70 | + (NSOperationQueue *)defaultOperationQueue; 71 | 72 | /** 73 | Initializes a `DRBOperationTree` with a specific NSOperationQueue 74 | */ 75 | - (id)initWithOperationQueue:(NSOperationQueue *)operationQueue; 76 | 77 | /** 78 | Passes an object to it's child nodes. Completion is called when the level order traversal of all child nodes is complete. 79 | */ 80 | - (void)sendObject:(id)object completion:(void(^)())completion; 81 | 82 | /** 83 | Adds a child node to the node. 84 | */ 85 | - (void)addChild:(DRBOperationTree *)node; 86 | 87 | /** 88 | Maps an input object to output objects and enqueue the corresponding NSOperations for each. 89 | */ 90 | - (void)enqueueOperationsForObject:(id)object completion:(void(^)())completion; 91 | 92 | /** 93 | The `DRBOperationProvider` responsible for mapping input objects to output objects and object to operations. 94 | */ 95 | @property (nonatomic, strong) id provider; 96 | 97 | /** 98 | The NSOperationQueue associated with this node 99 | */ 100 | @property (nonatomic, strong, readonly) NSOperationQueue *operationQueue; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /DRBOperationTree/DRBOperationTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBOperationTree.m 3 | // 4 | // Created by Dustin Barker on 12/5/13. 5 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import "DRBOperationTree.h" 26 | 27 | @interface DRBOperationTree () 28 | @property (nonatomic, strong) NSMutableSet *children; 29 | @property (nonatomic, strong, readwrite) NSOperationQueue *operationQueue; 30 | @property (nonatomic, strong) NSCountedSet *retries; 31 | @end 32 | 33 | static const NSUInteger kARSyncNodeMaxRetries = 3; 34 | 35 | @implementation DRBOperationTree 36 | 37 | + (NSOperationQueue *)defaultOperationQueue 38 | { 39 | static NSOperationQueue *operationQueue; 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | operationQueue = [[NSOperationQueue alloc] init]; 43 | }); 44 | return operationQueue; 45 | } 46 | 47 | + (DRBOperationTree *)tree 48 | { 49 | NSOperationQueue *operationQueue = [self defaultOperationQueue]; 50 | return [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 51 | } 52 | 53 | - (id)initWithOperationQueue:(NSOperationQueue *)operationQueue 54 | { 55 | if ((self = [super init])) { 56 | _operationQueue = operationQueue; 57 | _children = [NSMutableSet set]; 58 | _retries = [NSCountedSet set]; 59 | } 60 | return self; 61 | } 62 | 63 | - (void)addChild:(DRBOperationTree *)node 64 | { 65 | [self.children addObject:node]; 66 | } 67 | 68 | - (void)sendObject:(id)object completion:(void(^)())completion 69 | { 70 | dispatch_group_t group = dispatch_group_create(); 71 | for (DRBOperationTree *child in _children) { 72 | dispatch_group_enter(group); 73 | [child enqueueOperationsForObject:object completion:^{ 74 | dispatch_group_leave(group); 75 | }]; 76 | } 77 | 78 | // the completion block will be called after all child nodes have called their completion block 79 | dispatch_group_notify(group, dispatch_get_main_queue(), completion); 80 | } 81 | 82 | - (void)enqueueOperationForObject:(id)object dispatchGroup:(dispatch_group_t)group 83 | { 84 | dispatch_group_enter(group); 85 | NSOperation *operation = [self.provider operationTree:self 86 | operationForObject:object 87 | continuation:^(id result, void(^completion)()) { 88 | dispatch_async(dispatch_get_main_queue(), ^{ 89 | [self sendObject:result completion:^{ 90 | 91 | // call the completion block associated with this node 92 | if (completion) completion(); 93 | 94 | dispatch_group_leave(group); 95 | }]; 96 | }); 97 | } 98 | failure:^{ 99 | [_retries addObject:object]; 100 | 101 | // retry failed operations 102 | if ([_retries countForObject:object] < kARSyncNodeMaxRetries) { 103 | [self enqueueOperationForObject:object dispatchGroup:group]; 104 | } 105 | 106 | dispatch_group_leave(group); 107 | }]; 108 | [self.operationQueue addOperation:operation]; 109 | } 110 | 111 | - (void)enqueueOperationsForObject:(id)object completion:(void(^)())completion 112 | { 113 | [self.provider operationTree:self objectsForObject:object completion:^(NSArray *objects) { 114 | dispatch_group_t group = dispatch_group_create(); 115 | 116 | for (id object in objects) { 117 | [self enqueueOperationForObject:object dispatchGroup:group]; 118 | } 119 | 120 | dispatch_group_notify(group, dispatch_get_main_queue(), completion); 121 | }]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | Pods 2 | *.xcworkspace 3 | Podfile.lock 4 | 5 | -------------------------------------------------------------------------------- /Example/CREDITS: -------------------------------------------------------------------------------- 1 | 2 | Image Credits 3 | 4 | 5 | http://www.flickr.com/photos/cathydanhphotography/5515494109/sizes/sq/in/photolist-9pomf2-7Efctv-8YnSPF-bvmGur-bDZYAc-e3255k-8qUTHB-hE8DXs-936m4C-8GrcqU-93xN7h-d4mEUG-dQSwz5-d1J1bU-aeHLDf-eHJ1Hd-dWN1am-fJu9Uj-8gGK5A-97PXTY-8b26JD-7Uibix-a2vcgj-892yqr-8pFdkN-8K1yPj-ayL4yi-a2sjaH-a2veXh-ayGmhQ-9whCxv-aFHeB2-7Vjzgc-8pYieT-aYcHr4-9xzZ37-9xwZTV-a2spuk-e48yKG-7MGgwG-aw7Ew6-8rqLg8-bXszPq-9cfWya/ 6 | 7 | CC Some rights reserved by kulinarno 8 | http://www.flickr.com/photos/jhard/5117662053/sizes/sq/in/photolist-8NemCD-8cbTY9-bsenJC-bxjS2B-bxjSg8-aEjfUQ-ey3KN7-7DsV62-avxWCk-8VCfKy-7TuJkg-aXK5Pr-7QnhR7-7Qnhyd-gjMgic-9Hbxug-94k8Ur-9s6GqP-7DwHCy-7DwHu9-bFNDbx-8NemFe-bhRWDP-9dWHUA-7DsUvM-7DwHT3-ebhL93-bMhyJx-bzfb1B-7Us4uP-7QiX7B-dTTLt5/ 9 | 10 | CC Some rights reserved by DBduo Photography 11 | http://www.flickr.com/photos/drb62/1842820851/sizes/sq/in/photolist-3NQWri-4h8hRv-4P3BmQ-5coHY2-5mMWZ9-5ygU3K-5P7ToV-5Qoos3-5SqgLt-63UZtr-6iZart-6ohSet-6ust3y-6F2rjF-6GSoPx-6GWuiw-6MgMJm-6PhPVw-6RPNAv-6SLDLk-6XnfZ8-auikN9-7JRQM2-dd9DqN-bRKPoa-c9LomS-c9LoEh-7UXKN5-c6Yqd9-c6YqfJ-8UrVB5-dYSui7-c9LoRo-ayiXjt-bvuScb-ezzbho-8iJLt6-bMbdAt-9ouCzo-9orzxx-c9LoJw-bPbtc4-bdk9Fe-grG3si-aymGom-aymBfY-c6Yqiw-bAKHyi-9hgiyS-aPAFLM-aPADEx/ 12 | 13 | CC Some rights reserved by lowjumpingfrog 14 | http://www.flickr.com/photos/jenorton/2212742541/sizes/sq/in/photolist-4nwTgt-4zT71i-4LakQc-52a1Lt-5nWyeK-5nWykT-5Njh7w-5PwqNi-5ZZTnc-64xq8w-659En2-6aJk6B-6cjspJ-6dUr7G-6dXHDb-6dXHDd-6dXHDj-6dXHDm-6dXHDo-6dXHDu-6mHVKK-6mHVKX-6mHVL4-6mHVLp-6mHVLv-6mHVLz-6mMAKf-6mMAKh-6mNbAW-6mNbB1-6mNbB9-6mNbBf-6xAxBK-6zwvs4-6KGh1d-6NJDv8-6Pn6dE-6RJfK4-6ZbUsU-78qcSn-7i7hVQ-7rJmE8-7rMkPK-7rNhXJ-7rNS1E-ay66He-9sMjAt-cEoZK1-8epYY4-ciDorC-c9pSQG/ 15 | 16 | CC Some rights reserved by John-Morgan 17 | http://www.flickr.com/photos/aidanmorgan/2256197976/sizes/sq/in/photolist-4rnB47-4ssf7L-4sTipk-4tRhMT-4ytjqx-4yvQVE-4EtQeh-4HPoeh-4QSKMZ-4QSKVv-4S17yg-4S5egG-4TKUwT-4UJZmv-51frqH-51NC7i-5cdcU3-5h6doo-5n2E1m-5tiYoF-5uvade-5uCPbA-5uTxVr-5vb1Pc-5z1mTw-5zRNZ6-5zRP9c-5zW6JA-5PHdTz-5VhxHM-5YjQgi-6e4hRS-6eiJkJ-6eBVoh-6gVtuo-6qUJRp-6xkyYy-6GbbwW-6LReqQ-6WbLa5-6WqBwf-6ZycYy-6ZWAgc-6ZWAKF-7acAcy-7gvJyY-7iY5D6-7wQDs7-859FGb-9yR4vu-dpTZfK/ 18 | 19 | CC Some rights reserved by Stacy Spensley 20 | http://www.flickr.com/photos/notahipster/4605791600/sizes/sq/in/photolist-81ZTo1-9krLcs-eaAyh8-dHASEq-gKeeyp-ddFa54-duaDkf-eimDnM-e9YQd4-9z4rR7-ebE1Tk-d9d4cG-fRS8H2-dnRMTd-dz5tDe-aqde9X-avUPqK-8rrrCS-8ubY1L-9e9Rsh-9ESk2K-eci7KN-7JcSmt-aaPc6G-aaKxqD-abBHE9-hW4zNN-guZ15J-8ZgRgs-8u8StF-dfiw1c-cZqq95-8APBvf-cxrM5y-7yyoTd-7yyqwu-8roemV-aCRjsu-bdeRk2-bdeRkc-bEtVLZ-9cWi5n-9cWhNV-aCNBMe-8rrm5y-guZhPx-foNfY9-9eVsdt-9eYz8j-7yyqxL-7yuAr4/ 21 | 22 | CC Some rights reserved by RobotSkirts 23 | http://www.flickr.com/photos/hackaday/7178625061/sizes/m/in/photolist-bWmk8B-d8211A-csLSgW-eauQmZ-deCPua-8r6mGC-7yMkjL-aXXrbP-9SLJ3g-8MyeUq-8Mvana-dfJGPZ-dfJGC8-dfJGFP-dfJGdM-dfJKBu-dfJGye-dfJGKH-dzFxTK-dwkJbB-7BAuKU-7BAuKW-8vpXg3-8vnc7T-8XNNnd-7KBgJW-7Kxknr-7Kxkke-7KBgMu-8r5m7u-f4gRHE-9X6UXL-aohj4F-7SkZCa-a2ekQk-a2ekVX-8NHyN9-9SjJvZ-9EDbvT-9EDbHH-9EG7mw-9EG7hQ-9EG76f-9EDbCF-9EG6Z1-9EDbDV-9EDbF4-9EG75b-9EDbst-9EDbEM-9EDbq6/ 24 | 25 | CC Some rights reserved by SaucyGlo 26 | http://www.flickr.com/photos/67238971@N04/7198774374/sizes/m/in/photolist-bY8APy-czHhLL-8muU98-8XJeq5-gRtVbL-gRtYq2-gRtZUW-dwd1qG-9HGSWk-anR191-aNttFc-8EgMVQ-bxjR8V-efHGCr-dRfM2J-bY8KKU-bxjRE8-bY8EqY-aWmB9B-aFrC44-eProRY-ePeZwR-ePeZai-euvjJv-heF1z9-hVVnD3-dNyoGv-dkBH5y-dkBF1B-93Bxgh-aYnVze-9zv3YU-ajyqh8-a3RBmc-bCWM8M-7AMGu1-7AHV2n-8WjrUM-aHGiCK-8Wbmqb-av5FEE-aNtujT-d6j6uq-8WcrZD-7SrWgz-b9818v-b981et-b981jk-aNtt26-gzunCw-hJVwpL/ 27 | 28 | http://www.flickr.com/photos/grantguarino/8849306604/sizes/m/in/photolist-etZ1xN-eR3Lan-euuz4A-ccE6ML-dCBcyp-9MizkJ-9Mh126-9MfPgZ-9Meuy8-9MeqZP-9Mhm9j-9Mh11Z-9MewrT-bUgPKy-cCVnv5-9LFCBt-euAHH1-euA5Hj-cR3rzG-euwmcd-9L64y8-euzKLs-eqiN9d-9JhGaX-epnyze-eqiN4f-euvquN-bpGy2a-7URGDJ-euwuxJ-9piNUn-cR3rYh-7UNs2M-9e1c6u-euxYv1-cR3rnd-9JkvNG-8wVxcq-8T7iAo-7TYCst-7CrsH2-g7f4Sa-bY8q8o-bEkpZG-9e3hr5-euB2Qh-cR3sdb-9beYNk-cR3rPG-8W4Zj6-dQB3uF/ 29 | 30 | http://www.flickr.com/photos/araswami/8171256437/sizes/m/in/photolist-ds4PRF-7ykg2t-9cL5L4-7HKXcF-8e7mMy-7UrT1D-dMACgR-7HGUgR-7V1mDN-9cPbdm-7HnktE-cm6NDE-7zMKrV-9Z7321-h9LTRe-ermuhf-bkWn3q-dMGbMY-dMGbGm-dMAC9n-8j9V5g-dZh5k1/ 31 | 32 | http://www.flickr.com/photos/micsten/245380370/sizes/sq/in/photolist-nFD33-pcLkx-si1p7-si3Fg-si3PH-wcorQ-xXwGU-B9Jvj-BPBUo-CCbat-CCtG3-DyN4N-GBuAF-McxVq-Nkgr2-Nkgr6-P67b9-ULYCB-2iYEim-46cjZ8-4e5WFL-4enGXA-4ggn55-4kW8ks-4mZEfX-4w8KE7-4FiPZ1-4Q7V3v-4TtsFH-4UNkQt-4Wc8r1-52L8T9-5dzoip-5dzoit-5jokf6-5k5Nci-5xBQSr-5JxkPY-5Kr7Zm-5TdPzF-62qFAk-64uLnF-6cstme-6pYx75-6qPMjp-6rw2S5-6BXvEj-6CZgep-6CZghV-6CZgmg-6D4qkN/ 33 | 34 | http://www.flickr.com/photos/jeffreyww/4075663345/sizes/m/in/photolist-7d9QHF-7uqWjs-7ur4i5-afyarr-dZSE6H-b6GVjn-bwR9gh/ 35 | 36 | http://www.flickr.com/photos/63723146@N08/8187291243/sizes/m/in/photolist-dtu1rR-8QCgue-g4JMUh-8K63UM-7WMfPQ-7WJ2m2-7WMfG7-97yKJo-dtfSWR-7MHgNN-7WJ2p6-7WJnPP-guEn3d-8K64Wi-9cqNg4-968xd7-965wJ8-c8aWHj-7Yi4mw-bwnLrc-cySNp9-cy6ZGG-9AVHi8-7FvKCq-95hfuq-b4UPPi-fQy3QX-fQy45B-fQKnCN-fQKneh-fwvKAB-fwvHRR-8kxXaz-bAdMwU-bP8rwr-a2q1Lq-9RyN5j/ 37 | 38 | http://www.flickr.com/photos/timmy2s/8328383653/sizes/m/in/photolist-dFX9iB-dvPHYh-cwJRWb-8a9A3B-8jAmd5-9RTJyV-9RWDxj-9RWDaq-9RTJox-9RWE3d-9RWDt7-eZ7sqH-d2noRJ-bBNERc-cwuRZJ-dpabPQ-8HT6WR-brgJFK-9uChxN-8acQwy-7CSE2k-8vUXut-8kgwNV-bnU4QH-dT1H2H-9RWDvh-aMvaQg-9Bb1Re-7z2hAE-bV7kn5-8jvNb4-bEToR1-94WWBu-fFib61-8fLGZ4-am2q6u-bpiuaW-8jXvME-bcnRmx-bvqSXg-aytiwC-8xVqgB-hLBjpE-akvuHv-8VV3ZY-8pcVDD-aMvbFr-dvJ9AH-cswZn3-8VRGzP-8tRoqA/ 39 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 856BB1E4ED6C4979BE10350C /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 532AD9A57F8D45FFAC944895 /* libPods.a */; }; 11 | B3C3E864185E253600C1FC7E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E863185E253600C1FC7E /* Foundation.framework */; }; 12 | B3C3E866185E253600C1FC7E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E865185E253600C1FC7E /* CoreGraphics.framework */; }; 13 | B3C3E868185E253600C1FC7E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E867185E253600C1FC7E /* UIKit.framework */; }; 14 | B3C3E86A185E253600C1FC7E /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E869185E253600C1FC7E /* CoreData.framework */; }; 15 | B3C3E870185E253600C1FC7E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B3C3E86E185E253600C1FC7E /* InfoPlist.strings */; }; 16 | B3C3E872185E253600C1FC7E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E871185E253600C1FC7E /* main.m */; }; 17 | B3C3E876185E253600C1FC7E /* DRBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E875185E253600C1FC7E /* DRBAppDelegate.m */; }; 18 | B3C3E87C185E253600C1FC7E /* Example.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E87A185E253600C1FC7E /* Example.xcdatamodeld */; }; 19 | B3C3E87F185E253600C1FC7E /* DRBMasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E87E185E253600C1FC7E /* DRBMasterViewController.m */; }; 20 | B3C3E882185E253600C1FC7E /* DRBDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E881185E253600C1FC7E /* DRBDetailViewController.m */; }; 21 | B3C3E884185E253600C1FC7E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B3C3E883185E253600C1FC7E /* Images.xcassets */; }; 22 | B3C3E88B185E253600C1FC7E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E88A185E253600C1FC7E /* XCTest.framework */; }; 23 | B3C3E88C185E253600C1FC7E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E863185E253600C1FC7E /* Foundation.framework */; }; 24 | B3C3E88D185E253600C1FC7E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E867185E253600C1FC7E /* UIKit.framework */; }; 25 | B3C3E88E185E253600C1FC7E /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C3E869185E253600C1FC7E /* CoreData.framework */; }; 26 | B3C3E896185E253600C1FC7E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B3C3E894185E253600C1FC7E /* InfoPlist.strings */; }; 27 | B3C3E898185E253600C1FC7E /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E897185E253600C1FC7E /* ExampleTests.m */; }; 28 | B3C3E8A3185E281100C1FC7E /* DRBRecipeProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8A2185E281100C1FC7E /* DRBRecipeProvider.m */; }; 29 | B3C3E8A6185E2C1700C1FC7E /* DRBCookbookProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8A5185E2C1700C1FC7E /* DRBCookbookProvider.m */; }; 30 | B3C3E8A9185E30AE00C1FC7E /* DRBCookbook.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8A8185E30AE00C1FC7E /* DRBCookbook.m */; }; 31 | B3C3E8AF185E30AF00C1FC7E /* DRBIngredient.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8AE185E30AF00C1FC7E /* DRBIngredient.m */; }; 32 | B3C3E8B3185E425300C1FC7E /* cassette.json in Resources */ = {isa = PBXBuildFile; fileRef = B3C3E8B2185E425300C1FC7E /* cassette.json */; }; 33 | B3C3E8B6185E91D100C1FC7E /* DRBRecipeImageProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8B5185E91D100C1FC7E /* DRBRecipeImageProvider.m */; }; 34 | B3C3E8B9185EBDB500C1FC7E /* DRBIngredientProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8B8185EBDB500C1FC7E /* DRBIngredientProvider.m */; }; 35 | B3C3E8C9185EC48100C1FC7E /* DRBRecipe.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8C8185EC48100C1FC7E /* DRBRecipe.m */; }; 36 | B3C3E8CC185ECBD400C1FC7E /* DRBIngredientImageProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3E8CB185ECBD400C1FC7E /* DRBIngredientImageProvider.m */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | B3C3E88F185E253600C1FC7E /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = B3C3E858185E253600C1FC7E /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = B3C3E85F185E253600C1FC7E; 45 | remoteInfo = Example; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 4E42D06C908B4D53A02BA830 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 51 | 532AD9A57F8D45FFAC944895 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | B3C3E860185E253600C1FC7E /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | B3C3E863185E253600C1FC7E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 54 | B3C3E865185E253600C1FC7E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 55 | B3C3E867185E253600C1FC7E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 56 | B3C3E869185E253600C1FC7E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 57 | B3C3E86D185E253600C1FC7E /* Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Example-Info.plist"; sourceTree = ""; }; 58 | B3C3E86F185E253600C1FC7E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 59 | B3C3E871185E253600C1FC7E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 60 | B3C3E873185E253600C1FC7E /* Example-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Prefix.pch"; sourceTree = ""; }; 61 | B3C3E874185E253600C1FC7E /* DRBAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DRBAppDelegate.h; sourceTree = ""; }; 62 | B3C3E875185E253600C1FC7E /* DRBAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DRBAppDelegate.m; sourceTree = ""; }; 63 | B3C3E87B185E253600C1FC7E /* Example.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Example.xcdatamodel; sourceTree = ""; }; 64 | B3C3E87D185E253600C1FC7E /* DRBMasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DRBMasterViewController.h; sourceTree = ""; }; 65 | B3C3E87E185E253600C1FC7E /* DRBMasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DRBMasterViewController.m; sourceTree = ""; }; 66 | B3C3E880185E253600C1FC7E /* DRBDetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DRBDetailViewController.h; sourceTree = ""; }; 67 | B3C3E881185E253600C1FC7E /* DRBDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DRBDetailViewController.m; sourceTree = ""; }; 68 | B3C3E883185E253600C1FC7E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 69 | B3C3E889185E253600C1FC7E /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | B3C3E88A185E253600C1FC7E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 71 | B3C3E893185E253600C1FC7E /* ExampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleTests-Info.plist"; sourceTree = ""; }; 72 | B3C3E895185E253600C1FC7E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 73 | B3C3E897185E253600C1FC7E /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = ""; }; 74 | B3C3E8A1185E281100C1FC7E /* DRBRecipeProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBRecipeProvider.h; sourceTree = ""; }; 75 | B3C3E8A2185E281100C1FC7E /* DRBRecipeProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBRecipeProvider.m; sourceTree = ""; }; 76 | B3C3E8A4185E2C1700C1FC7E /* DRBCookbookProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBCookbookProvider.h; sourceTree = ""; }; 77 | B3C3E8A5185E2C1700C1FC7E /* DRBCookbookProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBCookbookProvider.m; sourceTree = ""; }; 78 | B3C3E8A7185E30AE00C1FC7E /* DRBCookbook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBCookbook.h; sourceTree = ""; }; 79 | B3C3E8A8185E30AE00C1FC7E /* DRBCookbook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBCookbook.m; sourceTree = ""; }; 80 | B3C3E8AD185E30AF00C1FC7E /* DRBIngredient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBIngredient.h; sourceTree = ""; }; 81 | B3C3E8AE185E30AF00C1FC7E /* DRBIngredient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBIngredient.m; sourceTree = ""; }; 82 | B3C3E8B2185E425300C1FC7E /* cassette.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cassette.json; sourceTree = SOURCE_ROOT; }; 83 | B3C3E8B4185E91D100C1FC7E /* DRBRecipeImageProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBRecipeImageProvider.h; sourceTree = ""; }; 84 | B3C3E8B5185E91D100C1FC7E /* DRBRecipeImageProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBRecipeImageProvider.m; sourceTree = ""; }; 85 | B3C3E8B7185EBDB500C1FC7E /* DRBIngredientProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBIngredientProvider.h; sourceTree = ""; }; 86 | B3C3E8B8185EBDB500C1FC7E /* DRBIngredientProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBIngredientProvider.m; sourceTree = ""; }; 87 | B3C3E8C7185EC48100C1FC7E /* DRBRecipe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBRecipe.h; sourceTree = ""; }; 88 | B3C3E8C8185EC48100C1FC7E /* DRBRecipe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBRecipe.m; sourceTree = ""; }; 89 | B3C3E8CA185ECBD400C1FC7E /* DRBIngredientImageProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DRBIngredientImageProvider.h; sourceTree = ""; }; 90 | B3C3E8CB185ECBD400C1FC7E /* DRBIngredientImageProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DRBIngredientImageProvider.m; sourceTree = ""; }; 91 | /* End PBXFileReference section */ 92 | 93 | /* Begin PBXFrameworksBuildPhase section */ 94 | B3C3E85D185E253600C1FC7E /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | B3C3E866185E253600C1FC7E /* CoreGraphics.framework in Frameworks */, 99 | B3C3E86A185E253600C1FC7E /* CoreData.framework in Frameworks */, 100 | B3C3E868185E253600C1FC7E /* UIKit.framework in Frameworks */, 101 | B3C3E864185E253600C1FC7E /* Foundation.framework in Frameworks */, 102 | 856BB1E4ED6C4979BE10350C /* libPods.a in Frameworks */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | B3C3E886185E253600C1FC7E /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | B3C3E88B185E253600C1FC7E /* XCTest.framework in Frameworks */, 111 | B3C3E88E185E253600C1FC7E /* CoreData.framework in Frameworks */, 112 | B3C3E88D185E253600C1FC7E /* UIKit.framework in Frameworks */, 113 | B3C3E88C185E253600C1FC7E /* Foundation.framework in Frameworks */, 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXFrameworksBuildPhase section */ 118 | 119 | /* Begin PBXGroup section */ 120 | B3C3E857185E253600C1FC7E = { 121 | isa = PBXGroup; 122 | children = ( 123 | B3C3E86B185E253600C1FC7E /* Example */, 124 | B3C3E891185E253600C1FC7E /* ExampleTests */, 125 | B3C3E862185E253600C1FC7E /* Frameworks */, 126 | B3C3E861185E253600C1FC7E /* Products */, 127 | 4E42D06C908B4D53A02BA830 /* Pods.xcconfig */, 128 | ); 129 | sourceTree = ""; 130 | }; 131 | B3C3E861185E253600C1FC7E /* Products */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | B3C3E860185E253600C1FC7E /* Example.app */, 135 | B3C3E889185E253600C1FC7E /* ExampleTests.xctest */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | B3C3E862185E253600C1FC7E /* Frameworks */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | B3C3E863185E253600C1FC7E /* Foundation.framework */, 144 | B3C3E865185E253600C1FC7E /* CoreGraphics.framework */, 145 | B3C3E867185E253600C1FC7E /* UIKit.framework */, 146 | B3C3E869185E253600C1FC7E /* CoreData.framework */, 147 | B3C3E88A185E253600C1FC7E /* XCTest.framework */, 148 | 532AD9A57F8D45FFAC944895 /* libPods.a */, 149 | ); 150 | name = Frameworks; 151 | sourceTree = ""; 152 | }; 153 | B3C3E86B185E253600C1FC7E /* Example */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | B3C3E874185E253600C1FC7E /* DRBAppDelegate.h */, 157 | B3C3E875185E253600C1FC7E /* DRBAppDelegate.m */, 158 | B3C3E87D185E253600C1FC7E /* DRBMasterViewController.h */, 159 | B3C3E87E185E253600C1FC7E /* DRBMasterViewController.m */, 160 | B3C3E880185E253600C1FC7E /* DRBDetailViewController.h */, 161 | B3C3E881185E253600C1FC7E /* DRBDetailViewController.m */, 162 | B3C3E8B1185E30C500C1FC7E /* Models */, 163 | B3C3E8B0185E30B300C1FC7E /* Operation Providers */, 164 | B3C3E883185E253600C1FC7E /* Images.xcassets */, 165 | B3C3E87A185E253600C1FC7E /* Example.xcdatamodeld */, 166 | B3C3E86C185E253600C1FC7E /* Supporting Files */, 167 | ); 168 | path = Example; 169 | sourceTree = ""; 170 | }; 171 | B3C3E86C185E253600C1FC7E /* Supporting Files */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | B3C3E8B2185E425300C1FC7E /* cassette.json */, 175 | B3C3E86D185E253600C1FC7E /* Example-Info.plist */, 176 | B3C3E86E185E253600C1FC7E /* InfoPlist.strings */, 177 | B3C3E871185E253600C1FC7E /* main.m */, 178 | B3C3E873185E253600C1FC7E /* Example-Prefix.pch */, 179 | ); 180 | name = "Supporting Files"; 181 | sourceTree = ""; 182 | }; 183 | B3C3E891185E253600C1FC7E /* ExampleTests */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | B3C3E897185E253600C1FC7E /* ExampleTests.m */, 187 | B3C3E892185E253600C1FC7E /* Supporting Files */, 188 | ); 189 | path = ExampleTests; 190 | sourceTree = ""; 191 | }; 192 | B3C3E892185E253600C1FC7E /* Supporting Files */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | B3C3E893185E253600C1FC7E /* ExampleTests-Info.plist */, 196 | B3C3E894185E253600C1FC7E /* InfoPlist.strings */, 197 | ); 198 | name = "Supporting Files"; 199 | sourceTree = ""; 200 | }; 201 | B3C3E8B0185E30B300C1FC7E /* Operation Providers */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | B3C3E8A4185E2C1700C1FC7E /* DRBCookbookProvider.h */, 205 | B3C3E8A5185E2C1700C1FC7E /* DRBCookbookProvider.m */, 206 | B3C3E8A1185E281100C1FC7E /* DRBRecipeProvider.h */, 207 | B3C3E8A2185E281100C1FC7E /* DRBRecipeProvider.m */, 208 | B3C3E8B4185E91D100C1FC7E /* DRBRecipeImageProvider.h */, 209 | B3C3E8B5185E91D100C1FC7E /* DRBRecipeImageProvider.m */, 210 | B3C3E8B7185EBDB500C1FC7E /* DRBIngredientProvider.h */, 211 | B3C3E8B8185EBDB500C1FC7E /* DRBIngredientProvider.m */, 212 | B3C3E8CA185ECBD400C1FC7E /* DRBIngredientImageProvider.h */, 213 | B3C3E8CB185ECBD400C1FC7E /* DRBIngredientImageProvider.m */, 214 | ); 215 | name = "Operation Providers"; 216 | sourceTree = ""; 217 | }; 218 | B3C3E8B1185E30C500C1FC7E /* Models */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | B3C3E8A7185E30AE00C1FC7E /* DRBCookbook.h */, 222 | B3C3E8A8185E30AE00C1FC7E /* DRBCookbook.m */, 223 | B3C3E8C7185EC48100C1FC7E /* DRBRecipe.h */, 224 | B3C3E8C8185EC48100C1FC7E /* DRBRecipe.m */, 225 | B3C3E8AD185E30AF00C1FC7E /* DRBIngredient.h */, 226 | B3C3E8AE185E30AF00C1FC7E /* DRBIngredient.m */, 227 | ); 228 | name = Models; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXGroup section */ 232 | 233 | /* Begin PBXNativeTarget section */ 234 | B3C3E85F185E253600C1FC7E /* Example */ = { 235 | isa = PBXNativeTarget; 236 | buildConfigurationList = B3C3E89B185E253600C1FC7E /* Build configuration list for PBXNativeTarget "Example" */; 237 | buildPhases = ( 238 | C644B8239C214B178591384B /* Check Pods Manifest.lock */, 239 | B3C3E85C185E253600C1FC7E /* Sources */, 240 | B3C3E85D185E253600C1FC7E /* Frameworks */, 241 | B3C3E85E185E253600C1FC7E /* Resources */, 242 | 23E0F5B3D6C344E58108BBAF /* Copy Pods Resources */, 243 | ); 244 | buildRules = ( 245 | ); 246 | dependencies = ( 247 | ); 248 | name = Example; 249 | productName = Example; 250 | productReference = B3C3E860185E253600C1FC7E /* Example.app */; 251 | productType = "com.apple.product-type.application"; 252 | }; 253 | B3C3E888185E253600C1FC7E /* ExampleTests */ = { 254 | isa = PBXNativeTarget; 255 | buildConfigurationList = B3C3E89E185E253600C1FC7E /* Build configuration list for PBXNativeTarget "ExampleTests" */; 256 | buildPhases = ( 257 | B3C3E885185E253600C1FC7E /* Sources */, 258 | B3C3E886185E253600C1FC7E /* Frameworks */, 259 | B3C3E887185E253600C1FC7E /* Resources */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | B3C3E890185E253600C1FC7E /* PBXTargetDependency */, 265 | ); 266 | name = ExampleTests; 267 | productName = ExampleTests; 268 | productReference = B3C3E889185E253600C1FC7E /* ExampleTests.xctest */; 269 | productType = "com.apple.product-type.bundle.unit-test"; 270 | }; 271 | /* End PBXNativeTarget section */ 272 | 273 | /* Begin PBXProject section */ 274 | B3C3E858185E253600C1FC7E /* Project object */ = { 275 | isa = PBXProject; 276 | attributes = { 277 | CLASSPREFIX = DRB; 278 | LastUpgradeCheck = 0500; 279 | ORGANIZATIONNAME = dstnbrkr; 280 | TargetAttributes = { 281 | B3C3E888185E253600C1FC7E = { 282 | TestTargetID = B3C3E85F185E253600C1FC7E; 283 | }; 284 | }; 285 | }; 286 | buildConfigurationList = B3C3E85B185E253600C1FC7E /* Build configuration list for PBXProject "Example" */; 287 | compatibilityVersion = "Xcode 3.2"; 288 | developmentRegion = English; 289 | hasScannedForEncodings = 0; 290 | knownRegions = ( 291 | en, 292 | Base, 293 | ); 294 | mainGroup = B3C3E857185E253600C1FC7E; 295 | productRefGroup = B3C3E861185E253600C1FC7E /* Products */; 296 | projectDirPath = ""; 297 | projectRoot = ""; 298 | targets = ( 299 | B3C3E85F185E253600C1FC7E /* Example */, 300 | B3C3E888185E253600C1FC7E /* ExampleTests */, 301 | ); 302 | }; 303 | /* End PBXProject section */ 304 | 305 | /* Begin PBXResourcesBuildPhase section */ 306 | B3C3E85E185E253600C1FC7E /* Resources */ = { 307 | isa = PBXResourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | B3C3E884185E253600C1FC7E /* Images.xcassets in Resources */, 311 | B3C3E8B3185E425300C1FC7E /* cassette.json in Resources */, 312 | B3C3E870185E253600C1FC7E /* InfoPlist.strings in Resources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | B3C3E887185E253600C1FC7E /* Resources */ = { 317 | isa = PBXResourcesBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | B3C3E896185E253600C1FC7E /* InfoPlist.strings in Resources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | /* End PBXResourcesBuildPhase section */ 325 | 326 | /* Begin PBXShellScriptBuildPhase section */ 327 | 23E0F5B3D6C344E58108BBAF /* Copy Pods Resources */ = { 328 | isa = PBXShellScriptBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | ); 332 | inputPaths = ( 333 | ); 334 | name = "Copy Pods Resources"; 335 | outputPaths = ( 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | shellPath = /bin/sh; 339 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 340 | showEnvVarsInLog = 0; 341 | }; 342 | C644B8239C214B178591384B /* Check Pods Manifest.lock */ = { 343 | isa = PBXShellScriptBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | ); 347 | inputPaths = ( 348 | ); 349 | name = "Check Pods Manifest.lock"; 350 | outputPaths = ( 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | shellPath = /bin/sh; 354 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 355 | showEnvVarsInLog = 0; 356 | }; 357 | /* End PBXShellScriptBuildPhase section */ 358 | 359 | /* Begin PBXSourcesBuildPhase section */ 360 | B3C3E85C185E253600C1FC7E /* Sources */ = { 361 | isa = PBXSourcesBuildPhase; 362 | buildActionMask = 2147483647; 363 | files = ( 364 | B3C3E872185E253600C1FC7E /* main.m in Sources */, 365 | B3C3E882185E253600C1FC7E /* DRBDetailViewController.m in Sources */, 366 | B3C3E87C185E253600C1FC7E /* Example.xcdatamodeld in Sources */, 367 | B3C3E8A6185E2C1700C1FC7E /* DRBCookbookProvider.m in Sources */, 368 | B3C3E8B6185E91D100C1FC7E /* DRBRecipeImageProvider.m in Sources */, 369 | B3C3E8B9185EBDB500C1FC7E /* DRBIngredientProvider.m in Sources */, 370 | B3C3E8A3185E281100C1FC7E /* DRBRecipeProvider.m in Sources */, 371 | B3C3E8CC185ECBD400C1FC7E /* DRBIngredientImageProvider.m in Sources */, 372 | B3C3E876185E253600C1FC7E /* DRBAppDelegate.m in Sources */, 373 | B3C3E8AF185E30AF00C1FC7E /* DRBIngredient.m in Sources */, 374 | B3C3E8C9185EC48100C1FC7E /* DRBRecipe.m in Sources */, 375 | B3C3E87F185E253600C1FC7E /* DRBMasterViewController.m in Sources */, 376 | B3C3E8A9185E30AE00C1FC7E /* DRBCookbook.m in Sources */, 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | }; 380 | B3C3E885185E253600C1FC7E /* Sources */ = { 381 | isa = PBXSourcesBuildPhase; 382 | buildActionMask = 2147483647; 383 | files = ( 384 | B3C3E898185E253600C1FC7E /* ExampleTests.m in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | /* End PBXSourcesBuildPhase section */ 389 | 390 | /* Begin PBXTargetDependency section */ 391 | B3C3E890185E253600C1FC7E /* PBXTargetDependency */ = { 392 | isa = PBXTargetDependency; 393 | target = B3C3E85F185E253600C1FC7E /* Example */; 394 | targetProxy = B3C3E88F185E253600C1FC7E /* PBXContainerItemProxy */; 395 | }; 396 | /* End PBXTargetDependency section */ 397 | 398 | /* Begin PBXVariantGroup section */ 399 | B3C3E86E185E253600C1FC7E /* InfoPlist.strings */ = { 400 | isa = PBXVariantGroup; 401 | children = ( 402 | B3C3E86F185E253600C1FC7E /* en */, 403 | ); 404 | name = InfoPlist.strings; 405 | sourceTree = ""; 406 | }; 407 | B3C3E894185E253600C1FC7E /* InfoPlist.strings */ = { 408 | isa = PBXVariantGroup; 409 | children = ( 410 | B3C3E895185E253600C1FC7E /* en */, 411 | ); 412 | name = InfoPlist.strings; 413 | sourceTree = ""; 414 | }; 415 | /* End PBXVariantGroup section */ 416 | 417 | /* Begin XCBuildConfiguration section */ 418 | B3C3E899185E253600C1FC7E /* Debug */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ALWAYS_SEARCH_USER_PATHS = NO; 422 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 423 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 424 | CLANG_CXX_LIBRARY = "libc++"; 425 | CLANG_ENABLE_MODULES = YES; 426 | CLANG_ENABLE_OBJC_ARC = YES; 427 | CLANG_WARN_BOOL_CONVERSION = YES; 428 | CLANG_WARN_CONSTANT_CONVERSION = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INT_CONVERSION = YES; 433 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 436 | COPY_PHASE_STRIP = NO; 437 | GCC_C_LANGUAGE_STANDARD = gnu99; 438 | GCC_DYNAMIC_NO_PIC = NO; 439 | GCC_OPTIMIZATION_LEVEL = 0; 440 | GCC_PREPROCESSOR_DEFINITIONS = ( 441 | "DEBUG=1", 442 | "$(inherited)", 443 | ); 444 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 452 | ONLY_ACTIVE_ARCH = YES; 453 | SDKROOT = iphoneos; 454 | }; 455 | name = Debug; 456 | }; 457 | B3C3E89A185E253600C1FC7E /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ALWAYS_SEARCH_USER_PATHS = NO; 461 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 462 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 463 | CLANG_CXX_LIBRARY = "libc++"; 464 | CLANG_ENABLE_MODULES = YES; 465 | CLANG_ENABLE_OBJC_ARC = YES; 466 | CLANG_WARN_BOOL_CONVERSION = YES; 467 | CLANG_WARN_CONSTANT_CONVERSION = YES; 468 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INT_CONVERSION = YES; 472 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 473 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 474 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 475 | COPY_PHASE_STRIP = YES; 476 | ENABLE_NS_ASSERTIONS = NO; 477 | GCC_C_LANGUAGE_STANDARD = gnu99; 478 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 479 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 480 | GCC_WARN_UNDECLARED_SELECTOR = YES; 481 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 482 | GCC_WARN_UNUSED_FUNCTION = YES; 483 | GCC_WARN_UNUSED_VARIABLE = YES; 484 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 485 | SDKROOT = iphoneos; 486 | VALIDATE_PRODUCT = YES; 487 | }; 488 | name = Release; 489 | }; 490 | B3C3E89C185E253600C1FC7E /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | baseConfigurationReference = 4E42D06C908B4D53A02BA830 /* Pods.xcconfig */; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 496 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 497 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 498 | INFOPLIST_FILE = "Example/Example-Info.plist"; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | WRAPPER_EXTENSION = app; 501 | }; 502 | name = Debug; 503 | }; 504 | B3C3E89D185E253600C1FC7E /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = 4E42D06C908B4D53A02BA830 /* Pods.xcconfig */; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 510 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 511 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 512 | INFOPLIST_FILE = "Example/Example-Info.plist"; 513 | PRODUCT_NAME = "$(TARGET_NAME)"; 514 | WRAPPER_EXTENSION = app; 515 | }; 516 | name = Release; 517 | }; 518 | B3C3E89F185E253600C1FC7E /* Debug */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 522 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 523 | FRAMEWORK_SEARCH_PATHS = ( 524 | "$(SDKROOT)/Developer/Library/Frameworks", 525 | "$(inherited)", 526 | "$(DEVELOPER_FRAMEWORKS_DIR)", 527 | ); 528 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 529 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 530 | GCC_PREPROCESSOR_DEFINITIONS = ( 531 | "DEBUG=1", 532 | "$(inherited)", 533 | ); 534 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | TEST_HOST = "$(BUNDLE_LOADER)"; 537 | WRAPPER_EXTENSION = xctest; 538 | }; 539 | name = Debug; 540 | }; 541 | B3C3E8A0185E253600C1FC7E /* Release */ = { 542 | isa = XCBuildConfiguration; 543 | buildSettings = { 544 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 545 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 546 | FRAMEWORK_SEARCH_PATHS = ( 547 | "$(SDKROOT)/Developer/Library/Frameworks", 548 | "$(inherited)", 549 | "$(DEVELOPER_FRAMEWORKS_DIR)", 550 | ); 551 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 552 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 553 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | TEST_HOST = "$(BUNDLE_LOADER)"; 556 | WRAPPER_EXTENSION = xctest; 557 | }; 558 | name = Release; 559 | }; 560 | /* End XCBuildConfiguration section */ 561 | 562 | /* Begin XCConfigurationList section */ 563 | B3C3E85B185E253600C1FC7E /* Build configuration list for PBXProject "Example" */ = { 564 | isa = XCConfigurationList; 565 | buildConfigurations = ( 566 | B3C3E899185E253600C1FC7E /* Debug */, 567 | B3C3E89A185E253600C1FC7E /* Release */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | B3C3E89B185E253600C1FC7E /* Build configuration list for PBXNativeTarget "Example" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | B3C3E89C185E253600C1FC7E /* Debug */, 576 | B3C3E89D185E253600C1FC7E /* Release */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | B3C3E89E185E253600C1FC7E /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | B3C3E89F185E253600C1FC7E /* Debug */, 585 | B3C3E8A0185E253600C1FC7E /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | /* End XCConfigurationList section */ 591 | 592 | /* Begin XCVersionGroup section */ 593 | B3C3E87A185E253600C1FC7E /* Example.xcdatamodeld */ = { 594 | isa = XCVersionGroup; 595 | children = ( 596 | B3C3E87B185E253600C1FC7E /* Example.xcdatamodel */, 597 | ); 598 | currentVersion = B3C3E87B185E253600C1FC7E /* Example.xcdatamodel */; 599 | path = Example.xcdatamodeld; 600 | sourceTree = ""; 601 | versionGroupType = wrapper.xcdatamodel; 602 | }; 603 | /* End XCVersionGroup section */ 604 | }; 605 | rootObject = B3C3E858185E253600C1FC7E /* Project object */; 606 | } 607 | -------------------------------------------------------------------------------- /Example/Example/DRBAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBAppDelegate.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | @interface DRBAppDelegate : UIResponder 27 | 28 | @property (strong, nonatomic) UIWindow *window; 29 | 30 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 31 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 32 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 33 | 34 | - (NSURL *)applicationDocumentsDirectory; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Example/Example/DRBAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBAppDelegate.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBAppDelegate.h" 27 | #import "DRBMasterViewController.h" 28 | #import "DRBOperationTree.h" 29 | #import "DRBCookbookProvider.h" 30 | #import "DRBRecipeProvider.h" 31 | #import "DRBRecipeImageProvider.h" 32 | #import "DRBIngredientProvider.h" 33 | #import "DRBIngredientImageProvider.h" 34 | #import "VCR.h" 35 | 36 | @implementation DRBAppDelegate 37 | 38 | @synthesize managedObjectContext = _managedObjectContext; 39 | @synthesize managedObjectModel = _managedObjectModel; 40 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; 41 | 42 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 43 | { 44 | DRBMasterViewController *controller = [[DRBMasterViewController alloc] initWithStyle:UITableViewStylePlain]; 45 | controller.managedObjectContext = self.managedObjectContext; 46 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller]; 47 | 48 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 49 | self.window.rootViewController = navigationController; 50 | [self.window makeKeyAndVisible]; 51 | 52 | NSString *cassettePath = [[NSBundle mainBundle] pathForResource:@"cassette" ofType:@"json"]; 53 | NSURL *cassetteURL = [NSURL fileURLWithPath:cassettePath]; 54 | [VCR loadCassetteWithContentsOfURL:cassetteURL]; 55 | [VCR start]; 56 | 57 | [[DRBOperationTree defaultOperationQueue] setMaxConcurrentOperationCount:5]; 58 | 59 | DRBOperationTree *cookbook = [DRBOperationTree tree]; 60 | DRBOperationTree *recipes = [DRBOperationTree tree]; 61 | DRBOperationTree *recipeImages = [DRBOperationTree tree]; 62 | DRBOperationTree *ingredients = [DRBOperationTree tree]; 63 | DRBOperationTree *ingredientImages = [DRBOperationTree tree]; 64 | 65 | cookbook.provider = [[DRBCookbookProvider alloc] initWithManagedObjectContext:self.managedObjectContext]; 66 | recipes.provider = [[DRBRecipeProvider alloc] initWithManagedObjectContext:self.managedObjectContext]; 67 | recipeImages.provider = [[DRBRecipeImageProvider alloc] init]; 68 | ingredients.provider = [[DRBIngredientProvider alloc] initWithManagedObjectContext:self.managedObjectContext]; 69 | ingredientImages.provider = [[DRBIngredientImageProvider alloc] init]; 70 | 71 | [cookbook addChild:recipes]; 72 | [recipes addChild:recipeImages]; 73 | [recipes addChild:ingredients]; 74 | [ingredients addChild:ingredientImages]; 75 | 76 | [cookbook enqueueOperationsForObject:@"a-cookbook" completion:^{ 77 | NSLog(@"All entities downloaded"); 78 | }]; 79 | 80 | return YES; 81 | } 82 | 83 | #pragma mark - Core Data stack 84 | 85 | - (NSManagedObjectContext *)managedObjectContext 86 | { 87 | if (_managedObjectContext != nil) { 88 | return _managedObjectContext; 89 | } 90 | 91 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 92 | if (coordinator != nil) { 93 | _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 94 | [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 95 | } 96 | return _managedObjectContext; 97 | } 98 | 99 | - (NSManagedObjectModel *)managedObjectModel 100 | { 101 | if (_managedObjectModel != nil) { 102 | return _managedObjectModel; 103 | } 104 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Example" withExtension:@"momd"]; 105 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 106 | return _managedObjectModel; 107 | } 108 | 109 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 110 | { 111 | if (_persistentStoreCoordinator != nil) { 112 | return _persistentStoreCoordinator; 113 | } 114 | 115 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Example.sqlite"]; 116 | 117 | NSError *error = nil; 118 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 119 | if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 120 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 121 | abort(); 122 | } 123 | 124 | return _persistentStoreCoordinator; 125 | } 126 | 127 | #pragma mark - Application's Documents directory 128 | 129 | - (NSURL *)applicationDocumentsDirectory 130 | { 131 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Example/Example/DRBCookbook.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBCookbook.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | @interface DRBCookbook : NSManagedObject 27 | 28 | + (DRBCookbook *)cookbookWithJSON:(id)JSON context:(NSManagedObjectContext *)context; 29 | 30 | @property (nonatomic, strong) NSArray *recipeIDs; 31 | @property (nonatomic, strong) NSManagedObject *recipes; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Example/Example/DRBCookbook.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBCookbook.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBCookbook.h" 27 | 28 | @implementation DRBCookbook 29 | 30 | @synthesize recipeIDs; 31 | 32 | @dynamic recipes; 33 | 34 | + (DRBCookbook *)cookbookWithJSON:(id)JSON context:(NSManagedObjectContext *)context 35 | { 36 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"DRBCookbook" inManagedObjectContext:context]; 37 | DRBCookbook *cookbook = [[DRBCookbook alloc] initWithEntity:entity insertIntoManagedObjectContext:context]; 38 | cookbook.recipeIDs = [JSON objectForKey:@"recipe_ids"]; 39 | return cookbook; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Example/Example/DRBCookbookProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBCookbookProvider.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBOperationTree.h" 27 | 28 | @interface DRBCookbookProvider : NSObject 29 | 30 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Example/Example/DRBCookbookProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBCookbookProvider.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBCookbookProvider.h" 27 | #import "DRBCookbook.h" 28 | #import "AFJSONRequestOperation.h" 29 | 30 | @interface DRBCookbookProvider () { 31 | NSManagedObjectContext *_managedObjectContext; 32 | } 33 | @end 34 | 35 | @implementation DRBCookbookProvider 36 | 37 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context 38 | { 39 | if ((self = [super init])) { 40 | _managedObjectContext = context; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(NSString *)cookbookID completion:(void (^)(NSArray *))completion 46 | { 47 | completion(@[ cookbookID ]); 48 | } 49 | 50 | - (NSOperation *)operationTree:(DRBOperationTree *)node 51 | operationForObject:(NSString *)cookbookID 52 | continuation:(void (^)(id, void (^)()))continuation 53 | failure:(void (^)())failure 54 | { 55 | NSString *path = [NSString stringWithFormat:@"http://api.example.com/cookbooks/%@", cookbookID]; 56 | NSURL *URL = [NSURL URLWithString:path]; 57 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 58 | AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request]; 59 | [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 60 | DRBCookbook *cookbook = [DRBCookbook cookbookWithJSON:responseObject context:_managedObjectContext]; 61 | continuation(cookbook, nil); 62 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 63 | failure(); 64 | }]; 65 | return operation; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Example/Example/DRBDetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBDetailViewController.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBRecipe.h" 27 | 28 | @interface DRBDetailViewController : UITableViewController 29 | 30 | @property (strong, nonatomic) DRBRecipe *recipe; 31 | 32 | @property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel; 33 | @end 34 | -------------------------------------------------------------------------------- /Example/Example/DRBDetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBDetailViewController.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBDetailViewController.h" 27 | #import "DRBIngredient.h" 28 | 29 | @interface DRBDetailViewController () 30 | @property (nonatomic, strong) NSArray *ingredients; 31 | @end 32 | 33 | @implementation DRBDetailViewController 34 | 35 | - (void)setRecipe:(DRBRecipe *)recipe 36 | { 37 | self.ingredients = [[recipe.ingredients allObjects] sortedArrayUsingSelector:@selector(name)]; 38 | } 39 | 40 | #pragma mark - Table View 41 | 42 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 43 | { 44 | return [self.ingredients count]; 45 | } 46 | 47 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 48 | { 49 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; 50 | if (!cell) { 51 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; 52 | } 53 | DRBIngredient *ingredient = [self.ingredients objectAtIndex:indexPath.row]; 54 | cell.textLabel.text = ingredient.name; 55 | cell.imageView.image = ingredient.image; 56 | return cell; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredient.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredient.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | @interface DRBIngredient : NSManagedObject 27 | 28 | + (DRBIngredient *)ingredientWithJSON:(id)JSON context:(NSManagedObjectContext *)context; 29 | 30 | @property (nonatomic, retain) NSString * name; 31 | @property (nonatomic, retain) NSString * imagePath; 32 | 33 | // derived 34 | @property (nonatomic, readonly) NSString *imageFilePath; 35 | @property (nonatomic, readonly) UIImage *image; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredient.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredient.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBIngredient.h" 27 | 28 | @implementation DRBIngredient 29 | 30 | @dynamic name; 31 | @dynamic imagePath; 32 | 33 | + (DRBIngredient *)ingredientWithJSON:(id)JSON context:(NSManagedObjectContext *)context 34 | { 35 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"DRBIngredient" inManagedObjectContext:context]; 36 | DRBIngredient *ingredient = [[DRBIngredient alloc] initWithEntity:entity insertIntoManagedObjectContext:context]; 37 | ingredient.name = [JSON objectForKey:@"name"]; 38 | ingredient.imagePath = [JSON objectForKey:@"image_path"]; 39 | return ingredient; 40 | } 41 | 42 | - (NSString *)imageFilePath 43 | { 44 | NSString *fileName = [[self.imagePath componentsSeparatedByString:@"/"] lastObject]; 45 | return fileName ? [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), fileName] : nil; 46 | } 47 | 48 | - (UIImage *)image 49 | { 50 | return [UIImage imageWithContentsOfFile:[self imageFilePath]]; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredientImageProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredientImageProvider.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/16/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBOperationTree.h" 27 | 28 | @interface DRBIngredientImageProvider : NSObject 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredientImageProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredientImageProvider.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/16/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBIngredientImageProvider.h" 27 | #import "DRBIngredient.h" 28 | #import "AFImageRequestOperation.h" 29 | 30 | @implementation DRBIngredientImageProvider 31 | 32 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(DRBIngredient *)ingredient completion:(void (^)(NSArray *))completion 33 | { 34 | NSURL *URL = [NSURL URLWithString:ingredient.imagePath]; 35 | completion(@[ @[ URL, ingredient.imageFilePath ] ]); 36 | } 37 | 38 | - (NSOperation *)operationTree:(DRBOperationTree *)node 39 | operationForObject:(NSArray *)params 40 | continuation:(void (^)(id, void (^)()))continuation 41 | failure:(void (^)())failure 42 | { 43 | NSURL *URL = params[0]; 44 | NSString *filePath = params[1]; 45 | NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 46 | return [AFImageRequestOperation imageRequestOperationWithRequest:request 47 | imageProcessingBlock:nil 48 | success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { 49 | [UIImagePNGRepresentation(image) writeToFile:filePath atomically:YES]; 50 | continuation(image, nil); 51 | } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { 52 | failure(); 53 | }]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredientProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredientProvider.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBOperationTree.h" 27 | 28 | @interface DRBIngredientProvider : NSObject 29 | 30 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Example/Example/DRBIngredientProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBIngredientProvider.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBIngredientProvider.h" 27 | #import "DRBIngredient.h" 28 | #import "DRBRecipe.h" 29 | #import "AFJSONRequestOperation.h" 30 | 31 | @interface DRBIngredientProvider () { 32 | NSManagedObjectContext *_managedObjectContext; 33 | } 34 | @end 35 | 36 | @implementation DRBIngredientProvider 37 | 38 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context 39 | { 40 | if ((self = [super init])) { 41 | _managedObjectContext = context; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(DRBRecipe *)recipe completion:(void (^)(NSArray *))completion 47 | { 48 | NSMutableArray *objects = [NSMutableArray array]; 49 | for (NSString *ingredientID in recipe.ingredientIDs) { 50 | [objects addObject:@[ ingredientID, recipe ]]; 51 | } 52 | completion(objects); 53 | } 54 | 55 | - (NSOperation *)operationTree:(DRBOperationTree *)node 56 | operationForObject:(NSArray *)params 57 | continuation:(void (^)(id, void (^)()))continuation 58 | failure:(void (^)())failure 59 | { 60 | NSString *ingredientID = params[0]; 61 | DRBRecipe *recipe = params[1]; 62 | NSString *path = [NSString stringWithFormat:@"http://api.example.com/ingredients/%@", ingredientID]; 63 | NSURL *URL = [NSURL URLWithString:path]; 64 | NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 65 | AFHTTPRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request]; 66 | [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 67 | DRBIngredient *ingredient = [DRBIngredient ingredientWithJSON:responseObject context:_managedObjectContext]; 68 | [recipe addIngredientsObject:ingredient]; 69 | continuation(ingredient, nil); 70 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 71 | failure(); 72 | }]; 73 | return operation; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Example/Example/DRBMasterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBMasterViewController.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | @interface DRBMasterViewController : UITableViewController 27 | 28 | @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; 29 | @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Example/Example/DRBMasterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBMasterViewController.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBMasterViewController.h" 27 | #import "DRBDetailViewController.h" 28 | #import "DRBRecipe.h" 29 | 30 | @interface DRBMasterViewController () 31 | - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; 32 | @end 33 | 34 | @implementation DRBMasterViewController 35 | 36 | #pragma mark - Table View 37 | 38 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 39 | { 40 | return [[self.fetchedResultsController sections] count]; 41 | } 42 | 43 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 44 | { 45 | id sectionInfo = [self.fetchedResultsController sections][section]; 46 | return [sectionInfo numberOfObjects]; 47 | } 48 | 49 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 50 | { 51 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; 52 | if (!cell) { 53 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; 54 | } 55 | [self configureCell:cell atIndexPath:indexPath]; 56 | return cell; 57 | } 58 | 59 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 60 | { 61 | DRBRecipe *recipe = [[self fetchedResultsController] objectAtIndexPath:indexPath]; 62 | DRBDetailViewController *controller = [[DRBDetailViewController alloc] init]; 63 | controller.recipe = recipe; 64 | [self.navigationController pushViewController:controller animated:YES]; 65 | } 66 | 67 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 68 | { 69 | if ([[segue identifier] isEqualToString:@"showDetail"]) { 70 | NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; 71 | DRBRecipe *recipe = [[self fetchedResultsController] objectAtIndexPath:indexPath]; 72 | [[segue destinationViewController] setRecipe:recipe]; 73 | } 74 | } 75 | 76 | #pragma mark - Fetched results controller 77 | 78 | - (NSFetchedResultsController *)fetchedResultsController 79 | { 80 | if (_fetchedResultsController != nil) { 81 | return _fetchedResultsController; 82 | } 83 | 84 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 85 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"DRBRecipe" inManagedObjectContext:self.managedObjectContext]; 86 | [fetchRequest setEntity:entity]; 87 | 88 | [fetchRequest setFetchBatchSize:20]; 89 | 90 | NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; 91 | NSArray *sortDescriptors = @[sortDescriptor]; 92 | 93 | [fetchRequest setSortDescriptors:sortDescriptors]; 94 | 95 | NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; 96 | aFetchedResultsController.delegate = self; 97 | self.fetchedResultsController = aFetchedResultsController; 98 | 99 | NSError *error = nil; 100 | if (![self.fetchedResultsController performFetch:&error]) { 101 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 102 | abort(); 103 | } 104 | 105 | return _fetchedResultsController; 106 | } 107 | 108 | - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 109 | { 110 | [self.tableView beginUpdates]; 111 | } 112 | 113 | - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo 114 | atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type 115 | { 116 | switch(type) { 117 | case NSFetchedResultsChangeInsert: 118 | [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 119 | break; 120 | 121 | case NSFetchedResultsChangeDelete: 122 | [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 123 | break; 124 | } 125 | } 126 | 127 | - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject 128 | atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type 129 | newIndexPath:(NSIndexPath *)newIndexPath 130 | { 131 | UITableView *tableView = self.tableView; 132 | 133 | switch(type) { 134 | case NSFetchedResultsChangeInsert: 135 | [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 136 | break; 137 | 138 | case NSFetchedResultsChangeDelete: 139 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 140 | break; 141 | 142 | case NSFetchedResultsChangeUpdate: 143 | [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 144 | break; 145 | 146 | case NSFetchedResultsChangeMove: 147 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 148 | [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 149 | break; 150 | } 151 | } 152 | 153 | - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 154 | { 155 | [self.tableView endUpdates]; 156 | } 157 | 158 | - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath 159 | { 160 | DRBRecipe *recipe = [self.fetchedResultsController objectAtIndexPath:indexPath]; 161 | cell.textLabel.text = recipe.name; 162 | cell.imageView.image = recipe.image; 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipe.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipe.h 3 | // Pods 4 | // 5 | // Created by Dustin Barker on 12/16/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | @class DRBCookbook, DRBIngredient; 27 | 28 | @interface DRBRecipe : NSManagedObject 29 | 30 | + (DRBRecipe *)recipeWithJSON:(id)JSON context:(NSManagedObjectContext *)context; 31 | - (NSString *)imageFilePath; 32 | - (UIImage *)image; 33 | 34 | @property (nonatomic, strong) NSString * name; 35 | @property (nonatomic, strong) NSString * imagePath; 36 | @property (nonatomic, strong) DRBCookbook *cookbook; 37 | @property (nonatomic, strong) NSSet *ingredients; 38 | 39 | // transient 40 | @property (nonatomic, strong) NSArray *ingredientIDs; 41 | 42 | @end 43 | 44 | @interface DRBRecipe (CoreDataGeneratedAccessors) 45 | 46 | - (void)addIngredientsObject:(DRBIngredient *)value; 47 | - (void)removeIngredientsObject:(DRBIngredient *)value; 48 | - (void)addIngredients:(NSSet *)values; 49 | - (void)removeIngredients:(NSSet *)values; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipe.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipe.m 3 | // Pods 4 | // 5 | // Created by Dustin Barker on 12/16/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBRecipe.h" 27 | #import "DRBCookbook.h" 28 | #import "DRBIngredient.h" 29 | 30 | @implementation DRBRecipe 31 | 32 | @dynamic name; 33 | @dynamic imagePath; 34 | @dynamic cookbook; 35 | @dynamic ingredients; 36 | 37 | @synthesize ingredientIDs; 38 | 39 | + (DRBRecipe *)recipeWithJSON:(id)JSON context:(NSManagedObjectContext *)context 40 | { 41 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"DRBRecipe" inManagedObjectContext:context]; 42 | DRBRecipe *recipe = [[DRBRecipe alloc] initWithEntity:entity insertIntoManagedObjectContext:context]; 43 | recipe.name = [JSON objectForKey:@"name"]; 44 | recipe.ingredientIDs = [JSON objectForKey:@"ingredient_ids"]; 45 | recipe.imagePath = [JSON objectForKey:@"image_path"]; 46 | return recipe; 47 | } 48 | 49 | - (NSString *)imageFilePath 50 | { 51 | NSString *fileName = [[self.imagePath componentsSeparatedByString:@"/"] lastObject]; 52 | return fileName ? [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), fileName] : nil; 53 | } 54 | 55 | - (UIImage *)image 56 | { 57 | return [UIImage imageWithContentsOfFile:[self imageFilePath]]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipeImageProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipeImageProvider.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBOperationTree.h" 27 | 28 | @interface DRBRecipeImageProvider : NSObject 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipeImageProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipeImageProvider.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBRecipeImageProvider.h" 27 | #import "DRBRecipe.h" 28 | #import "AFImageRequestOperation.h" 29 | 30 | @implementation DRBRecipeImageProvider 31 | 32 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(DRBRecipe *)recipe completion:(void (^)(NSArray *))completion 33 | { 34 | NSURL *URL = [NSURL URLWithString:recipe.imagePath]; 35 | completion(@[ @[ URL, recipe.imageFilePath ] ]); 36 | } 37 | 38 | - (NSOperation *)operationTree:(DRBOperationTree *)node 39 | operationForObject:(NSArray *)params 40 | continuation:(void (^)(id, void (^)()))continuation 41 | failure:(void (^)())failure 42 | { 43 | NSURL *URL = params[0]; 44 | NSString *filePath = params[1]; 45 | NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 46 | return [AFImageRequestOperation imageRequestOperationWithRequest:request 47 | imageProcessingBlock:nil 48 | success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { 49 | [UIImagePNGRepresentation(image) writeToFile:filePath atomically:YES]; 50 | continuation(image, nil); 51 | } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { 52 | failure(); 53 | }]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipeProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipeProvider.h 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBOperationTree.h" 27 | 28 | @interface DRBRecipeProvider : NSObject 29 | 30 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Example/Example/DRBRecipeProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBRecipeProvider.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). 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 16 | // all 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 24 | // THE SOFTWARE. 25 | 26 | #import "DRBRecipeProvider.h" 27 | #import "DRBCookbook.h" 28 | #import "DRBRecipe.h" 29 | #import "AFJSONRequestOperation.h" 30 | 31 | @interface DRBRecipeProvider () { 32 | NSManagedObjectContext *_managedObjectContext; 33 | } 34 | @end 35 | 36 | @implementation DRBRecipeProvider 37 | 38 | - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context 39 | { 40 | if ((self = [super init])) { 41 | _managedObjectContext = context; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(DRBCookbook *)cookbook completion:(void (^)(NSArray *))completion 47 | { 48 | completion(cookbook.recipeIDs); 49 | } 50 | 51 | - (NSOperation *)operationTree:(DRBOperationTree *)node 52 | operationForObject:(NSString *)recipeID 53 | continuation:(void (^)(id, void (^)()))continuation 54 | failure:(void (^)())failure 55 | { 56 | NSString *path = [NSString stringWithFormat:@"http://api.example.com/recipes/%@", recipeID]; 57 | NSURL *URL = [NSURL URLWithString:path]; 58 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 59 | AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request]; 60 | [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 61 | 62 | NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 63 | context.parentContext = _managedObjectContext; 64 | DRBRecipe *recipe = [DRBRecipe recipeWithJSON:responseObject context:context]; 65 | 66 | continuation(recipe, ^{ 67 | [context performBlock:^{ 68 | [context save:nil]; 69 | }]; 70 | }); 71 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 72 | failure(); 73 | }]; 74 | return operation; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Example/Example/Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | net.dstnbrkr.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarTintParameters 32 | 33 | UINavigationBar 34 | 35 | Style 36 | UIBarStyleDefault 37 | Translucent 38 | 39 | 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/Example/Example-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #import 17 | #endif 18 | -------------------------------------------------------------------------------- /Example/Example/Example.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Example.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example/Example.xcdatamodeld/Example.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 dstnbrkr. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DRBAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DRBAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.dstnbrkr.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.m 3 | // ExampleTests 4 | // 5 | // Created by Dustin Barker on 12/15/13. 6 | // Copyright (c) 2013 dstnbrkr. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ExampleTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Example/ExampleTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, "7.0" 2 | 3 | pod 'AFNetworking', "~> 1.0" 4 | pod 'VCRURLConnection', :git => 'https://github.com/dstnbrkr/VCRURLConnection.git', :commit => '75f7ba74' 5 | pod 'DRBOperationTree', :git => 'https://github.com/dstnbrkr/DRBOperationTree.git' 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Example/cassette.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "method" : "GET", 4 | "uri" : "http://api.example.com/cookbooks/a-cookbook", 5 | "status" : 200, 6 | "headers" : { 7 | "Content-Type" : "application/json" 8 | }, 9 | "body" : { "recipe_ids" : [ "vegetable-stew", "cauliflower-and-sweet-potatoes", "fried-chicken" ] } 10 | }, 11 | { 12 | "method" : "GET", 13 | "uri" : "http://api.example.com/recipes/vegetable-stew", 14 | "status" : 200, 15 | "headers" : { 16 | "Content-Type" : "application/json" 17 | }, 18 | "body" : { "name" : "Vegetable Stew", "ingredient_ids" : [ "onion", "garlic", "carrot", "bayleaf" ], "image_path" : "http://farm2.staticflickr.com/1239/5117662053_49bd207706_s.jpg" } 19 | }, 20 | { 21 | "method" : "GET", 22 | "uri" : "http://api.example.com/recipes/cauliflower-and-sweet-potatoes", 23 | "status" : 200, 24 | "headers" : { 25 | "Content-Type" : "application/json" 26 | }, 27 | "body" : { "name" : "Cauliflower and Sweet Potatoes", "ingredient_ids" : [ "onion", "ginger", "cumin", "sweet-potato", "cauliflower" ], "image_path" : "http://farm9.staticflickr.com/8476/8434681717_3849faaa02_s.jpg" } 28 | }, 29 | { 30 | "method" : "GET", 31 | "uri" : "http://api.example.com/recipes/fried-chicken", 32 | "status" : 200, 33 | "headers" : { 34 | "Content-Type" : "application/json" 35 | }, 36 | "body" : { "name" : "Fried Chicken", "ingredient_ids" : [ "chicken", "flour", "buttermilk", "pepper" ], "image_path" : "http://farm4.staticflickr.com/3191/2875255560_ae0051f91e_s.jpg" } 37 | }, 38 | { 39 | "method" : "GET", 40 | "uri" : "http://api.example.com/ingredients/onion", 41 | "status" : 200, 42 | "headers" : { 43 | "Content-Type" : "application/json" 44 | }, 45 | "body" : { "name" : "Onion", "image_path" : "http://farm3.staticflickr.com/2263/1842820851_98e9a56047_s.jpg" } 46 | }, 47 | { 48 | "method" : "GET", 49 | "uri" : "http://api.example.com/ingredients/garlic", 50 | "status" : 200, 51 | "headers" : { 52 | "Content-Type" : "application/json" 53 | }, 54 | "body" : { "name" : "Garlic", "image_path" : "http://farm3.staticflickr.com/2033/2212742541_967dd6e992_s.jpg" } 55 | }, 56 | { 57 | "method" : "GET", 58 | "uri" : "http://api.example.com/ingredients/carrot", 59 | "status" : 200, 60 | "headers" : { 61 | "Content-Type" : "application/json" 62 | }, 63 | "body" : { "name" : "Carrot", "image_path" : "http://farm3.staticflickr.com/2233/2256197976_87fc8a00cf_s.jpg" } 64 | }, 65 | { 66 | "method" : "GET", 67 | "uri" : "http://api.example.com/ingredients/bayleaf", 68 | "status" : 200, 69 | "headers" : { 70 | "Content-Type" : "application/json" 71 | }, 72 | "body" : { "name" : "Bay Leaf", "image_path" : "http://farm5.staticflickr.com/4020/4605791600_a18407bebd_s.jpg" } 73 | }, 74 | { 75 | "method" : "GET", 76 | "uri" : "http://api.example.com/ingredients/ginger", 77 | "status" : 200, 78 | "headers" : { 79 | "Content-Type" : "application/json" 80 | }, 81 | "body" : { "name" : "Ginger", "image_path" : "http://farm6.staticflickr.com/5159/7178625061_796d8d7f0f_s.jpg" } 82 | }, 83 | { 84 | "method" : "GET", 85 | "uri" : "http://api.example.com/ingredients/cumin", 86 | "status" : 200, 87 | "headers" : { 88 | "Content-Type" : "application/json" 89 | }, 90 | "body" : { "name" : "Cumin", "image_path" : "http://farm9.staticflickr.com/8478/8171256437_84e4318a80_s.jpg" } 91 | }, 92 | { 93 | "method" : "GET", 94 | "uri" : "http://api.example.com/ingredients/cauliflower", 95 | "status" : 200, 96 | "headers" : { 97 | "Content-Type" : "application/json" 98 | }, 99 | "body" : { "name" : "Cauliflower", "image_path" : "http://farm8.staticflickr.com/7304/8849306604_529708d1f3_s.jpg" } 100 | }, 101 | { 102 | "method" : "GET", 103 | "uri" : "http://api.example.com/ingredients/chicken", 104 | "status" : 200, 105 | "headers" : { 106 | "Content-Type" : "application/json" 107 | }, 108 | "body" : { "name" : "Chicken", "image_path" : "http://farm1.staticflickr.com/92/245380370_4eba03a1d4_s.jpg" } 109 | }, 110 | { 111 | "method" : "GET", 112 | "uri" : "http://api.example.com/ingredients/buttermilk", 113 | "status" : 200, 114 | "headers" : { 115 | "Content-Type" : "application/json" 116 | }, 117 | "body" : { "name" : "Buttermilk", "image_path" : "http://farm4.staticflickr.com/3523/4075663345_6091b24f44_s.jpg" } 118 | }, 119 | { 120 | "method" : "GET", 121 | "uri" : "http://api.example.com/ingredients/flour", 122 | "status" : 200, 123 | "headers" : { 124 | "Content-Type" : "application/json" 125 | }, 126 | "body" : { "name" : "Flour", "image_path" : "http://farm9.staticflickr.com/8339/8187291243_3b7b4da837_s.jpg" } 127 | }, 128 | { 129 | "method" : "GET", 130 | "uri" : "http://api.example.com/ingredients/pepper", 131 | "status" : 200, 132 | "headers" : { 133 | "Content-Type" : "application/json" 134 | }, 135 | "body" : { "name" : "Pepper", "image_path" : "http://farm9.staticflickr.com/8071/8328383653_309221c59b_s.jpg" } 136 | } 137 | ] 138 | 139 | 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Artsy + Dustin Barker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 4 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 11 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CONFIGURATION ?= "Debug" 2 | BUILD_DIR = $(shell pwd)/build 3 | PROJECT = DRBOperationTree.xcodeproj 4 | 5 | xctool_command: 6 | xctool -sdk $(SDK) \ 7 | -project $(PROJECT) \ 8 | -scheme $(SCHEME) \ 9 | -configuration $(CONFIGURATION) $(COMMAND) 10 | 11 | test_ios: SDK = iphonesimulator 12 | test_ios: SCHEME = Tests-iOS 13 | test_ios: COMMAND = test 14 | test_ios: xctool_command 15 | 16 | test_osx: SDK = macosx 17 | test_osx: SCHEME = Tests-OSX 18 | test_osx: COMMAND = test 19 | test_osx: xctool_command 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dstnbrkr/DRBOperationTree.png?branch=master)](https://travis-ci.org/dstnbrkr/DRBOperationTree) 2 | 3 | # DRBOperationTree 4 | DRBOperationTree is an iOS and OSX API to organize work (NSOperations) into a tree such that the output of each parent is passed to it's children for further processing. 5 | 6 | # Quick Start 7 | If you want to get right into the details, take a look at the Example project. In particular: 8 | * [DRBOperationTree configuration](https://github.com/dstnbrkr/DRBOperationTree/blob/0.0.1/Example/Example/DRBAppDelegate.m#L57-L78) 9 | * [DRBOperationProvider implementation](https://github.com/dstnbrkr/DRBOperationTree/blob/0.0.1/Example/Example/DRBRecipeProvider.m) 10 | 11 | More in-depth explanation of these topics and the motivation below. 12 | 13 | # Example 14 | Let's say we have an API with the following endpoints: 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
/cookbook/{cookbook_id}provides a list of recipe ids
/recipes/{recipe_id}provides JSON representation of recipe
/ingredients/{ingredient_id}provides JSON representation for an ingredient
/images/{image_id}provides PNG image
34 | 35 | In order to serialize the object graph for a cookbook, we'd need to fetch the list of recipe ids from the `/cookbook/{cookbook_id}` endpoint, then fetch each recipe, followed by each image and each ingredient for each recipe. What we request at each step depends on the response of the request in the previous step. For example, to know which (and how many) `/ingredients/{ingredient_id}` requests we'll make, we have to first fetch and parse `/recipes/{recipe_id}/ingredient`. We can model these dependencies as a tree. 36 | 37 | If were to serialize the object graph for a vegetable stew recipe, the tree of requests we'd make would be something like this: 38 | 39 | ![Cookbook Request Tree](cookbook.png) 40 | 41 | Now let's use this API to make an iOS app which will display all the recipes in a cookbook. For this example, the main view is a list of recipes with an image and an abbreviated list of their ingredients. To display a recipe, we'll need to serialize a recipe and all it's child objects. We don't want to keep the user waiting until we're done with all the requests we need to make, so let's show the user recipes as soon as they're ready. This means we're going to traverse that request tree in level order, making requests in parallel wherever we can. We could try an approach like: 42 | 43 | ```objective-c 44 | [self fetchCookbook:cookbookID completion:^(id cookbook) { 45 | for (id recipeID in cookbook.recipeIDs) { 46 | [self fetchRecipe:recipeID completion:^{id recipe) { 47 | for (id ingredientID in recipe.ingredientIDs) { 48 | [self fetchIngredient:ingredientID completion:^(id ingredient) { 49 | for (id imageID in ingredient.imageIDs) { 50 | [recipe addIngredient:ingredient]; 51 | 52 | [self fetchImage:imageID completion:^(id image) { 53 | [ingredient addImage:image]; 54 | }]; 55 | } 56 | [recipe addIngredient:ingredient]; 57 | }]; 58 | } 59 | }]; 60 | 61 | for (id imageID in recipe.images) { 62 | [self fetchImage:imageID completion:^(id image) { 63 | [recipe addImage:image]; 64 | }]; 65 | } 66 | } 67 | }]; 68 | ``` 69 | 70 | Assuming all our fetch methods allow concurrent requests, this will achieve our goal but the code is less than ideal. We could clean up the code, but there's another problem to solve: we need to know when all the child objects are serialized. In our request tree above, we have two sets of leaf nodes: recipe images and ingredient images. In the approach above, we'd need to add code to detect when both sets of asynchronous requests are complete. 71 | 72 | An alternate approach would take into account that all the code above follows a pattern of: 73 | * map an object to one or more child objects (i.e. cookbook -> recipes, recipe -> ingredients) 74 | * enqueue some work for each child object 75 | 76 | DRBOperationTree takes exactly that approach. DRBOperationTree lets us define dependencies as a tree, then it performs the 'work' each node corresponds to in level order. A node is marked completed once the post-order traversal below it has completed. Here's how the code above could be refactored to use DRBOperationTree: 77 | 78 | ```objective-c 79 | DRBOperationTree *cookbook = [DRBOperationTree tree]; 80 | DRBOperationTree *recipe = [DRBOperationTree tree]; 81 | DRBOperationTree *recipeImage = [DRBOperationTree tree]; 82 | DRBOperationTree *ingredient = [DRBOperationTree tree]; 83 | DRBOperationTree *ingredientImage = [DRBOperationTree tree]; 84 | 85 | recipe.provider = [[RecipeProvider alloc] init]; 86 | recipeImage.provider = [[RecipeImageProvider alloc] init]; 87 | ingredient.provider = [[IngredientProvider alloc] init]; 88 | ingredientImage.provider = [[IngredientImageProvider alloc] init]; 89 | 90 | [cookbook addChild:recipe]; 91 | [recipe addChild:recipeImage]; 92 | [recipe addChild:ingredient]; 93 | [ingredient addChild:ingredientImage]; 94 | 95 | [cookbook sendObject:@"a-cookbook" completion:^{ 96 | // all done 97 | }]; 98 | ```` 99 | 100 | Each node in the tree sends it's output to it's children. In this example, the recipe node sends serialized recipe objects to the ingredient nodes. The ingredient.provider is responsible for mapping the incoming recipe object to outgoing ingredient objects. Then it creates NSOperations which will download and serialize each ingredient object. The provider object confirms to the DRBOperationProvider protocol, which has these two methods: 101 | 102 | ```objective-c 103 | 104 | // maps input objects to output objects (ex. recipe -> ingredient ids) 105 | - (void)operationTree:(DRBOperationTree *)node 106 | objectsForObject:(CBRecipe *)recipe 107 | completion:(void(^)(NSArray *objects))completion { 108 | 109 | // this method is optionally asynchronous 110 | // in this example, we're just mapping a recipe to it's child ingredient ids 111 | completion(recipe.ingredientIDs); 112 | } 113 | 114 | // given an object, returns an operation for that object and passes along the result 115 | // (ex. ingredient id -> operation to fetch ingredient -> serialized ingredient object) 116 | - (NSOperation *)operationTree:(DRBOperationTree *)node 117 | operationForObject:(id)ingredientID 118 | continuation:(void(^)(id, void(^)()))continuation 119 | failure:(void(^)())failure; 120 | 121 | return [NSBlockOperation blockOperationWithBlock:^{ 122 | [self fetchIngredient:ingredientID success:^(CBIngredient *ingredient) { 123 | continuation(ingredient, nil); 124 | }]; 125 | }]; 126 | } 127 | ``` 128 | 129 | Using this approach with DRBOperationTree, we've: 130 | * solved our initial problem of serializing the graph in parallel to make the best user experience 131 | * found a way to detect when the full serialization is complete (i.e. when the post-order traversal of the tree is complete) 132 | * refactored the outer code into a structure that corresponds to our request tree 133 | * refactored the inner code into specialized objects that correspond to each step in our serialization 134 | 135 | # Maintainer 136 | 137 | * [Dustin Barker @dstnbrkr](http://twitter.com/dstnbrkr) 138 | 139 | # License 140 | 141 | DRBOperation is available under the MIT license. See the LICENSE file for more info. 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /Tests-OSX/Tests-OSX-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.dstnbrkr.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests-iOS/DRBOperationTreeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBOperationTreeTests.m 3 | // 4 | // Created by Dustin Barker on 12/5/13. 5 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import 26 | #import "XCTestCase+SRTAdditions.h" 27 | #import "DRBOperationTree.h" 28 | 29 | @interface DRBOperationTestProvider : NSObject 30 | @property (nonatomic, readonly) id object; 31 | @end 32 | 33 | @interface DRBOperationFailingProvider : NSObject 34 | @property (nonatomic, readonly) NSUInteger retryCount; 35 | @end 36 | 37 | @interface DRBOperationTreeTests : XCTestCase 38 | @end 39 | 40 | @implementation DRBOperationTreeTests 41 | 42 | - (void)setUp 43 | { 44 | [super setUp]; 45 | } 46 | 47 | - (void)tearDown 48 | { 49 | [super tearDown]; 50 | } 51 | 52 | - (void)testEnqueueOperationWithSingleNode 53 | { 54 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 55 | DRBOperationTree *node = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 56 | DRBOperationTestProvider *delegate = [[DRBOperationTestProvider alloc] init]; 57 | node.provider = delegate; 58 | 59 | id object = @"foo"; 60 | 61 | __block BOOL completed = NO; 62 | [node enqueueOperationsForObject:object completion:^{ completed = YES; }]; 63 | 64 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 65 | return completed == YES; 66 | } timeout:1]; 67 | 68 | XCTAssert([delegate.object isEqual:object], @"Expected delegate to be completed"); 69 | } 70 | 71 | - (void)testSendObjectWithSingleNode 72 | { 73 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 74 | DRBOperationTree *node = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 75 | DRBOperationTestProvider *delegate = [[DRBOperationTestProvider alloc] init]; 76 | node.provider = delegate; 77 | 78 | __block BOOL completed = NO; 79 | [node sendObject:nil completion:^{ completed = YES; }]; 80 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 81 | return completed == YES; 82 | } timeout:1]; 83 | } 84 | 85 | - (void)testSendObjectWithChildren 86 | { 87 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 88 | 89 | DRBOperationTree *root = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 90 | DRBOperationTree *child1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 91 | DRBOperationTree *child2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 92 | 93 | DRBOperationTestProvider *child1Delegate = [[DRBOperationTestProvider alloc] init]; 94 | DRBOperationTestProvider *child2Delegate = [[DRBOperationTestProvider alloc] init]; 95 | 96 | child1.provider = child1Delegate; 97 | child2.provider = child2Delegate; 98 | 99 | [root addChild:child1]; 100 | [root addChild:child2]; 101 | 102 | __block BOOL completed = NO; 103 | [root sendObject:@"foo" completion:^{ 104 | XCTAssert(child1Delegate.object, @"Expected child 1 to be completed"); 105 | XCTAssert(child2Delegate.object, @"Expected child 2 to be completed"); 106 | completed = YES; 107 | }]; 108 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 109 | return completed; 110 | } timeout:1]; 111 | } 112 | 113 | - (void)testCompletionWithGrandchildren 114 | { 115 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 116 | 117 | DRBOperationTree *root = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 118 | DRBOperationTree *child1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 119 | DRBOperationTree *child2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 120 | DRBOperationTree *grandchild1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 121 | DRBOperationTree *grandchild2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 122 | 123 | DRBOperationTestProvider *grandchild1Delegate = [[DRBOperationTestProvider alloc] init]; 124 | DRBOperationTestProvider *grandchild2Delegate = [[DRBOperationTestProvider alloc] init]; 125 | 126 | child1.provider = [[DRBOperationTestProvider alloc] init]; 127 | child2.provider = [[DRBOperationTestProvider alloc] init]; 128 | grandchild1.provider = grandchild1Delegate; 129 | grandchild2.provider = grandchild2Delegate; 130 | 131 | [root addChild:child1]; 132 | [root addChild:child2]; 133 | [child1 addChild:grandchild1]; 134 | [child2 addChild:grandchild2]; 135 | 136 | __block BOOL completed = NO; 137 | [root sendObject:@"foo" completion:^{ 138 | XCTAssert(grandchild1Delegate.object, @"Expected grandchild 1 to be completed"); 139 | XCTAssert(grandchild2Delegate.object, @"Expected grandchild 2 to be completed"); 140 | completed = YES; 141 | }]; 142 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 143 | return completed == YES; 144 | } timeout:1]; 145 | } 146 | 147 | - (void)testRetry 148 | { 149 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 150 | 151 | DRBOperationFailingProvider *provider = [[DRBOperationFailingProvider alloc] init]; 152 | DRBOperationTree *tree = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 153 | tree.provider = provider; 154 | 155 | __block BOOL completed = NO; 156 | [tree enqueueOperationsForObject:@"foo" completion:^{ 157 | completed = YES; 158 | }]; 159 | 160 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 161 | return completed == YES; 162 | } timeout:10]; 163 | 164 | XCTAssertEqual(provider.retryCount, 3U, @"Expected 3 retries"); 165 | } 166 | 167 | @end 168 | 169 | @implementation DRBOperationTestProvider 170 | 171 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(id)object completion:(void (^)(NSArray *))completion 172 | { 173 | completion(@[ object ]); 174 | } 175 | 176 | - (NSOperation *)operationTree:(DRBOperationTree *)node operationForObject:(id)object success:(void (^)(id result))success failure:(void (^)())failure 177 | { 178 | NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 179 | _object = object; 180 | }]; 181 | operation.completionBlock = ^{ success(object); }; 182 | return operation; 183 | } 184 | 185 | @end 186 | 187 | @implementation DRBOperationFailingProvider 188 | 189 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(id)object completion:(void (^)(NSArray *))completion 190 | { 191 | completion(@[ object ]); 192 | } 193 | 194 | - (NSOperation *)operationTree:(DRBOperationTree *)node operationForObject:(id)object success:(void (^)(id result))success failure:(void (^)())failure 195 | { 196 | NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 197 | _retryCount++; 198 | }]; 199 | operation.completionBlock = failure; 200 | return operation; 201 | } 202 | 203 | @end 204 | 205 | -------------------------------------------------------------------------------- /Tests-iOS/Tests-iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.dstnbrkr.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/DRBOperationTreeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DRBOperationTreeTests.m 3 | // 4 | // Created by Dustin Barker on 12/5/13. 5 | // Copyright (c) 2013 Dustin Barker (http://github.com/dstnbrkr), Artsy (http://artsy.net). All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import "DRBOperationTree.h" 26 | #import 27 | #import "XCTestCase+SRTAdditions.h" 28 | 29 | @interface DRBOperationTestProvider : NSObject 30 | @property (nonatomic, readonly) id object; 31 | @property (nonatomic, readonly) BOOL completed; 32 | @end 33 | 34 | @interface DRBOperationFailingProvider : NSObject 35 | @property (nonatomic, readonly) NSUInteger retryCount; 36 | @end 37 | 38 | @interface DRBOperationTreeTests : XCTestCase 39 | @end 40 | 41 | @implementation DRBOperationTreeTests 42 | 43 | - (void)setUp 44 | { 45 | [super setUp]; 46 | } 47 | 48 | - (void)tearDown 49 | { 50 | [super tearDown]; 51 | } 52 | 53 | - (void)testEnqueueOperationWithSingleNode 54 | { 55 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 56 | DRBOperationTree *node = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 57 | DRBOperationTestProvider *provider = [[DRBOperationTestProvider alloc] init]; 58 | node.provider = provider; 59 | 60 | id object = @"foo"; 61 | 62 | __block BOOL completed = NO; 63 | [node enqueueOperationsForObject:object completion:^{ completed = YES; }]; 64 | 65 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 66 | return completed == YES; 67 | } timeout:1]; 68 | 69 | XCTAssert([provider.object isEqual:object], @"Expected provider to receive object"); 70 | XCTAssert(provider.completed, @"Expected provider to be completed"); 71 | } 72 | 73 | - (void)testSendObjectWithSingleNode 74 | { 75 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 76 | DRBOperationTree *node = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 77 | DRBOperationTestProvider *provider = [[DRBOperationTestProvider alloc] init]; 78 | node.provider = provider; 79 | 80 | __block BOOL completed = NO; 81 | [node sendObject:nil completion:^{ completed = YES; }]; 82 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 83 | return completed == YES; 84 | } timeout:1]; 85 | } 86 | 87 | - (void)testSendObjectWithChildren 88 | { 89 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 90 | 91 | DRBOperationTree *root = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 92 | DRBOperationTree *child1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 93 | DRBOperationTree *child2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 94 | 95 | DRBOperationTestProvider *child1Provider = [[DRBOperationTestProvider alloc] init]; 96 | DRBOperationTestProvider *child2Provider = [[DRBOperationTestProvider alloc] init]; 97 | 98 | child1.provider = child1Provider; 99 | child2.provider = child2Provider; 100 | 101 | [root addChild:child1]; 102 | [root addChild:child2]; 103 | 104 | __block BOOL completed = NO; 105 | [root sendObject:@"foo" completion:^{ 106 | XCTAssert(child1Provider.object, @"Expected child 1 to be completed"); 107 | XCTAssert(child2Provider.object, @"Expected child 2 to be completed"); 108 | completed = YES; 109 | }]; 110 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 111 | return completed; 112 | } timeout:1]; 113 | } 114 | 115 | - (void)testCompletionWithGrandchildren 116 | { 117 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 118 | 119 | DRBOperationTree *root = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 120 | DRBOperationTree *child1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 121 | DRBOperationTree *child2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 122 | DRBOperationTree *grandchild1 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 123 | DRBOperationTree *grandchild2 = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 124 | 125 | DRBOperationTestProvider *grandchild1Delegate = [[DRBOperationTestProvider alloc] init]; 126 | DRBOperationTestProvider *grandchild2Delegate = [[DRBOperationTestProvider alloc] init]; 127 | 128 | child1.provider = [[DRBOperationTestProvider alloc] init]; 129 | child2.provider = [[DRBOperationTestProvider alloc] init]; 130 | grandchild1.provider = grandchild1Delegate; 131 | grandchild2.provider = grandchild2Delegate; 132 | 133 | [root addChild:child1]; 134 | [root addChild:child2]; 135 | [child1 addChild:grandchild1]; 136 | [child2 addChild:grandchild2]; 137 | 138 | __block BOOL completed = NO; 139 | [root sendObject:@"foo" completion:^{ 140 | XCTAssert(grandchild1Delegate.object, @"Expected grandchild 1 to be completed"); 141 | XCTAssert(grandchild2Delegate.object, @"Expected grandchild 2 to be completed"); 142 | completed = YES; 143 | }]; 144 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 145 | return completed == YES; 146 | } timeout:1]; 147 | } 148 | 149 | - (void)testRetry 150 | { 151 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 152 | 153 | DRBOperationFailingProvider *provider = [[DRBOperationFailingProvider alloc] init]; 154 | DRBOperationTree *tree = [[DRBOperationTree alloc] initWithOperationQueue:operationQueue]; 155 | tree.provider = provider; 156 | 157 | __block BOOL completed = NO; 158 | [tree enqueueOperationsForObject:@"foo" completion:^{ 159 | completed = YES; 160 | }]; 161 | 162 | [self runCurrentRunLoopUntilTestPasses:^BOOL{ 163 | return completed == YES; 164 | } timeout:10]; 165 | 166 | NSUInteger expected = 3; 167 | XCTAssertEqual(provider.retryCount, expected, @"Expected 3 retries"); 168 | } 169 | 170 | @end 171 | 172 | @implementation DRBOperationTestProvider 173 | 174 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(id)object completion:(void (^)(NSArray *))completion 175 | { 176 | completion(@[ object ]); 177 | } 178 | 179 | - (NSOperation *)operationTree:(DRBOperationTree *)node 180 | operationForObject:(id)object 181 | continuation:(void (^)(id, void (^)()))continuation 182 | failure:(void (^)())failure 183 | { 184 | NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 185 | _object = object; 186 | }]; 187 | operation.completionBlock = ^{ continuation(object, ^{ _completed = YES; }); }; 188 | return operation; 189 | } 190 | 191 | @end 192 | 193 | @implementation DRBOperationFailingProvider 194 | 195 | - (void)operationTree:(DRBOperationTree *)node objectsForObject:(id)object completion:(void (^)(NSArray *))completion 196 | { 197 | completion(@[ object ]); 198 | } 199 | 200 | - (NSOperation *)operationTree:(DRBOperationTree *)node 201 | operationForObject:(id)object 202 | continuation:(void (^)(id, void (^)()))continuation 203 | failure:(void (^)())failure 204 | { 205 | NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 206 | _retryCount++; 207 | }]; 208 | operation.completionBlock = failure; 209 | return operation; 210 | } 211 | 212 | @end 213 | 214 | -------------------------------------------------------------------------------- /Vendor/XCTestCase+SRTAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Square Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import 18 | 19 | typedef BOOL (^PXPredicateBlock)(); 20 | 21 | @interface XCTestCase (PXAdditions) 22 | 23 | - (void)runCurrentRunLoopUntilTestPasses:(PXPredicateBlock)predicate timeout:(NSTimeInterval)timeout; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Vendor/XCTestCase+SRTAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Square Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import "XCTestCase+SRTAdditions.h" 18 | 19 | 20 | @implementation XCTestCase (SRTAdditions) 21 | 22 | - (void)runCurrentRunLoopUntilTestPasses:(PXPredicateBlock)predicate timeout:(NSTimeInterval)timeout; 23 | { 24 | NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; 25 | 26 | NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate]; 27 | NSTimeInterval currentTime; 28 | 29 | for (currentTime = [NSDate timeIntervalSinceReferenceDate]; 30 | !predicate() && currentTime < timeoutTime; 31 | currentTime = [NSDate timeIntervalSinceReferenceDate]) { 32 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 33 | } 34 | 35 | XCTAssert(currentTime <= timeoutTime, @"Timed out"); 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /cookbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstnbrkr/DRBOperationTree/5948d2b6db7b039c399000410e3eb382b73cc550/cookbook.png -------------------------------------------------------------------------------- /cookbook.viz: -------------------------------------------------------------------------------- 1 | digraph G { 2 | "cookbook" [label="/cookbook/a-cookbook", shape=box]; 3 | 4 | "vegetable-stew" [label="/recipes/vegetable-stew", shape=box]; 5 | "vegetable-stew.png" [label="/images/vegetable-stew.png", shape=box]; 6 | 7 | "onion" [label="/ingredients/onion", shape=box]; 8 | "onion.png" [label="/images/onion.png", shape=box]; 9 | 10 | "garlic" [label="/ingredients/garlic", shape=box]; 11 | "garlic.png" [label="/images/garlic.png", shape=box]; 12 | 13 | "carrot" [label="/ingredients/carrot", shape=box]; 14 | "carrot.png" [label="/images/carrot.png", shape=box]; 15 | 16 | "bay leaf" [label="/ingredient/bayleaf", shape=box]; 17 | "bay leaf.png" [label="/images/bayleaf.png", shape=box]; 18 | 19 | "cookbook" -> "vegetable-stew" 20 | 21 | "vegetable-stew" -> "vegetable-stew.png" 22 | 23 | "vegetable-stew" -> "onion" 24 | "vegetable-stew" -> "garlic" 25 | "vegetable-stew" -> "carrot" 26 | "vegetable-stew" -> "bay leaf" 27 | 28 | "onion" -> "onion.png" 29 | "garlic" -> "garlic.png" 30 | "carrot" -> "carrot.png" 31 | "bay leaf" -> "bay leaf.png" 32 | } 33 | --------------------------------------------------------------------------------