├── .codebeatignore ├── .travis.yml ├── DADependencyInjection.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── DADependencyInjection.xcscheme └── xcuserdata │ └── dejan.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── SiriTest.xcscheme │ └── xcschememanagement.plist ├── DADependencyInjection ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Core │ ├── Data │ │ ├── DataProvider │ │ │ ├── MoviesDataProvider.swift │ │ │ ├── MoviesDataSource.swift │ │ │ └── MoviesDataSource_Operations.swift │ │ ├── Factory │ │ │ ├── JSONMoviesFactory.swift │ │ │ └── MoviesFactoryProvider.swift │ │ ├── ListDisplayable.swift │ │ ├── Manager │ │ │ ├── ListDisplayableDataProvider.swift │ │ │ └── MoviesManager.swift │ │ └── MovieItem.swift │ ├── Networking │ │ ├── AFNetworkConnector.swift │ │ ├── NSURLNetworkConnector.swift │ │ └── NetworkingProvider.swift │ └── Operations │ │ ├── DAOperation.swift │ │ ├── GetDataOperation.swift │ │ └── ParseDataOperation.swift ├── DADependencyInjection.entitlements ├── DetailsViewController.swift ├── Info.plist ├── Libraries │ └── Alamofire │ │ ├── AFError.swift │ │ ├── Alamofire.h │ │ ├── Alamofire.swift │ │ ├── DispatchQueue+Alamofire.swift │ │ ├── MultipartFormData.swift │ │ ├── NetworkReachabilityManager.swift │ │ ├── Notifications.swift │ │ ├── ParameterEncoding.swift │ │ ├── Request.swift │ │ ├── Response.swift │ │ ├── ResponseSerialization.swift │ │ ├── Result.swift │ │ ├── ServerTrustPolicy.swift │ │ ├── SessionDelegate.swift │ │ ├── SessionManager.swift │ │ ├── TaskDelegate.swift │ │ ├── Timeline.swift │ │ └── Validation.swift ├── SiriConstants.swift └── ViewController.swift ├── DADependencyInjectionTests ├── Data │ ├── TestData.swift │ ├── TestFileNames.swift │ └── popularMovies.json ├── Info.plist ├── MockObjects │ └── MockNetworkProvider.swift ├── MoviesDataSourceTests.swift ├── MoviesFactoryTests.swift └── MoviesManagerTests.swift ├── README.md ├── SiriTest ├── Info.plist └── IntentHandler.swift └── codecov.yml /.codebeatignore: -------------------------------------------------------------------------------- 1 | DADependencyInjection/Libraries/** 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode8.3 3 | script: 4 | - xcodebuild test -project DADependencyInjection.xcodeproj -scheme DADependencyInjection -destination 'platform=iOS Simulator,name=iPhone 7,OS=10.3.1' 5 | after_success: 6 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/xcshareddata/xcschemes/DADependencyInjection.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 | -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/xcuserdata/dejan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/xcuserdata/dejan.xcuserdatad/xcschemes/SiriTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 70 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /DADependencyInjection.xcodeproj/xcuserdata/dejan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DADependencyInjection.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | SiriTest.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 7757D2341E5A627400C23217 21 | 22 | primary 23 | 24 | 25 | 7757D2481E5A627400C23217 26 | 27 | primary 28 | 29 | 30 | 77A3A0771ECD1299009363DC 31 | 32 | primary 33 | 34 | 35 | 77A3A0801ECD129A009363DC 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DADependencyInjection/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 21 | 22 | if let title = userActivity.userInfo?[SiriConstants.ItemTitle.rawValue] as? String, 23 | let desc = userActivity.userInfo?[SiriConstants.ItemDescription.rawValue] as? String { 24 | 25 | if let detailsVC = self.window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "DetailsVC") as? DetailsViewController { 26 | detailsVC.itemTitle = title 27 | detailsVC.itemDescription = desc 28 | 29 | self.window?.rootViewController?.present(detailsVC, animated: true, completion: nil) 30 | } 31 | } 32 | 33 | return true 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /DADependencyInjection/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 | } -------------------------------------------------------------------------------- /DADependencyInjection/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 | -------------------------------------------------------------------------------- /DADependencyInjection/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 117 | 126 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/DataProvider/MoviesDataProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesDataProvider.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MoviesDataProvider { 12 | var networkingProvider: NetworkingProvider { get set } 13 | var moviesFactory: MoviesFactoryProvider { get set } 14 | func getMovies(onCompleted: (([MovieItem]) -> ())?) 15 | } 16 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/DataProvider/MoviesDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesDataSource.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct DataSourceConstants { 12 | static let TMDBBaseURL = "https://api.themoviedb.org" 13 | static let MoviesURL = "/3/movie/popular" 14 | static let APIParameterKey = "api_key" 15 | static let APIParameterValue = "_YOUR_API_KEY_HERE_" 16 | static let PageParameterKey = "page" 17 | 18 | static func URLString(forPage page: String = "1") -> String? { 19 | 20 | let urlComponents = NSURLComponents(string: DataSourceConstants.TMDBBaseURL) 21 | urlComponents?.path = DataSourceConstants.MoviesURL 22 | 23 | let apiKey = URLQueryItem(name: DataSourceConstants.APIParameterKey, value: DataSourceConstants.APIParameterValue) 24 | let page = URLQueryItem(name: DataSourceConstants.PageParameterKey, value: page) 25 | 26 | urlComponents?.queryItems = [apiKey, page] 27 | 28 | return urlComponents?.string 29 | } 30 | } 31 | 32 | class MoviesDataSource: MoviesDataProvider { 33 | 34 | public var networkingProvider: NetworkingProvider 35 | public var moviesFactory: MoviesFactoryProvider 36 | 37 | public init(withNetworkingProvider networking: NetworkingProvider = AFNetworkConnector(), andFactory factory: MoviesFactoryProvider = JSONMoviesFactory()) { 38 | self.networkingProvider = networking 39 | self.moviesFactory = factory 40 | } 41 | 42 | 43 | func getMovies(onCompleted: (([MovieItem]) -> ())?) { 44 | 45 | var result: [MovieItem] = [] 46 | 47 | let dispatchGroup = DispatchGroup() 48 | 49 | for i in 1...5 { 50 | guard 51 | let urlString = DataSourceConstants.URLString(forPage: "\(i)") 52 | else { 53 | continue 54 | } 55 | 56 | dispatchGroup.enter() 57 | self.networkingProvider.restCall(urlString: urlString) { 58 | 59 | (responseObject) in 60 | 61 | guard 62 | let responseData = responseObject, 63 | let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments]) 64 | else { 65 | dispatchGroup.leave() 66 | return 67 | } 68 | 69 | let movies = self.moviesFactory.movieItems(withJSON: jsonObject) 70 | result = result + movies 71 | 72 | dispatchGroup.leave() 73 | } 74 | } 75 | 76 | dispatchGroup.notify(queue: DispatchQueue.global()) { 77 | onCompleted?(result) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/DataProvider/MoviesDataSource_Operations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesDataSource_Operations.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 28/07/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MoviesDataSource_Operations: MoviesDataProvider { 12 | 13 | public var networkingProvider: NetworkingProvider 14 | public var moviesFactory: MoviesFactoryProvider 15 | 16 | private let operationQueue: OperationQueue = OperationQueue() 17 | 18 | public init(withNetworkingProvider networking: NetworkingProvider = AFNetworkConnector(), andFactory factory: MoviesFactoryProvider = JSONMoviesFactory()) { 19 | self.networkingProvider = networking 20 | self.moviesFactory = factory 21 | self.operationQueue.maxConcurrentOperationCount = 5 22 | } 23 | 24 | func getMovies(onCompleted: (([MovieItem]) -> ())?) { 25 | 26 | operationQueue.cancelAllOperations() 27 | 28 | var result: [MovieItem] = [] 29 | 30 | let queueCompletionOperation = BlockOperation { 31 | onCompleted?(result) 32 | } 33 | 34 | var operations: [Operation] = [] 35 | 36 | operationQueue.isSuspended = true 37 | 38 | for i in 1...5 { 39 | guard 40 | let urlString = DataSourceConstants.URLString(forPage: "\(i)") 41 | else { 42 | continue 43 | } 44 | 45 | let networkingOperation = GetDataOperation(withURLString: urlString, andNetworkingProvider: networkingProvider) 46 | let parsingOperation = ParseDataOperation(withFactory: moviesFactory) 47 | 48 | networkingOperation.completionBlock = { 49 | parsingOperation.moviesData = networkingOperation.responseData 50 | } 51 | 52 | parsingOperation.completionBlock = { 53 | if let moviesArray = parsingOperation.movies { 54 | DispatchQueue.global().sync(flags: .barrier) { 55 | result = result + moviesArray 56 | } 57 | } 58 | } 59 | 60 | parsingOperation.addDependency(networkingOperation) 61 | 62 | queueCompletionOperation.addDependency(parsingOperation) 63 | 64 | operations.append(contentsOf: [parsingOperation, networkingOperation]) 65 | } 66 | 67 | operations.append(queueCompletionOperation) 68 | 69 | operationQueue.addOperations(operations, waitUntilFinished: false) 70 | operationQueue.isSuspended = false 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/Factory/JSONMoviesFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesFactory.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 22/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private struct MovieItemKey { 12 | static let Results = "results" 13 | static let ID = "id" 14 | static let Title = "title" 15 | static let Description = "overview" 16 | static let AverageVote = "vote_average" 17 | } 18 | 19 | class JSONMoviesFactory: MoviesFactoryProvider { 20 | 21 | private func movieItem(withJSON json: Any) -> MovieItem? { 22 | 23 | guard 24 | let jsonDict = json as? [String: Any?], 25 | let id = jsonDict[MovieItemKey.ID] as? Int, 26 | let title = jsonDict[MovieItemKey.Title] as? String, 27 | let description = jsonDict[MovieItemKey.Description] as? String, 28 | let vote = jsonDict[MovieItemKey.AverageVote] as? Double 29 | else { 30 | return nil 31 | } 32 | 33 | return MovieFactoryItem(id: id, title: title, movieDescription: description, averageVote: vote) 34 | } 35 | 36 | func movieItems(withJSON json: Any) -> [MovieItem] { 37 | 38 | guard 39 | let jsonDict = json as? [String: Any?], 40 | let arrayDict = jsonDict[MovieItemKey.Results] as? [[String: Any?]] 41 | else { 42 | return [] 43 | } 44 | 45 | return arrayDict.flatMap { movieItem(withJSON: $0) } 46 | } 47 | } 48 | 49 | private struct MovieFactoryItem: MovieItem { 50 | let id: Int 51 | let title: String 52 | let movieDescription: String 53 | let averageVote: Double 54 | } 55 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/Factory/MoviesFactoryProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesFactoryProtocol.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 22/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MoviesFactoryProvider { 12 | func movieItems(withJSON json: Any) -> [MovieItem] 13 | } 14 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/ListDisplayable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListDisplayable.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 22/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ListDisplayable { 12 | var listItemTitle: String { get } 13 | var listItemSubtitle: String? { get } 14 | } 15 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/Manager/ListDisplayableDataProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListDisplayableDataProvider.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 23/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ListDisplayableDataProvider { 12 | func getListItems(onCompleted: (([ListDisplayable]) -> ())?) 13 | func searchListItems(searchTerm: String, onCompleted: (([ListDisplayable]) -> ())?) 14 | } 15 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/Manager/MoviesManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesManager.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 22/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MoviesManager: ListDisplayableDataProvider { 12 | 13 | private var cachedItems: [ListDisplayable]? 14 | 15 | var moviesDataProvider: MoviesDataProvider 16 | 17 | init(withDataProvider dataProvider: MoviesDataProvider = MoviesDataSource()) { 18 | self.moviesDataProvider = dataProvider 19 | } 20 | 21 | func getListItems(onCompleted: (([ListDisplayable]) -> ())?) { 22 | self.moviesDataProvider.getMovies { 23 | 24 | (movies) in 25 | 26 | let listItems = movies.map({ ListDisplayableItem(withMovieItem: $0) }) 27 | 28 | DispatchQueue.main.async(execute: { 29 | onCompleted?(listItems) 30 | }) 31 | 32 | self.cachedItems = listItems 33 | } 34 | } 35 | 36 | // TODO: Save first movie 37 | 38 | func searchListItems(searchTerm: String, onCompleted: (([ListDisplayable]) -> ())?) { 39 | 40 | if let cached = cachedItems { 41 | onCompleted?(self.filter(cached, term: searchTerm)) 42 | } else { 43 | getListItems(onCompleted: { (items) in 44 | onCompleted?(self.filter(items, term: searchTerm)) 45 | }) 46 | } 47 | } 48 | 49 | private func filter(_ list: [ListDisplayable], term: String) -> [ListDisplayable] { 50 | return list.filter(){ $0.listItemTitle.lowercased().contains(term.lowercased()) } 51 | } 52 | } 53 | 54 | // MARK: ListDisplayable 55 | private struct ListDisplayableItem: ListDisplayable { 56 | var listItemTitle: String 57 | var listItemSubtitle: String? 58 | 59 | init(withMovieItem item: MovieItem) { 60 | self.listItemTitle = item.title 61 | self.listItemSubtitle = item.movieDescription 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Data/MovieItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieItem.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MovieItem { 12 | var id: Int { get } 13 | var title: String { get } 14 | var movieDescription: String { get } 15 | var averageVote: Double { get } 16 | } 17 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Networking/AFNetworkConnector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AFNetworkConnector.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 20/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AFNetworkConnector: NetworkingProvider { 12 | 13 | func restCall(urlString: String, onCompleted: ((Data?) -> ())?) { 14 | 15 | guard 16 | let url = try? urlString.asURL() 17 | else { 18 | onCompleted?(nil) 19 | return 20 | } 21 | 22 | SessionManager.default.request(url).responseData { 23 | response in 24 | onCompleted?(response.result.value) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Networking/NSURLNetworkConnector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLNetworkConnector.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 23/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class NSURLNetworkConnector: NetworkingProvider { 12 | 13 | func restCall(urlString: String, onCompleted: ((Data?) -> ())?) { 14 | 15 | guard 16 | let url = try? urlString.asURL() 17 | else { 18 | onCompleted?(nil) 19 | return 20 | } 21 | 22 | let urlTask = URLSession.shared.dataTask(with: url) { 23 | (data, response, error) in 24 | onCompleted?(data) 25 | } 26 | 27 | urlTask.resume() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Networking/NetworkingProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkConnectable.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol NetworkingProvider { 12 | func restCall(urlString: String, onCompleted: ((Data?) -> ())?) 13 | } 14 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Operations/DAOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAOperation.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 28/07/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DAOperation: Operation { 12 | 13 | private var _executing = false { 14 | willSet { 15 | willChangeValue(forKey: "isExecuting") 16 | } 17 | didSet { 18 | didChangeValue(forKey: "isExecuting") 19 | } 20 | } 21 | 22 | override var isExecuting: Bool { 23 | return _executing 24 | } 25 | 26 | private var _finished = false { 27 | willSet { 28 | willChangeValue(forKey: "isFinished") 29 | } 30 | 31 | didSet { 32 | didChangeValue(forKey: "isFinished") 33 | } 34 | } 35 | 36 | override var isFinished: Bool { 37 | return _finished 38 | } 39 | 40 | func executing(_ executing: Bool) { 41 | _executing = executing 42 | } 43 | 44 | func finish(_ finished: Bool) { 45 | _finished = finished 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Operations/GetDataOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetDataOperation.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 28/07/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GetDataOperation: DAOperation { 12 | 13 | private let urlString: String 14 | private let provider: NetworkingProvider 15 | 16 | var responseData: Data? 17 | 18 | init(withURLString urlString: String, andNetworkingProvider provider: NetworkingProvider = AFNetworkConnector()) { 19 | self.urlString = urlString 20 | self.provider = provider 21 | } 22 | 23 | override func main() { 24 | guard isCancelled == false else { 25 | finish(true) 26 | return 27 | } 28 | 29 | executing(true) 30 | provider.restCall(urlString: urlString) { (data) in 31 | self.responseData = data 32 | self.executing(false) 33 | self.finish(true) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DADependencyInjection/Core/Operations/ParseDataOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseDataOperation.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 28/07/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ParseDataOperation: DAOperation { 12 | 13 | private let factory: MoviesFactoryProvider 14 | 15 | var moviesData: Data? 16 | var movies: [MovieItem]? 17 | 18 | init(withFactory factory: MoviesFactoryProvider = JSONMoviesFactory()) { 19 | self.factory = factory 20 | } 21 | 22 | override func main() { 23 | guard isCancelled == false else { 24 | finish(true) 25 | return 26 | } 27 | 28 | executing(true) 29 | guard 30 | let data = moviesData, 31 | let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.allowFragments]) 32 | else { 33 | executing(false) 34 | finish(true) 35 | return 36 | } 37 | 38 | movies = factory.movieItems(withJSON: jsonObject) 39 | 40 | executing(false) 41 | finish(true) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DADependencyInjection/DADependencyInjection.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.siri 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DADependencyInjection/DetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsViewController.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/05/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailsViewController: UIViewController { 12 | 13 | public var itemTitle: String? 14 | public var itemDescription: String? 15 | 16 | @IBOutlet weak var titleLabel: UILabel! 17 | @IBOutlet weak var detailsLabel: UILabel! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.titleLabel.text = itemTitle 23 | self.detailsLabel.text = itemDescription 24 | } 25 | 26 | @IBAction func dismiss() { 27 | self.dismiss(animated: true, completion: nil) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DADependencyInjection/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Movies Browser 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSSiriUsageDescription 26 | Search for movies using Siri. 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Alamofire.h: -------------------------------------------------------------------------------- 1 | // 2 | // Alamofire.h 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | @import Foundation; 26 | 27 | FOUNDATION_EXPORT double AlamofireVersionNumber; 28 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 29 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Types adopting the `URLConvertible` protocol can be used to construct URLs, which are then used to construct 28 | /// URL requests. 29 | public protocol URLConvertible { 30 | /// Returns a URL that conforms to RFC 2396 or throws an `Error`. 31 | /// 32 | /// - throws: An `Error` if the type cannot be converted to a `URL`. 33 | /// 34 | /// - returns: A URL or throws an `Error`. 35 | func asURL() throws -> URL 36 | } 37 | 38 | extension String: URLConvertible { 39 | /// Returns a URL if `self` represents a valid URL string that conforms to RFC 2396 or throws an `AFError`. 40 | /// 41 | /// - throws: An `AFError.invalidURL` if `self` is not a valid URL string. 42 | /// 43 | /// - returns: A URL or throws an `AFError`. 44 | public func asURL() throws -> URL { 45 | guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) } 46 | return url 47 | } 48 | } 49 | 50 | extension URL: URLConvertible { 51 | /// Returns self. 52 | public func asURL() throws -> URL { return self } 53 | } 54 | 55 | extension URLComponents: URLConvertible { 56 | /// Returns a URL if `url` is not nil, otherise throws an `Error`. 57 | /// 58 | /// - throws: An `AFError.invalidURL` if `url` is `nil`. 59 | /// 60 | /// - returns: A URL or throws an `AFError`. 61 | public func asURL() throws -> URL { 62 | guard let url = url else { throw AFError.invalidURL(url: self) } 63 | return url 64 | } 65 | } 66 | 67 | // MARK: - 68 | 69 | /// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests. 70 | public protocol URLRequestConvertible { 71 | /// Returns a URL request or throws if an `Error` was encountered. 72 | /// 73 | /// - throws: An `Error` if the underlying `URLRequest` is `nil`. 74 | /// 75 | /// - returns: A URL request. 76 | func asURLRequest() throws -> URLRequest 77 | } 78 | 79 | extension URLRequestConvertible { 80 | /// The URL request. 81 | public var urlRequest: URLRequest? { return try? asURLRequest() } 82 | } 83 | 84 | extension URLRequest: URLRequestConvertible { 85 | /// Returns a URL request or throws if an `Error` was encountered. 86 | public func asURLRequest() throws -> URLRequest { return self } 87 | } 88 | 89 | // MARK: - 90 | 91 | extension URLRequest { 92 | /// Creates an instance with the specified `method`, `urlString` and `headers`. 93 | /// 94 | /// - parameter url: The URL. 95 | /// - parameter method: The HTTP method. 96 | /// - parameter headers: The HTTP headers. `nil` by default. 97 | /// 98 | /// - returns: The new `URLRequest` instance. 99 | public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws { 100 | let url = try url.asURL() 101 | 102 | self.init(url: url) 103 | 104 | httpMethod = method.rawValue 105 | 106 | if let headers = headers { 107 | for (headerField, headerValue) in headers { 108 | setValue(headerValue, forHTTPHeaderField: headerField) 109 | } 110 | } 111 | } 112 | 113 | func adapt(using adapter: RequestAdapter?) throws -> URLRequest { 114 | guard let adapter = adapter else { return self } 115 | return try adapter.adapt(self) 116 | } 117 | } 118 | 119 | // MARK: - Data Request 120 | 121 | /// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`, 122 | /// `method`, `parameters`, `encoding` and `headers`. 123 | /// 124 | /// - parameter url: The URL. 125 | /// - parameter method: The HTTP method. `.get` by default. 126 | /// - parameter parameters: The parameters. `nil` by default. 127 | /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. 128 | /// - parameter headers: The HTTP headers. `nil` by default. 129 | /// 130 | /// - returns: The created `DataRequest`. 131 | @discardableResult 132 | public func request( 133 | _ url: URLConvertible, 134 | method: HTTPMethod = .get, 135 | parameters: Parameters? = nil, 136 | encoding: ParameterEncoding = URLEncoding.default, 137 | headers: HTTPHeaders? = nil) 138 | -> DataRequest 139 | { 140 | return SessionManager.default.request( 141 | url, 142 | method: method, 143 | parameters: parameters, 144 | encoding: encoding, 145 | headers: headers 146 | ) 147 | } 148 | 149 | /// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the 150 | /// specified `urlRequest`. 151 | /// 152 | /// - parameter urlRequest: The URL request 153 | /// 154 | /// - returns: The created `DataRequest`. 155 | @discardableResult 156 | public func request(_ urlRequest: URLRequestConvertible) -> DataRequest { 157 | return SessionManager.default.request(urlRequest) 158 | } 159 | 160 | // MARK: - Download Request 161 | 162 | // MARK: URL Request 163 | 164 | /// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of the specified `url`, 165 | /// `method`, `parameters`, `encoding`, `headers` and save them to the `destination`. 166 | /// 167 | /// If `destination` is not specified, the contents will remain in the temporary location determined by the 168 | /// underlying URL session. 169 | /// 170 | /// - parameter url: The URL. 171 | /// - parameter method: The HTTP method. `.get` by default. 172 | /// - parameter parameters: The parameters. `nil` by default. 173 | /// - parameter encoding: The parameter encoding. `URLEncoding.default` by default. 174 | /// - parameter headers: The HTTP headers. `nil` by default. 175 | /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. 176 | /// 177 | /// - returns: The created `DownloadRequest`. 178 | @discardableResult 179 | public func download( 180 | _ url: URLConvertible, 181 | method: HTTPMethod = .get, 182 | parameters: Parameters? = nil, 183 | encoding: ParameterEncoding = URLEncoding.default, 184 | headers: HTTPHeaders? = nil, 185 | to destination: DownloadRequest.DownloadFileDestination? = nil) 186 | -> DownloadRequest 187 | { 188 | return SessionManager.default.download( 189 | url, 190 | method: method, 191 | parameters: parameters, 192 | encoding: encoding, 193 | headers: headers, 194 | to: destination 195 | ) 196 | } 197 | 198 | /// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of a URL based on the 199 | /// specified `urlRequest` and save them to the `destination`. 200 | /// 201 | /// If `destination` is not specified, the contents will remain in the temporary location determined by the 202 | /// underlying URL session. 203 | /// 204 | /// - parameter urlRequest: The URL request. 205 | /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. 206 | /// 207 | /// - returns: The created `DownloadRequest`. 208 | @discardableResult 209 | public func download( 210 | _ urlRequest: URLRequestConvertible, 211 | to destination: DownloadRequest.DownloadFileDestination? = nil) 212 | -> DownloadRequest 213 | { 214 | return SessionManager.default.download(urlRequest, to: destination) 215 | } 216 | 217 | // MARK: Resume Data 218 | 219 | /// Creates a `DownloadRequest` using the default `SessionManager` from the `resumeData` produced from a 220 | /// previous request cancellation to retrieve the contents of the original request and save them to the `destination`. 221 | /// 222 | /// If `destination` is not specified, the contents will remain in the temporary location determined by the 223 | /// underlying URL session. 224 | /// 225 | /// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken 226 | /// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the 227 | /// data is written incorrectly and will always fail to resume the download. For more information about the bug and 228 | /// possible workarounds, please refer to the following Stack Overflow post: 229 | /// 230 | /// - http://stackoverflow.com/a/39347461/1342462 231 | /// 232 | /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask` 233 | /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional 234 | /// information. 235 | /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default. 236 | /// 237 | /// - returns: The created `DownloadRequest`. 238 | @discardableResult 239 | public func download( 240 | resumingWith resumeData: Data, 241 | to destination: DownloadRequest.DownloadFileDestination? = nil) 242 | -> DownloadRequest 243 | { 244 | return SessionManager.default.download(resumingWith: resumeData, to: destination) 245 | } 246 | 247 | // MARK: - Upload Request 248 | 249 | // MARK: File 250 | 251 | /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` 252 | /// for uploading the `file`. 253 | /// 254 | /// - parameter file: The file to upload. 255 | /// - parameter url: The URL. 256 | /// - parameter method: The HTTP method. `.post` by default. 257 | /// - parameter headers: The HTTP headers. `nil` by default. 258 | /// 259 | /// - returns: The created `UploadRequest`. 260 | @discardableResult 261 | public func upload( 262 | _ fileURL: URL, 263 | to url: URLConvertible, 264 | method: HTTPMethod = .post, 265 | headers: HTTPHeaders? = nil) 266 | -> UploadRequest 267 | { 268 | return SessionManager.default.upload(fileURL, to: url, method: method, headers: headers) 269 | } 270 | 271 | /// Creates a `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for 272 | /// uploading the `file`. 273 | /// 274 | /// - parameter file: The file to upload. 275 | /// - parameter urlRequest: The URL request. 276 | /// 277 | /// - returns: The created `UploadRequest`. 278 | @discardableResult 279 | public func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest { 280 | return SessionManager.default.upload(fileURL, with: urlRequest) 281 | } 282 | 283 | // MARK: Data 284 | 285 | /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` 286 | /// for uploading the `data`. 287 | /// 288 | /// - parameter data: The data to upload. 289 | /// - parameter url: The URL. 290 | /// - parameter method: The HTTP method. `.post` by default. 291 | /// - parameter headers: The HTTP headers. `nil` by default. 292 | /// 293 | /// - returns: The created `UploadRequest`. 294 | @discardableResult 295 | public func upload( 296 | _ data: Data, 297 | to url: URLConvertible, 298 | method: HTTPMethod = .post, 299 | headers: HTTPHeaders? = nil) 300 | -> UploadRequest 301 | { 302 | return SessionManager.default.upload(data, to: url, method: method, headers: headers) 303 | } 304 | 305 | /// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for 306 | /// uploading the `data`. 307 | /// 308 | /// - parameter data: The data to upload. 309 | /// - parameter urlRequest: The URL request. 310 | /// 311 | /// - returns: The created `UploadRequest`. 312 | @discardableResult 313 | public func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest { 314 | return SessionManager.default.upload(data, with: urlRequest) 315 | } 316 | 317 | // MARK: InputStream 318 | 319 | /// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers` 320 | /// for uploading the `stream`. 321 | /// 322 | /// - parameter stream: The stream to upload. 323 | /// - parameter url: The URL. 324 | /// - parameter method: The HTTP method. `.post` by default. 325 | /// - parameter headers: The HTTP headers. `nil` by default. 326 | /// 327 | /// - returns: The created `UploadRequest`. 328 | @discardableResult 329 | public func upload( 330 | _ stream: InputStream, 331 | to url: URLConvertible, 332 | method: HTTPMethod = .post, 333 | headers: HTTPHeaders? = nil) 334 | -> UploadRequest 335 | { 336 | return SessionManager.default.upload(stream, to: url, method: method, headers: headers) 337 | } 338 | 339 | /// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for 340 | /// uploading the `stream`. 341 | /// 342 | /// - parameter urlRequest: The URL request. 343 | /// - parameter stream: The stream to upload. 344 | /// 345 | /// - returns: The created `UploadRequest`. 346 | @discardableResult 347 | public func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest { 348 | return SessionManager.default.upload(stream, with: urlRequest) 349 | } 350 | 351 | // MARK: MultipartFormData 352 | 353 | /// Encodes `multipartFormData` using `encodingMemoryThreshold` with the default `SessionManager` and calls 354 | /// `encodingCompletion` with new `UploadRequest` using the `url`, `method` and `headers`. 355 | /// 356 | /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative 357 | /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most 358 | /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to 359 | /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory 360 | /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be 361 | /// used for larger payloads such as video content. 362 | /// 363 | /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory 364 | /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, 365 | /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk 366 | /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding 367 | /// technique was used. 368 | /// 369 | /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. 370 | /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. 371 | /// `multipartFormDataEncodingMemoryThreshold` by default. 372 | /// - parameter url: The URL. 373 | /// - parameter method: The HTTP method. `.post` by default. 374 | /// - parameter headers: The HTTP headers. `nil` by default. 375 | /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. 376 | public func upload( 377 | multipartFormData: @escaping (MultipartFormData) -> Void, 378 | usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, 379 | to url: URLConvertible, 380 | method: HTTPMethod = .post, 381 | headers: HTTPHeaders? = nil, 382 | encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?) 383 | { 384 | return SessionManager.default.upload( 385 | multipartFormData: multipartFormData, 386 | usingThreshold: encodingMemoryThreshold, 387 | to: url, 388 | method: method, 389 | headers: headers, 390 | encodingCompletion: encodingCompletion 391 | ) 392 | } 393 | 394 | /// Encodes `multipartFormData` using `encodingMemoryThreshold` and the default `SessionManager` and 395 | /// calls `encodingCompletion` with new `UploadRequest` using the `urlRequest`. 396 | /// 397 | /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative 398 | /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most 399 | /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to 400 | /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory 401 | /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be 402 | /// used for larger payloads such as video content. 403 | /// 404 | /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory 405 | /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, 406 | /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk 407 | /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding 408 | /// technique was used. 409 | /// 410 | /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`. 411 | /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes. 412 | /// `multipartFormDataEncodingMemoryThreshold` by default. 413 | /// - parameter urlRequest: The URL request. 414 | /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete. 415 | public func upload( 416 | multipartFormData: @escaping (MultipartFormData) -> Void, 417 | usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, 418 | with urlRequest: URLRequestConvertible, 419 | encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?) 420 | { 421 | return SessionManager.default.upload( 422 | multipartFormData: multipartFormData, 423 | usingThreshold: encodingMemoryThreshold, 424 | with: urlRequest, 425 | encodingCompletion: encodingCompletion 426 | ) 427 | } 428 | 429 | #if !os(watchOS) 430 | 431 | // MARK: - Stream Request 432 | 433 | // MARK: Hostname and Port 434 | 435 | /// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `hostname` 436 | /// and `port`. 437 | /// 438 | /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. 439 | /// 440 | /// - parameter hostName: The hostname of the server to connect to. 441 | /// - parameter port: The port of the server to connect to. 442 | /// 443 | /// - returns: The created `StreamRequest`. 444 | @discardableResult 445 | @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) 446 | public func stream(withHostName hostName: String, port: Int) -> StreamRequest { 447 | return SessionManager.default.stream(withHostName: hostName, port: port) 448 | } 449 | 450 | // MARK: NetService 451 | 452 | /// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `netService`. 453 | /// 454 | /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. 455 | /// 456 | /// - parameter netService: The net service used to identify the endpoint. 457 | /// 458 | /// - returns: The created `StreamRequest`. 459 | @discardableResult 460 | @available(iOS 9.0, macOS 10.11, tvOS 9.0, *) 461 | public func stream(with netService: NetService) -> StreamRequest { 462 | return SessionManager.default.stream(with: netService) 463 | } 464 | 465 | #endif 466 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/NetworkReachabilityManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkReachabilityManager.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #if !os(watchOS) 26 | 27 | import Foundation 28 | import SystemConfiguration 29 | 30 | /// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and 31 | /// WiFi network interfaces. 32 | /// 33 | /// Reachability can be used to determine background information about why a network operation failed, or to retry 34 | /// network requests when a connection is established. It should not be used to prevent a user from initiating a network 35 | /// request, as it's possible that an initial request may be required to establish reachability. 36 | public class NetworkReachabilityManager { 37 | /// Defines the various states of network reachability. 38 | /// 39 | /// - unknown: It is unknown whether the network is reachable. 40 | /// - notReachable: The network is not reachable. 41 | /// - reachable: The network is reachable. 42 | public enum NetworkReachabilityStatus { 43 | case unknown 44 | case notReachable 45 | case reachable(ConnectionType) 46 | } 47 | 48 | /// Defines the various connection types detected by reachability flags. 49 | /// 50 | /// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi. 51 | /// - wwan: The connection type is a WWAN connection. 52 | public enum ConnectionType { 53 | case ethernetOrWiFi 54 | case wwan 55 | } 56 | 57 | /// A closure executed when the network reachability status changes. The closure takes a single argument: the 58 | /// network reachability status. 59 | public typealias Listener = (NetworkReachabilityStatus) -> Void 60 | 61 | // MARK: - Properties 62 | 63 | /// Whether the network is currently reachable. 64 | public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi } 65 | 66 | /// Whether the network is currently reachable over the WWAN interface. 67 | public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) } 68 | 69 | /// Whether the network is currently reachable over Ethernet or WiFi interface. 70 | public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) } 71 | 72 | /// The current network reachability status. 73 | public var networkReachabilityStatus: NetworkReachabilityStatus { 74 | guard let flags = self.flags else { return .unknown } 75 | return networkReachabilityStatusForFlags(flags) 76 | } 77 | 78 | /// The dispatch queue to execute the `listener` closure on. 79 | public var listenerQueue: DispatchQueue = DispatchQueue.main 80 | 81 | /// A closure executed when the network reachability status changes. 82 | public var listener: Listener? 83 | 84 | private var flags: SCNetworkReachabilityFlags? { 85 | var flags = SCNetworkReachabilityFlags() 86 | 87 | if SCNetworkReachabilityGetFlags(reachability, &flags) { 88 | return flags 89 | } 90 | 91 | return nil 92 | } 93 | 94 | private let reachability: SCNetworkReachability 95 | private var previousFlags: SCNetworkReachabilityFlags 96 | 97 | // MARK: - Initialization 98 | 99 | /// Creates a `NetworkReachabilityManager` instance with the specified host. 100 | /// 101 | /// - parameter host: The host used to evaluate network reachability. 102 | /// 103 | /// - returns: The new `NetworkReachabilityManager` instance. 104 | public convenience init?(host: String) { 105 | guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } 106 | self.init(reachability: reachability) 107 | } 108 | 109 | /// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0. 110 | /// 111 | /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing 112 | /// status of the device, both IPv4 and IPv6. 113 | /// 114 | /// - returns: The new `NetworkReachabilityManager` instance. 115 | public convenience init?() { 116 | var address = sockaddr_in() 117 | address.sin_len = UInt8(MemoryLayout.size) 118 | address.sin_family = sa_family_t(AF_INET) 119 | 120 | guard let reachability = withUnsafePointer(to: &address, { pointer in 121 | return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout.size) { 122 | return SCNetworkReachabilityCreateWithAddress(nil, $0) 123 | } 124 | }) else { return nil } 125 | 126 | self.init(reachability: reachability) 127 | } 128 | 129 | private init(reachability: SCNetworkReachability) { 130 | self.reachability = reachability 131 | self.previousFlags = SCNetworkReachabilityFlags() 132 | } 133 | 134 | deinit { 135 | stopListening() 136 | } 137 | 138 | // MARK: - Listening 139 | 140 | /// Starts listening for changes in network reachability status. 141 | /// 142 | /// - returns: `true` if listening was started successfully, `false` otherwise. 143 | @discardableResult 144 | public func startListening() -> Bool { 145 | var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) 146 | context.info = Unmanaged.passUnretained(self).toOpaque() 147 | 148 | let callbackEnabled = SCNetworkReachabilitySetCallback( 149 | reachability, 150 | { (_, flags, info) in 151 | let reachability = Unmanaged.fromOpaque(info!).takeUnretainedValue() 152 | reachability.notifyListener(flags) 153 | }, 154 | &context 155 | ) 156 | 157 | let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) 158 | 159 | listenerQueue.async { 160 | self.previousFlags = SCNetworkReachabilityFlags() 161 | self.notifyListener(self.flags ?? SCNetworkReachabilityFlags()) 162 | } 163 | 164 | return callbackEnabled && queueEnabled 165 | } 166 | 167 | /// Stops listening for changes in network reachability status. 168 | public func stopListening() { 169 | SCNetworkReachabilitySetCallback(reachability, nil, nil) 170 | SCNetworkReachabilitySetDispatchQueue(reachability, nil) 171 | } 172 | 173 | // MARK: - Internal - Listener Notification 174 | 175 | func notifyListener(_ flags: SCNetworkReachabilityFlags) { 176 | guard previousFlags != flags else { return } 177 | previousFlags = flags 178 | 179 | listener?(networkReachabilityStatusForFlags(flags)) 180 | } 181 | 182 | // MARK: - Internal - Network Reachability Status 183 | 184 | func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus { 185 | guard flags.contains(.reachable) else { return .notReachable } 186 | 187 | var networkStatus: NetworkReachabilityStatus = .notReachable 188 | 189 | if !flags.contains(.connectionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } 190 | 191 | if flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) { 192 | if !flags.contains(.interventionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } 193 | } 194 | 195 | #if os(iOS) 196 | if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) } 197 | #endif 198 | 199 | return networkStatus 200 | } 201 | } 202 | 203 | // MARK: - 204 | 205 | extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} 206 | 207 | /// Returns whether the two network reachability status values are equal. 208 | /// 209 | /// - parameter lhs: The left-hand side value to compare. 210 | /// - parameter rhs: The right-hand side value to compare. 211 | /// 212 | /// - returns: `true` if the two values are equal, `false` otherwise. 213 | public func ==( 214 | lhs: NetworkReachabilityManager.NetworkReachabilityStatus, 215 | rhs: NetworkReachabilityManager.NetworkReachabilityStatus) 216 | -> Bool 217 | { 218 | switch (lhs, rhs) { 219 | case (.unknown, .unknown): 220 | return true 221 | case (.notReachable, .notReachable): 222 | return true 223 | case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): 224 | return lhsConnectionType == rhsConnectionType 225 | default: 226 | return false 227 | } 228 | } 229 | 230 | #endif 231 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/ParameterEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParameterEncoding.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// HTTP method definitions. 28 | /// 29 | /// See https://tools.ietf.org/html/rfc7231#section-4.3 30 | public enum HTTPMethod: String { 31 | case options = "OPTIONS" 32 | case get = "GET" 33 | case head = "HEAD" 34 | case post = "POST" 35 | case put = "PUT" 36 | case patch = "PATCH" 37 | case delete = "DELETE" 38 | case trace = "TRACE" 39 | case connect = "CONNECT" 40 | } 41 | 42 | // MARK: - 43 | 44 | /// A dictionary of parameters to apply to a `URLRequest`. 45 | public typealias Parameters = [String: Any] 46 | 47 | /// A type used to define how a set of parameters are applied to a `URLRequest`. 48 | public protocol ParameterEncoding { 49 | /// Creates a URL request by encoding parameters and applying them onto an existing request. 50 | /// 51 | /// - parameter urlRequest: The request to have parameters applied. 52 | /// - parameter parameters: The parameters to apply. 53 | /// 54 | /// - throws: An `AFError.parameterEncodingFailed` error if encoding fails. 55 | /// 56 | /// - returns: The encoded request. 57 | func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest 58 | } 59 | 60 | // MARK: - 61 | 62 | /// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP 63 | /// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as 64 | /// the HTTP body depends on the destination of the encoding. 65 | /// 66 | /// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to 67 | /// `application/x-www-form-urlencoded; charset=utf-8`. Since there is no published specification for how to encode 68 | /// collection types, the convention of appending `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending 69 | /// the key surrounded by square brackets for nested dictionary values (`foo[bar]=baz`). 70 | public struct URLEncoding: ParameterEncoding { 71 | 72 | // MARK: Helper Types 73 | 74 | /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the 75 | /// resulting URL request. 76 | /// 77 | /// - methodDependent: Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` 78 | /// requests and sets as the HTTP body for requests with any other HTTP method. 79 | /// - queryString: Sets or appends encoded query string result to existing query string. 80 | /// - httpBody: Sets encoded query string result as the HTTP body of the URL request. 81 | public enum Destination { 82 | case methodDependent, queryString, httpBody 83 | } 84 | 85 | // MARK: Properties 86 | 87 | /// Returns a default `URLEncoding` instance. 88 | public static var `default`: URLEncoding { return URLEncoding() } 89 | 90 | /// Returns a `URLEncoding` instance with a `.methodDependent` destination. 91 | public static var methodDependent: URLEncoding { return URLEncoding() } 92 | 93 | /// Returns a `URLEncoding` instance with a `.queryString` destination. 94 | public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) } 95 | 96 | /// Returns a `URLEncoding` instance with an `.httpBody` destination. 97 | public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) } 98 | 99 | /// The destination defining where the encoded query string is to be applied to the URL request. 100 | public let destination: Destination 101 | 102 | // MARK: Initialization 103 | 104 | /// Creates a `URLEncoding` instance using the specified destination. 105 | /// 106 | /// - parameter destination: The destination defining where the encoded query string is to be applied. 107 | /// 108 | /// - returns: The new `URLEncoding` instance. 109 | public init(destination: Destination = .methodDependent) { 110 | self.destination = destination 111 | } 112 | 113 | // MARK: Encoding 114 | 115 | /// Creates a URL request by encoding parameters and applying them onto an existing request. 116 | /// 117 | /// - parameter urlRequest: The request to have parameters applied. 118 | /// - parameter parameters: The parameters to apply. 119 | /// 120 | /// - throws: An `Error` if the encoding process encounters an error. 121 | /// 122 | /// - returns: The encoded request. 123 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 124 | var urlRequest = try urlRequest.asURLRequest() 125 | 126 | guard let parameters = parameters else { return urlRequest } 127 | 128 | if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) { 129 | guard let url = urlRequest.url else { 130 | throw AFError.parameterEncodingFailed(reason: .missingURL) 131 | } 132 | 133 | if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { 134 | let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) 135 | urlComponents.percentEncodedQuery = percentEncodedQuery 136 | urlRequest.url = urlComponents.url 137 | } 138 | } else { 139 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 140 | urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") 141 | } 142 | 143 | urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false) 144 | } 145 | 146 | return urlRequest 147 | } 148 | 149 | /// Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion. 150 | /// 151 | /// - parameter key: The key of the query component. 152 | /// - parameter value: The value of the query component. 153 | /// 154 | /// - returns: The percent-escaped, URL encoded query string components. 155 | public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { 156 | var components: [(String, String)] = [] 157 | 158 | if let dictionary = value as? [String: Any] { 159 | for (nestedKey, value) in dictionary { 160 | components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) 161 | } 162 | } else if let array = value as? [Any] { 163 | for value in array { 164 | components += queryComponents(fromKey: "\(key)[]", value: value) 165 | } 166 | } else if let value = value as? NSNumber { 167 | if value.isBool { 168 | components.append((escape(key), escape((value.boolValue ? "1" : "0")))) 169 | } else { 170 | components.append((escape(key), escape("\(value)"))) 171 | } 172 | } else if let bool = value as? Bool { 173 | components.append((escape(key), escape((bool ? "1" : "0")))) 174 | } else { 175 | components.append((escape(key), escape("\(value)"))) 176 | } 177 | 178 | return components 179 | } 180 | 181 | /// Returns a percent-escaped string following RFC 3986 for a query string key or value. 182 | /// 183 | /// RFC 3986 states that the following characters are "reserved" characters. 184 | /// 185 | /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" 186 | /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" 187 | /// 188 | /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow 189 | /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" 190 | /// should be percent-escaped in the query string. 191 | /// 192 | /// - parameter string: The string to be percent-escaped. 193 | /// 194 | /// - returns: The percent-escaped string. 195 | public func escape(_ string: String) -> String { 196 | let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 197 | let subDelimitersToEncode = "!$&'()*+,;=" 198 | 199 | var allowedCharacterSet = CharacterSet.urlQueryAllowed 200 | allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") 201 | 202 | var escaped = "" 203 | 204 | //========================================================================================================== 205 | // 206 | // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few 207 | // hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no 208 | // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more 209 | // info, please refer to: 210 | // 211 | // - https://github.com/Alamofire/Alamofire/issues/206 212 | // 213 | //========================================================================================================== 214 | 215 | if #available(iOS 8.3, *) { 216 | escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string 217 | } else { 218 | let batchSize = 50 219 | var index = string.startIndex 220 | 221 | while index != string.endIndex { 222 | let startIndex = index 223 | let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex 224 | let range = startIndex.. String { 238 | var components: [(String, String)] = [] 239 | 240 | for key in parameters.keys.sorted(by: <) { 241 | let value = parameters[key]! 242 | components += queryComponents(fromKey: key, value: value) 243 | } 244 | 245 | return components.map { "\($0)=\($1)" }.joined(separator: "&") 246 | } 247 | 248 | private func encodesParametersInURL(with method: HTTPMethod) -> Bool { 249 | switch destination { 250 | case .queryString: 251 | return true 252 | case .httpBody: 253 | return false 254 | default: 255 | break 256 | } 257 | 258 | switch method { 259 | case .get, .head, .delete: 260 | return true 261 | default: 262 | return false 263 | } 264 | } 265 | } 266 | 267 | // MARK: - 268 | 269 | /// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the 270 | /// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. 271 | public struct JSONEncoding: ParameterEncoding { 272 | 273 | // MARK: Properties 274 | 275 | /// Returns a `JSONEncoding` instance with default writing options. 276 | public static var `default`: JSONEncoding { return JSONEncoding() } 277 | 278 | /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options. 279 | public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) } 280 | 281 | /// The options for writing the parameters as JSON data. 282 | public let options: JSONSerialization.WritingOptions 283 | 284 | // MARK: Initialization 285 | 286 | /// Creates a `JSONEncoding` instance using the specified options. 287 | /// 288 | /// - parameter options: The options for writing the parameters as JSON data. 289 | /// 290 | /// - returns: The new `JSONEncoding` instance. 291 | public init(options: JSONSerialization.WritingOptions = []) { 292 | self.options = options 293 | } 294 | 295 | // MARK: Encoding 296 | 297 | /// Creates a URL request by encoding parameters and applying them onto an existing request. 298 | /// 299 | /// - parameter urlRequest: The request to have parameters applied. 300 | /// - parameter parameters: The parameters to apply. 301 | /// 302 | /// - throws: An `Error` if the encoding process encounters an error. 303 | /// 304 | /// - returns: The encoded request. 305 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 306 | var urlRequest = try urlRequest.asURLRequest() 307 | 308 | guard let parameters = parameters else { return urlRequest } 309 | 310 | do { 311 | let data = try JSONSerialization.data(withJSONObject: parameters, options: options) 312 | 313 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 314 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 315 | } 316 | 317 | urlRequest.httpBody = data 318 | } catch { 319 | throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 320 | } 321 | 322 | return urlRequest 323 | } 324 | 325 | /// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body. 326 | /// 327 | /// - parameter urlRequest: The request to apply the JSON object to. 328 | /// - parameter jsonObject: The JSON object to apply to the request. 329 | /// 330 | /// - throws: An `Error` if the encoding process encounters an error. 331 | /// 332 | /// - returns: The encoded request. 333 | public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest { 334 | var urlRequest = try urlRequest.asURLRequest() 335 | 336 | guard let jsonObject = jsonObject else { return urlRequest } 337 | 338 | do { 339 | let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) 340 | 341 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 342 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 343 | } 344 | 345 | urlRequest.httpBody = data 346 | } catch { 347 | throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 348 | } 349 | 350 | return urlRequest 351 | } 352 | } 353 | 354 | // MARK: - 355 | 356 | /// Uses `PropertyListSerialization` to create a plist representation of the parameters object, according to the 357 | /// associated format and write options values, which is set as the body of the request. The `Content-Type` HTTP header 358 | /// field of an encoded request is set to `application/x-plist`. 359 | public struct PropertyListEncoding: ParameterEncoding { 360 | 361 | // MARK: Properties 362 | 363 | /// Returns a default `PropertyListEncoding` instance. 364 | public static var `default`: PropertyListEncoding { return PropertyListEncoding() } 365 | 366 | /// Returns a `PropertyListEncoding` instance with xml formatting and default writing options. 367 | public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) } 368 | 369 | /// Returns a `PropertyListEncoding` instance with binary formatting and default writing options. 370 | public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) } 371 | 372 | /// The property list serialization format. 373 | public let format: PropertyListSerialization.PropertyListFormat 374 | 375 | /// The options for writing the parameters as plist data. 376 | public let options: PropertyListSerialization.WriteOptions 377 | 378 | // MARK: Initialization 379 | 380 | /// Creates a `PropertyListEncoding` instance using the specified format and options. 381 | /// 382 | /// - parameter format: The property list serialization format. 383 | /// - parameter options: The options for writing the parameters as plist data. 384 | /// 385 | /// - returns: The new `PropertyListEncoding` instance. 386 | public init( 387 | format: PropertyListSerialization.PropertyListFormat = .xml, 388 | options: PropertyListSerialization.WriteOptions = 0) 389 | { 390 | self.format = format 391 | self.options = options 392 | } 393 | 394 | // MARK: Encoding 395 | 396 | /// Creates a URL request by encoding parameters and applying them onto an existing request. 397 | /// 398 | /// - parameter urlRequest: The request to have parameters applied. 399 | /// - parameter parameters: The parameters to apply. 400 | /// 401 | /// - throws: An `Error` if the encoding process encounters an error. 402 | /// 403 | /// - returns: The encoded request. 404 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 405 | var urlRequest = try urlRequest.asURLRequest() 406 | 407 | guard let parameters = parameters else { return urlRequest } 408 | 409 | do { 410 | let data = try PropertyListSerialization.data( 411 | fromPropertyList: parameters, 412 | format: format, 413 | options: options 414 | ) 415 | 416 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 417 | urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type") 418 | } 419 | 420 | urlRequest.httpBody = data 421 | } catch { 422 | throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error)) 423 | } 424 | 425 | return urlRequest 426 | } 427 | } 428 | 429 | // MARK: - 430 | 431 | extension NSNumber { 432 | fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) } 433 | } 434 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to store all data associated with an non-serialized response of a data or upload request. 28 | public struct DefaultDataResponse { 29 | /// The URL request sent to the server. 30 | public let request: URLRequest? 31 | 32 | /// The server's response to the URL request. 33 | public let response: HTTPURLResponse? 34 | 35 | /// The data returned by the server. 36 | public let data: Data? 37 | 38 | /// The error encountered while executing or validating the request. 39 | public let error: Error? 40 | 41 | /// The timeline of the complete lifecycle of the request. 42 | public let timeline: Timeline 43 | 44 | var _metrics: AnyObject? 45 | 46 | /// Creates a `DefaultDataResponse` instance from the specified parameters. 47 | /// 48 | /// - Parameters: 49 | /// - request: The URL request sent to the server. 50 | /// - response: The server's response to the URL request. 51 | /// - data: The data returned by the server. 52 | /// - error: The error encountered while executing or validating the request. 53 | /// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default. 54 | /// - metrics: The task metrics containing the request / response statistics. `nil` by default. 55 | public init( 56 | request: URLRequest?, 57 | response: HTTPURLResponse?, 58 | data: Data?, 59 | error: Error?, 60 | timeline: Timeline = Timeline(), 61 | metrics: AnyObject? = nil) 62 | { 63 | self.request = request 64 | self.response = response 65 | self.data = data 66 | self.error = error 67 | self.timeline = timeline 68 | } 69 | } 70 | 71 | // MARK: - 72 | 73 | /// Used to store all data associated with a serialized response of a data or upload request. 74 | public struct DataResponse { 75 | /// The URL request sent to the server. 76 | public let request: URLRequest? 77 | 78 | /// The server's response to the URL request. 79 | public let response: HTTPURLResponse? 80 | 81 | /// The data returned by the server. 82 | public let data: Data? 83 | 84 | /// The result of response serialization. 85 | public let result: Result 86 | 87 | /// The timeline of the complete lifecycle of the request. 88 | public let timeline: Timeline 89 | 90 | /// Returns the associated value of the result if it is a success, `nil` otherwise. 91 | public var value: Value? { return result.value } 92 | 93 | /// Returns the associated error value if the result if it is a failure, `nil` otherwise. 94 | public var error: Error? { return result.error } 95 | 96 | var _metrics: AnyObject? 97 | 98 | /// Creates a `DataResponse` instance with the specified parameters derived from response serialization. 99 | /// 100 | /// - parameter request: The URL request sent to the server. 101 | /// - parameter response: The server's response to the URL request. 102 | /// - parameter data: The data returned by the server. 103 | /// - parameter result: The result of response serialization. 104 | /// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`. 105 | /// 106 | /// - returns: The new `DataResponse` instance. 107 | public init( 108 | request: URLRequest?, 109 | response: HTTPURLResponse?, 110 | data: Data?, 111 | result: Result, 112 | timeline: Timeline = Timeline()) 113 | { 114 | self.request = request 115 | self.response = response 116 | self.data = data 117 | self.result = result 118 | self.timeline = timeline 119 | } 120 | } 121 | 122 | // MARK: - 123 | 124 | extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible { 125 | /// The textual representation used when written to an output stream, which includes whether the result was a 126 | /// success or failure. 127 | public var description: String { 128 | return result.debugDescription 129 | } 130 | 131 | /// The debug textual representation used when written to an output stream, which includes the URL request, the URL 132 | /// response, the server data, the response serialization result and the timeline. 133 | public var debugDescription: String { 134 | var output: [String] = [] 135 | 136 | output.append(request != nil ? "[Request]: \(request!.httpMethod ?? "GET") \(request!)" : "[Request]: nil") 137 | output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil") 138 | output.append("[Data]: \(data?.count ?? 0) bytes") 139 | output.append("[Result]: \(result.debugDescription)") 140 | output.append("[Timeline]: \(timeline.debugDescription)") 141 | 142 | return output.joined(separator: "\n") 143 | } 144 | } 145 | 146 | // MARK: - 147 | 148 | /// Used to store all data associated with an non-serialized response of a download request. 149 | public struct DefaultDownloadResponse { 150 | /// The URL request sent to the server. 151 | public let request: URLRequest? 152 | 153 | /// The server's response to the URL request. 154 | public let response: HTTPURLResponse? 155 | 156 | /// The temporary destination URL of the data returned from the server. 157 | public let temporaryURL: URL? 158 | 159 | /// The final destination URL of the data returned from the server if it was moved. 160 | public let destinationURL: URL? 161 | 162 | /// The resume data generated if the request was cancelled. 163 | public let resumeData: Data? 164 | 165 | /// The error encountered while executing or validating the request. 166 | public let error: Error? 167 | 168 | /// The timeline of the complete lifecycle of the request. 169 | public let timeline: Timeline 170 | 171 | var _metrics: AnyObject? 172 | 173 | /// Creates a `DefaultDownloadResponse` instance from the specified parameters. 174 | /// 175 | /// - Parameters: 176 | /// - request: The URL request sent to the server. 177 | /// - response: The server's response to the URL request. 178 | /// - temporaryURL: The temporary destination URL of the data returned from the server. 179 | /// - destinationURL: The final destination URL of the data returned from the server if it was moved. 180 | /// - resumeData: The resume data generated if the request was cancelled. 181 | /// - error: The error encountered while executing or validating the request. 182 | /// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default. 183 | /// - metrics: The task metrics containing the request / response statistics. `nil` by default. 184 | public init( 185 | request: URLRequest?, 186 | response: HTTPURLResponse?, 187 | temporaryURL: URL?, 188 | destinationURL: URL?, 189 | resumeData: Data?, 190 | error: Error?, 191 | timeline: Timeline = Timeline(), 192 | metrics: AnyObject? = nil) 193 | { 194 | self.request = request 195 | self.response = response 196 | self.temporaryURL = temporaryURL 197 | self.destinationURL = destinationURL 198 | self.resumeData = resumeData 199 | self.error = error 200 | self.timeline = timeline 201 | } 202 | } 203 | 204 | // MARK: - 205 | 206 | /// Used to store all data associated with a serialized response of a download request. 207 | public struct DownloadResponse { 208 | /// The URL request sent to the server. 209 | public let request: URLRequest? 210 | 211 | /// The server's response to the URL request. 212 | public let response: HTTPURLResponse? 213 | 214 | /// The temporary destination URL of the data returned from the server. 215 | public let temporaryURL: URL? 216 | 217 | /// The final destination URL of the data returned from the server if it was moved. 218 | public let destinationURL: URL? 219 | 220 | /// The resume data generated if the request was cancelled. 221 | public let resumeData: Data? 222 | 223 | /// The result of response serialization. 224 | public let result: Result 225 | 226 | /// The timeline of the complete lifecycle of the request. 227 | public let timeline: Timeline 228 | 229 | /// Returns the associated value of the result if it is a success, `nil` otherwise. 230 | public var value: Value? { return result.value } 231 | 232 | /// Returns the associated error value if the result if it is a failure, `nil` otherwise. 233 | public var error: Error? { return result.error } 234 | 235 | var _metrics: AnyObject? 236 | 237 | /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization. 238 | /// 239 | /// - parameter request: The URL request sent to the server. 240 | /// - parameter response: The server's response to the URL request. 241 | /// - parameter temporaryURL: The temporary destination URL of the data returned from the server. 242 | /// - parameter destinationURL: The final destination URL of the data returned from the server if it was moved. 243 | /// - parameter resumeData: The resume data generated if the request was cancelled. 244 | /// - parameter result: The result of response serialization. 245 | /// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`. 246 | /// 247 | /// - returns: The new `DownloadResponse` instance. 248 | public init( 249 | request: URLRequest?, 250 | response: HTTPURLResponse?, 251 | temporaryURL: URL?, 252 | destinationURL: URL?, 253 | resumeData: Data?, 254 | result: Result, 255 | timeline: Timeline = Timeline()) 256 | { 257 | self.request = request 258 | self.response = response 259 | self.temporaryURL = temporaryURL 260 | self.destinationURL = destinationURL 261 | self.resumeData = resumeData 262 | self.result = result 263 | self.timeline = timeline 264 | } 265 | } 266 | 267 | // MARK: - 268 | 269 | extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible { 270 | /// The textual representation used when written to an output stream, which includes whether the result was a 271 | /// success or failure. 272 | public var description: String { 273 | return result.debugDescription 274 | } 275 | 276 | /// The debug textual representation used when written to an output stream, which includes the URL request, the URL 277 | /// response, the temporary and destination URLs, the resume data, the response serialization result and the 278 | /// timeline. 279 | public var debugDescription: String { 280 | var output: [String] = [] 281 | 282 | output.append(request != nil ? "[Request]: \(request!.httpMethod ?? "GET") \(request!)" : "[Request]: nil") 283 | output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil") 284 | output.append("[TemporaryURL]: \(temporaryURL?.path ?? "nil")") 285 | output.append("[DestinationURL]: \(destinationURL?.path ?? "nil")") 286 | output.append("[ResumeData]: \(resumeData?.count ?? 0) bytes") 287 | output.append("[Result]: \(result.debugDescription)") 288 | output.append("[Timeline]: \(timeline.debugDescription)") 289 | 290 | return output.joined(separator: "\n") 291 | } 292 | } 293 | 294 | // MARK: - 295 | 296 | protocol Response { 297 | /// The task metrics containing the request / response statistics. 298 | var _metrics: AnyObject? { get set } 299 | mutating func add(_ metrics: AnyObject?) 300 | } 301 | 302 | extension Response { 303 | mutating func add(_ metrics: AnyObject?) { 304 | #if !os(watchOS) 305 | guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return } 306 | guard let metrics = metrics as? URLSessionTaskMetrics else { return } 307 | 308 | _metrics = metrics 309 | #endif 310 | } 311 | } 312 | 313 | // MARK: - 314 | 315 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) 316 | extension DefaultDataResponse: Response { 317 | #if !os(watchOS) 318 | /// The task metrics containing the request / response statistics. 319 | public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } 320 | #endif 321 | } 322 | 323 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) 324 | extension DataResponse: Response { 325 | #if !os(watchOS) 326 | /// The task metrics containing the request / response statistics. 327 | public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } 328 | #endif 329 | } 330 | 331 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) 332 | extension DefaultDownloadResponse: Response { 333 | #if !os(watchOS) 334 | /// The task metrics containing the request / response statistics. 335 | public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } 336 | #endif 337 | } 338 | 339 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, *) 340 | extension DownloadResponse: Response { 341 | #if !os(watchOS) 342 | /// The task metrics containing the request / response statistics. 343 | public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } 344 | #endif 345 | } 346 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to represent whether a request was successful or encountered an error. 28 | /// 29 | /// - success: The request and all post processing operations were successful resulting in the serialization of the 30 | /// provided associated value. 31 | /// 32 | /// - failure: The request encountered an error resulting in a failure. The associated values are the original data 33 | /// provided by the server as well as the error that caused the failure. 34 | public enum Result { 35 | case success(Value) 36 | case failure(Error) 37 | 38 | /// Returns `true` if the result is a success, `false` otherwise. 39 | public var isSuccess: Bool { 40 | switch self { 41 | case .success: 42 | return true 43 | case .failure: 44 | return false 45 | } 46 | } 47 | 48 | /// Returns `true` if the result is a failure, `false` otherwise. 49 | public var isFailure: Bool { 50 | return !isSuccess 51 | } 52 | 53 | /// Returns the associated value if the result is a success, `nil` otherwise. 54 | public var value: Value? { 55 | switch self { 56 | case .success(let value): 57 | return value 58 | case .failure: 59 | return nil 60 | } 61 | } 62 | 63 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 64 | public var error: Error? { 65 | switch self { 66 | case .success: 67 | return nil 68 | case .failure(let error): 69 | return error 70 | } 71 | } 72 | } 73 | 74 | // MARK: - CustomStringConvertible 75 | 76 | extension Result: CustomStringConvertible { 77 | /// The textual representation used when written to an output stream, which includes whether the result was a 78 | /// success or failure. 79 | public var description: String { 80 | switch self { 81 | case .success: 82 | return "SUCCESS" 83 | case .failure: 84 | return "FAILURE" 85 | } 86 | } 87 | } 88 | 89 | // MARK: - CustomDebugStringConvertible 90 | 91 | extension Result: CustomDebugStringConvertible { 92 | /// The debug textual representation used when written to an output stream, which includes whether the result was a 93 | /// success or failure in addition to the value or error. 94 | public var debugDescription: String { 95 | switch self { 96 | case .success(let value): 97 | return "SUCCESS: \(value)" 98 | case .failure(let error): 99 | return "FAILURE: \(error)" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/ServerTrustPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerTrustPolicy.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host. 28 | open class ServerTrustPolicyManager { 29 | /// The dictionary of policies mapped to a particular host. 30 | open let policies: [String: ServerTrustPolicy] 31 | 32 | /// Initializes the `ServerTrustPolicyManager` instance with the given policies. 33 | /// 34 | /// Since different servers and web services can have different leaf certificates, intermediate and even root 35 | /// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This 36 | /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key 37 | /// pinning for host3 and disabling evaluation for host4. 38 | /// 39 | /// - parameter policies: A dictionary of all policies mapped to a particular host. 40 | /// 41 | /// - returns: The new `ServerTrustPolicyManager` instance. 42 | public init(policies: [String: ServerTrustPolicy]) { 43 | self.policies = policies 44 | } 45 | 46 | /// Returns the `ServerTrustPolicy` for the given host if applicable. 47 | /// 48 | /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override 49 | /// this method and implement more complex mapping implementations such as wildcards. 50 | /// 51 | /// - parameter host: The host to use when searching for a matching policy. 52 | /// 53 | /// - returns: The server trust policy for the given host if found. 54 | open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? { 55 | return policies[host] 56 | } 57 | } 58 | 59 | // MARK: - 60 | 61 | extension URLSession { 62 | private struct AssociatedKeys { 63 | static var managerKey = "URLSession.ServerTrustPolicyManager" 64 | } 65 | 66 | var serverTrustPolicyManager: ServerTrustPolicyManager? { 67 | get { 68 | return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager 69 | } 70 | set (manager) { 71 | objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 72 | } 73 | } 74 | } 75 | 76 | // MARK: - ServerTrustPolicy 77 | 78 | /// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when 79 | /// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust 80 | /// with a given set of criteria to determine whether the server trust is valid and the connection should be made. 81 | /// 82 | /// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other 83 | /// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged 84 | /// to route all communication over an HTTPS connection with pinning enabled. 85 | /// 86 | /// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to 87 | /// validate the host provided by the challenge. Applications are encouraged to always 88 | /// validate the host in production environments to guarantee the validity of the server's 89 | /// certificate chain. 90 | /// 91 | /// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to 92 | /// validate the host provided by the challenge as well as specify the revocation flags for 93 | /// testing for revoked certificates. Apple platforms did not start testing for revoked 94 | /// certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is 95 | /// demonstrated in our TLS tests. Applications are encouraged to always validate the host 96 | /// in production environments to guarantee the validity of the server's certificate chain. 97 | /// 98 | /// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is 99 | /// considered valid if one of the pinned certificates match one of the server certificates. 100 | /// By validating both the certificate chain and host, certificate pinning provides a very 101 | /// secure form of server trust validation mitigating most, if not all, MITM attacks. 102 | /// Applications are encouraged to always validate the host and require a valid certificate 103 | /// chain in production environments. 104 | /// 105 | /// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered 106 | /// valid if one of the pinned public keys match one of the server certificate public keys. 107 | /// By validating both the certificate chain and host, public key pinning provides a very 108 | /// secure form of server trust validation mitigating most, if not all, MITM attacks. 109 | /// Applications are encouraged to always validate the host and require a valid certificate 110 | /// chain in production environments. 111 | /// 112 | /// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid. 113 | /// 114 | /// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust. 115 | public enum ServerTrustPolicy { 116 | case performDefaultEvaluation(validateHost: Bool) 117 | case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags) 118 | case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool) 119 | case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool) 120 | case disableEvaluation 121 | case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool) 122 | 123 | // MARK: - Bundle Location 124 | 125 | /// Returns all certificates within the given bundle with a `.cer` file extension. 126 | /// 127 | /// - parameter bundle: The bundle to search for all `.cer` files. 128 | /// 129 | /// - returns: All certificates within the given bundle. 130 | public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] { 131 | var certificates: [SecCertificate] = [] 132 | 133 | let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in 134 | bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil) 135 | }.joined()) 136 | 137 | for path in paths { 138 | if 139 | let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, 140 | let certificate = SecCertificateCreateWithData(nil, certificateData) 141 | { 142 | certificates.append(certificate) 143 | } 144 | } 145 | 146 | return certificates 147 | } 148 | 149 | /// Returns all public keys within the given bundle with a `.cer` file extension. 150 | /// 151 | /// - parameter bundle: The bundle to search for all `*.cer` files. 152 | /// 153 | /// - returns: All public keys within the given bundle. 154 | public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] { 155 | var publicKeys: [SecKey] = [] 156 | 157 | for certificate in certificates(in: bundle) { 158 | if let publicKey = publicKey(for: certificate) { 159 | publicKeys.append(publicKey) 160 | } 161 | } 162 | 163 | return publicKeys 164 | } 165 | 166 | // MARK: - Evaluation 167 | 168 | /// Evaluates whether the server trust is valid for the given host. 169 | /// 170 | /// - parameter serverTrust: The server trust to evaluate. 171 | /// - parameter host: The host of the challenge protection space. 172 | /// 173 | /// - returns: Whether the server trust is valid. 174 | public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool { 175 | var serverTrustIsValid = false 176 | 177 | switch self { 178 | case let .performDefaultEvaluation(validateHost): 179 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 180 | SecTrustSetPolicies(serverTrust, policy) 181 | 182 | serverTrustIsValid = trustIsValid(serverTrust) 183 | case let .performRevokedEvaluation(validateHost, revocationFlags): 184 | let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 185 | let revokedPolicy = SecPolicyCreateRevocation(revocationFlags) 186 | SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef) 187 | 188 | serverTrustIsValid = trustIsValid(serverTrust) 189 | case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost): 190 | if validateCertificateChain { 191 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 192 | SecTrustSetPolicies(serverTrust, policy) 193 | 194 | SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray) 195 | SecTrustSetAnchorCertificatesOnly(serverTrust, true) 196 | 197 | serverTrustIsValid = trustIsValid(serverTrust) 198 | } else { 199 | let serverCertificatesDataArray = certificateData(for: serverTrust) 200 | let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates) 201 | 202 | outerLoop: for serverCertificateData in serverCertificatesDataArray { 203 | for pinnedCertificateData in pinnedCertificatesDataArray { 204 | if serverCertificateData == pinnedCertificateData { 205 | serverTrustIsValid = true 206 | break outerLoop 207 | } 208 | } 209 | } 210 | } 211 | case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost): 212 | var certificateChainEvaluationPassed = true 213 | 214 | if validateCertificateChain { 215 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 216 | SecTrustSetPolicies(serverTrust, policy) 217 | 218 | certificateChainEvaluationPassed = trustIsValid(serverTrust) 219 | } 220 | 221 | if certificateChainEvaluationPassed { 222 | outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] { 223 | for pinnedPublicKey in pinnedPublicKeys as [AnyObject] { 224 | if serverPublicKey.isEqual(pinnedPublicKey) { 225 | serverTrustIsValid = true 226 | break outerLoop 227 | } 228 | } 229 | } 230 | } 231 | case .disableEvaluation: 232 | serverTrustIsValid = true 233 | case let .customEvaluation(closure): 234 | serverTrustIsValid = closure(serverTrust, host) 235 | } 236 | 237 | return serverTrustIsValid 238 | } 239 | 240 | // MARK: - Private - Trust Validation 241 | 242 | private func trustIsValid(_ trust: SecTrust) -> Bool { 243 | var isValid = false 244 | 245 | var result = SecTrustResultType.invalid 246 | let status = SecTrustEvaluate(trust, &result) 247 | 248 | if status == errSecSuccess { 249 | let unspecified = SecTrustResultType.unspecified 250 | let proceed = SecTrustResultType.proceed 251 | 252 | 253 | isValid = result == unspecified || result == proceed 254 | } 255 | 256 | return isValid 257 | } 258 | 259 | // MARK: - Private - Certificate Data 260 | 261 | private func certificateData(for trust: SecTrust) -> [Data] { 262 | var certificates: [SecCertificate] = [] 263 | 264 | for index in 0.. [Data] { 274 | return certificates.map { SecCertificateCopyData($0) as Data } 275 | } 276 | 277 | // MARK: - Private - Public Key Extraction 278 | 279 | private static func publicKeys(for trust: SecTrust) -> [SecKey] { 280 | var publicKeys: [SecKey] = [] 281 | 282 | for index in 0.. SecKey? { 295 | var publicKey: SecKey? 296 | 297 | let policy = SecPolicyCreateBasicX509() 298 | var trust: SecTrust? 299 | let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust) 300 | 301 | if let trust = trust, trustCreationStatus == errSecSuccess { 302 | publicKey = SecTrustCopyPublicKey(trust) 303 | } 304 | 305 | return publicKey 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/TaskDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskDelegate.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as 28 | /// executing all operations attached to the serial operation queue upon task completion. 29 | open class TaskDelegate: NSObject { 30 | 31 | // MARK: Properties 32 | 33 | /// The serial operation queue used to execute all operations after the task completes. 34 | open let queue: OperationQueue 35 | 36 | /// The data returned by the server. 37 | public var data: Data? { return nil } 38 | 39 | /// The error generated throughout the lifecyle of the task. 40 | public var error: Error? 41 | 42 | var task: URLSessionTask? { 43 | didSet { reset() } 44 | } 45 | 46 | var initialResponseTime: CFAbsoluteTime? 47 | var credential: URLCredential? 48 | var metrics: AnyObject? // URLSessionTaskMetrics 49 | 50 | // MARK: Lifecycle 51 | 52 | init(task: URLSessionTask?) { 53 | self.task = task 54 | 55 | self.queue = { 56 | let operationQueue = OperationQueue() 57 | 58 | operationQueue.maxConcurrentOperationCount = 1 59 | operationQueue.isSuspended = true 60 | operationQueue.qualityOfService = .utility 61 | 62 | return operationQueue 63 | }() 64 | } 65 | 66 | func reset() { 67 | error = nil 68 | initialResponseTime = nil 69 | } 70 | 71 | // MARK: URLSessionTaskDelegate 72 | 73 | var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? 74 | var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? 75 | var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? 76 | var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)? 77 | 78 | @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) 79 | func urlSession( 80 | _ session: URLSession, 81 | task: URLSessionTask, 82 | willPerformHTTPRedirection response: HTTPURLResponse, 83 | newRequest request: URLRequest, 84 | completionHandler: @escaping (URLRequest?) -> Void) 85 | { 86 | var redirectRequest: URLRequest? = request 87 | 88 | if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { 89 | redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) 90 | } 91 | 92 | completionHandler(redirectRequest) 93 | } 94 | 95 | @objc(URLSession:task:didReceiveChallenge:completionHandler:) 96 | func urlSession( 97 | _ session: URLSession, 98 | task: URLSessionTask, 99 | didReceive challenge: URLAuthenticationChallenge, 100 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 101 | { 102 | var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling 103 | var credential: URLCredential? 104 | 105 | if let taskDidReceiveChallenge = taskDidReceiveChallenge { 106 | (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) 107 | } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { 108 | let host = challenge.protectionSpace.host 109 | 110 | if 111 | let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host), 112 | let serverTrust = challenge.protectionSpace.serverTrust 113 | { 114 | if serverTrustPolicy.evaluate(serverTrust, forHost: host) { 115 | disposition = .useCredential 116 | credential = URLCredential(trust: serverTrust) 117 | } else { 118 | disposition = .cancelAuthenticationChallenge 119 | } 120 | } 121 | } else { 122 | if challenge.previousFailureCount > 0 { 123 | disposition = .rejectProtectionSpace 124 | } else { 125 | credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) 126 | 127 | if credential != nil { 128 | disposition = .useCredential 129 | } 130 | } 131 | } 132 | 133 | completionHandler(disposition, credential) 134 | } 135 | 136 | @objc(URLSession:task:needNewBodyStream:) 137 | func urlSession( 138 | _ session: URLSession, 139 | task: URLSessionTask, 140 | needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) 141 | { 142 | var bodyStream: InputStream? 143 | 144 | if let taskNeedNewBodyStream = taskNeedNewBodyStream { 145 | bodyStream = taskNeedNewBodyStream(session, task) 146 | } 147 | 148 | completionHandler(bodyStream) 149 | } 150 | 151 | @objc(URLSession:task:didCompleteWithError:) 152 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 153 | if let taskDidCompleteWithError = taskDidCompleteWithError { 154 | taskDidCompleteWithError(session, task, error) 155 | } else { 156 | if let error = error { 157 | if self.error == nil { self.error = error } 158 | 159 | if 160 | let downloadDelegate = self as? DownloadTaskDelegate, 161 | let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data 162 | { 163 | downloadDelegate.resumeData = resumeData 164 | } 165 | } 166 | 167 | queue.isSuspended = false 168 | } 169 | } 170 | } 171 | 172 | // MARK: - 173 | 174 | class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate { 175 | 176 | // MARK: Properties 177 | 178 | var dataTask: URLSessionDataTask { return task as! URLSessionDataTask } 179 | 180 | override var data: Data? { 181 | if dataStream != nil { 182 | return nil 183 | } else { 184 | return mutableData 185 | } 186 | } 187 | 188 | var progress: Progress 189 | var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? 190 | 191 | var dataStream: ((_ data: Data) -> Void)? 192 | 193 | private var totalBytesReceived: Int64 = 0 194 | private var mutableData: Data 195 | 196 | private var expectedContentLength: Int64? 197 | 198 | // MARK: Lifecycle 199 | 200 | override init(task: URLSessionTask?) { 201 | mutableData = Data() 202 | progress = Progress(totalUnitCount: 0) 203 | 204 | super.init(task: task) 205 | } 206 | 207 | override func reset() { 208 | super.reset() 209 | 210 | progress = Progress(totalUnitCount: 0) 211 | totalBytesReceived = 0 212 | mutableData = Data() 213 | expectedContentLength = nil 214 | } 215 | 216 | // MARK: URLSessionDataDelegate 217 | 218 | var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? 219 | var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? 220 | var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? 221 | var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)? 222 | 223 | func urlSession( 224 | _ session: URLSession, 225 | dataTask: URLSessionDataTask, 226 | didReceive response: URLResponse, 227 | completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) 228 | { 229 | var disposition: URLSession.ResponseDisposition = .allow 230 | 231 | expectedContentLength = response.expectedContentLength 232 | 233 | if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { 234 | disposition = dataTaskDidReceiveResponse(session, dataTask, response) 235 | } 236 | 237 | completionHandler(disposition) 238 | } 239 | 240 | func urlSession( 241 | _ session: URLSession, 242 | dataTask: URLSessionDataTask, 243 | didBecome downloadTask: URLSessionDownloadTask) 244 | { 245 | dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask) 246 | } 247 | 248 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 249 | if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } 250 | 251 | if let dataTaskDidReceiveData = dataTaskDidReceiveData { 252 | dataTaskDidReceiveData(session, dataTask, data) 253 | } else { 254 | if let dataStream = dataStream { 255 | dataStream(data) 256 | } else { 257 | mutableData.append(data) 258 | } 259 | 260 | let bytesReceived = Int64(data.count) 261 | totalBytesReceived += bytesReceived 262 | let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown 263 | 264 | progress.totalUnitCount = totalBytesExpected 265 | progress.completedUnitCount = totalBytesReceived 266 | 267 | if let progressHandler = progressHandler { 268 | progressHandler.queue.async { progressHandler.closure(self.progress) } 269 | } 270 | } 271 | } 272 | 273 | func urlSession( 274 | _ session: URLSession, 275 | dataTask: URLSessionDataTask, 276 | willCacheResponse proposedResponse: CachedURLResponse, 277 | completionHandler: @escaping (CachedURLResponse?) -> Void) 278 | { 279 | var cachedResponse: CachedURLResponse? = proposedResponse 280 | 281 | if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { 282 | cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse) 283 | } 284 | 285 | completionHandler(cachedResponse) 286 | } 287 | } 288 | 289 | // MARK: - 290 | 291 | class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate { 292 | 293 | // MARK: Properties 294 | 295 | var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask } 296 | 297 | var progress: Progress 298 | var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? 299 | 300 | var resumeData: Data? 301 | override var data: Data? { return resumeData } 302 | 303 | var destination: DownloadRequest.DownloadFileDestination? 304 | 305 | var temporaryURL: URL? 306 | var destinationURL: URL? 307 | 308 | var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL } 309 | 310 | // MARK: Lifecycle 311 | 312 | override init(task: URLSessionTask?) { 313 | progress = Progress(totalUnitCount: 0) 314 | super.init(task: task) 315 | } 316 | 317 | override func reset() { 318 | super.reset() 319 | 320 | progress = Progress(totalUnitCount: 0) 321 | resumeData = nil 322 | } 323 | 324 | // MARK: URLSessionDownloadDelegate 325 | 326 | var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)? 327 | var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? 328 | var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? 329 | 330 | func urlSession( 331 | _ session: URLSession, 332 | downloadTask: URLSessionDownloadTask, 333 | didFinishDownloadingTo location: URL) 334 | { 335 | temporaryURL = location 336 | 337 | guard 338 | let destination = destination, 339 | let response = downloadTask.response as? HTTPURLResponse 340 | else { return } 341 | 342 | let result = destination(location, response) 343 | let destinationURL = result.destinationURL 344 | let options = result.options 345 | 346 | self.destinationURL = destinationURL 347 | 348 | do { 349 | if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) { 350 | try FileManager.default.removeItem(at: destinationURL) 351 | } 352 | 353 | if options.contains(.createIntermediateDirectories) { 354 | let directory = destinationURL.deletingLastPathComponent() 355 | try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) 356 | } 357 | 358 | try FileManager.default.moveItem(at: location, to: destinationURL) 359 | } catch { 360 | self.error = error 361 | } 362 | } 363 | 364 | func urlSession( 365 | _ session: URLSession, 366 | downloadTask: URLSessionDownloadTask, 367 | didWriteData bytesWritten: Int64, 368 | totalBytesWritten: Int64, 369 | totalBytesExpectedToWrite: Int64) 370 | { 371 | if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } 372 | 373 | if let downloadTaskDidWriteData = downloadTaskDidWriteData { 374 | downloadTaskDidWriteData( 375 | session, 376 | downloadTask, 377 | bytesWritten, 378 | totalBytesWritten, 379 | totalBytesExpectedToWrite 380 | ) 381 | } else { 382 | progress.totalUnitCount = totalBytesExpectedToWrite 383 | progress.completedUnitCount = totalBytesWritten 384 | 385 | if let progressHandler = progressHandler { 386 | progressHandler.queue.async { progressHandler.closure(self.progress) } 387 | } 388 | } 389 | } 390 | 391 | func urlSession( 392 | _ session: URLSession, 393 | downloadTask: URLSessionDownloadTask, 394 | didResumeAtOffset fileOffset: Int64, 395 | expectedTotalBytes: Int64) 396 | { 397 | if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { 398 | downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) 399 | } else { 400 | progress.totalUnitCount = expectedTotalBytes 401 | progress.completedUnitCount = fileOffset 402 | } 403 | } 404 | } 405 | 406 | // MARK: - 407 | 408 | class UploadTaskDelegate: DataTaskDelegate { 409 | 410 | // MARK: Properties 411 | 412 | var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask } 413 | 414 | var uploadProgress: Progress 415 | var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? 416 | 417 | // MARK: Lifecycle 418 | 419 | override init(task: URLSessionTask?) { 420 | uploadProgress = Progress(totalUnitCount: 0) 421 | super.init(task: task) 422 | } 423 | 424 | override func reset() { 425 | super.reset() 426 | uploadProgress = Progress(totalUnitCount: 0) 427 | } 428 | 429 | // MARK: URLSessionTaskDelegate 430 | 431 | var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? 432 | 433 | func URLSession( 434 | _ session: URLSession, 435 | task: URLSessionTask, 436 | didSendBodyData bytesSent: Int64, 437 | totalBytesSent: Int64, 438 | totalBytesExpectedToSend: Int64) 439 | { 440 | if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } 441 | 442 | if let taskDidSendBodyData = taskDidSendBodyData { 443 | taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) 444 | } else { 445 | uploadProgress.totalUnitCount = totalBytesExpectedToSend 446 | uploadProgress.completedUnitCount = totalBytesSent 447 | 448 | if let uploadProgressHandler = uploadProgressHandler { 449 | uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) } 450 | } 451 | } 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Timeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Responsible for computing the timing metrics for the complete lifecycle of a `Request`. 28 | public struct Timeline { 29 | /// The time the request was initialized. 30 | public let requestStartTime: CFAbsoluteTime 31 | 32 | /// The time the first bytes were received from or sent to the server. 33 | public let initialResponseTime: CFAbsoluteTime 34 | 35 | /// The time when the request was completed. 36 | public let requestCompletedTime: CFAbsoluteTime 37 | 38 | /// The time when the response serialization was completed. 39 | public let serializationCompletedTime: CFAbsoluteTime 40 | 41 | /// The time interval in seconds from the time the request started to the initial response from the server. 42 | public let latency: TimeInterval 43 | 44 | /// The time interval in seconds from the time the request started to the time the request completed. 45 | public let requestDuration: TimeInterval 46 | 47 | /// The time interval in seconds from the time the request completed to the time response serialization completed. 48 | public let serializationDuration: TimeInterval 49 | 50 | /// The time interval in seconds from the time the request started to the time response serialization completed. 51 | public let totalDuration: TimeInterval 52 | 53 | /// Creates a new `Timeline` instance with the specified request times. 54 | /// 55 | /// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`. 56 | /// - parameter initialResponseTime: The time the first bytes were received from or sent to the server. 57 | /// Defaults to `0.0`. 58 | /// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`. 59 | /// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults 60 | /// to `0.0`. 61 | /// 62 | /// - returns: The new `Timeline` instance. 63 | public init( 64 | requestStartTime: CFAbsoluteTime = 0.0, 65 | initialResponseTime: CFAbsoluteTime = 0.0, 66 | requestCompletedTime: CFAbsoluteTime = 0.0, 67 | serializationCompletedTime: CFAbsoluteTime = 0.0) 68 | { 69 | self.requestStartTime = requestStartTime 70 | self.initialResponseTime = initialResponseTime 71 | self.requestCompletedTime = requestCompletedTime 72 | self.serializationCompletedTime = serializationCompletedTime 73 | 74 | self.latency = initialResponseTime - requestStartTime 75 | self.requestDuration = requestCompletedTime - requestStartTime 76 | self.serializationDuration = serializationCompletedTime - requestCompletedTime 77 | self.totalDuration = serializationCompletedTime - requestStartTime 78 | } 79 | } 80 | 81 | // MARK: - CustomStringConvertible 82 | 83 | extension Timeline: CustomStringConvertible { 84 | /// The textual representation used when written to an output stream, which includes the latency, the request 85 | /// duration and the total duration. 86 | public var description: String { 87 | let latency = String(format: "%.3f", self.latency) 88 | let requestDuration = String(format: "%.3f", self.requestDuration) 89 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 90 | let totalDuration = String(format: "%.3f", self.totalDuration) 91 | 92 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 93 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 94 | let timings = [ 95 | "\"Latency\": " + latency + " secs", 96 | "\"Request Duration\": " + requestDuration + " secs", 97 | "\"Serialization Duration\": " + serializationDuration + " secs", 98 | "\"Total Duration\": " + totalDuration + " secs" 99 | ] 100 | 101 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 102 | } 103 | } 104 | 105 | // MARK: - CustomDebugStringConvertible 106 | 107 | extension Timeline: CustomDebugStringConvertible { 108 | /// The textual representation used when written to an output stream, which includes the request start time, the 109 | /// initial response time, the request completed time, the serialization completed time, the latency, the request 110 | /// duration and the total duration. 111 | public var debugDescription: String { 112 | let requestStartTime = String(format: "%.3f", self.requestStartTime) 113 | let initialResponseTime = String(format: "%.3f", self.initialResponseTime) 114 | let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime) 115 | let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime) 116 | let latency = String(format: "%.3f", self.latency) 117 | let requestDuration = String(format: "%.3f", self.requestDuration) 118 | let serializationDuration = String(format: "%.3f", self.serializationDuration) 119 | let totalDuration = String(format: "%.3f", self.totalDuration) 120 | 121 | // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is 122 | // fixed, we should move back to string interpolation by reverting commit 7d4a43b1. 123 | let timings = [ 124 | "\"Request Start Time\": " + requestStartTime, 125 | "\"Initial Response Time\": " + initialResponseTime, 126 | "\"Request Completed Time\": " + requestCompletedTime, 127 | "\"Serialization Completed Time\": " + serializationCompletedTime, 128 | "\"Latency\": " + latency + " secs", 129 | "\"Request Duration\": " + requestDuration + " secs", 130 | "\"Serialization Duration\": " + serializationDuration + " secs", 131 | "\"Total Duration\": " + totalDuration + " secs" 132 | ] 133 | 134 | return "Timeline: { " + timings.joined(separator: ", ") + " }" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DADependencyInjection/Libraries/Alamofire/Validation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validation.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Request { 28 | 29 | // MARK: Helper Types 30 | 31 | fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason 32 | 33 | /// Used to represent whether validation was successful or encountered an error resulting in a failure. 34 | /// 35 | /// - success: The validation was successful. 36 | /// - failure: The validation failed encountering the provided error. 37 | public enum ValidationResult { 38 | case success 39 | case failure(Error) 40 | } 41 | 42 | fileprivate struct MIMEType { 43 | let type: String 44 | let subtype: String 45 | 46 | var isWildcard: Bool { return type == "*" && subtype == "*" } 47 | 48 | init?(_ string: String) { 49 | let components: [String] = { 50 | let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) 51 | let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex) 52 | return split.components(separatedBy: "/") 53 | }() 54 | 55 | if let type = components.first, let subtype = components.last { 56 | self.type = type 57 | self.subtype = subtype 58 | } else { 59 | return nil 60 | } 61 | } 62 | 63 | func matches(_ mime: MIMEType) -> Bool { 64 | switch (type, subtype) { 65 | case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): 66 | return true 67 | default: 68 | return false 69 | } 70 | } 71 | } 72 | 73 | // MARK: Properties 74 | 75 | fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) } 76 | 77 | fileprivate var acceptableContentTypes: [String] { 78 | if let accept = request?.value(forHTTPHeaderField: "Accept") { 79 | return accept.components(separatedBy: ",") 80 | } 81 | 82 | return ["*/*"] 83 | } 84 | 85 | // MARK: Status Code 86 | 87 | fileprivate func validate( 88 | statusCode acceptableStatusCodes: S, 89 | response: HTTPURLResponse) 90 | -> ValidationResult 91 | where S.Iterator.Element == Int 92 | { 93 | if acceptableStatusCodes.contains(response.statusCode) { 94 | return .success 95 | } else { 96 | let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode) 97 | return .failure(AFError.responseValidationFailed(reason: reason)) 98 | } 99 | } 100 | 101 | // MARK: Content Type 102 | 103 | fileprivate func validate( 104 | contentType acceptableContentTypes: S, 105 | response: HTTPURLResponse, 106 | data: Data?) 107 | -> ValidationResult 108 | where S.Iterator.Element == String 109 | { 110 | guard let data = data, data.count > 0 else { return .success } 111 | 112 | guard 113 | let responseContentType = response.mimeType, 114 | let responseMIMEType = MIMEType(responseContentType) 115 | else { 116 | for contentType in acceptableContentTypes { 117 | if let mimeType = MIMEType(contentType), mimeType.isWildcard { 118 | return .success 119 | } 120 | } 121 | 122 | let error: AFError = { 123 | let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes)) 124 | return AFError.responseValidationFailed(reason: reason) 125 | }() 126 | 127 | return .failure(error) 128 | } 129 | 130 | for contentType in acceptableContentTypes { 131 | if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { 132 | return .success 133 | } 134 | } 135 | 136 | let error: AFError = { 137 | let reason: ErrorReason = .unacceptableContentType( 138 | acceptableContentTypes: Array(acceptableContentTypes), 139 | responseContentType: responseContentType 140 | ) 141 | 142 | return AFError.responseValidationFailed(reason: reason) 143 | }() 144 | 145 | return .failure(error) 146 | } 147 | } 148 | 149 | // MARK: - 150 | 151 | extension DataRequest { 152 | /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the 153 | /// request was valid. 154 | public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult 155 | 156 | /// Validates the request, using the specified closure. 157 | /// 158 | /// If validation fails, subsequent calls to response handlers will have an associated error. 159 | /// 160 | /// - parameter validation: A closure to validate the request. 161 | /// 162 | /// - returns: The request. 163 | @discardableResult 164 | public func validate(_ validation: @escaping Validation) -> Self { 165 | let validationExecution: () -> Void = { [unowned self] in 166 | if 167 | let response = self.response, 168 | self.delegate.error == nil, 169 | case let .failure(error) = validation(self.request, response, self.delegate.data) 170 | { 171 | self.delegate.error = error 172 | } 173 | } 174 | 175 | validations.append(validationExecution) 176 | 177 | return self 178 | } 179 | 180 | /// Validates that the response has a status code in the specified sequence. 181 | /// 182 | /// If validation fails, subsequent calls to response handlers will have an associated error. 183 | /// 184 | /// - parameter range: The range of acceptable status codes. 185 | /// 186 | /// - returns: The request. 187 | @discardableResult 188 | public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { 189 | return validate { [unowned self] _, response, _ in 190 | return self.validate(statusCode: acceptableStatusCodes, response: response) 191 | } 192 | } 193 | 194 | /// Validates that the response has a content type in the specified sequence. 195 | /// 196 | /// If validation fails, subsequent calls to response handlers will have an associated error. 197 | /// 198 | /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. 199 | /// 200 | /// - returns: The request. 201 | @discardableResult 202 | public func validate(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String { 203 | return validate { [unowned self] _, response, data in 204 | return self.validate(contentType: acceptableContentTypes, response: response, data: data) 205 | } 206 | } 207 | 208 | /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content 209 | /// type matches any specified in the Accept HTTP header field. 210 | /// 211 | /// If validation fails, subsequent calls to response handlers will have an associated error. 212 | /// 213 | /// - returns: The request. 214 | @discardableResult 215 | public func validate() -> Self { 216 | return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes) 217 | } 218 | } 219 | 220 | // MARK: - 221 | 222 | extension DownloadRequest { 223 | /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a 224 | /// destination URL, and returns whether the request was valid. 225 | public typealias Validation = ( 226 | _ request: URLRequest?, 227 | _ response: HTTPURLResponse, 228 | _ temporaryURL: URL?, 229 | _ destinationURL: URL?) 230 | -> ValidationResult 231 | 232 | /// Validates the request, using the specified closure. 233 | /// 234 | /// If validation fails, subsequent calls to response handlers will have an associated error. 235 | /// 236 | /// - parameter validation: A closure to validate the request. 237 | /// 238 | /// - returns: The request. 239 | @discardableResult 240 | public func validate(_ validation: @escaping Validation) -> Self { 241 | let validationExecution: () -> Void = { [unowned self] in 242 | let request = self.request 243 | let temporaryURL = self.downloadDelegate.temporaryURL 244 | let destinationURL = self.downloadDelegate.destinationURL 245 | 246 | if 247 | let response = self.response, 248 | self.delegate.error == nil, 249 | case let .failure(error) = validation(request, response, temporaryURL, destinationURL) 250 | { 251 | self.delegate.error = error 252 | } 253 | } 254 | 255 | validations.append(validationExecution) 256 | 257 | return self 258 | } 259 | 260 | /// Validates that the response has a status code in the specified sequence. 261 | /// 262 | /// If validation fails, subsequent calls to response handlers will have an associated error. 263 | /// 264 | /// - parameter range: The range of acceptable status codes. 265 | /// 266 | /// - returns: The request. 267 | @discardableResult 268 | public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { 269 | return validate { [unowned self] _, response, _, _ in 270 | return self.validate(statusCode: acceptableStatusCodes, response: response) 271 | } 272 | } 273 | 274 | /// Validates that the response has a content type in the specified sequence. 275 | /// 276 | /// If validation fails, subsequent calls to response handlers will have an associated error. 277 | /// 278 | /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. 279 | /// 280 | /// - returns: The request. 281 | @discardableResult 282 | public func validate(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String { 283 | return validate { [unowned self] _, response, _, _ in 284 | let fileURL = self.downloadDelegate.fileURL 285 | 286 | guard let validFileURL = fileURL else { 287 | return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) 288 | } 289 | 290 | do { 291 | let data = try Data(contentsOf: validFileURL) 292 | return self.validate(contentType: acceptableContentTypes, response: response, data: data) 293 | } catch { 294 | return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL))) 295 | } 296 | } 297 | } 298 | 299 | /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content 300 | /// type matches any specified in the Accept HTTP header field. 301 | /// 302 | /// If validation fails, subsequent calls to response handlers will have an associated error. 303 | /// 304 | /// - returns: The request. 305 | @discardableResult 306 | public func validate() -> Self { 307 | return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes) 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /DADependencyInjection/SiriConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiriConstants.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 21/05/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SiriConstants: String { 12 | case ItemTitle 13 | case ItemDescription 14 | } 15 | -------------------------------------------------------------------------------- /DADependencyInjection/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/02/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Intents 11 | 12 | private enum Constants: String { 13 | case cellID = "ListItemCell" 14 | } 15 | 16 | class ViewController: UIViewController { 17 | 18 | fileprivate var items: [ListDisplayable]? 19 | var dataProvider: ListDisplayableDataProvider = MoviesManager() 20 | 21 | @IBOutlet weak var tableView: UITableView! 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | self.setupRefreshControl() 27 | self.requestSiriAuthorization() 28 | 29 | self.reloadData() 30 | } 31 | 32 | private func setupRefreshControl() { 33 | self.tableView.refreshControl = UIRefreshControl() 34 | self.tableView.refreshControl?.addTarget(self, action: #selector(ViewController.refreshControlAction), for: UIControlEvents.valueChanged) 35 | } 36 | 37 | private func requestSiriAuthorization() { 38 | INPreferences.requestSiriAuthorization { (status) in 39 | switch status { 40 | case .authorized: 41 | print("authorized") 42 | case .denied: 43 | print("denied") 44 | case .notDetermined: 45 | print("notDetermined") 46 | case .restricted: 47 | print("restricted") 48 | } 49 | } 50 | } 51 | 52 | func refreshControlAction() { 53 | self.reloadData() 54 | } 55 | 56 | private func reloadData() { 57 | self.tableView.refreshControl?.beginRefreshing() 58 | dataProvider.getListItems { (items) in 59 | self.tableView.refreshControl?.endRefreshing() 60 | self.items = items 61 | self.tableView.reloadData() 62 | } 63 | } 64 | 65 | @IBAction private func clearAllData() { 66 | self.items = nil 67 | self.tableView.reloadData() 68 | } 69 | } 70 | 71 | extension ViewController: UITableViewDataSource { 72 | 73 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | return items?.count ?? 0 75 | } 76 | 77 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 78 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellID.rawValue) 79 | 80 | if let cellItem = items?[indexPath.row] { 81 | cell?.textLabel?.text = cellItem.listItemTitle 82 | cell?.detailTextLabel?.text = cellItem.listItemSubtitle 83 | } 84 | 85 | return cell! 86 | } 87 | } 88 | 89 | // MARK: Just for testing 90 | extension ViewController { 91 | 92 | // This method is not part of the pattern, we'll use it for testing only 93 | @IBAction func segmentedControlAction(_ sender: UISegmentedControl) { 94 | 95 | guard let moviesManager = dataProvider as? MoviesManager else { 96 | return 97 | } 98 | 99 | if sender.selectedSegmentIndex == 0 { 100 | moviesManager.moviesDataProvider = MoviesDataSource() 101 | moviesManager.moviesDataProvider.networkingProvider = AFNetworkConnector() 102 | } else if sender.selectedSegmentIndex == 1 { 103 | moviesManager.moviesDataProvider = MoviesDataSource() 104 | moviesManager.moviesDataProvider.networkingProvider = NSURLNetworkConnector() 105 | } else if sender.selectedSegmentIndex == 2 { 106 | moviesManager.moviesDataProvider = MoviesDataSource_Operations() 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/Data/TestData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestData.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol TestDataProvider { 12 | init(withFileName fileName: String) 13 | var dataRepresentation: Data? { get } 14 | var jsonRepresentation: Any? { get } 15 | } 16 | 17 | class TestData { 18 | 19 | fileprivate let fileName: NSString 20 | 21 | required init(withFileName fileName: String) { 22 | self.fileName = fileName as NSString 23 | } 24 | } 25 | 26 | // MARK: TestDataSource 27 | extension TestData: TestDataProvider { 28 | public var dataRepresentation: Data? { 29 | get { 30 | let testBundle = Bundle(for: type(of: self)) 31 | let filePath = testBundle.path(forResource: fileName.deletingPathExtension, ofType: fileName.pathExtension) 32 | 33 | if let path = filePath { 34 | let result = FileManager.default.contents(atPath: path) 35 | return result 36 | } 37 | 38 | return nil 39 | } 40 | } 41 | 42 | public var jsonRepresentation: Any? { 43 | get { 44 | guard 45 | let data = dataRepresentation, 46 | let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.allowFragments]) else { 47 | return nil 48 | } 49 | 50 | return jsonObject 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/Data/TestFileNames.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestFiles.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct TestFileNames { 12 | static let PopularMovies = "popularMovies.json" 13 | } 14 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/Data/popularMovies.json: -------------------------------------------------------------------------------- 1 | { 2 | "page":1, 3 | "results":[ 4 | { 5 | "poster_path":"\/tWqifoYuwLETmmasnGHO7xBjEtt.jpg", 6 | "adult":false, 7 | "overview":"A live-action adaptation of Disney's version of the classic 'Beauty and the Beast' tale of a cursed prince and a beautiful young woman who helps him break the spell.", 8 | "release_date":"2017-03-16", 9 | "genre_ids":[ 10 | 14, 11 | 10749 12 | ], 13 | "id":321612, 14 | "original_title":"Beauty and the Beast", 15 | "original_language":"en", 16 | "title":"Beauty and the Beast", 17 | "backdrop_path":"\/6aUWe0GSl69wMTSWWexsorMIvwU.jpg", 18 | "popularity":158.103128, 19 | "vote_count":1824, 20 | "video":false, 21 | "vote_average":6.9 22 | }, 23 | { 24 | "poster_path":"\/unPB1iyEeTBcKiLg8W083rlViFH.jpg", 25 | "adult":false, 26 | "overview":"A story about how a new baby's arrival impacts a family, told from the point of view of a delightfully unreliable narrator, a wildly imaginative 7 year old named Tim.", 27 | "release_date":"2017-03-23", 28 | "genre_ids":[ 29 | 16, 30 | 35, 31 | 10751 32 | ], 33 | "id":295693, 34 | "original_title":"The Boss Baby", 35 | "original_language":"en", 36 | "title":"The Boss Baby", 37 | "backdrop_path":"\/bTFeSwh07oX99ofpDI4O2WkiFJ.jpg", 38 | "popularity":122.933756, 39 | "vote_count":551, 40 | "video":false, 41 | "vote_average":5.7 42 | }, 43 | { 44 | "poster_path":"\/45Y1G5FEgttPAwjTYic6czC9xCn.jpg", 45 | "adult":false, 46 | "overview":"In the near future, a weary Logan cares for an ailing Professor X in a hide out on the Mexican border. But Logan's attempts to hide from the world and his legacy are up-ended when a young mutant arrives, being pursued by dark forces.", 47 | "release_date":"2017-02-28", 48 | "genre_ids":[ 49 | 28, 50 | 18, 51 | 878 52 | ], 53 | "id":263115, 54 | "original_title":"Logan", 55 | "original_language":"en", 56 | "title":"Logan", 57 | "backdrop_path":"\/5pAGnkFYSsFJ99ZxDIYnhQbQFXs.jpg", 58 | "popularity":85.452554, 59 | "vote_count":2491, 60 | "video":false, 61 | "vote_average":7.5 62 | }, 63 | { 64 | "poster_path":"\/s9ye87pvq2IaDvjv9x4IOXVjvA7.jpg", 65 | "adult":false, 66 | "overview":"A koala named Buster recruits his best friend to help him drum up business for his theater by hosting a singing competition.", 67 | "release_date":"2016-11-23", 68 | "genre_ids":[ 69 | 16, 70 | 35, 71 | 18, 72 | 10751, 73 | 10402 74 | ], 75 | "id":335797, 76 | "original_title":"Sing", 77 | "original_language":"en", 78 | "title":"Sing", 79 | "backdrop_path":"\/fxDXp8un4qNY9b1dLd7SH6CKzC.jpg", 80 | "popularity":81.440749, 81 | "vote_count":1192, 82 | "video":false, 83 | "vote_average":6.7 84 | }, 85 | { 86 | "poster_path":"\/r2517Vz9EhDhj88qwbDVj8DCRZN.jpg", 87 | "adult":false, 88 | "overview":"Explore the mysterious and dangerous home of the king of the apes as a team of explorers ventures deep inside the treacherous, primordial island.", 89 | "release_date":"2017-03-08", 90 | "genre_ids":[ 91 | 28, 92 | 12, 93 | 14 94 | ], 95 | "id":293167, 96 | "original_title":"Kong: Skull Island", 97 | "original_language":"en", 98 | "title":"Kong: Skull Island", 99 | "backdrop_path":"\/pGwChWiAY1bdoxL79sXmaFBlYJH.jpg", 100 | "popularity":61.814365, 101 | "vote_count":1241, 102 | "video":false, 103 | "vote_average":6 104 | }, 105 | { 106 | "poster_path":"\/iNpz2DgTsTMPaDRZq2tnbqjL2vF.jpg", 107 | "adult":false, 108 | "overview":"When a mysterious woman seduces Dom into the world of crime and a betrayal of those closest to him, the crew face trials that will test them as never before.", 109 | "release_date":"2017-04-12", 110 | "genre_ids":[ 111 | 28, 112 | 80, 113 | 53 114 | ], 115 | "id":337339, 116 | "original_title":"The Fate of the Furious", 117 | "original_language":"en", 118 | "title":"The Fate of the Furious", 119 | "backdrop_path":"\/jzdnhRhG0dsuYorwvSqPqqnM1cV.jpg", 120 | "popularity":54.434998, 121 | "vote_count":578, 122 | "video":false, 123 | "vote_average":7.1 124 | }, 125 | { 126 | "poster_path":"\/myRzRzCxdfUWjkJWgpHHZ1oGkJd.jpg", 127 | "adult":false, 128 | "overview":"In the near future, Major is the first of her kind: a human saved from a terrible crash, who is cyber-enhanced to be a perfect soldier devoted to stopping the world's most dangerous criminals.", 129 | "release_date":"2017-03-29", 130 | "genre_ids":[ 131 | 28, 132 | 18, 133 | 878 134 | ], 135 | "id":315837, 136 | "original_title":"Ghost in the Shell", 137 | "original_language":"en", 138 | "title":"Ghost in the Shell", 139 | "backdrop_path":"\/lsRhmB7m36pEX0UHpkpJSE48BW5.jpg", 140 | "popularity":52.273404, 141 | "vote_count":616, 142 | "video":false, 143 | "vote_average":6 144 | }, 145 | { 146 | "poster_path":"\/jjBgi2r5cRt36xF6iNUEhzscEcb.jpg", 147 | "adult":false, 148 | "overview":"Twenty-two years after the events of Jurassic Park, Isla Nublar now features a fully functioning dinosaur theme park, Jurassic World, as originally envisioned by John Hammond.", 149 | "release_date":"2015-06-09", 150 | "genre_ids":[ 151 | 28, 152 | 12, 153 | 878, 154 | 53 155 | ], 156 | "id":135397, 157 | "original_title":"Jurassic World", 158 | "original_language":"en", 159 | "title":"Jurassic World", 160 | "backdrop_path":"\/dkMD5qlogeRMiEixC4YNPUvax2T.jpg", 161 | "popularity":47.726583, 162 | "vote_count":6725, 163 | "video":false, 164 | "vote_average":6.5 165 | }, 166 | { 167 | "poster_path":"\/rXMWOZiCt6eMX22jWuTOSdQ98bY.jpg", 168 | "adult":false, 169 | "overview":"Though Kevin has evidenced 23 personalities to his trusted psychiatrist, Dr. Fletcher, there remains one still submerged who is set to materialize and dominate all the others. Compelled to abduct three teenage girls led by the willful, observant Casey, Kevin reaches a war for survival among all of those contained within him — as well as everyone around him — as the walls between his compartments shatter apart.", 170 | "release_date":"2016-11-15", 171 | "genre_ids":[ 172 | 27, 173 | 53 174 | ], 175 | "id":381288, 176 | "original_title":"Split", 177 | "original_language":"en", 178 | "title":"Split", 179 | "backdrop_path":"\/4G6FNNLSIVrwSRZyFs91hQ3lZtD.jpg", 180 | "popularity":38.459995, 181 | "vote_count":1918, 182 | "video":false, 183 | "vote_average":6.8 184 | }, 185 | { 186 | "poster_path":"\/gri0DDxsERr6B2sOR1fGLxLpSLx.jpg", 187 | "adult":false, 188 | "overview":"In 1926, Newt Scamander arrives at the Magical Congress of the United States of America with a magically expanded briefcase, which houses a number of dangerous creatures and their habitats. When the creatures escape from the briefcase, it sends the American wizarding authorities after Newt, and threatens to strain even further the state of magical and non-magical relations.", 189 | "release_date":"2016-11-16", 190 | "genre_ids":[ 191 | 12, 192 | 28, 193 | 14 194 | ], 195 | "id":259316, 196 | "original_title":"Fantastic Beasts and Where to Find Them", 197 | "original_language":"en", 198 | "title":"Fantastic Beasts and Where to Find Them", 199 | "backdrop_path":"\/kMzU4PkXcKcDMngCxXji0BbVXsu.jpg", 200 | "popularity":34.726744, 201 | "vote_count":3375, 202 | "video":false, 203 | "vote_average":7 204 | }, 205 | { 206 | "poster_path":"\/kqjL17yufvn9OVLyXYpvtyrFfak.jpg", 207 | "adult":false, 208 | "overview":"An apocalyptic story set in the furthest reaches of our planet, in a stark desert landscape where humanity is broken, and most everyone is crazed fighting for the necessities of life. Within this world exist two rebels on the run who just might be able to restore order. There's Max, a man of action and a man of few words, who seeks peace of mind following the loss of his wife and child in the aftermath of the chaos. And Furiosa, a woman of action and a woman who believes her path to survival may be achieved if she can make it across the desert back to her childhood homeland.", 209 | "release_date":"2015-05-13", 210 | "genre_ids":[ 211 | 28, 212 | 12, 213 | 878, 214 | 53 215 | ], 216 | "id":76341, 217 | "original_title":"Mad Max: Fury Road", 218 | "original_language":"en", 219 | "title":"Mad Max: Fury Road", 220 | "backdrop_path":"\/phszHPFVhPHhMZgo0fWTKBDQsJA.jpg", 221 | "popularity":30.32121, 222 | "vote_count":7538, 223 | "video":false, 224 | "vote_average":7.2 225 | }, 226 | { 227 | "poster_path":"\/y31QB9kn3XSudA15tV7UWQ9XLuW.jpg", 228 | "adult":false, 229 | "overview":"Light years from Earth, 26 years after being abducted, Peter Quill finds himself the prime target of a manhunt after discovering an orb wanted by Ronan the Accuser.", 230 | "release_date":"2014-07-30", 231 | "genre_ids":[ 232 | 28, 233 | 878, 234 | 12 235 | ], 236 | "id":118340, 237 | "original_title":"Guardians of the Galaxy", 238 | "original_language":"en", 239 | "title":"Guardians of the Galaxy", 240 | "backdrop_path":"\/bHarw8xrmQeqf3t8HpuMY7zoK4x.jpg", 241 | "popularity":30.261692, 242 | "vote_count":6888, 243 | "video":false, 244 | "vote_average":7.9 245 | }, 246 | { 247 | "poster_path":"\/qjiskwlV1qQzRCjpV0cL9pEMF9a.jpg", 248 | "adult":false, 249 | "overview":"A rogue band of resistance fighters unite for a mission to steal the Death Star plans and bring a new hope to the galaxy.", 250 | "release_date":"2016-12-14", 251 | "genre_ids":[ 252 | 28, 253 | 18, 254 | 878, 255 | 10752 256 | ], 257 | "id":330459, 258 | "original_title":"Rogue One: A Star Wars Story", 259 | "original_language":"en", 260 | "title":"Rogue One: A Star Wars Story", 261 | "backdrop_path":"\/tZjVVIYXACV4IIIhXeIM59ytqwS.jpg", 262 | "popularity":29.993022, 263 | "vote_count":3187, 264 | "video":false, 265 | "vote_average":7.3 266 | }, 267 | { 268 | "poster_path":"\/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg", 269 | "adult":false, 270 | "overview":"Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.", 271 | "release_date":"2014-11-05", 272 | "genre_ids":[ 273 | 12, 274 | 18, 275 | 878 276 | ], 277 | "id":157336, 278 | "original_title":"Interstellar", 279 | "original_language":"en", 280 | "title":"Interstellar", 281 | "backdrop_path":"\/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg", 282 | "popularity":29.128103, 283 | "vote_count":8189, 284 | "video":false, 285 | "vote_average":8 286 | }, 287 | { 288 | "poster_path":"\/hLudzvGfpi6JlwUnsNhXwKKg4j.jpg", 289 | "adult":false, 290 | "overview":"Taking place after alien crafts land around the world, an expert linguist is recruited by the military to determine whether they come in peace or are a threat.", 291 | "release_date":"2016-11-10", 292 | "genre_ids":[ 293 | 53, 294 | 18, 295 | 878, 296 | 9648 297 | ], 298 | "id":329865, 299 | "original_title":"Arrival", 300 | "original_language":"en", 301 | "title":"Arrival", 302 | "backdrop_path":"\/yIZ1xendyqKvY3FGeeUYUd5X9Mm.jpg", 303 | "popularity":29.093654, 304 | "vote_count":3572, 305 | "video":false, 306 | "vote_average":6.9 307 | }, 308 | { 309 | "poster_path":"\/5vHssUeVe25bMrof1HyaPyWgaP.jpg", 310 | "adult":false, 311 | "overview":"Ex-hitman John Wick comes out of retirement to track down the gangsters that took everything from him.", 312 | "release_date":"2014-10-22", 313 | "genre_ids":[ 314 | 28, 315 | 53 316 | ], 317 | "id":245891, 318 | "original_title":"John Wick", 319 | "original_language":"en", 320 | "title":"John Wick", 321 | "backdrop_path":"\/mFb0ygcue4ITixDkdr7wm1Tdarx.jpg", 322 | "popularity":25.194248, 323 | "vote_count":3723, 324 | "video":false, 325 | "vote_average":7 326 | }, 327 | { 328 | "poster_path":"\/inVq3FRqcYIRl2la8iZikYYxFNR.jpg", 329 | "adult":false, 330 | "overview":"Based upon Marvel Comics’ most unconventional anti-hero, DEADPOOL tells the origin story of former Special Forces operative turned mercenary Wade Wilson, who after being subjected to a rogue experiment that leaves him with accelerated healing powers, adopts the alter ego Deadpool. Armed with his new abilities and a dark, twisted sense of humor, Deadpool hunts down the man who nearly destroyed his life.", 331 | "release_date":"2016-02-09", 332 | "genre_ids":[ 333 | 28, 334 | 12, 335 | 35, 336 | 10749 337 | ], 338 | "id":293660, 339 | "original_title":"Deadpool", 340 | "original_language":"en", 341 | "title":"Deadpool", 342 | "backdrop_path":"\/n1y094tVDFATSzkTnFxoGZ1qNsG.jpg", 343 | "popularity":24.94861, 344 | "vote_count":7846, 345 | "video":false, 346 | "vote_average":7.3 347 | }, 348 | { 349 | "poster_path":"\/z09QAf8WbZncbitewNk6lKYMZsh.jpg", 350 | "adult":false, 351 | "overview":"Dory is reunited with her friends Nemo and Marlin in the search for answers about her past. What can she remember? Who are her parents? And where did she learn to speak Whale?", 352 | "release_date":"2016-06-16", 353 | "genre_ids":[ 354 | 12, 355 | 16, 356 | 35, 357 | 10751 358 | ], 359 | "id":127380, 360 | "original_title":"Finding Dory", 361 | "original_language":"en", 362 | "title":"Finding Dory", 363 | "backdrop_path":"\/iWRKYHTFlsrxQtfQqFOQyceL83P.jpg", 364 | "popularity":24.257693, 365 | "vote_count":3000, 366 | "video":false, 367 | "vote_average":6.7 368 | }, 369 | { 370 | "poster_path":"\/aqiK5aOZmxvtLcY04B1fFeEJLV1.jpg", 371 | "adult":false, 372 | "overview":"In the fourth installment of the fighting franchise, Boyka is shooting for the big leagues when an accidental death in the ring makes him question everything he stands for. When he finds out the wife of the man he accidentally killed is in trouble, Boyka offers to fight in a series of impossible battles to free her from a life of servitude", 373 | "release_date":"2016-09-22", 374 | "genre_ids":[ 375 | 28 376 | ], 377 | "id":348893, 378 | "original_title":"Boyka: Undisputed IV", 379 | "original_language":"en", 380 | "title":"Boyka: Undisputed IV", 381 | "backdrop_path":"\/cGc5jUgAvcmA9kBCvtNDvlT4LpR.jpg", 382 | "popularity":24.256069, 383 | "vote_count":87, 384 | "video":false, 385 | "vote_average":5.9 386 | }, 387 | { 388 | "poster_path":"\/ylXCdC106IKiarftHkcacasaAcb.jpg", 389 | "adult":false, 390 | "overview":"Mia, an aspiring actress, serves lattes to movie stars in between auditions and Sebastian, a jazz musician, scrapes by playing cocktail party gigs in dingy bars, but as success mounts they are faced with decisions that begin to fray the fragile fabric of their love affair, and the dreams they worked so hard to maintain in each other threaten to rip them apart.", 391 | "release_date":"2016-09-12", 392 | "genre_ids":[ 393 | 35, 394 | 18, 395 | 10402, 396 | 10749 397 | ], 398 | "id":313369, 399 | "original_title":"La La Land", 400 | "original_language":"en", 401 | "title":"La La Land", 402 | "backdrop_path":"\/fp6X6yhgcxzxCpmM0EVC6V9B8XB.jpg", 403 | "popularity":22.160501, 404 | "vote_count":2579, 405 | "video":false, 406 | "vote_average":7.9 407 | } 408 | ], 409 | "total_results":19554, 410 | "total_pages":978 411 | } -------------------------------------------------------------------------------- /DADependencyInjectionTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/MockObjects/MockNetworkProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNetworkProvider.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import DADependencyInjection 11 | 12 | class MockNetworkProvider { 13 | 14 | fileprivate let dataProvider: TestDataProvider 15 | 16 | init(withDataProvider dataProvider: TestDataProvider) { 17 | self.dataProvider = dataProvider 18 | } 19 | } 20 | 21 | // MARK: NetworkingProvider 22 | extension MockNetworkProvider: NetworkingProvider { 23 | func restCall(urlString: String, onCompleted: ((Data?) -> ())?) { 24 | onCompleted?(self.dataProvider.dataRepresentation) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/MoviesDataSourceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesDataSourceTests.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import DADependencyInjection 11 | 12 | class MoviesDataSourceTests: XCTestCase { 13 | 14 | func testGetMovies_Normal_ListOfMovies() { 15 | let testDataProvider = TestData(withFileName: TestFileNames.PopularMovies) 16 | let networkingProvider = MockNetworkProvider(withDataProvider: testDataProvider) 17 | let dataSource = MoviesDataSource(withNetworkingProvider: networkingProvider, andFactory: JSONMoviesFactory()) 18 | var movies: [MovieItem]? 19 | 20 | let getMoviesExpectation = self.expectation(description: "Get Movies Expectation") 21 | dataSource.getMovies { (items) in 22 | movies = items 23 | getMoviesExpectation.fulfill() 24 | } 25 | self.waitForExpectations(timeout: 0.1) { (error) in 26 | guard error == nil else { 27 | XCTFail("Expectation error: \(String(describing: error?.localizedDescription))") 28 | return 29 | } 30 | 31 | XCTAssertNotNil(movies) 32 | XCTAssertTrue(movies?.count == 100) // Operations demo 33 | 34 | let firstMovie = movies?.sorted(){ $0.title > $1.title }.first 35 | XCTAssertEqual(firstMovie?.title, "The Fate of the Furious") 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/MoviesFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesFactoryTests.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import DADependencyInjection 11 | 12 | class MoviesFactoryTests: XCTestCase { 13 | 14 | func testMovieItems_Normal_ItemsArray() { 15 | let testDataProvider = TestData(withFileName: TestFileNames.PopularMovies) 16 | 17 | guard let testJSON = testDataProvider.jsonRepresentation else { 18 | XCTFail("Failed creating a JSON") 19 | return 20 | } 21 | 22 | let moviesFactory = JSONMoviesFactory() 23 | let movies = moviesFactory.movieItems(withJSON: testJSON) 24 | 25 | XCTAssertTrue(movies.count == 20) 26 | 27 | let firstMovie = movies.sorted(){ $0.title > $1.title }.first 28 | XCTAssertEqual(firstMovie?.title, "The Fate of the Furious") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DADependencyInjectionTests/MoviesManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesManagerTests.swift 3 | // DADependencyInjection 4 | // 5 | // Created by Dejan on 19/04/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import DADependencyInjection 11 | 12 | class MoviesManagerTests: XCTestCase { 13 | 14 | private func createManager() -> MoviesManager { 15 | let networkProvider = MockNetworkProvider(withDataProvider: TestData(withFileName: TestFileNames.PopularMovies)) 16 | let dataProvider = MoviesDataSource(withNetworkingProvider: networkProvider, andFactory: JSONMoviesFactory()) 17 | return MoviesManager(withDataProvider: dataProvider) 18 | } 19 | 20 | func testGetListItemsDisplayable_Normal_ListItemsArray() { 21 | let manager = createManager() 22 | 23 | var result: [ListDisplayable]? 24 | 25 | let getItemsExpectation = self.expectation(description: "Get List Items Displayable Expectation") 26 | manager.getListItems { (items) in 27 | result = items 28 | getItemsExpectation.fulfill() 29 | } 30 | self.waitForExpectations(timeout: 0.1) { (error) in 31 | guard error == nil else { 32 | XCTFail("Expectation error: \(String(describing: error?.localizedDescription))") 33 | return 34 | } 35 | 36 | XCTAssertNotNil(result) 37 | 38 | XCTAssertTrue(result?.count == 100) // Operations demo 39 | 40 | let firstItem = result?.sorted(){ $0.listItemTitle > $1.listItemTitle }.first 41 | XCTAssertEqual(firstItem?.listItemTitle, "The Fate of the Furious") 42 | XCTAssertEqual(firstItem?.listItemSubtitle, "When a mysterious woman seduces Dom into the world of crime and a betrayal of those closest to him, the crew face trials that will test them as never before.") 43 | } 44 | } 45 | 46 | func testSearchListItems_NotCached() { 47 | let manager = createManager() 48 | 49 | var result: [ListDisplayable]? 50 | 51 | let searchItemsExpectation = self.expectation(description: "Search List Items Displayable Expectation") 52 | manager.searchListItems(searchTerm: "ghost") { (items) in 53 | result = items 54 | searchItemsExpectation.fulfill() 55 | } 56 | self.waitForExpectations(timeout: 0.1) { (error) in 57 | guard error == nil else { 58 | XCTFail("Expectation error: \(String(describing: error?.localizedDescription))") 59 | return 60 | } 61 | 62 | XCTAssertNotNil(result) 63 | 64 | XCTAssertTrue(result?.count == 5) // Operations demo 65 | 66 | let item = result?.first 67 | XCTAssertEqual(item?.listItemTitle, "Ghost in the Shell") 68 | } 69 | } 70 | 71 | func testSearchListItems_Cached() { 72 | let manager = createManager() 73 | 74 | var result: [ListDisplayable]? 75 | 76 | let getItemsExpectation = self.expectation(description: "Get List Items Displayable Expectation") 77 | manager.getListItems { (items) in 78 | result = items 79 | getItemsExpectation.fulfill() 80 | } 81 | self.waitForExpectations(timeout: 0.1) { (error) in 82 | guard error == nil else { 83 | XCTFail("Expectation error: \(String(describing: error?.localizedDescription))") 84 | return 85 | } 86 | 87 | XCTAssertNotNil(result) 88 | } 89 | 90 | let searchItemsExpectation = self.expectation(description: "Search List Items Displayable Expectation") 91 | manager.searchListItems(searchTerm: "ghost") { (items) in 92 | result = items 93 | searchItemsExpectation.fulfill() 94 | } 95 | self.waitForExpectations(timeout: 0.1) { (error) in 96 | guard error == nil else { 97 | XCTFail("Expectation error: \(String(describing: error?.localizedDescription))") 98 | return 99 | } 100 | 101 | XCTAssertNotNil(result) 102 | 103 | XCTAssertTrue(result?.count == 5) // Operations demo 104 | 105 | let item = result?.first 106 | XCTAssertEqual(item?.listItemTitle, "Ghost in the Shell") 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DADependencyInjection [![Build Status](https://travis-ci.org/dagostini/DADependencyInjection.svg?branch=master)](https://travis-ci.org/dagostini/DADependencyInjection) [![codecov](https://codecov.io/gh/dagostini/DADependencyInjection/branch/master/graph/badge.svg)](https://codecov.io/gh/dagostini/DADependencyInjection) [![codebeat badge](https://codebeat.co/badges/57b159c6-a5e1-42aa-ab0d-a1b2902ff588)](https://codebeat.co/projects/github-com-dagostini-dadependencyinjection-master) 2 | 3 | Example project for blog post at: http://agostini.tech/2017/03/27/using-dependency-injection/ 4 | -------------------------------------------------------------------------------- /SiriTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | SiriTest 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | IntentsRestrictedWhileLocked 28 | 29 | INSearchForPhotosIntent 30 | 31 | IntentsSupported 32 | 33 | INSearchForPhotosIntent 34 | 35 | 36 | NSExtensionPointIdentifier 37 | com.apple.intents-service 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).IntentHandler 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SiriTest/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // SiriTest 4 | // 5 | // Created by Dejan on 18/05/2017. 6 | // Copyright © 2017 Dejan. All rights reserved. 7 | // 8 | 9 | import Intents 10 | 11 | // As an example, this class is set up to handle Message intents. 12 | // You will want to replace this or add other intents as appropriate. 13 | // The intents you wish to handle must be declared in the extension's Info.plist. 14 | 15 | // You can test your example integration by saying things to Siri like: 16 | // "Send a message using " 17 | // " John saying hello" 18 | // "Search for messages in " 19 | 20 | class IntentHandler: INExtension, INSearchForPhotosIntentHandling { 21 | 22 | var dataProvider: ListDisplayableDataProvider = MoviesManager() 23 | 24 | override func handler(for intent: INIntent) -> Any { 25 | return self 26 | } 27 | 28 | func handle(searchForPhotos intent: INSearchForPhotosIntent, completion: @escaping (INSearchForPhotosIntentResponse) -> Void) { 29 | guard let searchTerm = intent.searchTerms?.first else { 30 | let response = INSearchForPhotosIntentResponse(code: .failure, userActivity: nil) 31 | completion(response) 32 | return 33 | } 34 | 35 | dataProvider.searchListItems(searchTerm: searchTerm) { (items) in 36 | let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForPhotosIntent.self)) 37 | if let movie = items.first { 38 | userActivity.userInfo = [SiriConstants.ItemTitle.rawValue: movie.listItemTitle, SiriConstants.ItemDescription.rawValue: movie.listItemSubtitle ?? ""] 39 | userActivity.title = "Search Results" 40 | userActivity.isEligibleForHandoff = true 41 | userActivity.becomeCurrent() 42 | } 43 | 44 | let response = INSearchForPhotosIntentResponse(code: .continueInApp, userActivity: userActivity) 45 | 46 | completion(response) 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "DADependencyInjection/AppDelegate.swift" 3 | - "DADependencyInjection/ViewController.swift" 4 | - "DADependencyInjection/Libraries/.*" 5 | - "DADependencyInjectionTests/.*" 6 | - "**/*ViewController.swift" 7 | --------------------------------------------------------------------------------