iOS block 中循環引用以及weakSelf和strongSelf的使用

iOS開發中,我們會經常用到block,對於block的使用,想必最需要注意的是循環引用的問題了。當然,你會說,所有的block都用__weak ,這樣就不會有這個問題了。但是,事實並非如此!

假設有個對象person,person有個屬性block.

typedef void (^ClickBlock)(void);


@interface JKPerson : NSObject

@property (nonatomic,copy) ClickBlock block;

@end
#import "JKPerson.h"

@implementation JKPerson

- (void)dealloc
{
    NSLog(@"person - %s",__func__);
}
@end

我們對block 這樣賦值:

  {
      JKPerson *person = [[JKPerson alloc] init];  //引用計數1
      person.block = ^{
          NSLog(@"%@",person);//對外部auto變量自動拷貝 並強對person做一個強引用 
      };
  }

運行代碼:不會打印

對於ARC環境下,在外層 "}" 結束的時候,系統會自動爲我們加上[person release] 操作,但是,此時person有個_block有個對person的強引用,那麼person就不會釋放 , perosn不釋放的情況下,_block也不會釋放,這樣就會造成永遠釋放不了,也就是循環引用。

這裏貼出編譯過後的c++(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc  main.m)-sdk 編譯mac或iphone -arch 編譯cpu  

 {
            JKPerson *person = ((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JKPerson"), sel_registerName("alloc")), sel_registerName("init"));
            //很多強轉,不過可以看到調用objc_msgSend方法給person對象發送setBlock消息
            //__main_block_impl_0 就是block編譯成c++代碼的源碼(大致如此)
            ((void (*)(id, SEL, ClickBlock))(void *)objc_msgSend)((id)person, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)));
 }//

//__main_block_impl_0是一個結構體,裏面存在一個JKPerson *__strong person; 強指針指向、、person(就是因爲arc會自動copy block到堆上)
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  JKPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JKPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在生成的main.cpp文件中,搜索int main ,可以看到上面第一段{ }裏面的代碼 ,通過分析可知,__main_block_impl_0(其實就是block在內存中的狀態,以結構體存在)是一個結構體,裏面存在一個JKPerson *__strong person; 強指針指向person(就是因爲arc會自動copy block到堆上)

struct __main_block_desc_0 此結構體中的copy 和dispose 方法,分別在block拷貝到堆時以及block將要被釋放時調用。通過對源碼的分析,可以看到這兩個方法的實現如此

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*/);}

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

此結構體就是__main_block_impl_0 的一個成員,賦值如下

 __main_block_desc_0 {

    reserved=0

    Block_size=sizeof(struct __main_block_impl_0) //就是block的大小

    void (*copy)=__main_block_copy_0

    void (*dispose)=__main_block_dispose_0

 }

那麼我們知道,block會對person對象有一個強引用存在,而person對象又會對_block有一個強引用(此現象就會產生循環引用,兩者內存永遠得不到釋放)

那麼爲了解決這種問題的發生,我們會使用到__weak關鍵字

__unsafe_unretained (也可以不強引用,但是當引用計數爲0的,修飾的對象不會置爲nil,會造成野指針)

修改代碼如下,使用__weak修飾person

{
            
     JKPerson *person = [[JKPerson alloc] init];
     __weak typeof(person) weakPerson = person;
     person.block = ^{
          NSLog(@"%@",weakPerson);
     };
 }

運行代碼:打印 person - -[JKPerson dealloc]

當我們用__weak 修飾person對象時,再次編譯c++代碼(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m)-sdk 編譯mac或iphone -arch 編譯cpu  -fobjc-arc -fobjc-runtime=ios-8.0.0用來解決有__weak修飾變量的編譯  

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

很明顯可以看到,和上次編譯的c++,不同點是JKPerson *__weak weakPerson; 爲弱引用(這裏根據開發經驗就可以知道__strong __weak的作用)

通過打印信息,和對c++代碼的簡單分析,知道__weak的作用,可以使block對對象的引用爲弱引用 就不會造成循環引用了

__weak修飾的對象在block內不會強引用,那麼就有可能造成,外部提前釋放了preson對象,當block代碼執行時,person對象就已經被釋放了,person就爲nil. 這也是爲什麼外部用__weak修飾時,block裏面想通過__weakSelf->直接訪問成員變量時編譯器是會報錯的!

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章