你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 剖析RAC中的@weakify、@strongify

剖析RAC中的@weakify、@strongify

編輯:IOS開發基礎


Reactive Cocoa中的@weakify、@strongify是如何裝逼的

    • 0.很長的前言

    • 1.問題

    • 2.RAC是怎麼解決的

    • 2.weakify、strongify的定義

      • 預備知識

      • 一層層展開weakify

    • 3.RAC裝逼宏

      • metamacro_argcount 的定義

      • metamacro_foreach_cxt 的定義

      • RAC的宏裝逼過程總結

    0.很長的前言

    在block語句塊中,如果需引用self,而self對象中又持有block對象,就會造成循環引用循環引用(retain cycle),導致內存洩露,比如以下代碼

        self.block = ^{
            [self description];
        };

    一般我們是這麼解決的,使用一個__weal修飾的weakSelf變量指向self對象,在block中使用weakSelf:

        __weak typeof(self) weakSelf = self;
        self.block = ^{
            [weakSelf description];
        };

    但是醬紫寫,還是可能出問題,因為weakSelf是弱引用,而self一旦釋放了,weakSelf可能為nil,還是舉個栗子吧:
    1.先定義一個TestObj對象,他的屬性有一個block對象

    @interface TestObj : NSObject@property (nonatomic, copy)void(^block)();@end@implementation TestObj- (void)dealloc {
        NSLog(@"%s",__func__);}- (instancetype)init {
        self = [super init];
        if (self) {
            __weak typeof(self) weakSelf = self;
            self.block = ^{
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    [NSThread sleepForTimeInterval:1];
                    NSLog(@"%@",weakSelf);
                });
            };
        }
        return self;}@end

    2.再另一個類實例中定義一個testFunc方法

        TestObj *obj = [TestObj new];
        obj.block();}

    執行testFunc方法,結果是打印的是(null),因為block裡打印的方法是異步執行的,在 NSLog(@"%@",weakSelf);這句代碼執行之前testFunc函數就結束,所以obj對象已經被release了。
    怎麼解決呢?所以再對weakSelf做一次 __strong就可以了:

    __weak typeof(self) weakSelf = self;
            self.block = ^{
                __strong typeof(weakSelf) strongSelf = weakSelf;
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    [NSThread sleepForTimeInterval:1];
                    NSLog(@"%@",strongSelf);
                });
            };}

    使用了__strongstrongSelf變量作用域結束之前,對weakSelf有一個引用,防止對象(self)提前被釋放。而作用域一過,strongSelf不存在了,對象(self)也會被釋放。

    1.問題

    前面的寫法雖然嚴謹了,也解決了問題了,但是作為喜歡偷懶的程序猿,會不會覺得很啰嗦?每次都要寫那兩條長長的__weak__strong,而且在block裡用到的self的全部要改成strongSelf,假設把一段很多self的代碼拷貝到block裡,一個個改成strongSelf是不是很蛋疼?

    2.RAC是怎麼解決的

    @weakify(self);[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        @strongify(self);
        [self popViewControllerAnimated:YES];}];

    只要在block外用了@weakify(self);然後再block裡寫@strongify(self);就可以了,@strongify(self);語句後的的self可以原封不動,好像很神奇,下面一起看看@weakify、@strongify 這兩個神奇的宏最終替換了什麼東西。
    導入RAC的頭文件,把上面的測試代碼替換成RAC中用的@weakify(self);和@strongify(self), 分屏顯示Xcode,讓右側的顯示內容改為 preprocess“,就可以看到宏最終替換的結果。

    1.jpg

    1. @autoreleasepool {} 是什麼鬼?
      注意到@weakify(self)前面的@顏色並不是橙色沒有?@並不屬於宏的一部分,當然你不能平白無故寫個@對吧,所以RAC的weakify宏定義機智地給你補了一句autoreleasepool {} 這樣一前一後就變成了啥事都沒干的@autoreleasepool {}

    2. __attribute__((objc_ownership(weak)))是什麼鬼?
      這個就是__weak在編譯前被編譯器替換的結果,weakify這個宏後面最終替換成__weak(後面說到),所以編譯器再替換就成了__attribute__((objc_ownership(weak)))

    2.weakify、strongify的定義

    預備知識

    1. ...和 __VA_ARGS__ 看下NSLogprintf,他們的傳入參數有多個,用...表示不確定參數個數, 看看NSLog的定義: NSLog(NSString *format, ...)
      在宏裡也可以用...來表示多個參數,而__VA_ARGS__就對應多個參數的部分。 舉個例子,你覺得NSLog太難看,想造一個自己的log打印函數,比如Zlog你就可以這麼寫:
      #define Zlog(...) NSLog(__VA_ARGS__)

    2.## 是宏連接符,會將 ## 左右兩邊連接起來,
    舉個例子:宏定義為#define XLink(n) x##n,這宏的意思是把x和傳入的n連接起來書寫:

    int x1 = 1;int x2 = 2;int x3 = 3;//打印x1 x2 x3NSlog(@"%zd",XLink(1)); //NSlog(@"%zd",x1);NSlog(@"%zd",XLink(2)); //NSlog(@"%zd",x2);NSlog(@"%zd",XLink(3)); //NSlog(@"%zd",x3);

    一層層展開weakify

    假設我們寫了@weakify(self) 發生了什麼
    第一層:

    #define weakify(...)     rac_keywordify     metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

    rac_keywordify 實際上就是autoreleasepool {}的宏替換

    VA_ARGS就是對應我們傳入的參數
    這裡我們就可以變成這樣:

        autoreleasepool {} 
        metamacro_foreach_cxt(rac_weakify_,, __weak, self)

    第二層:

    #define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)         metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

    這一層開始就比較神奇了,
    替換第一層的結果就變成這樣:

        autoreleasepool {}
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_,  , __weak, self)

    我們先看metamacro_argcount(self)這部分,metamacro_argcount(...)這個宏很強大,可以替換可變參數(...)的個數:
    舉個例子
    metamacro_argcount(@"obj") > 會被替換成1
    metamacro_argcount(@"obj",@“obj”) > 會被替換成2
    所以metamacro_argcount(self) > 會被替換成1(只有一個參數)
    再看看metamacro_concat的定義:

    #define metamacro_concat(A, B)         metamacro_concat_(A, B)

    居然還包了一次,那好,再點進去看看metamacro_concat_的定義

    #define metamacro_concat_(A, B) A ## B

    嗯,搞了半天就是之前說到的宏連接符 ##
    所以metamacro_concat(A, B) 就是把A、B連接起來變成AB
    根據上面分析的metamacro_argcount(self) > 1 ,再用metamacro_concat連接:

        autoreleasepool {}
        metamacro_foreach_cxt ## 1 (rac_weakify_,  , __weak, self)

    也就是:

        autoreleasepool {}
        metamacro_foreach_cxt1(rac_weakify_,  , __weak, self)

    第三層:
    metamacro_foreach_cxt1 沒錯,不要懷疑,他還定義了metamacro_foreach_cxt1這個後面數組為1的宏,搜索一下:2.jpg

    嗯,你沒猜錯,有metamacro_foreach_cxt1就有metamacro_foreach_cxt2、3、4、5、6、7、8...,
    這些是什麼鬼,我們先不管,先看我們的metamacro_foreach_cxt1

    #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);

    再替換一次:
    __weak __typeof__(self) self ## _weak_ = self
    最終真相大白變成:

    autoreleasepool {}__weak __typeof__(self) self_weak_ = self

    同理 strongify(self) 這個宏最終展開是這樣的,注意這裡他重新定義了self,在block語句塊裡面是允許這麼干的,之後用self就是文章一開頭的使用strongSelf一樣。

    autoreleasepool {}__typeof__(self) self = self_weak_;

    褲子都脫了 你就給我看這個?有人要問了 為什麼不直接用
    rac_weakify_就好,搞那麼復雜饒了一大圈,這不是裝逼麼 - -
    其實饒了一大圈的函數就是 @weakify(...);可以支持最多20個參數
    比如: @weakify(ob1,obj2...,obj20);
    最終會替換成:

    @autoreleasepool {}__weak type(obj1) obj1_weak_ = obj1;__weak type(obj2) obj2_weak_ = obj2;...__weak type(obj20) obj20_weak_ = obj20;

    下面,我們來講一講RAC是怎麼裝逼的。

    3.RAC裝逼宏

    metamacro_argcount 的定義

    前面說過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)

    一臉懵逼?沒關系,我們一層層來看:
    假設:metamacro_at(self)
    就變成:
    metamacro_at(20, self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

    看看metamacro_at的定義

    #define metamacro_at(N, ...)         metamacro_concat(metamacro_at, N)(__VA_ARGS__)

    替換進去就是

    metamacro_concat(metamacro_at, 20)(self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

    之前已經知道metamacro_concat是連接宏,所以變成

    metamacro_at20(self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

    搜索metamacro_at20

    3.jpg呵呵 又是一堆亂七八糟的,沒事看懂一個就全懂了。

    #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__)

    嗯,這個宏的意思就是 去掉傳入參數中前20個參數,把剩下的參數傳入metamacro_head宏,上面的metamacro_at20 前20個參數就是:
    self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2
    所以剩下的參數是 1
    假設一開始不是 metamacro_at(self)而是兩個或多個參數會發生什麼?
    比如 metamacro_at(self,self)?
    根據上面的規則替換,就會變成

    metamacro_at20(self,self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

    除去前面20個參數,剩下 只有 2,1 所以把 2,1 這兩個參數傳入metamacro_head(2,1)
    看看的metamacro_head的定義

    #define metamacro_head(...)         metamacro_head_(__VA_ARGS__, 0)

    又是一層,繼續點進去metamacro_head_

    #define metamacro_head_(FIRST, ...) FIRST

    其實就是截取第一個參數,所以
    metamacro_head(2,1)就是 2。
    而前面的metamacro_head(1)就是 1。
    到這裡 相信你已經弄清楚 metamacro_at 是怎麼替換成參數個數的了

    其他metamacro_at 也是一個道理

    metamacro_foreach_cxt 的定義

    回過頭看metamacro_foreach_cxt

    #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)省略N多行

    回到最開始,舉個例子
    metamacro_argcount(obj1,obj2,obj3)
    通過上面metamacro_argcount宏,確定出參數為3後,對號入座傳入
    metamacro_foreach_cxt3

    #define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2)     metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1)     SEP     MACRO(2, CONTEXT, _2)

    所以也就變成了

    metamacro_foreach_cxt3(rac_weakify_, , __weak , obj1 ,obj2 ,obj3)

    發現是個遞歸,也就是

    metamacro_foreach_cxt2(rac_weakify_, , CONTEXT, obj1, obj2) rac_weakify_(2, __weak, obj3)

    而metamacro_foreach_cxt2 又是一層遞歸,最後
    obj1、obj2、obj3都被替換成了:

    __weak type(obj1) obj1_weak_ = obj1;__weak type(obj2) obj2_weak_ = obj2;__weak type(obj3) obj3_weak_ = obj3;

    RAC的宏裝逼過程總結

    其實總結起來很簡單,就2點:

    1. 通過metamacro_argcount確定可變參數個數x

    2. 根據1得到的x調用metamacro_foreach_cxtx,層層遞歸,對每個參數進行宏替換


    1. 上一頁:
    2. 下一頁:
    蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
    Copyright © Ios教程網 All Rights Reserved