├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── LoadIt.podspec ├── LoadIt.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── LoadIt.xcscheme │ └── LoadItExample.xcscheme ├── LoadItExample ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Models │ ├── CitiesResource.swift │ └── City.swift ├── ViewController.swift ├── america.json ├── asia.json └── europe.json ├── Package.swift ├── README.md ├── Sources └── LoadIt │ ├── Convenience Types │ ├── Result.swift │ └── Services │ │ ├── BundleType.swift │ │ ├── DiskJSONService.swift │ │ ├── NetworkJSONResourceService.swift │ │ └── URLSessionType.swift │ ├── Info.plist │ ├── LoadIt.h │ ├── Operations │ ├── BaseOperation.swift │ └── ResourceOperation.swift │ ├── Protocols │ ├── ResourceOperationType.swift │ ├── ResourceServiceType.swift │ └── Resources Definitions │ │ ├── DiskJSONResourceType.swift │ │ ├── JSONResourceType.swift │ │ └── NetworkJSONResourceType.swift │ └── Utilities │ └── NSThread+Additions.swift ├── Tests └── LoadIt │ ├── Helpers │ └── XCTestCase+Additions.swift │ ├── Info.plist │ ├── Mocks │ ├── MockDefaultJSONResourceType.swift │ ├── MockDefaultNetworkJSONResource.swift │ ├── MockDefaultNetworkResource.swift │ ├── MockJSONArrayResourceType.swift │ ├── MockJSONDictionaryResourceType.swift │ ├── MockNetworkJSONResource.swift │ ├── MockNilURLRequestNetworkJSONResource.swift │ ├── MockObject.swift │ ├── MockResource.swift │ ├── MockResourceService.swift │ └── MockURLSession.swift │ ├── Operations │ └── ResourceOperationTests.swift │ ├── Protocols │ ├── JSONResourceTypeTests.swift │ ├── NetworkResourceTypeTests.swift │ └── ResourceOperationTypeTests.swift │ └── Services │ ├── NetworkJSONResourceServiceTests.swift │ └── SubclassedNetworkJSONResourceServiceTests.swift └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7.3 3 | script: 4 | - xcodebuild -scheme LoadIt -configuration Debug -sdk iphonesimulator9.3 -destination "OS=9.3,name=iPad 2" test -enableCodeCoverage YES 5 | after_success: 6 | - bash <(curl -s https://codecov.io/bash) 7 | - pod spec lint LoadIt.podspec 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.1.0 4 | 5 | - Initial beta release 6 | - Contains support for retrieving JSON resources using a network service and operation provided by the libraryr -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Luciano Marisi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LoadIt.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'LoadIt' 3 | spec.version = '0.2.0' 4 | spec.license = { :type => 'MIT', :text => <<-LICENSE 5 | The MIT License (MIT) 6 | Copyright (c) 2016 Luciano Marisi 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 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: 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 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 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | LICENSE 11 | } 12 | spec.homepage = 'https://github.com/lucianomarisi/LoadIt' 13 | spec.authors = { 'Luciano Marisi' => 'luciano@techbrewers.com' } 14 | spec.summary = 'Define resources in protocols and load them using a generic service and operation' 15 | spec.source = { 16 | :git => "https://github.com/lucianomarisi/LoadIt.git", 17 | :tag => spec.version.to_s 18 | } 19 | spec.source_files = 'Sources/LoadIt/**/*.swift' 20 | spec.ios.deployment_target = '8.0' 21 | spec.deprecated_in_favor_of = 'TABResourceLoader' 22 | end 23 | -------------------------------------------------------------------------------- /LoadIt.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F196F551D1FF9580030101A /* ResourceOperationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F196F541D1FF9580030101A /* ResourceOperationType.swift */; }; 11 | 4F196F5D1D207F890030101A /* europe.json in Resources */ = {isa = PBXBuildFile; fileRef = 4F196F5C1D207F890030101A /* europe.json */; }; 12 | 4F3B08421D4035E4006B62B7 /* MockDefaultNetworkResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3B083F1D4035AC006B62B7 /* MockDefaultNetworkResource.swift */; }; 13 | 4F3CFC081D4013680074B6F5 /* ResourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6ED78B1D282B4E009EA9E8 /* ResourceOperation.swift */; }; 14 | 4F3CFC091D40136C0074B6F5 /* BaseOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F196F571D2001CC0030101A /* BaseOperation.swift */; }; 15 | 4F3CFC0D1D401C3F0074B6F5 /* ResourceOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC0C1D401C3F0074B6F5 /* ResourceOperationTests.swift */; }; 16 | 4F3CFC101D401C770074B6F5 /* MockResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC0F1D401C770074B6F5 /* MockResource.swift */; }; 17 | 4F3CFC121D401CD70074B6F5 /* MockResourceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC111D401CD60074B6F5 /* MockResourceService.swift */; }; 18 | 4F3CFC151D401DE40074B6F5 /* JSONResourceTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC141D401DE40074B6F5 /* JSONResourceTypeTests.swift */; }; 19 | 4F3CFC171D401E0B0074B6F5 /* MockJSONArrayResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC161D401E0B0074B6F5 /* MockJSONArrayResourceType.swift */; }; 20 | 4F3CFC191D401E220074B6F5 /* MockObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC181D401E220074B6F5 /* MockObject.swift */; }; 21 | 4F3CFC1B1D401E3E0074B6F5 /* MockJSONDictionaryResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC1A1D401E3E0074B6F5 /* MockJSONDictionaryResourceType.swift */; }; 22 | 4F3CFC1D1D401E530074B6F5 /* MockDefaultJSONResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC1C1D401E530074B6F5 /* MockDefaultJSONResourceType.swift */; }; 23 | 4F3CFC1F1D401EC10074B6F5 /* NetworkResourceTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC1E1D401EC10074B6F5 /* NetworkResourceTypeTests.swift */; }; 24 | 4F3CFC211D401EDF0074B6F5 /* MockDefaultNetworkJSONResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC201D401EDF0074B6F5 /* MockDefaultNetworkJSONResource.swift */; }; 25 | 4F3CFC231D401EF90074B6F5 /* MockNetworkJSONResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC221D401EF90074B6F5 /* MockNetworkJSONResource.swift */; }; 26 | 4F3CFC251D401FE00074B6F5 /* ResourceOperationTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC241D401FE00074B6F5 /* ResourceOperationTypeTests.swift */; }; 27 | 4F3CFC281D4020640074B6F5 /* NetworkJSONResourceServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC271D4020640074B6F5 /* NetworkJSONResourceServiceTests.swift */; }; 28 | 4F3CFC2B1D4020960074B6F5 /* XCTestCase+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC2A1D4020960074B6F5 /* XCTestCase+Additions.swift */; }; 29 | 4F3CFC2D1D4020BE0074B6F5 /* MockNilURLRequestNetworkJSONResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC2C1D4020BE0074B6F5 /* MockNilURLRequestNetworkJSONResource.swift */; }; 30 | 4F3CFC2F1D4020DD0074B6F5 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC2E1D4020DD0074B6F5 /* MockURLSession.swift */; }; 31 | 4F3CFC311D4022710074B6F5 /* SubclassedNetworkJSONResourceServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3CFC301D4022710074B6F5 /* SubclassedNetworkJSONResourceServiceTests.swift */; }; 32 | 4F3CFC321D4023FE0074B6F5 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69981D1F3EB900E753D6 /* BundleType.swift */; }; 33 | 4F3CFC331D4024290074B6F5 /* JSONResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69791D1F39A700E753D6 /* JSONResourceType.swift */; }; 34 | 4F3CFC361D402DD10074B6F5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D695C1D1F374E00E753D6 /* ViewController.swift */; }; 35 | 4F3D693E1D1F36D700E753D6 /* LoadIt.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F3D693D1D1F36D700E753D6 /* LoadIt.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36 | 4F3D69451D1F36D800E753D6 /* LoadIt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F3D693A1D1F36D700E753D6 /* LoadIt.framework */; }; 37 | 4F3D695B1D1F374E00E753D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D695A1D1F374E00E753D6 /* AppDelegate.swift */; }; 38 | 4F3D69601D1F374E00E753D6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D695E1D1F374E00E753D6 /* Main.storyboard */; }; 39 | 4F3D69621D1F374E00E753D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D69611D1F374E00E753D6 /* Assets.xcassets */; }; 40 | 4F3D69651D1F374E00E753D6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D69631D1F374E00E753D6 /* LaunchScreen.storyboard */; }; 41 | 4F3D69781D1F38BB00E753D6 /* City.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69771D1F38BB00E753D6 /* City.swift */; }; 42 | 4F3D697B1D1F39CF00E753D6 /* LoadIt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F3D693A1D1F36D700E753D6 /* LoadIt.framework */; }; 43 | 4F3D697D1D1F3A6F00E753D6 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D697C1D1F3A6F00E753D6 /* Result.swift */; }; 44 | 4F3D697F1D1F3AB700E753D6 /* URLSessionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D697E1D1F3AB700E753D6 /* URLSessionType.swift */; }; 45 | 4F3D69811D1F3B0400E753D6 /* DiskJSONResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69801D1F3B0400E753D6 /* DiskJSONResourceType.swift */; }; 46 | 4F3D69831D1F3B1D00E753D6 /* CitiesResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69821D1F3B1D00E753D6 /* CitiesResource.swift */; }; 47 | 4F3D69891D1F3BDB00E753D6 /* NetworkJSONResourceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69881D1F3BDB00E753D6 /* NetworkJSONResourceService.swift */; }; 48 | 4F3D698B1D1F3C6E00E753D6 /* NetworkJSONResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D698A1D1F3C6E00E753D6 /* NetworkJSONResourceType.swift */; }; 49 | 4F3D698D1D1F3CB500E753D6 /* DiskJSONService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D698C1D1F3CB500E753D6 /* DiskJSONService.swift */; }; 50 | 4F3D69921D1F3DD800E753D6 /* NSThread+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3D69911D1F3DD800E753D6 /* NSThread+Additions.swift */; }; 51 | 4F3D699C1D1F426500E753D6 /* america.json in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D699A1D1F426500E753D6 /* america.json */; }; 52 | 4F3D699D1D1F426500E753D6 /* asia.json in Resources */ = {isa = PBXBuildFile; fileRef = 4F3D699B1D1F426500E753D6 /* asia.json */; }; 53 | 4F6F43401D280C1F00501353 /* ResourceServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6F433F1D280C1F00501353 /* ResourceServiceType.swift */; }; 54 | /* End PBXBuildFile section */ 55 | 56 | /* Begin PBXContainerItemProxy section */ 57 | 4F3D69461D1F36D800E753D6 /* PBXContainerItemProxy */ = { 58 | isa = PBXContainerItemProxy; 59 | containerPortal = 4F3D69311D1F36D700E753D6 /* Project object */; 60 | proxyType = 1; 61 | remoteGlobalIDString = 4F3D69391D1F36D700E753D6; 62 | remoteInfo = LoadIt; 63 | }; 64 | /* End PBXContainerItemProxy section */ 65 | 66 | /* Begin PBXFileReference section */ 67 | 4F196F541D1FF9580030101A /* ResourceOperationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceOperationType.swift; sourceTree = ""; }; 68 | 4F196F571D2001CC0030101A /* BaseOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseOperation.swift; sourceTree = ""; }; 69 | 4F196F5C1D207F890030101A /* europe.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = europe.json; sourceTree = ""; }; 70 | 4F3B083F1D4035AC006B62B7 /* MockDefaultNetworkResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDefaultNetworkResource.swift; sourceTree = ""; }; 71 | 4F3CFC0C1D401C3F0074B6F5 /* ResourceOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceOperationTests.swift; sourceTree = ""; }; 72 | 4F3CFC0F1D401C770074B6F5 /* MockResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockResource.swift; sourceTree = ""; }; 73 | 4F3CFC111D401CD60074B6F5 /* MockResourceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockResourceService.swift; sourceTree = ""; }; 74 | 4F3CFC141D401DE40074B6F5 /* JSONResourceTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONResourceTypeTests.swift; sourceTree = ""; }; 75 | 4F3CFC161D401E0B0074B6F5 /* MockJSONArrayResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockJSONArrayResourceType.swift; sourceTree = ""; }; 76 | 4F3CFC181D401E220074B6F5 /* MockObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockObject.swift; sourceTree = ""; }; 77 | 4F3CFC1A1D401E3E0074B6F5 /* MockJSONDictionaryResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockJSONDictionaryResourceType.swift; sourceTree = ""; }; 78 | 4F3CFC1C1D401E530074B6F5 /* MockDefaultJSONResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDefaultJSONResourceType.swift; sourceTree = ""; }; 79 | 4F3CFC1E1D401EC10074B6F5 /* NetworkResourceTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkResourceTypeTests.swift; sourceTree = ""; }; 80 | 4F3CFC201D401EDF0074B6F5 /* MockDefaultNetworkJSONResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDefaultNetworkJSONResource.swift; sourceTree = ""; }; 81 | 4F3CFC221D401EF90074B6F5 /* MockNetworkJSONResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockNetworkJSONResource.swift; sourceTree = ""; }; 82 | 4F3CFC241D401FE00074B6F5 /* ResourceOperationTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceOperationTypeTests.swift; sourceTree = ""; }; 83 | 4F3CFC271D4020640074B6F5 /* NetworkJSONResourceServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONResourceServiceTests.swift; sourceTree = ""; }; 84 | 4F3CFC2A1D4020960074B6F5 /* XCTestCase+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Additions.swift"; sourceTree = ""; }; 85 | 4F3CFC2C1D4020BE0074B6F5 /* MockNilURLRequestNetworkJSONResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockNilURLRequestNetworkJSONResource.swift; sourceTree = ""; }; 86 | 4F3CFC2E1D4020DD0074B6F5 /* MockURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; 87 | 4F3CFC301D4022710074B6F5 /* SubclassedNetworkJSONResourceServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubclassedNetworkJSONResourceServiceTests.swift; sourceTree = ""; }; 88 | 4F3D693A1D1F36D700E753D6 /* LoadIt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LoadIt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 4F3D693D1D1F36D700E753D6 /* LoadIt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoadIt.h; sourceTree = ""; }; 90 | 4F3D693F1D1F36D700E753D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 91 | 4F3D69441D1F36D800E753D6 /* LoadItTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoadItTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | 4F3D694B1D1F36D800E753D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 93 | 4F3D69581D1F374E00E753D6 /* LoadItExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LoadItExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | 4F3D695A1D1F374E00E753D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 95 | 4F3D695C1D1F374E00E753D6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 96 | 4F3D695F1D1F374E00E753D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97 | 4F3D69611D1F374E00E753D6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 98 | 4F3D69641D1F374E00E753D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 99 | 4F3D69661D1F374E00E753D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 100 | 4F3D69771D1F38BB00E753D6 /* City.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = City.swift; sourceTree = ""; }; 101 | 4F3D69791D1F39A700E753D6 /* JSONResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONResourceType.swift; sourceTree = ""; }; 102 | 4F3D697C1D1F3A6F00E753D6 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 103 | 4F3D697E1D1F3AB700E753D6 /* URLSessionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionType.swift; sourceTree = ""; }; 104 | 4F3D69801D1F3B0400E753D6 /* DiskJSONResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskJSONResourceType.swift; sourceTree = ""; }; 105 | 4F3D69821D1F3B1D00E753D6 /* CitiesResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CitiesResource.swift; sourceTree = ""; }; 106 | 4F3D69881D1F3BDB00E753D6 /* NetworkJSONResourceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONResourceService.swift; sourceTree = ""; }; 107 | 4F3D698A1D1F3C6E00E753D6 /* NetworkJSONResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONResourceType.swift; sourceTree = ""; }; 108 | 4F3D698C1D1F3CB500E753D6 /* DiskJSONService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskJSONService.swift; sourceTree = ""; }; 109 | 4F3D69911D1F3DD800E753D6 /* NSThread+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSThread+Additions.swift"; sourceTree = ""; }; 110 | 4F3D69981D1F3EB900E753D6 /* BundleType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleType.swift; sourceTree = ""; }; 111 | 4F3D699A1D1F426500E753D6 /* america.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = america.json; sourceTree = ""; }; 112 | 4F3D699B1D1F426500E753D6 /* asia.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = asia.json; sourceTree = ""; }; 113 | 4F6ED78B1D282B4E009EA9E8 /* ResourceOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceOperation.swift; sourceTree = ""; }; 114 | 4F6F433F1D280C1F00501353 /* ResourceServiceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceServiceType.swift; sourceTree = ""; }; 115 | /* End PBXFileReference section */ 116 | 117 | /* Begin PBXFrameworksBuildPhase section */ 118 | 4F3D69361D1F36D700E753D6 /* Frameworks */ = { 119 | isa = PBXFrameworksBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | ); 123 | runOnlyForDeploymentPostprocessing = 0; 124 | }; 125 | 4F3D69411D1F36D800E753D6 /* Frameworks */ = { 126 | isa = PBXFrameworksBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | 4F3D69451D1F36D800E753D6 /* LoadIt.framework in Frameworks */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | 4F3D69551D1F374E00E753D6 /* Frameworks */ = { 134 | isa = PBXFrameworksBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | 4F3D697B1D1F39CF00E753D6 /* LoadIt.framework in Frameworks */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXFrameworksBuildPhase section */ 142 | 143 | /* Begin PBXGroup section */ 144 | 4F196F591D2001EF0030101A /* Operations */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 4F196F571D2001CC0030101A /* BaseOperation.swift */, 148 | 4F6ED78B1D282B4E009EA9E8 /* ResourceOperation.swift */, 149 | ); 150 | path = Operations; 151 | sourceTree = ""; 152 | }; 153 | 4F196F5A1D2001FD0030101A /* Models */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 4F3D69821D1F3B1D00E753D6 /* CitiesResource.swift */, 157 | 4F3D69771D1F38BB00E753D6 /* City.swift */, 158 | ); 159 | path = Models; 160 | sourceTree = ""; 161 | }; 162 | 4F3CFC0A1D4017E10074B6F5 /* Services */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 4F3D69981D1F3EB900E753D6 /* BundleType.swift */, 166 | 4F3D698C1D1F3CB500E753D6 /* DiskJSONService.swift */, 167 | 4F3D69881D1F3BDB00E753D6 /* NetworkJSONResourceService.swift */, 168 | 4F3D697E1D1F3AB700E753D6 /* URLSessionType.swift */, 169 | ); 170 | path = Services; 171 | sourceTree = ""; 172 | }; 173 | 4F3CFC0B1D401C210074B6F5 /* Operations */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 4F3CFC0C1D401C3F0074B6F5 /* ResourceOperationTests.swift */, 177 | ); 178 | path = Operations; 179 | sourceTree = ""; 180 | }; 181 | 4F3CFC0E1D401C670074B6F5 /* Mocks */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 4F3CFC1C1D401E530074B6F5 /* MockDefaultJSONResourceType.swift */, 185 | 4F3CFC201D401EDF0074B6F5 /* MockDefaultNetworkJSONResource.swift */, 186 | 4F3B083F1D4035AC006B62B7 /* MockDefaultNetworkResource.swift */, 187 | 4F3CFC161D401E0B0074B6F5 /* MockJSONArrayResourceType.swift */, 188 | 4F3CFC1A1D401E3E0074B6F5 /* MockJSONDictionaryResourceType.swift */, 189 | 4F3CFC221D401EF90074B6F5 /* MockNetworkJSONResource.swift */, 190 | 4F3CFC2C1D4020BE0074B6F5 /* MockNilURLRequestNetworkJSONResource.swift */, 191 | 4F3CFC181D401E220074B6F5 /* MockObject.swift */, 192 | 4F3CFC0F1D401C770074B6F5 /* MockResource.swift */, 193 | 4F3CFC111D401CD60074B6F5 /* MockResourceService.swift */, 194 | 4F3CFC2E1D4020DD0074B6F5 /* MockURLSession.swift */, 195 | ); 196 | path = Mocks; 197 | sourceTree = ""; 198 | }; 199 | 4F3CFC131D401DCA0074B6F5 /* Protocols */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 4F3CFC141D401DE40074B6F5 /* JSONResourceTypeTests.swift */, 203 | 4F3CFC1E1D401EC10074B6F5 /* NetworkResourceTypeTests.swift */, 204 | 4F3CFC241D401FE00074B6F5 /* ResourceOperationTypeTests.swift */, 205 | ); 206 | path = Protocols; 207 | sourceTree = ""; 208 | }; 209 | 4F3CFC261D4020540074B6F5 /* Services */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 4F3CFC271D4020640074B6F5 /* NetworkJSONResourceServiceTests.swift */, 213 | 4F3CFC301D4022710074B6F5 /* SubclassedNetworkJSONResourceServiceTests.swift */, 214 | ); 215 | path = Services; 216 | sourceTree = ""; 217 | }; 218 | 4F3CFC291D4020890074B6F5 /* Helpers */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 4F3CFC2A1D4020960074B6F5 /* XCTestCase+Additions.swift */, 222 | ); 223 | path = Helpers; 224 | sourceTree = ""; 225 | }; 226 | 4F3CFC341D402BF60074B6F5 /* LoadIt */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 4F3CFC291D4020890074B6F5 /* Helpers */, 230 | 4F3CFC0E1D401C670074B6F5 /* Mocks */, 231 | 4F3CFC0B1D401C210074B6F5 /* Operations */, 232 | 4F3CFC131D401DCA0074B6F5 /* Protocols */, 233 | 4F3CFC261D4020540074B6F5 /* Services */, 234 | 4F3D694B1D1F36D800E753D6 /* Info.plist */, 235 | ); 236 | path = LoadIt; 237 | sourceTree = ""; 238 | }; 239 | 4F3CFC351D402D610074B6F5 /* LoadIt */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | 4F3D69971D1F3E7C00E753D6 /* Convenience Types */, 243 | 4F196F591D2001EF0030101A /* Operations */, 244 | 4F3D69961D1F3E6F00E753D6 /* Protocols */, 245 | 4F6F43411D280F2100501353 /* Utilities */, 246 | 4F3D693F1D1F36D700E753D6 /* Info.plist */, 247 | 4F3D693D1D1F36D700E753D6 /* LoadIt.h */, 248 | ); 249 | path = LoadIt; 250 | sourceTree = ""; 251 | }; 252 | 4F3D69301D1F36D700E753D6 = { 253 | isa = PBXGroup; 254 | children = ( 255 | 4F3D69591D1F374E00E753D6 /* LoadItExample */, 256 | 4F3D693B1D1F36D700E753D6 /* Products */, 257 | 4F3D693C1D1F36D700E753D6 /* Sources */, 258 | 4F3D69481D1F36D800E753D6 /* Tests */, 259 | ); 260 | sourceTree = ""; 261 | }; 262 | 4F3D693B1D1F36D700E753D6 /* Products */ = { 263 | isa = PBXGroup; 264 | children = ( 265 | 4F3D693A1D1F36D700E753D6 /* LoadIt.framework */, 266 | 4F3D69441D1F36D800E753D6 /* LoadItTests.xctest */, 267 | 4F3D69581D1F374E00E753D6 /* LoadItExample.app */, 268 | ); 269 | name = Products; 270 | sourceTree = ""; 271 | }; 272 | 4F3D693C1D1F36D700E753D6 /* Sources */ = { 273 | isa = PBXGroup; 274 | children = ( 275 | 4F3CFC351D402D610074B6F5 /* LoadIt */, 276 | ); 277 | path = Sources; 278 | sourceTree = ""; 279 | }; 280 | 4F3D69481D1F36D800E753D6 /* Tests */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | 4F3CFC341D402BF60074B6F5 /* LoadIt */, 284 | ); 285 | path = Tests; 286 | sourceTree = ""; 287 | }; 288 | 4F3D69591D1F374E00E753D6 /* LoadItExample */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | 4F196F5A1D2001FD0030101A /* Models */, 292 | 4F3D695A1D1F374E00E753D6 /* AppDelegate.swift */, 293 | 4F3D69611D1F374E00E753D6 /* Assets.xcassets */, 294 | 4F3D69661D1F374E00E753D6 /* Info.plist */, 295 | 4F3D69631D1F374E00E753D6 /* LaunchScreen.storyboard */, 296 | 4F3D695E1D1F374E00E753D6 /* Main.storyboard */, 297 | 4F3D695C1D1F374E00E753D6 /* ViewController.swift */, 298 | 4F3D699A1D1F426500E753D6 /* america.json */, 299 | 4F3D699B1D1F426500E753D6 /* asia.json */, 300 | 4F196F5C1D207F890030101A /* europe.json */, 301 | ); 302 | path = LoadItExample; 303 | sourceTree = ""; 304 | }; 305 | 4F3D69951D1F3E6400E753D6 /* Resources Definitions */ = { 306 | isa = PBXGroup; 307 | children = ( 308 | 4F3D69801D1F3B0400E753D6 /* DiskJSONResourceType.swift */, 309 | 4F3D69791D1F39A700E753D6 /* JSONResourceType.swift */, 310 | 4F3D698A1D1F3C6E00E753D6 /* NetworkJSONResourceType.swift */, 311 | ); 312 | path = "Resources Definitions"; 313 | sourceTree = ""; 314 | }; 315 | 4F3D69961D1F3E6F00E753D6 /* Protocols */ = { 316 | isa = PBXGroup; 317 | children = ( 318 | 4F3D69951D1F3E6400E753D6 /* Resources Definitions */, 319 | 4F196F541D1FF9580030101A /* ResourceOperationType.swift */, 320 | 4F6F433F1D280C1F00501353 /* ResourceServiceType.swift */, 321 | ); 322 | path = Protocols; 323 | sourceTree = ""; 324 | }; 325 | 4F3D69971D1F3E7C00E753D6 /* Convenience Types */ = { 326 | isa = PBXGroup; 327 | children = ( 328 | 4F3CFC0A1D4017E10074B6F5 /* Services */, 329 | 4F3D697C1D1F3A6F00E753D6 /* Result.swift */, 330 | ); 331 | path = "Convenience Types"; 332 | sourceTree = ""; 333 | }; 334 | 4F6F43411D280F2100501353 /* Utilities */ = { 335 | isa = PBXGroup; 336 | children = ( 337 | 4F3D69911D1F3DD800E753D6 /* NSThread+Additions.swift */, 338 | ); 339 | path = Utilities; 340 | sourceTree = ""; 341 | }; 342 | /* End PBXGroup section */ 343 | 344 | /* Begin PBXHeadersBuildPhase section */ 345 | 4F3D69371D1F36D700E753D6 /* Headers */ = { 346 | isa = PBXHeadersBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | 4F3D693E1D1F36D700E753D6 /* LoadIt.h in Headers */, 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | /* End PBXHeadersBuildPhase section */ 354 | 355 | /* Begin PBXNativeTarget section */ 356 | 4F3D69391D1F36D700E753D6 /* LoadIt */ = { 357 | isa = PBXNativeTarget; 358 | buildConfigurationList = 4F3D694E1D1F36D800E753D6 /* Build configuration list for PBXNativeTarget "LoadIt" */; 359 | buildPhases = ( 360 | 4F3D69351D1F36D700E753D6 /* Sources */, 361 | 4F3D69361D1F36D700E753D6 /* Frameworks */, 362 | 4F3D69371D1F36D700E753D6 /* Headers */, 363 | 4F3D69381D1F36D700E753D6 /* Resources */, 364 | ); 365 | buildRules = ( 366 | ); 367 | dependencies = ( 368 | ); 369 | name = LoadIt; 370 | productName = LoadIt; 371 | productReference = 4F3D693A1D1F36D700E753D6 /* LoadIt.framework */; 372 | productType = "com.apple.product-type.framework"; 373 | }; 374 | 4F3D69431D1F36D800E753D6 /* LoadItTests */ = { 375 | isa = PBXNativeTarget; 376 | buildConfigurationList = 4F3D69511D1F36D800E753D6 /* Build configuration list for PBXNativeTarget "LoadItTests" */; 377 | buildPhases = ( 378 | 4F3D69401D1F36D800E753D6 /* Sources */, 379 | 4F3D69411D1F36D800E753D6 /* Frameworks */, 380 | 4F3D69421D1F36D800E753D6 /* Resources */, 381 | ); 382 | buildRules = ( 383 | ); 384 | dependencies = ( 385 | 4F3D69471D1F36D800E753D6 /* PBXTargetDependency */, 386 | ); 387 | name = LoadItTests; 388 | productName = LoadItTests; 389 | productReference = 4F3D69441D1F36D800E753D6 /* LoadItTests.xctest */; 390 | productType = "com.apple.product-type.bundle.unit-test"; 391 | }; 392 | 4F3D69571D1F374E00E753D6 /* LoadItExample */ = { 393 | isa = PBXNativeTarget; 394 | buildConfigurationList = 4F3D69671D1F374E00E753D6 /* Build configuration list for PBXNativeTarget "LoadItExample" */; 395 | buildPhases = ( 396 | 4F3D69541D1F374E00E753D6 /* Sources */, 397 | 4F3D69551D1F374E00E753D6 /* Frameworks */, 398 | 4F3D69561D1F374E00E753D6 /* Resources */, 399 | ); 400 | buildRules = ( 401 | ); 402 | dependencies = ( 403 | ); 404 | name = LoadItExample; 405 | productName = LoadItExample; 406 | productReference = 4F3D69581D1F374E00E753D6 /* LoadItExample.app */; 407 | productType = "com.apple.product-type.application"; 408 | }; 409 | /* End PBXNativeTarget section */ 410 | 411 | /* Begin PBXProject section */ 412 | 4F3D69311D1F36D700E753D6 /* Project object */ = { 413 | isa = PBXProject; 414 | attributes = { 415 | LastSwiftUpdateCheck = 0730; 416 | LastUpgradeCheck = 0730; 417 | ORGANIZATIONNAME = "Luciano Marisi"; 418 | TargetAttributes = { 419 | 4F3D69391D1F36D700E753D6 = { 420 | CreatedOnToolsVersion = 7.3.1; 421 | }; 422 | 4F3D69431D1F36D800E753D6 = { 423 | CreatedOnToolsVersion = 7.3.1; 424 | }; 425 | 4F3D69571D1F374E00E753D6 = { 426 | CreatedOnToolsVersion = 7.3.1; 427 | }; 428 | }; 429 | }; 430 | buildConfigurationList = 4F3D69341D1F36D700E753D6 /* Build configuration list for PBXProject "LoadIt" */; 431 | compatibilityVersion = "Xcode 3.2"; 432 | developmentRegion = English; 433 | hasScannedForEncodings = 0; 434 | knownRegions = ( 435 | en, 436 | Base, 437 | ); 438 | mainGroup = 4F3D69301D1F36D700E753D6; 439 | productRefGroup = 4F3D693B1D1F36D700E753D6 /* Products */; 440 | projectDirPath = ""; 441 | projectRoot = ""; 442 | targets = ( 443 | 4F3D69571D1F374E00E753D6 /* LoadItExample */, 444 | 4F3D69391D1F36D700E753D6 /* LoadIt */, 445 | 4F3D69431D1F36D800E753D6 /* LoadItTests */, 446 | ); 447 | }; 448 | /* End PBXProject section */ 449 | 450 | /* Begin PBXResourcesBuildPhase section */ 451 | 4F3D69381D1F36D700E753D6 /* Resources */ = { 452 | isa = PBXResourcesBuildPhase; 453 | buildActionMask = 2147483647; 454 | files = ( 455 | ); 456 | runOnlyForDeploymentPostprocessing = 0; 457 | }; 458 | 4F3D69421D1F36D800E753D6 /* Resources */ = { 459 | isa = PBXResourcesBuildPhase; 460 | buildActionMask = 2147483647; 461 | files = ( 462 | ); 463 | runOnlyForDeploymentPostprocessing = 0; 464 | }; 465 | 4F3D69561D1F374E00E753D6 /* Resources */ = { 466 | isa = PBXResourcesBuildPhase; 467 | buildActionMask = 2147483647; 468 | files = ( 469 | 4F3D69651D1F374E00E753D6 /* LaunchScreen.storyboard in Resources */, 470 | 4F3D69621D1F374E00E753D6 /* Assets.xcassets in Resources */, 471 | 4F3D699D1D1F426500E753D6 /* asia.json in Resources */, 472 | 4F196F5D1D207F890030101A /* europe.json in Resources */, 473 | 4F3D699C1D1F426500E753D6 /* america.json in Resources */, 474 | 4F3D69601D1F374E00E753D6 /* Main.storyboard in Resources */, 475 | ); 476 | runOnlyForDeploymentPostprocessing = 0; 477 | }; 478 | /* End PBXResourcesBuildPhase section */ 479 | 480 | /* Begin PBXSourcesBuildPhase section */ 481 | 4F3D69351D1F36D700E753D6 /* Sources */ = { 482 | isa = PBXSourcesBuildPhase; 483 | buildActionMask = 2147483647; 484 | files = ( 485 | 4F3D69891D1F3BDB00E753D6 /* NetworkJSONResourceService.swift in Sources */, 486 | 4F3D69921D1F3DD800E753D6 /* NSThread+Additions.swift in Sources */, 487 | 4F3D69811D1F3B0400E753D6 /* DiskJSONResourceType.swift in Sources */, 488 | 4F3D697D1D1F3A6F00E753D6 /* Result.swift in Sources */, 489 | 4F196F551D1FF9580030101A /* ResourceOperationType.swift in Sources */, 490 | 4F3D698D1D1F3CB500E753D6 /* DiskJSONService.swift in Sources */, 491 | 4F6F43401D280C1F00501353 /* ResourceServiceType.swift in Sources */, 492 | 4F3CFC331D4024290074B6F5 /* JSONResourceType.swift in Sources */, 493 | 4F3CFC091D40136C0074B6F5 /* BaseOperation.swift in Sources */, 494 | 4F3D698B1D1F3C6E00E753D6 /* NetworkJSONResourceType.swift in Sources */, 495 | 4F3CFC321D4023FE0074B6F5 /* BundleType.swift in Sources */, 496 | 4F3D697F1D1F3AB700E753D6 /* URLSessionType.swift in Sources */, 497 | 4F3CFC081D4013680074B6F5 /* ResourceOperation.swift in Sources */, 498 | ); 499 | runOnlyForDeploymentPostprocessing = 0; 500 | }; 501 | 4F3D69401D1F36D800E753D6 /* Sources */ = { 502 | isa = PBXSourcesBuildPhase; 503 | buildActionMask = 2147483647; 504 | files = ( 505 | 4F3CFC211D401EDF0074B6F5 /* MockDefaultNetworkJSONResource.swift in Sources */, 506 | 4F3CFC191D401E220074B6F5 /* MockObject.swift in Sources */, 507 | 4F3CFC231D401EF90074B6F5 /* MockNetworkJSONResource.swift in Sources */, 508 | 4F3CFC311D4022710074B6F5 /* SubclassedNetworkJSONResourceServiceTests.swift in Sources */, 509 | 4F3CFC1D1D401E530074B6F5 /* MockDefaultJSONResourceType.swift in Sources */, 510 | 4F3CFC2F1D4020DD0074B6F5 /* MockURLSession.swift in Sources */, 511 | 4F3B08421D4035E4006B62B7 /* MockDefaultNetworkResource.swift in Sources */, 512 | 4F3CFC2B1D4020960074B6F5 /* XCTestCase+Additions.swift in Sources */, 513 | 4F3CFC251D401FE00074B6F5 /* ResourceOperationTypeTests.swift in Sources */, 514 | 4F3CFC1F1D401EC10074B6F5 /* NetworkResourceTypeTests.swift in Sources */, 515 | 4F3CFC171D401E0B0074B6F5 /* MockJSONArrayResourceType.swift in Sources */, 516 | 4F3CFC1B1D401E3E0074B6F5 /* MockJSONDictionaryResourceType.swift in Sources */, 517 | 4F3CFC2D1D4020BE0074B6F5 /* MockNilURLRequestNetworkJSONResource.swift in Sources */, 518 | 4F3CFC151D401DE40074B6F5 /* JSONResourceTypeTests.swift in Sources */, 519 | 4F3CFC0D1D401C3F0074B6F5 /* ResourceOperationTests.swift in Sources */, 520 | 4F3CFC101D401C770074B6F5 /* MockResource.swift in Sources */, 521 | 4F3CFC281D4020640074B6F5 /* NetworkJSONResourceServiceTests.swift in Sources */, 522 | 4F3CFC121D401CD70074B6F5 /* MockResourceService.swift in Sources */, 523 | ); 524 | runOnlyForDeploymentPostprocessing = 0; 525 | }; 526 | 4F3D69541D1F374E00E753D6 /* Sources */ = { 527 | isa = PBXSourcesBuildPhase; 528 | buildActionMask = 2147483647; 529 | files = ( 530 | 4F3D69831D1F3B1D00E753D6 /* CitiesResource.swift in Sources */, 531 | 4F3D695B1D1F374E00E753D6 /* AppDelegate.swift in Sources */, 532 | 4F3CFC361D402DD10074B6F5 /* ViewController.swift in Sources */, 533 | 4F3D69781D1F38BB00E753D6 /* City.swift in Sources */, 534 | ); 535 | runOnlyForDeploymentPostprocessing = 0; 536 | }; 537 | /* End PBXSourcesBuildPhase section */ 538 | 539 | /* Begin PBXTargetDependency section */ 540 | 4F3D69471D1F36D800E753D6 /* PBXTargetDependency */ = { 541 | isa = PBXTargetDependency; 542 | target = 4F3D69391D1F36D700E753D6 /* LoadIt */; 543 | targetProxy = 4F3D69461D1F36D800E753D6 /* PBXContainerItemProxy */; 544 | }; 545 | /* End PBXTargetDependency section */ 546 | 547 | /* Begin PBXVariantGroup section */ 548 | 4F3D695E1D1F374E00E753D6 /* Main.storyboard */ = { 549 | isa = PBXVariantGroup; 550 | children = ( 551 | 4F3D695F1D1F374E00E753D6 /* Base */, 552 | ); 553 | name = Main.storyboard; 554 | path = .; 555 | sourceTree = ""; 556 | }; 557 | 4F3D69631D1F374E00E753D6 /* LaunchScreen.storyboard */ = { 558 | isa = PBXVariantGroup; 559 | children = ( 560 | 4F3D69641D1F374E00E753D6 /* Base */, 561 | ); 562 | name = LaunchScreen.storyboard; 563 | path = .; 564 | sourceTree = ""; 565 | }; 566 | /* End PBXVariantGroup section */ 567 | 568 | /* Begin XCBuildConfiguration section */ 569 | 4F3D694C1D1F36D800E753D6 /* Debug */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | ALWAYS_SEARCH_USER_PATHS = NO; 573 | CLANG_ANALYZER_NONNULL = YES; 574 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 575 | CLANG_CXX_LIBRARY = "libc++"; 576 | CLANG_ENABLE_MODULES = YES; 577 | CLANG_ENABLE_OBJC_ARC = YES; 578 | CLANG_WARN_BOOL_CONVERSION = YES; 579 | CLANG_WARN_CONSTANT_CONVERSION = YES; 580 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 581 | CLANG_WARN_EMPTY_BODY = YES; 582 | CLANG_WARN_ENUM_CONVERSION = YES; 583 | CLANG_WARN_INT_CONVERSION = YES; 584 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 585 | CLANG_WARN_UNREACHABLE_CODE = YES; 586 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 587 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 588 | COPY_PHASE_STRIP = NO; 589 | CURRENT_PROJECT_VERSION = 1; 590 | DEBUG_INFORMATION_FORMAT = dwarf; 591 | ENABLE_STRICT_OBJC_MSGSEND = YES; 592 | ENABLE_TESTABILITY = YES; 593 | GCC_C_LANGUAGE_STANDARD = gnu99; 594 | GCC_DYNAMIC_NO_PIC = NO; 595 | GCC_NO_COMMON_BLOCKS = YES; 596 | GCC_OPTIMIZATION_LEVEL = 0; 597 | GCC_PREPROCESSOR_DEFINITIONS = ( 598 | "DEBUG=1", 599 | "$(inherited)", 600 | ); 601 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 602 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 603 | GCC_WARN_UNDECLARED_SELECTOR = YES; 604 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 605 | GCC_WARN_UNUSED_FUNCTION = YES; 606 | GCC_WARN_UNUSED_VARIABLE = YES; 607 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 608 | MTL_ENABLE_DEBUG_INFO = YES; 609 | ONLY_ACTIVE_ARCH = YES; 610 | SDKROOT = iphoneos; 611 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 612 | TARGETED_DEVICE_FAMILY = "1,2"; 613 | VERSIONING_SYSTEM = "apple-generic"; 614 | VERSION_INFO_PREFIX = ""; 615 | }; 616 | name = Debug; 617 | }; 618 | 4F3D694D1D1F36D800E753D6 /* Release */ = { 619 | isa = XCBuildConfiguration; 620 | buildSettings = { 621 | ALWAYS_SEARCH_USER_PATHS = NO; 622 | CLANG_ANALYZER_NONNULL = YES; 623 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 624 | CLANG_CXX_LIBRARY = "libc++"; 625 | CLANG_ENABLE_MODULES = YES; 626 | CLANG_ENABLE_OBJC_ARC = YES; 627 | CLANG_WARN_BOOL_CONVERSION = YES; 628 | CLANG_WARN_CONSTANT_CONVERSION = YES; 629 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 630 | CLANG_WARN_EMPTY_BODY = YES; 631 | CLANG_WARN_ENUM_CONVERSION = YES; 632 | CLANG_WARN_INT_CONVERSION = YES; 633 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 634 | CLANG_WARN_UNREACHABLE_CODE = YES; 635 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 636 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 637 | COPY_PHASE_STRIP = NO; 638 | CURRENT_PROJECT_VERSION = 1; 639 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 640 | ENABLE_NS_ASSERTIONS = NO; 641 | ENABLE_STRICT_OBJC_MSGSEND = YES; 642 | GCC_C_LANGUAGE_STANDARD = gnu99; 643 | GCC_NO_COMMON_BLOCKS = YES; 644 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 645 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 646 | GCC_WARN_UNDECLARED_SELECTOR = YES; 647 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 648 | GCC_WARN_UNUSED_FUNCTION = YES; 649 | GCC_WARN_UNUSED_VARIABLE = YES; 650 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 651 | MTL_ENABLE_DEBUG_INFO = NO; 652 | SDKROOT = iphoneos; 653 | TARGETED_DEVICE_FAMILY = "1,2"; 654 | VALIDATE_PRODUCT = YES; 655 | VERSIONING_SYSTEM = "apple-generic"; 656 | VERSION_INFO_PREFIX = ""; 657 | }; 658 | name = Release; 659 | }; 660 | 4F3D694F1D1F36D800E753D6 /* Debug */ = { 661 | isa = XCBuildConfiguration; 662 | buildSettings = { 663 | CLANG_ENABLE_MODULES = YES; 664 | DEFINES_MODULE = YES; 665 | DYLIB_COMPATIBILITY_VERSION = 1; 666 | DYLIB_CURRENT_VERSION = 1; 667 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 668 | INFOPLIST_FILE = Sources/LoadIt/Info.plist; 669 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 670 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 671 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadIt; 672 | PRODUCT_NAME = "$(TARGET_NAME)"; 673 | SKIP_INSTALL = YES; 674 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 675 | }; 676 | name = Debug; 677 | }; 678 | 4F3D69501D1F36D800E753D6 /* Release */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | CLANG_ENABLE_MODULES = YES; 682 | DEFINES_MODULE = YES; 683 | DYLIB_COMPATIBILITY_VERSION = 1; 684 | DYLIB_CURRENT_VERSION = 1; 685 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 686 | INFOPLIST_FILE = Sources/LoadIt/Info.plist; 687 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 688 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 689 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadIt; 690 | PRODUCT_NAME = "$(TARGET_NAME)"; 691 | SKIP_INSTALL = YES; 692 | }; 693 | name = Release; 694 | }; 695 | 4F3D69521D1F36D800E753D6 /* Debug */ = { 696 | isa = XCBuildConfiguration; 697 | buildSettings = { 698 | INFOPLIST_FILE = Tests/LoadIt/Info.plist; 699 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 700 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadItTests; 701 | PRODUCT_NAME = "$(TARGET_NAME)"; 702 | }; 703 | name = Debug; 704 | }; 705 | 4F3D69531D1F36D800E753D6 /* Release */ = { 706 | isa = XCBuildConfiguration; 707 | buildSettings = { 708 | INFOPLIST_FILE = Tests/LoadIt/Info.plist; 709 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 710 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadItTests; 711 | PRODUCT_NAME = "$(TARGET_NAME)"; 712 | }; 713 | name = Release; 714 | }; 715 | 4F3D69681D1F374E00E753D6 /* Debug */ = { 716 | isa = XCBuildConfiguration; 717 | buildSettings = { 718 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 719 | INFOPLIST_FILE = LoadItExample/Info.plist; 720 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 721 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadItExample; 722 | PRODUCT_NAME = "$(TARGET_NAME)"; 723 | }; 724 | name = Debug; 725 | }; 726 | 4F3D69691D1F374E00E753D6 /* Release */ = { 727 | isa = XCBuildConfiguration; 728 | buildSettings = { 729 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 730 | INFOPLIST_FILE = LoadItExample/Info.plist; 731 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 732 | PRODUCT_BUNDLE_IDENTIFIER = com.lucianomarisi.LoadItExample; 733 | PRODUCT_NAME = "$(TARGET_NAME)"; 734 | }; 735 | name = Release; 736 | }; 737 | /* End XCBuildConfiguration section */ 738 | 739 | /* Begin XCConfigurationList section */ 740 | 4F3D69341D1F36D700E753D6 /* Build configuration list for PBXProject "LoadIt" */ = { 741 | isa = XCConfigurationList; 742 | buildConfigurations = ( 743 | 4F3D694C1D1F36D800E753D6 /* Debug */, 744 | 4F3D694D1D1F36D800E753D6 /* Release */, 745 | ); 746 | defaultConfigurationIsVisible = 0; 747 | defaultConfigurationName = Release; 748 | }; 749 | 4F3D694E1D1F36D800E753D6 /* Build configuration list for PBXNativeTarget "LoadIt" */ = { 750 | isa = XCConfigurationList; 751 | buildConfigurations = ( 752 | 4F3D694F1D1F36D800E753D6 /* Debug */, 753 | 4F3D69501D1F36D800E753D6 /* Release */, 754 | ); 755 | defaultConfigurationIsVisible = 0; 756 | defaultConfigurationName = Release; 757 | }; 758 | 4F3D69511D1F36D800E753D6 /* Build configuration list for PBXNativeTarget "LoadItTests" */ = { 759 | isa = XCConfigurationList; 760 | buildConfigurations = ( 761 | 4F3D69521D1F36D800E753D6 /* Debug */, 762 | 4F3D69531D1F36D800E753D6 /* Release */, 763 | ); 764 | defaultConfigurationIsVisible = 0; 765 | defaultConfigurationName = Release; 766 | }; 767 | 4F3D69671D1F374E00E753D6 /* Build configuration list for PBXNativeTarget "LoadItExample" */ = { 768 | isa = XCConfigurationList; 769 | buildConfigurations = ( 770 | 4F3D69681D1F374E00E753D6 /* Debug */, 771 | 4F3D69691D1F374E00E753D6 /* Release */, 772 | ); 773 | defaultConfigurationIsVisible = 0; 774 | defaultConfigurationName = Release; 775 | }; 776 | /* End XCConfigurationList section */ 777 | }; 778 | rootObject = 4F3D69311D1F36D700E753D6 /* Project object */; 779 | } 780 | -------------------------------------------------------------------------------- /LoadIt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LoadIt.xcodeproj/xcshareddata/xcschemes/LoadIt.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /LoadIt.xcodeproj/xcshareddata/xcschemes/LoadItExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /LoadItExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LoadItExample 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /LoadItExample/Assets.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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /LoadItExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LoadItExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LoadItExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSAppTransportSecurity 47 | 48 | NSAllowsArbitraryLoads 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /LoadItExample/Models/CitiesResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CitiesResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LoadIt 11 | 12 | private let baseURL = NSURL(string: "http://localhost:8000/")! 13 | 14 | struct CitiesResource: NetworkJSONResourceType, DiskJSONResourceType { 15 | typealias Model = [City] 16 | 17 | let url: NSURL 18 | let filename: String 19 | 20 | init(continent: String) { 21 | url = baseURL.URLByAppendingPathComponent("\(continent).json") 22 | filename = continent 23 | } 24 | 25 | //MARK: JSONResource 26 | func modelFrom(jsonDictionary jsonDictionary: [String: AnyObject]) -> [City]? { 27 | guard let 28 | citiesJSONArray = jsonDictionary["cities"] as? [[String: AnyObject]] 29 | else { 30 | return [] 31 | } 32 | return citiesJSONArray.flatMap(City.init) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /LoadItExample/Models/City.swift: -------------------------------------------------------------------------------- 1 | // 2 | // City.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct City { 12 | let name: String 13 | } 14 | 15 | extension City { 16 | init?(jsonDictionary: [String : AnyObject]) { 17 | guard let parsedName = jsonDictionary["name"] as? String else { 18 | return nil 19 | } 20 | name = parsedName 21 | } 22 | } -------------------------------------------------------------------------------- /LoadItExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // LoadItExample 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import LoadIt 11 | 12 | struct MockResource: ResourceType { 13 | typealias Model = String 14 | } 15 | 16 | struct MockService: ResourceServiceType { 17 | typealias Resource = MockResource 18 | 19 | func fetch(resource resource: Resource, completion: (Result) -> Void) { 20 | completion(Result.Success("Mock result")) 21 | } 22 | 23 | } 24 | 25 | private typealias CitiesNetworkResourceOperation = ResourceOperation> 26 | private typealias CitiesDiskResourceOperation = ResourceOperation> 27 | private typealias MockResourceOperation = ResourceOperation 28 | 29 | class ViewController: UIViewController { 30 | 31 | private let operationQueue = NSOperationQueue() 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | let americaResource = CitiesResource(continent: "america") 37 | let citiesNetworkResourceOperation = CitiesNetworkResourceOperation(resource: americaResource) { [weak self] operation, result in 38 | if operation.cancelled { return } 39 | self?.log(result: result) 40 | } 41 | operationQueue.addOperation(citiesNetworkResourceOperation) 42 | // citiesNetworkJSONOperation.cancel() 43 | // operationQueue.cancelAllOperations() 44 | 45 | let asiaResource = CitiesResource(continent: "asia") 46 | let citiesResourceOperation = CitiesDiskResourceOperation(resource: asiaResource) { [weak self] operation, result in 47 | if operation.cancelled { return } 48 | self?.log(result: result) 49 | } 50 | operationQueue.addOperation(citiesResourceOperation) 51 | 52 | let mockResource = MockResource() 53 | let mockService = MockService() 54 | let mockCitiesResourceOperation = MockResourceOperation(resource: mockResource, service: mockService){ [weak self] operation, result in 55 | if operation.cancelled { return } 56 | self?.log(result: result) 57 | } 58 | // mockCitiesResourceOperation.execute() 59 | // mockCitiesResourceOperation.cancel() 60 | operationQueue.addOperation(mockCitiesResourceOperation) 61 | 62 | 63 | let europeResource = CitiesResource(continent: "europe") 64 | 65 | let diskJSONService = DiskJSONService() 66 | diskJSONService.fetch(resource: europeResource) {[weak self] result in 67 | self?.log(result: result) 68 | } 69 | 70 | let networkJSONService = NetworkJSONResourceService() 71 | networkJSONService.fetch(resource: europeResource) { [weak self] result in 72 | self?.log(result: result) 73 | } 74 | 75 | } 76 | 77 | func log(result result: Result) { 78 | if case .Success(let cities) = result { 79 | print(cities) 80 | } else { 81 | print(result) 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /LoadItExample/america.json: -------------------------------------------------------------------------------- 1 | { 2 | "cities": [{ 3 | "name": "Buenos Aires" 4 | }, { 5 | "name": "Montevideo" 6 | }] 7 | } -------------------------------------------------------------------------------- /LoadItExample/asia.json: -------------------------------------------------------------------------------- 1 | { 2 | "cities": [{ 3 | "name": "Beijing" 4 | }, { 5 | "name": "Tokyo" 6 | }] 7 | } -------------------------------------------------------------------------------- /LoadItExample/europe.json: -------------------------------------------------------------------------------- 1 | { 2 | "cities": [{ 3 | "name": "Paris" 4 | }, { 5 | "name": "London" 6 | }] 7 | } -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "LoadIt" 5 | ) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS IS REPOSITORY IS NOW DEPRECATED, USE IN [TABResourceLoader](https://github.com/theappbusiness/TABResourceLoader) INSTEAD 2 | 3 | # LoadIt 4 | 5 | [![Build Status](https://travis-ci.org/lucianomarisi/LoadIt.svg?branch=master)](https://travis-ci.org/lucianomarisi/LoadIt) 6 | [![](https://img.shields.io/cocoapods/v/LoadIt.svg)](https://cocoapods.org/pods/LoadIt) 7 | [![](https://img.shields.io/cocoapods/p/LoadIt.svg?style=flat)](https://cocoapods.org/pods/LoadIt) 8 | [![codecov.io](http://codecov.io/github/lucianomarisi/LoadIt/coverage.svg?branch=master)](http://codecov.io/github/lucianomarisi/LoadIt?branch=master) 9 | [![Documentation](https://img.shields.io/cocoapods/metrics/doc-percent/LoadIt.svg?style=flat)](http://cocoadocs.org/docsets/LoadIt/) 10 | 11 | The idea behind this library is for the user to define the resources in their application by conforming to protocols that define where and how to get them. These resource can then be retrieved using a generic service type with or without an operation provided by the library. 12 | 13 | **Note that this library is still under development and so at the moment the only supported resource type is JSON.** 14 | 15 | **The original idea for this pattern is explained on [Protocol oriented loading of resources from a network service in Swift](http://www.marisibrothers.com/2016/07/protocol-oriented-loading-of-resources.html)** 16 | 17 | ## Simple example 18 | 19 | Let's say you need to retrieve this JSON from an server, `http://localhost:8000/.json`: 20 | 21 | ```json 22 | { 23 | "cities": [{ 24 | "name": "Paris" 25 | }, { 26 | "name": "London" 27 | }] 28 | } 29 | ``` 30 | 31 | The Swift model for a city could look something like this: 32 | 33 | ```swift 34 | struct City { 35 | let name: String 36 | } 37 | ``` 38 | 39 | Some basic parsing logic: 40 | 41 | ```swift 42 | extension City { 43 | init?(jsonDictionary: [String : AnyObject]) { 44 | guard let parsedName = jsonDictionary["name"] as? String else { 45 | return nil 46 | } 47 | name = parsedName 48 | } 49 | } 50 | ``` 51 | 52 | ### Defining a `Resource` 53 | 54 | A `CitiesResource` can be created to define where the cities will come from and how the base endpoint should be parsed: 55 | 56 | ```swift 57 | private let baseURL = NSURL(string: "http://localhost:8000/europe.json")! 58 | 59 | struct CitiesResource: NetworkJSONResourceType { 60 | typealias Model = [City] 61 | 62 | let url: NSURL 63 | 64 | init(continent: String) { 65 | url = baseURL.URLByAppendingPathComponent("\(continent).json") 66 | } 67 | 68 | //MARK: JSONResource 69 | func modelFrom(jsonDictionary jsonDictionary: [String: AnyObject]) -> [City]? { 70 | guard let 71 | citiesJSONArray = jsonDictionary["cities"] as? [[String: AnyObject]] 72 | else { 73 | return [] 74 | } 75 | return citiesJSONArray.flatMap(City.init) 76 | } 77 | 78 | } 79 | ``` 80 | 81 | ### Retrieving the resource 82 | 83 | Use the provided `NetworkJSONResourceService` to retrieve your `CitiesResource` from a web service: 84 | 85 | ```swift 86 | let europeResource = CitiesResource(continent: "europe") 87 | let networkJSONService = NetworkJSONResourceService() 88 | networkJSONService.fetch(resource: europeResource) { [weak self] result in 89 | // do something with the result 90 | } 91 | ``` 92 | 93 | **OR** 94 | 95 | Define a typealias for conveniency if you using `NSOperation`s: 96 | 97 | ```swift 98 | private typealias CitiesNetworkResourceOperation = ResourceOperation> 99 | ``` 100 | 101 | Create the operation using a `CitiesResource` 102 | 103 | ```swift 104 | let europeResource = CitiesResource(continent: "europe") 105 | let citiesNetworkResourceOperation = CitiesNetworkResourceOperation(resource: europeResource) { [weak self] operation, result in 106 | // do something with the result 107 | } 108 | ``` 109 | 110 | Add the operation to some queue 111 | ```swift 112 | let operationQueue = NSOperationQueue() 113 | operationQueue.addOperation(citiesNetworkResourceOperation) 114 | ``` 115 | 116 | ## Creating your own services and operations 117 | 118 | At the moment the only service provided is the `NetworkJSONResourceService`. This will change in future updates where it may be relevant to ship this library with more default services. In the meantime the user can create they own service by conforming to `ResourceServiceType`. Similarly, even though the `ResourceOperation` may cater for most needs the developer can choose to have their own resource operation that conforms to `ResourceOperationType`. 119 | -------------------------------------------------------------------------------- /Sources/LoadIt/Convenience Types/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Result { 12 | case Success(T) 13 | case Failure(ErrorType) 14 | } 15 | 16 | extension Result { 17 | 18 | /// Returns the success result if it exists, otherwise nil 19 | func successResult() -> T? { 20 | switch self { 21 | case .Success(let successResult): 22 | return successResult 23 | case .Failure: 24 | return nil 25 | } 26 | } 27 | 28 | /// Returns the error if it exists, otherwise nil 29 | func error() -> ErrorType? { 30 | switch self { 31 | case .Success: 32 | return nil 33 | case .Failure(let error): 34 | return error 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/LoadIt/Convenience Types/Services/BundleType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol BundleType { 12 | func URLForResource(name: String?, withExtension ext: String?) -> NSURL? 13 | } 14 | 15 | extension NSBundle: BundleType {} 16 | -------------------------------------------------------------------------------- /Sources/LoadIt/Convenience Types/Services/DiskJSONService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiskJSONService.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum DiskJSONServiceError: ErrorType { 12 | case FileNotFound 13 | case NoData 14 | } 15 | 16 | public struct DiskJSONService { 17 | 18 | private let bundle: BundleType 19 | 20 | public init() { 21 | self.init(bundle: NSBundle.mainBundle()) 22 | } 23 | 24 | init(bundle: BundleType) { 25 | self.bundle = bundle 26 | } 27 | 28 | private func resultFrom(resource resource: Resource) -> Result{ 29 | guard let url = bundle.URLForResource(resource.filename, withExtension: "json") else { 30 | return.Failure(DiskJSONServiceError.FileNotFound) 31 | } 32 | 33 | guard let data = NSData(contentsOfURL: url) else { 34 | return.Failure(DiskJSONServiceError.NoData) 35 | } 36 | 37 | return resource.resultFrom(data: data) 38 | } 39 | } 40 | 41 | // MARK: - ResourceServiceType 42 | extension DiskJSONService: ResourceServiceType { 43 | 44 | public func fetch(resource resource: Resource, completion: (Result) -> Void) { 45 | completion(resultFrom(resource: resource)) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/LoadIt/Convenience Types/Services/NetworkJSONResourceService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkJSONResourceService.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Enum representing an error from a network service 13 | 14 | - CouldNotCreateURLRequest: The URL request could be formed 15 | - StatusCodeError: A status code error between 400 and 600 (not including 600) was returned 16 | - NetworkingError: Any other networking error 17 | - NoData: No data was returned 18 | */ 19 | public enum NetworkServiceError: ErrorType { 20 | case CouldNotCreateURLRequest 21 | case StatusCodeError(statusCode: Int) 22 | case NetworkingError(error: NSError) 23 | case NoData 24 | } 25 | 26 | public class NetworkJSONResourceService: ResourceServiceType { 27 | 28 | /** 29 | Method designed to be implemented on subclasses, these fields will be overriden by any HTTP header field 30 | key that is defined in the resource (in case of conflicts) 31 | 32 | - returns: Return any additional header fields that need to be added to the url request 33 | */ 34 | public func additionalHeaderFields() -> [String: String] { 35 | return [:] 36 | } 37 | 38 | let session: URLSessionType 39 | 40 | public required init() { 41 | self.session = NSURLSession.sharedSession() 42 | } 43 | 44 | init(session: URLSessionType) { 45 | self.session = session 46 | } 47 | 48 | public final func fetch(resource resource: Resource, completion: (Result) -> Void) { 49 | guard let urlRequest = resource.urlRequest() as? NSMutableURLRequest else { 50 | completion(.Failure(NetworkServiceError.CouldNotCreateURLRequest)) 51 | return 52 | } 53 | 54 | urlRequest.allHTTPHeaderFields = allHTTPHeaderFields(resourceHTTPHeaderFields: urlRequest.allHTTPHeaderFields) 55 | 56 | session.perform(request: urlRequest) { (data, URLResponse, error) in 57 | completion(self.resultFrom(resource: resource, data: data, URLResponse: URLResponse, error: error)) 58 | } 59 | } 60 | 61 | private func allHTTPHeaderFields(resourceHTTPHeaderFields resourceHTTPHeaderFields: [String: String]?) -> [String: String]? { 62 | var generalHTTPHeaderFields = additionalHeaderFields() 63 | if let resourceHTTPHeaderFields = resourceHTTPHeaderFields { 64 | for (key, value) in resourceHTTPHeaderFields { 65 | generalHTTPHeaderFields[key] = value 66 | } 67 | } 68 | return generalHTTPHeaderFields 69 | } 70 | 71 | private func resultFrom(resource resource: Resource, data: NSData?, URLResponse: NSURLResponse?, error: NSError?) -> Result { 72 | 73 | if let HTTPURLResponse = URLResponse as? NSHTTPURLResponse { 74 | switch HTTPURLResponse.statusCode { 75 | case 400..<600: 76 | return .Failure(NetworkServiceError.StatusCodeError(statusCode: HTTPURLResponse.statusCode)) 77 | default: break 78 | } 79 | } 80 | 81 | if let error = error { 82 | return .Failure(NetworkServiceError.NetworkingError(error: error)) 83 | } 84 | 85 | guard let data = data else { 86 | return .Failure(NetworkServiceError.NoData) 87 | } 88 | 89 | return resource.resultFrom(data: data) 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Sources/LoadIt/Convenience Types/Services/URLSessionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionType.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol URLSessionType { 12 | func perform(request request: NSURLRequest, completion: (NSData?, NSURLResponse?, NSError?) -> Void) 13 | } 14 | 15 | extension NSURLSession: URLSessionType { 16 | public func perform(request request: NSURLRequest, completion: (NSData?, NSURLResponse?, NSError?) -> Void) { 17 | dataTaskWithRequest(request, completionHandler: completion).resume() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/LoadIt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/LoadIt/LoadIt.h: -------------------------------------------------------------------------------- 1 | // 2 | // LoadIt.h 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LoadIt. 12 | FOUNDATION_EXPORT double LoadItVersionNumber; 13 | 14 | //! Project version string for LoadIt. 15 | FOUNDATION_EXPORT const unsigned char LoadItVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/LoadIt/Operations/BaseOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseOperation.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class BaseOperation: NSOperation { 12 | 13 | public override var asynchronous: Bool { 14 | get{ 15 | return true 16 | } 17 | } 18 | 19 | private var _executing: Bool = false 20 | public override var executing:Bool { 21 | get { return _executing } 22 | set { 23 | willChangeValueForKey("isExecuting") 24 | _executing = newValue 25 | didChangeValueForKey("isExecuting") 26 | if _cancelled == true { 27 | self.finished = true 28 | } 29 | } 30 | } 31 | private var _finished: Bool = false 32 | public override var finished: Bool { 33 | get { return _finished } 34 | set { 35 | willChangeValueForKey("isFinished") 36 | _finished = newValue 37 | didChangeValueForKey("isFinished") 38 | } 39 | } 40 | 41 | private var _cancelled: Bool = false 42 | public override var cancelled: Bool { 43 | get { return _cancelled } 44 | set { 45 | willChangeValueForKey("isCancelled") 46 | _cancelled = newValue 47 | didChangeValueForKey("isCancelled") 48 | } 49 | } 50 | 51 | public final override func start() { 52 | super.start() 53 | self.executing = true 54 | } 55 | 56 | public final override func main() { 57 | if cancelled { 58 | executing = false 59 | finished = true 60 | return 61 | } 62 | execute() 63 | } 64 | 65 | public func execute() { 66 | assertionFailure("execute must overriden") 67 | finish() 68 | } 69 | 70 | public final func finish(errors: [NSError] = []) { 71 | self.finished = true 72 | self.executing = false 73 | } 74 | 75 | public final override func cancel() { 76 | super.cancel() 77 | cancelled = true 78 | if executing { 79 | executing = false 80 | finished = true 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/LoadIt/Operations/ResourceOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceOperation.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 02/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class ResourceOperation: BaseOperation, ResourceOperationType { 12 | 13 | public typealias Resource = ResourceService.Resource 14 | public typealias DidFinishFetchingResourceCallback = (ResourceOperation, Result) -> Void 15 | 16 | private let resource: Resource 17 | private let service: ResourceService 18 | private let didFinishFetchingResourceCallback: DidFinishFetchingResourceCallback 19 | 20 | public init(resource: ResourceService.Resource, service: ResourceService = ResourceService(), didFinishFetchingResourceCallback: DidFinishFetchingResourceCallback) { 21 | self.resource = resource 22 | self.service = service 23 | self.didFinishFetchingResourceCallback = didFinishFetchingResourceCallback 24 | super.init() 25 | } 26 | 27 | override public func execute() { 28 | fetch(resource: resource, usingService: service) 29 | } 30 | 31 | public func didFinishFetchingResource(result result: Result) { 32 | didFinishFetchingResourceCallback(self, result) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/LoadIt/Protocols/ResourceOperationType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceOperation.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 26/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Define a type that is cancellable 12 | public protocol Cancellable: class { 13 | /// Returns whether or not the type has been cancelled 14 | var cancelled: Bool { get } 15 | } 16 | 17 | public protocol Finishable: class { 18 | /** 19 | Method to be called when the type finished all it's work 20 | 21 | - parameter errors: Any error from the work done 22 | */ 23 | func finish(errors: [NSError]) 24 | } 25 | 26 | public protocol ResourceOperationType: Cancellable, Finishable { 27 | 28 | associatedtype Resource: ResourceType 29 | 30 | /** 31 | Fetches a resource using the provided service 32 | 33 | - parameter resource: The resource to fetch 34 | - parameter service: The service to be used for fetching the resource 35 | */ 36 | func fetch(resource resource: Resource, usingService service: Service) 37 | 38 | /** 39 | Called when the operation has finished, called on Main thread 40 | 41 | - parameter result: The result of the operation 42 | */ 43 | func didFinishFetchingResource(result result: Result) 44 | } 45 | 46 | public extension ResourceOperationType { 47 | 48 | public func fetch(resource resource: Resource, usingService service: Service) { 49 | if cancelled { return } 50 | service.fetch(resource: resource) { [weak self] (result) in 51 | NSThread.li_executeOnMain { [weak self] in 52 | guard let strongSelf = self where !strongSelf.cancelled else { return } 53 | strongSelf.finish([]) 54 | strongSelf.didFinishFetchingResource(result: result) 55 | } 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Sources/LoadIt/Protocols/ResourceServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceService.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 02/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol ResourceServiceType { 12 | associatedtype Resource: ResourceType 13 | 14 | /** 15 | Designated initialzer for constructing a ResourceServiceType 16 | */ 17 | init() 18 | 19 | /** 20 | Fetch a resource 21 | 22 | - parameter resource: The resource to fetch 23 | - parameter completion: A completion handler called with a Result type of the fetching computation 24 | */ 25 | func fetch(resource resource: Resource, completion: (Result) -> Void) 26 | } 27 | -------------------------------------------------------------------------------- /Sources/LoadIt/Protocols/Resources Definitions/DiskJSONResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiskJSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol DiskJSONResourceType: JSONResourceType { 12 | var filename: String { get } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/LoadIt/Protocols/Resources Definitions/JSONResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Protocol used to define a resource, contains an associated type that is the domain specific model type 13 | */ 14 | public protocol ResourceType { 15 | associatedtype Model 16 | } 17 | 18 | /** 19 | * Defines a specific ResourceType used for JSON resources 20 | */ 21 | public protocol JSONResourceType: ResourceType { 22 | /** 23 | Parse a Model from a JSON dictionary 24 | 25 | - parameter jsonDictionary: The JSON dictionary used to decode the Model 26 | 27 | - returns: A parsed Model or nil 28 | */ 29 | func modelFrom(jsonDictionary jsonDictionary: [String : AnyObject]) -> Model? 30 | 31 | /** 32 | Parse a Model from a JSON array 33 | 34 | - parameter jsonArray: The JSON array used to decode the Model 35 | 36 | - returns: A parsed Model or nil 37 | */ 38 | func modelFrom(jsonArray jsonArray: [AnyObject]) -> Model? 39 | } 40 | 41 | // MARK: - Parsing defaults 42 | extension JSONResourceType { 43 | /** 44 | Parse this resources Model from a JSON dictionary 45 | 46 | - parameter jsonDictionary: The JSON dictionary to parse 47 | 48 | - returns: An instantiated model if parsing was succesful, otherwise nil 49 | */ 50 | public func modelFrom(jsonDictionary jsonDictionary: [String : AnyObject]) -> Model? { return nil } 51 | 52 | /** 53 | Parse this resources Model from a JSON array 54 | 55 | - parameter jsonArray: The JSON array to parse 56 | 57 | - returns: An instantiated model if parsing was succesful, otherwise nil 58 | */ 59 | public func modelFrom(jsonArray jsonArray: [AnyObject]) -> Model? { return nil } 60 | } 61 | 62 | enum JSONParsingError: ErrorType { 63 | case InvalidJSONData 64 | case CannotParseJSONDictionary 65 | case CannotParseJSONArray 66 | case UnsupportedType 67 | } 68 | 69 | // MARK: - Convenince parsing functions 70 | extension JSONResourceType { 71 | 72 | func resultFrom(data data: NSData) -> Result { 73 | guard let jsonObject = try? NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers) else { 74 | return .Failure(JSONParsingError.InvalidJSONData) 75 | } 76 | 77 | if let jsonDictionary = jsonObject as? [String: AnyObject] { 78 | return resultFrom(jsonDictionary: jsonDictionary) 79 | } 80 | 81 | if let jsonArray = jsonObject as? [AnyObject] { 82 | return resultFrom(jsonArray: jsonArray) 83 | } 84 | 85 | // This is likely an impossible case since `JSONObjectWithData` likely only returns [String: AnyObject] or [AnyObject] but still needed to appease the compiler 86 | return .Failure(JSONParsingError.UnsupportedType) 87 | } 88 | 89 | private func resultFrom(jsonDictionary jsonDictionary: [String: AnyObject]) -> Result { 90 | guard let parsedResults = modelFrom(jsonDictionary: jsonDictionary) else { 91 | return .Failure(JSONParsingError.CannotParseJSONDictionary) 92 | } 93 | return .Success(parsedResults) 94 | } 95 | 96 | private func resultFrom(jsonArray jsonArray: [AnyObject]) -> Result { 97 | guard let parsedResults = modelFrom(jsonArray: jsonArray) else { 98 | return .Failure(JSONParsingError.CannotParseJSONArray) 99 | } 100 | return .Success(parsedResults) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Sources/LoadIt/Protocols/Resources Definitions/NetworkJSONResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkJSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Enum used to represent the HTTP method 13 | */ 14 | public enum HTTPMethod: String { 15 | case GET 16 | case POST 17 | case PATCH 18 | case DELETE 19 | case HEAD 20 | case PUT 21 | } 22 | 23 | /** 24 | * Protocol used to define a resource to be retrieved from the network 25 | */ 26 | public protocol NetworkResourceType { 27 | /// The URL of the resource 28 | var url: NSURL { get } 29 | 30 | /// The HTTP method used to fetch this resource 31 | var HTTPRequestMethod: HTTPMethod { get } 32 | 33 | /// The HTTP header fields used to fetch this resource 34 | var HTTPHeaderFields: [String: String]? { get } 35 | 36 | /// The HTTP body as JSON used to fetch this resource 37 | var JSONBody: AnyObject? { get } 38 | 39 | /// The query items to be added to the url to fetch this resource 40 | var queryItems: [NSURLQueryItem]? { get } 41 | 42 | /** 43 | Convenience function that builds a URLRequest for this resource 44 | 45 | - returns: A URLRequest or nil if the construction of the request failed 46 | */ 47 | func urlRequest() -> NSURLRequest? 48 | } 49 | 50 | // MARK: - NetworkJSONResource defaults 51 | public extension NetworkResourceType { 52 | 53 | public var HTTPRequestMethod: HTTPMethod { return .GET } 54 | public var HTTPHeaderFields: [String: String]? { return [:] } 55 | public var JSONBody: AnyObject? { return nil } 56 | public var queryItems: [NSURLQueryItem]? { return nil } 57 | 58 | public func urlRequest() -> NSURLRequest? { 59 | let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: true) 60 | urlComponents?.queryItems = queryItems 61 | 62 | guard let urlFromComponents = urlComponents?.URL else { return nil } 63 | 64 | let request = NSMutableURLRequest(URL: urlFromComponents) 65 | request.allHTTPHeaderFields = HTTPHeaderFields 66 | request.HTTPMethod = HTTPRequestMethod.rawValue 67 | print(request.allHTTPHeaderFields) 68 | if let body = JSONBody { 69 | request.HTTPBody = try? NSJSONSerialization.dataWithJSONObject(body, options: NSJSONWritingOptions.PrettyPrinted) 70 | } 71 | 72 | return request 73 | } 74 | } 75 | 76 | public protocol NetworkJSONResourceType: NetworkResourceType, JSONResourceType {} 77 | 78 | // MARK: - NetworkJSONResourceType defaults 79 | public extension NetworkJSONResourceType { 80 | var HTTPHeaderFields: [String: String]? { 81 | return ["Content-Type": "application/json"] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/LoadIt/Utilities/NSThread+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSThread+Additions.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 25/06/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSThread { 12 | 13 | static func li_executeOnMain(mainThreadClosure: () -> Void) { 14 | if self.currentThread() == self.mainThread() { 15 | mainThreadClosure() 16 | } else { 17 | 18 | let queue = dispatch_get_main_queue() 19 | dispatch_sync(queue, { 20 | mainThreadClosure() 21 | }) 22 | 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Tests/LoadIt/Helpers/XCTestCase+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+Additions.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | extension XCTestCase { 12 | 13 | /** 14 | Helper for running async unit tests, creates an expectation and waits for that expectation with 15 | a defined timeout. 16 | 17 | - parameter timeout: Time to wait for the expectation to fufill default value of one second 18 | - parameter failureMessage: Failure message to be shown when the test fails 19 | - parameter runTest: A block used to wrap the test to be run asynchronously. The block has an 20 | input paramater of the expectation this function will wait for. Your test 21 | must fufill this expectation or the test will fail. 22 | */ 23 | func performAsyncTest(timeout: NSTimeInterval = 1.0, failureMessage: String? = nil, file: StaticString = #file, lineNumber: UInt = #line, @noescape test runTest: (XCTestExpectation) -> Void) { 24 | let expectation = expectationWithDescription("Async test expectation") 25 | runTest(expectation) 26 | waitForExpectationsWithTimeout(timeout) { error in 27 | if let error = error { 28 | if let failureMessage = failureMessage { 29 | XCTFail(failureMessage, file: file, line: lineNumber) 30 | } else { 31 | XCTFail(error.localizedDescription, file: file, line: lineNumber) 32 | } 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Tests/LoadIt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockDefaultJSONResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockDefaultJSONResourceType.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockDefaultJSONResourceType: JSONResourceType { 13 | typealias Model = MockObject 14 | } 15 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockDefaultNetworkJSONResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockDefaultNetworkJSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockDefaultNetworkJSONResource: NetworkJSONResourceType { 13 | typealias Model = String 14 | let url: NSURL 15 | 16 | init(url: NSURL) { 17 | self.url = url 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockDefaultNetworkResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockDefaultNetworkResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockDefaultNetworkResource: NetworkResourceType { 13 | let url: NSURL 14 | 15 | init(url: NSURL) { 16 | self.url = url 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockJSONArrayResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockJSONArrayResourceType.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockJSONArrayResourceType: JSONResourceType { 13 | typealias Model = [MockObject] 14 | 15 | func modelFrom(jsonArray jsonArray: [AnyObject]) -> [MockObject]? { 16 | let parsedMockObjects: [MockObject] = jsonArray.flatMap { 17 | guard let jsonDictionary = $0 as? [String: AnyObject] else { return nil } 18 | return MockObject(jsonDictionary: jsonDictionary) 19 | } 20 | guard parsedMockObjects.count > 0 else { return nil } 21 | return parsedMockObjects 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockJSONDictionaryResourceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockJSONDictionaryResourceType.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockJSONDictionaryResourceType: JSONResourceType { 13 | typealias Model = MockObject 14 | 15 | func modelFrom(jsonDictionary jsonDictionary: [String : AnyObject]) -> MockObject? { 16 | return MockObject(jsonDictionary: jsonDictionary) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockNetworkJSONResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNetworkJSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockNetworkJSONResource: NetworkJSONResourceType { 13 | typealias Model = String 14 | 15 | let url: NSURL 16 | let HTTPRequestMethod: HTTPMethod 17 | let HTTPHeaderFields: [String: String]? 18 | let JSONBody: AnyObject? 19 | let queryItems: [NSURLQueryItem]? 20 | 21 | init(url: NSURL, HTTPRequestMethod: HTTPMethod = .GET, HTTPHeaderFields: [String : String]? = nil, JSONBody: AnyObject? = nil, queryItems: [NSURLQueryItem]? = nil) { 22 | self.url = url 23 | self.HTTPRequestMethod = HTTPRequestMethod 24 | self.HTTPHeaderFields = HTTPHeaderFields 25 | self.JSONBody = JSONBody 26 | self.queryItems = queryItems 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockNilURLRequestNetworkJSONResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNilURLRequestNetworkJSONResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockNilURLRequestNetworkJSONResource: NetworkJSONResourceType { 13 | typealias Model = String 14 | let url: NSURL = NSURL(string: "www.test.com")! 15 | func urlRequest() -> NSURLRequest? { 16 | return nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockObject.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MockObject { 12 | let name: String 13 | } 14 | 15 | extension MockObject { 16 | init?(jsonDictionary: [String : AnyObject]) { 17 | guard let parsedName = jsonDictionary["name"] as? String else { 18 | return nil 19 | } 20 | name = parsedName 21 | } 22 | } 23 | 24 | extension MockObject: Equatable {} 25 | 26 | func ==(lhs: MockObject, rhs: MockObject) -> Bool { 27 | return lhs.name == rhs.name 28 | } 29 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockResource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockResource.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | struct MockResource: ResourceType { 13 | typealias Model = String 14 | } 15 | -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockResourceService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockResourceService.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | class MockResourceService: ResourceServiceType { 13 | 14 | typealias Resource = MockResource 15 | 16 | var capturedResource: Resource? 17 | var capturedCompletion: ((Result) -> Void)? 18 | 19 | required init() {} 20 | 21 | func fetch(resource resource: Resource, completion: (Result) -> Void) { 22 | capturedResource = resource 23 | capturedCompletion = completion 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /Tests/LoadIt/Mocks/MockURLSession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockURLSession.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import LoadIt 11 | 12 | final class MockURLSession: URLSessionType { 13 | var capturedRequest: NSURLRequest? 14 | var capturedCompletion: ((NSData?, NSURLResponse?, NSError?) -> Void)? 15 | 16 | func perform(request request: NSURLRequest, completion: (NSData?, NSURLResponse?, NSError?) -> Void) { 17 | capturedRequest = request 18 | capturedCompletion = completion 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Tests/LoadIt/Operations/ResourceOperationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceOperationTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | class ResourceOperationTests: XCTestCase { 13 | 14 | var mockResource: MockResource! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | mockResource = MockResource() 19 | } 20 | 21 | func test_didFinishFetchingResource_calledWithCorrectResult() { 22 | let expectation = expectationWithDescription("didFinishFetchingResourceCallback expectation") 23 | let didFinishFetchingResourceCallback: (ResourceOperation, Result) -> Void = { (operation, result) in 24 | XCTAssertEqual(result.successResult(), "success") 25 | expectation.fulfill() 26 | } 27 | let resourceOperation = ResourceOperation(resource: mockResource, didFinishFetchingResourceCallback: didFinishFetchingResourceCallback) 28 | resourceOperation.didFinishFetchingResource(result: .Success("success")) 29 | waitForExpectationsWithTimeout(1, handler: nil) 30 | } 31 | 32 | func test_resourceOperationExecute_callsFetchOnService() { 33 | let mockService = MockResourceService() 34 | let resourceOperation = ResourceOperation(resource: mockResource, service: mockService, didFinishFetchingResourceCallback: { (_) in }) 35 | resourceOperation.execute() 36 | XCTAssertNotNil(mockService.capturedResource) 37 | XCTAssertNotNil(mockService.capturedCompletion) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /Tests/LoadIt/Protocols/JSONResourceTypeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONResourceTypeTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | class JSONResourceTypeTests: XCTestCase { 13 | 14 | func test_InvalidJSONData() { 15 | let mockJSONObjectResourceType = MockJSONDictionaryResourceType() 16 | let result = mockJSONObjectResourceType.resultFrom(data: NSData()) 17 | guard let error = result.error() else { 18 | XCTFail("No error found") 19 | return 20 | } 21 | if case JSONParsingError.InvalidJSONData = error { return } 22 | XCTFail() 23 | } 24 | 25 | func test_validJSONDictionary() { 26 | let mockJSONObjectResourceType = MockJSONDictionaryResourceType() 27 | let jsonDictionary = ["name": "mock"] 28 | let data = try! NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: NSJSONWritingOptions.PrettyPrinted) 29 | let result = mockJSONObjectResourceType.resultFrom(data: data) 30 | guard let calculatedMockObject = result.successResult() else { 31 | XCTFail("No error found") 32 | return 33 | } 34 | let expectedMockObject = MockObject(name: "mock") 35 | XCTAssertEqual(calculatedMockObject, expectedMockObject) 36 | } 37 | 38 | func test_invalidJSONDictionary() { 39 | let mockJSONObjectResourceType = MockJSONDictionaryResourceType() 40 | let jsonDictionary = ["invalid_key": "mock"] 41 | let data = try! NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: NSJSONWritingOptions.PrettyPrinted) 42 | let result = mockJSONObjectResourceType.resultFrom(data: data) 43 | guard let error = result.error() else { 44 | XCTFail("No error found") 45 | return 46 | } 47 | if case JSONParsingError.CannotParseJSONDictionary = error { return } 48 | XCTFail("Did not match correct error: \(error)") 49 | } 50 | 51 | func test_validJSONArray() { 52 | let mockJSONObjectResourceType = MockJSONArrayResourceType() 53 | let jsonArray = [ 54 | ["name": "mock 1"], 55 | ["name": "mock 2"] 56 | ] 57 | let data = try! NSJSONSerialization.dataWithJSONObject(jsonArray, options: NSJSONWritingOptions.PrettyPrinted) 58 | let result = mockJSONObjectResourceType.resultFrom(data: data) 59 | guard let calculatedMockObjectsArray = result.successResult() else { 60 | XCTFail("No error found") 61 | return 62 | } 63 | let expectedMockObjectsArray = [MockObject(name: "mock 1"), MockObject(name: "mock 2")] 64 | XCTAssertEqual(calculatedMockObjectsArray, expectedMockObjectsArray) 65 | } 66 | 67 | func test_invalidJSONArray() { 68 | let mockJSONObjectResourceType = MockJSONArrayResourceType() 69 | let jsonArray = [["invalid-key"]] 70 | let data = try! NSJSONSerialization.dataWithJSONObject(jsonArray, options: NSJSONWritingOptions.PrettyPrinted) 71 | let result = mockJSONObjectResourceType.resultFrom(data: data) 72 | guard let error = result.error() else { 73 | XCTFail("No error found") 74 | return 75 | } 76 | if case JSONParsingError.CannotParseJSONArray = error { return } 77 | XCTFail() 78 | } 79 | 80 | func test_defaultParsing() { 81 | let mockDefaultJSONResourceType = MockDefaultJSONResourceType() 82 | let modelFromJSONDictionary = mockDefaultJSONResourceType.modelFrom(jsonDictionary: ["Key": "value"]) 83 | XCTAssertNil(modelFromJSONDictionary) 84 | let modelFromJSONArray = mockDefaultJSONResourceType.modelFrom(jsonArray: ["value"]) 85 | XCTAssertNil(modelFromJSONArray) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Tests/LoadIt/Protocols/NetworkResourceTypeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkResourceTypeTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | private let url = NSURL(string: "www.test.com")! 13 | 14 | class NetworkResourceTypeTests: XCTestCase { 15 | 16 | func test_NetworkResource_hasCorrectDefaultValues() { 17 | let mockDefaultNetworkJSONResource = MockDefaultNetworkResource(url: url) 18 | XCTAssertEqual(mockDefaultNetworkJSONResource.HTTPRequestMethod, HTTPMethod.GET) 19 | XCTAssertEqual(mockDefaultNetworkJSONResource.HTTPHeaderFields!, [String: String]()) 20 | XCTAssertNil(mockDefaultNetworkJSONResource.JSONBody) 21 | XCTAssertNil(mockDefaultNetworkJSONResource.queryItems) 22 | } 23 | 24 | func test_NetworkJSONResourceType_hasCorrectDefaultValues() { 25 | let mockDefaultNetworkJSONResource = MockDefaultNetworkJSONResource(url: url) 26 | XCTAssertEqual(mockDefaultNetworkJSONResource.HTTPRequestMethod, HTTPMethod.GET) 27 | XCTAssertEqual(mockDefaultNetworkJSONResource.HTTPHeaderFields!, ["Content-Type": "application/json"]) 28 | XCTAssertNil(mockDefaultNetworkJSONResource.JSONBody) 29 | XCTAssertNil(mockDefaultNetworkJSONResource.queryItems) 30 | } 31 | 32 | func test_NetworkJSONResource_urlRequest() { 33 | let expectedHTTPMethod = HTTPMethod.POST 34 | let expectedAllHTTPHeaderFields = ["key": "value"] 35 | let expectedJSONBody = ["jsonKey": "jsonValue"] 36 | let mockedURLQueryItems = [NSURLQueryItem(name: "query-name", value: "query-value")] 37 | let expectedURL = "\(url)?query-name=query-value" 38 | let mockDefaultNetworkJSONResource = MockNetworkJSONResource(url: url, HTTPRequestMethod: expectedHTTPMethod, HTTPHeaderFields: expectedAllHTTPHeaderFields, JSONBody: expectedJSONBody, queryItems: mockedURLQueryItems) 39 | 40 | let urlRequest = mockDefaultNetworkJSONResource.urlRequest() 41 | XCTAssertNotNil(urlRequest) 42 | XCTAssertEqual(urlRequest?.URL?.absoluteString, expectedURL) 43 | XCTAssertEqual(urlRequest?.HTTPMethod, expectedHTTPMethod.rawValue) 44 | XCTAssertEqual(urlRequest!.allHTTPHeaderFields!, expectedAllHTTPHeaderFields) 45 | let expectedJSONData = try! NSJSONSerialization.dataWithJSONObject(expectedJSONBody, options: NSJSONWritingOptions.PrettyPrinted) 46 | XCTAssertEqual(urlRequest!.HTTPBody!, expectedJSONData) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Tests/LoadIt/Protocols/ResourceOperationTypeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceOperationTypeTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | class TestResourceOperation: ResourceOperationType { 13 | 14 | typealias Resource = MockResource 15 | 16 | var cancelled: Bool = false 17 | var capturedFinishedErrors: [NSError]? 18 | var capturedResult: Result? 19 | var captureDidFinishFetchingResourceThread: NSThread? 20 | 21 | func finish(errors: [NSError]) { 22 | capturedFinishedErrors = errors 23 | } 24 | 25 | func didFinishFetchingResource(result result: Result) { 26 | captureDidFinishFetchingResourceThread = NSThread.currentThread() 27 | capturedResult = result 28 | } 29 | 30 | } 31 | 32 | class ResourceOperationTypeTests: XCTestCase { 33 | 34 | var mockService: MockResourceService! 35 | var testResourceOperation: TestResourceOperation! 36 | 37 | override func setUp() { 38 | super.setUp() 39 | mockService = MockResourceService() 40 | testResourceOperation = TestResourceOperation() 41 | } 42 | 43 | func test_fetch_callsFinishAndDidFinish_withCorrectResultOnSuccess() { 44 | testResourceOperation.fetch(resource: MockResource(), usingService: mockService) 45 | let expectedResult = Result.Success("some result") 46 | mockService.capturedCompletion!(expectedResult) 47 | XCTAssertNotNil(testResourceOperation.capturedFinishedErrors) 48 | XCTAssertEqual(testResourceOperation.capturedFinishedErrors!.count, 0) 49 | guard let successResult = testResourceOperation.capturedResult?.successResult() else { 50 | XCTFail("Result was not succesful") 51 | return 52 | } 53 | XCTAssertEqual(successResult, "some result") 54 | XCTAssert(testResourceOperation.captureDidFinishFetchingResourceThread!.isMainThread) 55 | } 56 | 57 | func test_fetch_doesNotCallFetchOnService_whenOperationIsCancelled_beforeServiceFetches() { 58 | testResourceOperation.cancelled = true 59 | testResourceOperation.fetch(resource: MockResource(), usingService: mockService) 60 | XCTAssertNil(mockService.capturedCompletion) 61 | } 62 | 63 | func test_fetch_doesNotCallFinishAndDidFinish_whenOperationIsCancelled_afterServiceFetches() { 64 | testResourceOperation.fetch(resource: MockResource(), usingService: mockService) 65 | testResourceOperation.cancelled = true 66 | let expectedResult = Result.Success("some result") 67 | mockService.capturedCompletion!(expectedResult) 68 | XCTAssertNil(testResourceOperation.capturedFinishedErrors) 69 | XCTAssertNil(testResourceOperation.capturedResult) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Tests/LoadIt/Services/NetworkJSONResourceServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkJSONResourceServiceTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | private let testURL = NSURL(string: "http://test.com")! 13 | 14 | class NetworkJSONResourceServiceTests: XCTestCase { 15 | 16 | var testService: NetworkJSONResourceService! 17 | var mockSession: MockURLSession! 18 | var mockResource: MockDefaultNetworkJSONResource! 19 | 20 | let testRequest = NSURLRequest(URL: testURL) 21 | 22 | override func setUp() { 23 | super.setUp() 24 | mockSession = MockURLSession() 25 | testService = NetworkJSONResourceService(session: mockSession) 26 | mockResource = MockDefaultNetworkJSONResource(url: testURL) 27 | } 28 | 29 | func test_publicInitializerUsesSharedURLSession() { 30 | testService = NetworkJSONResourceService() 31 | XCTAssert(testService.session is NSURLSession) 32 | XCTAssertEqual(testService.session as? NSURLSession, NSURLSession.sharedSession()) 33 | } 34 | 35 | func test_fetch_callsPerformRequestOnSessionWithCorrectURLRequest() { 36 | testService.fetch(resource: mockResource) { _ in } 37 | let capturedRequest = mockSession.capturedRequest 38 | let expectedRequest = mockResource.urlRequest() 39 | XCTAssertEqual(capturedRequest, expectedRequest) 40 | } 41 | 42 | func test_fetch_withInvalidURLRequest_callsFailureWithCorrectError() { 43 | let mockInvalidURLResource = MockNilURLRequestNetworkJSONResource() 44 | let newTestRequestManager = NetworkJSONResourceService(session: mockSession) 45 | XCTAssertNil(mockInvalidURLResource.urlRequest()) 46 | performAsyncTest() { expectation in 47 | newTestRequestManager.fetch(resource: mockInvalidURLResource) { result in 48 | expectation.fulfill() 49 | guard let error = result.error() else { 50 | XCTFail("No error found") 51 | return 52 | } 53 | if case NetworkServiceError.CouldNotCreateURLRequest = error { return } 54 | XCTFail("Unexpected error: \(error)") 55 | } 56 | } 57 | } 58 | 59 | func test_fetch_whenSessionCompletesWithFailureStatusCode_callsFailureWithCorrectError() { 60 | let handledStatusCodes = [400, 499, 500, 599] 61 | handledStatusCodes.forEach { 62 | assert_fetch_whenSessionCompletesWithHandledStatusCode_callsFailureWithCorrectError(expectedStatusCode: $0) 63 | } 64 | } 65 | 66 | private func assert_fetch_whenSessionCompletesWithHandledStatusCode_callsFailureWithCorrectError(expectedStatusCode expectedStatusCode: Int, file: StaticString = #file, lineNumber: UInt = #line) { 67 | let expectedError = NSError(domain: "test", code: 999, userInfo: nil) 68 | let mockHTTPURLResponse = NSHTTPURLResponse(URL: NSURL(string: "www.test.com")!, statusCode: expectedStatusCode, HTTPVersion: nil, headerFields: nil) 69 | performAsyncTest(file: file, lineNumber: lineNumber) { expectation in 70 | testService.fetch(resource: mockResource) { result in 71 | expectation.fulfill() 72 | guard let error = result.error() else { 73 | XCTFail("No error found") 74 | return 75 | } 76 | 77 | guard case NetworkServiceError.StatusCodeError(let statusCode) = error else { 78 | XCTFail() 79 | return 80 | } 81 | XCTAssert(statusCode == expectedStatusCode) 82 | } 83 | mockSession.capturedCompletion!(nil, mockHTTPURLResponse, expectedError) 84 | } 85 | } 86 | 87 | func test_fetch_whenSessionCompletesWithUnhandledStatusCode_callsFailureWithCorrectError() { 88 | let unhandledStatusCodes = [300, 399, 600, 601] 89 | unhandledStatusCodes.forEach { 90 | assert_fetch_whenSessionCompletesWithUnhandledStatusCode_callsFailureWithCorrectError(expectedStatusCode: $0) 91 | } 92 | } 93 | 94 | private func assert_fetch_whenSessionCompletesWithUnhandledStatusCode_callsFailureWithCorrectError(expectedStatusCode expectedStatusCode: Int, file: StaticString = #file, lineNumber: UInt = #line) { 95 | let expectedError = NSError(domain: "test", code: 999, userInfo: nil) 96 | let mockHTTPURLResponse = NSHTTPURLResponse(URL: NSURL(string: "www.test.com")!, statusCode: expectedStatusCode, HTTPVersion: nil, headerFields: nil) 97 | performAsyncTest(file: file, lineNumber: lineNumber) { expectation in 98 | testService.fetch(resource: mockResource) { result in 99 | expectation.fulfill() 100 | guard let error = result.error() else { 101 | XCTFail("No error found") 102 | return 103 | } 104 | 105 | guard case NetworkServiceError.NetworkingError(let testError) = error else { 106 | XCTFail() 107 | return 108 | } 109 | XCTAssert(testError.domain == expectedError.domain) 110 | } 111 | mockSession.capturedCompletion!(nil, mockHTTPURLResponse, expectedError) 112 | } 113 | } 114 | 115 | func test_fetch_whenSessionCompletesWithNetworkingError_callsFailureWithCorrectError() { 116 | let expectedError = NSError(domain: "test", code: 999, userInfo: nil) 117 | 118 | performAsyncTest() { expectation in 119 | testService.fetch(resource: mockResource) { result in 120 | expectation.fulfill() 121 | guard let error = result.error() else { 122 | XCTFail("No error found") 123 | return 124 | } 125 | 126 | guard case NetworkServiceError.NetworkingError(let testError) = error else { 127 | XCTFail() 128 | return 129 | } 130 | XCTAssert(testError.domain == expectedError.domain) 131 | } 132 | mockSession.capturedCompletion!(nil, nil, expectedError) 133 | } 134 | } 135 | 136 | func test_fetch_whenSessionCompletes_WithNoData_callsFailureWithCorrectError() { 137 | performAsyncTest() { expectation in 138 | testService.fetch(resource: mockResource) { result in 139 | expectation.fulfill() 140 | guard let error = result.error() else { 141 | XCTFail("No error found") 142 | return 143 | } 144 | if case NetworkServiceError.NoData = error { return } 145 | XCTFail() 146 | } 147 | mockSession.capturedCompletion!(nil, nil, nil) 148 | } 149 | } 150 | 151 | func test_fetch_WhenSessionCompletes_WithInvalidJSON_callsFailureWithCorrectError() { 152 | performAsyncTest() { expectation in 153 | testService.fetch(resource: mockResource) { result in 154 | expectation.fulfill() 155 | guard let error = result.error() else { 156 | XCTFail("No error found") 157 | return 158 | } 159 | if case JSONParsingError.InvalidJSONData = error { return } 160 | XCTFail() 161 | } 162 | mockSession.capturedCompletion!(NSData(), nil, nil) 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /Tests/LoadIt/Services/SubclassedNetworkJSONResourceServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubclassedNetworkJSONResourceServiceTests.swift 3 | // LoadIt 4 | // 5 | // Created by Luciano Marisi on 20/07/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LoadIt 11 | 12 | private let commonKey = "common" 13 | 14 | final class SubclassedNetworkJSONResourceService: NetworkJSONResourceService { 15 | 16 | // Needed because of https://bugs.swift.org/browse/SR-416 17 | required init() { 18 | super.init() 19 | } 20 | 21 | override init(session: URLSessionType) { 22 | super.init(session: session) 23 | } 24 | 25 | override func additionalHeaderFields() -> [String: String] { 26 | return [commonKey: "subclass"] 27 | } 28 | } 29 | 30 | private let testURL = NSURL(string: "http://test.com")! 31 | private let url = NSURL(string: "www.test.com")! 32 | 33 | class SubclassedNetworkJSONResourceServiceTests: XCTestCase { 34 | 35 | var testService: SubclassedNetworkJSONResourceService! 36 | var mockSession: MockURLSession! 37 | var mockResource: MockNetworkJSONResource! 38 | 39 | let testRequest = NSURLRequest(URL: testURL) 40 | 41 | override func setUp() { 42 | super.setUp() 43 | mockSession = MockURLSession() 44 | testService = SubclassedNetworkJSONResourceService(session: mockSession) 45 | } 46 | 47 | func test_subclassHTTPHeaderFields_areOverridenByResourceHTTPHeaderFields() { 48 | let resourceHTTPHeaderFields = [commonKey: "resource"] 49 | mockResource = MockNetworkJSONResource(url: testURL, HTTPHeaderFields: resourceHTTPHeaderFields) 50 | testService.fetch(resource: mockResource) { _ in } 51 | XCTAssertEqual(mockSession.capturedRequest!.allHTTPHeaderFields!, resourceHTTPHeaderFields) 52 | } 53 | 54 | func test_finalRequestInclude_subclassHTTPHeaderFields_and_resourceHTTPHeaderFields() { 55 | let resourceHTTPHeaderFields = ["resource_key" : "resource"] 56 | mockResource = MockNetworkJSONResource(url: testURL, HTTPHeaderFields: resourceHTTPHeaderFields) 57 | testService.fetch(resource: mockResource) { _ in } 58 | let expectedHeaderFields = [commonKey: "subclass", "resource_key" : "resource"] 59 | XCTAssertEqual(mockSession.capturedRequest!.allHTTPHeaderFields!, expectedHeaderFields) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: header, changes, diff 3 | coverage: 4 | ignore: 5 | - Tests/.* 6 | status: 7 | patch: false 8 | --------------------------------------------------------------------------------