技術筆記-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

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


11會員
84內容數
漫步,悠閒自在的隨意行走!是行為,是態度,也是一種境界,存在於得勝的王者。
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
創作者要怎麼好好休息 + 避免工作過量?《黑貓創作報#4》午安,最近累不累? 這篇不是虛假的關心。而是《黑貓創作報》發行以來可能最重要的一篇。 是的,我們這篇講怎麼補充能量,也就是怎麼休息。
Thumbnail
avatar
黑貓老師
2024-06-29
深夜發牢騷文章,關於我的技術分享筆記遺失的那件事大家是否有撰寫的心血因為不明原因,網路異常而造成心血全都消失,這篇純粹是一個抒發文...
avatar
DDDDD
2023-08-30
【書選閱讀#074】打造你的知識複利筆記術:卡片盒筆記法的數位應用實戰指南提到「做筆記」你會想到什麼?如果說知識的消化可以分為「輸入、處理和輸出」三個階段,那麼你又認為筆記扮演的任務角色會落到哪一個階段?做筆記只是「輸入」成效不高,想要「輸出」又耗時費力。要如何解決這個問題呢?卡片盒筆記法,就是目前最有效的解方,特別是在數位工具智能化之後,更顯得這套方法的強大。
Thumbnail
avatar
劉奕酉
2023-08-11
原紫短文024:數位筆記術的改變力量數位筆記術的改變力量 您有曾經煩惱過學習的效率嗎?或者是覺得難以捕捉那些稍縱即逝的靈感?在我們的日常生活和學習中,這些問題都是常見的挑戰。但幸運的是,有一種工具可以幫助我們改變這些情況,那就是數位筆記術。接下來,我將會講解三個使用數位筆記術的重要原則,這些原則能幫助我們更有效地學習和捕捉靈感。 原則
Thumbnail
avatar
王啟樺
2023-06-20
【藝術筆記】花漾年華 | 現代水墨創作展 | 2016《花漾年華》四連幅是我用當代水墨的抽象語彙,表現臺灣春天茂盛的新綠,充滿生命力,山櫻花盛開。傳統花鳥畫的創作哲學是「一花一世界,一葉一如來」,透過描繪大自然的生機呈現藝術家空靈的精神世界,心靈與自然合一,既超越自然又貼近自然。 在手工宣紙,揮灑充滿想像力的純淨世界,留白,紓解現代人過量的影音刺激。
Thumbnail
avatar
心靜 Hsinching
2022-08-25
【藝術筆記】人生首展,如魚得水(開放試閱)2014年人生峰迴路轉,從一個藝術愛好者,開始投身書畫創作,九個月後受邀舉辦第一次個展。江心靜透過墨彩融合的技法展現內在生命能量,在堆疊墨韻、鮮明色彩和象徵性符號中,營造藝術與靈性追求的意境,優遊於具象和抽象水墨間。 我用畫畫寫詩,好的詩打破既有窠臼,以全新角度看待人生,詩有無限可能,可以多重解讀。
Thumbnail
avatar
心靜 Hsinching
2022-08-15
【藝術筆記】藍色空間,海洋的能量藝術家江心靜投入創作第二年,內在巨大無比的能量爆發,完成了 2015《藍色空間》三連幅當代水墨創作。 詩人的心找到了視覺語言,用深邃迷離的藍,與宇宙的能量結合,與早年創作的詩句呼應,內外合一的文人走向天命之路。
Thumbnail
avatar
心靜 Hsinching
2022-08-03
【藝術筆記】黑白的力量,第一張畫雲海流動如一首神秘的歌,興起一股衝動,想透過創作表達心中感悟,第一個想法是強調「氣韻生動」的水墨,2012年採訪現代水墨之父劉國松,高齡八十的畫家精神奕奕,親自示範技法。 看著成品,在廚房燈下微笑,黑白構成的畫面,有一股力量,從山間飛到城市,推動著自己繼續,不在意繁華如夢,一步一步,往前走。
Thumbnail
avatar
心靜 Hsinching
2022-07-24
技術筆記|使用 Hugo 免費搭建個人網站兩年多前,開始有了重新架構網站的想法,所以把 Blogger 架設的網站,移轉至到了新的平台,當年我也做過了一些優缺點評析,剛好最近又再重新整理網站的所有架構,順便也重新啟動了一次 Hugo 架設網站的循環。
Thumbnail
avatar
Liu Will
2021-06-15
[閱讀] Evernote超效率數位筆記術 [Bset技巧提升版] :如果我當初這樣做筆記因為要轉換一份新工作,認為自己在工作上必需更提高效率,剛好週圍有些朋友使用電子筆記,對於喜歡寫紙本筆記的人來說,是一個新的嘗試,原因有二:其一,筆記上有很多資料,在用完整本筆記之後,隨之石沉大海;其二,現在越來越多資訊是透過網路傳送,如果沒有好好收藏,資料就"放在"硬碟內了。 我是先去Youtube
Thumbnail
avatar
威力漫步
2019-04-12