在上篇中,仔細分析了一下Block的實現原理以及__block捕獲外部變量的原理。然而實際使用Block過程當中,仍是會遇到一些問題,好比Retain Circle的問題。html
循環引用的問題相信你們都很理解了,這裏仍是簡單的提一下。objective-c
當A對象裏面強引用了B對象,B對象又強引用了A對象,這樣二者的retainCount值一直都沒法爲0,因而內存始終沒法釋放,致使內存泄露。所謂的內存泄露就是本應該釋放的對象,在其生命週期結束以後依舊存在。vim
這是2個對象之間的,相應的,這種循環還能存在於3,4……個對象之間,只要相互造成環,就會致使Retain Cicle的問題。segmentfault
固然也存在自身引用自身的,當一個對象內部的一個obj,強引用的自身,也會致使循環引用的問題出現。常見的就是block裏面引用的問題。網絡
在ARC環境下,id類型和對象類型和C語言其餘類型不一樣,類型前必須加上全部權的修飾符。async
全部權修飾符總共有4種:ide
1.__strong修飾符 2.__weak修飾符 3.__unsafe_unretained修飾符 4.__autoreleasing修飾符函數
通常咱們若是不寫,默認的修飾符是__strong。學習
要想弄清楚__strong,__weak的實現原理,咱們就須要研究研究clang(LLVM編譯器)和objc4 Objective-C runtime庫了。fetch
關於clang有一份關於ARC詳細的文檔,有興趣的能夠仔細研究一下文檔裏面的說明和例子,頗有幫助。
如下的講解,也會來自於上述文檔中的函數說明。
首先咱們先來看看生成的對象持有本身的狀況,利用alloc/new/copy/mutableCopy生成對象。
當咱們聲明瞭一個__strong對象
{
id __strong obj = [[NSObject alloc] init];
}複製代碼
LLVM編譯器會把上述代碼轉換成下面的樣子
id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));複製代碼
相應的會調用
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);複製代碼
上述這些方法都好理解。在ARC有效的時候就會自動插入release代碼,在做用域結束的時候自動釋放。
生成對象的時候不用alloc/new/copy/mutableCopy等方法。
{
id __strong obj = [NSMutableArray array];
}複製代碼
LLVM編譯器會把上述代碼轉換成下面的樣子
id __attribute__((objc_ownership(strong))) array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));複製代碼
查看LLVM文檔,實際上是下述的過程
相應的會調用
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);複製代碼
與以前對象會持有本身的狀況不一樣,這裏多了一個objc_retainAutoreleasedReturnValue函數。
這裏有3個函數須要說明: 1.id objc_retainAutoreleaseReturnValue(id value)
id objc_retainAutoreleaseReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it performs a retain operation followed by the operation described in objc_autoreleaseReturnValue.
Equivalent to the following code: id objc_retainAutoreleaseReturnValue(id value) {
return objc_autoreleaseReturnValue(objc_retain(value)); }Always returns value
2.id objc_retainAutoreleaseReturnValue(id value)
id objc_retainAutoreleasedReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it attempts to accept a hand off of a retain count from a call to objc_autoreleaseReturnValue on value in a recently-called function or something it calls. If that fails, it performs a retain operation exactly like objc_retain.
Always returns value
3.id objc_autoreleaseReturnValue(id value)
id objc_autoreleaseReturnValue(id value); Precondition: value is null or a pointer to a valid object.
If value is null, this call has no effect. Otherwise, it makes a best effort to hand off ownership of a retain count on the object to a call toobjc_retainAutoreleasedReturnValue for the same object in an enclosing call frame. If this is not possible, the object is autoreleased as above.
Always returns value
這3個函數其實都是在描述一件事情。 it makes a best effort to hand off ownership of a retain count on the object to a call to objc_retainAutoreleasedReturnValue for the same object in an enclosing call frame。
這屬於LLVM編譯器的一個優化。objc_retainAutoreleasedReturnValue函數是用於本身持有(retain)對象的函數,它持有的對象應爲返回註冊在autoreleasepool中對象的方法或者是函數的返回值。
在ARC中本來對象生成以後是要註冊到autoreleasepool中,可是調用了objc_autoreleasedReturnValue 以後,緊接着調用了 objc_retainAutoreleasedReturnValue,objc_autoreleasedReturnValue函數會去檢查該函數方法或者函數調用方的執行命令列表,若是裏面有objc_retainAutoreleasedReturnValue()方法,那麼該對象就直接返回給方法或者函數的調用方。達到了即便對象不註冊到autoreleasepool中,也能夠返回拿到相應的對象。
聲明一個__weak對象
{
id __weak obj = strongObj;
}複製代碼
假設這裏的strongObj是一個已經聲明好了的對象。
LLVM轉換成對應的代碼
id __attribute__((objc_ownership(none))) obj1 = strongObj;複製代碼
相應的會調用
id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);複製代碼
看看文檔描述
id objc_initWeak(id *object, id value); Precondition: object is a valid pointer which has not been registered as a __weak object.
value is null or a pointer to a valid object. If value is a null pointer or the object to which it points has begun deallocation, object is zero-initialized. Otherwise, object is registered as a __weak object pointing to value
Equivalent to the following code: id objc_initWeak(id object, id value) { object = nil; return objc_storeWeak(object, value); }
Returns the value of object after the call. Does not need to be atomic with respect to calls to objc_storeWeak on object
objc_initWeak的實現實際上是這樣的
id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}複製代碼
會把傳入的object變成0或者nil,而後執行objc_storeWeak函數。
那麼objc_destoryWeak函數是幹什麼的呢?
void objc_destroyWeak(id *object); Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object.
object is unregistered as a weak object, if it ever was. The current value of object is left unspecified; otherwise, equivalent to the following code:
void objc_destroyWeak(id *object) { objc_storeWeak(object, nil); }
Does not need to be atomic with respect to calls to objc_storeWeak on object
objc_destoryWeak函數的實現
void objc_destroyWeak(id *object) {
objc_storeWeak(object, nil);
}複製代碼
也是會去調用objc_storeWeak函數。objc_initWeak和objc_destroyWeak函數都會去調用objc_storeWeak函數,惟一不一樣的是調用的入參不一樣,一個是value,一個是nil。
那麼重點就都落在objc_storeWeak函數上了。
id objc_storeWeak(id *object, id value); Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value
Returns the value of object after the call.
objc_storeWeak函數的用途就很明顯了。因爲weak表也是用Hash table實現的,因此objcstoreWeak函數就把第一個入參的變量地址註冊到weak表中,而後根據第二個入參來決定是否移除。若是第二個參數爲0,那麼就把\_weak變量從weak表中刪除記錄,並從引用計數表中刪除對應的鍵值記錄。
因此若是__weak引用的原對象若是被釋放了,那麼對應的__weak對象就會被指爲nil。原來就是經過objc_storeWeak函數這些函數來實現的。
以上就是ARC中__strong和__weak的簡單的實現原理,更加詳細的還請你們去看看這一章開頭提到的那個LLVM文檔,裏面說明的很詳細。
在提weakSelf、strongSelf以前,咱們先引入一個Retain Cicle的例子。
假設自定義的一個student類
例子1:
#import <Foundation/Foundation.h>
typedef void(^Study)();
@interface Student : NSObject
@property (copy , nonatomic) NSString *name;
@property (copy , nonatomic) Study study;
@end複製代碼
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",student.name);
};
}複製代碼
到這裏,你們應該看出來了,這裏確定出現了循環引用了。student的study的Block裏面強引用了student自身。根據上篇文章的分析,能夠知道,_NSConcreteMallocBlock捕獲了外部的對象,會在內部持有它。retainCount值會加一。
咱們用Instruments來觀察一下。添加Leak觀察器。
當程序運行起來以後,在Leak Checks觀察器裏面應該能夠看到紅色的❌,點擊它就會看到內存leak了。有2個泄露的對象。Block和Student相互循環引用了。
打開Cycles & Roots 觀察一下循環的環。
這裏造成環的緣由block裏面持有student自己,student自己又持有block。
那再看一個例子2:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^(NSString * name){
NSLog(@"my name is = %@",name);
};
student.study(student.name);
}複製代碼
我把block新傳入一個參數,傳入的是student.name。這個時候會引發循環引用麼?
答案確定是不會。
如上圖,並不會出現內存泄露。緣由是由於,student是做爲形參傳遞進block的,block並不會捕獲形參到block內部進行持有。因此確定不會形成循環引用。
再改一下。看例子3:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@property (copy,nonatomic) NSString *name;
@property (strong, nonatomic) Student *stu;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
self.name = @"halfrost";
self.stu = student;
student.study = ^{
NSLog(@"my name is = %@",self.name);
};
student.study();
}複製代碼
這樣會造成循環引用麼?
答案也是否認的。
ViewController雖然強引用着student,可是student裏面的blcok強引用的是viewController的name屬性,並無造成環。若是把上述的self.name改爲self,也依舊不會產生循環引用。由於他們都沒有強引用這個block。
那遇到循環引用咱們改如何處理呢??類比平時咱們常常寫的delegate,能夠知道,只要有一邊是__weak就能夠打破循環。
先說一種作法,利用__block解決循環的作法。例子4:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
__block Student *stu = student;
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",stu.name);
stu = nil;
};
}複製代碼
這樣寫會循環麼?看上去應該不會。可是實際上倒是會的。
因爲沒有執行study這個block,如今student持有該block,block持有__block變量,__block變量又持有student對象。3者造成了環,致使了循環引用了。 想打破環就須要破壞掉其中一個引用。__block不持有student便可。
只須要執行一下block便可。例子5:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__block Student *stu = student;
student.study = ^{
NSLog(@"my name is = %@",stu.name);
stu = nil;
};
student.study();
}複製代碼
這樣就不會循環引用了。
使用__block解決循環引用雖然能夠控制對象持有時間,在block中還能動態的控制是__block變量的值,能夠賦值nil,也能夠賦值其餘的值,可是有一個惟一的缺點就是須要執行一次block才行。不然仍是會形成循環引用。
值得注意的是,在ARC下__block會致使對象被retain,有可能致使循環引用。而在MRC下,則不會retain這個對象,也不會致使循環引用。
接下來能夠正式開始講講weakSelf 和 strongSelf的用法了。
說道weakSelf,須要先來區分幾種寫法。 __weak __typeof(self)weakSelf = self; 這是AFN裏面的寫法。。
#define WEAKSELF typeof(self) __weak weakSelf = self; 這是咱們平時的寫法。。
先區分__typeof() 和 typeof() 因爲筆者一直很崇拜AFNetWorking的做者,這個庫裏面的代碼都很整潔,裏面各方面的代碼均可以當作代碼範原本閱讀。遇到不懂疑惑的,都要深究,確定會有收穫。這裏就是一處,平時咱們的寫法是不帶__的,AFN裏面用這種寫法有什麼特殊的用途麼?
在SOF上能找到相關的答案:
__typeof__() and __typeof() are compiler-specific extensions to the C language, because standard C does not include such an operator. Standard C requires compilers to prefix language extensions with a double-underscore (which is also why you should never do so for your own functions, variables, etc.) typeof() is exactly the same, but throws the underscores out the window with the understanding that every modern compiler supports it. (Actually, now that I think about it, Visual C++ might not. It does support decltype() though, which generally provides the same behaviour as typeof().) All three mean the same thing, but none are standard C so a conforming compiler may choose to make any mean something different.
其實二者都是同樣的東西,只不過是C裏面不一樣的標準,兼容性不一樣罷了。
更加詳細的官方說明
那麼抽象出來就是這2種寫法。 #define WEAKSELF __weak typeof(self)weakSelf = self; #define WEAKSELF typeof(self) __weak weakSelf = self;
這樣子看就清楚了,兩種寫法就是徹底同樣的。
咱們能夠用WEAKSELF來解決循環引用的問題。例子6:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
NSLog(@"my name is = %@",weakSelf.name);
};
student.study();
}複製代碼
這樣就解決了循環引用的問題了。
解決循環應用的問題必定要分析清楚哪裏出現了循環引用,只須要把其中一環加上weakSelf這相似的宏,就能夠解決循環引用。若是分析不清楚,就只能無腦添加weakSelf、strongSelf,這樣的作法不可取。
在上面的例子3中,就徹底不存在循環引用,要是無腦加weakSelf、strongSelf是不對的。在例子6中,也只須要加一個weakSelf就能夠了,也不須要加strongSelf。
曾經在segmentfault也看到過這樣一個問題,問:爲何iOS的Masonry中的self不會循環引用?
UIButton *testButton = [[UIButton alloc] init];
[self.view addSubview:testButton];
testButton.backgroundColor = [UIColor redColor];
[testButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@100);
make.height.equalTo(@100);
make.left.equalTo(self.view.mas_left);
make.top.equalTo(self.view.mas_top);
}];
[testButton bk_addEventHandler:^(id sender) {
[self dismissViewControllerAnimated:YES completion:nil];
} forControlEvents:UIControlEventTouchUpInside];複製代碼
若是我用blocksKit的bk_addEventHandler 方法, 其中使用strong self, 該viewController就沒法dealloc, 我理解是由於,self retain self.view, retain testButton, retain self. 可是若是隻用Mansonry的mas_makeConstraints 方法, 一樣使用strong self, 該viewController卻能正常dealloc, 請問這是爲何, 爲何Masonry沒有致使循環引用?
看到這裏,讀者應該就應該能回答這個問題了。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}複製代碼
在Masonry這個block中,block僅僅捕獲了self的translatesAutoresizingMaskIntoConstraints變量,可是並無持有self。這個徹底能夠類比我上面舉的例子3,就知道了。這裏不存在循環引用。
上面介紹完了weakSelf,既然weakSelf能完美解決Retain Circle的問題了,那爲什麼還須要strongSelf呢?
仍是先從AFN經典提及,如下是AFN其中的一段代碼:
#pragma mark - NSOperation
- (void)setCompletionBlock:(void (^)(void))block {
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
dispatch_group_async(group, queue, ^{
block();
});
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
[strongSelf setCompletionBlock:nil];
});
}];
}
[self.lock unlock];
}複製代碼
若是block裏面不加__strong __typeof(weakSelf)strongSelf = weakSelf會如何呢?
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",weakSelf.name);
});
};
student.study();
}複製代碼
輸出:
my name is = (null)複製代碼
爲何輸出是這樣的呢?
重點就在dispatch_after這個函數裏面。在study()的block結束以後,student被自動釋放了。又因爲dispatchafter裏面捕獲的\_weak的student,根據第二章講過的__weak的實現原理,在原對象釋放以後,__weak對象就會變成null,防止野指針。因此就輸出了null了。
那麼咱們怎麼才能在weakSelf以後,block裏面還能繼續使用weakSelf以後的對象呢?
究其根本緣由就是weakSelf以後,沒法控制何時會被釋放,爲了保證在block內不會被釋放,須要添加__strong。
在block裏面使用的__strong修飾的weakSelf是爲了在函數生命週期中防止self提早釋放。strongSelf是一個自動變量當block執行完畢就會釋放自動變量strongSelf不會對self進行一直進行強引用。
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
__strong typeof(student) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",strongSelf.name);
});
};
student.study();
}複製代碼
輸出
my name is = Hello World複製代碼
至此,咱們就明白了weakSelf、strongSelf的用途了。
weakSelf 是爲了block不持有self,避免Retain Circle循環引用。在 Block 內若是須要訪問 self 的方法、變量,建議使用 weakSelf。
strongSelf的目的是由於一旦進入block執行,假設不容許self在這個執行過程當中釋放,就須要加入strongSelf。block執行完後這個strongSelf 會自動釋放,沒有不會存在循環引用問題。若是在 Block 內須要屢次 訪問 self,則須要使用 strongSelf。
關於Retain Circle最後總結一下,有3種方式能夠解決循環引用。
結合《Effective Objective-C 2.0》(編寫高質量iOS與OS X代碼的52個有效方法)這本書的例子,來總結一下。
EOCNetworkFetcher.h
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject
@property (nonatomic, strong, readonly) NSURL *url;
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end複製代碼
EOCNetworkFetcher.m
@interface EOCNetworkFetcher ()
@property (nonatomic, strong, readwrite) NSURL *url;
@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic, strong) NSData *downloadData;
@end
@implementation EOCNetworkFetcher
- (id)initWithURL:(NSURL *)url {
if(self = [super init]) {
_url = url;
}
return self;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion {
self.completionHandler = completion;
//開始網絡請求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_downloadData = [[NSData alloc] initWithContentsOfURL:_url];
dispatch_async(dispatch_get_main_queue(), ^{
//網絡請求完成
[self p_requestCompleted];
});
});
}
- (void)p_requestCompleted {
if(_completionHandler) {
_completionHandler(_downloadData);
}
}
@end複製代碼
EOCClass.m
@implementation EOCClass {
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchedData;
}
- (void)downloadData {
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
_fetchedData = data;
}];
}
@end複製代碼
在這個例子中,存在3者之間造成環
一、completion handler的block由於要設置_fetchedData實例變量的值,因此它必須捕獲self變量,也就是說handler塊保留了EOCClass實例;
二、EOCClass實例經過strong實例變量保留了EOCNetworkFetcher,最後EOCNetworkFetcher實例對象也會保留了handler的block。
書上說的3種方法來打破循環。
方法一:手動釋放EOCNetworkFetcher使用以後持有的_networkFetcher,這樣能夠打破循環引用
- (void)downloadData {
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
_fetchedData = data;
_networkFetcher = nil;//加上此行,打破循環引用
}];
}複製代碼
方法二:直接釋放block。由於在使用完對象以後須要人爲手動釋放,若是忘記釋放就會形成循環引用了。若是使用完completion handler以後直接釋放block便可。打破循環引用
- (void)p_requestCompleted {
if(_completionHandler) {
_completionHandler(_downloadData);
}
self.completionHandler = nil;//加上此行,打破循環引用
}複製代碼
方法三:使用weakSelf、strongSelf
- (void)downloadData {
__weak __typeof(self) weakSelf = self;
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
__typeof(&*weakSelf) strongSelf = weakSelf;
if (strongSelf) {
strongSelf.fetchedData = data;
}
}];
}複製代碼
上面講完了weakSelf、strongSelf以後,接下來再講講@weakify、@strongify,這兩個關鍵字是RAC中避免Block循環引用而開發的2個宏,這2個宏的實現過程很牛,值得咱們學習。
@weakify、@strongify的做用和weakSelf、strongSelf對應的同樣。這裏咱們具體看看大神是怎麼實現這2個宏的。
直接從源碼看起來。
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")複製代碼
看到這種宏定義,咋一看什麼都不知道。那就只能一層層的往下看。
先從weakify(...)開始。
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif複製代碼
這裏在debug模式下使用@autoreleasepool是爲了維持編譯器的分析能力,而使用@try/@catch 是爲了防止插入一些沒必要要的autoreleasepool。rac_keywordify 實際上就是autoreleasepool {} 的宏替換。由於有了autoreleasepool {}的宏替換,因此weakify要加上@,造成@autoreleasepool {}。
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)複製代碼
__VA_ARGS__:整體來講就是將左邊宏中 ... 的內容原樣抄寫在右邊 __VA_ARGS__ 所在的位置。它是一個可變參數的宏,是新的C99規範中新增的,目前彷佛只有gcc支持(VC從VC2005開始支持)。
那麼咱們使用@weakify(self)傳入進去。__VA_ARGS__至關於self。此時咱們能夠把最新開始的weakify套下來。因而就變成了這樣:
rac_weakify_,, __weak, __VA_ARGS__總體替換MACRO, SEP, CONTEXT, ...
這裏須要注意的是,源碼中就是給的兩個","逗號是連着的,因此咱們也要等效替換參數,至關於SEP是空值。
替換完成以後就是下面這個樣子:
autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)複製代碼
如今咱們須要弄懂的就是metamacro_concat 和 metamacro_argcount是幹什麼用的。
繼續看看metamacro_concat 的實現
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B複製代碼
## 是宏鏈接符。舉個例子:
假設宏定義爲#define XNAME(n) x##n,代碼爲:XNAME(4),則在預編譯時,宏發現XNAME(4)與XNAME(n)匹配,則令 n 爲 4,而後將右邊的n的內容也變爲4,而後將整個XNAME(4)替換爲 x##n,亦即 x4,故 最終結果爲 XNAME(4) 變爲 x4。因此A##B就是AB。
metamacro_argcount 的實現
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)複製代碼
metamacro_concat是上面講過的鏈接符,那麼metamacro_at, N = metamacro_atN,因爲N = 20,因而metamacro_atN = metamacro_at20。
#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)複製代碼
metamacro_at20的做用就是截取前20個參數,剩下的參數傳入metamacro_head。
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST複製代碼
metamacro_head的做用返回第一個參數。返回到上一級metamacro_at20,若是咱們從最源頭的@weakify(self),傳遞進來,那麼metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前20個參數,最後一個留給metamacro_head_(1),那麼就應該返回1。
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1) 最終能夠替換成metamacro_foreach_cxt1。
在源碼中繼續搜尋。
// metamacro_foreach_cxt expansions
#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
SEP \
MACRO(1, CONTEXT, _1)
#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
SEP \
MACRO(2, CONTEXT, _2)
#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
SEP \
MACRO(3, CONTEXT, _3)
#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
SEP \
MACRO(4, CONTEXT, _4)
#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
SEP \
MACRO(5, CONTEXT, _5)
#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
SEP \
MACRO(6, CONTEXT, _6)
#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
SEP \
MACRO(7, CONTEXT, _7)
#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
SEP \
MACRO(8, CONTEXT, _8)
#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
SEP \
MACRO(9, CONTEXT, _9)
#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
SEP \
MACRO(10, CONTEXT, _10)
#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
SEP \
MACRO(11, CONTEXT, _11)
#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
SEP \
MACRO(12, CONTEXT, _12)
#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
SEP \
MACRO(13, CONTEXT, _13)
#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
SEP \
MACRO(14, CONTEXT, _14)
#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
SEP \
MACRO(15, CONTEXT, _15)
#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
SEP \
MACRO(16, CONTEXT, _16)
#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
SEP \
MACRO(17, CONTEXT, _17)
#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
SEP \
MACRO(18, CONTEXT, _18)
#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
SEP \
MACRO(19, CONTEXT, _19)複製代碼
metamacro_foreach_cxt這個宏定義有點像遞歸,這裏能夠看到N 最大就是20,因而metamacro_foreach_cxt19就是最大,metamacro_foreach_cxt19會生成rac_weakify_(0,__weak,_18),而後再把前18個數傳入metamacro_foreach_cxt18,並生成rac_weakify_(0,__weak,_17),依次類推,一直遞推到metamacro_foreach_cxt0。
#define metamacro\_foreach\_cxt0(MACRO, SEP, CONTEXT)複製代碼
metamacro_foreach_cxt0就是終止條件,不作任何操做了。
因而最初的@weakify就被替換成
autoreleasepool {}
metamacro_foreach_cxt1(rac_weakify_, , __weak, self)複製代碼
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)複製代碼
代入參數
autoreleasepool {}
rac_weakify_(0,__weak,self)複製代碼
最終須要解析的就是rac_weakify_
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);複製代碼
把(0,__weak,self)的參數替換進來(INDEX, CONTEXT, VAR)。 INDEX = 0, CONTEXT = __weak,VAR = self,
因而
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
等效替換爲
__weak __typeof__(self) self_weak_ = self;複製代碼
最終@weakify(self) = __weak __typeof__(self) self_weak_ = self;
這裏的self_weak_ 就徹底等價於咱們以前寫的weakSelf。
再繼續分析strongify(...)
rac_keywordify仍是和weakify同樣,是autoreleasepool {},只爲了前面能加上@
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
_Pragma("clang diagnostic pop")複製代碼
strongify比weakify多了這些_Pragma語句。
關鍵字_Pragma是C99裏面引入的。_Pragma比#pragma(在設計上)更加合理,於是功能也有所加強。
上面的等效替換
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic pop複製代碼
這裏的clang語句的做用:忽略當一個局部變量或類型聲明遮蓋另外一個變量的警告。
最初的
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")複製代碼
strongify裏面須要弄清楚的就是metamacro_foreach 和 rac_strongify_。
#define metamacro_foreach(MACRO, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);複製代碼
咱們先替換一次,SEP = 空 , MACRO = rac_strongify_ , __VA_ARGS__ , 因而替換成這樣。
metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)複製代碼
根據以前分析,metamacro_foreach_cxt再次等效替換,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify_,self)
根據
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)複製代碼
再次替換成metamacro_foreach_iter(0, rac_strongify_, self)
繼續看看metamacro_foreach_iter的實現
#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)複製代碼
最終替換成rac_strongify_(0,self)
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);複製代碼
INDEX = 0, VAR = self,因而@strongify(self)就等價於
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
等價於
__strong __typeof__(self) self = self_weak_;複製代碼
注意@strongify(self)只能使用在block中,若是用在block外面,會報錯,由於這裏會提示你Redefinition of 'self'。
總結一下
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
通過分析之後,其實@weakify(self) 和 @strongify(self) 就是比咱們平常寫的weakSelf、strongSelf多了一個@autoreleasepool{}而已,至於爲什麼要用這些複雜的宏定義來作,目前我尚未理解。若是有大神指導其中的緣由,還請多多指點。
更新
針對文章中給的例子3,你們都提出了疑問,爲什麼沒有檢測出循環引用?其實這個例子有點很差。由於這個ViewController的引用計數一出來就是6,由於它被其餘不少對象引用着。固然它是強引用了student,由於student的retainCount值是2。ViewController釋放的時候纔會把student的值減一。針對這個例子3,我從新抽取出中間的模型,從新舉一個例子。
既然ViewController特殊,那咱們就新建一個類。
#import <Foundation/Foundation.h>
#import "Student.h"
@interface Teacher : NSObject
@property (copy , nonatomic) NSString *name;
@property (strong, nonatomic) Student *stu;
@end複製代碼
#import "ViewController.h"
#import "Student.h"
#import "Teacher.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
Teacher *teacher = [[Teacher alloc]init];
teacher.name = @"i'm teacher";
teacher.stu = student;
student.name = @"halfrost";
student.study = ^{
NSLog(@"my name is = %@",teacher.name);
};
student.study();
}複製代碼
如圖所示,仍是出現了循環引用,student的block強引用了teacher,teacher又強引用了student,致使二者都沒法釋放。