iOS-block3-__block變量的內(nèi)存管理、__forwarding、__block修飾的對象類型、循環(huán)引用

上文講了一下__block的原理,但是關(guān)于__block還有一些其他東西,這篇文章就來慢慢講述。

一. __block變量的內(nèi)存管理

通過上文我們知道,block捕獲對象類型的auto變量就會多出兩個函數(shù)用于做內(nèi)存管理操作(__main_block_copy_0和__main_block_dispose_0),如下:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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};

block也會使用這兩個函數(shù)管理__block修飾的變量的內(nèi)存,這也從側(cè)面證明“編譯器會將__block變量包裝成一個對象”,這句話是對的,因為只有對象才需要內(nèi)存管理。
下面我們就研究使用__block修飾的變量的內(nèi)存管理:

  1. 當(dāng)block在棧上時,并不會對__block變量產(chǎn)生強(qiáng)引用
  2. 當(dāng)block被copy到堆時(自己拷貝的或者ARC下系統(tǒng)自動拷貝的)
    會調(diào)用block內(nèi)部的copy函數(shù)
    copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    _Block_object_assign函數(shù)會對__block變量形成強(qiáng)引用(retain)

如下圖:

__block的內(nèi)存管理-copy

解釋:剛開始的時候,__block變量和block0、block1肯定都在棧區(qū),假如他們同時使用__block變量。
當(dāng)把block0復(fù)制到堆區(qū),也會把__block變量復(fù)制到堆區(qū),并且block0強(qiáng)引用__block變量。
再把block1復(fù)制到堆區(qū),就不會再次復(fù)制__block變量了(因為已經(jīng)拷貝過了),這時候block0和block1都會強(qiáng)引用著__block變量。

  1. 當(dāng)block從堆中移除時
    會調(diào)用block內(nèi)部的dispose函數(shù)
    dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)

如下圖:

__block的內(nèi)存管理-dispose

解釋:當(dāng)沒有block引用著__block變量,__block變量才會被釋放。

為什么要這么管理呢?
因為__block變量是個對象,block內(nèi)部使用了它,所以需要block來管理它的內(nèi)存。

二. 對象類型的auto變量和__block變量的區(qū)別

(static修飾的變量和全局變量在全局區(qū))

先把下面代碼轉(zhuǎn)成C++代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int no = 20;
        
        __block int age = 10;
        
        NSObject *object = [[NSObject alloc] init];
        __weak NSObject *weakObject = object;
        
        MJBlock block = ^{
            age = 20;
            
            NSLog(@"%d", no);
            NSLog(@"%d", age);
            NSLog(@"%p", weakObject);
        };
        
            block();
    }
    return 0;
}

轉(zhuǎn)成的C++代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no;  
  NSObject *__weak weakObject;  
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}

可以發(fā)現(xiàn),結(jié)構(gòu)體多了三個成員,分別對應(yīng)捕獲的三個auto變量,如下??:

 int no;  
 __Block_byref_age_0 *age; // by ref
 NSObject *__weak weakObject;  

1. 對象類型的auto變量、__block變量

相同點:

  1. 當(dāng)block在棧上時,對它們都不會產(chǎn)生強(qiáng)引用
  2. 當(dāng)block拷貝到堆上時,都會通過copy函數(shù)來處理它們

對象類型的auto變量(假設(shè)變量名叫做p)

_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block變量(假設(shè)變量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 當(dāng)block從堆上移除時,都會通過dispose函數(shù)來釋放它們

對象類型的auto變量(假設(shè)變量名叫做p)

_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block變量(假設(shè)變量名叫做a)

_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

不同點:

  1. 對于對象類型的auto變量,_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用
  2. 對于__block變量:_Block_object_assign函數(shù)只會產(chǎn)生強(qiáng)引用(retain)

2. 普通的的auto變量

例如:int a = 10,就是值引用,然后放到__main_block_impl_0結(jié)構(gòu)體里面。

三. __forwarding指針的作用

我們知道__Block_byref_age_0結(jié)構(gòu)體中的__forwarding指針存的是自己的地址,當(dāng)我們想要訪問age,需要先通過age結(jié)構(gòu)體中的__forwarding獲取自己,然后再獲取age:

(age->__forwarding->age) = 20;

為什么設(shè)計這么奇怪呢?如下圖:

__block的__forwarding指針

解釋:如果棧上的block復(fù)制到堆上了,那么棧上堆上肯定都有一塊內(nèi)存。如果我們想把20賦值到堆上的block,如果不用__forwarding指針,訪問棧上的block,就會把20賦值到棧上的block。如果使用__forwarding指針,不管訪問的block在哪,最后賦值到的一定是堆上的block。

四. __block修飾的對象類型

以前我們是使用__block修飾基本數(shù)據(jù)類型,如果使用__block修飾對象類型會怎么樣呢?

如下代碼,內(nèi)部會發(fā)生什么呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        //不能反過來(如:__weak __block),因為__weak只能修飾OC對象
        __block __weak MJPerson *weakPerson = person;
        
        MJBlock block = ^{
            NSLog(@"%p", weakPerson);
        };
        
        block();
    }
    return 0;
}

轉(zhuǎn)成C++代碼,如下:

struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指針,這里就是弱指針 (以前這里直接是int age;的)
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // 默認(rèn)強(qiáng)指針
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

首先,發(fā)現(xiàn)__main_block_impl_0里面默認(rèn)有一個強(qiáng)指針指向__Block_byref_weakPerson_0結(jié)構(gòu)體。由于外面是用__weak修飾的,所以__Block_byref_weakPerson_0結(jié)構(gòu)體里面有一個弱指針指向person對象,如下圖:

__block修飾對象類型

上面的代碼還可以看出__Block_byref_weakPerson_0結(jié)構(gòu)體里面多了兩個方法copy和dispose方法。(這兩個方法在以前我們使用__block修飾基本數(shù)據(jù)類型的時候是沒有的)

在__Block_byref_weakPerson_0結(jié)構(gòu)體創(chuàng)建里面(就是__block __weak MJPerson *weakPerson = person;的底層實現(xiàn)),傳入了copy和dispose兩個函數(shù)地址,如下:

//copy block內(nèi)存管理相關(guān)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakPerson, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));

       //__block __weak MJPerson *weakPerson = person;的底層實現(xiàn)
       //就是創(chuàng)建__Block_byref_weakPerson_0結(jié)構(gòu)體
__Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0),
            __Block_byref_id_object_copy_131, //傳入copy方法的地址
            __Block_byref_id_object_dispose_131, //傳入dispose方法的地址
            person};

        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指針,這里就是弱指針 (以前這里直接是int age;的)
};

//copy 對象內(nèi)存管理相關(guān)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//dispose 
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

觀察上面的copy,發(fā)現(xiàn)它里面也有調(diào)用_Block_object_assign函數(shù),(char*)dst就是結(jié)構(gòu)體自己的地址值,但是它卻+40 (8+8+4+4+8+8),從上上面我們分析可知:(char*)dst+40就是“MJPerson *__weak weakPerson;”的地址值。然后_Block_object_assign函數(shù)再根據(jù)傳入的對象是強(qiáng)還是弱,再決定retain還是弱引用。所以上圖的第二根指針的強(qiáng)弱,取決于外面修飾對象的的是強(qiáng)指針還是弱指針?

當(dāng)block銷毀的時候,block內(nèi)部會調(diào)用__main_block_dispose_0函數(shù),然后上圖的第一根線就沒了。第一根線就沒了,__Block_byref_weakPerson_0也會銷毀,它也會調(diào)用自己里面的__Block_byref_id_object_dispose_131函數(shù),__Block_byref_id_object_dispose_131函數(shù)再把person引用計數(shù)器減一(或者銷毀)。

總結(jié):

對于block:

  1. 如果block是在棧上,將不會對__Block_byref_weakPerson_0產(chǎn)生強(qiáng)引用
  2. 如果棧上的block被拷貝到堆上
    _Block_object_assign函數(shù)會對__Block_byref_weakPerson_0產(chǎn)生強(qiáng)引用
  3. 如果堆上的block被移除
    _Block_object_dispose函數(shù)會對__Block_byref_weakPerson_0產(chǎn)生弱引用或者移除

對于__block修飾的對象類型:

  1. 當(dāng)__block變量在棧上時,不會對指向的對象產(chǎn)生強(qiáng)引用
  2. 當(dāng)__block變量被copy到堆時(自己拷貝的或者ARC下系統(tǒng)自動拷貝的)
    會調(diào)用__block變量內(nèi)部的copy函數(shù)
    copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain,一直是弱的。這個特例只會在MRC并且是__block修飾對象類型才有
  3. 如果__block變量從堆上移除
    會調(diào)用__block變量內(nèi)部的dispose函數(shù)
    dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放指向的對象(release)

下面驗證上面的“注意”:

將項目切換成MRC環(huán)境,使用__block修飾對象類型,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block MJPerson *person = [[MJPerson alloc] init];
        
        MJBlock block = [^{
            NSLog(@"%p", person);
        } copy];

        [person release];

        block();  //在此處打斷點

        [block release];
    }
    return 0;
}

在上面代碼斷點處,可以發(fā)現(xiàn)打印:“[MJPerson dealloc]”,此時block還沒釋放,但是person卻不在了,驗證了我們上面說的“注意”。根據(jù)這一點,在MRC環(huán)境下,我們可以使用__block來解決循環(huán)引用,這個后面會說到。

解釋如上代碼,當(dāng)block被拷貝到堆上的時候,__block修飾的person也會被拷貝到堆上,這時候會調(diào)用__block修飾的變量內(nèi)部的copy函數(shù),MRC環(huán)境下,copy函數(shù)內(nèi)部只會對person對象產(chǎn)生弱引用。如下圖:

__block修飾的對象.png

如果將上面代碼的__block去掉,那么在斷點處就不會打印:“[MJPerson dealloc]”,之后block釋放之后person才會釋放。這時候就是block直接捕獲person對象了,如下圖:

直接引用.png

五. block循環(huán)引用

1. 循環(huán)引用產(chǎn)生的原因

如下代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

首先“ MJPerson *person = [[MJPerson alloc] init];”執(zhí)行完,會有個person強(qiáng)指針指向MJPerson。MJPerson里面有個_block,當(dāng)執(zhí)行完 person.block = ^{NSLog(@"age is %d", person.age);},MJPerson和block之間的循環(huán)引用就會產(chǎn)生,如下圖:

循環(huán)引用.png

(block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象)

同理,self和block之間的循環(huán)引用如下圖:

block捕獲self

因為self是局部變量,所以block也會捕獲self,也會造成循環(huán)引用。block底層C++代碼如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __strong self; //block內(nèi)部有個self強(qiáng)指針,指向self對象
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2. ARC如何解決循環(huán)引用

① 用__weak解決

首先,我們想一下,如何解決循環(huán)引用,肯定是把其中一根線變成虛線就可以了,但是把哪根線變成虛線呢?我們想了下,person中的block屬性不能變成虛的,因為person要擁有block,那么只能把block里面的person變成虛線了,怎么把block里面的person變成虛線呢?其實就是在上面的代碼,把__strong變成__weak就好了,前面我們已經(jīng)學(xué)過了,在外面用__weak修飾,里面就會變成__weak了,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;

        //__weak MJPerson *weakPerson = person; //這樣寫死了,不推薦
        __weak typeof(person) weakPerson = person;
        person.block = ^{
            NSLog(@"age is %d", weakPerson.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

這時候block底層C++代碼如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __weak weakPerson;  //weakPerson弱指針
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

使用__weak后圖示如下:

__weak.png

解釋:當(dāng){}里面的代碼執(zhí)行完,MJPerson *person局部變量就會消失,person指向MJPerson的線就會消失,這時候沒有實線指向MJPerson,MJPerson就會銷毀,MJPerson銷毀后,MJPerson指向block的線就會消失,這時候沒有實線指向block,block也會銷毀,循環(huán)引用解除。

② 用__unsafe_unretained解決

上面的代碼,把__weak替換成__unsafe_unretained也能解決循環(huán)引用,代碼就省略了。

它們有什么區(qū)別呢?直接看字面意思,__weak:不會產(chǎn)生強(qiáng)引用,__unsafe_unretained:不會產(chǎn)生強(qiáng)引用,不安全。

__weak:不會產(chǎn)生強(qiáng)引用,指向的對象銷毀時,會自動讓指針置為nil
__unsafe_unretained:不會產(chǎn)生強(qiáng)引用,不安全,指向的對象銷毀時,指針存儲的地址值不變,這時候如果再去訪問指針指向的地址就會報野指針錯誤

如下圖,如果使用__weak,當(dāng)weakPerson指向的對象銷毀時,會把weakPerson置為nil,使用__unsafe_unretained就不會。

__weak會把指針置為nil

③ 用__block解決(必須要調(diào)用block)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //使用__block解決循環(huán)引用
        __block MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
            person = nil;
        };
        
        person.block();
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代碼,即可解決循環(huán)引用。

首先我們分析這里的循環(huán)引用是如何產(chǎn)生的。
前面我們說過使用__block修飾對象,block內(nèi)部會捕獲__block變量,__block變量內(nèi)部又有person對象,而且默認(rèn)是強(qiáng)引用,對象又擁有block,所以block、__block變量、person對象之間會有循環(huán)引用,示意圖如下:

循環(huán)引用.png

所以,如果想要解決__block的循環(huán)引用,我們可以調(diào)用block,并且在block里面把person對象置為nil,這樣__block變量就不會持有person對象了,循環(huán)引用被打破,代碼如上,示意圖如下:(以前我們也驗證過,__block變量里的person指針就是我們在外面拿到的person指針)

打破三者循環(huán)引用

總結(jié):不用想也知道ARC環(huán)境下使用__weak解決循環(huán)引用最好。

3. MRC如何解決循環(huán)引用

我們知道MRC不支持使用__weak的,否則報錯“Cannot create __weak reference in file using manual reference counting”
所以,相對于ARC,MRC就只有兩種方式解決循環(huán)引用了。

① 使用__unsafe_unretained解決

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // MRC不支持__weak的否則報錯:??
        // Cannot create __weak reference in file using manual reference counting
        
        __unsafe_unretained MJPerson *person = [[MJPerson alloc] init];

        person.age = 10;
        person.block = [^{
            NSLog(@"age is %d", person.age);
        } copy];
        
        [person release];
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代碼,使用__unsafe_unretained修飾person,當(dāng)block引用person的時候,person的引用計數(shù)器不會增加,還是剛開始創(chuàng)建的時候的1,所以當(dāng)“ [person release];”person就掛了。
如果不使用__unsafe_unretained修飾,person創(chuàng)建的時候引用計數(shù)器為1,因為person默認(rèn)強(qiáng)指針,所以block引用person的時候又加1,所以當(dāng)“ [person release];”person引用計數(shù)器還是1,不會掛。

② 使用__block解決

還記得上面驗證的“注意”嗎?MRC環(huán)境,使用__block修飾對象類型,對象不會被retain,所以也可以解決循環(huán)引用。

代碼省略,可自行將上面的__unsafe_unretained替換成__block進(jìn)行驗證。

補(bǔ)充1:block屬性的建議寫法

MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
  1. 使用copy會將棧上的block拷貝到堆上,如果不使用copy,block就不會拷貝到堆上,因為MRC編譯器不會自動copy,所以只能用copy。
  2. 以前我們說過,ARC環(huán)境,并且把block賦值給強(qiáng)指針,編譯器會自動把block拷貝到堆上,所以ARC使用copy和strong都可以,沒區(qū)別。
  3. 為了統(tǒng)一好記,我們統(tǒng)一對block使用copy。

為什么一定要copy到堆上呢?如果在棧上我們就無法控制block的生命周期,在堆上,什么時候讓它生讓它死都可以。

補(bǔ)充2:__strong typeof(weakSelf) myself = weakSelf;

為什么要這么寫:

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) myself = weakSelf;
//報錯:
//Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
    NSLog(@"age is %d", myself->_age);
};
  1. 原因一,如果在block內(nèi)部通過weakSelf->_age會報錯“弱指針不允許訪問,因為有可能為null,讓你使用強(qiáng)指針”,所以我們就用__strong強(qiáng)指針來訪問self。
  2. 原因二,訪問self的時候我們使用一個臨時的強(qiáng)指針來訪問self,這樣在整個block執(zhí)行期間,可以保證self對象不會被銷毀,同時,block調(diào)用完后,臨時的強(qiáng)指針被銷毀,一切又回歸原來的樣子。這樣既能保證整個block執(zhí)行期間,self對象不會被銷毀,又能保證不會產(chǎn)生循環(huán)引用。

面試題:

  1. block的原理是怎樣的?本質(zhì)是什么?
    block是封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的OC對象,比如函數(shù)的調(diào)用地址、捕獲的變量都封裝到了里面。

  2. __block的作用是什么?有什么使用注意點?
    編譯器會將__block變量包裝成一個對象,就是__Block_byref_person_0這種結(jié)構(gòu)體,可以解決block內(nèi)部無法修改auto變量的問題(自己思考為什么不能修改)。
    使用注意:__block變量內(nèi)部自己也會進(jìn)行內(nèi)存管理,而且MRC環(huán)境下,__block修飾對象,對象不會被retain的。

  3. 為什么使用__block修飾auto變量,在block內(nèi)部就能修改此變量了?
    block內(nèi)部有個指針指向__Block_byref_person_0結(jié)構(gòu)體,通過訪問結(jié)構(gòu)體,再通過結(jié)構(gòu)體訪問變量進(jìn)行修改的。

  4. block的屬性修飾詞為什么是copy?使用block有哪些使用注意?
    block一旦沒有進(jìn)行copy操作,就不會在堆上。使用注意:循環(huán)引用問題。

  5. block在修改NSMutableArray,需不需要添加__block?

NSMutableArray *array = [NSMutableArray array];
person.block = ^{
    [array addObject:@"123"];
};  

不需要。
如上代碼,這個我們以前說過,給array數(shù)組添加成員只是操作array,除非要把a(bǔ)rray指針指向其他地方才要使用__block(比如:array = nil)。
一般如果沒必要不要用__block修飾,因為還要包裝一層。

Demo地址:block循環(huán)引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容