做者:fengsh998安全
原文地址:http://blog.csdn.net/fengsh998/article/details/38090205微信
轉載請註明出處app
若是以爲文章對你有所幫助,請經過留言或關注微信公衆賬號fengsh998來支持我,謝謝!框架
本文不講block如何聲明及使用,只講block在使用過程當中暫時遇到及帶來的隱性危險。測試
主要基於兩點進行演示:this
1.block 的循環引用(retain cycle)atom
2.去除block產生的告警時,需注意問題。spa
有一次,朋友問我當一個對象中的block塊中的訪問本身的屬性會不會形成循環引用,我哈綽綽的就回了一句,不會。兄弟,看完這個,但願你能理解我爲何會說不會循環引用。別廢話,演示開始。.net
下面是我專們寫了一個類來演示:code
頭文件.h
[objc] view plaincopy
//
// BlockDemo.h
// blockDemo
//
// Created by apple on 14-7-24.
// Copyright (c) 2014年 fengsh. All rights reserved.
/*
-fno-objc-arc
因爲Block是默認創建在棧上, 因此若是離開方法做用域, Block就會被丟棄,
在非ARC狀況下, 咱們要返回一個Block ,須要 [Block copy];
在ARC下, 如下幾種狀況, Block會自動被從棧複製到堆:
1.被執行copy方法
2.做爲方法返回值
3.將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時
4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中傳遞的時候.
*/
#import <Foundation/Foundation.h>
@class BlockDemo;
typedef void(^executeFinishedBlock)(void);
typedef void(^executeFinishedBlockParam)(BlockDemo *);
@interface BlockDemo : NSObject
{
executeFinishedBlock finishblock;
executeFinishedBlockParam finishblockparam;
}
/**
* 執行結果
*/
@property (nonatomic,assign) NSInteger resultCode;
/**
* 每次調用都產生一個新對象
*
* @return
*/
+ (BlockDemo *)blockdemo;
/**
* 不帶參數的block
*
* @param block
*/
- (void)setExecuteFinished:(executeFinishedBlock)block;
/**
* 帶參數的block
*
* @param block
*/
- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;
- (void)executeTest;
@end
實現文件
[objc] view plaincopy
//
// BlockDemo.m
// blockDemo
//
// Created by apple on 14-7-24.
// Copyright (c) 2014年 fengsh. All rights reserved.
//
#if __has_feature(objc_arc) && __clang_major__ >= 3
#define OBJC_ARC_ENABLED 1
#endif // __has_feature(objc_arc)
#if OBJC_ARC_ENABLED
#define OBJC_RETAIN(object) (object)
#define OBJC_COPY(object) (object)
#define OBJC_RELEASE(object) object = nil
#define OBJC_AUTORELEASE(object) (object)
#else
#define OBJC_RETAIN(object) [object retain]
#define OBJC_COPY(object) [object copy]
#define OBJC_RELEASE(object) [object release], object = nil
#define OBJC_AUTORELEASE(object) [object autorelease]
#endif
#import "BlockDemo.h"
@implementation BlockDemo
+ (BlockDemo *)blockdemo
{
return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}
- (id)init
{
self = [super init];
if (self) {
NSLog(@"Object Constructor!");
}
return self;
}
- (void)dealloc
{
NSLog(@"Object Destoryed!");
#if !__has_feature(objc_arc)
[super dealloc];
#endif
}
- (void)setExecuteFinished:(executeFinishedBlock)block
{
OBJC_RELEASE(finishblock);
finishblock = OBJC_COPY(block); //在非ARC下這裏不能使用retain
}
- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block
{
OBJC_RELEASE(finishblockparam);
finishblockparam = OBJC_COPY(block); //在非ARC下這裏不能使用retain
}
- (void)executeTest
{
[self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];
}
- (void)executeCallBack
{
_resultCode = 200;
if (finishblock)
{
finishblock();
}
if (finishblockparam)
{
finishblockparam(self);
}
}
@end
上面是由於考慮到在ARC 和非ARC中進行編譯演示,因此我特地加了ARC預編譯判斷。主要是方便不要改動太多的代碼來給你們演示。
在非ARC環境下
執行下在語句的測試:
[objc] view plaincopy
- (IBAction)onTest:(id)sender
{
BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
[demo setExecuteFinished:^{
if (demo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
輸出結果:
[cpp] view plaincopy
2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor!
2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.
很顯然。儘管demo 是局部變量,並autorelease但能夠看出天然至終並無獲得釋放,這是由於block中使用了 block內進行訪問了自身的resultCode屬性。相信不少朋友也都會解決這種循環引用問題。就是在變量前面加個__block,就像這樣。
[objc] view plaincopy
__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
在非ARC下,只雖一個__block關鍵詞就能夠。相對仍是簡單的。
好下面再來看一下在ARC模式下的block循環引用又是怎麼樣的。
在ARC模式下
執行下面語句:
[cpp] view plaincopy
- (IBAction)onTest:(id)sender
{
BlockDemo *demo = [[BlockDemo alloc]init];
[demo setExecuteFinished:^{
if (demo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
執行輸出結果:
[objc] view plaincopy
2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor!
2014-07-24 19:20:39.000 blockDemo[25215:60b] call back ok.
一樣會被引入循環。
相信看到這裏的人,大多都要噴了,這哪一個不知道呀,還知道怎麼解決呢,非ARC中加了個__block,固然的在ARC中加一個__weak就搞定了。嗯,確實是這樣,但別急,接着往下看,絕對有收穫。在這裏先本身默認想一下,你是如何加這個__weak的。
對於第一個問是點block 的循環引用(retain cycle)到這裏暫告結束。下面講第二點。由於block告警在非ARC 中暫未發現因寫法引入(若是你知道,麻煩告訴我怎麼弄產生告警,我好研究一下。)
下面講在ARC模式下去除因寫法產生的告警時須要注意的問題。
像上面的寫法其實在ARC中會產生(Capturing 'demo' strongly in this block is likely to lead to a retain cycle)告警。以下圖:
在ARC中,編譯器智能化了,直接提示這樣寫會產生循環引用。所以不少愛去除告警的朋友就會想法去掉,好,咱再來看去掉時需注意的問題。
狀況一:
[objc] view plaincopy
- (IBAction)onTest:(id)sender
{
__weak BlockDemo *demo = [[BlockDemo alloc]init];
[demo setExecuteFinished:^{
if (demo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
直接在前面加一個__weak,但這樣真的沒有告警了嗎?若是有,哪麼恭喜歡你,說明編譯器還幫你大忙。見下圖
這時還會告警,說這是一個WEAK變量,就立刻會被release。所以就不會執行block中的內容。你們能夠運行一下看
輸出結果爲:
[cpp] view plaincopy
2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor!
2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!
很顯然,立刻被release了,因此block 中的代碼根本就不執行。
謝天謝地,幸虧編譯器提早告訴了咱們有這個隱性危險。相信你們爲解決告警,又會獲得一個比較圓滿的解決方案,見下:
[objc] view plaincopy
- (IBAction)onTest:(id)sender
{
BlockDemo *demo = [[BlockDemo alloc]init];
__weak typeof(BlockDemo) *weakDemo = demo;
[demo setExecuteFinished:^{
if (weakDemo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
這樣寫,即去除了告警又保證了block的運行。這纔是咱們最終想要的結果。
輸出爲:
[cpp] view plaincopy
2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor!
2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok.
2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!
但你們別得意。有提示,相信你們都能處理,並獲得個好的解決方法。哪麼下面大來再來看一下這個寫法,讓你真心甘拜下風。。。。。
[objc] view plaincopy
- (IBAction)onTest:(id)sender
{
__weak BlockDemo *demo = [BlockDemo blockdemo]; //這裏纔是重點,前面是[[BlockDemo alloc]init];會有告警。
[demo setExecuteFinished:^{
if (demo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
其實只是把init放到了類方法中進行書寫而已,但會有什麼不一樣。
[objc] view plaincopy
+ (BlockDemo *)blockdemo
{
return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}
不一樣點見下圖:真心看不到做何告警,是否是。但這存在什麼風險,風險就是運行的時候,block根本就沒有run。由於對象早就釋放了。
直接輸出:
[cpp] view plaincopy
2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor!
2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!
所以,寫這個主要用來告戒一些喜歡用BLOCK但又想固然的朋友,有一些朋友喜歡去除告警,但只是盲目的加上__weak 或__block關鍵語,每每可能存在一些重大的安全隱患。就像演示中block根本不走。若是到了發佈時,爲了去告警而這樣簡單的處理了,並無進行測試就打包。哪麼將死得很慘。。。。。
好,到了尾聲,來講說爲何朋友問我block會不會引行死循環,我說不會的理由。
見碼:
[objc] view plaincopy
- (IBAction)onTest:(id)sender
{
BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];
[demo setExecuteFinishedParam:^(BlockDemo * ademo) {
if (ademo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
不論是在外面init,仍是在裏面,且沒有加__block 及__weak。爲何,由於我我的經常在使用本身寫的block時,若是是回調,比較喜歡把自身看成參數傳到block中。這樣期實是編譯器給咱們作了弱引用。所以不會產生循環引用。
因爲我一直都這樣寫block,因此朋友一問起,我就說不會循環引用了,由於壓根他碰到的就是前面講述的哪一種訪問方式,而我回答的是個人這種使用方式。正由於口頭描述,與實際回覆真是差之千里。。。哈哈。爲了驗證我朋友的這個,我特地寫了個這篇文章,但願對你們有所幫助。最後,謝謝你們花時間閱讀。