Block源碼解析和深入理解

Block源碼解析和深入理解

Block的本質(zhì)

Block是"帶有自動變量值的匿名函數(shù)".

我們通過Clang(LLVM編譯器)來將OC的代碼轉(zhuǎn)換成C++源碼的形式妖谴,通過如下命令:

clang -rewrite-objc 源代碼文件名

下面,我們要轉(zhuǎn)換的Block語法

int main(int argc, const char * argv[]) {
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    blk();
    return 0;
}

該源代碼通過Clang 可變換為以下形式:

/*
    __block_impl (block)結(jié)構(gòu)體聲明
*/
struct __block_impl {
  void *isa; // isa 指針分蓖,指向父類的實例刮便。void * 相當于 id 是個實例。
  int Flags; // 
  int Reserved;
  void *FuncPtr; //函數(shù)指針 指向block代碼塊的實現(xiàn)函數(shù)
};
/*
    __main_block_impl_0 匿名的block 結(jié)構(gòu)體聲明和實現(xiàn)
*/
struct __main_block_impl_0 {
  struct __block_impl impl;//block 的結(jié)構(gòu)體實例
  struct __main_block_desc_0* Desc; //block des的指針 指向block的詳情
  /*
    __main_block_impl_0 結(jié)構(gòu)體構(gòu)造函數(shù)實現(xiàn)
  */
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; // 初始化 block 實例屬性 isa 提前,表示該block 是 _NSConcreteStackBlock (棧)類型的代碼塊
    impl.Flags = flags;
    impl.FuncPtr = fp;// block 具體的函數(shù)實現(xiàn)指針
    Desc = desc;//desc 指針
  }
};
/*
     匿名block 具體的函數(shù)實現(xiàn)
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }
/*
    匿名block desc 指針的具體函數(shù)實現(xiàn)崇渗,對block(__main_block_impl_0) 結(jié)構(gòu)體實例的大小進行初始化
*/
static struct __main_block_desc_0 {
  size_t reserved; // 升級所需區(qū)域
  size_t Block_size;//block 實際內(nèi)存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/*
    把多余的轉(zhuǎn)換去掉牧挣,看起來就比較清楚了:
    第一部分:block的初始化
    __main_block_func_0: 參數(shù)一 是block語法轉(zhuǎn)換的C語言函數(shù)指針急前。
    __main_block_desc_0_DATA: 參數(shù)二 作為靜態(tài)全局變量初始化的 __main_block_desc_0 結(jié)構(gòu)體實例指針
    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;
    第二部分:
    block的執(zhí)行: blk()
    去掉轉(zhuǎn)化部分:
       (*blk -> imp.FuncPtr)(blk);
    這就是簡單地使用函數(shù)指針調(diào)用函數(shù)。由Block語法轉(zhuǎn)換的 __main_block_func_0 函數(shù)的指針被賦值成員變量FuncPtr中浸踩,另外 __main_block_func_0的函數(shù)的參數(shù) __cself 指向Block的值叔汁,通過源碼可以看出 Block 正式作為參數(shù)進行傳遞的。
*/
int main(int argc, const char * argv[]) {
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

針對源碼的解釋 大部分在代碼中都注釋了检碗。需要特別指出的是:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 

中的參數(shù) __cself 是指向 __main_block_impl_0 的指針据块,及匿名block 自身。
擴展:該句源碼類似如 OC 中的方法消息傳遞折剃,OC中每個方法都默認帶兩個參數(shù) 一個是指向自身的實例self 一個是該方法的SEL 對象另假。
例如:

    - (void) method: (int)argc{
        NLog(@"%p %d \n",self,arg)
    }

Objective - C 編譯器同C++的方法一樣,也將該方法作為C語言的函數(shù)來處理.源碼如下:

/*
    方法中 在轉(zhuǎn)換成源碼后 自動的添加了self, _cmd兩個參數(shù) 
*/
    void _I_MyObjct_method_(struct Myobject *self,SEL _cmd, int arg){
        NSLog (@"%p %d \n",self,arg);
    }

截獲自動變量值(局部變量)

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val; //局部變量跟block外的類型一直
  const char *fmt; //跟block外的類型一致
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _dmy, int _val, const char *_fmt, int flags=0) : dmy(_dmy), val(_val), fmt(_fmt) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy //block 調(diào)用外部的局部變量 實際上 相當于Copy 了一份 所以不會影響 局部變量的值 也不能修改值 
  const char *fmt = __cself->fmt; // bound by copy

        printf("Block\n .. ,%d %s",dmy,val,fmt);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    int dmy = 256; //局部變量 
    int val = 10; // 局部變量
    const char *fmt = "val = %d \n"; //局部變量
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, fmt));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

源碼解析:block 在調(diào)用 外部局部變量的時候 其實是將外部局部變量 copy了一份 使用的 所以在沒有任何修飾符的時候是不可以修改外部局部變量的怕犁。

__block 說明符

之前的分析中边篮,block 無法改變被截獲的自動變量的值。這樣極為不便:
解決這個問題有兩種方法奏甫,
第一種:C 語言中有一個變量戈轿,允許block改成值。具體如下:

  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量

雖然Block語法的匿名函數(shù)部分簡單的轉(zhuǎn)換為了C語言函數(shù)阵子,但從這個C語言函數(shù)中訪問靜態(tài)全局思杯,全局變量并沒有任何改變,可直接使用挠进。
但靜態(tài)變量的情況色乾,轉(zhuǎn)換后的函數(shù)原本就設置在含有Block語法的函數(shù)外,所以無法從變量作用域訪問领突。
看看這段代碼的源碼:

     int global_val = 1;
     
     static int static_global_val = 2;
     
     int main(int argc, const char * argv[]) {
 
    static int static_val = 3;
    
    void (^blk)(void) = ^{
    
        global_val += 1;
        
        static_global_val += 2;
        
        static_val += 3;
        
        
    };
    
    blk();
    
    return 0;
    
    }

該源代碼中使用了Block 改寫靜態(tài)變量 靜態(tài)全局變量 全局變量暖璧。該源代碼轉(zhuǎn)換后如下:

int global_val = 1; //全局變量
 static int static_global_val = 2; //靜態(tài)全局變量
            
 struct __main_block_impl_0 {
          struct __block_impl impl;
          struct __main_block_desc_0* Desc;
          int *static_val;//局部靜態(tài)變量   --->  可以看出 跟局部變量不同 這邊是接受的指針
          __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
          }
        };
        static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
          int *static_val = __cself->static_val; // bound by copy // 改代碼跟局部變量 相似,實際上改變的是一個 復制后的指針.但該指針最終指向的 還是最初的變量值君旦。
        
                global_val += 1;
                static_global_val += 2;
                (*static_val) += 3;
        
            }
        
        static struct __main_block_desc_0 {
          size_t reserved;
          size_t Block_size;
        } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
        int main(int argc, const char * argv[]) {
            static int static_val = 3;
            void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
            ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
            return 0;
        }
        

分析該源碼:發(fā)現(xiàn)無論是全局 還是 靜態(tài)全局 都可以在Block中直接訪問 修改變量值澎办。

然而嘲碱,靜態(tài)局部變量,貌似也可以正常訪問浮驳,其調(diào)用原理悍汛,跟之前的局部變量的調(diào)用相似,唯一的不同是至会,在Block中調(diào)用的是 指向該變量的指針,并且是賦值了一份指針(但還是最終指向原來的變量)谱俭。所以我們可以在Block中改變原理變量的值奉件。
這樣就有個疑問,我們?yōu)槭裁床皇褂渺o態(tài)局部變量昆著,來使用去自動變量(局部變量)的訪問呢县貌?
原因:在該靜態(tài)局部變量,有變量作用域凑懂,當block超出了該作用域煤痕,執(zhí)行的時候,其內(nèi)部調(diào)用的靜態(tài)局部變量會被廢棄接谨,我們就無法調(diào)用到摆碉。因此Block中超出變量作用域而存在的變量同靜態(tài)變量一樣,將不能通過指針訪問原來的自動變量脓豪。

解決Block 中不能保存值這一問題的第二個方法是使用__block

int main(int argc, const char * argv[]) {
    __block int val = 3;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    return 0;
}

將上面代碼用 clang 轉(zhuǎn)化后如下:

/*
    __block 轉(zhuǎn)化成了結(jié)構(gòu)體 
*/
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding; //相當于一個指向源變量的指針
 int __flags;
 int __size;
 int val; //相當于源變量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref //持有源變量的結(jié)構(gòu)體實例
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock; // block 為棧類型
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref 巷帝;類似于 靜態(tài)局部變量 都是copy 一份指向源變量的結(jié)構(gòu)體指針。

        (val->__forwarding->val) = 1;//通過訪問 __block 結(jié)構(gòu)體 成員變量 __forwarding 來訪問源變量
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

源碼解析:__Block_byref_val_0 結(jié)構(gòu)體實例的成員變量__forwarding持有指向該實例自身的指針扫夜。通過成員變量__forwarding訪問成員變量val楞泼。(成員變量val是該實例自身持有的變量,它相當于原自動變量)
如圖所示:

Block存儲域
Block 是Objective-C對象笤闯。上面我們所創(chuàng)建的block類 都為_NSConcreteStackBlock.
由上面我們提到的源碼可以知道:

    impl.isa = &_NSConcreteStackBlock

根據(jù) block 結(jié)構(gòu)體實例的 isa 指針進行分類:

  • _NSConcreteStackBlock //不難看出 其存儲域在棧上
  • _NSConcreteGlobalBlock // 其存儲域 在全局
  • _NSConcreteMallocBlock // 其存儲域 在堆上
    詳細分類如圖所示:

_NSConcreteGlobalBlock: 存在的情況:

  • 記述全局變量的地方有Block語法時
  • Block語法的表達式中不使用應截獲的自動變量時

以上情況Block 為 全局類對象堕阔。除此之外Block語法生成的Block為棧類對象,
例如(一):

/*
    在下面的block中由于for循環(huán)的值 一直在變 所以Block截獲的局部變量一直在變颗味。
*/
    typedef int (^blk_t)(int);
    for (int rate = 0;rate < 10; ++rate){
        blk_t blk = ^(int count){
            return rate * count;
        }
    }

轉(zhuǎn)化為源碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int rate;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _rate, int flags=0) : rate(_rate) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

由此可見 雖然block 聲明在全局中超陆,但由于block初始化的時候調(diào)用了局部變量,所以該block創(chuàng)建成棧類型的脱衙。
_NSConcreteMallocBlock :存在的情況
在分析之前我們看下之前遺留的問題:

  • Block 超出變量作用域可存在的原因
  • __block變量用結(jié)構(gòu)體成員變量__forwarding存在的原因

配置在全局變量上的Block,從變量作用域外也可以通過指針安全的使用侥猬。但設置在棧上的Blcok,如果其變量作用域結(jié)束捐韩,該Block就被廢棄退唠,同樣的__block也配置在棧上,所以其所屬的變量作用域結(jié)束荤胁,則該__block變量也會被廢棄瞧预。
Block提供了將Block和__block變量從棧上復制到堆上的方法來解決這個問題



而__block 變量用結(jié)構(gòu)體成員變量__forwarding可以實現(xiàn)無論__block變量配置在棧上還是堆上都能夠正確的訪問__block變量。

深入理解blocks提供的復制方法究竟是啥?

實際上當ARC有效時垢油,編譯器會進行判斷自動的將block從棧上復制到堆上
如:

    typedef int (^blk_t)(int);
        blk_t func (int count){
            return ^(int count){
                return rate *count;
            };
        }

源碼轉(zhuǎn)換為:

    blk_t func (int rate)
    {
        blk_t tmp = &__func_block_impl_0(
            _func_block_func_0,&_func_block_desc_0_DATA,rate
        );
        tmp = objc_retainBlock(tmp);
        return objc_autoreleaseReturnValue(tmp);
    }

分析源碼:從源碼來看 在ARC狀態(tài)下 block復制到堆上 實際上其引用計數(shù)增加了盆驹。

__block變量的存儲域

當block從棧中 復制到堆上時,由于block持有__block變量滩愁,所以其__blcok變量也會從棧中復制到堆上躯喇,所以當block超出作用域調(diào)用__block變量也可以成功。這是和靜態(tài)局部變量最大的區(qū)別硝枉。而靜態(tài)局部變量廉丽,在block從棧中復制到堆上時,由于block不持有變量妻味,所以靜態(tài)局部變量不 會復制到堆上正压,其作用域沒變。故出作用域調(diào)用會崩潰责球。
如圖所示:



截獲對象

下面我們將id對象類型的局部變量 在block中調(diào)用焦履。id類型的對象 默認修飾符 都是__strong類型的。

typedef void (^blk_t)(id);
blk_t blk;
int main(int argc, const char * argv[]) {
    {
        id array = [[NSMutableArray alloc]init]; // __strong 類型修改的局部變量
        blk = [^(id objc){
            [array addObject:objc];
            NSLog(@"array count = %ld",[array count]);
        } copy];
    }
    blk(@"ww");
    return 0;
}

分析 :按理來說 array 對象出了大括號作用域雏逾,強引用失效 其對象就會廢棄嘉裤。但改代碼運行正常。那么就意味著校套,array對象出大括號作用域時价脾,沒有被廢棄 ,仍能正常訪問笛匙。那么是什么原因呢侨把,我們看下Clang之后的源碼.

typedef void (*blk_t)(id);
blk_t blk;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id objc) {
  id array = __cself->array; // bound by copy //復制一份指針 賦值

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)objc);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
        /*
                關(guān)鍵方法:該方法 相當于ARC 中的 retain方法,將對象的引用計數(shù)加一妹孙。但該方法除引用計數(shù)加一外秋柄,還有一個操作就是將block 從棧上復制到堆上,從而可以出作用域蠢正,調(diào)用id __strong修飾類型的對象骇笔。
        */
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
/*
        dispose 相當于ARC 模式下的 release 將對象的引用計數(shù)減一。引用計數(shù)減一得同時嚣崭,將堆上的block 廢棄掉笨触。
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    {
        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));///必須調(diào)用block 的copy 方法才能正常運行
    }
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, (NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_1);
    return 0;
}

//從上面的源碼可以發(fā)現(xiàn):前提:當block調(diào)用copy方法,從棧中復制到對象雹舀,當Block調(diào)用的局部變量是個id對象的時候芦劣,該對象在block中自動的引用計數(shù)加一,并且該block持有該對象说榆,也就是說虚吟,對象出了作用域也能被調(diào)用寸认,知道block 從堆上廢棄掉為止。如果block 的最后沒有調(diào)用copy串慰,那么該對象值偏塞,也會隨著作用域的結(jié)束而被廢棄。
總結(jié):

什么時候棧上的Block會復制到堆上呢邦鲫?

  • 調(diào)用Block的copy實例方法時灸叼。
  • Block作為函數(shù)返回值返回時。
  • 將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時掂碱。
  • 在方法名中含有usingBlock的cocoa框架方法或者GCD的API中傳遞Block時怜姿。

對象和__block的區(qū)別?

  • 如果調(diào)用對象的Block疼燥,沒有調(diào)用Copy 或者不在棧上,那么該對象出作用域就會被釋放蚁堤。
  • 如果調(diào)用對象的Block醉者,調(diào)用了Copy,或者Block在堆上披诗,那么該對象的作用域跟使用__block修飾的變量的作用域一直撬即,都會被Block所持有,并且生命周期呈队,會隨著Block的廢除剥槐,而釋放。

因此當Block中使用對象類型的自動變量時宪摧,除以下情形外粒竖,推薦調(diào)用Block的copy實例方法!几于!

  • block作為函數(shù)返回值返回時蕊苗。
  • Block賦值給類的附加__strong修飾符的id類型或者Block類型的成員變量時。
  • 向方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞Block時沿彭。

__block變量和對象

從前面我們看到__block可以修飾任意類型:

  • 當然包括id對象__strong類型了朽砰,其原理是相同的:
    當 block 從棧上復制到 堆上時,__block 所修飾的自動變量也會從棧上復制到堆上喉刘,使用_Block_objct_assign函數(shù)瞧柔,持有賦值給__block變量的對象。當 block 廢棄時睦裳,__block所修飾的自動變量造锅,也會通過函數(shù)_Block_objct_dispose ,釋放掉__block變量的對象推沸。
  • 當__weak修飾符修飾時备绽,由于__weak修飾的自動變量出作用域后會廢棄 自動置nil券坞,所以當block調(diào)用的時候,其實是調(diào)用的nil對象所以不會崩潰肺素,但取不到值恨锚。
  • 當__block __weak 同時修飾自動變量時,還是因為__weak(不持有對象)的原因倍靡,當 block 從棧上復制到堆上時猴伶,__block變量復制到堆上的是一個nil值,所以對該變量進行的操作都是無效的塌西。
  • 當__block 和 __unsafe__unretained 同時修飾變量時他挎,跟__weak不同,當__unsafe__unretained捡需,所修飾的對象邊nil 時 該變量不會自動置nil办桨,而是變成野指針,所以當block 從棧上復制到堆上時站辉,實際上__block變量是一個野指針呢撞,所以當調(diào)用的時候回出錯,導致程序崩潰
  • __block 和 __autoreleasing 修飾跟 上面的__unsafe__unretained是一樣的饰剥。

Block 循環(huán)引用

存在循環(huán)引用的情況:當block對象 作為類的 屬性或者成員變量殊霞,并且在block初始化的時候,調(diào)用了self或者self相關(guān)類的成員變量汰蓉。都會引起引用循環(huán)绷蹲。

解決方法:

  • 使用__weak 修飾要截取的自動變量,
  • 當在MRC 中時顾孽,可以使用__unsafe_unretained(弊端 不會自動置nil 容易出現(xiàn)野指針) 修飾祝钢。
  • 可以使用__block 修飾,前提是 必須 執(zhí)行block代碼塊岩齿,而且可以適當?shù)卦诖a塊中 手動的把__block變量置nil
    以下是相關(guān)解決方法的實例:
    實例一:
    typedef void (^blk_t)(void);
    @interface Myobject : NSObject
    {
        blk_t blk_; //成員變量
        id _objc;//成員變量
    }
    @end
    @implementation MyObject
    - (id)init
    {
        self = [super init];
        /*
            分析改代碼會出現(xiàn)兩種情況的引用循環(huán):
             * 一種是:成員變量block 調(diào)用 self太颤,self中持有block ,block中也持有self盹沈,導致引用循環(huán)龄章,解決方法在之前 加入
             __weak typeof(self) weakSelf = self;
             * 第二中,雖然成員變量block沒有直接調(diào)用self 乞封,但其調(diào)用了成員變量_objc做裙,所以也會造成引用循環(huán):
             解決方法: __weak id weakObjc = _objc;
        */
        blk_ = ^{
            NSLog(@"self = %@, objc = %@",self,_objc);
        }   
        return self;
    }

實例二:

    typedef void (^blk_t)(void);
    @interface Myobject : NSObject
    {
        blk_t blk_; //成員變量
    }
    @end
    @implementation MyObject
    - (id)init
    {
        self = [super init];
        /*
            此處使用__block修飾變量,是的block 持有__block變量肃晚,而__block變量持有MyObject對象锚贱,而MyObject持有block對象。出現(xiàn)引用循環(huán):
            然而 當 block執(zhí)行的時候关串,__block變量廢棄拧廊,從而消除引用循環(huán)
        */
    __block id temp = self;
        blk_ = ^{
            NSLog(@"self = %@,,self);
            temp = nil;
        }   
        return self;
    }
    - (void)execBlock
    {
        blk_()
     }
     int main (){
        id o = [[MyObject alloc] init];
        [o execBlock];//必須執(zhí)行 否則導致引用循環(huán)
        return 0;    
     }
     

總結(jié)下__block 和 __weak 之間的優(yōu)缺點:
使用__block變量的優(yōu)點:

  • 通過__block 變量可控制對象的持有期間
  • 在不能使用__weak修飾符的環(huán)境中不使用__unsafe__unretain修飾符即可(不必擔心野指針)
    在執(zhí)行Block時可動態(tài)的決定是否將nil或者其他對象賦值在__block變量中监徘。

使用__block變量的缺點如下:

  • 為避免循環(huán)引用必須執(zhí)行Block
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吧碾,隨后出現(xiàn)的幾起案子凰盔,更是在濱河造成了極大的恐慌,老刑警劉巖倦春,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户敬,死亡現(xiàn)場離奇詭異,居然都是意外死亡睁本,警方通過查閱死者的電腦和手機尿庐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呢堰,“玉大人抄瑟,你說我怎么就攤上這事⊥魈郏” “怎么了锐借?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長往衷。 經(jīng)常有香客問我,道長严卖,這世上最難降的妖魔是什么席舍? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哮笆,結(jié)果婚禮上来颤,老公的妹妹穿的比我還像新娘。我一直安慰自己稠肘,他們只是感情好福铅,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著项阴,像睡著了一般滑黔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上环揽,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天略荡,我揣著相機與錄音,去河邊找鬼歉胶。 笑死汛兜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的通今。 我是一名探鬼主播粥谬,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肛根,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漏策?” 一聲冷哼從身側(cè)響起派哲,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哟玷,沒想到半個月后狮辽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡巢寡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年喉脖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抑月。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡树叽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谦絮,到底是詐尸還是另有隱情题诵,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布层皱,位于F島的核電站性锭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏叫胖。R本人自食惡果不足惜草冈,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓮增。 院中可真熱鬧怎棱,春花似錦、人聲如沸绷跑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砸捏。三九已至谬运,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間带膜,已是汗流浹背吩谦。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留膝藕,地道東北人式廷。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像芭挽,于是被迫代替她去往敵國和親滑废。 傳聞我的和親對象是個殘疾皇子蝗肪,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容