技術筆記-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
22會員
107內容數
漫步是一種境界。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
newman的沙龍 的其他內容
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
首先安裝 python,依照 官網 的指示,下載正確的版本並執行就可,重點在以下。 虛擬環境的管理平台許多人使用 anaconda,許多教 python 的課程第一堂就是不管三七二十一先安裝再說,但我用了一陣子之後覺得它實在太笨重了,早有掙脫的想法,欣見原生的 python 已經具有虛擬環境管理模
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
首先安裝 python,依照 官網 的指示,下載正確的版本並執行就可,重點在以下。 虛擬環境的管理平台許多人使用 anaconda,許多教 python 的課程第一堂就是不管三七二十一先安裝再說,但我用了一陣子之後覺得它實在太笨重了,早有掙脫的想法,欣見原生的 python 已經具有虛擬環境管理模
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
我自己是希望可以製作iOS app來更好存放我的文章, 更進階一點,可以變成直接錄音後, 照我設定的方式轉換成文檔,讓iPhone變成我更強的助手。 感覺有很多可以探索,用時間慢慢累積經驗。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
大家好,好久沒更新,最近教學論命繁忙,這邊有所耽擱,真的抱歉。 最近也有跟設計師在討論要架官網,時間真的是不夠用了,因此方格子這邊我會先暫停一下,架好官網之後會將文章轉移到官網,至於會不會有收費文章,這邊我還再思考當中。 如果想看我最新的貼文,可以轉到我的社群上面去。目前是每周二晚上八點發放貼文
Thumbnail
不知道你有沒有遇過一種情形,突然有一天想回頭找之前的資料,但因為檔名相似,因此需要將想找的檔案一個個打開查看,若是檔案太大常常需要等待一段時間檔案才能開啟。
Thumbnail
當我們在開發一個AI應用服務時, 常常會需要載入大模型, But… 我們總不可能每一次的請求就載入一次模型吧! 這樣太沒有效率了, 也非常的浪費資源, 因此我們通常會希望應用程式啟動時就能夠載入模型, 之後每一次的請求只要讓模型進行運算即可, 那麼在FastAPI的框架中究竟要如何使用呢? 首
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
因為使用安卓手機,一直以來我都是使用Keep記事本寫稿。 因為會自動儲存上雲端,所以就算用其他裝置,只要有登入帳號,都可以在幾分鐘內在其他裝置同步更新,換裝置也能無縫銜接,非常迅速方便。 其他手機像是iOS應該也有類似App可以利用。 然後剛好看到這篇,我沒有女朋友也沒辦法給建議🥹
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
我自己是希望可以製作iOS app來更好存放我的文章, 更進階一點,可以變成直接錄音後, 照我設定的方式轉換成文檔,讓iPhone變成我更強的助手。 感覺有很多可以探索,用時間慢慢累積經驗。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
大家好,好久沒更新,最近教學論命繁忙,這邊有所耽擱,真的抱歉。 最近也有跟設計師在討論要架官網,時間真的是不夠用了,因此方格子這邊我會先暫停一下,架好官網之後會將文章轉移到官網,至於會不會有收費文章,這邊我還再思考當中。 如果想看我最新的貼文,可以轉到我的社群上面去。目前是每周二晚上八點發放貼文
Thumbnail
不知道你有沒有遇過一種情形,突然有一天想回頭找之前的資料,但因為檔名相似,因此需要將想找的檔案一個個打開查看,若是檔案太大常常需要等待一段時間檔案才能開啟。
Thumbnail
當我們在開發一個AI應用服務時, 常常會需要載入大模型, But… 我們總不可能每一次的請求就載入一次模型吧! 這樣太沒有效率了, 也非常的浪費資源, 因此我們通常會希望應用程式啟動時就能夠載入模型, 之後每一次的請求只要讓模型進行運算即可, 那麼在FastAPI的框架中究竟要如何使用呢? 首
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
因為使用安卓手機,一直以來我都是使用Keep記事本寫稿。 因為會自動儲存上雲端,所以就算用其他裝置,只要有登入帳號,都可以在幾分鐘內在其他裝置同步更新,換裝置也能無縫銜接,非常迅速方便。 其他手機像是iOS應該也有類似App可以利用。 然後剛好看到這篇,我沒有女朋友也沒辦法給建議🥹
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息