资讯专栏INFORMATION COLUMN

Swift中的ARC相关

刘明 / 2422人阅读

摘要:再次提醒一下中的只针对引用类型对象。对于我们中的大多数来说,我们其实并不清楚内存中发生的了什么。那就是循环引用,类似于操作系统中的死锁。除了声明外,还有一个不常用的关键字,于类似该声明也是表面非持有关系,当时两种存在区别。

关于内存管理

当我们选择这条职业道路的时候,不可避免的我们都要内存管理打交道。无论是C中的malloc、free还是C++中的new、delete。它如此重要又如此麻烦易错。为了把大家从内存管理解脱出来,C++中引入了智能指针,iOS中引入了ARC(automatic reference counting),其实两种的原理都是一样的就是对动态分配的对象进行自动引用技术确保对象能够正确销毁,防止出现内存泄露。下面我们就一起了解一下Swift中该机制。

Swift中的ARC

在Swift中对于引用类型的对象采用的就是自动内存管理,也就是说系统会帮我们处理好内存的申请和分配。当我们初始化创建对象的时候系统自动分配内存,而在释放内存的时候会遵循自动引用计数原则:当对象的引用计算为0的时候内存会自动被回收。这样一来我们只需要将我们的注意力放在在合适的地方置空引用就行了。再次提醒一下:Swift中的ARC只针对引用类型对象。首先,我们从一段简单的Objective-C代码来认识该机制:

NSObject *box = [NSObject new];
//More code, and then later on…
[box release];  

该段代码中我们先创建了一个NSObject类对象box,此时对象的retain计数为1,后面我们对box进行了release操作,那么此时的box的retain计数为0,然后系统会自动销毁box对象,box也就变为nil。

对于我们中的大多数来说,我们其实并不清楚内存中发生的了什么。这不是什么讽刺,仅仅是因为我们只是习惯性的享受便利,而放弃去探究其机制。但是这是不正确的,我们总会在某个时间会遇到于内存管理相关的内容,如果我们对这个特性仅仅停留在使用上面,而没有一个清新的认识的话那么当问题出现的时候,你会发现问题非常难调适(相信使用过C++的人对此肯定深有感触)。我们来看一个Swift的例子:

class Post
{
    var topic:String
    init(t:String)
    {
        self.topic = t
        print(“A post lives!”)
    }
    deinit
    {
        print(“A post has passed away ?”)
    }
}  

//An optional of type Post, so the default value is nil so far
var postRef:Post?
//Console prints "A post lives!", postRef has a strong ref
postRef = Post(t: "iOS")
//This instance of Post now has another strong ref, so its
//retain count now sits at 2
var anotherPostRef = postRef
//postRef equals nil, one strong ref is gone. Retain count is 1
postRef = nil
//ARC knew it still had a strong ref due to keeping a retain count.
//Thus, anotherPostRef is *not* nil
print(anotherPostRef)  

上面的例子很好理解,首先Post是一个引用类型适用ARC规则,后面定义了一个可选类型的变量默认为nil,然后初始化了该变量引用技术加1,后来又赋值给另一个变量再加1,在置空postRef此时引用减1,对象引用数不为0没有销毁可以继续打印anotherPostRef。如果我们在最后再添加 anotherPostRef = nil,此时对象就会自动销毁。

循环引用的坑

但是,自动引用计数机制中也有一个坑需要我们注意。那就是循环引用,类似于操作系统中的死锁。下面看一下示例代码:

class Post
{
    var topic:String
    var performance: Analytics?
    init(t:String)
    {
        self.topic = t
        print(“A topic lives!”)
    }
    deinit
    {
        print(“A topic has passed away ?”)
    }
}  

class Analytics
{
    var thePost:Post?
    var hits:Int
    init(h: Int)
    {
        self.hits = h
        print(“Some analytics are being served on up.”)
    }
    deinit
    {
        print(“Analytics are gone!”)
    }
}  

var aPost:Post? = Post(t: “ARC”)
var postMetrics:Analytics? = Analytics(h: 3422)
aPost!.performance = postMetrics
postMetrics!.thePost = aPost
//Ruh roh, postMetrics"s deinit func wasn"t called!
aPost = nil  

我们可以看到上面的代码中,析构函数并没有调用。因为aPost持有对象postMetrics,析构时也要析构postMetrics,而postMetrics对象析构的前提却是要析构aPost,这就造成了一个死锁。除非杀死进程否则这两个对象都不会被释放掉。

循环引用的解决

为了避免这种情况的出现,我们需要使两个实例不能互相持有对方,将类Analytics中的thePost变量声明改为:

weak var thePost: Post?  

我们在变量前面加上了weak声明,也就是告诉编译器表明我们并不希望持有thePost变量。因此当aPost = nil时对象都可以成功析构,会打印如下信息:

A topic has passed away ?
Analytics are gone!  

注意:因为weak声明的变量在变量析构后会将变量置为nil,所以变量一定时可选(Optional)类型。

除了weak声明外,还有一个不常用的关键字unowned,于weak类似该声明也是表面非持有关系,当时两种存在区别。Swift中的weakunowned对应Objective-C中的weakunsafe_unretained,这表明前者在对象释放后 会自动将对象置为nil,而后者依然保持一个“无效的”引用,如果此时调用这个“无效的”引用将会引起程序崩溃。对于后者的使 用,我们必须保证访问的时候对象没有释放。相反,weak则友好一点我们也更加习惯使用它,最常见的场景就是:

设置delegate时

在self属性存储为闭包,闭包中存在self时。

第一种情况常见就不说了,对于第二个其实是一个容易忽略的地方。闭包的一个特性就是对于闭包中的所有元素它都自动持有,因此如果闭包了包含了self的话,就会形成一个self -> 闭包 -> self的循环引用,可以采用下面的方法解决:

class Person {
    let name: String
    lazy var printName: ()->() = {
        [weak self] in
        if let strongSelf = self {
            print("The name is (strongSelf.name)")
        }
    }
    
    init(personName: String) {
        name = personName
    }
    
    deinit {
        print("Person deinit (self.name)")
    }
}

var BigNerd: Person? = Person(personName: "BigNerd")
BigNerd!.printName()
BigNerd = nil  

//输出:
The name is BigNerd
Person deinit BigNerd

此处是在闭包内部添加了weak声明来消除循环引用。此处也可以使用unowned,这样还可以省去if let的判断,因为整个过程中self没有被释放。注意这个前提,前提不成立的话,还是可能会出现问题。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16034.html

相关文章

  • 《The Swift Programming Language》2.0版之自动引用计数

    摘要:例子以一个简单的类开始,并定义了一个叫的常量属性类有一个构造函数,此构造函数为实例的属性赋值,并打印一条消息以表明初始化过程生效。类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。由此可以确定构造函数被执行。 Swift 1.0文档翻译:TimothyYe Swift 1.0文档校对:Hawstein Swift 2.0文档校对及翻译润色:Channe ...

    LeviDing 评论0 收藏0
  • iOS进阶

    摘要:然而,副作用对于系统的可测试性来说就是一剂毒药,并且可能会因应用程序和请求的不同而出现差异性。这些事件并不具备特定时序性,甚至它们可能同时发生。粘性动画中,粘性小球会根据移动距离的大小拥有不同的弹性程度。 PPAsyncDrawingKit - 实现了一系列基础 UI 控件的轻量级 ASDK 一款轻量级的 ASDK,实现了一系列基础 UI 控件。 iOS 开发之 Runtime 常用示...

    Cheng_Gang 评论0 收藏0
  • Swift随机数产生

    摘要:参考是一个十分优秀的随机数算法,并且在中也可以使用。但是的是和构架有关的在位的上实际上他是,而在位是。下面是使用函数求一个的随机数下面使用函数求一个的随机数在这里推荐两个库来方便你使用这两个库呢,都不是很大小巧精干。 参考Swifter arc4random是一个十分优秀的随机数算法,并且在Swift中也可以使用。它会返回给我们一个任意整数,我们想要在某个范围里的数的话,可以做模运算取...

    高璐 评论0 收藏0
  • Swift Tips

    摘要:和架构有关在位上及以前是,位上及以后为。在添加自定义操作符时,分别加上表示中位符前位符后位符。方法必须确保所有成员对象被初始化了。是强制子类必须重写的关键字。初始化方法返回后加或允许在初始化中返回来实例化对象。 Int和CPU架构有关 在32位CPU上(iphone5及以前)是Int32,64位上(5s及以后)为Int64。UInt同理。 可选链式调用 可选链式调用失败时,等号右侧的代...

    Bmob 评论0 收藏0
  • 从Java到Swift

    摘要:函数的定义形如函数可以返回多个返回值,这个功能真是太猛了。支持函数类型,根据输入参数和返回值确定一个函数类型。例如函数的参数可以另外一个函数,注意,不是另外一个函数的返回值,而是另外一个函数,只要类型符合即可。 我们学习的新事物时,通常并不是从0开始,而是从已知开始,将新事物与已知的进行比较分析,从而快速全面地了解新事物。而我熟悉Java,所以在学习Swift时,就会将Swift与Ja...

    lemon 评论0 收藏0

发表评论

0条评论

刘明

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<