block

要了解什么是block, 我們先寫一個(gè)block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^myBlock)(void) = ^{
            NSLog(@"this is a block");
        };
        myBlock();
    }
    return 0;
}

現(xiàn)在我寫了一個(gè)簡單的block
利用


xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

命令行生成編譯完的C++代碼, 發(fā)現(xiàn)block被編譯后的樣子:
這是block的聲明:

void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

這是block的調(diào)用:

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

去掉一些無用的類型轉(zhuǎn)換:

void(*block)(void) = &__main_block_impl_0(
    __main_block_func_0, 
    &__main_block_desc_0_DATA
);

我們發(fā)現(xiàn)block實(shí)際上就是一個(gè)__main_block_impl_0函數(shù)的返回值的地址, 將地址賦值給一個(gè)名叫block的函數(shù)指針
那么__main_block_impl_0是什么呢?

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

我們發(fā)現(xiàn)__main_block_impl_0是一個(gè)結(jié)構(gòu)體,

要注意的是, 還有一個(gè)__main_block_impl_0的同名函數(shù), 這是C++里定義的一個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù), 也就是說, 這個(gè)構(gòu)造函數(shù)返回的是一個(gè)結(jié)構(gòu)體struct __main_block_impl_0, 我們在上面看到的這個(gè)就是利用這個(gè)構(gòu)造函數(shù)產(chǎn)生的一個(gè)struct __main_block_impl_0結(jié)構(gòu)體
void(*block)(void) = &__main_block_impl_0(
    __main_block_func_0, 
    &__main_block_desc_0_DATA
);
  • 第一個(gè)參數(shù)__main_block_func_0,

    image.png

    通過這個(gè)NSLog就可以看出, 這是block這里面的代碼實(shí)現(xiàn).

  • 第二個(gè)參數(shù)&__main_block_desc_0_DATA
    這相當(dāng)于是一個(gè)block的描述, 也是一個(gè)block,
    第一個(gè)成員變量是保留字段, 現(xiàn)在傳的是0
    第二個(gè)成員變量是Block_size, 就是block的大小. 傳的就是struct __main_block_impl_0的大小(size of)

    image.png

參數(shù)傳進(jìn)去了之后就是給這個(gè)結(jié)構(gòu)體賦值, 讓我們再來看看這個(gè)結(jié)構(gòu)體

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

第一個(gè)成員變量impl, 類型是struct __block_impl是這樣的:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

相當(dāng)于block的內(nèi)存布局是這樣的:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
};
我們發(fā)現(xiàn)block也是有isa指針的, 所以從本質(zhì)上說, block也是OC對象
image.png

__main_block_func_0就是函數(shù)指針, 當(dāng)做參數(shù)傳進(jìn)去構(gòu)造函數(shù), 然后在構(gòu)造函數(shù)里傳給結(jié)構(gòu)體里的變量FuncPtr

那我們來看一下block的調(diào)用:


((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

去掉一些類型轉(zhuǎn)換

myBlock->FuncPtr(myBlock)
image.png

實(shí)際上就是找到myBlock中保存的FuncPtr函數(shù)指針, 然后直接調(diào)用就好了

我們再來看一下復(fù)雜一點(diǎn)的block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void (^myBlock)(void) = ^{
            NSLog(@"this is a block--%d", a);
        };
        a = 20;
        myBlock();
    }
    return 0;
}

然后再編譯成C++文件, 看看發(fā)生了什么:

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

可以很清楚的看到, 這個(gè)block包含了一個(gè)新的成員變量int a, 這個(gè)block的內(nèi)存布局就是:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
  int a;
};

這個(gè)結(jié)構(gòu)體的最后一個(gè)成員變量就是int a, 看這個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù):

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

這個(gè)構(gòu)造函數(shù)多了一個(gè)參數(shù), 就是int _a

image.png

而創(chuàng)建這個(gè)block的時(shí)候, 傳進(jìn)去的參數(shù)就是我定義的int a = 10, 傳進(jìn)去了之后, 就把_a的值, 賦給了結(jié)構(gòu)體內(nèi)部的成員變量a,

: a(_a)

這就是C++的語法, 將_a賦值給a, 所以外面的a和里面的a, 不是同一個(gè)東西, 外面的a, 是我定義的變量a, 里面的ablock在創(chuàng)建的時(shí)候, 由編譯器生成的成員變量a

相當(dāng)于, 結(jié)構(gòu)體在創(chuàng)建的時(shí)候, 捕獲了這個(gè)外部的變量a

image.png

這就造成, 即使在block調(diào)用之前, 修改變量的值為20, 也不會改變block調(diào)用時(shí)獲取的變量值, 因?yàn)?code>block調(diào)用的值, 是block的成員變量的那個(gè)值.

請記住一個(gè)關(guān)鍵的詞- Capture捕獲

那么什么情況下會捕獲呢?
記住以下原則:

  • 局部變量會捕獲
  • 全局變量不會捕獲
    請問, 什么是局部變量, 什么是全局變量呢?
    簡單來說, 聲明在函數(shù)內(nèi)部的變量是局部變量, 聲明在函數(shù)外部的變量稱之為全局變量

block真的不會捕獲全局變量嗎?

image.png

好, 記住了兩條大的原則:

  • 局部變量會捕獲
  • 全局變量不會捕獲
    還有, 局部變量又分為auto變量和static變量
    像這種聲明之后存在于棧上的變量稱之為auto變量, auto這個(gè)關(guān)鍵字是可以省略的
    image.png

那么, 被static修飾了的變量和auto變量有什么不同呢?

image.png

放在常量區(qū)的變量有一個(gè)特點(diǎn)是生命周期延長了, 他的生命周期跟程序的運(yùn)行周期是一致的, 只要程序沒有終止, 那么常量區(qū)的數(shù)據(jù)是一直存在的. 這和棧區(qū)的數(shù)據(jù)不同, 棧區(qū)存放的數(shù)據(jù)的特點(diǎn)是, 只要作用域結(jié)束, 那棧區(qū)的內(nèi)存就會被回收. 那我們來看看是不是這樣的:

圖1
圖2

如圖2的所示, 當(dāng)用static關(guān)鍵字修飾變量時(shí), 當(dāng)作用域結(jié)束時(shí), 變量是不會銷毀的, 當(dāng)時(shí)離開作用域, 是訪問不到變量的, 意思是, 雖然變量存在數(shù)據(jù)段, 但離開作用域, 無法訪問變量. 這相當(dāng)于變量的作用域不變, 變量的生命周期延長了.

由于這個(gè)情況, 局部變量中, auto變量和static變量被block時(shí), 處理情況是不同的:

  • 值傳遞(auto變量)
    因?yàn)?code>auto變量在作用域結(jié)束之后, 變量就會回收, 它的生命周期是很短的, 所以block在捕獲時(shí), 會把auto變量的值賦值給block內(nèi)部的同名成員變量, 這個(gè)成員變量是一個(gè)新的內(nèi)存空間存儲這個(gè)值

  • 指針傳遞(static變量)
    static變量就不一樣了, 它存儲在數(shù)據(jù)段(常量區(qū)), 它的生命周期是跟程序的生命周期一致的, 也就是說, 在block需要訪問這個(gè)變量的時(shí)候, 我訪問的仍然是這個(gè)變量本身, 那么這時(shí)候, 我捕獲的變量, 就是這個(gè)變量的指針(存儲這個(gè)變量的地址), block把變量的指針賦值給block的同名成員變量

image.png

可以很清楚地看到, 是將變量a的地址值傳到了block的構(gòu)造函數(shù)中

image.png

最終, 賦值給了block內(nèi)部的指針變量a, 所以block的成員變量的類型是int *.

image.png

image.png

因此, 在調(diào)用myBlock之前, 修改了static變量a的值, 在調(diào)用myBlock時(shí), a的值已經(jīng)改了

image.png

上面是基本數(shù)據(jù)類型的情況, 那么如果是OC對象, block又將如何捕獲呢?
通過編譯, 我們發(fā)現(xiàn), 在struct __main_block_desc_0結(jié)構(gòu)體中, 多出來兩個(gè)成員變量

image.png

  • copy
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

棧 -> 堆

  • dispose
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

那么這個(gè)有什么用呢? 還得從ARC的自動copy說起
ARC模式下,

  • block作為函數(shù)返回值時(shí)
  • block賦值給__strong指針時(shí)(ARC環(huán)境下, 一般默認(rèn)就是__strong指針, 除非用__weak或者__unsafe_unretained修飾的指針),
  • 或者GCD,
  • 或者函數(shù)名中有usingBlock的,
    都會對block進(jìn)行一次copy操作. 那么這個(gè)copy操作有什么用呢?
    我們先來看看block的種類:
image.png

從上圖可以看出, 我們把編譯環(huán)境改成MRC(這是因?yàn)?code>ARC環(huán)境編譯器幫我們做了很多事情, 不方便我們探究本質(zhì)), 然后打印這三個(gè)block的類型, 我們發(fā)現(xiàn)block是分為三種的:

  • __NSGlobalBlock__這個(gè)block是存在數(shù)據(jù)段的, 暫時(shí)不探究
  • __NSStackBlock__這個(gè)是存在棧區(qū), 也叫棧block
  • __NSMallocBlock__這個(gè)是在堆區(qū), 所以叫堆block

說回來copy, 當(dāng)我們對一個(gè)block執(zhí)行copy操作時(shí)

  • __NSGlobalBlock__還是__NSGlobalBlock__
  • __NSStackBlock__會升級為__NSMallocBlock__(這一點(diǎn)要尤其注意)
  • __NSMallocBlock__還是__NSMallocBlock__

當(dāng)棧block升級為堆block時(shí), 這時(shí)候堆中的數(shù)據(jù)就依靠程序員來管理了, 而不是像棧block一樣, 椣胄恚空間自動回收之后, 保存在棧中的block就沒有了. 在MRC環(huán)境下, 如果是棧block的話, 如下圖所示

image.png

block內(nèi)部的person指針只是指向person存儲的那片內(nèi)存空間, 并不會對person對象引用計(jì)數(shù)+1, 那么當(dāng)person對象被回收后[person release], 再去訪問棧block中的person指針指向的那片內(nèi)存空間, 就很危險(xiǎn)了, 就會造成野指針訪問.

但如果是堆block呢? 這時(shí)候就會對person對象進(jìn)行引用計(jì)數(shù)+1, 那這時(shí)候再去訪問堆block中的person指針指向的那片內(nèi)存空間, 是沒有問題的

image.png

但如果使用__unsafe_unretained關(guān)鍵字修飾person對象時(shí), 會發(fā)生什么呢?

image.png

可以看到的是, 壞內(nèi)存訪問. 這是因?yàn)? 因?yàn)槲覀冇?code>__unsafe_unretained關(guān)鍵字修飾了person對象, 所以, 即使block被拷貝到堆區(qū), block內(nèi)部也不會對person對象引用計(jì)數(shù)+1 , 那么當(dāng)我向person對象發(fā)送release消息后, person對象引用計(jì)數(shù)-1, 這時(shí)候是會被銷毀的, 此時(shí)再去訪問block內(nèi)部的person指針指向的那片內(nèi)存空間, 就會造成野指針訪問

ARC模式下,

  • block作為函數(shù)返回值時(shí)
  • block賦值給__strong指針時(shí)(ARC環(huán)境下, 一般默認(rèn)就是__strong指針, 除非用__weak或者__unsafe_unretained修飾的指針),
  • 或者GCD,
  • 或者函數(shù)名中有usingBlock的,
    ARC在以上四種情況下, 會自動對block進(jìn)行copy操作, 也就是說, 這個(gè)棧block會升級成堆block, 升級成堆block后, 堆block中的person指針會對person對象強(qiáng)引用, 那么這樣一來, 即使block外面的person指針被回收了, person對象依然不會銷毀, 它會隨著block的生命周期結(jié)束而銷毀.
上面提到的例子中, 如果是局部的auto變量, 我們其實(shí)是無法修改變量的值. 因?yàn)?code>auto變量的地址沒有變, 假設(shè)我們要修改定義的局部變量的值, 我們需要做一件事, 就是加上__block關(guān)鍵字, 那么__block的作用是什么呢?

還是先看看編譯情況:

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};;

簡化下來就是:

 __Block_byref_a_0 a = {
  0,
  &a, 
  0, 
  sizeof(__Block_byref_a_0), 
  18
};

__Block_byref_a_0這個(gè)又是什么東西呢?

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

可以看到的是, __block修飾的變量, 在編譯時(shí)期, 自動包裝成了一個(gè)結(jié)構(gòu)體(這個(gè)結(jié)構(gòu)體的名字跟修飾的變量同名), 這個(gè)結(jié)構(gòu)體的內(nèi)存布局如上圖所示.

  • 0賦值給了_isa指針
  • &a(結(jié)構(gòu)體a的地址)賦值給了__forwarding指針
  • 0賦值給__flags
  • sizeof(__Block_byref_a_0)賦值給了__size
  • 最后18這個(gè)變量賦值給了結(jié)構(gòu)體的最后一個(gè)變量a

所以原本的int a, 包裝成了一個(gè)結(jié)構(gòu)體, 然后這個(gè)結(jié)構(gòu)體的地址作為參數(shù)傳給了block的構(gòu)造函數(shù). block內(nèi)部有一個(gè)成員變量__Block_byref_a_0 *相當(dāng)于捕獲了這個(gè)變量

但是很明顯, 在MRC模式下, 包裝過后的這個(gè)結(jié)構(gòu)體也存在于棧區(qū)

image.png
, block此時(shí)也是一個(gè)棧block, 那棧的內(nèi)存空間是隨著作用域的結(jié)束而回收的!
事實(shí)上, 我理解的是, 例如下段代碼:
image.png

在31行代碼的時(shí)候, age作為一個(gè)存在于棧的變量, 它已經(jīng)被系統(tǒng)回收了, 所以31行block去訪問age這片內(nèi)存空間的時(shí)候, 其實(shí)是很危險(xiǎn)的. 雖然這里成功打印了age的值, 但這么做是不合理的.

image.png

這幅圖應(yīng)該這么畫. 也就是說, 在MRC情況下, 實(shí)際上并沒有強(qiáng)弱引用的概念. 指針只是指向這這片存儲空間, 并沒有強(qiáng)指針, 弱指針的概念. 當(dāng)作用域結(jié)束, 椛ǎ空間被系統(tǒng)回收, 指針再指向被回收的棧空間, 是一件很危險(xiǎn)的事, 可能取到的值不正確.

即使是指針指向的是堆空間的對象, 也沒有強(qiáng)弱指針的概念, 這就是為什么在MRC環(huán)境下, 需要手動給引用計(jì)數(shù)+1.

image.png

那么在MRC環(huán)境下, 需要手動將block拷貝到堆區(qū). 或者, 在屬性修飾時(shí), 使用copy修飾
沒有用copy修飾

image.png

打印出來就是棧block
image.png

使用copy修飾
image.png

打印出來就是堆block
image.png

當(dāng)使用了copy關(guān)鍵字時(shí), block已經(jīng)升級成了堆block, 同時(shí), __block修飾的變量也在堆區(qū), 何以見得? 請看下圖:

image.png

很明顯, age這個(gè)變量已經(jīng)到了堆區(qū). 那么問題來了, block內(nèi)部會對這個(gè)__block修飾的變量有一個(gè)retain操作嗎? 我覺得應(yīng)該有一個(gè)類似retain的操作, 理由如下:
__block修飾的變量在堆區(qū), 堆區(qū)的變量回收是程序員來決定的, 而__block修飾的變量的生命周期是和block一致的, 其實(shí)就相當(dāng)于block持有了__block修飾的變量

我猜測內(nèi)部的原理是這樣的:
堆區(qū)的block會調(diào)用__main_block_copy_0方法

image.png

__main_block_copy_0方法內(nèi)部又調(diào)用了_Block_object_assign

image.png

你也可以理解為將__block修飾的變量(此時(shí)被包裝成了一個(gè)對象), 然后堆block會持有這個(gè)對象(也就是引用計(jì)數(shù)+1).

在這一點(diǎn)上ARCMRC是一致的, 那么不同點(diǎn)是什么呢?
MRC環(huán)境下, __block修飾的變量(包裝后的對象), 這個(gè)對象內(nèi)部的指針并不會持有外面的對象, 舉個(gè)例子:

image.png

這里出現(xiàn)了壞內(nèi)存訪問的錯(cuò)誤, 盡管block持有了 __block修飾的變量(包裝后的對象), 這個(gè)對象內(nèi)部的指針并不會持有外面的對象, 也就是圖中的person對象并沒有被__block修飾的變量(包裝后的對象)持有, 在MRC環(huán)境下:

image.png

MRC環(huán)境下, 給person對象發(fā)送release消息, 引用計(jì)數(shù)-1, 對象直接銷毀, 壞內(nèi)存訪問, 說明person1并沒有持有person對象, 畫圖表示就是

image.png

但是, 在ARC環(huán)境下, __block修飾的變量的person指針是會通過__Block_byref_id_object_copy方法, 對person對象強(qiáng)引用的(引用計(jì)數(shù)+1)

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

簡化下來就是:

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign(dst + 40, src + 40, 131);
}

這句代碼實(shí)際上就是調(diào)用_Block_object_assign, 內(nèi)部對person對象的引用計(jì)數(shù)+1, 這樣就形成了強(qiáng)引用.畫圖表示就是:

image.png

通過這幅圖, 可以看出來的是, block內(nèi)部的內(nèi)存管理就是:
MRC環(huán)境下, 執(zhí)行copy操作. 在ARC環(huán)境下, 系統(tǒng)默認(rèn)執(zhí)行copy操作, 此時(shí), block內(nèi)部的person指針會指向__block修飾的結(jié)構(gòu)體, 通過
_Block_object_assign方法, 對__block修飾的結(jié)構(gòu)體進(jìn)行一次copy操作, 也就是引用計(jì)數(shù)+1, 在block執(zhí)行完畢, 需要被銷毀時(shí), 執(zhí)行_Block_object_dispose方法, 對__block修飾的結(jié)構(gòu)體進(jìn)行一次release操作, 也就是引用計(jì)數(shù)-1.此時(shí), __block修飾的結(jié)構(gòu)體和block一起被銷毀.
不同的是, 在MRC環(huán)境下, __block修飾的結(jié)構(gòu)體內(nèi)部的_Block_object_assign不會再對person對象引用計(jì)數(shù)+1, _Block_object_dispose方法也不會對person對象引用計(jì)數(shù)-1,
, 在ARC環(huán)境下, __block修飾的結(jié)構(gòu)體內(nèi)部的_Block_object_assign會對person對象引用計(jì)數(shù)+1, _Block_object_dispose方法也會對person對象引用計(jì)數(shù)-1.

以上就是block內(nèi)部的內(nèi)存管理.
還有一點(diǎn)需要提到的是, __block修飾的變量轉(zhuǎn)化成的結(jié)構(gòu)體中, __forwarding指針是干嘛用的? 在我們之前的代碼中, 看到是將結(jié)構(gòu)體自己的地址傳給了__forwarding指針. 那么這個(gè)指針的值就是自己這個(gè)結(jié)構(gòu)體的地址, 也就是說__forwarding指針指向了自己, 那么為什么不直接從結(jié)構(gòu)體中取值, 而是要通過一個(gè)__forwarding指針呢?

當(dāng)__block修飾的這個(gè)變量包裝成的結(jié)構(gòu)體還存在于堆區(qū)的時(shí)候, 現(xiàn)在這個(gè)地址是指向棧區(qū)的地址的, 但這本身并沒有什么意義.
但當(dāng)這個(gè)包裝結(jié)構(gòu)體被拷貝到了堆區(qū), 此時(shí)再去訪問這個(gè)變量的時(shí)候, 就會指向堆區(qū)的那個(gè)包裝結(jié)構(gòu)體. 也就是說, a->__forwarding->a的這個(gè)過程就是訪問堆區(qū)數(shù)據(jù)的過程.

image.png
image.png

那么假設(shè), 我想和MRC一樣, 對person對象不進(jìn)行強(qiáng)引用呢?
這時(shí)候就需要用到__weak__unsafe_unretain關(guān)鍵字了. 事實(shí)上, 默認(rèn)狀態(tài)下, 都是相當(dāng)于使用了__strong關(guān)鍵字, 相當(dāng)于__block修飾的變量結(jié)構(gòu)體里的那根person指針默認(rèn)就是強(qiáng)指針. 當(dāng)使用__weak修飾時(shí), 被__block修飾的變量就變成了:

image.png

編譯器還爆出了警告, 讓我不要這么做:
image.png

將持有的對象賦值給一個(gè)弱指針, 對象將在賦值完成后立即釋放

使用__unsafe_unretained也是差不多的效果:

image.png

__weak__unsafe_unretained這兩個(gè)關(guān)鍵字是用來解決循環(huán)引用的時(shí)候用到的. 那么什么是循環(huán)引用呢? 如下圖所以:
ARC環(huán)境下, 被強(qiáng)指針指向的對象引用計(jì)數(shù)+1, 此時(shí)person對象創(chuàng)建時(shí)引用計(jì)數(shù)+1, 被強(qiáng)指針指向時(shí), 引用計(jì)數(shù)+1, 此時(shí)引用計(jì)數(shù)是2, block對象創(chuàng)建時(shí)引用計(jì)數(shù)+1, 被person對象內(nèi)部的強(qiáng)指針指向時(shí), 引用計(jì)數(shù)+1, 此時(shí)引用計(jì)數(shù)是2. 那么當(dāng)他們引用計(jì)數(shù)都是2時(shí). 它們兩個(gè)就都無法銷毀. 此時(shí), 必須打破這個(gè)循環(huán)

image.png

一般打破循環(huán)的方式, 就是讓其中一根指針變成弱指針, 一般就是將block內(nèi)部指向?qū)ο蟮闹羔樧兂扇踔羔?br>

image.png

一旦這跟指針變成弱指針后, person對象銷毀后, person對象內(nèi)部的那根指針被回收, 回收后, block對象釋放.

其實(shí)被__block修飾的變量也是同理, 它是這樣形成:

image.png

而我們用__weak修飾變量后, 形成的閉環(huán)其實(shí)是將__block修飾的結(jié)構(gòu)體里的person變成弱指針:

image.png

那么__weak__unsafe_unretained有什么區(qū)別呢?
__weak修飾的變量, 一旦內(nèi)存空間被回收, __weak修飾的指針變量就會置為nil, 后面再訪問就會直接return, 因此它是安全的
__unsafe_unretained修飾的變量, 一旦內(nèi)存空間被回收,__unsafe_unretained修飾的指針變量不會置為nil, 后面再訪問, 是非常危險(xiǎn)的. 有可能會造成野指針訪問.

最后再探討一個(gè)問題:

iOS block內(nèi)部為什么要加__strong?

這是為了防止當(dāng)程序執(zhí)行block時(shí), block內(nèi)部的指針指向的那塊地址突然為空. 舉個(gè)例子:

image.png

假設(shè)上圖中, 在程序執(zhí)行到第22行時(shí), self突然為空, 如果不寫__strong typeof(self)strongSelf = weakSelf;這行代碼時(shí), 那后面訪問weakSelf指向的地址空間時(shí), 就可能為空. 但是當(dāng)我寫了__strong typeof(self)strongSelf = weakSelf;時(shí), 此時(shí), 我用一個(gè)棧區(qū)的局部變量強(qiáng)引用了self對象. 那self此時(shí)的引用計(jì)數(shù)+1, 它不會被置為空. 我后面的代碼就可以繼續(xù)訪問. 等到作用域結(jié)束, 局部變量椨诟觯空間回收, self對象的引用計(jì)數(shù)-1. 這樣是不會形成循環(huán)引用的, 如下圖所示
image.png

iOS開發(fā)中在block中為什么要__weak和__strong配合使用
上文中舉出了一個(gè)__weak__strong配合使用的例子, 僅供參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市垃沦,隨后出現(xiàn)的幾起案子涯鲁,更是在濱河造成了極大的恐慌,老刑警劉巖浮梢,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彤路,居然都是意外死亡秕硝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門洲尊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來远豺,“玉大人,你說我怎么就攤上這事坞嘀∏ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵丽涩,是天一觀的道長棺滞。 經(jīng)常有香客問我,道長矢渊,這世上最難降的妖魔是什么继准? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮矮男,結(jié)果婚禮上锰瘸,老公的妹妹穿的比我還像新娘。我一直安慰自己昂灵,他們只是感情好避凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眨补,像睡著了一般管削。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撑螺,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天含思,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛稿静,可吹牛的內(nèi)容都是我干的苔巨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼济蝉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起漱逸,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泪姨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饰抒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肮砾,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年袋坑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仗处。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枣宫,死狀恐怖婆誓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镶柱,我是刑警寧澤旷档,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站歇拆,受9級特大地震影響鞋屈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜故觅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一厂庇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧输吏,春花似錦权旷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至它浅,卻和暖如春译柏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姐霍。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工鄙麦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镊折。 一個(gè)月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓胯府,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恨胚。 傳聞我的和親對象是個(gè)殘疾皇子骂因,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

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