在這篇文章中我們會(huì)看到怎樣實(shí)現(xiàn)用純swift編寫(xiě)網(wǎng)絡(luò)層,而不依靠任何第三方庫(kù)。讓我們快去看看吧。相信看完之后我們的代碼能夠做到:
下面是一個(gè)最終我們網(wǎng)絡(luò)層的示例 這個(gè)項(xiàng)目的最終目標(biāo) 通過(guò)輸入router.request(. 借助枚舉的力量,我們可以看到所有有效的終端和我們請(qǐng)求的參數(shù)) 首先,一些結(jié)構(gòu) 創(chuàng)建任何東西之前,有個(gè)結(jié)構(gòu)都是很重要的,這樣后面我們就容易找到需要的東西。我堅(jiān)定相信文件夾結(jié)構(gòu)對(duì)軟件架構(gòu)至關(guān)重要。為了讓我們的文件組織有序,讓我們提前建立好所有的組,我會(huì)標(biāo)記好每一個(gè)文件該放的位置。這是一個(gè)項(xiàng)目結(jié)構(gòu)總覽。(請(qǐng)注意這里的名字僅僅是建議,你可以按你喜好給你的類(lèi)和組命名) 項(xiàng)目文件夾結(jié)構(gòu) 終端類(lèi)型(EndPointType)協(xié)議 我們要做的第一件事情就是定義我們的終端類(lèi)型協(xié)議。這個(gè)協(xié)議要包含用于配置終端的所有信息。什么是終端?本質(zhì)上來(lái)講它是一個(gè)包含各種組件比如頭文件(headers),查詢(xún)參數(shù)(query parameters),體參數(shù)(body parameters)的URL請(qǐng)求(URLRequest)。終端類(lèi)型協(xié)議是我們網(wǎng)絡(luò)層實(shí)現(xiàn)的基石。我們建一個(gè)文件,并命名EndPointType,把它放到服務(wù)組中(不是終端組,后面我們分清楚的)。 終端類(lèi)型協(xié)議 HTTP協(xié)議 為了創(chuàng)建一個(gè)完整的終端,我的終端類(lèi)型協(xié)議里有很多HTTP協(xié)議。讓我們看看這些協(xié)議需要什么。 HTTP方法 創(chuàng)建一個(gè)名為HTTPMethod的文件并把它放在服務(wù)組中。這個(gè)枚舉會(huì)用于設(shè)置我們請(qǐng)求用的HTTP方法。 HTTPMethod枚舉 HTTP任務(wù) 創(chuàng)建一個(gè)名為HTTPTask的文件并把它放在服務(wù)組中。HTTPTask用于為一個(gè)特定的終端配置參數(shù),你可以添加適當(dāng)數(shù)量的案例(cases)到你的網(wǎng)絡(luò)層請(qǐng)求中。我會(huì)按下圖建立我的請(qǐng)求,它只包含3個(gè)案例 HTTPTask 枚舉 在下一章我們會(huì)討論參數(shù)和如何處理參數(shù)的編碼。 HTTP頭文件 HTTPHeaders是一個(gè)字典的別名(typealias)。你可以在你HTTPTask文件的開(kāi)頭創(chuàng)建它。 public typealias HTTPHeaders = [String:String] 參數(shù)與編碼 創(chuàng)建一個(gè)名為ParameterEncoding的文件并把它放在編碼組中。我們首先要定義一個(gè)參數(shù)的別名,通過(guò)它我們可以讓代碼更干凈簡(jiǎn)潔。 public typealias Parameters = [String:Any] 之后用一個(gè)靜態(tài)函數(shù)編碼定義一個(gè)協(xié)議參數(shù)編碼器(ParameterEncoder)。這種編碼方式含有2個(gè)參數(shù),一個(gè)inout URLRequest和Parameters。(為了防止混淆,后面我會(huì)把函數(shù)參數(shù)稱(chēng)為參量)。INOUT是一個(gè)swift關(guān)鍵詞,用于把一個(gè)參量定義為引用參量。通常變量作為值類(lèi)型傳送給函數(shù)。通過(guò)在參量的開(kāi)頭加上inout,我們把它定義為引用類(lèi)型。要學(xué)更多關(guān)于雙向參量,你可以點(diǎn)擊這里。參數(shù)編碼器協(xié)議會(huì)通過(guò)JSONParameterEncoder和URLPameterEncoder實(shí)現(xiàn)。 public protocol ParameterEncoder { 參數(shù)編碼器執(zhí)行編碼參數(shù)的函數(shù),這個(gè)方法會(huì)失敗,返回一個(gè)錯(cuò)誤,因而我們需要處理它。 能夠返回一個(gè)自定的錯(cuò)誤提示比標(biāo)準(zhǔn)錯(cuò)誤提示會(huì)更有價(jià)值。我總是花很多時(shí)間去分析Xcode給的一些錯(cuò)誤提示。有了自定的錯(cuò)誤提示你就可以定義屬于自己的錯(cuò)誤信息,就能清楚知道錯(cuò)誤到底來(lái)自哪里。為了做到這些,我創(chuàng)建了一個(gè)繼承自Error的枚舉。 NetworkError枚舉 URL參數(shù)編碼器(URLParameterEncoder) 創(chuàng)建一個(gè)名為URLParameterEncoder的文件并把它放在編碼組中。 URL參數(shù)編碼器代碼 上面的代碼含有一些參數(shù),它可以將他們變成URL參數(shù)來(lái)安全傳遞。你要知道一些字符在URL中一些字符是禁用的。參數(shù)也被‘&’標(biāo)記分開(kāi),我們需要考慮到所有這些。如果之前沒(méi)有設(shè)置,我們還要為請(qǐng)求添加合適的頭文件。 這個(gè)示例代碼是使用單元測(cè)試時(shí)應(yīng)該考慮到的。如果URL沒(méi)有正確建立,我們就會(huì)有很多不必要的錯(cuò)誤。如果你在使用一個(gè)開(kāi)放API,你一定不希望自己的請(qǐng)求配額被一堆錯(cuò)誤測(cè)試用完。 JSON參數(shù)編碼器(JSONParameterEncoder) 創(chuàng)建一個(gè)名為JSONParameterEncoder的文件,也把它放在編碼組中。 JSON參數(shù)編碼器代碼 類(lèi)似URL參數(shù)編碼器,不過(guò)這里是為JSON編碼參數(shù),同樣要添加合適的頭文件。 網(wǎng)絡(luò)路由器(NetworkRouter) 創(chuàng)建一個(gè)名為NetworkRouter的文件并把它放在服務(wù)組中。我們從為一個(gè)完成部分(completion)定義別名開(kāi)始。 public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->() 之后我們定義一個(gè)協(xié)議網(wǎng)絡(luò)路由器 NetworkRouter代碼 一個(gè)網(wǎng)絡(luò)路由器有一個(gè)用于產(chǎn)生請(qǐng)求的終端,一旦請(qǐng)求產(chǎn)生,它會(huì)傳遞對(duì)完成部分的應(yīng)答。我加入了一個(gè)取消函數(shù),有它當(dāng)然好,但不是一定要用到。這個(gè)函數(shù)可以在一個(gè)請(qǐng)求存在周期的任意時(shí)刻調(diào)用并取消它。如果你的應(yīng)用有上傳或下載任務(wù),這會(huì)很有用。為了讓我們的路由器能處理任何終端類(lèi)型,我們這里使用了關(guān)聯(lián)類(lèi)型。如果不用關(guān)聯(lián)類(lèi)型,路由器就不得不有一個(gè)具體的終端類(lèi)型。 路由器 創(chuàng)建一個(gè)名為Router的文件并把它放在服務(wù)組中。我們聲明一個(gè)URLSessionTask類(lèi)型的私有變量任務(wù)。這個(gè)任務(wù)本質(zhì)上是整個(gè)工作要做的。我們讓這個(gè)變量私有化,因?yàn)槲覀儾幌肴魏芜@個(gè)類(lèi)之外的任何東西會(huì)調(diào)整我們的任務(wù)。 Router方法存根 請(qǐng)求 這里我們使用共享的會(huì)話(huà)管理(session)創(chuàng)建URLSession,這是創(chuàng)建URLSession最簡(jiǎn)單的辦法,但請(qǐng)記住這不是唯一的方法。要實(shí)現(xiàn)對(duì)URLSession更復(fù)雜的配置,則要用能夠改變會(huì)話(huà)管理表現(xiàn)的配置。想了解更多,我推薦讀一讀這篇文章。 這里我們通過(guò)調(diào)用buildRequest生成我們的請(qǐng)求,并給它一個(gè)終端作為路徑。這個(gè)buildRequest的調(diào)用被限制在一個(gè)do-try-catch區(qū)塊,因?yàn)槲覀兊木幋a器可能會(huì)報(bào)出錯(cuò)誤。我們僅僅把所有應(yīng)答,數(shù)據(jù)和錯(cuò)誤傳送給完成部分。 Request方法代碼 建立請(qǐng)求 在Router中創(chuàng)建一個(gè)名為buildRequest的私有函數(shù),這個(gè)函數(shù)負(fù)責(zé)我們網(wǎng)絡(luò)層中一切重要工作。本質(zhì)上就是把EndPointType轉(zhuǎn)化為URLRequest。一旦我們的終端生成請(qǐng)求,我們可以把它傳遞給會(huì)話(huà)管理。這里有很多工作要做,所以我們將會(huì)分別看看每個(gè)方法。讓我們分解buildRequest方法:
buildRequest方法代碼. 配置參數(shù) 在Router中創(chuàng)建一個(gè)名為configureParameters的函數(shù) configureParameters方法的實(shí)現(xiàn) 這個(gè)函數(shù)負(fù)責(zé)為我們的參數(shù)編碼。因?yàn)槲覀兊腁PI要求所有的bodyParameters都是JSON,并且URLParameters是URL編碼的,我們把合適的參數(shù)傳遞給設(shè)計(jì)好的編碼器。如果你正在用一個(gè)有多種編碼方式的API,我建議修改HTTPTask來(lái)使用編碼器枚舉。這個(gè)枚舉需要包含所有你需要的不同類(lèi)型編碼器。之后在configureParameters添加一個(gè)關(guān)于你編碼枚舉的附加參量。開(kāi)啟這個(gè)枚舉,合適地為參數(shù)編碼。 添加附加頭文件 在Router中創(chuàng)建一個(gè)名為addAdditionalHeaders的函數(shù) addAdditionalHeaders方法的實(shí)現(xiàn) 添加所有附加頭文件,讓它們成為請(qǐng)求頭文件的一部分。 取消 取消函數(shù)的實(shí)現(xiàn)是這樣的: Cancel方法的實(shí)現(xiàn) 實(shí)踐 現(xiàn)在讓我們用一個(gè)實(shí)際例子看看我們建立的網(wǎng)絡(luò)層。我們將從TheMovieDB獲取一些電影數(shù)據(jù)到我們的應(yīng)用。 電影終端(MovieEndPoint) 電影終端與我們?cè)?a href='https:///flawless-app-stories/getting-started-with-moya-f559c406e990' target='_blank'>Getting Started with Moya中提到的目標(biāo)類(lèi)型很相似。與實(shí)現(xiàn)Moya中目標(biāo)類(lèi)型不同的是這里我們實(shí)現(xiàn)我們自己的終端類(lèi)型。把這個(gè)文件放在終端組中。 import Foundation 終端類(lèi)型 電影模式(MovieModel) 因?yàn)閷?duì)TheMovieDB的回應(yīng)同樣是JSON,我們的電影模式也不會(huì)改變。我們用可解碼協(xié)議來(lái)把JSON轉(zhuǎn)化為我們的模式。把這個(gè)文件放在模式組中。 import Foundation 電影模式 網(wǎng)絡(luò)管理員 創(chuàng)建一個(gè)名為NetworkManager的文件并把它放在管理員組中。 現(xiàn)在開(kāi)始我們的網(wǎng)絡(luò)管理員將僅有2個(gè)靜態(tài)屬性:你的API密碼和網(wǎng)絡(luò)環(huán)境(引用MovieEndPoint)。網(wǎng)絡(luò)管理員也有一個(gè)類(lèi)型為MovieApi的Router。 網(wǎng)絡(luò)管理員代碼 網(wǎng)絡(luò)響應(yīng) 在NetworkManager中創(chuàng)建一個(gè)名為NetworkResponse的枚舉。 NetworkResponse枚舉 我們將用這個(gè)枚舉處理來(lái)自API的響應(yīng),并顯示相應(yīng)的信息。 結(jié)果 在NetworkManager中創(chuàng)建一個(gè)枚舉Result。 Result枚舉 一個(gè)結(jié)果枚舉可以用在很多不同事情上,非常有用。我們根據(jù)結(jié)果確定我們對(duì)API的調(diào)用是成功還是失敗。如果失敗了,我們會(huì)返回一個(gè)錯(cuò)誤信息并說(shuō)明原因。 處理網(wǎng)絡(luò)響應(yīng) 創(chuàng)建一個(gè)名為handleNetworkResponse的函數(shù),這個(gè)函數(shù)有一個(gè)參量,即HTTPResponse,并返回一個(gè)Result 這里我們開(kāi)啟HTTPResponse的狀態(tài)碼,狀態(tài)碼是一個(gè)能告訴我們響應(yīng)狀態(tài)的HTTP協(xié)議。基本上200-299之間都是成功。更多關(guān)于狀態(tài)碼點(diǎn)擊這里。 產(chǎn)生調(diào)用 現(xiàn)在我們已經(jīng)為我們的網(wǎng)絡(luò)層打下雄厚的基礎(chǔ)。是時(shí)候開(kāi)始調(diào)用了。 我們將會(huì)從API獲取一個(gè)新電影列表。創(chuàng)建一個(gè)名為getNewMovies的函數(shù)。 getNewMovies方法的實(shí)現(xiàn) 讓我們分解這個(gè)方法的每一步
這就完成了,這就是我們不依賴(lài)Cocoapods和第三方庫(kù)的純Swift網(wǎng)絡(luò)層。想要測(cè)試api請(qǐng)求能否獲取電影,就創(chuàng)建一個(gè)帶有Network Manager 的viewController之后在管理員調(diào)用getNewMovies。 class MainViewController: UIViewController { MainViewControoler的示例 迂回網(wǎng)絡(luò)(DETOUR- NETWORK)記錄器 我最喜歡的Moya特性之一就是網(wǎng)絡(luò)記錄器。它使得調(diào)試變得更容易,并且通過(guò)記錄所有網(wǎng)絡(luò)通信可以看到關(guān)于請(qǐng)求和響應(yīng)發(fā)生了什么。我決定實(shí)現(xiàn)這個(gè)網(wǎng)絡(luò)層時(shí)候就想要有這個(gè)特性了。創(chuàng)建一個(gè)名為NetworkLogger的文件并把它放在服務(wù)組中。我已經(jīng)實(shí)現(xiàn)了一個(gè)記錄對(duì)控制臺(tái)請(qǐng)求的代碼。我不會(huì)展示我們應(yīng)該把代碼放到代碼層中的哪里。這是對(duì)你的一個(gè)挑戰(zhàn),創(chuàng)建一個(gè)記錄控制臺(tái)響應(yīng)的函數(shù),并在我們的架構(gòu)中找到合適的位置放置它們。 提示:靜態(tài)函數(shù)記錄(響應(yīng):URLResponse) 小技巧 你在Xcode中遇到過(guò)不理解的占位符嗎?比如讓我們看看剛剛為了實(shí)現(xiàn)Router寫(xiě)的代碼 NetworkRouterCompletion是我們實(shí)現(xiàn)的。即使我們實(shí)現(xiàn)了它,有時(shí)候也很難記清它是哪種類(lèi)型,我們?cè)撛趺从盟?。我們喜歡的Xcode有解決辦法。只要在占位符上雙擊,Xcode就會(huì)告訴你。 結(jié)論 我們有了一個(gè)簡(jiǎn)單好用,面向協(xié)議,還可以自己定制的網(wǎng)絡(luò)層。我們能完全控制它的功能,完全理解它的機(jī)制。通過(guò)進(jìn)行這個(gè)練習(xí),我可以說(shuō)我本人學(xué)到不少新事情。所以比起那些只需要裝一個(gè)庫(kù)就能完成的工作,我對(duì)這項(xiàng)工作更感到自豪。希望這篇文章能說(shuō)明,用Swift創(chuàng)建你自己的網(wǎng)絡(luò)層并沒(méi)那么難。只要不做這樣的事情就行了: 你可以在我的GitHub上找到源代碼,感謝閱讀。 相關(guān)鏈接:
|
|