Start Developing iOS Apps (Swift)->定義數(shù)據(jù)模型

在本課中片排,你將為FoodTracker應(yīng)用定義并測試一個數(shù)據(jù)模型(data model)怖竭。數(shù)據(jù)模型代表的是存儲在應(yīng)用中的信息的結(jié)構(gòu)惊楼。

學習目標

在本課結(jié)束的時候捻艳,你將能夠:

  • 創(chuàng)建一個數(shù)據(jù)模型
  • 為自定義類編寫可失敗的初始化器
  • 從概念上理解可失敗和不可失敗初始化器之間的區(qū)別
  • 通過編寫和運行單元測試來測試數(shù)據(jù)模型

創(chuàng)建數(shù)據(jù)模型

現(xiàn)在你將創(chuàng)建一個數(shù)據(jù)模型來存儲那些要在場景中顯示的信息。為了做到這一點,你定義一個包含名字(name)该溯、照片(photo)岛抄、評分(rating)的類。

創(chuàng)建一個新的數(shù)據(jù)模型類

  1. 選擇File > New > File (或者按下Command-N)狈茉。
  2. 在出現(xiàn)的對話框的頂部夫椭,選擇iOS。
  3. 選擇Swift 文件氯庆,點擊Next蹭秋。
    由于你給數(shù)據(jù)模型定義了一個基類,意味著它不需要從其他類中繼承堤撵,所以它的創(chuàng)建方式和之前的RatingControl類創(chuàng)建方式不同仁讨。
  4. 在Save As字段,鍵入Meal实昨。
  5. 默認保存位置是你的項目目錄洞豁。
    Group 選項默認是應(yīng)用名字,F(xiàn)oodTracker’荒给。在Targets部分丈挟,應(yīng)用被選中,而應(yīng)用冊測試沒有被選中志电。
  6. 其他的不變曙咽,點擊Create。
    Xcode創(chuàng)建名為Meal.swift的文件挑辆。如有必要例朱,在Project navigator中,拖拽Meal.swift文件把它放置到其他Swift文件的下面之拨。

在Swift中茉继,你可以用String表示名字咧叭、用UIImage表示照片蚀乔、用Int表示評分。因為菜品總是有名字和評分菲茬,但不一定有照片吉挣,所以UIImage設(shè)置為可選(optional)。

為菜品定義數(shù)據(jù)模型

  1. 如果助理編輯器開著婉弹,則返回到標準編輯器睬魂。


    image: ../Art/standard_toggle_2x.png
  2. 打開Meal.swift。
  3. 改變import語句镀赌,用UIKit代替Foundation
import UIKit

當一個Xcode創(chuàng)建一個新swift文件氯哮,默認情況下會導入(import)Foundation框架,讓你在代碼中使用Foundation數(shù)據(jù)結(jié)構(gòu)商佛。由于你將要使用來自UIKit框架的類喉钢,所以你需要導入UIKit姆打。然而,導入了UIKit就能訪問Foundation肠虽,所以你可以刪除冗余的包含F(xiàn)oundation的代碼幔戏。

  1. 在import語句后面,添加如下代碼:
class Meal {
            
            //MARK: Properties
            
            var name: String
            var photo: UIImage?
            var rating: Int
            
        }

這些代碼定義了你需要存儲的數(shù)據(jù)的基本屬性税课。你使用變量(var)來替代常量(let)是因為你將需要在Meal對象的整個生命周期中對它們進行修改闲延。

  1. 在屬性的下面,添加代碼來聲明一個初始化器韩玩。
//MARK: Initialization
         
        init(name: String, photo: UIImage?, rating: Int) {
            
        }

回想一下垒玲,初始化器方法能準備一個類的實例來使用,它需要為每個屬性設(shè)置一個初始化值找颓,并執(zhí)行任何其他的設(shè)置和初始化操作侍匙。

  1. 通過設(shè)置屬性等于參數(shù)值來填寫基本的實現(xiàn)。
// Initialize stored properties.
        self.name = name
        self.photo = photo
        self.rating = rating

但是叮雳,如果你嘗試創(chuàng)建一個使用了不正確值的Meal將會發(fā)生什么想暗,比如給評分一個空值或者一個負值?你需要返回nil來表示這個項目不能被創(chuàng)建帘不,并已設(shè)置了一個默認值说莫。你需要添加代碼來檢查這種情況,如果失敗則返回nil寞焙。

  1. 緊跟著初始化存儲屬性代碼下面储狭,添加如下代碼:
// Initialization should fail if there is no name or if the rating is negative.
        if name.isEmpty || rating < 0  {
            return nil
        }

這個代碼驗證傳入?yún)?shù),如果它們包含無效值則返回nil捣郊。
注意辽狈,編譯器會提示一個錯誤,“Only failable initializers can return nil (只有可失敗初始化器能返回nil)”

  1. 點擊錯誤圖標顯示fix-it信息呛牲。


    image: ../Art/DYDM_init_fixit_2x.png
    image: ../Art/DYDM_init_fixit_2x.png
  2. 雙擊fix it來更新你的初始化器」蚊龋現(xiàn)在初始化器應(yīng)該是這樣的:
    init?(name: String, photo: UIImage?, rating: Int) {

可失敗初始化器總是使用init?或者init!。這些初始化器分別返回可選類型(optional)值或隱式解包可選類型(implicitly unwrapped optional)值娘扩∽湃祝可選類型能同時包含有效值和nil。你必須檢查是否可選類型有一個值琐旁,然后在使用之前安全的解包這個值涮阔。隱式解包可選類型也是可選類型,但是系統(tǒng)會對它們進行隱式解包灰殴。在本例中敬特,你的初始化器返回一個可選類型對象,Meal?

現(xiàn)在,你的init?(name: String, photo: UIImage?, rating: Int)初始化器看上去是這樣的:

init?(name: String, photo: UIImage?, rating: Int) {
            
            // Initialization should fail if there is no name or if the rating is negative.
            if name.isEmpty || rating < 0  {
                return nil
            }
            
            // Initialize stored properties.
            self.name = name
            self.photo = photo
            self.rating = rating
            
        }

進一步探索
正如你在本系列課程后面看到的伟阔,可失敗初始化器較難使用尸变,因為你需要在使用之前對它的返回可選類型進行解包。一些程序員比較喜歡使用assert()和precondition()方法強行執(zhí)行初始化器减俏。這些方法在它們檢測到錯誤的時候終止應(yīng)用召烂。這意味著在調(diào)用初始化器之前,調(diào)用代碼必須有有效數(shù)據(jù)娃承。
更多關(guān)于初始化器的信息奏夫,查看Initialization。關(guān)于在代碼中添加內(nèi)聯(lián)性檢查和前提條件的信息历筝,查看assert(::file:line:)和precondition(::file:line:)

檢查點:通過選擇Product > Build(或按下Command-B)來構(gòu)建項目酗昼。你還沒有使用新類來做任何事情,但是構(gòu)建可以給編譯器一個機會來證實沒有輸入錯誤梳猪。如果你有麻削,根據(jù)編譯器提供的錯誤或警告信息來修復它,然后再回顧一下本課中的說明春弥,確保每件事都如它描述的那樣呛哟。

測試你的數(shù)據(jù)

雖然你的數(shù)據(jù)模型代碼已經(jīng)構(gòu)建,但是你還沒有把它并入到應(yīng)用中匿沛。因此扫责,很難判斷你已經(jīng)正確的實現(xiàn)了每件事,如你可能遇到在運行時未考慮到的邊緣情況逃呼。

為了解決這種不確定鳖孤,你可以寫單元測試。Unit tests(單元測試)是使用小型的抡笼、獨立的代碼片段苏揣,來確保它們的行為正確。Meal類是單元測試完美的候選人推姻。

查看FoodTracker的單元測試文件

  1. 在project navigator中點擊FoodTrackerTests文件旁的小三角來展開它平匈。


    image: ../Art/DYDM_foodtrackertests_2x.png
  2. 打開FoodTrackerTests.swift。

花一點時間來理解這個文件中迄今為止的代碼拾碌。

import XCTest
        @testable import FoodTracker
         
        class FoodTrackerTests: XCTestCase {
            
            override func setUp() {
                super.setUp()
                // Put setup code here. This method is called before the invocation of each test method in the class.
            }
            
            override func tearDown() {
                // Put teardown code here. This method is called after the invocation of each test method in the class.
                super.tearDown()
            }
            
            func testExample() {
                // This is an example of a functional test case.
                // Use XCTAssert and related functions to verify your tests produce the correct results.
            }
            
            func testPerformanceExample() {
                // This is an example of a performance test case.
                self.measure {
                    // Put the code you want to measure the time of here.
                }
            }
            
        }

代碼從導入(import)XCText框架到你的應(yīng)用開始吐葱。

注意街望,代碼在導入你的應(yīng)用的時候使用了@testable屬性校翔。這給了你的測試文件訪問你的應(yīng)用代碼內(nèi)部元素的入口。記住灾前,Swift默認對所有代碼中的類型防症、變量、屬性、初始化方法蔫敲、以及函數(shù)進行內(nèi)部訪問控制饲嗽。如果你沒有明確的標記一個項目是文件私有或私有,那么你就可以從測試訪問它奈嘿。

XCTest框架是Xcode的測試框架貌虾。單元測試本身在一個類中被定義,F(xiàn)oodTrackerTests裙犹,它繼承自XCTestCase尽狠。這些代碼注釋解釋了 setUp() 和 tearDown()方法,以及兩個測試用例:testExample() 和testPerformanceExample().

你能寫的測試的主要類型是函數(shù)測試(檢查它們是否能得到你期望的值)和性能測試(檢查代碼是否如你期望的那樣快)叶圃。因為你還沒有寫過任何很影響性能的代碼,所以你將只需要寫一些函數(shù)測試掺冠。

測試用例是簡單的方法,它們是作為單元測試的一部分由系統(tǒng)自動運行的德崭。為了創(chuàng)建測試用例,創(chuàng)建一個方法眉厨,方法名要以test開頭。最好給你的測試用例描述性的名字缺猛。這些名字可以讓你在以后很容易的識別單個測試。例如荔燎,一個測試檢查Meal類的初始化代碼,可以命名為testMealInitialization有咨。

為Meal對象初始化編寫單元測試

  1. 在 FoodTrackerTests.swift中,你不需要任何模版創(chuàng)建的方法座享。刪除這些模版方法。你的菜品跟蹤測試應(yīng)該是下面這樣的:
import XCTest
        @testable import FoodTracker
         
        class FoodTrackerTests: XCTestCase {
            
        }
  1. 在結(jié)束的花括號之前渣叛,添加如下內(nèi)容:
//MARK: Meal Class Tests
  1. 在注釋下面,添加一個新的測試用例:
// Confirm that the Meal initializer returns a Meal object when passed valid parameters.
        func testMealInitializationSucceeds() {
            
        }

當單環(huán)測試運行的時候系統(tǒng)自動的運行這個測試用例蘑秽。

  1. 添加測試到測試用例饺著,測試使用0分和最高分來進行:
// Zero rating
        let zeroRatingMeal = Meal.init(name: "Zero", photo: nil, rating: 0)
        XCTAssertNotNil(zeroRatingMeal)
         
        // Highest positive rating
        let positiveRatingMeal = Meal.init(name: "Positive", photo: nil, rating: 5)
        XCTAssertNotNil(positiveRatingMeal)

如果初始化器如預想般工作,則調(diào)用init(name:, photo:, rating:)將成功肠牲。XCTAssertNotNil通過檢查返回的Meal對象是否為nil來證明這一點幼衰。

  1. 現(xiàn)在在Meal類的初始化失敗的情況下添加測試用例。添加下面的方法到testMealInitializationSucceeds()方法下面缀雳。
// Confirm that the Meal initialier returns nil when passed a negative rating or an empty name.
        func testMealInitializationFails() {
            
        }

再次渡嚣,當單元測試運行的時候,系統(tǒng)會自動運行測試單元肥印。

  1. 現(xiàn)在添加測試代碼來測試使用無效參數(shù)調(diào)用初始化器的情況严拒。
// Negative rating
        let negativeRatingMeal = Meal.init(name: "Negative", photo: nil, rating: -1)
        XCTAssertNil(negativeRatingMeal)
         
        // Empty String
        let emptyStringMeal = Meal.init(name: "", photo: nil, rating: 0)
        XCTAssertNil(emptyStringMeal)

如果初始化器如預想般工作,這些對init(name:, photo:, rating:)的調(diào)用會失敗竖独。XCTAssertNil通過檢查返回的Meal對象是否為nil來這時它裤唠。

  1. 到現(xiàn)在為止,這些測試都應(yīng)該是成功的∮。現(xiàn)在測試一個錯誤的情況种蘸。在負評分和空字符串測試代碼之間添加下面的代碼:
// Rating exceeds maximum
        let largeRatingMeal = Meal.init(name: "Large", photo: nil, rating: 6)
        XCTAssertNil(largeRatingMeal)

你的單元測試類應(yīng)該看上去是這樣的:

class FoodTrackerTests: XCTestCase {

你能添加額外的子類到你的FoodTrackerTests目標來添加額外的測試用例。選擇Product > Test (或者按下 Command-U)來同時運行所有的單元測試竞膳。你也可以運行一個單獨的測試航瞭。

檢查點:通過選擇Product > Test 菜單項運行單元測試。 testMealInitializationSucceeds()測試用例將成功坦辟,而testMealInitializationFails()測試用例會失敗刊侯。

注意Xcode在左側(cè)自動打開的Test navigator,高亮顯示失敗的測試锉走。


image: ../Art/DYDM_failtest_2x.png

在編輯器窗口顯示當前打開文件的結(jié)果滨彻。在本例中,如果測試用例的一個或多個方法失敗的時候這個用例也就失敗挪蹭。如果測試方法的一個或多個測試失敗這個方法也就失敗亭饵。在本例中,只有XCTAssertNil(largeRatingMeal)測試失敗了梁厉。

Test navigator還列出了通過測試用例分組的各種測試方法辜羊。點擊測試方法可以在編輯器中導航到它的代碼。右側(cè)的圖標顯示了這個測試方法是成功還是失敗词顾。你能通過移動鼠標到成功或失敗的圖標上來返回一個測試方法八秃。當圖標變成一個播放箭頭圖標時,點擊它肉盹。

就像你看到的昔驱,單元測試幫助捕捉你代碼中的錯誤垮媒。它們還能幫你定義你的類期望的行為航棱。在本例中,Meal類的初始化器在你傳遞一個空字符串或者負評分時會失敗秕豫,但是傳遞一個大于5的值的時候不失敗观蓄。要回去修復它侮穿。

修改錯誤

  1. 在Meal.swift中亲茅,找到init?(name:, photo:, rating:)方法克锣。
  2. 你可以修改if子句,但是復雜的布爾表達式會讓理解變得困難验残。這里可以使用一系列檢查來替代它您没。而且胆绊,因為你在執(zhí)行代碼之前驗證數(shù)據(jù)辑舷,所以要使用guard語句。
    guard(保護)語句聲明了一個條件肢础,這個條件必須為真传轰,以便執(zhí)行g(shù)uard語句后面的代碼被執(zhí)行谷婆。如果條件為假是,保護語句后面的else分支必須退出當前的代碼塊(例如跟匆,通過調(diào)用 return, break, continue, throw,或者一個類似fatalError(_:file:line:)不需要返回的方法)玛臂。
    替換此代碼:
// Initialization should fail if there is no name or if the rating is negative.
        if name.isEmpty || rating < 0  {
            return nil
        }

用下面的代碼:

// The name must not be empty
        guard !name.isEmpty else {
            return nil
        }
         
        // The rating must be between 0 and 5 inclusively
        guard (rating >= 0) && (rating <= 5) else {
            return nil
        }

你的init?(name:, photo:, rating:)方法應(yīng)該看上去是這樣的:

init?(name: String, photo: UIImage?, rating: Int) {
            
            // The name must not be empty
            guard !name.isEmpty else {
                return nil
            }
            
            // The rating must be between 0 and 5 inclusively
            guard (rating >= 0) && (rating <= 5) else {
                return nil
            }
            
            // Initialize stored properties.
            self.name = name
            self.photo = photo
            self.rating = rating
            
        }

檢查點:使用單元測試運行應(yīng)用迹冤。所有的測試用例都應(yīng)該通過泡徙。


image: ../Art/DYDM_passtest_2x.png

單元測試是編寫代碼的重要部分堪藐,因為它幫助你不活你可能忽略的錯誤庶橱。就像它們的名字所示苏章,保持單元測試模塊化石重要的奏瞬。每個測試應(yīng)該檢查一個特定的基本類型行為。如果你寫長的復雜的單元測試并淋,就難以跟蹤錯誤县耽。

小結(jié)

在本課中兔毙,你構(gòu)建了一個模型(model)類來持有你的應(yīng)用數(shù)據(jù)澎剥。你還比較了常規(guī)初始化器和可失敗初始化器之間的區(qū)別哑姚。最后,你添加了幾個單元測試來幫助你找到代碼中的錯誤并修復它們倡蝙。

在稍后的課程中宛乃,你將在應(yīng)用的代碼中使用模型對象來創(chuàng)建和管理菜品列表征炼。但是谆奥,在你做這些之前拂玻,你需要學習如何使用表視圖(table view)來呈現(xiàn)菜品列表檐蚜。

注意
想看本課的完整代碼,下載這個文件并在Xcode中打開市栗。
下載文件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末填帽,一起剝皮案震驚了整個濱河市篡腌,隨后出現(xiàn)的幾起案子嘹悼,更是在濱河造成了極大的恐慌杨伙,老刑警劉巖卒密,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帝洪,居然都是意外死亡,警方通過查閱死者的電腦和手機怔揩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門商膊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晕拆,“玉大人实幕,你說我怎么就攤上這事昆庇≌海” “怎么了掂为?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵勇哗,是天一觀的道長欲诺。 經(jīng)常有香客問我渺鹦,道長毅厚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮伴网,結(jié)果婚禮上澡腾,老公的妹妹穿的比我還像新娘动分。我一直安慰自己,他們只是感情好留特,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布纠脾。 她就那樣靜靜地躺著玛瘸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苟蹈。 梳的紋絲不亂的頭發(fā)上糊渊,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音慧脱,去河邊找鬼渺绒。 笑死,一個胖子當著我的面吹牛菱鸥,可吹牛的內(nèi)容都是我干的宗兼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼氮采,長吁一口氣:“原來是場噩夢啊……” “哼主到!你這毒婦竟也來了娶靡?” 一聲冷哼從身側(cè)響起度陆,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叽粹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辆脸,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年门坷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡度帮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冕臭,我是刑警寧澤念颈,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布歉井,位于F島的核電站菩貌,受9級特大地震影響仇参,放射性物質(zhì)發(fā)生泄漏怕磨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一棠枉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦悬蔽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椒惨。三九已至康谆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間细层,已是汗流浹背虚缎。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留正蛙,地道東北人营曼。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓乒验,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蒂阱。 傳聞我的和親對象是個殘疾皇子锻全,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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