iOS夯实:ARC时代的内存管理

iOS夯实:ARC时代的内存管理

什么是ARC

Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations [^1]

[^1]: Transitioning to ARC Release Notes

ARC提供是一个编译器的特性,帮助我们在编译的时候自动插入管理引用计数的代码。
最重要的是我们要认识到ARC的本质仍然是通过引用计数来管理内存。因此有时候如果我们操作不当,仍然会有内存泄露的危险。下面就总结一下ARC时代可能出现内存泄露的场景。

内存泄露类型

  1. 循环引用

    基于引用计数的内存管理机制无法绕过的一个问题便是循环引用(retain cycle)
    (Python同样也采用了基于引用计数的内存管理,但是它采用了另外的机制来清除引用循环导致的内存泄露,而OC和Swift需要我们自己来处理这样的问题[^2])

    • 对象之间的循环引用:使用弱引用避免
    • block与对象之间的循环引用:

    会导致Block与对象之间的循环引用的情况有:

    self.myBlock = ^{ self.someProperty = XXX; };  

    对于这种Block与Self直接循环引用的情况,编译器会给出提示。

    但是对于有多个对象参与的情况,编译器便无能为力了,因此涉及到block内使用到self的情况,我们需要非常谨慎。(推荐涉及到self的情况,如果自己不是非常清楚对象引用关系,统一使用解决方案处理)

    someObject.someBlock = ^{ self.someProperty = XXX; }; //还没有循环引用
    self.someObjectWithBlock = someObject; // 导致循环引用,且编译器不会提醒

    解决方案:

    __weak SomeObjectClass *weakSelf = self;
    
    SomeBlockType someBlock = ^{
        SomeObjectClass *strongSelf = weakSelf;
        if (strongSelf == nil) {
        // The original self doesn't exist anymore.
        // Ignore, notify or otherwise handle this case.
        }
        [strongSelf someMethod];
    };

    我们还有一种更简便的方法来进行处理,实际原理与上面是一样的,但简化后的指令更易用。

    @weakify(self)
    [self.context performBlock:^{
    // Analog to strongSelf in previous code snippet.
    @strongify(self)
    
    // You can just reference self as you normally would. Hurray.
    NSError *error;
    [self.context save:&error];
    
    // Do something
    }];

    你可以在这里找到@weakify,@strongify工具:MyTools_iOS

[^2]: How does Python deal with retain cycles?

  1. NSTimer

    一般情况下在action/target模式里 target一般都是被weak引用,除了NSTimer。

    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds
                            target:(id)target
                          selector:(SEL)aSelector
                          userInfo:(id)userInfo
                           repeats:(BOOL)repeats

    在官方文档中:

    target
    The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.

    Timer Programming Topics :

    A timer maintains a strong reference to its target. This means that as long as a timer remains valid, its target will not be deallocated. As a corollary, this means that it does not make sense for a timer’s target to try to invalidate the timer in its dealloc method—the dealloc method will not be invoked as long as the timer is valid.

    举一个例子,一个Timer的Target是ViewController.

    这个时候,如果我们是在dealloc方法里让timer invalidate,就会造成内存泄露.

    事实上,timer是永远不会被invalidate.因为此时VC的引用计数永远不会为零。因为Timer强引用了VC。而因为VC的引用计数不为零,dealloc永远也不会被执行,所以Timer永远持有了VC.

    因此我们需要注意在什么地方invalidate计时器,我们可以在viewWillDisappear里面做这样的工作。

Swift's ARC

在Swift中,ARC的机制与Objective-C基本是一致的。

相对应的解决方案:

  • 对象之间的循环引用:使用弱引用避免

protocol aProtocol:class{}

class aClass{
    weak var delegate:aProtocol?
}

注意到这里,aProtocol通过在继承列表中添加关键词class来限制协议只能被class类型所遵循。这也是为什么我们能够声明delegate为weak的原因,weak仅适用于引用类型。而在Swift,enumstruct这些值类型中也是可以遵循协议的。

  • 闭包引起的循环引用:

Swift提供了一个叫closure capture list的解决方案。

语法很简单,就是在闭包的前面用[]声明一个捕获列表。

let closure = { [weak self] in
    self?.doSomething() //Remember, all weak variables are Optionals!
}

我们用一个实际的例子来介绍一下,比如我们常用的NotificationCenter:

class aClass{
    var name:String
    init(name:String){
        self.name = name
        NSNotificationCenter.defaultCenter().addObserverForName("print", object: self, queue: nil)
        { [weak self] notification in print("hello \(self?.name)")}
    }

    deinit{
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Swift的新东西

swift为我们引入了一个新的关键词unowned。这个关键词同样用来管理内存和避免引用循环,和weak一样,unowned不会导致引用计数+1。

  1. 那么几时用weak,几时用unowned呢?

    举上面Notification的例子来说:

    • 如果Self在闭包被调用的时候有可能是Nil。则使用weak
    • 如果Self在闭包被调用的时候永远不会是Nil。则使用unowned
  2. 那么使用unowned有什么坏处呢?

    如果我们没有确定好Self在闭包里调用的时候不会是Nil就使用了unowned。当闭包调用的时候,访问到声明为unowned的Self时。程序就会奔溃。这类似于访问了悬挂指针(进一步了解,请阅读Crash in Cocoa

    对于熟悉Objective-C的大家来说,unowned在这里就类似于OC的unsafe_unretained。在对象被清除后,声明为weak的对象会置为nil,而声明为unowned的对象则不会。

  3. 那么既然unowned可能会导致崩溃,为什么我们不全部都用weak来声明呢?

    原因是使用unowned声明,我们能直接访问。而用weak声明的,我们需要unwarp后才能使用。并且直接访问在速度上也更快。(这位国外的猿说:Unowned is faster and allows for immutability and nonoptionality. If you don't need weak, don't use it.

    其实说到底,unowned的引入是因为Swift的Optional机制。

    因此我们可以根据实际情况来选择使用weak还是unowned。个人建议,如果无法确定声明对象在闭包调用的时候永远不会是nil,还是使用weak来声明。安全更重要。

延伸阅读:从Objective-C到Swift

参考链接:
shall-we-always-use-unowned-self-inside-closure-in-swif

what-is-the-difference-between-a-weak-reference-and-an-unowned-reference

时间: 2024-09-20 13:32:18

iOS夯实:ARC时代的内存管理的相关文章

iOS开发系列—Objective-C之内存管理

概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存.其他高级语言如C#.Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护.今天将着重介绍ObjC内存管理: 引用计数器 属性参数 自动释放池 引用计数器 在Xcode4.2及之后的版本中

看朋友日志发现的一个ios下block相关的内存管理问题,很奇怪,请大家帮忙一起来回答!

http://blog.csdn.net/fengsh998/article/details/38090205 这篇文章下面是我的回复,同样的代码只是把变量的定义从局部变量改为类的成员变量就发现了很大的差异,目前还没有找到明确的答案,请大家帮忙看一下!

iOS夯实:内存管理

iOS夯实:内存管理 最近的学习计划是将iOS的机制原理好好重新打磨学习一下,总结和加入自己的思考. 有不正确的地方,多多指正. 目录: 基本信息 旧时代的细节 新时代 基本信息 Objective-C 提供了两种内存管理方式. MRR (manual retain-release) 手动内存管理这是基于reference counting实现的,由NSObject与runtime environment共同工作实现. ARC (Automatic Reference Counting)自动引用

详解iOS应用开发中的ARC内存管理方式_IOS

提示:本文中所说的"实例变量"即是"成员变量","局部变量"即是"本地变量" 零.简介ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的retain.release.autorelease语句.你不再需要担心内存管理,因为编译器为你处理了一切 注意:ARC 是编译器特性,而不是 iOS 运行时特性(除了weak指针系统),它也不是类似于其它语言中的垃圾收集器.因此 ARC 和

理解iOS的内存管理

远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳,而我还是一个默默无闻的刚毕业的小子.那个时候的 iOS 开发过程是这样的: 我们先写好一段 iOS 的代码,然后屏住呼吸,开始运行它,不出所料,它崩溃了.在 MRC 时代,即使是最牛逼的 iOS 开发者,也不能保证一次性就写出完美的内存管理代码.于是,我们开始一步一步调试,试着打印出每个怀疑对象的

《iOS应用开发》——2.3节内存管理

2.3 内存管理 iOS应用开发 我不是吓唬你们.在iOS 5.0系统之前,内存管理毫无疑问是iOS开发最困难的部分.简而言之,问题是这样的.无论何时你创建了一个变量,你就要在内存中给它分配一定的空间.对于局部变量来说,我们通常使用栈上的内存,这些内存是自动管理的,当函数返回时,函数中定义的任何局部变量都会从内存中自动删除. 这听起来很棒,但是栈有两个严重的局限.首先,它的空间非常有限,如果用尽了内存,应用程序就会崩溃.其次,这些变量很难共享.请记住,函数使用值传参和返回.这意味着所有传入函数或

iOS/OS X 内存管理(二):借助工具解决内存问题

上一篇博客iOS/OS X内存管理(一):基本概念与原理主要讲了iOS/OSX 内存管理中引用计数和内存管理规则,以及引入ARC新的内存管理机制之后如何选择ownership qualifiers(__strong.__weak.__unsafe_unretained和__autoreleasing)来管理内存.这篇我们主要关注在实际开发中会遇到哪些内存管理问题,以及如何使用工具来调试和解决. 在往下看之前请下载实例MemoryProblems,我们将以这个工程展开如何检查和解决内存问题. 悬挂

IOS中内存管理那些事_IOS

Objective-C 和 Swift 语言的内存管理方式都是基于引用计数「Reference Counting」的,引用计数是一个简单而有效管理对象生命周期的方式.引用计数分为手动引用计数「ARC: AutomaticReference Counting」和自动引用计数「MRC: Manual Reference Counting」,现在都是用 ARC 了,但是我们还是很有必要了解 MRC. 1. 引用计数的原理是什么? 当我们创建一个新对象时,他的引用计数为1: 当有一个新的指针指向这个对象

objective-c-oc内存管理基础问题@property

问题描述 oc内存管理基础问题@property 对象alloc 以后 计数应该是1 赋值给retain 类型 计数应该+1 所以计数是2 但是为什么都是1 ,请哪个前辈帮忙解释下 解决方案 写属性的时候(nonatomic, retain)中的retain 只会在stter方法里有效,也就是说只会在setter里给它retain一次,你这是自定义的方法,而且也没有 _myobject = [testjob retain]; 他当让不会+1了 解决方案二: http://m.blog.csdn.