技術筆記-iOS實戰002-搞定 api 非同步呼叫,json 解碼,和一些檔案儲存的事

更新於 發佈於 閱讀時間約 10 分鐘

需求情境:

在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。

每次畫面一點的小修改,都會觸發 preview 重跑

每次畫面一點的小修改,都會觸發 preview 重跑


解決方案:

將後台傳回的資料以檔案形式暫存在本地端,每次 preview 都呼叫本地端檔案以節省時間和資源。再加上一個配套措施,就是由一個按鈕,觸發真正呼叫後台的程序,而呼叫後順便更新本地端檔案。以下開始實作。


實作步驟:

首先探討呼叫 api 的程序,檢討上一篇「iOS實戰001」所提的方法,有一個缺點,就是程序和 UI 元件混在一起,導致呼叫邏輯無法抽離到另一個檔案。因爲這是專案中經常會用到的通用程序,抽離才可以更好的共用,所以改用 async await 語法改寫,讓程序傳回通用型別 (Data) 的值:

func fetchApiDataAsync(urlString: String) async throws -> Data {
    guard let url = URL(string: urlString)
    else {
        throw MyError.error1
    }
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw MyError.error1
    }
    return data
}

這樣在任何一個畫面需要呼叫後台時,都可以在 onAppear() 事件中執行以下程序,取得資料後再執行與畫面相依的 decode 和 bind。以下 dashItems 就是宣告在畫面的狀態變數:

func getDashItems(urlString: String) {
    Task {
        do {
            let data = try await fetchApiDataAsync(urlString: urlString)
            let decoder = JSONDecoder()
            let dashItems = try decoder.decode([DashItem].self, from: data)                
            self.dashItems = dashItems // 此行把資料 bind 到畫面上
        } catch {
            print("Fail to fetch api data")
        }
    }
}


為了把回傳資料取出,用最原始的文字檔方式儲存在檔案,我們需要把 Data 的內容解開,轉換成為 String,我們把它包裝成另一個 function:

func fetchApiRawDataAsync(urlString: String) async throws -> String {
    guard let url = URL(string: urlString)
    else {
        throw MyError.error1
    }
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw MyError.error1
    }
    let rawString = String(data: data, encoding: .utf8)! // 關鍵是這一行
    return rawString
}


解開 String 之後,就可以存在檔案裡了,檔案儲存的位置有學問。依照 iOS 的運行規則,應用程式只可以把檔案存在一個特殊的資料夾稱為 DocumentDirectory,可用系統內建管理物件 FileManager 取得,我們把它包裝成另一個函數 getDocumentDirectory() ,供儲存檔案的函數呼叫:

func getDocumentDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

func saveDocumentFile(text: String) {
    let fileName = "cache.json"
    let filePath = getDocumentDirectory().appendingPathComponent(fileName)
    do {
        try text.write(to: filePath, atomically: true, encoding: String.Encoding.utf8)
    } catch {
        print("Error when writing file")
    }
}


寫入的檔案到底在哪兒?把路徑印出來長這樣:

file:///Users/newman/Library/Developer/Xcode/UserData/Previews/Simulator%20Devices/4BAD6318-2C87-4437-A00C-7EEFE7ED17F3/data/Containers/Data/Application/F4CC2965-6E39-400C-AA73-DD55FCB18E8C/Documents/cache.json

這雲深不知處的檔案,無法用 finder 查看內容是否正確,就作罷了,因為重點是若可以讀出來就達成目的了,因此先做一個讀出檔案的 function:

func loadDocumentFile(fileName: String) -> String {
    let data: Data
    let filePath = getDocumentDirectory().appendingPathComponent(fileName)
    do {
        data = try Data(contentsOf: filePath)
        return String(data: data, encoding: .utf8)!
    } catch {
        print("Couldn't load \(fileName) from document folder:\n\(error)")
        fatalError("Couldn't load \(fileName) from document folder:\n\(error)")
    }
}


經測試可正常顯示,所以以上會用到的功能都已經備齊了,可由畫面端程式自由組合顯示的邏輯。以下 getDashItemsFromFile() 供每次畫面顯示時呼叫,讀取本地端檔案可以非常快速。另外再加一個 refreshData() 由按鈕觸發,由使用者在必要時執行,呼叫後台並更新本地端檔案:

func getDashItemsFromFile() {
    Task {
        do {
            let rawString = loadDocumentFile(fileName: "cache.json")
            let decoder = JSONDecoder()
            let dashItems = try decoder.decode([DashItem].self, from: Data(rawString.utf8))
            self.dashItems = dashItems
        } catch {
            print("Fail to fetch api data")
        }
    }
}

func refreshData() {
Task {
        do {
            let rawString = try await fetchApiRawDataAsync(urlString: myApiUrl)
            saveDocumentFile(text: rawString)
            let decoder = JSONDecoder()
            let dashItems = try decoder.decode([DashItem].self, from: Data(rawString.utf8))
            self.dashItems = dashItems
        } catch {
            print("Fail to fetch api data")
        }
    }
}
  


Newman 2024/6/14

導覽頁:紐曼的技術筆記-索引


留言
avatar-img
留言分享你的想法!
avatar-img
newman的沙龍
25會員
119內容數
漫步是一種境界。
newman的沙龍的其他內容
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
Thumbnail
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
當我們在撰寫一套系統的時候, 總是會提供一個介面讓使用者來觸發功能模組並回傳使用者所需的請求, 而傳統的安裝包模式總是太侷限, 需要個別主機獨立安裝, 相當繁瑣, 但隨著時代的演進與互聯網的崛起, 大部分的工作都可以藉由網頁端、裝置端來觸發, 而伺服端則是負責接收指令、運算與回傳結果, 雲端
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
Thumbnail
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News