iOS之ARC和循环引用

ARC简介

        Swift使用ARC(自动引用计数)来跟踪和管理应用程序的内存使用情况。大多数情况下,您不需要自己考虑内存管理。当不在需要类实例时,ARC会自动释放类实例使用的内存。结构体和枚举是值类型,而不是引用类型,他们不是通过引用传递和存储,所以ARC不会管理他们。

ARC的工作原理

        我们每次创建一个类的实例时,ARC都会分配一块内存来存储有关该实例的信息。此内存保存有关实例类型的信息,以及与该实例关联的任何存储属性的值。当不在需要某个实例时,ARC会释放该实例使用的内存,以便将内存用于其他目的,这确保了类实例在不再需要时不会占用内存。但是,如果ARC取消分配仍在使用的实例,则无法在访问该实例的属性或调用该实例的方法,如果你尝试访问该实例,那么你的应用程序很可能会奔溃。

        为了确保实例在仍然需要时不会消失,ARC会跟踪当前引用每个类实例的属性、常量和变量的数量,只要该实例的至少一个活动引用仍然存在(至少有一个对该实例有强引用),ARC就不会释放该实例。下面我们来看一个自动引用计数的示例:

定义Person

        从上图看到我们定义了三个Person对象,但是他们都是可选类型,所以它们在内存总并不存在,他们的值也是nil,并且也不会引用Person实例

初始化person1

        从上图看出,消息是在调用类的初始化时打印的,这说明初始化已经发生。当我们把Person(name: "张三")赋值给person1后,person1不再为nil,person1这时对Person有了一个强引用,ARC确保它保存在内存中并且不会被释放。

赋值和置为nil

        当我们把person1赋值给了person2person3,这两个实例没有被初始化,只是它们强引用了和person1同样的Person实例。当我们把person1person2这两个实例都置为nil之后,deinit函数并不会被执行,因为ARC知道还有person3在引用着person1。所以该实例仍然不会被释放。

全置为nil

        当我们把person3也置为nil时,person1才会被释放,并调用deinit函数,打印张三 被释放

类实例之间的强引用循环

        在上面的实例中,ARC能够追踪对你创建的新实例的引用数量,并在不需要该实例时释放该实例。但是,在编写代码中,其中类的实例不会达到它没有一个强引用的程度。如果两个类相互持有强引用,则可能会发生这种情况,这样每个实例都会使另一个实例保持活动状态,这称为强参考循环。下面,我们来了解下这种循环是怎么产生的:

        每个Person实例都有一个name属性和一个apartment的可选属性,它最初为nil,该apartment属性是可选的,因为一个人可能并不总是有公寓的。类似,每个Apartment实例都有一个unit属性和一个tenant的可选属性,它最初为nil,该tenant属性是可选的,因为公寓可能并不总是有租户。

        上图中,我们定义了john和unit4A的可选类型变量,并创建了一个特定的Person实例和Apartment实例,将这些实例分配给john和unit4A变量。

下图中是它们的引用关系,

        我们将这两个实例链接在一起,以便人拥有公寓,公寓拥有人。

john!.apartment = unit4A

unit4A!.tenant = john

        现在它们的引用关系变为下图:

        当我们把两个实例置为nil的时候,ARC会把他们的引用计数置为0,这是没有错的,但是控制台却没有打印任何释放实例的信息?

        此时我们在看它们的引用情况:

        我们发现他们自已已经移除了对Person和Apartment的互相引用,但是ARC依然在追踪他们的属性的引用计数,Person实例和Apartment实例之间的强引用仍然存在,不能被破坏。

解决循环引用的两种方案

        当您使用类类型的属性时,Swift 提供了两种解决强引用循环的方法:弱引用(weak)和无主引用(unowned)。

    弱引用

        为了打破强引用循环,可以将相互引用的实例之间的关系设置为弱(weak)

        基本情况下,所有的引用都是强引用,会影响引用计数。弱引用则不会增加对象的引用计数,另外:弱引用永远都要声明为可选类型,因此必须使用 var 声明;当引用数变为0时,引用会自动的设置为 nil

    无主引用

        与弱引用一样,无主引用不会对其所引用的实例保持强引用。

    unowned 和 weak 有什么区别呢?

        弱引用必须为可选类型,当引用的对象不存在时,自动设置为 nil

        无主引用则不能为可选类型,如果你引用一个已经被销毁的对象属性,会抛出错误。

        由上图可以看出,我们将apartment和tenant属性用weak修饰后,它们就会正常被释放。它们的关系图是:

闭包的循环引用(Closure/Block)

        我们在上面看到了当两个类实例属性相互持有强引用时如何创建强引用循环,还看到了如何使用弱引用和无主引用来打破这些强引用循环。而我们将闭包分配给类实例的属性,并且该闭包的主体捕获该实例,也会发生强引用循环。

        之所以会出现这种强引用循环,是因为闭包和类一样,都是引用类型。下面我们来看Swift是如何发生这种问题,以及应该怎样去解决这样的问题?


在HTMLElement我们定义了三个属性,其中asHTML是一个懒加载的闭包属性,并且返回()-String的一个函数。我们想要打印出来像这种样式的html语句:<h1>Hello World!</h1>,我们来写下

        我们为heading.asHTML赋值,并且打印他,发现h1已经被释放,因为它并没有走asHTML的存储属性的get函数,所以没有捕获到self,因此并不会造成循环引用。下面我们来从新改下:

        我们发现即使将实例paragraph置为nil,该实例依然没有被释放。是因为该闭包已经造成了一个循环引用。引用关系如下:

        实例的asHTML属性持有对其闭包的强引用。但是,由于闭包self在其主体内引用,因此闭包会捕获self,这意味着它持有对HTMLElement实例的强引用。两者之间形成了一个循环引用。

解决闭包的循环引用

        我们知道了闭包造成的循环引用,我们依然需要用weak或unwoned来打破循环引用,我们知道weak是当被修饰的属性被置为nil之后,ARC就会帮助我们销毁该引用,并将retainCount记为0。然而我们的闭包持有了self,而self又持有了闭包,所以self是不可能为nil的,如果为nil,那我们调用该asHTML将会永远crash,所以我们最合适的方式是使用unowned。

        由上图我们可以看出,该实例被正常释放。那么它们之间的引用关系就变为了如下图所示:

        这样我们也就解决了闭包的循环引用。

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

推荐阅读更多精彩内容

  • ARC简介 ARC负责跟踪和管理应用程序的内存状态,一般情况下不需要考虑内存管理问题,当一个实例不再需要时,ARC...
    Hellolad阅读 695评论 0 2
  • Swift 枚举 枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数...
    齐玉婷阅读 667评论 0 1
  • Objective-C 采用的是引用计数式的内存管理方式: 自己生成的对象自己持有。 非自己生成的对象自己也能持有...
    s_在路上阅读 1,251评论 0 10
  • 自动引用计数 Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内...
    xiaofu666阅读 383评论 0 0
  • Cocoa内存管理机制 (1)当你使用new、alloc、copy方法创建一个对象时,该对象的保留计数器值为1.当...
    John_LS阅读 2,761评论 0 6