Swift中的ARC相关

news/2024/7/20 15:08:11 标签: swift, 内存管理, c/c++

关于内存管理

当我们选择这条职业道路的时候,不可避免的我们都要内存管理打交道。无论是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则友好一点我们也更加习惯使用它,最常见的场景就是:

  1. 设置delegate时

  2. 在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没有被释放。注意这个前提,前提不成立的话,还是可能会出现问题。


http://www.niftyadmin.cn/n/1678297.html

相关文章

Ext JS 4前瞻:快速、易用和稳定

很棒新特性: 图表 : Most people have used Ext JS 3’s Flash-based charting package. You’ll probably know that it’s good at the basics but is really hard to customize. For Ext JS 4.0 we’ve created a brand new charting package that is…

Android小技巧:json转map

在处理json时&#xff0c;我们一般是把json转为类对象&#xff0c;但是有时候我们不能明确知道json中的数据是什么&#xff0c;需要把它转为map去处理。 /*** 将json格式的字符串转成Map对象*/private static Map<String, String> jsonToHashMap( JSONObject jsonObject)…

个人小项目原型图设计(主页)

个人小项目原型图设计&#xff08;主页&#xff09;一、前言二、了解网站的布局三、前端界面绘制&#xff08;主页&#xff09;1、“T”字形2、“口”字形3、“同”字形4、“三”字形5、“川”字形四、总结一、前言 在我制作个人项目的时候&#xff0c;经常会要自己独立去完成一…

[转载]:【读书笔记】.NET本质论

【读书笔记】.NET本质论 引自&#xff1a; http://www.cnblogs.com/yuyijq/category/187749.html 【读书笔记】.NET本质论第四章-Programming with Type(Part Two)摘要: 上一篇简单的叙说了一下不同类型内存分配的问题&#xff0c;以及如何在源代码上控制内存的布局&#xff0c…

Photoshop画小项目原型图设计

Photoshop画小项目原型图设计一、前言二、网站的布局三、前端界面绘制&#xff08;使用Photoshop&#xff09;1、“T”字形绘制第一步&#xff1a;新建画布第二步&#xff1a;新建图层第三步&#xff1a;图形工具第四步&#xff1a;插入图片第五步&#xff1a;输入文字第六步&a…

设计模式笔记---6大设计原则

注意&#xff1a;这是个人的学习笔记&#xff0c;针对重点内容进行了摘录&#xff0c;不是具体的学习资料 1、单一职责原则单一职责原则的定义是&#xff1a;应该有且仅有一个原因引起类的变更。例如下图中&#xff0c;用户类包含两种职责&#xff0c;1是用户信息&#xff08;用…

flex 字体加载方式3

flex 加载方式3&#xff1a;通过actionScript加载&#xff0c;通过loader加载字体然后调用getDefinitionByName()获得字体Class&#xff0c;注册字体后&#xff0c;就可以用了。 package{import flash.display.Loader;import flash.display.Sprite;import flash.events.Event;i…

管理Active Directory的工具

一、Dsadd 用于在特定的目录分区上创建 Active Directory 对象类的实例。这些类包括用户、计算机、联系人、组、组织单位和配额。Dsadd 具备一个由下列内容组成的通用语法&#xff1a;dsadd <ObjectType> <ObjectDistinguishedName> attributes请注意&#xff0c;您…