首先需要知道:
block,本質(zhì)是OC對象,對象的內(nèi)容,是代碼塊。
封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境。
block也有自己的isa指針,依據(jù)block的類別不同,分別指向
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
__NSStackBlock __ ( _NSConcreteStackBlock )
__NSMallocBlock __ ( _NSConcreteMallocBlock )
注:作為block的參數(shù),傳給block內(nèi)部使用,對block的實際類型,無影響
global block:
1、位于全局區(qū)的(比如在類的外部進(jìn)行聲明的)
2、block內(nèi)部僅直接使用了全局變量或者靜態(tài)變量;
stack block變量:
內(nèi)部使用了外部屬性或者變量,創(chuàng)建時,使用的是weak變量指向,并且未顯式做copy操作。
(1)block創(chuàng)建時,使用了外部變量,并且直接賦值給一個weak變量,未顯式做copy操作。
(2)一個stack block,賦值給一個局部block變量(不論該變量是weak還是strong修飾)
malloc block:
通過棧區(qū)block進(jìn)行拷貝而來;
(1)block創(chuàng)建時,使用了外部變量,此時為一個stack block
(3)進(jìn)行顯式copy操作,會拷貝出一個malloc block
(4)進(jìn)行隱式copy操作(賦值給一個對象的strong、copy屬性時,或者創(chuàng)建時直接賦值給一個strong局部變量時),會拷貝出一個malloc block
為什么block要被拷貝到堆區(qū),變成__NSMallocBlock,可以看如下鏈接解釋:Ios開發(fā)-block為什么要用copy修飾
block特性:
1、對外部變量,會連__weak、__strong等修飾符一起拷貝進(jìn)去
2、外部變量是弱引用,則block 內(nèi)部拷貝的也是一個弱引用變量
3、外部變量是強(qiáng)引用,則block 內(nèi)部拷貝的也是一個強(qiáng)引用變量
對于基礎(chǔ)數(shù)據(jù)類型,是值傳遞,修改變量的值,修改的是a所指向的內(nèi)存空間的值,不會改變a指向的地址。
對于指針(對象)數(shù)據(jù)類型,修改變量的值,是修改指針變量所指向的對象內(nèi)存空間的地址,不會改變指針變量本身的地址
簡單來說,基礎(chǔ)數(shù)據(jù)類型,只需要考慮值的地址,而指針類型,則需要考慮有指針變量的地址和指針變量指向的對象的地址
以變量a為例
1、基礎(chǔ)數(shù)據(jù)類型,都是指值的地址
1.1無__block修飾,
a=12,地址為A
block內(nèi)部,a地址變B,不能修改a的值
block外部,a的地址依舊是A,可以修改a的值,與block內(nèi)部的a互不影響
內(nèi)外a的地址不一致
1.2有__block修飾
a=12,地址為A
block內(nèi)部,地址變?yōu)锽,可以修改a的值,修改后a的地址依舊是B
block外部,地址保持為B,可以修改a的值,修改后a的地址依舊是B
2、指針數(shù)據(jù)類型
2.1無__block修飾
a=[NSObject new],a指針變量的地址為A,指向的對象地址為B
block內(nèi)部,a指針變量的地址為C,指向的對象地址為B,不能修改a指向的對象地址
block外部,a指針變量的地址為A,指向的對象地址為B,可以修改a指向的對象地址,
block外部修改后,
外部a指針變量的地址依舊是A,指向的對象地址變?yōu)镈
內(nèi)部a指針變量的地址依舊是C,指向的對象地址依舊是B
2.1有__block修飾
a=[NSObject new],a指針變量的地址為A,指向的對象地址為B
block內(nèi)部,a指針變量的地址為C,指向的對象地址為B,能修改a指向的對象地址
block外部,a指針變量的地址為C,指向的對象地址為B,能修改a指向的對象地址
block內(nèi)外,或者另一個block中,無論哪里修改,a指針變量地址都保持為C,指向的對象地址保持為修改后的一致
block內(nèi)修改變量的實質(zhì)(有__block修飾):
block內(nèi)部能夠修改的值,必須都是存放在堆區(qū)的。
1、基礎(chǔ)數(shù)據(jù)類型,__block修飾后,調(diào)用block時,會在堆區(qū)開辟新的值的存儲空間,
指針數(shù)據(jù)類型,__block修飾后,調(diào)用block時,會在堆區(qū)開辟新的指針變量地址的存儲空間
2、并且無論是基礎(chǔ)數(shù)據(jù)類型還是指針類型,block內(nèi)和使用block之后,變量的地址所有地址(包括基礎(chǔ)數(shù)據(jù)類型的值的地址,指針類型的指針變量地址,指針指向的對象的地址),都是保持一致的
當(dāng)然,只有block進(jìn)行了真實的調(diào)用,才會在調(diào)用后發(fā)生這些地址的變化
另外需要注意的是,如果對一個已存在的對象(變量a),進(jìn)行__block聲明另一個變量b去指向它,
a的指針變量地址為A,b的指針變量會是B,而不是A,
原因很簡單,不管有沒__block修飾,不同變量名指向即使指向同一個對象,他們的指針變量地址都是不同的。
__weak,__strong
兩者本身也都會增加引用計數(shù)。
區(qū)別在于,__strong聲明,會在作用域區(qū)間范圍增加引用計數(shù)1,超過其作用域然后引用計數(shù)-1
而__weak聲明的變量,只會在其使用的時候(這里使用的時候,指的是一句代碼里最終并行使用的次數(shù)),臨時生成一個__strong引用,引用+次數(shù),一旦使用使用完畢,馬上-次數(shù),而不是超出其作用域再-次數(shù)
NSObject *obj = [NSObject new];
NSLog(@"聲明時obj:%p, %@, 引用計數(shù):%ld",&obj, obj, CFGetRetainCount((__bridge CFTypeRef)(obj)));
__weak NSObject *weakObj = obj;
NSLog(@"聲明時weakObj:%p, %@,%@, %@, 引用計數(shù):%ld",&weakObj, weakObj,weakObj,weakObj, CFGetRetainCount((__bridge CFTypeRef)(weakObj)));
NSLog(@"聲明后weakObj引用計數(shù):%ld", CFGetRetainCount((__bridge CFTypeRef)(weakObj)));
聲明時obj:0x16daa3968, <NSObject: 0x282ea0500>, 引用計數(shù):1
聲明時weakObj:0x16daa3960, <NSObject: 0x282ea0500>,<NSObject: 0x282ea0500>, <NSObject: 0x282ea0500>, 引用計數(shù):5
聲明后weakObj引用計數(shù):2
這個5,是因為obj本來計數(shù)是1,
NSLog(@"聲明時weakObj:%p, %@,%@, %@, 引用計數(shù):%ld",&weakObj, weakObj,weakObj,weakObj, CFGetRetainCount((__bridge CFTypeRef)(weakObj)));
這句代碼打印5,是因為除去&weakObj(&這個不是使用weakObj指向的對象,而只是取weakObj的指針變量地址,所以不會引起計數(shù)+1),另外還使用了4次weakObj,導(dǎo)致引用計數(shù)+4
NSLog(@"聲明后weakObj引用計數(shù):%ld", CFGetRetainCount((__bridge CFTypeRef)(weakObj)));
這句打印2,說明上一句使用完畢后,weakObj引用增加的次數(shù)會馬上清楚,重新變回1,而這句使用了一次weakObj,加上obj的一次引用,就是2了
__weak 與 weak
通常,__weak是單獨為某個對象,添加一條弱引用變量的。
weak則是property屬性里修飾符。
LGTestBlockObj *testObj = [LGTestBlockObj new];
self.prpertyObj = testObj;
__weak LGTestBlockObj *weakTestObj = testObj;
NSLog(@"testObj:, 引用計數(shù):%ld", CFGetRetainCount((__bridge CFTypeRef)(testObj)));
NSLog(@"prpertyObj:%p, %@,%@, %@, 引用計數(shù):%ld",&(_prpertyObj), self.prpertyObj,self.prpertyObj,self.prpertyObj, CFGetRetainCount((__bridge CFTypeRef)(self.prpertyObj)));
NSLog(@"prpertyObj:%p, %@,%@, %@, 引用計數(shù):%ld",&(_prpertyObj), _prpertyObj,_prpertyObj,_prpertyObj, CFGetRetainCount((__bridge CFTypeRef)(_prpertyObj)));
NSLog(@"prpertyObj:, 引用計數(shù):%ld", CFGetRetainCount((__bridge CFTypeRef)(_prpertyObj)));
NSLog(@"testObj:, 引用計數(shù):%ld", CFGetRetainCount((__bridge CFTypeRef)(testObj)));
NSLog(@"weakTestObj:%p, %@,%@, %@, 引用計數(shù):%ld",&weakTestObj, weakTestObj,weakTestObj,weakTestObj, CFGetRetainCount((__bridge CFTypeRef)(weakTestObj)));
prpertyObj:0x1088017b0, <LGTestBlockObj: 0x281e1c140>,<LGTestBlockObj: 0x281e1c140>, <LGTestBlockObj: 0x281e1c140>, 引用計數(shù):2
prpertyObj:, 引用計數(shù):2
testObj:, 引用計數(shù):2
weakTestObj:0x16b387958, <LGTestBlockObj: 0x281e1c140>,<LGTestBlockObj: 0x281e1c140>, <LGTestBlockObj: 0x281e1c140>, 引用計數(shù):6
待補(bǔ)充...
Block常見疑問收錄
1、block循環(huán)引用
通常,block作為屬性,并且block內(nèi)部直接引用了self,就會出現(xiàn)循環(huán)引用,這時就需要__weak來打破循環(huán)。
2、__weak為什么能打破循環(huán)引用?
一個變量一旦被__weak聲明后,這個變量本身就是一個弱引用,只有在使用的那行代碼里,才會臨時增加引用結(jié)束,一旦那句代碼執(zhí)行完畢,引用計數(shù)馬上-1,所以看起來的效果是,不會增加引用計數(shù),block中也就不會真正持有這個變量了
3、為什么有時候又需要使用__strong來修飾__weak聲明的變量?
在block中使用__weak聲明的變量,由于block沒有對該變量的強(qiáng)引用,block執(zhí)行的過程中,一旦對象被銷毀,該變量就是nil了,會導(dǎo)致block無法繼續(xù)正常向后執(zhí)行。
使用__strong,會使得block作用區(qū)間,保存一份對該對象的強(qiáng)引用,引用計數(shù)+1,一旦block執(zhí)行完畢,__strong變量就會銷毀,引用計數(shù)-1
比如block中,代碼執(zhí)行分7步,在執(zhí)行第二步時,weak變量銷毀了,而第五步要用到weak變量。
而在block第一步,可先判斷weak變量是否存在,如果存在,加一個__strong引用,這樣block執(zhí)行過程中,就始終存在對weak變量的強(qiáng)引用了,直到block執(zhí)行完畢
4、看以下代碼,obj對象最后打印的引用計數(shù)是多少,為什么?
NSObject *obj = [NSObject new];
void (^testBlock)(void) = ^{
NSLog(@"%@",obj);
};
NSLog(@"引用計數(shù):%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
最后的打印的是3
作為一個局部變量的block,由于引用了外部變量(非靜態(tài)、常量、全局),定義的時候其實是棧區(qū)block,但由于ARC機(jī)制,使其拷貝到堆上,變成堆block,所以整個函數(shù)執(zhí)行的過程中,實際上該block,存在兩份,一個棧區(qū),一個堆區(qū),這就是使得obj引用計數(shù)+2了,加上創(chuàng)建obj的引用,就是3了
5、為什么棧區(qū)block要copy到堆上
block:我們稱代碼塊,他類似一個方法。而每一個方法都是在被調(diào)用的時候從硬盤到內(nèi)存,然后去執(zhí)行,執(zhí)行完就消失,所以,方法的內(nèi)存不需要我們管理,也就是說,方法是在內(nèi)存的棧區(qū)。所以,block不像OC中的類對象(在堆區(qū)),他也是在棧區(qū)的。如果我們使用block作為一個對象的屬性,我們會使用關(guān)鍵字copy修飾他,因為他在棧區(qū),我們沒辦法控制他的消亡,當(dāng)我們用copy修飾的時候,系統(tǒng)會把該 block的實現(xiàn)拷貝一份到堆區(qū),這樣我們對應(yīng)的屬性,就擁有的該block的所有權(quán)。就可以保證block代碼塊不會提前消亡。