類(一)

作者 Cosmin Pup?z?

從結(jié)構(gòu)中學(xué)習(xí)到了命名類型。在本章中你將學(xué)習(xí)另一種具有屬性和方法的命名類型眯分,類误甚,它與結(jié)構(gòu)非常像缚甩。

類是引用類型,與值類型相反窑邦,類有不同于值類型的優(yōu)點和缺點擅威。你通常會使用結(jié)構(gòu)去表示值,用類去表示對象冈钦。

那么郊丛,什么是值,什么是對象呢?

創(chuàng)建類

思考下面類的定義:

class Person {
  var firstName: String
  var lastName: String
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  var fullName: String {
    return "\(firstName) \(lastName)"
  }
}
let john = Person(firstName: "Johnny", lastName: "Appleseed")

是不是很簡單宾袜!類幾乎與結(jié)構(gòu)完全相同。關(guān)鍵字 class 在類名的前面驾窟,花括號內(nèi)都是類的成員庆猫。

但是你可以看到類與結(jié)構(gòu)的不同,上面的類定義了一個初始化方法绅络,它為firstName和lastName設(shè)置初始值月培。與結(jié)構(gòu)不同的是,一個類并不會自動地提供一個成員初始化方法恩急,這意味著如果你需要的話杉畜,你必須自己編寫它。

如果你忘記編寫初始化方法衷恭,swift編譯器會提醒你:


QQ20180522-103212@2x.png

除了初始化方法之外此叠,類和結(jié)構(gòu)的初始化規(guī)則頁非常類似。類初始化方法是名為init的函數(shù)随珠,所有存儲屬性必須在init結(jié)束前分配初始值灭袁。

實際上類還有更多的初始化方式,但是你必須等到下一章“高級類”窗看,它將向你介紹繼承的概念以及它對初始化規(guī)則的影響∪灼纾現(xiàn)在,通過使用基本類初始化方法显沈,你可以輕松地使用Swift的類软瞎。

引用類型

在Swift中,結(jié)構(gòu)的實例是不可變的值拉讯。另一方面涤浇,類的實例是一個可變對象。因為類是引用類型遂唧,類型的變量不存儲實際實例芙代,而是引用存儲實例在內(nèi)存中的位置。如果你要創(chuàng)建一個SimplePerson類實例:

class SimplePerson {
  let name: String
  init(name: String) {
    self.name = name
  }
}
var var1 = SimplePerson(name: "John")

它在內(nèi)存中是這樣的:


QQ20180522-104707@2x.png

如果你要創(chuàng)建一個新的變量var2并將其賦值為var1:

var var2 = var1

然后盖彭,var1和var2中的引用將在內(nèi)存中引用相同的位置纹烹。


QQ20180522-105009@2x.png

相反,作為值類型的結(jié)構(gòu)存儲實際值召边,提供對其的直接訪問铺呵。將SimplePerson類實現(xiàn)替換為這樣的結(jié)構(gòu):

struct SimplePerson {
  let name: String
}

在內(nèi)存中,變量不會引用內(nèi)存中的某個位置隧熙,但是該值將只屬于var1:


QQ20180522-105120@2x.png

賦值var var2 = var1在本例中復(fù)制var1的值:


QQ20180522-105154@2x.png

值類型和引用類型各有各自的優(yōu)缺點片挂。在本章后面,你將考慮在給定的情況下使用哪種類型。現(xiàn)在音念,讓我們來看看類和結(jié)構(gòu)是如何工作的沪饺。雖然下面的描述并不適用于所有情況,但是要記住它是一個很好的一階模型闷愤。

堆&堆棧

當(dāng)你創(chuàng)建一個引用類型(如類)時整葡,系統(tǒng)將實際的實例存儲在一個稱為堆的內(nèi)存區(qū)域中。值類型的實例(例如結(jié)構(gòu)體)駐留在稱為堆棧的內(nèi)存區(qū)域中讥脐,除非該值是類實例的一部分遭居,在這種情況下,該值將與類實例的其余部分一起存儲在堆中旬渠。

堆和堆棧在執(zhí)行任何程序時都有重要的作用俱萍。對它們是什么以及它們?nèi)绾喂ぷ鞯囊话憷斫鈱椭憧梢暬惡徒Y(jié)構(gòu)之間的功能差異:

?系統(tǒng)使用堆棧來存儲任何立即執(zhí)行的線程;它受到CPU的嚴(yán)格管理和優(yōu)化。當(dāng)函數(shù)創(chuàng)建一個變量時告丢,堆棧存儲該變量枪蘑,然后在函數(shù)退出時銷毀它。由于這個堆棧組織得很好芋齿,所以它非常高效腥寇,因此也非常快觅捆。

?系統(tǒng)使用堆來存儲引用類型的實例赦役。堆通常是一個大內(nèi)存池,系統(tǒng)可以請求并動態(tài)地分配內(nèi)存塊栅炒。生命是靈活多變的掂摔。堆不會像堆棧那樣自動銷毀它的數(shù)據(jù);這樣做需要額外的工作。這使得在堆上創(chuàng)建和刪除數(shù)據(jù)比在堆棧上更慢赢赊。

也許你已經(jīng)知道了它與結(jié)構(gòu)和類的關(guān)系乙漓。看看下面的圖表:


QQ20180522-114312@2x.png

?當(dāng)你創(chuàng)建一個類的實例時释移,你的代碼請求堆上的一個內(nèi)存塊來存儲實例本身;如圖右側(cè)的實例中的第一個名稱和最后一個名稱叭披。它將該內(nèi)存的地址存儲在堆棧上的命名變量中;右側(cè)是對存儲在圖左側(cè)的引用。
?當(dāng)你創(chuàng)建一個結(jié)構(gòu)的實例(它不是類實例的一部分)時玩讳,實例本身就存儲在堆棧中涩蜘,而堆從未涉及到。

這只是簡單介紹了堆和堆棧的動態(tài)熏纯,但你現(xiàn)在已經(jīng)知道了足夠的知識來理解用于與類工作的引用語義同诫。

使用引用

在“結(jié)構(gòu)”中,你看到了在使用結(jié)構(gòu)和其他值類型時所涉及的復(fù)制語義樟澜。這里有一個小提示误窖,利用這一章的Location和DeliveryArea結(jié)構(gòu):

struct Location {
  let x: Int
let y: Int }
struct DeliveryArea {
  var range: Double
  let center: Location
}
var area1 = DeliveryArea(range: 2.5,center: Location(x: 2, y: 4))
var area2 = area1
print(area1.range) // 2.5
print(area2.range) // 2.5
area1.range = 4
print(area1.range) // 4.0
print(area2.range) // 2.5

當(dāng)你將area1的值賦給area2時叮盘,area2將收到area1值的副本。當(dāng)area1.range 收到一個新的值4霹俺,這個數(shù)字只反映在area1中柔吼,而area2仍然具有原來的2.5的值。

由于類是一個引用類型丙唧,當(dāng)你分配給變量給類時嚷堡,系統(tǒng)不會復(fù)制實例;只復(fù)制引用。

將前面的代碼與下面的代碼進行對比:

var homeOwner = John
john.firstName = "John" // John wants to use his short name!
john.firstName // "John"
homeOwner.firstName // "John"

正如你所看到的艇棕,john和homeOwner的值是一樣的!

這種在類實例之間的隱含共享導(dǎo)致了一種新的思維方式。例如串塑,如果john對象發(fā)生了變化沼琉,那么任何引用john的東西都會自動看到更新。如果你使用的是一個結(jié)構(gòu)桩匪,那么您必須單獨更新每個副本打瘪,否則它仍然具有“Johnny”的舊值。

對象id

在前面的代碼示例中傻昙,可以看到j(luò)ohn和homeOwner都指向同一個對象闺骚。兩個引用都是命名變量。如果你想知道變量后面的值是否是John怎么辦?

你可能會想要檢查firstName的值妆档,但是你怎么知道它是你要找的John而不是冒名頂替者呢?更糟的是僻爽,如果john又改了名字會怎么樣?

在Swift中,==操作符讓你檢查一個對象的id是否等于另一個對象的id:

 john === homeOwner // true

當(dāng)==操作符檢查兩個值是否相等時贾惦,===身份操作符比較兩個引用的內(nèi)存地址胸梆。它告訴你引用的值是否相同;也就是說,它們指向堆上的同一塊數(shù)據(jù)须板。

這意味著這個===操作符可以區(qū)分出你要找的john和一個假冒的john碰镜。

let imposterJohn = Person(firstName: "Johnny", lastName: "Appleseed")
john === homeOwner // true
john === imposterJohn // false
imposterJohn === homeOwner // false
// Assignment of existing variables changes the instances the variables reference.
homeOwner = imposterJohn
john === homeOwner // false
homeOwner = John
john === homeOwner // true

當(dāng)您不能依賴于常規(guī)的等式(==)來比較和識別你關(guān)心的對象時,這一點特別有用:

// Create fake, imposter Johns. Use === to see if any of these imposters
are our real John.
var imposters = (0...100).map { _ in
  Person(firstName: "John", lastName: "Appleseed")
}
// Equality (==) is not effective when John cannot be identified by his
name alone
imposters.contains {
  $0.firstName == john.firstName && $0.lastName == john.lastName
} // true

通過使用id操作符习瑰,你可以驗證引用本身是否相等绪颖,并將我們真正的John從人群中分離出來:

// Check to ensure the real John is not found among the imposters.
imposters.contains {
  $0 === John
} // false
// Now hide the "real" John somewhere among the imposters.
imposters.insert(john, at: Int(arc4random_uniform(100)))
// John can now be found among the imposters.
imposters.contains {
  $0 === John
} // true
// Since `Person` is a reference type, you can use === to grab the real
John out of the list of imposters and modify the value.
// The original `john` variable will print the new last name!
if let indexOfJohn = imposters.index(where: { $0 === john }) {
  imposters[indexOfJohn].lastName = "Bananapeel"
}
john.fullName // John Bananapeel

你可能會發(fā)現(xiàn),你不會在日常的Swift中使用identity操作符==甜奄。重要的是了解它的功能柠横,以及它對引用類型的屬性的說明。

方法和可變性

正如您以前所讀到的贺嫂,類的實例是可變對象滓鸠,而結(jié)構(gòu)的實例是不可變的值。下面的例子說明了這個區(qū)別:

struct Grade {
  let letter: String
  let points: Double
  let credits: Double
}
class Student {
  var firstName: String
  var lastName: String
  var grades: [Grade] = []
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}
let jane = Student(firstName: "Jane", lastName: "Appleseed")
let history = Grade(letter: "B", points: 9.0, credits: 3.0)
var math = Grade(letter: "A", points: 16.0, credits: 4.0)
jane.recordGrade(history)
jane.recordGrade(math)

注意第喳,recordGrade(_:)可以通過向末尾添加更多的值來改變數(shù)組grades糜俗。盡管該方法會改變當(dāng)前對象,但不需要關(guān)鍵字mutating。

如果你用一個struct嘗試過這樣做悠抹,那么你將會得到一個失敗珠月,因為結(jié)構(gòu)是不可變的。記住楔敌,當(dāng)你改變一個結(jié)構(gòu)的值時啤挎,你不是在修改它的值,而是在創(chuàng)造一個新的值卵凑。關(guān)鍵詞mutating標(biāo)記方法用一個新值替換當(dāng)前值庆聘。使用類時,這個關(guān)鍵字不會被使用勺卢,因為實例本身是可變的

可變性和常量

前面的例子可能讓你困惑伙判,jane被定義為一個常量,卻被修改啦黑忱。

當(dāng)你定義一個常數(shù)時宴抚,常數(shù)的值是不能改變的。如果你回想一下關(guān)于值類型和引用類型的討論甫煞,請記住菇曲,使用引用類型時,值是引用抚吠。


QQ20180522-164021@2x.png

紅色中“reference1”的值是存儲在jane中的值常潮。這個值是一個引用,因為jane被聲明為一個常量楷力,這個引用是常量蕊玷。如果你試圖將另一個學(xué)生分配給jane,你會得到一個構(gòu)建錯誤:

// Error: jane is a `let` constant
jane = Student(firstName: "John", lastName: "Appleseed")

如果你將jane聲明為一個變量弥雹,那么你就能夠為它分配另一個在堆上的學(xué)生實例:

var jane = Student(firstName: "Jane", lastName: "Appleseed")
jane = Student(firstName: "John", lastName: "Appleseed")

在另一個學(xué)生被指派給jane后垃帅,jane后面的引用的值會被更新,以指向新的Student對象剪勿。


QQ20180522-164303@2x.png

因為沒有任何東西引用原始的“Jane”對象贸诚,它的內(nèi)存將被釋放到其他地方。你將在“異步閉包和內(nèi)存管理”中了解更多厕吉。

一個類的任何單個成員都可以通過使用常量來保護酱固,但是由于引用類型本身并不是作為值,它們不能作為一個整體從變化中得到保護头朱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末运悲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子项钮,更是在濱河造成了極大的恐慌班眯,老刑警劉巖希停,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異署隘,居然都是意外死亡宠能,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門磁餐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來违崇,“玉大人,你說我怎么就攤上這事诊霹⌒哐樱” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵脾还,是天一觀的道長肴楷。 經(jīng)常有香客問我,道長荠呐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任砂客,我火速辦了婚禮泥张,結(jié)果婚禮上汹胃,老公的妹妹穿的比我還像新娘喷楣。我一直安慰自己夜赵,他們只是感情好眠副,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布瞒瘸。 她就那樣靜靜地躺著伟桅,像睡著了一般雕蔽。 火紅的嫁衣襯著肌膚如雪刊侯。 梳的紋絲不亂的頭發(fā)上声离,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天芒炼,我揣著相機與錄音,去河邊找鬼术徊。 笑死本刽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赠涮。 我是一名探鬼主播子寓,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笋除!你這毒婦竟也來了斜友?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤垃它,失蹤者是張志新(化名)和其女友劉穎鲜屏,沒想到半個月后烹看,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡墙歪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年听系,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虹菲。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡靠胜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毕源,到底是詐尸還是另有隱情浪漠,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布霎褐,位于F島的核電站址愿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏冻璃。R本人自食惡果不足惜响谓,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望省艳。 院中可真熱鬧娘纷,春花似錦、人聲如沸跋炕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辐烂。三九已至遏插,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纠修,已是汗流浹背胳嘲。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扣草,地道東北人胎围。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像德召,于是被迫代替她去往敵國和親白魂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理上岗,服務(wù)發(fā)現(xiàn)福荸,斷路器,智...
    卡卡羅2017閱讀 134,638評論 18 139
  • 126.析構(gòu)器 在一個類實例銷毀前,一個析構(gòu)器會立即調(diào)用肴掷。使用deinit 關(guān)鍵字來表示析構(gòu)器, 跟構(gòu)造器寫法類似...
    無灃閱讀 787評論 0 4
  • 結(jié)構(gòu)體和類模塊分兩篇筆記來學(xué)習(xí): 第一篇: 結(jié)構(gòu)體和類的區(qū)別 分析類和結(jié)構(gòu)體可變性 以一個具體的例子來學(xué)習(xí)使用類和...
    SmartisanBool閱讀 892評論 0 4
  • F303申某某(Aglarn)第七次作業(yè)非暴力溝通訓(xùn)練營第三期 我想起前面一段時間朋友跟我說的敬锐,他躲在一邊抽煙被他...
    申某某Aglarn閱讀 269評論 1 2
  • 一- 回憶似繭背传,密不透風(fēng)。- 蟬為了七天的短暫生命台夺,在塵土里蜇伏七年径玖,暗無天日,只為在陽光最燦爛的盛夏破土而生颤介,擁...
    游牧_5258閱讀 450評論 0 0