在講解正文之前,先說一個block修飾符為copy的緣由。在蘋果官方文檔中有官方示例,如下所示:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.
block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”。在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū)。在 ARC 中寫不寫都行:對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作。如果不寫 copy ,該類的調(diào)用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行了 copy 操作”,他們有可能會在調(diào)用之前自行拷貝屬性值。這種操作多余而低效。你也許會感覺我這種做法有些怪異,不需要寫依然寫。如果你這樣想,其實是你“日用而不知”,你平時開發(fā)中是經(jīng)常在用我說的這種做法的,比如下面的屬性不寫copy也行,但是你會選擇寫還是不寫呢?
一、Block場景分析
在實際使用Block過程中,經(jīng)常會遇到各種問題,比如Retain Cycle,這時候大家可能第一個想到的就是使用__weak修飾對象打破循環(huán)引用。在此之前我們需要明確一點:是不是所有的block,使用self都會出現(xiàn)循環(huán)引用? 其實不然,系統(tǒng)和第三方框架的block絕大部分不會出現(xiàn)循環(huán)引用,只有少數(shù)block以及我們自定義的block會出現(xiàn)循環(huán)引用。在使用__weak之前,要詳細的分析是否造成了循環(huán)引用。下面我們分析幾種場景:
- UIViewAnimationWithBlocks
- GCD
- enumerateObjectsUsingBlock
- MJRefreshHeader
- AFNetworking
1. UIViewAnimationWithBlocks
[UIView animateWithDuration:0.25 animations:^{
self.testView.frame = CGRectMake(80, 100, 100,100);
}];
這里是可以直接寫self的,因為這是一個類方法,當前的self并沒有直接或間接持有這個block。不會循環(huán)引用
animation framework -> block
block -> self
2. GCD
在YYCache源碼中,ibireme大神在_trimInBackground方法中使用dispatch_async用到了__weak,ibireme 認為self->_queue->block->self構(gòu)成了循環(huán)引用。具體源碼如下:
- (void)_trimInBackground {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self; // ①
if (!self) return; // ②
dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER);
[self _trimToCost:self.costLimit];
[self _trimToCount:self.countLimit];
[self _trimToAge:self.ageLimit];
[self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
dispatch_semaphore_signal(self->_lock);
});
}
但筆者認為dispatch時,self->_queue->block->self確實產(chǎn)生了循環(huán)引用,但queue在事后主動釋放了block,破除了循環(huán)引用。所以此處也可以不用__Weak破除循環(huán)。
3. enumerateObjectsUsingBlock
NSArray的enumerateObjectsUsingBlock 跟UIViewAnimationWithBlocks同一個道理self并不會持有block, 所以不會循環(huán)引用。
[self.dataArray enumerateObjectsUsingBlock:^(NSString *str, NSUInteger idx, BOOL * _Nonnull stop) {
[self dosomething:str];
}];
Foundation framework-> block
block -> self
4. MJRefreshHeader
//下拉刷新
WEAKSELF;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakSelf dosomething];
}];
這里用了__weak,我們來具體分析一下:
self -> tableView -> mj_header.block -> self
5. AFNetworking
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:testURL parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"請求成功---%@", responseObject);
self.testLabel.text = @"請求成功";
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
具體分析如下:
AFHTTPSessionManager -> AFURLSessionManagerTaskDelegate(包含manager)
AFURLSessionManagerTaskDelegate ->ViewController block
未發(fā)生循環(huán)引用問題 所以可以直接使用self
二、Block引用計數(shù)測試
在實際使用中,當遇到無法確定是否發(fā)生循環(huán)引用的,可以通過實際測試確定。
條件1:MRC + copy
@property (nonatomic,copy) GXBlock blockTest;
- (void)test {
NSLog(@"1.self的引用計數(shù)=%ld",CFGetRetainCount((__bridge CFTypeRef)(self)));
self.blockTest = ^{
NSLog(@"self == %@",self);
};
self.blockTest();
NSLog(@"2.self的引用計數(shù)=%ld",CFGetRetainCount((__bridge CFTypeRef)(self)));
[self.blockTest copy];
NSLog(@"3.self的引用計數(shù)=%ld",CFGetRetainCount((__bridge CFTypeRef)(self)));
[self.blockTest copy];
NSLog(@"4.self的引用計數(shù)=%ld",CFGetRetainCount((__bridge CFTypeRef)(self)));
}
測試結(jié)果如下:

條件2:MRC + assign
@property (nonatomic,assign) GXBlock blockTest;

條件3:ARC + copy

條件4:ARC + assign

小結(jié):
- 拷貝堆上的block ,self 引用計數(shù)不會改變??截悧I系腷lock,self的引用計數(shù)+1;
- ARC機制下,會自動拷貝棧上的block至堆上,所以手動調(diào)用copy時,不會增加self的引用計數(shù);MRC下,每次將棧上的block拷貝至堆后,self引用計數(shù)+1;
- assign修飾block不會增加引用計數(shù),copy修飾block會在增加引用計數(shù)。
- ARC下,copy修飾的block中有self時,self引用計數(shù)+2;assign修飾的block中有self時,引用計數(shù)+1;
- MRC下,copy修飾的block中有self時,self引用計數(shù)+1;assign修飾的block中有self時,引用計數(shù)不變;
- 使用weakself可以破除循環(huán)引用的問題;