SwiftUI學(xué)習(xí)100天(Day51 - 項目 10,第三部分)

原創(chuàng)鏈接:https://www.hackingwithswift.com/100/swiftui
以下內(nèi)容僅供學(xué)習(xí)參考:?

多年前,一家名為 Sun Microsystems 的公司提出了一個遠遠超前于時代的口號:“網(wǎng)絡(luò)就是計算機”。今天,這似乎是顯而易見的:無論身在何處,我們都依靠手機、筆記本電腦甚至手表保持聯(lián)系,因此我們會收到來自世界各地的推送消息、電子郵件、推文等。
想一想:我們今天擁有的 iPhone 得名于 iPod,而 iPod 又得名于 iMac——一款早在 1998 年就推出的產(chǎn)品。Ken Segall 是想出“iMac”這個名字的營銷人員,具體來說,“I”代表“internet”,因為在 90 年代,上網(wǎng)并不像今天那么容易。
因此,我們的 iPhone(我們的互聯(lián)網(wǎng)電話)將網(wǎng)絡(luò)置于其存在的核心位置也就不足為奇了,而且由于幾乎可以保證互聯(lián)網(wǎng)連接,因此許多應(yīng)用程序變得更加豐富和有用。今天,你終于要將網(wǎng)絡(luò)添加到你自己的應(yīng)用程序中,我希望你對 iOS 為我們提供的如此簡單的服務(wù)印象深刻!
今天你只有兩個主題需要完成,你將在其中創(chuàng)建自定義Codable
實現(xiàn),然后使用URLSession
?發(fā)送和接收網(wǎng)絡(luò)數(shù)據(jù)。

為 ObservableObject 類編碼
我們組織了代碼,以便我們有一個Order
對象,可以在我們所有的屏幕之間共享,它的優(yōu)點是我們可以在這些屏幕之間來回移動而不會丟失數(shù)據(jù)。然而,這種方法是有代價的:我們必須對類中的屬性使用@Published
屬性包裝器,一旦這樣做,我們就失去了對Codable
自動
一致性的支持。
如果你不相信我,只需嘗試修改Order
在
Codable
中的定義
,如下所示:
現(xiàn)在構(gòu)建將失敗,因為 Swift 不理解如何編碼和解碼已發(fā)布的屬性。這是一個問題,因為我們想將用戶的訂單提交給互聯(lián)網(wǎng)服務(wù)器,這意味著我們需要它作為 JSON——我們需要Codable
協(xié)議才能工作。
這里的修復(fù)是手動添加Codable
一致性,這意味著告訴 Swift 應(yīng)該編碼什么,應(yīng)該如何編碼,以及應(yīng)該如何解碼——從 JSON 轉(zhuǎn)換回 Swift 數(shù)據(jù)。
第一步意味著添加一個符合 CodingKey
的枚舉
,列出我們要保存的所有屬性。在我們的Order
類中,這幾乎是一切——我們唯一不需要的是靜態(tài)types
屬性。
因此,現(xiàn)在將此枚舉添加到Order
:
第二步要求我們編寫一個encode(to:)
方法,使用我們剛剛創(chuàng)建的編碼鍵枚舉創(chuàng)建一個容器,然后寫出附加到它們各自鍵的所有屬性。這只是encode(_:forKey:)
反復(fù)調(diào)用的問題,每次都傳入不同的屬性和編碼密鑰。
將此方法添加到Order
現(xiàn)在:
因為該方法標(biāo)有throws
,所以我們無需擔(dān)心捕獲內(nèi)部拋出的任何錯誤——我們可以直接使用try
而不添加catch
,因為知道任何問題都會自動向上傳播并在別處處理。
我們的最后一步是實現(xiàn)一個必需的初始化程序,以Order
從一些存檔數(shù)據(jù)中解碼一個實例。這幾乎與編碼相反,甚至受益于相同的throws
功能:
值得在這里補充的是,你可以按照你想要的任何順序?qū)?shù)據(jù)進行編碼——你不需要匹配對象中屬性聲明的順序。
這使我們的代碼完全兼容Codable
:我們有效地繞過了@Published
屬性包裝器,直接讀取和寫入值。然而,它并沒有使我們的代碼編譯——事實上,我們現(xiàn)在在 ContentView.swift 中得到了一個完全不同的錯誤。
現(xiàn)在的問題是我們剛剛為我們的Order
類創(chuàng)建了一個自定義初始化器init(from:)
,而 Swift 希望我們在任何地方都使用它——即使是在我們因為應(yīng)用程序剛剛啟動而只想創(chuàng)建一個新的空訂單的地方。
幸運的是,Swift 允許我們?yōu)橐粋€類添加多個初始化器,這樣我們就可以用多種不同的方式創(chuàng)建它。在這種情況下,這意味著我們需要編寫一個新的初始化程序來創(chuàng)建一個沒有任何數(shù)據(jù)的訂單——它將完全依賴于我們分配的默認屬性值。
因此,現(xiàn)在將這個新的初始化程序添加到Order
:
現(xiàn)在我們的代碼返回編譯,我們的Codable
一致性已經(jīng)完成。這意味著我們已經(jīng)為最后一步做好了準備:Order
通過網(wǎng)絡(luò)發(fā)送和接收對象。



通過互聯(lián)網(wǎng)發(fā)送和接收訂單
iOS 帶有一些用于處理網(wǎng)絡(luò)的出色功能,特別是URLSession
類使發(fā)送和接收數(shù)據(jù)變得異常容易。如果我們將它與Codable
將 Swift 對象與 JSON 相互轉(zhuǎn)換,我們可以使用一個新的URLRequest
結(jié)構(gòu)來準確配置數(shù)據(jù)的發(fā)送方式,在大約 20 行代碼中完成偉大的事情。
首先,讓我們創(chuàng)建一個可以從下訂單按鈕調(diào)用的方法——將其添加到CheckoutView
:
就像我們使用URLSession
下載數(shù)據(jù)一樣
,上傳也是異步完成的。
現(xiàn)在將 Place Order 按鈕修改為:
這段代碼行不通,Swift 很清楚原因:它從不支持并發(fā)的函數(shù)調(diào)用異步函數(shù)。它的意思是我們的按鈕期望能夠立即執(zhí)行它的動作,并且不明白如何等待某些東西——即使我們寫了await placeOrder()
它仍然不會工作,因為按鈕不想等待。
之前我提到過它onAppear()
不適用于這些異步函數(shù),我們需要改用task()
修飾符。這不是一個選項,因為我們正在執(zhí)行一個動作,而不僅僅是附加修飾符,但 Swift 提供了一個替代方案:我們可以憑空創(chuàng)建一個新任務(wù),就像task()
修飾符一樣,這將運行任何類型的異步代碼我們想要。
事實上,它所要做的就是將我們的await
調(diào)用放在一個任務(wù)中,如下所示:
現(xiàn)在我們都準備好了——代碼將placeOrder()
異步調(diào)用就好了。當(dāng)然,該函數(shù)實際上還沒有做任何事情,所以現(xiàn)在讓我們修復(fù)它。
在里面placeOrder()
我們需要做三件事:
將我們當(dāng)前的
order
對象轉(zhuǎn)換成一些可以發(fā)送的 JSON 數(shù)據(jù)。告訴 Swift 如何通過網(wǎng)絡(luò)調(diào)用發(fā)送該數(shù)據(jù)。
運行該請求并處理響應(yīng)。
第一個很簡單,所以讓我們把它寫出來。我們已經(jīng)使Order
類符合Codable
,這意味著我們可以通過在placeOrder()中
添加以下代碼來使用JSONEncoder
來存檔:
第二步意味著使用一個名為 URLRequest
的新類型
,URL
除了為我們提供了添加額外信息(如請求類型、用戶數(shù)據(jù)等)的選項之外,它類似于一個新類型。
我們需要以一種非常特定的方式附加數(shù)據(jù),以便服務(wù)器可以正確處理它,這意味著我們需要提供兩個額外的數(shù)據(jù),而不僅僅是我們的訂單:
請求的 HTTP 方法決定了數(shù)據(jù)應(yīng)該如何發(fā)送。HTTP方法有好幾種,但在實踐中只有GET(“我要讀數(shù)據(jù)”)和POST(“我要寫數(shù)據(jù)”)用得比較多。我們想在這里寫入數(shù)據(jù),所以我們將使用 POST。
請求的內(nèi)容類型決定了發(fā)送的數(shù)據(jù)類型,這會影響服務(wù)器處理我們數(shù)據(jù)的方式。這是在所謂的 MIME 類型中指定的,它最初是為在電子郵件中發(fā)送附件而設(shè)計的,它有幾千個高度特定的選項。
因此,接下來給placeOrder()
的代碼
將是創(chuàng)建一個URLRequest
對象,然后將其配置為使用 HTTP POST 請求發(fā)送 JSON 數(shù)據(jù)。然后我們可以使用它來上傳我們的數(shù)據(jù)URLSession
,并處理返回的任何內(nèi)容。
當(dāng)然,真正的問題是將我們的請求發(fā)送到哪里,我不認為你真的想要設(shè)置自己的 Web 服務(wù)器來學(xué)習(xí)本教程。因此,我們將改用一個名為https://reqres.in的非常有用的網(wǎng)站——它可以讓我們發(fā)送我們想要的任何數(shù)據(jù),并會自動將其發(fā)回。這是制作網(wǎng)絡(luò)代碼原型的好方法,因為你可以從發(fā)送的任何內(nèi)容中獲得真實數(shù)據(jù)。
將此代碼添加到placeOrder()
現(xiàn)在:
第一行包含對URL(string:)
初始化程序的強制解包,這意味著“這將返回一個可選的 URL,但請將其強制為非可選的?!?從字符串創(chuàng)建 URL 可能會失敗,因為你插入了一些亂碼,但我在這里手動輸入了 URL,因此我可以看到它總是正確的——其中沒有可能導(dǎo)致問題的字符串插值。
在這一點上,我們都準備好發(fā)出我們的網(wǎng)絡(luò)請求,我們將使用一個名為的新方法URLSession.shared.upload()
和我們剛剛發(fā)出的 URL 請求來完成。所以,繼續(xù)并將其添加到placeOrder
()
:
現(xiàn)在進行重要的工作:當(dāng)一切正常時,我們需要讀取請求的結(jié)果。如果出現(xiàn)問題——可能是因為沒有互聯(lián)網(wǎng)連接——那么我們的catch
塊將運行,所以我們不必在這里擔(dān)心。
因為我們使用的是 ReqRes.in,所以我們實際上會取回我們發(fā)送的相同訂單,這意味著我們可以使用JSONDecoder
來將其從 JSON 轉(zhuǎn)換回對象。
為確認一切正常,我們將顯示一個包含訂單詳細信息的警報,但我們將使用從 ReqRes.in 返回的解碼訂單。是的,這應(yīng)該與我們發(fā)送的相同,所以如果不是,則意味著我們在編碼時犯了錯誤。
顯示警報需要屬性來存儲消息以及它是否可見,因此現(xiàn)在請將這兩個新屬性添加到CheckoutView
:
我們還需要附加一個alert()
修飾符來監(jiān)視該布爾值,并在其為真時立即顯示警報。在 CheckoutView
中的導(dǎo)航標(biāo)題修飾符下方添加此修飾符
:
現(xiàn)在我們可以完成我們的網(wǎng)絡(luò)代碼:我們將解碼返回的數(shù)據(jù),使用它來設(shè)置我們的確認消息屬性,然后設(shè)置showingConfirmation
為 true 以顯示警報。如果解碼失敗——如果服務(wù)器出于某種原因發(fā)回了一些不是訂單的東西——我們將只打印一條錯誤消息。
將此最終代碼添加到placeOrder()
,替換// handle the result
注釋:
有了最終代碼,我們的網(wǎng)絡(luò)代碼就完成了,實際上我們的應(yīng)用程序也完成了。如果你現(xiàn)在嘗試運行它,你應(yīng)該能夠選擇你想要的確切蛋糕,輸入你的送貨信息,然后按“下訂單”以查看出現(xiàn)的警報!
我們完成了!好吧,我完成了——你還有一些挑戰(zhàn)需要完成!


