[转]iOS中ARC下Block的循环引用

ARC的特性

  ARC下,所有NSObject类型指针,

  1. 默认为__strong类型

  2. 可以显示的指定为__weak类型,__weak类型指针在所指向对象销毁后会自动置为nil

  3. __autorelesing类型用于inout参数类型

  ARC下,当一个函数返回一个NSObject指针时,编译器会帮我们实现autorelease调用。例如:

  return pObject;

  编译器会帮我们扩展为 return [pObject autorelease];

  ARC下,不能显式release,可以使用将值赋为nil来让编译器为我们release。

ARC与Block

  Block的生命周期管理非常的微妙,与ARC混在一起后,更加复杂。

  当Block延stack向上(up)传递的时候,直接返回,编译器会添加[[ copy] autorelease]代码。

  当Block延stack向下传递给需要retain的容器的时候,需要显式的调用[^{} copy]方法。

  在ARC下,__block修改的NSObject指针依然会被retain。

在ARC下,一个block内引用一个对象的实例变量后,self会被retain,所以极易造成strong reference cycle,可以通过__weak指针来避免这种情形,因为ARC不会为__weak指针retain。

在iOS4.0推出了Blocks這個語言特性後
到現在iOS都已經出到5.0了
所以我想Blocks應該可以被廣泛應用了
但現在iOS環境是從MRC(Manual Reference Counting) 走到ARC (Automatic Reference Counting)
在Reference Counting的環境中Runtime是無法自動解除Retain cycle的
而Blocks有很多隱性的retain的動作
很容易不小心的造成retain cycle。
而本篇的重點是點出三種會造成Retain Cycle的Anti-patterns
再來講一下怎麼解決。

在討論之前還是先大概重述一些概念
block當中是允許去使用外部的variable
但是local variable是會自動做retain的動作
例如

MyClass* foo = ….;
self.someBlock = ^{
    [foo bar];
};

上面的foo在此block被copy到heap的時候
也會一起被自動retain
而這就是我說的很容易造成retain cycle的主因。

Anti Pattern 1
第一個例子我們先用大家很常用的Opne source library ASIHttpRequest當作一個範例
看看下面的例子,有發現任何問題嗎?

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
    // Use when fetching text data
    NSString *responseString = [request responseString];

    // Use when fetching binary data
    NSData *responseData = [request responseData];
}];

這邊我們要首先要注意的點就是[request setCompletionBlock:…]這裡
很明顯的這邊的用途是要做一個event callback的用途
也就是說我給你一個block,當動作完成的時候callback我。
這是一個典型的非同步的作法。
但由於如果你要把block拿來之後使用,
你一定要呼叫[aBlock copy]的動作,
此動作會把block從stack丟進heap。
因為在iOS的環境block也是一個object,
此時這個block的retain count就會增加1
這時候根據定義,這個block中參照的request這個local變數就也會被retain起來。
所以request的retain count也會增加1。
但問題來了,一旦request完成任務,應當要被release的時候
卻會發現retain count始終無法歸零。理由是
request <-> block 這兩個互相retain
而無法正常釋放,這就是所謂的retain cycle了。

解決方法很簡單,看看ASIHttpRequest官網的文件
也就是用__block來描述request

__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
    // Use when fetching text data
    NSString *responseString = [request responseString];

    // Use when fetching binary data
    NSData *responseData = [request responseData];
}];

通過block variable不會retain的特性,
有點類似weak reference的作用
此時block就不會retain request
當然也就不會有retain cycle的問題。

Anti Pattern 2
Anti Pattern 1是在使用別人的library的時候容易出現的
Anti Pattern 2是在實作自己的class的時候容易出現
請看下面這段code

//MyClass.h
@property <nonatomic, copy=""> MyBlock onCompleteBlock;

//MyClass.c
self.onCompleteBlock = ^{
    [self doSomething];
}

我相信這邊大家已經馬上看出問題在哪裡了
其實Anti Pattern2算是Anti Pattern 1的特例
只是這邊使用的是特殊變數self

但有些時候我們更容易忽略的是在block中始用自己的member variable
例如

//MyClass.h
@interface MyClass : NSObject
{
    NSDate* lastModifed;
}

//MyClass.c
self.onCompleteBlock = ^{
    lastModifed = [[NSDate date] retain];
}

這時候就沒有那麼容易察覺了。
根據定義,在使用block的時候,
如果我們使用到member variable,
此時retain的不是lastModified指到的object
而是retain self
所以造成的就是
self <-> block 互相retain
跟anti pattern 1一樣的結果就是無法最終釋放記憶體。

這時候的解決方法也是一樣是拿出__block來用

//MyClass.c
__block MyClass* tempSelf = self;
self.onCompleteBlock = ^{
    tempSelf.lastModifed = [NSDate date];
}

Anti Pattern 3
繼續看下面的code

SettingsViewController* settingsViewController =
   [[[SettingsViewController alloc] init] autorelease];
settingsViewController.onUpdate = ^{
    [self doUpdate];
}
self.settingsViewController = settingsViewController;

雖然這個Block中沒有直接使用到settingsViewController,感覺應該不會有retain cycle
但是因為self -> settingsViewController
而setttingsViewController -> block
再來block -> self
這就剛好繞了一圈,同樣會有retain cycle。

所以呢,還是要想一樣用anti pattern 2的解法去解決

//RootViewController.m
SettingsViewController* settingsViewController =
   [[[SettingsViewController alloc] init] autorelease];
__block RootViewController* tempSelf = self;
settingsViewController.onUpdate = ^{
    [tempSelf doUpdate];
}
self.settingsViewController = settingsViewController;

在reference counting的環境裡,
我建議要解決retain cycle的最好思維就是想清楚從屬關係
例如最後一個anti pattern
他們的從屬關係應該就是
RootViewController -> SettingsViewController -> block
如果block要用到SettingsViewController或是RootViewController,
則就要使用weak reference (也就是__block)
在這樣的原則之下,就可以知道哪些要給他retain哪些不要了。

最後要補充一點就是上面的例子都是在MRC環境下當做範例
在MRC中__block variable在block中使用是不會retain的
但是ARC中__block則是會Retain的。
取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的
其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil)
而後者是ARC的環境下為了相容4.x的解決方案。
所以上面的範例中

__block MyClass* temp = …;    // MRC環境下使用
__weak MyClass* temp = …;    // ARC但只支援iOS5.0以上的版本
__unsafe_retained MyClass* temp = …;  //ARC且可以相容4.x以後的版本

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

block是可以捕捉上下文的特殊代码块。

block可以访问定义在block外的变量,当在block中使用时,它就会为其在作用域内的每个标量变量创建一个副本。

如果通过self拥有一个block,然后又在block中改变了实例变量,就会出错。

例如:

1 self.block = ^(NSString *aString)
2 {
3     self.aLabel.text = aString;
4 });

这段代码中,self保留了block,同时block又保留了self,会引发循环保留。很危险。

如果未使用ARC,可以使用__block和__unsafe_unretained来复制一个未保留的引用副本。

 1 //例如:(无ARC)
 2 __block id safeSelf = self;
 3 self.block = ^(NSString *aString)
 4 {
 5     safeSelf.aLabel.text = aString;
 6 });
 7
 8 //(有ARC)
 9 __weak id safeSelf = self;        //ios 5
10 // __unsafe_unretained id safeSelf = self; //ios 4
11 self.block = ^(NSString *aString)
12 {
13     safeSelf.aLabel.text = aString;
14 });

在arc出现之前,我们可以自由的把CF*对象转成NS*对象,这称为自己桥接。用了arc之后,我们需要指定一个所有权转移修饰符。

目前arc中提供的修饰符有:

1.__bridge

2.__bridge_retained

3.__bridge_transfer

第一个修饰符__bridge是一个普通的转换,表示不需要增加引用计数,不更改所有权。

第二个是在转换C指针类型时,增加引用计数的值。

第三个是把Core Foundation 指针类型转换成obj-c指针,变把引用计数值+1。如用Core Foundation 方法创建一个对象,并且要用arc来管理对象的内存,就可以用这个。

arc移植的常见错误

1.强制转换obj-c指针位C指针(或者反过来转换)

2.在arc中把void*指针强制转成id类型(或者反过来转),如果要转,就必须是用修饰符

例如: id selfPointer = (__bridge void *)self;

3.在结构体或者(union)集合体中是用obj-c对象

4.使用NSAutoreleasePool

转自:

http://blog.csdn.net/a330416020/article/details/19119491

时间: 2024-09-29 18:18:26

[转]iOS中ARC下Block的循环引用的相关文章

IOS中Block的循环引用

@interface DemoObj() @property (nonatomic, strong) NSOperationQueue *queue; @end @implementation DemoObj - (instancetype)init { self = [super init]; if (self) { self.queue = [[NSOperationQueue alloc] init]; } return self; } - (void)dealloc { NSLog(@"

ARC下block使用情况

ARC与MRC的block有着一些区别,笔记整理ARC的block,仅仅是自己参考的笔记,详情请参考 http://www.cnbluebox.com/?p=255 在开始之前,请新建一个Model类,写几个如下的属性,用于后面测试block的特性. Block的类型与内存管理 根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock. NSGlobalBlock:类似函数,位于text段: NSStackBlock:位于栈内存,

iOS开发Block的介绍以及Block的循环引用问题

1:block的循环引用问题最主要记住两点: 如果[block内部]使用[外部声明的强引用]访问[对象A], 那么[block内部]会自动产生一个[强引用]指向[对象A] 如果[block内部]使用[外部声明的弱引用]访问[对象A], 那么[block内部]会自动产生一个[弱引用]指向[对象A] 2: #import "ViewController.h" #import "XMGPerson.h" @interface ViewController () @prop

iOS中arc的设置与使用

工程配置arc方案: 1,直接在targets->build phases中修改compiler Flags,是否支持arc.添加:-fobjc-arc,就可以让旧项目支持arc.如果想让原来支持arc的不使用arc则添加-fno-objc-arc iOS中arc的设置与使用

避免Block的循环引用

避免Block的循环引用 什么是循环引用,什么时候发生循环引用 1 循环引用就是当self 拥有一个block的时候,在block 又调用self的方法.形成你中有我,我中有你,谁都无法将谁释放的困局. self.myBlock = ^{ [self doSomething]; }; +-----------+ +-----------+ | self | | Block | ---> | | --------> | | | retain 2 | <-------- | retain 1

IOS中解决ARC类实例间循环引用(Swfit)

原创Blog,转载请注明出处 http://blog.csdn.net/column/details/swfitexperience.html 备注:本文代码和图片主要来自于官方文档 不熟悉ARC的同学可以看看前一篇关于ARC的简述,这个是我的Swfit教程专栏 http://blog.csdn.net/column/details/swift-hwc.html 一.几个用到的关键概念 弱引用(weak):不会增加自动引用计数,必须为可选类型变量,因为弱引用在引用计数为0的时候,会自动赋为nil

iOS开发——Block引起循环引用的解决方案

内存问题始终是软件开发中的头等大事,iOS开发中也不例外,在面试中也是必问的问题.今天我们主要来讲讲Block中涉及的循环引用问题.当我们自己一开始写代码的时候,可能会大量在block中使用self,但是当看到别人优秀的代码的时候,发现别人常常不是用self,而使用weakSelf. 为什么呢?本文的示例代码上传至 https://github.com/chenyufeng1991/Block_WeakSelf . 首先我先来说说内存管理的原则: 1.默认使用strong,可选weak.stro

ios Block解决循环引用和回传值

存在这么一个需求:为了降低控制器的耦合度,自定义了视图控件,但是现在另外一个页面需要显示自定义视图上的值:需要用block回调到控制器中来显示 啰嗦了一大堆,说个简单明了的(需求:B控制器要向A控制器传值). 1.首先第一步要在B控制器中定义block 例如: #import "BViewController.h" typedef void (^ AnswerBlock)(NSString *resutlStr); @class GXRiskRelatedQuery; @interfa

iOS——在ARC下引入MRC文件

在写一些工程时我们总是要引入一些第三方文件,但是这些文件有些是MRC下的有些是ARC下的.所以我们要进行转换. 引入三方文件时首先要阅读引入的文件的.h 文件头部信息 如下面的文件:头部文件要求:Header Search Paths包含/usr/include/libxml2 Other Linker Flags包含-lxml2 所以 在Bulid Setting下进行搜索 搜索后对其进行修改 对.h文件所要求的路径进行一一添加,添加完成后,就是把让MRC得文件在ARC的工程下进行运行 首先在