自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面向協(xié)議編程與 Cocoa 的邂逅 (下)

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
本文 (下) 主要展示了一些筆者日常使用面向協(xié)議思想和 Cocoa 開發(fā)結(jié)合的示例代碼,并對其進(jìn)行了一些解說。

[[403619]]

本文筆者在 MDCC 16 (移動開發(fā)者大會) 上 iOS 專場中的主題演講的文字整理。發(fā)布于 2016年11月29日 最后更新于 2020年10月22日

您可以在這里[1]找到演講使用的 Keynote,部分示例代碼可以在 MDCC 2016 的 官方 repo[2]中找到。

在上半部分[3]主要介紹了一些理論方面的內(nèi)容,包括面向?qū)ο缶幊檀嬖诘膯栴},面向協(xié)議的基本概念和決策模型等。本文 (下) 主要展示了一些筆者日常使用面向協(xié)議思想和 Cocoa 開發(fā)結(jié)合的示例代碼,并對其進(jìn)行了一些解說。

1. 在日常開發(fā)中使用協(xié)議

WWDC 2015 在 POP 方面有一個非常優(yōu)秀的主題演講:#408 Protocol-Oriented Programming in Swift[4]。Apple 的工程師通過舉了畫圖表和排序兩個例子,來闡釋 POP 的思想。我們可以使用 POP 來解耦,通過組合的方式讓代碼有更好的重用性。不過在 #408 中,涉及的內(nèi)容偏向理論,而我們每天的 app 開發(fā)更多的面臨的還是和 Cocoa 框架打交道。在看過 #408 以后,我們就一直在思考,如何把 POP 的思想運(yùn)用到日常的開發(fā)中?

我們在這個部分會舉一個實際的例子,來看看 POP 是如何幫助我們寫出更好的代碼的。

1.1 基于 Protocol 的網(wǎng)絡(luò)請求

網(wǎng)絡(luò)請求層是實踐 POP 的一個理想場所。我們在接下的例子中將從零開始,用最簡單的面向協(xié)議的方式先構(gòu)建一個不那么完美的網(wǎng)絡(luò)請求和模型層,它可能包含一些不合理的設(shè)計和耦合,但是卻是初步最容易得到的結(jié)果。

然后我們將逐步捋清各部分的所屬,并用分離職責(zé)的方式來進(jìn)行重構(gòu)。最后我們會為這個網(wǎng)絡(luò)請求層進(jìn)行測試。通過這個例子,我希望能夠設(shè)計出包括類型安全,解耦合,易于測試和良好的擴(kuò)展性等諸多優(yōu)秀特性在內(nèi)的 POP 代碼。

  • Talk is cheap, show me the code.

1.1.1 初步實現(xiàn)

首先,我們想要做的事情是從一個 API 請求一個 JSON,然后將它轉(zhuǎn)換為 Swift 中可用的實例。返回內(nèi)容:

  1. {"name":"onevcat","message":"Welcome to MDCC 16!"

我們可以新建一個項目,并添加 User.swift 來作為模型:

  1. // User.swift 
  2. import Foundation 
  3.  
  4. struct User { 
  5.     let name: String 
  6.     let message: String 
  7.      
  8.     init?(data: Data) { 
  9.         guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Anyelse { 
  10.             return nil 
  11.         } 
  12.         guard let name = obj?["name"as? String else { 
  13.             return nil 
  14.         } 
  15.         guard let message = obj?["message"as? String else { 
  16.             return nil 
  17.         } 
  18.          
  19.         self.name = name 
  20.         self.message = message 
  21.     } 

User.init(data:) 將輸入的數(shù)據(jù) (從網(wǎng)絡(luò)請求 API 獲取) 解析為 JSON 對象,然后從中取出 name 和 message,并構(gòu)建代表 API 返回的 User 實例,非常簡單。

現(xiàn)在讓我們來看看有趣的部分,也就是如何使用 POP 的方式從 URL 請求數(shù)據(jù),并生成對應(yīng)的 User。首先,我們可以創(chuàng)建一個 protocol 來代表請求。對于一個請求,我們需要知道它的請求路徑,HTTP 方法,所需要的參數(shù)等信息。一開始這個協(xié)議可能是這樣的:

  1. enum HTTPMethod: String { 
  2.     case GET 
  3.     case POST 
  4.  
  5. protocol Request { 
  6.     var host: String { get } 
  7.     var path: String { get } 
  8.      
  9.     var method: HTTPMethod { get } 
  10.     var parameter: [String: Any] { get } 

將 host 和 path 拼接起來可以得到我們需要請求的 API 地址。為了簡化,HTTPMethod 現(xiàn)在只包含了 GET 和 POST 兩種請求方式,而在我們的例子中,我們只會使用到 GET 請求。

現(xiàn)在,可以新建一個 UserRequest 來實現(xiàn) Request 協(xié)議:

  1. struct UserRequest: Request { 
  2.     let name: String 
  3.      
  4.     let host = "https://api.onevcat.com" 
  5.     var path: String { 
  6.         return "/users/\(name)" 
  7.     } 
  8.     let method: HTTPMethod = .GET 
  9.     let parameter: [String: Any] = [:] 

UserRequest 中有一個未定義初始值的 name 屬性,其他的屬性都是為了滿足協(xié)議所定義的。因為請求的參數(shù)用戶名 name 會通過 URL 進(jìn)行傳遞,所以 parameter 是一個空字典就足夠了。有了協(xié)議定義和一個滿足定義的具體請求,現(xiàn)在我們需要發(fā)送請求。為了任意請求都可以通過同樣的方法發(fā)送,我們將發(fā)送的方法定義在 Request 協(xié)議擴(kuò)展上:

  1. extension Request { 
  2.     func send(handler: @escaping (User?) -> Void) { 
  3.         // ... send 的實現(xiàn) 
  4.     } 

在 send(handler:) 的參數(shù)中,我們定義了可逃逸的 (User?) -> Void,在請求完成后,我們調(diào)用這個 handler 方法來通知調(diào)用者請求是否完成,如果一切正常,則將一個 User 實例傳回,否則傳回 nil。

我們想要這個 send 方法對于所有的 Request 都通用,所以顯然回調(diào)的參數(shù)類型不能是 User。通過在 Request 協(xié)議中添加一個關(guān)聯(lián)類型,我們可以將回調(diào)參數(shù)進(jìn)行抽象。在 Request 最后添加:

  1. protocol Request { 
  2.     ... 
  3.     associatedtype Response 

然后在 UserRequest 中,我們也相應(yīng)地添加類型定義,以滿足協(xié)議:

  1. struct UserRequest: Request { 
  2.     ... 
  3.     typealias Response = User 

現(xiàn)在,我們來重新實現(xiàn) send 方法,現(xiàn)在,我們可以用 Response 代替具體的 User,讓 send 一般化。我們這里使用 URLSession 來發(fā)送請求:

  1. extension Request { 
  2.     func send(handler: @escaping (Response?) -> Void) { 
  3.         let url = URL(string: host.appending(path))! 
  4.         var request = URLRequest(url: url) 
  5.         request.httpMethod = method.rawValue 
  6.          
  7.         // 在示例中我們不需要 `httpBody`,實踐中可能需要將 parameter 轉(zhuǎn)為 data 
  8.         // request.httpBody = ... 
  9.          
  10.         let task = URLSession.shared.dataTask(with: request) { 
  11.             data, res, error in 
  12.             // 處理結(jié)果 
  13.             print(data) 
  14.         } 
  15.         task.resume() 
  16.     } 

通過拼接 host 和 path,可以得到 API 的 entry point。根據(jù)這個 URL 創(chuàng)建請求,進(jìn)行配置,生成 data task 并將請求發(fā)送。剩下的工作就是將回調(diào)中的 data 轉(zhuǎn)換為合適的對象類型,并調(diào)用 handler 通知外部調(diào)用者了。對于 User 我們知道可以使用 User.init(data:),但是對于一般的 Response,我們還不知道要如何將數(shù)據(jù)轉(zhuǎn)為模型。我們可以在 Request 里再定義一個 parse(data:) 方法,來要求滿足該協(xié)議的具體類型提供合適的實現(xiàn)。這樣一來,提供轉(zhuǎn)換方法的任務(wù)就被“下放”到了 UserRequest:

  1. protocol Request { 
  2.     ... 
  3.     associatedtype Response 
  4.     func parse(data: Data) -> Response? 
  5.  
  6. struct UserRequest: Request { 
  7.     ... 
  8.     typealias Response = User 
  9.     func parse(data: Data) -> User? { 
  10.         return User(data: data) 
  11.     } 

有了將 data 轉(zhuǎn)換為 Response 的方法后,我們就可以對請求的結(jié)果進(jìn)行處理了:

  1. extension Request { 
  2.     func send(handler: @escaping (Response?) -> Void) { 
  3.         let url = URL(string: host.appending(path))! 
  4.         var request = URLRequest(url: url) 
  5.         request.httpMethod = method.rawValue 
  6.          
  7.         // 在示例中我們不需要 `httpBody`,實踐中可能需要將 parameter 轉(zhuǎn)為 data 
  8.         // request.httpBody = ... 
  9.          
  10.         let task = URLSession.shared.dataTask(with: request) { 
  11.             data, _, error in 
  12.             if let data = data, let res = parse(data: data) { 
  13.                 DispatchQueue.main.async { handler(res) } 
  14.             } else { 
  15.                 DispatchQueue.main.async { handler(nil) } 
  16.             } 
  17.         } 
  18.         task.resume() 
  19.     } 

現(xiàn)在,我們來試試看請求一下這個 API:

  1. let request = UserRequest(name"onevcat"
  2. request.send { user in 
  3.     if let user = user { 
  4.         print("\(user.message) from \(user.name)"
  5.     } 
  6.  
  7. // Welcome to MDCC 16! from onevcat 

1.1.2 重構(gòu),關(guān)注點分離

雖然能夠?qū)崿F(xiàn)需求,但是上面的實現(xiàn)可以說非常糟糕。讓我們看看現(xiàn)在 Request 的定義和擴(kuò)展:

  1. protocol Request { 
  2.     var host: String { get } 
  3.     var path: String { get } 
  4.      
  5.     var method: HTTPMethod { get } 
  6.     var parameter: [String: Any] { get } 
  7.      
  8.     associatedtype Response 
  9.     func parse(data: Data) -> Response? 
  10.  
  11. extension Request { 
  12.     func send(handler: @escaping (Response?) -> Void) { 
  13.         ... 
  14.     } 

這里最大的問題在于,Request 管理了太多的東西。一個 Request 應(yīng)該做的事情應(yīng)該僅僅是定義請求入口和期望的響應(yīng)類型,而現(xiàn)在 Request 不光定義了 host 的值,還對如何解析數(shù)據(jù)了如指掌。最后 send方法被綁死在了 URLSession 的實現(xiàn)上,而且是作為 Request 的一部分存在。

這是很不合理的,因為這意味著我們無法在不更改請求的情況下更新發(fā)送請求的方式,它們被耦合在了一起。這樣的結(jié)構(gòu)讓測試變得異常困難,我們可能需要通過 stub 和 mock 的方式對請求攔截,然后返回構(gòu)造的數(shù)據(jù),這會用到 NSURLProtocol 的內(nèi)容,或者是引入一些第三方的測試框架,大大增加了項目的復(fù)雜度。在 Objective-C 時期這可能是一個可選項,但是在 Swift 的新時代,我們有好得多的方法來處理這件事情。

讓我們開始著手重構(gòu)剛才的代碼,并為它們加上測試吧。首先我們將 send(handler:) 從 Request 分離出來。我們需要一個單獨(dú)的類型來負(fù)責(zé)發(fā)送請求。這里基于 POP 的開發(fā)方式,我們從定義一個可以發(fā)送請求的協(xié)議開始:

  1. protocol Client { 
  2.     func send(_ r: Request, handler: @escaping (Request.Response?) -> Void) 
  3.  
  4. // 編譯錯誤 

從上面的聲明從語義上來說是挺明確的,但是因為 Request 是含有關(guān)聯(lián)類型的協(xié)議,所以它并不能作為獨(dú)立的類型來使用,我們只能夠?qū)⑺鳛轭愋图s束,來限制輸入?yún)?shù) request。正確的聲明方式應(yīng)當(dāng)是:

  1. protocol Client { 
  2.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) 
  3.  
  4.     var host: String { get } 

除了使用 <T: Request> 這個泛型方式以外,我們還將 host 從 Request 移動到了 Client 里,這是更適合它的地方?,F(xiàn)在,我們可以把含有 send 的 Request 協(xié)議擴(kuò)展刪除,重新創(chuàng)建一個類型來滿足 Client 了。和之前一樣,它將使用 URLSession 來發(fā)送請求:

  1. struct URLSessionClient: Client { 
  2.     let host = "https://api.onevcat.com" 
  3.      
  4.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  5.         let url = URL(string: host.appending(r.path))! 
  6.         var request = URLRequest(url: url) 
  7.         request.httpMethod = r.method.rawValue 
  8.          
  9.         let task = URLSession.shared.dataTask(with: request) { 
  10.             data, _, error in 
  11.             if let data = data, let res = r.parse(data: data) { 
  12.                 DispatchQueue.main.async { handler(res) } 
  13.             } else { 
  14.                 DispatchQueue.main.async { handler(nil) } 
  15.             } 
  16.         } 
  17.         task.resume() 
  18.     } 

現(xiàn)在發(fā)送請求的部分和請求本身分離開了,而且我們使用協(xié)議的方式定義了 Client。除了 URLSessionClient 以外,我們還可以使用任意的類型來滿足這個協(xié)議,并發(fā)送請求。這樣網(wǎng)絡(luò)層的具體實現(xiàn)和請求本身就不再相關(guān)了,我們之后在測試的時候會進(jìn)一步看到這么做所帶來的好處。

現(xiàn)在這個的實現(xiàn)里還有一個問題,那就是 Request 的 parse 方法。請求不應(yīng)該也不需要知道如何解析得到的數(shù)據(jù),這項工作應(yīng)該交給 Response 來做。而現(xiàn)在我們沒有對 Response 進(jìn)行任何限定。接下來我們將新增一個協(xié)議,滿足這個協(xié)議的類型將知道如何將一個 data 轉(zhuǎn)換為實際的類型:

  1. protocol Decodable { 
  2.     static func parse(data: Data) -> Self? 

Decodable 定義了一個靜態(tài)的 parse 方法,現(xiàn)在我們需要在 Request 的 Response 關(guān)聯(lián)類型中為它加上這個限制,這樣我們可以保證所有的 Response 都可以對數(shù)據(jù)進(jìn)行解析,原來 Request 中的 parse 聲明也就可以移除了:

  1. // 最終的 Request 協(xié)議 
  2. protocol Request { 
  3.     var path: String { get } 
  4.     var method: HTTPMethod { get } 
  5.     var parameter: [String: Any] { get } 
  6.      
  7.     // associatedtype Response 
  8.     // func parse(data: Data) -> Response? 
  9.     associatedtype Response: Decodable 

最后要做的就是讓 User 滿足 Decodable,并且修改上面 URLSessionClient 的解析部分的代碼,讓它使用 Response 中的 parse 方法:

  1. extension User: Decodable { 
  2.     static func parse(data: Data) -> User? { 
  3.         return User(data: data) 
  4.     } 
  5.  
  6. struct URLSessionClient: Client { 
  7.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  8.         ... 
  9.      // if let data = data, let res = parse(data: data) { 
  10.         if let data = data, let res = T.Response.parse(data: data) { 
  11.             ... 
  12.         } 
  13.     } 

最后,將 UserRequest 中不再需要的 host 和 parse 等清理一下,一個類型安全,解耦合的面向協(xié)議的網(wǎng)絡(luò)層就呈現(xiàn)在我們眼前了。想要調(diào)用 UserRequest 時,我們可以這樣寫:

  1. URLSessionClient().send(UserRequest(name"onevcat")) { user in 
  2.     if let user = user { 
  3.         print("\(user.message) from \(user.name)"
  4.     } 

當(dāng)然,你也可以為 URLSessionClient 添加一個單例來減少請求時的創(chuàng)建開銷,或者為請求添加 Promise 的調(diào)用方式等等。在 POP 的組織下,這些改動都很自然,也不會牽扯到請求的其他部分。你可以用和 UserRequest 類型相似的方式,為網(wǎng)絡(luò)層添加其他的 API 請求,只需要定義請求所必要的內(nèi)容,而不用擔(dān)心會觸及網(wǎng)絡(luò)方面的具體實現(xiàn)。

1.1.3 網(wǎng)絡(luò)層測試

將 Client 聲明為協(xié)議給我們帶來了額外的好處,那就是我們不在局限于使用某種特定的技術(shù) (比如這里的 URLSession) 來實現(xiàn)網(wǎng)絡(luò)請求。利用 POP,你只是定義了一個發(fā)送請求的協(xié)議,你可以很容易地使用像是 AFNetworking 或者 Alamofire 這樣的成熟的第三方框架來構(gòu)建具體的數(shù)據(jù)并處理請求的底層實現(xiàn)。我們甚至可以提供一組“虛假”的對請求的響應(yīng),用來進(jìn)行測試。這和傳統(tǒng)的 stub & mock 的方式在概念上是接近的,但是實現(xiàn)起來要簡單得多,也明確得多。我們現(xiàn)在來看一看具體應(yīng)該怎么做。

我們先準(zhǔn)備一個文本文件,將它添加到項目的測試 target 中,作為網(wǎng)絡(luò)請求返回的內(nèi)容:

  1. // 文件名:users:onevcat 
  2. {"name":"Wei Wang""message""hello"

接下來,可以創(chuàng)建一個新的類型,讓它滿足 Client 協(xié)議。但是與 URLSessionClient 不同,這個新類型的 send 方法并不會實際去創(chuàng)建請求,并發(fā)送給服務(wù)器。我們在測試時需要驗證的是一個請求發(fā)出后如果服務(wù)器按照文檔正確響應(yīng),那么我們應(yīng)該也可以得到正確的模型實例。所以這個新的 Client 需要做的事情就是從本地文件中加載定義好的結(jié)果,然后驗證模型實例是否正確:

  1. struct LocalFileClient: Client { 
  2.     func send<T : Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  3.         switch r.path { 
  4.         case "/users/onevcat"
  5.             guard let fileURL = Bundle(for: ProtocolNetworkTests.self).url(forResource: "users:onevcat", withExtension: ""else { 
  6.                 fatalError() 
  7.             } 
  8.             guard let data = try? Data(contentsOf: fileURL) else { 
  9.                 fatalError() 
  10.             } 
  11.             handler(T.Response.parse(data: data)) 
  12.         default
  13.             fatalError("Unknown path"
  14.         } 
  15.     } 
  16.      
  17.     // 為了滿足 `Client` 的要求,實際我們不會發(fā)送請求 
  18.     let host = "" 

LocalFileClient 做的事情很簡單,它先檢查輸入請求的 path 屬性,如果是 /users/onevcat (也就是我們需要測試的請求),那么就從測試的 bundle 中讀取預(yù)先定義的文件,將其作為返回結(jié)果進(jìn)行 parse,然后調(diào)用 handler。如果我們需要增加其他請求的測試,可以添加新的 case 項。另外,加載本地文件資源的部分應(yīng)該使用更通用的寫法,不過因為我們這里只是示例,就不過多糾結(jié)了。

在 LocalFileClient 的幫助下,現(xiàn)在可以很容易地對 UserRequest 進(jìn)行測試了:

  1. func testUserRequest() { 
  2.     let client = LocalFileClient() 
  3.     client.send(UserRequest(name"onevcat")) { 
  4.         user in 
  5.         XCTAssertNotNil(user
  6.         XCTAssertEqual(user!.name"Wei Wang"
  7.     } 

通過這種方法,我們沒有依賴任何第三方測試庫,也沒有使用 url 代理或者運(yùn)行時消息轉(zhuǎn)發(fā)等等這些復(fù)雜的技術(shù),就可以進(jìn)行請求測試了。保持簡單的代碼和邏輯,對于項目維護(hù)和發(fā)展是至關(guān)重要的

1.1.4 可擴(kuò)展性

因為高度解耦,這種基于 POP 的實現(xiàn)為代碼的擴(kuò)展提供了相對寬松的可能性。我們剛才已經(jīng)說過,你不必自行去實現(xiàn)一個完整的 Client,而可以依賴于現(xiàn)有的網(wǎng)絡(luò)請求框架,實現(xiàn)請求發(fā)送的方法即可。

也就是說,你也可以很容易地將某個正在使用的請求方式替換為另外的方式,而不會影響到請求的定義和使用。類似地,在 Response 的處理上,現(xiàn)在我們定義了 Decodable,用自己手寫的方式在解析模型。我們完全也可以使用任意的第三方 JSON 解析庫,來幫助我們迅速構(gòu)建模型類型,這僅僅只需要實現(xiàn)一個將 Data 轉(zhuǎn)換為對應(yīng)模型類型的方法即可。

如果你對 POP 方式的網(wǎng)絡(luò)請求和模型解析感興趣的話,不妨可以看看 APIKit[5] 這個框架,我們在示例中所展示的方法,正是這個框架的核心思想。

使用協(xié)議幫助改善代碼設(shè)計

通過面向協(xié)議的編程,我們可以從傳統(tǒng)的繼承上解放出來,用一種更靈活的方式,搭積木一樣對程序進(jìn)行組裝。每個協(xié)議專注于自己的功能,特別得益于協(xié)議擴(kuò)展,我們可以減少類和繼承帶來的共享狀態(tài)的風(fēng)險,讓代碼更加清晰。

高度的協(xié)議化有助于解耦、測試以及擴(kuò)展,而結(jié)合泛型來使用協(xié)議,更可以讓我們免于動態(tài)調(diào)用和類型轉(zhuǎn)換的苦惱,保證了代碼的安全性。

提問環(huán)節(jié)

主題演講后有幾位朋友提了一些很有意義的問題,在這里我也稍作整理。有可能問題和回答與當(dāng)時的情形會有小的出入,僅供參考。

我剛才在看 demo 的時候發(fā)現(xiàn),你都是直接先寫 protocol,而不是 struct 或者 class。是不是我們在實踐 POP 的時候都應(yīng)該直接先定義協(xié)議?

  • 我直接寫 protocol 是因為我已經(jīng)對我要做什么有充分的了解,并且希望演講不要超時。但是實際開發(fā)的時候你可能會無法一開始就寫出合適的協(xié)議定義。建議可以像我在 demo 中做的那樣,先“粗略”地進(jìn)行定義,然后通過不斷重構(gòu)來得到一個最終的版本。當(dāng)然,你也可以先用紙筆勾勒一個輪廓,然后再去定義和實現(xiàn)協(xié)議。當(dāng)然了,也沒人規(guī)定一定需要先定義協(xié)議,你完全也可以從普通類型開始寫起,然后等發(fā)現(xiàn)共通點或者遇到我們之前提到的困境時,再回頭看看是不是面向協(xié)議更加合適,這需要一定的 POP 經(jīng)驗。

既然 POP 有這么多好處,那我們是不是不再需要面向?qū)ο?,可以全面轉(zhuǎn)向面向協(xié)議了?

  • 答案可能讓你失望。在我們的日常項目中,每天打交道的 Cocoa 其實還是一個帶有濃厚 OOP 色彩的框架。也就是說,可能一段時期內(nèi)我們不可能拋棄 OOP。不過 POP 其實可以和 OOP “和諧共處”,我們也已經(jīng)看到了不少使用 POP 改善代碼設(shè)計的例子。另外需要補(bǔ)充的是,POP 其實也并不是銀彈,它有不好的一面。最大的問題是協(xié)議會增加代碼的抽象層級 (這點上和類繼承是一樣的),特別是當(dāng)你的協(xié)議又繼承了其他協(xié)議的時候,這個問題尤為嚴(yán)重。在經(jīng)過若干層的繼承后,滿足末端的協(xié)議會變得困難,你也難以確定某個方法究竟?jié)M足的是哪個協(xié)議的要求。這會讓代碼迅速變得復(fù)雜。如果一個協(xié)議并沒有能描述很多共通點,或者說能讓人很快理解的話,可能使用基本的類型還會更簡單一些。

謝謝你的演講,想問一下你們在項目中使用 POP 的情況

  • 我們在項目里用了很多 POP 的概念。上面 demo 里的網(wǎng)絡(luò)請求的例子就是從實際項目中抽出來的,我們覺得這樣的請求寫起來非常輕松,因為代碼很簡單,新人進(jìn)來交接也十分愜意。除了模型層之外,我們在 view 和 view controller 層也用了一些 POP 的代碼,比如從 nib 創(chuàng)建 view 的 NibCreatable,支持分頁請求 tableview controller 的 NextPageLoadable,空列表時顯示頁面的 EmptyPage 等等。因為時間有限,不可能展開一一說明,所以這里我只挑選了一個具有代表性,又不是很復(fù)雜的網(wǎng)絡(luò)的例子。其實每個協(xié)議都讓我們的代碼,特別是 View Controller 變短,而且使測試變?yōu)榭赡堋?梢哉f,我們的項目從 POP 受益良多,而且我們應(yīng)該會繼續(xù)使用下去。

本文轉(zhuǎn)載自微信公眾號「Swift 社區(qū)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Swift 社區(qū)公眾號。

 

責(zé)任編輯:姜華 來源: Swift 社區(qū)
相關(guān)推薦

2021-06-03 08:55:58

面向協(xié)議編程

2016-12-12 15:22:41

編程

2022-07-30 23:41:53

面向過程面向?qū)ο?/a>面向協(xié)議編程

2011-08-11 15:46:55

CocoaCocoa Touch框架

2011-07-08 18:03:30

Cocoa Touch 網(wǎng)絡(luò)

2011-05-11 15:27:58

Windows OOPCocoa MVCCocoa

2018-07-23 15:55:28

協(xié)議自定義viewSwift

2011-07-22 15:50:06

Cocoa MVC 視圖

2015-10-16 09:59:52

SwiftCocoa

2009-04-22 09:20:26

Erlang并發(fā)函數(shù)式

2011-08-15 15:56:29

Cocoa編程模塊

2013-07-30 09:42:41

實現(xiàn)編程接口編程對象編程

2015-03-20 09:54:44

網(wǎng)絡(luò)編程面向連接無連接

2024-01-03 13:38:00

C++面向?qū)ο缶幊?/a>OOP

2009-06-16 15:02:18

面向?qū)ο缶幊?/a>PHP異常PHP代理

2014-05-08 14:13:00

Java面向GC

2010-07-09 11:12:09

UDP協(xié)議

2011-09-07 15:33:33

CocoaiOSObjective-C

2011-07-18 10:03:18

CocoaQt

2011-07-28 18:11:18

Objective-C Cocoa 編程
點贊
收藏

51CTO技術(shù)棧公眾號