您的位置:首页 > 移动开发 > IOS开发

ios高效开发二--ARC跟block那点事

2013-10-21 18:28 337 查看
/article/5489487.html

/article/8764987.html

/article/6113650.html

在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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: