类(一)

作者 Cosmin Pupăză

从结构中学习到了命名类型。在本章中你将学习另一种具有属性和方法的命名类型,类,它与结构非常像。

类是引用类型,与值类型相反,类有不同于值类型的优点和缺点。你通常会使用结构去表示值,用类去表示对象。

那么,什么是值,什么是对象呢?

创建类

思考下面类的定义:

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")

是不是很简单!类几乎与结构完全相同。关键字 class 在类名的前面,花括号内都是类的成员。

但是你可以看到类与结构的不同,上面的类定义了一个初始化方法,它为firstName和lastName设置初始值。与结构不同的是,一个类并不会自动地提供一个成员初始化方法,这意味着如果你需要的话,你必须自己编写它。

如果你忘记编写初始化方法,swift编译器会提醒你:


QQ20180522-103212@2x.png

除了初始化方法之外,类和结构的初始化规则页非常类似。类初始化方法是名为init的函数,所有存储属性必须在init结束前分配初始值。

实际上类还有更多的初始化方式,但是你必须等到下一章“高级类”,它将向你介绍继承的概念以及它对初始化规则的影响。现在,通过使用基本类初始化方法,你可以轻松地使用Swift的类。

引用类型

在Swift中,结构的实例是不可变的值。另一方面,类的实例是一个可变对象。因为类是引用类型,类型的变量不存储实际实例,而是引用存储实例在内存中的位置。如果你要创建一个SimplePerson类实例:

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

它在内存中是这样的:


QQ20180522-104707@2x.png

如果你要创建一个新的变量var2并将其赋值为var1:

var var2 = var1

然后,var1和var2中的引用将在内存中引用相同的位置。


QQ20180522-105009@2x.png

相反,作为值类型的结构存储实际值,提供对其的直接访问。将SimplePerson类实现替换为这样的结构:

struct SimplePerson {
  let name: String
}

在内存中,变量不会引用内存中的某个位置,但是该值将只属于var1:


QQ20180522-105120@2x.png

赋值var var2 = var1在本例中复制var1的值:


QQ20180522-105154@2x.png

值类型和引用类型各有各自的优缺点。在本章后面,你将考虑在给定的情况下使用哪种类型。现在,让我们来看看类和结构是如何工作的。虽然下面的描述并不适用于所有情况,但是要记住它是一个很好的一阶模型。

堆&堆栈

当你创建一个引用类型(如类)时,系统将实际的实例存储在一个称为堆的内存区域中。值类型的实例(例如结构体)驻留在称为堆栈的内存区域中,除非该值是类实例的一部分,在这种情况下,该值将与类实例的其余部分一起存储在堆中。

堆和堆栈在执行任何程序时都有重要的作用。对它们是什么以及它们如何工作的一般理解将帮助你可视化类和结构之间的功能差异:

•系统使用堆栈来存储任何立即执行的线程;它受到CPU的严格管理和优化。当函数创建一个变量时,堆栈存储该变量,然后在函数退出时销毁它。由于这个堆栈组织得很好,所以它非常高效,因此也非常快。

•系统使用堆来存储引用类型的实例。堆通常是一个大内存池,系统可以请求并动态地分配内存块。生命是灵活多变的。堆不会像堆栈那样自动销毁它的数据;这样做需要额外的工作。这使得在堆上创建和删除数据比在堆栈上更慢。

也许你已经知道了它与结构和类的关系。看看下面的图表:


QQ20180522-114312@2x.png

•当你创建一个类的实例时,你的代码请求堆上的一个内存块来存储实例本身;如图右侧的实例中的第一个名称和最后一个名称。它将该内存的地址存储在堆栈上的命名变量中;右侧是对存储在图左侧的引用。
•当你创建一个结构的实例(它不是类实例的一部分)时,实例本身就存储在堆栈中,而堆从未涉及到。

这只是简单介绍了堆和堆栈的动态,但你现在已经知道了足够的知识来理解用于与类工作的引用语义。

使用引用

在“结构”中,你看到了在使用结构和其他值类型时所涉及的复制语义。这里有一个小提示,利用这一章的Location和DeliveryArea结构:

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

当你将area1的值赋给area2时,area2将收到area1值的副本。当area1.range 收到一个新的值4,这个数字只反映在area1中,而area2仍然具有原来的2.5的值。

由于类是一个引用类型,当你分配给变量给类时,系统不会复制实例;只复制引用。

将前面的代码与下面的代码进行对比:

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

正如你所看到的,john和homeOwner的值是一样的!

这种在类实例之间的隐含共享导致了一种新的思维方式。例如,如果john对象发生了变化,那么任何引用john的东西都会自动看到更新。如果你使用的是一个结构,那么您必须单独更新每个副本,否则它仍然具有“Johnny”的旧值。

对象id

在前面的代码示例中,可以看到john和homeOwner都指向同一个对象。两个引用都是命名变量。如果你想知道变量后面的值是否是John怎么办?

你可能会想要检查firstName的值,但是你怎么知道它是你要找的John而不是冒名顶替者呢?更糟的是,如果john又改了名字会怎么样?

在Swift中,==操作符让你检查一个对象的id是否等于另一个对象的id:

 john === homeOwner // true

当==操作符检查两个值是否相等时,===身份操作符比较两个引用的内存地址。它告诉你引用的值是否相同;也就是说,它们指向堆上的同一块数据。

这意味着这个===操作符可以区分出你要找的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

当您不能依赖于常规的等式(==)来比较和识别你关心的对象时,这一点特别有用:

// 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

你可能会发现,你不会在日常的Swift中使用identity操作符==。重要的是了解它的功能,以及它对引用类型的属性的说明。

方法和可变性

正如您以前所读到的,类的实例是可变对象,而结构的实例是不可变的值。下面的例子说明了这个区别:

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(_:)可以通过向末尾添加更多的值来改变数组grades。尽管该方法会改变当前对象,但不需要关键字mutating。

如果你用一个struct尝试过这样做,那么你将会得到一个失败,因为结构是不可变的。记住,当你改变一个结构的值时,你不是在修改它的值,而是在创造一个新的值。关键词mutating标记方法用一个新值替换当前值。使用类时,这个关键字不会被使用,因为实例本身是可变的

可变性和常量

前面的例子可能让你困惑,jane被定义为一个常量,却被修改啦。

当你定义一个常数时,常数的值是不能改变的。如果你回想一下关于值类型和引用类型的讨论,请记住,使用引用类型时,值是引用。


QQ20180522-164021@2x.png

红色中“reference1”的值是存储在jane中的值。这个值是一个引用,因为jane被声明为一个常量,这个引用是常量。如果你试图将另一个学生分配给jane,你会得到一个构建错误:

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

如果你将jane声明为一个变量,那么你就能够为它分配另一个在堆上的学生实例:

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

在另一个学生被指派给jane后,jane后面的引用的值会被更新,以指向新的Student对象。


QQ20180522-164303@2x.png

因为没有任何东西引用原始的“Jane”对象,它的内存将被释放到其他地方。你将在“异步闭包和内存管理”中了解更多。

一个类的任何单个成员都可以通过使用常量来保护,但是由于引用类型本身并不是作为值,它们不能作为一个整体从变化中得到保护。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 126.析构器 在一个类实例销毁前,一个析构器会立即调用。使用deinit 关键字来表示析构器, 跟构造器写法类似...
    无沣阅读 798评论 0 4
  • 结构体和类模块分两篇笔记来学习: 第一篇: 结构体和类的区别 分析类和结构体可变性 以一个具体的例子来学习使用类和...
    SmartisanBool阅读 892评论 0 4
  • F303申某某(Aglarn)第七次作业非暴力沟通训练营第三期 我想起前面一段时间朋友跟我说的,他躲在一边抽烟被他...
    申某某Aglarn阅读 272评论 1 2
  • 一­ 回忆似茧,密不透风。­ 蝉为了七天的短暂生命,在尘土里蜇伏七年,暗无天日,只为在阳光最灿烂的盛夏破土而生,拥...
    游牧_5258阅读 453评论 0 0