浅析在项目开发(使用Delegate回调时)如何正确使用ARC

ARC(自动引用计数)是2011年伴随iOS5来的一项技术。简单来说就是通过LLVM3.0编译器帮助程序处理“一大部分”OC中的内存管理。为什么是“一大部分”,这个等会儿解释。

一直以来内存管理这个话题都是初学iOS开发,初学OC语言必须要面对的知识点,也是大家容易出错的地方。对象释放后调用会造成crash、不释放的对象会造成内存泄漏这些问题困扰着初学者。ARC的到来按理说应该是福音,不需要自己管理内存了嘛,多简单。但是随之而来的两个问题:1,我发现周围有些拥有2-5年开发经验的“半老手”(要谦虚- -#)不愿意使用ARC,包括半年前的我。因为当年MRC(手动引用计数)花了好些精力去慢慢习惯培养起来,熟练了后,反而对新的ARC有不放心、不习惯;2,新手在使用ARC时,以为编译器已经全部为你做好了内存管理,而不去做相应的操作,最终导致内存泄漏。另外,在工作中我甚至发现,一些PL,都会在ARC上面犯错,或许他们真的没在意,但问题不能就这么放着!就是以上原因让我有动力在空闲时间来好好做个Demo来总结一下。

好了,说了半天废话,首先如果你还对“内存管理”、“MRC”、“ARC”的原理有些模糊说不清的话,这里有一篇好文章你可以借鉴一下:请戳http://onevcat.com/2012/06/arc-hand-by-hand/ 作者大牛对ARC的解释非常详细,包括后面对MRC项目转换为ARC项目都有详细说明。我就不班门弄斧写教程了(也没那个实力呵呵)。

先说说我对ARC的理解:

1,一个对象,只要有大于或等于一个Strong类型指针指向它,该对象就不会被释放;

2,一个对象,假如只剩下一个或多个weak指针指向它,那么对不起,该对象就自动被释放,然后这些一个或多个weak指针都会被自动设置为nil;

3,默认条件下,指针都是Strong,除非你特别声明,如在最前面加__weak;

4,使用ARC,不代表和内存管理完全拜拜了,你仅仅不需要retain、release、autorelease、[super dealloc],但是在必要的时候,还是得向一个NSObject setNil,才能完成对内存的释放。

下面用一个demo来简单演示普通情况下的UIView子类内存释放以及使用delegate回调时如何正确释放。demo很简单,在这里可以看到源代码https://github.com/pigpigdaddy/ARCTestDemo

一:普通情况下删除一个UIView子类,正确释放内存

我创建了一个空项目,又创建了两个类,一个是ViewController,用于window的rootViewController。一个是ARCTestView,继承自UIView,我们就是要看看它的实例在删除时候是否按照我们的想法正确被释放。有一点要先说明一下,在ARC下,仍然可以显示地写出dealloc函数,并且实例只有自动调用dealloc函数了,才说明它被正确释放了!

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4     // Do any additional setup after loading the view.
 5
 6     self.button = [UIButton buttonWithType:UIButtonTypeSystem];
 7     self.button.frame = CGRectMake(20, 20, 40, 30);
 8     self.button.backgroundColor = [UIColor blueColor];
 9     [self.button addTarget:self action:@selector(removeTestView:) forControlEvents:UIControlEventTouchUpInside];
10     [self.view addSubview:self.button];
11
12     ARCTestView *testView = [[ARCTestView alloc] init];
13     testView.frame = CGRectMake(0, 80, 320, 350);
14     testView.backgroundColor = [UIColor redColor];
15     testView.tag = 100;
16     [self.view addSubview:testView];
17 }

在viewDidLoad函数中,我创建了一个ARCTestView的实例,并把它addSubview,请注意按照之前所说的默认状态下指向该对象的指针是Strong,因此testView指向的实例在该作用域(函数viewDidLoad)中是被持有的,该实例在该作用域结束前不会被释放。一会儿说一下“作用域”的情况。ARCTestView的.m文件中的dealloc函数内,我打了断点(或者你可以写一个NSLog函数在里面)用来检查是否ARCTestView的实例被释放。另外,viewdidload中我创建了一个button,绑定了点击事件,实现了将ARCTestView实例从界面上删除的功能。

1 - (void)removeTestView:(id)sender
2 {
3     UIView *view = [self.view viewWithTag:100];
4     if (view) {
5         [view removeFromSuperview];
6     }
7 }

OK,现在是解释“作用域”的好时机:由于临时变量(指针)哪怕它是Strong的,只要出了作用域,就必然失效。所以,如果此时该实例没有被其他Strong指针所指,那么就会被释放。这符合之前的归纳的要点:1,一个对象,只要有任何大于或等于一个Strong类型指针指向它,该对象就不会被释放;2,一个对象,假如只剩下一个或多个weak指针指向它,那么对不起,该对象就被释放,然后这些一个或多个weak指针都会被设置为nil。因此你不用担心viewDidLoad和removeTestView里的临时变量会不被释放,或者需要手动设置为nil。因为在这个例子中,这些临时指针指向的变量,没有其他Strong指针指向它们了。

验证方法:你可以将viewDidLoad中的最后一句注释掉 1 [self.view addSubview:testView]; ,让self.view不去持有testView指向的实例。这样新创建的ARCTestView实例,在作用域结束时,testView指针失效,另外再也没有指针持有它了,因此viewDidLoad结束,实例被释放,进入ARCTestView的dealloc函数。

我之所以要着重强调这一点,因为是想解释一种机制:原本在MRC中,临时变量创建后(alloc init),用完后是需要手动release的。而既然ARC不能release,那么肯定是在作用域结束后,随即结束该临时指针的使命。这呼应了ARC的机制!

运行程序你会看到:

蓝色按钮、红色的ARCTestView。至于红色内部的黄色块,不要着急,那是“二”里面的内容。

现在点击蓝色按钮,看看效果,红色的界面被删除了。同时,进入了ARCTestView的dealloc函数。有人要问,假如我创建的ARCTestView不是临时的而是一个Strong类型的@property呢。答案是:只有在删除界面后(removeFromSuperView)再将该@property setNil才能真正释放。当然实际项目中你可能不会这么做,因为既然是@property,那么你一定是希望它的生命一直持续到本实例消亡为止。

1 @property (nonatomic, strong)ARCTestView *testView;

这就是为什么文章开头说的“一大部分”,因为在有些(很少的)地方,你还是得自己setNil。

1 - (void)removeTestView:(id)sender
2 {
3     [self.testView removeFromSuperview];
4     self.testView = nil;
5 }

二:delegate回调时,删除一个UIView子类,正确释放内存

我创建了一个新的类叫SubView,声明了一个SubViewDelegate(里面没有任何东西)

在开发中,我们经常给界面设置回调,本界面只处理本界面的事情,数据获取,或是事件处理都以回调的形式让上层做处理。这是iOS开发中很常见的设计。在MRC中,我和我周围的人习惯性的会重写一个自定义View的初始化方法,在初始化的时候,就将subView的delegate设好。而不是在创建好后再用类似someView.delegate = self;来设置回调对象。这样做的好处是有两个,一是整洁了代码(个人认为),二是可以在subView的初始化方法中直接调用回调。

1 @interface SubView : UIView
2 {
3     id<SubViewDelegate> _delegate;
4 }

1 - (id)initWithFrame:(CGRect)frame withDelegate:(id<SubViewDelegate>)delegate;

 1 @implementation SubView
 2
 3 - (id)initWithFrame:(CGRect)frame withDelegate:(id<SubViewDelegate>)delegate
 4 {
 5     self = [super initWithFrame:frame];
 6     if (self) {
 7         // Initialization code
 8         _delegate = delegate;
 9     }
10     return self;
11 }

在MRC中,默认情况下,id<SubViewDelegate> _delegate;就是一个一般的指针,当你在初始化方法中使用_delegate = delegate;不会对delegate(上层ARCTestView)进行任何retain操作,因此没有任何潜在的问题。但是,问题来了,如果现在变成了ARC,那这个_delegate会被解读为Strong类型,所以delegate(上层ARCTestView)就多了一个Strong指针指向它。从而在viewController中删除上册界面ARCTestView时,由于上册界面ARCTestView的subView内有一个Strong指向ARCTestView,因此ARCTestView不会被释放!呵呵有些绕吧?打个比方,就是儿子用Strong把爸爸劫持了,爸爸不能出去玩了!请结合代码,在把我写的这段话看一遍。

那么解决的方法是什么呢?有两种:

1,如果一定要使用自定义的初始化方法,将_delegate = delegate,那么请这样声明你的_delegate:

1 @interface SubView : UIView
2 {
3     __weak id<SubViewDelegate> _delegate;
4 }

对了就是加上__weak关键字,这样显示的表明,_delegate是个weak指针,不会持有“爸爸”即ARCTestView。

2,使用原生的初始化方法,然后将delegate声明为weak的@property

1 @property (nonatomic, weak)id<SubViewDelegate> delegate;

其实以上两种方法并不互斥,你可以使用自定义的初始化方法,同时也讲delegate设置为@property,但不管怎么样切记一定要是weak!

这样,当我点击蓝色按钮时,界面被删除,同时进入dealloc函数,表明释放正确!

有人说,为什么不用assign呢?呵呵,你当然可以用,不过assign是MRC年代的东西了。ARC使用strong和weak。而且,还记得吗,weak指针可以自动设置为nil哦,assign确没有这个特性。

好了,总结一下本文:

1,请看一下http://onevcat.com/2012/06/arc-hand-by-hand/ 真的是大牛的好文章!

2,我对ARC的理解

     1,一个对象,只要有大于或等于一个Strong类型指针指向它,该对象就不会被释放;2,一个对象,假如只剩下一个或多个weak指针指向它,那么对不起,该对象就自动被释放,      然后这些一个或多个weak指针都会被自动设置为nil;3,默认条件下,指针都是Strong,除非你特别声明,如在最前面加__weak;4,使用ARC,不代表和内存管理完全拜拜了,      你仅仅不需要retain、release、autorelease、[super dealloc],但是在必要的时候,还是得向一个NSObject setNil,才能完成对内存的释放(如第一个例子中的self.textView      = nil;)。

3,使用delegate模式时一定要注意,weak的使用,不然你的界面是不能正确释放的!从MRC转过来的“半老手”一定要注意!

写的不对的地方请多包涵,并帮忙指出,谢谢!

浅析在项目开发(使用Delegate回调时)如何正确使用ARC

时间: 2024-10-10 14:19:40

浅析在项目开发(使用Delegate回调时)如何正确使用ARC的相关文章

.NET程序员项目开发必知必会—Dev环境中的集成测试用例执行时上下文环境检查(实战)

Microsoft.NET 解决方案,项目开发必知必会. 从这篇文章开始我将分享一系列我认为在实际工作中很有必要的一些.NET项目开发的核心技术点,所以我称为必知必会.尽管这一些列是使用.NET/C#来展现,但是同样适用于其他类似的OO技术平台,这些技术点可能称不上完整的技术,但是它是经验的总结,是掉过多少坑之后的觉醒,所以有必要花几分钟时间记住它,在真实的项目开发中你就知道是多么的有帮助.好了,废话不说了,进入主题. 我们在开发服务时为了调试方便会在本地进行一个基本的模块测试,你也可以认为是集

当用Myeclipse8.6集成开发环境,进行JavaWeb项目开发的时候,用集成开发环境中的run Server进行程序调试时,出现如下错误解决方案

当用Myeclipse8.6集成开发环境,进行JavaWeb项目开发的时候,用集成开发环境中的run Server进行程序调试时,出现如下错误解决方案: 'Starting Tomcat v6.0 Server at localhost'has encountered a problem 错误提示: Several ports(8080,8009)required by Tomcatv6.0 Server at localhost are already in use.The server ma

Expo大作战--针对已经开发过react native项目开发人员有针对性的介绍了expo,expo的局限性,开发时项目选型注意点等

简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo依赖,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人修改补充+demo测试的形式,对expo进行一次大补血!欢迎加入expo兴趣学习交流群:597732981 [之前我写过一些列关于expo和rn入门配置的东i西,大家可以点击这里查看:从零学习rn开发] 相关文章: Expo大作战--什么是expo,如何安装expo clinet和xde,xde如何

仿LOL项目开发第四天

---恢复内容开始--- 仿LOL项目开发第四天 by草帽 上节讲了几乎所有的更新版本的逻辑,那么这节课我们来补充界面框架的搭建的讲解. 我们知道游戏中的每个界面都有自己的一个类型:比如登陆界面,创建角色界面. 既然有这么多的界面,所以呢,我们创建一个单例的UI管理器:WindowManager.cs,然后里面创建一个字典来存所有类型的界面: using UnityEngine; using System.Collections.Generic; using Game; using Game.C

项目开发中常用的PHP函数

日期操作 为了便于存储.比较和传递,我们通常需要使用strtotime()函数将日期转换成UNIX时间戳,只有在显示给用户看的时候才使用date()函数将日期转换成常用的时间格式. strtotime()  函数将任何英文文本的日期时间描述解析为 Unix 时间戳 eg: <?php echo(strtotime("now")); echo(strtotime("3 October 2005")); echo(strtotime("+5 hours&

Android项目开发全程(二)--Afinal用法简单介绍

本篇博文接上篇的<Android项目开发全程(一)--创建工程>,主要介绍一下在本项目中用到的一个很重要的框架-Afinal,由于本系列博文重点是项目开发全程,所以在这里就先介绍一下本项目中用到的几个功能: Afinal简介 Afinal 是一个android的sqlite orm 和 ioc 框架.同时封装了android中的http框架,使其更加简单易用: 使用finalBitmap,无需考虑bitmap在android中加载的时候oom的问题和快速滑动的时候图片加载位置错位等问题. Af

窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用

协议与委托代理回调在之前的博客中也是经常提到和用到的在<Objective-C中的委托(代理)模式>和<iOS开发之窥探UICollectionViewController(四) --一款功能强大的自定义瀑布流>等博客内容中都用到的Delegate回调.说到协议,在Objective-C中也是有协议的,并且Swift中的协议和Objc中的协议使用起来也是大同小异的,在Java等现代面向对象编程语言中有接口(Interface)的概念,其实和Swift中或者Objc中的Protoco

使用Jquery+EasyUI 进行框架项目开发案例讲解之四 组织机构管理源码分享

http://www.cnblogs.com/huyong/p/3404647.html 在上三篇文章  <使用Jquery+EasyUI进行框架项目开发案例讲解之一---员工管理源码分享> <使用Jquery+EasyUI 进行框架项目开发案例讲解之二---用户管理源码分享> <使用Jquery+EasyUI 进行框架项目开发案例讲解之三---角色管理源码分享> 我们分享了使用Jquery EasyUI来进行ASP.NET项目的开发的相关方法,每一个模块都有其共用性,

AngularJS进阶(三十一)AngularJS项目开发技巧之获取模态对话框中的组件ID

AngularJS项目开发技巧之获取模态对话框中的组件ID 需求 出于项目开发需求,须要实现的业务逻辑是:药店端点击查看"已发货""已收货"订单详情时.模块弹出框中仅仅应出现"取消"button.但现实的情况例如以下图所看到的. 模态框核心代码例如以下: <script type="text/ng-template" id="billDtlContent.html"> <div class