strong、weak和unowned的区别

编写代码时需注意是否产生了循环引用,因此就产生了什么时候使用weakunowned问题?这篇文章将介绍 Swift 中的strongweakunowned的区别。

1. ARC

自动引用计数(即 Automated Reference Count,简称 ARC)是 Xcode 4.2版本的新特性,其与手动管理内存使用了相同的计数系统。不同点在于:系统在编译时会帮助我们插入合适的内存管理方法,保留和释放都会自动进行,避免了手动管理引用计数的一些潜在问题。

Swift 使用自动引用计数跟踪、管理app的内存。通常情况下,这意味着ARC会自动管理内存,开发者无需关注内存管理。当类的实例不再使用时,ARC会自动释放其占用的内存。

为帮助管理内存,ARC 有时需了解类之间的关系。在 Swift 中使用 ARC 与在 Objective-C 中使用 ARC 类似。

引用计数只适用类的实例。结构体和枚举是值类型,不是引用类型,存储和传递的时候并非使用引用。

2. strong

strong指针通过增加指向对象的引用计数,保护被指向对象不被ARC释放。即,只要有一个强指针指向该对象,它就不会被释放。

Swift 中声明的属性默认是strong。当对象间引用关系是线性时,使用strong指针不会产生问题。

当两个实例使用强指针指向彼此时,两个实例引用计数都不会变为零,即产生循环引用(strong reference cycle)。

下面是一个循环引用的示例:

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitizlized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    
    var tenant: Person?
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

上面定义了两个类:PersonApartment,代表住户和公寓。两个类都实现了deinitializer方法,当类的实例销毁时进行打印,方便观察实例占用的内存是否释放了。

下面代码定义了两个可选类型的变量,初始值为nil。并为其分配两个新的实例:

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

目前,john变量强引用Person实例,unit4A变量强引用Apartment实例,如下所示:

UnownedReferenceCycle01.png

现在连接两个实例。person持有apartmentapartment持有person

john?.apartment = unit4A
unit4A?.tenant = John

连接两个实例后,引用关系如下:

UnownedReferenceCycle02.png

Person的实例强引用了Apartment的实例,Apartment的实例强引用了Person的实例,即产生了循环引用。当移除johnunit4A的引用时,实例的引用计数不会变为零,也就是实例内存不会被ARC释放。

john = nil
unit4A = nil

设置johnunit4A变量为nil后,引用关系如下:

UnownedReferenceCycle03.png

PersonApartment实例之间的强引用无法破除。

3. 解决循环引用

Swift 提供了两种解决循环引用的方案:weakunownedweakunowned引用其它实例时不会产生强引用,引用计数不会加一。因此,不会产生循环引用。

当一个实例的生命周期短于另一个时(即一个实例可以先被销毁),使用weak引用。在上面公寓的示例中可能出现公寓没有住户的情况。因此,可以使用weak解决循环引用问题。当另一个实例生命周期与当前实例相同,或长于当前实例时,使用unowned引用。

3.1 weak引用

weak引用不会强持有引用的实例,也就不会阻止ARC释放实例。通过在声明属性、变量前添加weak关键字的方式使用弱引用。

当实例被销毁时,ARC 会自动设置弱指针为nil。由于弱指针在运行时可能被设置为nil,弱指针应被声明为可选类型的变量,而非常量。

和其它可选类型一样,可以检查弱引用值是否存在,这样就不会得到一个无效实例。

设置弱引用为nil时,不会调用属性观察器。

使用weak修饰之前实例Apartment中的tenant属性,更新后如下:

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitizlized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    
    // 使用weak修饰
    weak var tenant: Person?
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

下图是实例间引用关系:

UnownedWeakReference01.png

Person实例强引用Apartment实例,但Apartment实例没有强引用Person实例。当移除john实例对Person的强引用,Person实例就没有被强引用了,也就可以被销毁了。

3.2 unowned

weak一样,unowned指针也不会对指向的对象产生强引用,但unowned用在另一个实例生命周期一样或更长的情况。通过在声明属性、变量前添加unowned关键字的方式使用unowned

weak不同,unowned修饰的引用永远不为空。因此,标记为unowned的值不是可选类型,ARC 也不会将unowned引用设置为nil

只有确信引用不会被释放的时候才使用unowned,使用unowned修饰的对象被销毁后再次访问会产生运行时错误。

现在定义两个类:CustomerCreditCardCustomer是银行的客户,CreditCard是该客户的银行卡。CustomerCreditCard类都有一个属性持有彼此,这种持有关系会产生强引用。

CustomerCreditCard的关系与PersonApartment的关系稍有不同。Customer可能持有CreditCard,也可能不持有CreditCard;但CreditCard不会脱离Customer而存在。即Customer有一个可选类型的card属性,CreditCard有一个 unowned 的customer属性。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit {
        print("Card #\(number) is being deinitialized")
    }
}

下面创建Customer实例,并使用该实例创建CreditCard,如下所示:

var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)

其引用关系如下:

UnownedUnownedReference01.png

Customer实例强引用CreditCard实例,CreditCard实例 unowned Customer实例。

john变量取消对实例的强引用后,就没有强引用指向该实例,该实例就会被销毁。该实例销毁后,没有强引用指向CreditCard,其也会被销毁。

上面的示例介绍了如何使用 safe unowned 引用,Swift 同时提供了 unsafe unowned 引用,其可以避免 runtime 的安全检查,提高性能。使用 unsafe 相关操作时,开发者需自行检查其是否存在,确保安全。

使用unowned(unsafe)标记 unsafe unowned 引用。当实例销毁后,再次访问实例会直接访问销毁前的内存地址。

参考资料:

  1. What is the difference in Swift between 'unowned(safe)' and 'unowned(unsafe)'?
  2. Automatic Reference Counting

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/strong%E3%80%81weak%E5%92%8Cunowned%E7%9A%84%E5%8C%BA%E5%88%AB.md

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

推荐阅读更多精彩内容