Slide 1

Slide 1 text

URLProtocol Ͱελϒͯ͠։ൃ 2017/11/24

Slide 2

Slide 2 text

Introduction

Slide 3

Slide 3 text

ʢલ৬ͰͷʣதؒϨϏϡʔ • ͓٬͞Μʹը໘ϨΠΞ΢τ΍ಈ͖ʢը໘ભҠͳͲʣͷೝ ͕͍ࣝ͋ͬͯΔ͔֬ೝͯ͠΋Β͏ɻ • ͜ͷ࣌఺ͰαʔόαΠυAPI͸ະ׬ • σʔλऔಘʢAPIϦΫΤετʣͷίʔυΛԾ࣮૷͢Δ͕ ຊ࣮૷͢Δ࣌ʹେ෯ͳมߋΛՃ͑ͨ͘ͳ͍ • ͋ͱͰෆཁʹͳΔϞοΫσʔλΛੜ੒͢Δίʔυ΋ॻ͖ ͨ͘ͳ͍

Slide 4

Slide 4 text

#if DEBUG DispatchQueue.global().async { // ϞοΫσʔλΛ࡞੒ let user = User.mockData() // ݁Ռॲཧ } #else let request = URLRequest(url: URL(string: "https://api.test.com/ users/foo")!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in // ݁Ռॲཧ } task.resume() #endif

Slide 5

Slide 5 text

Demo

Slide 6

Slide 6 text

"NS" URLProtocol Swift3Ͱ `Foundation` APIͷ"NS"ϓϨϑΟοΫ ε͕࡟আ͞Ε·͕ͨ͠ɺυΩϡϝϯτʹ͸׬શ ʹ൓ө͞Ε͓ͯΒͣɺ"NS"͕෇͍͍ͯͨΓ෇͍ ͯͳ͔ͬͨΓ͠·͢ɻ

Slide 7

Slide 7 text

URL Loading System

Slide 8

Slide 8 text

URL Loading System The URL loading system is a set of classes and protocols that allow your app to access content referenced by a URL. At the heart of this technology is the NSURL class, which lets your app manipulate URLs and the resources they refer to. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ URLLoadingSystem/URLLoadingSystem.html

Slide 9

Slide 9 text

URL loading system͕αϙʔ τ͍ͯ͠Δϓϩτίϧ • ftp:// • http:// • https:// • file:/// • data:

Slide 10

Slide 10 text

Foundation͕ఏڙ͍ͯ͠ΔΫϥ ε • URLͷίϯςϯπΛϩʔυ • αʔόʹσʔλΛΞοϓϩʔυ • ΫοΩʔετϨʔδͷ؅ཧ • ϨεϙϯεΩϟογϡͷ੍ޚ • ΞϓϦέʔγϣϯݻ༗ͷํ๏ͰͷΫϨσϯγϟϧετϨʔδͱೝ ূͷॲཧ • ΧελϜϓϩτίϧͷ֦ு

Slide 11

Slide 11 text

ϔϧύʔΫϥε

Slide 12

Slide 12 text

ϔϧύʔΫϥε

Slide 13

Slide 13 text

URLProtocol

Slide 14

Slide 14 text

URLProtocol An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports. https://developer.apple.com/documentation/foundation/urlprotocol

Slide 15

Slide 15 text

ͲͷΑ͏ͳ໨తͰ࢖͏͔ • ΧελϜϨεϙϯεΛฦ͢ʢςετ໨తͷελϒʣ • ωοτϫʔΫΛεΩοϓͯ͠ϩʔΧϧσʔλΛฦ͢ʢΩϟο γϡʣ • ϦμΠϨΫτʢϓϩΩγʣ • ϢʔβʔΤʔδΣϯτͷมߋ • ΧελϜϓϩτίϧ https://www.raywenderlich.com/76735/using-nsurlprotocol-swift

Slide 16

Slide 16 text

࢖͍ํ

Slide 17

Slide 17 text

ొ࿥ • URLProtocolͷαϒΫϥεΛ௚઀Πϯελϯε Խ͢Δඞཁ͸ͳ͍ • ࣗ෼Ͱ࡞੒ͨ͠URLProtocolαϒΫϥεΛ࢖͑ ΔΑ͏ʹ registerClass(_:) ΫϥεϝιουΛ ݺͿ

Slide 18

Slide 18 text

ඞ࣮ͣ૷͠ͳ͍ͱ͍͚ͳ͍΋ͷ • class func canInit(with request: URLRequest) - > Bool • class func canonicalRequest(for request: URLRequest) -> URLRequest • func startLoading() • func stopLoading()

Slide 19

Slide 19 text

ࣗ෼͕Α͘ར༻͢Δ࢖͍ํ

Slide 20

Slide 20 text

ͲͷΑ͏ͳ໨తͰ࢖͏͔ • ΧελϜϨεϙϯεΛฦ͢ʢςετ໨తͷελϒʣ • ωοτϫʔΫΛεΩοϓͯ͠ϩʔΧϧσʔλΛฦ͢ʢΩϟο γϡʣ • ϦμΠϨΫτʢϓϩΩγʣ • ϢʔβʔΤʔδΣϯτͷมߋ • ΧελϜϓϩτίϧ https://www.raywenderlich.com/76735/using-nsurlprotocol-swift

Slide 21

Slide 21 text

URLProtocolͷΧελϚΠζ

Slide 22

Slide 22 text

• ϦΫΤετΛϋϯυϦϯά͢Δ৔߹͸ `true` Λฦ͢ • ΠϯελϯεԽ͞Εͯ௨৴ॲཧ΁ • `false`Λฦͨ͠৔߹͸ଞͷURLProtocol΁ॲཧ ͕ҠΔ override class func canInit(with request: URLRequest) -> Bool { return true }

Slide 23

Slide 23 text

• 'canonical'ͳϦΫΤετΛฦ͢ • ඞཁͳ৔߹͸͜͜Ͱ'canonical'ͳܗࣜʹมߋ ͢Δ • ඞཁͳ͚Ε͹ͦͷ··ฦ͢ override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }

Slide 24

Slide 24 text

override func startLoading() { // ϞοΫσʔλΛऔಘʢྫ͑͹Bundle͔ΒJSONσʔλΛऔಘʣ guard let path = Bundle.main.path(forResource: "user", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { // Τϥʔॲཧ } let client = self.client let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Type" : "application/ json;"]) // ϨεϙϯεΛฦ͢ client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed) // σʔλΛฦ͢ client?.urlProtocol(self, didLoad: data) // ௨৴Λऴྃ client?.urlProtocolDidFinishLoading(self) }

Slide 25

Slide 25 text

override func startLoading() { ... // Τϥʔॲཧ let client = self.client let response = HTTPURLResponse(url: request.url!, statusCode: 500, httpVersion: "HTTP/1.1", headerFields: ["Content-Type" : "application/ json;"]) // ϨεϙϯεΛฦ͢ client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed) // ΤϥʔΛ࡞੒ͯ͠௨৴Λऴྃ let error = NSError(domain: "CustomURLProtocolError", code: 10001, userInfo: nil) client?.urlProtocol(self, didFailWithError: error) return .... }

Slide 26

Slide 26 text

URLProtocolClient • URLProtocolͷαϒΫϥε͕"URL Loading System"ͱ΍ΓͱΓ͢Δϓϩτίϧ • func urlProtocol(URLProtocol, didReceive: URLResponse, cacheStoragePolicy: URLCache.StoragePolicy) • func urlProtocol(URLProtocol, didLoad: Data) • func urlProtocolDidFinishLoading(URLProtocol) • func urlProtocol(URLProtocol, didFailWithError: Error) • func urlProtocol(URLProtocol, didReceive: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, didCancel: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, wasRedirectedTo: URLRequest, redirectResponse: URLResponse) • func urlProtocol(URLProtocol, cachedResponseIsValid: CachedURLResponse)

Slide 27

Slide 27 text

URLProtocolClient • URLProtocolͷαϒΫϥε͕"URL Loading System"ͱ΍ΓͱΓ͢Δϓϩτίϧ • func urlProtocol(URLProtocol, didReceive: URLResponse, cacheStoragePolicy: URLCache.StoragePolicy) • func urlProtocol(URLProtocol, didLoad: Data) • func urlProtocolDidFinishLoading(URLProtocol) • func urlProtocol(URLProtocol, didFailWithError: Error) • func urlProtocol(URLProtocol, didReceive: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, didCancel: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, wasRedirectedTo: URLRequest, redirectResponse: URLResponse) • func urlProtocol(URLProtocol, cachedResponseIsValid: CachedURLResponse) ੒ޭ࣌

Slide 28

Slide 28 text

URLProtocolClient • URLProtocolͷαϒΫϥε͕"URL Loading System"ͱ΍ΓͱΓ͢Δϓϩτίϧ • func urlProtocol(URLProtocol, didReceive: URLResponse, cacheStoragePolicy: URLCache.StoragePolicy) • func urlProtocol(URLProtocol, didLoad: Data) • func urlProtocolDidFinishLoading(URLProtocol) • func urlProtocol(URLProtocol, didFailWithError: Error) • func urlProtocol(URLProtocol, didReceive: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, didCancel: URLAuthenticationChallenge) • func urlProtocol(URLProtocol, wasRedirectedTo: URLRequest, redirectResponse: URLResponse) • func urlProtocol(URLProtocol, cachedResponseIsValid: CachedURLResponse) ࣦഊ࣌

Slide 29

Slide 29 text

• ϦΫΤετ͕Ωϟϯηϧ͞Εͨ࣌ʹݺ͹ΕΔ • ผʹURLSessionΛ࢖༻ͯ͠ผͷϦΫΤετΛ ͍ͯͨ͠ΓɺGCDͰඇಉظॲཧΛ͍ͯͨ͠৔ ߹ʹͦΕΒ΋Ωϟϯηϧ͢Δ override func stopLoading() { }

Slide 30

Slide 30 text

ΧελϚΠζͨ͠ URLProtocolΛར༻͢Δ

Slide 31

Slide 31 text

• ྫ͑͹ `application(_: didFinishLaunchingWithOptions:)`Ͱొ࿥ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // ࢖༻ొ࿥ URLProtocol.registerClass(CustomURLProtocol.self) return true } URL Loading Systemʹొ࿥

Slide 32

Slide 32 text

• Կ΋ҙࣝ͠ͳͯ͘ྑ͍ • ϦϦʔε༻ίʔυͦͷ··ͷ࣮૷Ͱྑ͍ let request = URLRequest(url: URL(string: "https://api.test.com/users/ foo")!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in // ݁Ռॲཧ } task.resume() ϦΫΤετॲཧ

Slide 33

Slide 33 text

• ྫ͑͹ίϯύΠϧFlagsͰ࣮૷Λ෼͚Δඞཁ͕ ͳ͍ #if DEBUG let path = Bundle.main.path(forResource: "user", ofType: "json")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) DispatchQueue.global().async { // ݁Ռॲཧ } #else let request = URLRequest(url: URL(string: "https://api.test.com/users/foo")!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in // ݁Ռॲཧ } task.resume() #endif

Slide 34

Slide 34 text

͔͠͠ɺ ͜Ε͸URLSession.sharedΛ ར༻͍ͯ͠Δ৔߹

Slide 35

Slide 35 text

αʔυύʔςΟϥΠϒϥϦͰ ར༻͢Δ

Slide 36

Slide 36 text

• Կ΋ҙࣝ͠ͳͯ͘ྑ͍ • ϦϦʔε༻ίʔυͦͷ··ͷ࣮૷Ͱྑ͍ • ྫ͑͹ίϯύΠϧFlagsͰ࣮૷Λ෼͚Δඞཁ͕ͳ͍ let request = URLRequest(url: URL(string: "https://api.test.com/users/ foo")!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in // ݁Ռॲཧ } task.resume() ϦΫΤετॲཧ

Slide 37

Slide 37 text

• ྫ͑͹ `application(_: didFinishLaunchingWithOptions:)`Ͱొ࿥ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // ࢖༻ొ࿥ URLProtocol.registerClass(CustomURLProtocol.self) return true } URL Loading Systemʹొ࿥

Slide 38

Slide 38 text

URLSession.shared • The shared session uses the shared NSURLCache, NSHTTPCookieStorage, and NSURLCredentialStorage objects, uses a shared custom networking protocol list (configured with registerClass(_:) and unregisterClass(_:)), and is based on a default configuration. • In other words, if you’re doing anything with caches, cookies, authentication, or custom networking protocols, you should probably be using a default session instead of the shared session. https://developer.apple.com/documentation/foundation/urlsession/1409000-shared

Slide 39

Slide 39 text

• URLSession.sharedΛར༻͠ͳ͍৔߹͸ɺΧελϚΠζ ͨ͠URLSessionConfigurationΛར༻͢Δ • αʔυύʔςΟϥΠϒϥϦͰ͸େ఍ URLSessionConfiguration.defaultΛجʹͨ͠URLSession Λར༻͍ͯ͠Δ • URLProtocol.registerClassͰ͸ػೳ͠ͳ͍ URLProtocol.registerClass(CustomURLProtocol.self)

Slide 40

Slide 40 text

• URLSessionConfigurationͷ var protocolClasses: [AnyClass]? ʹઃఆ let config = URLSessionConfiguration.default config.protocolClasses = [CustomURLProtocol.self] let session = URLSession(configuration: config) let task = session.dataTask(with: urlRequest) { (data, response, error) in ... } ಛఆͷϦΫΤετͷΈར༻

Slide 41

Slide 41 text

• ΧελϜURLProtocolΛઃఆͨ͠URLSessionConfigurationΛݩʹ SessionManagerΛੜ੒ͯ͠ར༻ • (஫)࡞੒ͨ͠SessionManager͸Ͳ͔͜Ͱอ͓࣋ͯ͘͠ඞཁ͋Γ let config = URLSessionConfiguration.default config.protocolClasses = [CustomURLProtocol.self] self.manager = SessionManager(configuration: config) // อ࣋͢Δ manager.request(url).response { response in ... } AlamofireͰར༻

Slide 42

Slide 42 text

• ΧελϜURLProtocolΛઃఆͨ͠URLSessionConfigurationΛݩʹ URLSessionAdapterɺSessionΛੜ੒ͯ͠ར༻ let config = URLSessionConfiguration.default config.protocolClasses = [CustomURLProtocol.self] let adapter = URLSessionAdapter(configuration: config) let session = Session(adapter: adapter) session.send(request) { (result) in ... } APIKitͰར༻

Slide 43

Slide 43 text

Ͳ͏͍͏࣌ʹ࢖͏ʁ

Slide 44

Slide 44 text

• ։ൃϑΣʔζͰͷը໘࡞੒ • ·ͩAPI͕࣮૷͞Ε͍ͯͳ͍࣌ʹதؒϨϏϡʔ Ͱ͓٬͞Μʹಈ͖Λݟͯ΋Β͏ɻ • Qiita APIͳͲճ਺੍ݶ͕ܾ·͍ͬͯΔαʔυ ύʔςΟͷAPIΛར༻͢Δ৔߹ʹɺΞΫηεෆ ՄʹͳΒͳ͍Α͏ʹ։ൃத͸μ΢ϯϩʔυ͠ ͓͍ͯͨσʔλΛར༻͢Δ

Slide 45

Slide 45 text

URLProtocolͷ࣮༻ྫ • https://github.com/Alamofire/Alamofire/ blob/master/Tests/URLProtocolTests.swift • https://github.com/ishkawa/APIKit/blob/ master/Tests/APIKitTests/TestComponents/ HTTPStub.swift • https://github.com/AliSoftware/ OHHTTPStubs

Slide 46

Slide 46 text

Appendix • [URL Loading System]: https://developer.apple.com/library/content/ documentation/Cocoa/Conceptual/URLLoadingSystem/ URLLoadingSystem.html • [URLProtocol]: https://developer.apple.com/documentation/foundation/ urlprotocol • [Using NSURLProtocol with Swift]: https://www.raywenderlich.com/76735/ using-nsurlprotocol-swift • [iOS Advent Calendar 2011 5೔໨ / NSURLProtocolͷ࢖͍ํ]: http:// faultier.blog.jp/archives/1762587.html • [Using NSURLProtocol for Testing]: https://yahooeng.tumblr.com/post/ 141143817861/using-nsurlprotocol-for-testing