高级类(三)

何时以及为什么要子类化。

本章介绍了类的继承,以及子类化相关的众多编程技术。

但是你可能会问,“什么时候我应该子类化?”

对于这个问题,很少有正确或错误的答案。辩证的看点这个问题可以帮助你为任何特殊情况做出最佳决策。

以Student学生和StudentAthlete学生运动员类为例,你可以简单地把StudentAthlete学生运动员的所有特征都放在Student学生身上:

class Student: Person {
  var grades: [Grade]
  var sports: [Sport]
  // original code
}

这可以解决所有需要的用例。一个不做运动的学生只会有一个空的sports数组,你可以避免一些额外的复杂的子类化。

单一职责

在软件开发中,单一职责是说,声明的任何类都应该有一个单独职责。在学生和学生运动员中,学生有学生的责任,运动员有运动员的责任,学生的责任不应该封装到运动员里,运动员的也不应该封装到学生里。

强类型

使用Swift的类型系统,子类化创建了一个附加类型。你可以基于学生运动员而不是普通学生的对象声明属性或行为:

class Team {
  var players: [StudentAthlete] = []
  var isEligible: Bool {
    for player in players {
      if !player.isEligible {
        return false
      } 
    }
    return true
  } 
}

一个队有运动员,他们是学生运动员。如果你试图将一个普通的学生对象添加到players数组中,类型系统将不允许。这很有用,因为编译器可以帮助你执行系统的逻辑和需求。

共享基类

你可以通过具有互斥行为的类,多次子类化一个共享基类:

// A button that can be pressed.
class Button {
  func press() {}
}
// An image that can be rendered on a button
class Image {}
// A button that is composed entirely of an image.
class ImageButton: Button {
  var image: Image
  init(image: Image) {
    self.image = image
  }
}

// A button that renders as text.
class TextButton: Button {
  var text: String
  init(text: String) {
    self.text = text
  }
}

在本例中,你可以想象许多Button子类只共享按下的事件。因此当按下按钮时,Button子类必须实现自己的行为。ImageButton和TextButton类有完全不同的机制来呈现按钮的外观。

你可以在这里看到,在Button类中存储图像和文本——更不用说可能出现的任何其他类型的按钮,还会有其他的样式。因此按钮与媒体行为有关,只有处理按钮的实际外观的子类才是有意义的。

可扩展性

有时,如果你要扩展你的代码不拥有的行为,那么你就必须进行子类化。在上面的示例中,可能Button是你正在使用的框架的一部分,并且你不可能修改或扩展源代码以满足你的需要。

在这种情况下,子类化Button可以添加自定义子类,并使用这种类型按钮对象来使用Button。

同一性

最后,重要的是要理解类和类层次结构模型的对象是什么。如果你的目标是在类型之间共享行为(对象可以做什么),那么通常你应该更喜欢协议而不是子类化。

理解类的生命周期

在前一章中,你了解到对象是在内存中创建的,它们存储在堆中。堆上的对象不会被自动销毁,因为堆只是一个巨大的内存池。如果没有调用堆栈的实用程序,就没有自动的方法来知道一个内存将不再被使用。

在Swift中,决定何时清理堆上未使用的对象的机制称为引用计数。简而言之,每个对象都有一个引用计数,每一个常量或变量对该对象的引用都会递增,并且每次删除引用时都会递减。

注意:在其他书籍和在线资源中,你可能会看到引用计数被称为“保留计数”。他们指的是同一件事!

当引用计数达到0时,这意味着该对象现在被放弃,因为系统中没有任何引用。当这种情况发生时,Swift将清理对象。

这里展示了一个对象的引用计数的变化。注意,在这个示例中只创建了一个实际对象;一个对象有很多引用。

var someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Person object has a reference count of 1 (someone variable)
var anotherSomeone: Person? = someone
// Reference count 2 (someone, anotherSomeone)
var lotsOfPeople = [someone, someone, anotherSomeone, someone]
// Reference count 6 (someone, anotherSomeone, 4 references in
lotsOfPeople)
anotherSomeone = nil
// Reference count 5 (someone, 4 references in lotsOfPeople)
lotsOfPeople = []
// Reference count 1 (someone)
someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Reference count 0 for the original Person object!
// Variable someone now references a new object

在本例中,你不需要自己做任何工作来增加或减少对象的引用计数。这是因为Swift具有自动引用计数或ARC的特性。

虽然一些较旧的语言要求你在代码中增加和减少引用计数,但是Swift编译器在编译时自动添加这些调用。

注意:如果你使用像C这样的低级语言,那么你需要手动释放内存。像Java和c#这样的高级语言使用了称为垃圾收集的东西。在这种情况下,运行时,将在清理不再使用的对象之前,搜索进程引用的对象。垃圾收集虽然比ARC更强大,但它带来的内存利用率低和性能成本高,所以苹果公司决定不接受移动设备或通用系统语言。

Deinitialization

当一个对象的引用计数达到0时,Swift将对象从内存中移除,并将其标记为空闲内存。

deinitializer是一个特殊的方法,它在对象的引用计数达到0时运行,但是在Swift将对象从内存中移除之前。

修改类Person如下:

class Person {
  // original code
  deinit {
    print("\(firstName) \(lastName) is being removed
          from memory!")
  }
}

很像init是类初始化中的一种特殊方法,deinit是一个处理初始化的特殊方法。与init不同,deinit不是必需的,并且会被Swift自动调用。你也不需要覆盖它或在其中调用super。Swift将确保调用每个类的deinitializer。

如果你添加这个deinitializer,在运行前一个示例后的调试区域中,你将看到Johnny Appleseed正在被从内存中删除的消息!

你在deinitializer中所做的事情取决于你。通常,你将使用它来清理其他资源,将状态保存到磁盘,或者在对象超出范围时执行你可能需要的任何其他逻辑。

循环引用和弱引用

由于Swift的类依赖于引用计数来将它们从内存中删除,所以理解retain cycle的概念非常重要。

添加一个代表同学的字段—例如,一个实验室伙伴—和一个deinitializer方法,这样的学生:

class Student: Person {
  var partner: Student?
  // original code
  deinit {
    print("\(firstName) is being deallocated!")
  }
}
var alice: Student? = Student(firstName: "Alice",
                              lastName: "Appleseed")
var bob: Student? = Student(firstName: "Bob",
                            lastName: "Appleseed")
alice?.partner = bob
bob?.partner = Alice

现在假设alice和bob都辍学了:

alice = nil 
bob = nil

如果你在你的playground上运行这个,你会注意到,Swift不调用deinit。这是为什么呢?

Alice和Bob各自有一个引用,所以引用计数永远不会达到零!更糟糕的是,通过给alice和bob分配nil,就没有对初始对象的引用了。这是一个循环引用的经典案例,它导致了一个被称为内存泄漏的软件缺陷。

在内存泄漏的情况下,即使它的实际生命周期已经结束,内存也不会释放出来。循环引用是内存泄漏最常见的原因。

幸运的是,有一种方法,学生对象可以引用另一个学生而不容易循环引用,这是通过使用弱引用。

class Student: Person {
  weak var partner: Student?
  // original code
}

这个简单的修改将伙伴变量标记为弱,这意味着该变量中的引用不会参与引用计数。当引用不弱时,它被称为强引用,这是Swift的默认值。弱引用必须声明为可选类型,以便当它们引用的对象被释放时,它会自动变为nil。

关键点

•类继承是类最重要的特性之一,它支持多态性。
•Swift类使用两阶段初始化作为安全措施,确保在使用所有存储属性之前都进行了初始化。
•子类化是一个强大的工具,但是知道什么时候子类化是很好的。当你想要扩展一个对象,并且可以从子类和超类之间的“is-a”关系中受益,但是要注意继承的状态和深层的类层次结构。
•类实例有它们自己的生命周期,它们由它们的引用计数控制。
•自动引用计数,或ARC,称为自动处理引用计数,但重要的是要注意循环引用。

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

推荐阅读更多精彩内容