成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用Swift編寫網(wǎng)絡層:面向協(xié)議方式

移動開發(fā)
在這篇文章中我們會看到怎樣實現(xiàn)用純swift編寫網(wǎng)絡層,而不依靠任何第三方庫。讓我們快去看看吧。相信看完之后我們的代碼能夠做到。

用Swift編寫網(wǎng)絡層:面向協(xié)議方式

在這篇文章中我們會看到怎樣實現(xiàn)用純swift編寫網(wǎng)絡層,而不依靠任何第三方庫。讓我們快去看看吧。相信看完之后我們的代碼能夠做到:

  • 面向協(xié)議
  • 易用
  • 容易實現(xiàn)
  • 類型安全
  • 用枚舉(enums)來配置終端(endPoints)

下面是一個最終我們網(wǎng)絡層的示例

 

2.png
這個項目的最終目標

通過輸入router.request(. 借助枚舉的力量,我們可以看到所有有效的終端和我們請求的參數(shù))

首先,一些結構

創(chuàng)建任何東西之前,有個結構都是很重要的,這樣后面我們就容易找到需要的東西。我堅定相信文件夾結構對軟件架構至關重要。為了讓我們的文件組織有序,讓我們提前建立好所有的組,我會標記好每一個文件該放的位置。這是一個項目結構總覽。(請注意這里的名字僅僅是建議,你可以按你喜好給你的類和組命名)

 

3.png
項目文件夾結構

終端類型(EndPointType)協(xié)議

我們要做的***件事情就是定義我們的終端類型協(xié)議。這個協(xié)議要包含用于配置終端的所有信息。什么是終端?本質(zhì)上來講它是一個包含各種組件比如頭文件(headers),查詢參數(shù)(query parameters),體參數(shù)(body parameters)的URL請求(URLRequest)。終端類型協(xié)議是我們網(wǎng)絡層實現(xiàn)的基石。我們建一個文件,并命名EndPointType,把它放到服務組中(不是終端組,后面我們分清楚的)。

 

4.png
終端類型協(xié)議

HTTP協(xié)議

為了創(chuàng)建一個完整的終端,我的終端類型協(xié)議里有很多HTTP協(xié)議。讓我們看看這些協(xié)議需要什么。

HTTP方法

創(chuàng)建一個名為HTTPMethod的文件并把它放在服務組中。這個枚舉會用于設置我們請求用的HTTP方法。

 

5.png
HTTPMethod枚舉

HTTP任務

創(chuàng)建一個名為HTTPTask的文件并把它放在服務組中。HTTPTask用于為一個特定的終端配置參數(shù),你可以添加適當數(shù)量的案例(cases)到你的網(wǎng)絡層請求中。我會按下圖建立我的請求,它只包含3個案例

 

6.png
HTTPTask枚舉

在下一章我們會討論參數(shù)和如何處理參數(shù)的編碼。

HTTP頭文件

HTTPHeaders是一個字典的別名(typealias)。你可以在你HTTPTask文件的開頭創(chuàng)建它。

  1. public typealias HTTPHeaders = [String:String] 

參數(shù)與編碼

創(chuàng)建一個名為ParameterEncoding的文件并把它放在編碼組中。我們首先要定義一個參數(shù)的別名,通過它我們可以讓代碼更干凈簡潔。

  1. public typealias Parameters = [String:Any

之后用一個靜態(tài)函數(shù)編碼定義一個協(xié)議參數(shù)編碼器(ParameterEncoder)。這種編碼方式含有2個參數(shù),一個inout URLRequest和Parameters。(為了防止混淆,后面我會把函數(shù)參數(shù)稱為參量)。INOUT是一個swift關鍵詞,用于把一個參量定義為引用參量。通常變量作為值類型傳送給函數(shù)。通過在參量的開頭加上inout,我們把它定義為引用類型。要學更多關于雙向參量,你可以點擊這里。參數(shù)編碼器協(xié)議會通過JSONParameterEncoder和URLPameterEncoder實現(xiàn)。

 

  1. public protocol ParameterEncoder { 
  2.  static func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws 

參數(shù)編碼器執(zhí)行編碼參數(shù)的函數(shù),這個方法會失敗,返回一個錯誤,因而我們需要處理它。

能夠返回一個自定的錯誤提示比標準錯誤提示會更有價值。我總是花很多時間去分析Xcode給的一些錯誤提示。有了自定的錯誤提示你就可以定義屬于自己的錯誤信息,就能清楚知道錯誤到底來自哪里。為了做到這些,我創(chuàng)建了一個繼承自Error的枚舉。

 

7.png
NetworkError枚舉

URL參數(shù)編碼器

創(chuàng)建一個名為URLParameterEncoder的文件并把它放在編碼組中。

 

8.png
URL參數(shù)編碼器代碼

上面的代碼含有一些參數(shù),它可以將他們變成URL參數(shù)來安全傳遞。你要知道一些字符在URL中一些字符是禁用的。參數(shù)也被‘&’標記分開,我們需要考慮到所有這些。如果之前沒有設置,我們還要為請求添加合適的頭文件。

這個示例代碼是使用單元測試時應該考慮到的。如果URL沒有正確建立,我們就會有很多不必要的錯誤。如果你在使用一個開放API,你一定不希望自己的請求配額被一堆錯誤測試用完。如果你想學更多關于單元測試內(nèi)容,你可以看S.T.Huang的這篇文章。

JSON參數(shù)編碼器

創(chuàng)建一個名為JSONParameterEncoder的文件,也把它放在編碼組中。

 

9.png
JSON參數(shù)編碼器代碼

類似URL參數(shù)編碼器,不過這里是為JSON編碼參數(shù),同樣要添加合適的頭文件。

網(wǎng)絡路由器

創(chuàng)建一個名為NetworkRouter的文件并把它放在服務組中。我們從為一個完成部分(completion)定義別名開始。

  1. public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->() 

之后我們定義一個協(xié)議網(wǎng)絡路由器

 

10.png
NetworkRouter代碼

一個網(wǎng)絡路由器有一個用于產(chǎn)生請求的終端,一旦請求產(chǎn)生,它會傳遞對完成部分的應答。我加入了一個取消函數(shù),有它當然好,但不是一定要用到。這個函數(shù)可以在一個請求存在周期的任意時刻調(diào)用并取消它。如果你的應用有上傳或下載任務,這會很有用。為了讓我們的路由器能處理任何終端類型,我們這里使用了關聯(lián)類型。如果不用關聯(lián)類型,路由器就不得不有一個具體的終端類型。想對關聯(lián)類型了解更多,建議看NatashaTheRobot的這篇文章。

路由器

創(chuàng)建一個名為Router的文件并把它放在服務組中。我們聲明一個URLSessionTask類型的私有變量任務。這個任務本質(zhì)上是整個工作要做的。我們讓這個變量私有化,因為我們不想任何這個類之外的任何東西會調(diào)整我們的任務。

 

11.png
Router方法存根

請求

這里我們使用共享的會話管理(session)創(chuàng)建URLSession,這是創(chuàng)建URLSession最簡單的辦法,但請記住這不是***的方法。要實現(xiàn)對URLSession更復雜的配置,則要用能夠改變會話管理表現(xiàn)的配置。想了解更多,我推薦讀一讀這篇文章。

這里我們通過調(diào)用buildRequest生成我們的請求,并給它一個終端作為路徑。這個buildRequest的調(diào)用被限制在一個do-try-catch區(qū)塊,因為我們的編碼器可能會報出錯誤。我們僅僅把所有應答,數(shù)據(jù)和錯誤傳送給完成部分。

 

12.png
Request方法代碼

建立請求

在Router中創(chuàng)建一個名為buildRequest的私有函數(shù),這個函數(shù)負責我們網(wǎng)絡層中一切重要工作。本質(zhì)上就是把EndPointType轉化為URLRequest。一旦我們的終端生成請求,我們可以把它傳遞給會話管理。這里有很多工作要做,所以我們將會分別看看每個方法。讓我們分解buildRequest方法:

我們舉了一個URLRequest類型的變量請求的例子。把我們的基礎URL給它,并附上我們要用到的路徑。

我們設定這請求的httpMethod和我們終端的一致。

考慮到我們的編碼器會報告錯誤,我們創(chuàng)建一個do-try-catch區(qū)塊。只要創(chuàng)建一個大的do-try-catch區(qū)塊,我們就不需要為每次嘗試分別建一個。

開啟route.task

根據(jù)任務,調(diào)用合適的編碼器。

 

13.png
buildRequest方法代碼.

配置參數(shù)

在Router中創(chuàng)建一個名為configureParameters的函數(shù)

 

14.png
configureParameters方法的實現(xiàn)

這個函數(shù)負責為我們的參數(shù)編碼。因為我們的API要求所有的bodyParameters都是JSON,并且URLParameters是URL編碼的,我們把合適的參數(shù)傳遞給設計好的編碼器。如果你正在用一個有多種編碼方式的API,我建議修改HTTPTask來使用編碼器枚舉。這個枚舉需要包含所有你需要的不同類型編碼器。之后在configureParameters添加一個關于你編碼枚舉的附加參量。開啟這個枚舉,合適地為參數(shù)編碼。

添加附加頭文件

在Router中創(chuàng)建一個名為addAdditionalHeaders的函數(shù)

 

15.png
addAdditionalHeaders方法的實現(xiàn)

添加所有附加頭文件,讓它們成為請求頭文件的一部分。

取消

取消函數(shù)的實現(xiàn)是這樣的:

 

16.png
cancel方法的實現(xiàn)

實踐

現(xiàn)在讓我們用一個實際例子看看我們建立的網(wǎng)絡層。我們將從TheMovieDB獲取一些電影數(shù)據(jù)到我們的應用。

電影終端(MovieEndPoint)

電影終端與我們在Getting Started with Moya中提到的目標類型很相似。與實現(xiàn)Moya中目標類型不同的是這里我們實現(xiàn)我們自己的終端類型。把這個文件放在終端組中。

 

  1. import Foundation   
  2.   
  3. enum NetworkEnvironment { 
  4.     case qa 
  5.     case production 
  6.     case staging 
  7.   
  8. public enum MovieApi { 
  9.     case recommended(id:Int
  10.     case popular(page:Int
  11.     case newMovies(page:Int
  12.     case video(id:Int
  13.   
  14. extension MovieApi: EndPointType { 
  15.      
  16.     var environmentBaseURL : String { 
  17.         switch NetworkManager.environment { 
  18.         case .production: return "https://api.themoviedb.org/3/movie/" 
  19.         case .qa: return "https://qa.themoviedb.org/3/movie/" 
  20.         case .staging: return "https://staging.themoviedb.org/3/movie/" 
  21.         } 
  22.     } 
  23.       
  24.     var baseURL: URL { 
  25.         guard let url = URL(string: environmentBaseURL) else { fatalError("baseURL could not be configured.")} 
  26.         return url 
  27.     } 
  28.      
  29.     var path: String { 
  30.         switch self { 
  31.         case .recommended(let id): 
  32.             return "\(id)/recommendations" 
  33.         case .popular: 
  34.             return "popular" 
  35.         case .newMovies: 
  36.             return "now_playing" 
  37.         case .video(let id): 
  38.             return "\(id)/videos" 
  39.         } 
  40.     } 
  41.       
  42.     var httpMethod: HTTPMethod { 
  43.         return .get 
  44.     } 
  45.       
  46.     var task: HTTPTask { 
  47.         switch self { 
  48.         case .newMovies(let page): 
  49.             return .requestParameters(bodyParameters: nil, 
  50.                                       urlParameters: ["page":page, 
  51.                                                       "api_key":NetworkManager.MovieAPIKey]) 
  52.         default
  53.             return .request 
  54.         } 
  55.     } 
  56.       
  57.     var headers: HTTPHeaders? { 
  58.         return nil 
  59.     } 

終端類型

電影模式(MovieModel)

因為對TheMovieDB的回應同樣是JSON,我們的電影模式也不會改變。我們用可解碼協(xié)議來把JSON轉化為我們的模式。把這個文件放在模式組中。

 

  1. import Foundation 
  2.   
  3. struct MovieApiResponse { 
  4.     let page: Int 
  5.     let numberOfResults: Int 
  6.     let numberOfPages: Int 
  7.     let movies: [Movie] 
  8.   
  9. extension MovieApiResponse: Decodable { 
  10.      
  11.     private enum MovieApiResponseCodingKeys: String, CodingKey { 
  12.         case page 
  13.         case numberOfResults = "total_results" 
  14.         case numberOfPages = "total_pages" 
  15.         case movies = "results" 
  16.     } 
  17.      
  18.     init(from decoder: Decoder) throws { 
  19.         let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self) 
  20.           
  21.         page = try container.decode(Int.self, forKey: .page) 
  22.         numberOfResults = try container.decode(Int.self, forKey: .numberOfResults) 
  23.         numberOfPages = try container.decode(Int.self, forKey: .numberOfPages) 
  24.         movies = try container.decode([Movie].self, forKey: .movies) 
  25.           
  26.     } 
  27.   
  28.   
  29. struct Movie { 
  30.     let id: Int 
  31.     let posterPath: String 
  32.     let backdrop: String 
  33.     let title: String 
  34.     let releaseDate: String 
  35.     let rating: Double 
  36.     let overview: String 
  37.   
  38. extension Movie: Decodable { 
  39.      
  40.     enum MovieCodingKeys: String, CodingKey { 
  41.         case id 
  42.         case posterPath = "poster_path" 
  43.         case backdrop = "backdrop_path" 
  44.         case title 
  45.         case releaseDate = "release_date" 
  46.         case rating = "vote_average" 
  47.         case overview 
  48.     } 
  49.       
  50.      
  51.     init(from decoder: Decoder) throws { 
  52.         let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self) 
  53.          
  54.         id = try movieContainer.decode(Int.self, forKey: .id) 
  55.         posterPath = try movieContainer.decode(String.self, forKey: .posterPath) 
  56.         backdrop = try movieContainer.decode(String.self, forKey: .backdrop) 
  57.         title = try movieContainer.decode(String.self, forKey: .title) 
  58.         releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate) 
  59.         rating = try movieContainer.decode(Double.self, forKey: .rating) 
  60.         overview = try movieContainer.decode(String.self, forKey: .overview) 
  61.     } 

電影模式

網(wǎng)絡管理員

創(chuàng)建一個名為NetworkManager的文件并把它放在管理員組中。

現(xiàn)在開始我們的網(wǎng)絡管理員將僅有2個靜態(tài)屬性:你的API密碼和網(wǎng)絡環(huán)境(引用MovieEndPoint)。網(wǎng)絡管理員也有一個類型為MovieApi的Router。

 

17.png
NetworkManager代碼

網(wǎng)絡響應

在NetworkManager中創(chuàng)建一個名為NetworkResponse的枚舉。

 

18.png
NetworkResponse枚舉

我們將用這個枚舉處理來自API的響應,并顯示相應的信息。

結果

在NetworkManager中創(chuàng)建一個枚舉Result。

 

19.png
Result枚舉

一個結果枚舉可以用在很多不同事情上,非常有用。我們根據(jù)結果確定我們對API的調(diào)用是成功還是失敗。如果失敗了,我們會返回一個錯誤信息并說明原因。想了解更多面向結果的編程,你可以看這篇對話。

處理網(wǎng)絡響應

創(chuàng)建一個名為handleNetworkResponse的函數(shù),這個函數(shù)有一個參量,即HTTPResponse,并返回一個Result.

 

20.png

這里我們開啟HTTPResponse的狀態(tài)碼,狀態(tài)碼是一個能告訴我們響應狀態(tài)的HTTP協(xié)議?;旧?00-299之間都是成功。

產(chǎn)生調(diào)用

現(xiàn)在我們已經(jīng)為我們的網(wǎng)絡層打下雄厚的基礎。是時候開始調(diào)用了。

我們將會從API獲取一個新電影列表。創(chuàng)建一個名為getNewMovies的函數(shù)。

 

21.png
getNewMovies方法的實現(xiàn)

讓我們分解這個方法的每一步

  1. 我們定義getNewMovies方法含有2個參量:一個頁碼和一個能返回電影數(shù)組或錯誤信息的完成部分(completion)。
  2. 我們調(diào)用我們的路由器,輸入頁碼并在一個閉包(closure)內(nèi)處理這個完成部分。
  3. 如果沒有網(wǎng)絡或者出于一些原因無法調(diào)用API,URLSession會返回錯誤。請注意這并不是API的失敗。這種失敗多是客服端的,很可能是因為網(wǎng)絡連接不好。
  4. 我們需要把我們的響應轉變?yōu)橐粋€HTTPURLResponse,因為我們需要訪問狀態(tài)碼屬性。
  5. 我們聲明一個從handleNetworkResponse方法得到的結果,之后在switch-case區(qū)塊檢查這個結果。
  6. 成功意味著我們成功地和API聯(lián)系,并得到一個適當?shù)捻憫?。之后我們檢查這個響應是否攜帶數(shù)據(jù)。如果沒有數(shù)據(jù)我們就用返回語句退出這個方法。
  7. 如果攜帶有數(shù)據(jù),我們需要把數(shù)據(jù)編碼成我們的模式,之后我們把編碼好的電影傳遞給完成部分。
  8. 如果結果是失敗,我們就把錯誤傳遞給完成部分。

這就完成了,這就是我們不依賴Cocoapods和第三方庫的純Swift網(wǎng)絡層。想要測試api請求能否獲取電影,就創(chuàng)建一個帶有Network Manager 的viewController之后在管理員調(diào)用getNewMovies。

 

  1. class MainViewController: UIViewController { 
  2.      
  3.     var networkManager: NetworkManager! 
  4.       
  5.     init(networkManager: NetworkManager) { 
  6.         super.init(nibName: nil, bundle: nil) 
  7.         self.networkManager = networkManager 
  8.     } 
  9.      
  10.     required init?(coder aDecoder: NSCoder) { 
  11.         fatalError("init(coder:) has not been implemented"
  12.     } 
  13.       
  14.     override func viewDidLoad() { 
  15.         super.viewDidLoad() 
  16.         view.backgroundColor = .green 
  17.         networkManager.getNewMovies(page: 1) { movies, error in 
  18.             if let error = error { 
  19.                 print(error) 
  20.             } 
  21.             if let movies = movies { 
  22.                 print(movies) 
  23.             } 
  24.         } 
  25.     } 

MainViewControoler的示例

迂回網(wǎng)絡(DETOUR- NETWORK)記錄器

我最喜歡的Moya特性之一就是網(wǎng)絡記錄器。它使得調(diào)試變得更容易,并且通過記錄所有網(wǎng)絡通信可以看到關于請求和響應發(fā)生了什么。我決定實現(xiàn)這個網(wǎng)絡層時候就想要有這個特性了。創(chuàng)建一個名為NetworkLogger的文件并把它放在服務組中。我已經(jīng)實現(xiàn)了一個記錄對控制臺請求的代碼。我不會展示我們應該把代碼放到代碼層中的哪里。這是對你的一個挑戰(zhàn),創(chuàng)建一個記錄控制臺響應的函數(shù),并在我們的架構中找到合適的位置放置它們。

提示:靜態(tài)函數(shù)記錄(響應:URLResponse)

小技巧

你在Xcode中遇到過不理解的占位符嗎?比如讓我們看看剛剛為了實現(xiàn)Router寫的代碼

 

22.png

NetworkRouterCompletion是我們實現(xiàn)的。即使我們實現(xiàn)了它,有時候也很難記清它是哪種類型,我們該怎么用它。我們喜歡的Xcode有解決辦法。只要在占位符上雙擊,Xcode就會告訴你。

 

23.png

結論

我們有了一個簡單好用,面向協(xié)議,還可以自己定制的網(wǎng)絡層。我們能完全控制它的功能,完全理解它的機制。通過進行這個練習,我可以說我本人學到不少新事情。所以比起那些只需要裝一個庫就能完成的工作,我對這項工作更感到自豪。希望這篇文章能說明,用Swift創(chuàng)建你自己的網(wǎng)絡層并沒那么難。只要不做這樣的事情就行了:

 

[[228878]]

你可以在我的GitHub上找到源代碼,感謝閱讀。

責任編輯:未麗燕 來源: Malcolm Kumwenda
相關推薦

2015-08-04 08:56:14

swift子類

2015-09-15 10:40:41

Swift2.0MVVM

2018-07-23 15:55:28

協(xié)議自定義viewSwift

2014-06-27 10:04:55

網(wǎng)絡協(xié)議ipv4IP

2010-07-13 13:50:44

HART協(xié)議

2018-09-19 15:53:11

SwiftiOS系統(tǒng)

2010-07-06 16:08:51

HART協(xié)議

2019-01-30 10:18:46

七層協(xié)議網(wǎng)絡通信

2019-04-14 22:33:52

網(wǎng)絡層協(xié)議VLAN虛擬局域網(wǎng)

2022-07-30 23:41:53

面向過程面向對象面向協(xié)議編程

2021-03-11 13:56:13

協(xié)議Python網(wǎng)絡

2010-09-09 16:56:08

七層網(wǎng)絡協(xié)議

2010-06-09 10:28:20

2010-09-09 16:48:50

七層網(wǎng)絡協(xié)議

2011-11-10 09:43:14

ZigBee協(xié)議棧網(wǎng)絡層

2016-12-12 15:22:41

編程

2023-04-06 07:57:29

RPC服務網(wǎng)絡協(xié)議

2019-05-21 09:11:50

七層協(xié)議OSITCP

2010-06-09 12:20:34

網(wǎng)絡通信協(xié)議層

2010-06-10 14:20:23

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日本一区二区三区视频在线 | a欧美 | 国色天香成人网 | 免费一级欧美在线观看视频 | 麻豆久久久 | 久久网站免费视频 | 精品一区二区三区在线视频 | 国产精品成人一区二区 | 日日夜夜天天久久 | 亚洲自拍偷拍免费视频 | 亚洲精品视频在线 | 91精品久久| 久久伊人青青草 | 中文字幕 国产精品 | 精品成人 | 色综合九九 | 粉嫩av久久一区二区三区 | 欧美另类日韩 | 中文字幕免费观看 | 亚洲精品一区二区三区 | 国产在线精品一区二区三区 | 成人精品免费 | 欧美无乱码久久久免费午夜一区 | 亚洲美女一区二区三区 | 亚洲视频在线一区 | 日本精品视频在线 | 国产真实精品久久二三区 | 男女网站在线观看 | 亚洲精品丝袜日韩 | 日韩在线一区二区三区 | 日韩成人在线视频 | 久久亚洲一区 | 日本一区二区三区四区 | 黑人巨大精品欧美一区二区免费 | 亚洲日本乱码在线观看 | 国产精品99久久久久久久久 | 99re6在线视频精品免费 | 亚洲丝袜天堂 | 黄色综合 | 日韩一二三区视频 | 午夜小视频在线播放 |