block常見的幾個問題:
1、block是什么
block是一個指向結構體的指針,編譯器會將block的內部代碼生成對應的函數。2、block為什么用copy修飾
正常創建出來的block的內存是放在棧中(如果block內部沒有調用外部變量時存放在全局區),程序員無法去管理,什么時候釋放內存也不是程序員可以決定的,棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰。所以一般情況下,就需要使用copy去修飾,會把block copy到堆上,在ARC中,對block代碼塊內部的對象強引用,在非ARC中對于引用對象進行一次retain操作。3、block與循環引用
在block代碼塊內部如果使用了當前對象進行調用方法,或者他的的操作,就會對當前對象進行強引用一次,并且如果這個block歸當前對象持有,就會導致block和當前對象互相持有引用,而都不能正常釋放。(注意:如果block不是當前對象所持有,例如在控制器中使用AFNetWorking請求,AFNetWorking的block并不是當前控制器所持有的,所以這里不用__weak,不會造成循環引用)4、_strong的使用
使用_weak來修飾可以避免對控制器或者當前類的循環引用.
但是有的時候,當前對象self已經銷毀了,之后再去執行這個block,里面的weakSelf就是個nil了,可能會出現異常,于是最好是如下寫法:
__weak typeof(self) weakSelf = self;
self.rightBtnClickHandler = ^() {
__strong typeof(self) strongSelf = weakSelf;
[strongSelf defaultRightBtnClick];
[strongSelf defaultLeftBtnClick];
};
? 在block內部使用__strong對weakSelf進行一次強引用,self的引用計數+1,此時strongSelf相當于是block內部的一個局部變量,只要block不結束,self這個對象就不會釋放,等到block執行完畢,局部變量strongSelf也會自動銷毀,完成釋放。
block底層初識
接下來看幾個常見的面試題
void test1()
{
int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印結果:value = 10
}
void test2()
{
__block int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印結果:value = 20
}
void test3()
{
static int value = 10;
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印結果:value = 20
}
int value = 10;
void test4()
{
void (^block)(void) = ^{
NSLog(@"value = %d", value);
};
value = 20;
block(); // 打印結果:value = 20
}
可以看出,先test1中,我們更改了局部變量value的值,但是block中的value值并沒有變化,但是在test2、3、4中,卻能成功更改。這是為什么呢?
我們嘗試著看下它們的底層代碼。
終端執行clang -rewrite-objc main.m
把文件編譯成c++代碼,得到.cpp文件,打開后估計有接近10萬行代碼,直接command+??,我們來到文件的最后,再往上面滑一點,可以看到如下:
我們可以看到 void (*block)(void) 是一個沒有參數沒有返回值的函數指針,既然是一個函數指針,那它就是一個變量,變量里面只能保存函數地址,等式右邊接收是下面這個函數的返回值:
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
這個函數有4個參數,參數 flags= 0,這個參數其實就相當于Swift中指定了一個默認值,不傳也有值,可以忽略。所以上面test1中傳了3個參數,test2中傳了4個參數,其中第三個參數是value,前者是傳值,后者是傳的value的地址,故后者value的值發生了變化
總結:
- block可以修改全部變量和靜態變量,不可以修改局部變量,如果想要修改使用__block,
- block之所以能夠修改全局變量、靜態變量、__block修飾的局部變量,是因為把指向變量的指針地址copy到block結構體內部,而局部變量是copy的變量值到block內部.