用manager封裝網(wǎng)絡(luò)訪問(wèn)

我們把請(qǐng)求DarkSky的代碼封裝起來(lái)壤躲,以降低這部分代碼在未來(lái)對(duì)我們App的影響城菊。并為這部分的單元測(cè)試,做一些準(zhǔn)備工作碉克。

設(shè)計(jì)DataManager

為了封裝DarkSky的請(qǐng)求凌唬,我們?cè)赟ky中新建一個(gè)分組:Manager,并在其中添加一個(gè)WeatherDataManager.swif文件漏麦。在這里客税,我們創(chuàng)建一個(gè)class WeatherDataManager來(lái)管理對(duì)DarkSky的請(qǐng)求:

final class WeatherDataManager { }

這里,由于WeatherDataManager不會(huì)作為其它類的基類撕贞,我們?cè)诼暶髦惺褂昧?code>final關(guān)鍵字更耻,可以提高這個(gè)對(duì)象的訪問(wèn)性能。

WeatherDataManager有一個(gè)屬性麻掸,表示請(qǐng)求的URL:

final class WeatherDataManager {
    private let baseURL: URL
}

然后酥夭,我們用下面的代碼創(chuàng)建一個(gè)單例,便于我們用一致的方式請(qǐng)求天氣數(shù)據(jù):

final class WeatherDataManager {
    private let baseURL: URL

    private init(baseURL: URL) {
        self.baseURL = baseURL
    }

    static let shared =
        WeatherDataManager(API.authenticatedUrl)
}

這樣脊奋,我們就只能通過(guò)WeatherDataManager.shared這樣的形式熬北,來(lái)訪問(wèn)WeatherDataManager對(duì)象了。

接下來(lái)诚隙,我們要在WeatherDataManager中創(chuàng)建一個(gè)根據(jù)地理位置返回天氣信息的方法讶隐。由于網(wǎng)絡(luò)請(qǐng)求是異步的,這個(gè)過(guò)程只能通過(guò)回調(diào)函數(shù)完成久又。因此巫延,這個(gè)方法看上去應(yīng)該是這樣的:

final class WeatherDataManager {
    // ...
    typealias CompletionHandler =
        (WeatherData?, DataManagerError?) -> Void

    func weatherDataAt(
        latitude: Double,
        longitude: Double,
        completion: @escaping CompletionHandler) {}
}

然后效五,我們來(lái)定義獲取數(shù)據(jù)時(shí)的錯(cuò)誤:

enum DataManagerError: Error {
    case failedRequest
    case invalidResponse
    case unknown
}

簡(jiǎn)單起見(jiàn),我們只定義了三種情況:非法請(qǐng)求炉峰、非法返回以及未知錯(cuò)誤畏妖。然后,我們來(lái)實(shí)現(xiàn)weatherAt方法疼阔,它的邏輯很簡(jiǎn)單戒劫,只是按約定拼接URL,設(shè)置HTTP header婆廊,然后使用URLSession發(fā)起請(qǐng)求就好了:

func weatherDataAt(latitude: Double,
    longitude: Double,
    completion: @escaping CompletionHandler) {
    // 1\. Concatenate the URL
    let url = baseURL.appendingPathComponent("\(latitude), \(longitude)")
    var request = URLRequest(url: url)

    // 2\. Set HTTP header
    request.setValue("application/json",
        forHTTPHeaderField: "Content-Type")
    request.httpMethod = "GET"

    // 3\. Launch the request
    URLSession.shared.dataTask(
        with: request, completionHandler: {
        (data, response, error) in
        // 4\. Get the response here
    }).resume()
}

dataTaskcompletionHandler中迅细,為了讓代碼看上去干凈一些,我們只調(diào)用一個(gè)幫助函數(shù):

URLSession.shared.dataTask(with: request,
    completionHandler: {
    (data, response, error) in
    DispatchQueue.main.async {
        self.didFinishGettingWeatherData(
            data: data,
            response: response,
            error: error,
            completion: completion)
    }
}).resume()

這里淘邻,為了保證可以在dataTask的回調(diào)函數(shù)中更新UI茵典,我們把它派發(fā)到主線程隊(duì)列執(zhí)行。完成后宾舅,我們來(lái)實(shí)現(xiàn)didFinishGettingWeatherData

func didFinishGettingWeatherData(
        data: Data?,
        response: URLResponse?,
        error: Error?,
        completion: CompletionHandler) {
        if let _ = error {
            completion(nil, .failedRequest)
        }
        else if let data = data,
            let response = response as? HTTPURLResponse {
            if response.statusCode == 200 {
                do {
                    let weatherData =
                        try JSONDecoder().decode(WeatherData.self, from: data)
                    completion(weatherData, nil)
                }
                catch {
                    completion(nil, .invalidResponse)
                }
            }
            else {
                completion(nil, .failedRequest)
            }
        }
        else {
            completion(nil, .unknown)
        }
    }

其實(shí)邏輯很簡(jiǎn)單统阿,就是根據(jù)請(qǐng)求以及服務(wù)器的返回值是否可用,把對(duì)應(yīng)的參數(shù)傳遞給了一個(gè)可以自定義的回調(diào)函數(shù)贴浙。這樣砂吞,這個(gè)WeatherDataManager就實(shí)現(xiàn)好了。

現(xiàn)在崎溃,回想起來(lái)蜻直,我們?cè)谶@兩節(jié)中,關(guān)于model的部分袁串,已經(jīng)寫(xiě)了不少的代碼了概而,它們真的能正常工作么?我們?nèi)绾未_定這個(gè)事情呢囱修?在把model關(guān)聯(lián)到controller之前赎瑰,我們最好確定一下。

當(dāng)然破镰,一個(gè)直觀的辦法就是在類似某個(gè)viewDidLoad之類的方法里餐曼,寫(xiě)個(gè)代碼實(shí)際請(qǐng)求一下看看。但是估計(jì)你也能感覺(jué)到這種做法并不地道鲜漩,如果未來(lái)你修改了Manager的代碼呢源譬?難道還要重新找個(gè)viewDidLoad方法插個(gè)空來(lái)測(cè)試么?估計(jì)你自己都不太敢這樣做孕似,萬(wàn)一你在恢復(fù)的時(shí)候不慎修改掉了哪部分功能代碼踩娘,就很容易隨隨便便坑上你幾個(gè)小時(shí)。

為此喉祭,我們需要一種更專業(yè)和安全的方式养渴,來(lái)確定局部代碼的正確性雷绢。這種方式,就是單元測(cè)試理卑。在開(kāi)始測(cè)試我們的WeatherDataManager之前翘紊,我們要先了解一下Xcode提供的單元測(cè)試模板。

了解單元測(cè)試模板

首先藐唠,在Xcode默認(rèn)創(chuàng)建的SkyTests分組中霞溪,刪掉默認(rèn)的SkyTests.swift。然后在SkyTests Group上點(diǎn)右鍵中捆,選擇New File...

DarkSkyAndModel

其次,在右上角的filter中坊饶,輸入unit泄伪,找到單元測(cè)試的模板。選中Unit Test Case Class匿级,點(diǎn)擊Next

DarkSkyAndModel

第三蟋滴,給測(cè)試用例起個(gè)名字,例如WeatherDataManagerTest痘绎。這個(gè)名字最好可以直接表達(dá)我們要測(cè)試的內(nèi)容津函。這樣,不同的開(kāi)發(fā)者都可以方便的了解到實(shí)際測(cè)試的內(nèi)容:

DarkSkyAndModel

第四孤页,接下來(lái)尔苦,Xcode就會(huì)提示我們是否需要?jiǎng)?chuàng)建一個(gè)bridge header,由于我們?cè)诩僑wift環(huán)境中開(kāi)發(fā)行施,因此允坚,選擇Don't Create,并點(diǎn)擊Finish按鈕蛾号。

設(shè)置好保存路徑之后稠项,我們就可以在SkyTests分組中,找到新添加的測(cè)試用例了鲜结。在開(kāi)始編寫(xiě)測(cè)試之前展运,這個(gè)文件中有幾個(gè)值得說(shuō)明的地方:

首先,在文件一開(kāi)始精刷,要添加下面的代碼引入項(xiàng)目的main module拗胜。這樣,才能在測(cè)試用例中贬养,訪問(wèn)到項(xiàng)目定義的類型:

import XCTest
@testable import Sky

其次挤土,在生成的代碼中,WeatherDataManagerTest派生自XCTestCase误算,表示這是一個(gè)測(cè)試用例仰美。

第三迷殿,在WeatherDataManagerTest里,我們可以把所有的測(cè)試前要準(zhǔn)備的代碼咖杂,寫(xiě)在setUp方法里庆寺,而把測(cè)試后需要清理的代碼,寫(xiě)在tearDown方法里诉字。這里要注意下面代碼中注釋的位置懦尝,初始化代碼寫(xiě)在super.setUp()后面,清理代碼要寫(xiě)在super.tearDown()前面:

class WeatherDataManagerTest: XCTestCase {

    override func setUp() {
        super.setUp()
        // Your set up code here...
    }

    override func tearDown() {
        // Your tear down code here...
        super.tearDown()
    }

    // ...
}

第四壤圃,Xcode為我們生成了兩個(gè)默認(rèn)的測(cè)試方法:

class WeatherDataManagerTest: XCTestCase {
    func testExample() {
        // ...
    }

    func testPerformanceExample() {
        // ...
    }
}

要注意的是陵霉,所有測(cè)試方法都必須用test開(kāi)頭,Xcode才會(huì)識(shí)別它們并自動(dòng)執(zhí)行伍绳。這里踊挠,可以先把它們刪掉,稍后我們會(huì)編寫(xiě)自己的測(cè)試方法冲杀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末效床,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子权谁,更是在濱河造成了極大的恐慌剩檀,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旺芽,死亡現(xiàn)場(chǎng)離奇詭異沪猴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)采章,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)字币,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人共缕,你說(shuō)我怎么就攤上這事洗出。” “怎么了图谷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵翩活,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我便贵,道長(zhǎng)菠镇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任承璃,我火速辦了婚禮利耍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己隘梨,他們只是感情好程癌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著轴猎,像睡著了一般嵌莉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捻脖,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天锐峭,我揣著相機(jī)與錄音,去河邊找鬼可婶。 笑死沿癞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矛渴。 我是一名探鬼主播抛寝,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼曙旭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晶府,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桂躏,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后川陆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體剂习,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年较沪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳞绕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尸曼,死狀恐怖们何,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情控轿,我是刑警寧澤冤竹,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站茬射,受9級(jí)特大地震影響鹦蠕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜在抛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一钟病、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦肠阱、人聲如沸票唆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惰说。三九已至,卻和暖如春缘回,著一層夾襖步出監(jiān)牢的瞬間吆视,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工酥宴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啦吧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓拙寡,卻偏偏與公主長(zhǎng)得像授滓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肆糕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容