1. 關(guān)于 Block 的幾道題
1. void exampleA() {
char a = 'A';
^{
printf("%c\n", a);
}()
}
The example ()
A. always works.
B. only works with ARC.
C. only works without ARC.
D. never works.
2. void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block()
}
The example ()
A. always works.
B. only works with ARC.
C. only works without ARC.
D. never works.
3. void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("C\n");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
The example ()
A. always works.
B. only works with ARC.
C. only works without ARC.
D. never works.
4. typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%c\n", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
The example ()
A. always works.
B. only works with ARC.
C. only works without ARC.
D. never works.
5. typedef void (^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%c\n", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block;
}
The example ()
A. always works.
B. only works with ARC.
C. only works without ARC.
D. never works.
結(jié)果分別為:A尽楔、B、A第练、B阔馋、B
解釋:
- 第一題中,Block 訪問外部變量 a娇掏,之后在棧上生成了一個同名的只讀變量 a呕寝。需要注意的是,兩個 a 的生命周期是相同的婴梧,同樣都存在于棧上下梢。由于調(diào)用函數(shù) exampleA() 中調(diào)用了 Block,故 ARC 與非 ARC 都是可以運行的塞蹭。
- 第二題中怔球,如果不是在 ARC 模式下調(diào)用,則 Block 為
__NSStackBlock
類型 浮还,當(dāng)在 exampleB() 中調(diào)用 Block 的時候,Block
是不可用的闽巩。在 ARC 模式下钧舌,Block 會自動 copy 到堆上面(也是自動釋放的),是__NSMallocBlock
類型的涎跨。 - 第三題中洼冻,Block 沒有訪問任何的外部變量,則 Block 為
__NSGlobalBlock
類型的隅很。它既不在堆上也不在棧上撞牢,而是在全局區(qū)中。 - 第四題中叔营,與第二題類似屋彪,Block 訪問了棧上的變量 d,但是在 exampleD() 函數(shù)中調(diào)用了 Block绒尊。在非 ARC 模式下畜挥,會報錯:
error: returning block that lives on the local stack
。在 ARC 模式下婴谱,Block 會自動 copy 到堆上并且自動
release蟹但,其類型為__NSMallocBlock
躯泰。 - 第五題中,與第四題其實是一樣的华糖,只不過將 Block 返回給了一個局部變量麦向,之后將這個局部變量返回。這樣在非 ARC 模式下編譯器不會再報錯客叉,但是還是會出現(xiàn)問題诵竭。ARC 模式下,Block 會自動 copy 到堆上十办,并且自動 release秀撇,類型為
__NSMallocBlock
。
2. Block 的分類
-
__NSGlobalBlock 類型(全局 Block)
總結(jié):對于沒有引用外部變量的 Block向族,無論是在 ARC 還是在非 ARC 下呵燕,類型都是__NSGlobalBlock
。這種類型的 Block 可以理解為一種全局的 Block件相,不需要考慮作用域的問題再扭。同時,對它進(jìn)行 copy 或者 retain 操作也都是無效的夜矗。 -
__NSStackBlock 類型(棧 Block)
總結(jié):對于引用了外部局部變量(注意不是局部靜態(tài)變量)的 Block泛范,在 MRC 下如果沒有對它進(jìn)行 copy 操作,它的作用域只會在定義它的函數(shù)棧內(nèi)(類型為__NSStackBlock
)紊撕。在 ARC 下罢荡,由于對象指針默認(rèn)為__strong
修飾,故直接賦值的話 Block 會被 copy 到堆上对扶。如果對象指針用__weak
修飾区赵,則 Block 不會被拷貝到堆上面。
-
__NSMallocBlock 類型(堆 Block)
總結(jié):在 MRC 下浪南,對 Block 進(jìn)行 copy 操作后笼才,Block 會被拷貝到堆上。在 ARC
下络凿,由于對象指針默認(rèn)由__strong
修飾骡送,則程序默認(rèn)會將 Block 拷貝到堆上(如何使用__weak
則程序不會拷貝 Block 到堆上)。
3. __block 關(guān)鍵字
-
默認(rèn)情況
總結(jié):由圖中可以得知絮记,Block 中的 a 與外部變量 a 它們的地址并不相同摔踱,但是值是相同的。實際上 Block 中的 a 是外部變量 a 的一個拷貝到千,且為常量昌渤。
-
使用 __block 關(guān)鍵字
總結(jié):由圖中可知,在使用了__block
關(guān)鍵字后憔四,Block 中的 a 與外部變量 a 的地址相同膀息。這種情況下 Block 中使用的就是貨真價實的外部變量 a般眉,而且可以對
a 進(jìn)行賦值操作稚照。
-
copy 的情況
總結(jié):由圖可知巷折,局部變量 a 在 Block 進(jìn)行 copy 前和 copy 后的地址不同了。實際上對 Block 對象進(jìn)行 copy 后趟据,Block 中引用的變量都會被復(fù)制到堆上冗酿。而被標(biāo)記為__block
的變量埠对,實際被移動到了堆上。為什么要將 a 移動到堆上面呢裁替?歸根結(jié)底是因為 a 被聲明了__block
项玛。copy 之后,Block 中的 a 將被放置到堆上面弱判,但是程序需要保持 Block 中的 a 與外部的 a 的一致性 (生命周期與作用域)襟沮,故外部的 a 也就移動到了堆上面。
ps: 哪里有 copy 呢昌腰?
4. Block 的循環(huán)引用
Block 對外部引用的對象都會進(jìn)行持有开伏,直到 Block 執(zhí)行完。如果此時 Block 持有的對象正好持有 Block遭商,則會發(fā)生內(nèi)存泄漏固灵。
-
常見的 Block 循環(huán)引用
解釋:學(xué)生類中定義了一個 Block 屬性 work,此時學(xué)生類對象 s 持有 work 屬性劫流。在 work 的 Block 中巫玻,又訪問了外部對象 s,則 work 持有對象 s祠汇,這就形成了一個環(huán)大审。
-
觀察者模式引起的循環(huán)引用
解釋:在通知中心的這個方法中,Block 中引用了 self 或者 self 的成員變量(無論是通過點方法還是直接訪問實例變量)座哩,Block 都會持有當(dāng)前對象。如果在對象的 dealloc 方法中將通知移除粮彤,則會形成循環(huán)引用根穷。通知中心會一直持有該對象,直到解除 Observer 的注冊导坟。 -
NSTimer 引起的循環(huán)引用
解釋:由圖中可知屿良,self 對象對 NSTimer 對象有強引用,而且又作為 NSTimer 對象的 target惫周。而 NSTimer 對象會一直持有 target 對象尘惧,直到 NSTimer 對象不再有效 (invalidate
方法) ,這樣就形成了一個循環(huán)引用递递。代碼又在 self 對象的 dealloc 方法中對 NSTimer 進(jìn)行銷毀喷橙,而 self 對象的 dealloc 方法是在 self 對象將要釋放的時候調(diào)用的啥么,所以 self 對象和 NSTimer 對象永遠(yuǎn)都無法釋放。
-
NSURLSession 對象引起的循環(huán)引用
解釋:NSURLSession 對象會對 delegate 保持強引用贰逾,直到程序釋放或者調(diào)用了NSURLSession 對象的finishTasksAndInvalidate
方法或者invalidateAndCancel
方法悬荣。與上面的例子相似,在 dealloc 中調(diào)用 invalidate 相關(guān)方法疙剑,是無法解決循環(huán)引用的情況的氯迂,在使用的時候要多加注意。
5. Block 的底層結(jié)構(gòu)
Block 的底層結(jié)構(gòu)大體是由結(jié)構(gòu)體以及額外的函數(shù)來構(gòu)成言缤,具體可以使用 clang 的命令編譯 .m 文件來查看:clang -rewirte-objc xxx.m
嚼蚀。網(wǎng)上有很多分析的文章,這里就不再一一的分析了管挟。
6. 解決循環(huán)引用
-
通過將Block中訪問的對象設(shè)置為weak轿曙。
或者更加安全的方式:
-
使用完 Block 的時候及時將 Block 置為空
解釋:循環(huán)引用的發(fā)生一般都是由于 Block 持有了對象,而對象又持有了 Block 哮独。這樣我們可以在使用完 Block 之后拳芙,立刻釋放對象對 Block 的持有,將 Block 置空就可以打破引用環(huán)皮璧。
-
根據(jù)情況將環(huán)的任一強引用置空即可
在實際的開發(fā)中舟扎,有可能存在多個節(jié)點的復(fù)雜的環(huán),除了可以將 Block 置空外悴务,還可以打破環(huán)中的任一強引用睹限,就可以解決掉循環(huán)引用的問題。
6. 練習(xí)
下面這四種情況中讯檐,哪個會存在內(nèi)存泄漏的情況羡疗?
- (void)test1
{
self.student = [Student new];
self.student.work = ^{
self.name = @"Hello";
};
self.student.work = nil;
}
- (void)test2
{
self.student = [Student new];
self.student.work = ^{
self.name = @"Hello";
};
self.student = nil;
}
- (void)test3
{
Student *student = [Student new];
student.work = ^{
student.name = @"小明";
};
student.work = nil;
}
- (void)test4
{
Student *student = [Student new];
student.work = ^{
student.name = @"小明";
};
student = nil;
}
7. 總結(jié)
- ARC 下 Block 會從棧上自動拷貝到堆上,原因是由于
__strong
的原因别洪。 - Block 由于會持有外部引用的變量叨恨,容易引發(fā)循環(huán)引用的問題。解決的辦法是把環(huán)打破挖垛,或者根本不讓環(huán)生成痒钝。
- iOS 開發(fā)中使用
NSTimer
、NSURLSession
痢毒、NSNotificationCenter
的時候一定要注意送矩。 - 需要注意,非 ARC下
__block
可以解決循環(huán)引用的問題哪替。在 ARC 下不可以栋荸,需要使用__weak
來解決。