iOS底層原理 block本質 --(5)

上篇Category和關聯對象講解了+loadinitialize區別和練習,沒看過的朋友能夠去溫習一下,本章講解block的用法和底層數據結構,以及使用過程當中須要注意的點。ios

block本質

前幾篇文章講過了,class是對象,元類也是對象,本質是結構體,那麼block是否也是如此呢?block具備這幾個特色:c++

  • block本質上也是一個OC對象,它內部也有isa指針
  • block是封裝了函數調用以及函數調用環境的oc對象

先簡單來看一下block編譯以後的樣子git

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^(void){
            NSLog(@"hello word");
        };
        block();

    }
    return 0;
}
複製代碼

命令行執行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mm -o main.cpp,來到main.cpp內部,已經去除多餘的轉化函數,剩餘骨架,能夠看得更清晰。程序員

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    //構造函數 相似OC init函數
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;//block類型
    impl.Flags = flags;
    impl.FuncPtr = fp;// 執行函數的地址
    Desc = desc;//desc 存儲 __main_block_desc_0(0,sizeof(__main_block_impl_0))的值
  }
};
    //block 內部代碼封裝成函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b7cca8_mii_0);
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
//定義block
        void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
        //執行block
        block->FuncPtr(block);
    }
    return 0;
}
複製代碼

最終block轉化成__main_block_impl_0結構體,賦值給變量block,傳入參數是__main_block_func_0__main_block_desc_0_DATA來執行__main_block_impl_0的構造函數,__main_block_desc_0_DATA函數賦值給__main_block_impl_0->FuncPtr,執行函數是block->FuncPtr(block),刪除冗餘代碼以前是((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);,那麼爲何block能夠直接強制轉化成__block_impl呢?由於__main_block_impl_0結構體的第一行變量是__block_impl,至關於__main_block_impl_0的內存地址和__block_impl的內存地址同樣,強制轉化也不會有問題。github

變量捕獲

變量捕獲分爲3種:chrome

變量類型 是否會捕獲到block內部 訪問方式 內部變量假定是a
局部變量 auto 值傳遞 a
局部變量 static 指針傳遞 *a
全局變量 不會 直接訪問

auto變量捕獲

auto 變量,通常auto是省略不寫的,訪問方式是值傳遞,關於值傳遞不懂的話能夠看這篇博客, 看下這個例子express

int age = 10;
void (^block)(void) = ^(void){
    NSLog(@"age is %d",age);
};
age = 20;
block();
//實際輸出是 age is 10
複製代碼

有沒有疑問呢?在block執行以前age =20,爲何輸出是10呢? 將這段代碼轉化成c/c++,以下所示:bash

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;//多了一個變量age,存儲值是10
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_baf352_mii_0,age);
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}
複製代碼

結構體__main_block_impl_0多了一個變量age,在block轉化成c函數的時候__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)直接將age的值存儲在__main_block_impl_0.age中,此時__main_block_impl_0.age是存儲在堆上的,以前的age是存儲在數據段的,執行block訪問的變量是堆上的``__main_block_impl_0.age,因此最終輸出來age is 10數據結構

static變量捕獲

咱們經過一個例子來說解static和auto區別:app

void(^block)(void);
void test(){
    int age = 10;
    static int level = 12;
    block = ^(void){
        NSLog(@"age is %d,level is %d",age,level);
    };
    age = 20;
    level = 13;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}

//輸出:age is 10,level is 13
複製代碼

轉化成源碼:

void(*block)(void);

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  int *level;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_level, int flags=0) : age(_age), level(_level) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *level = __cself->level; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b26797_mii_0,age,(*level));
    }

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
void test(){
    int age = 10;
    static int level = 12;
    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &level));
    age = 20;
    level = 13;
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        test();
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
複製代碼

當執行完test()函數,age變量已經被收回,可是age的值存儲在block結構體中,level的地址存儲在__test_block_impl_0.level,能夠看到level類型是指針類型,讀取值的時候也是*level,則無論什麼時間改動level的值,讀level的值都是最新的,由於它是從地址直接讀的。因此結果是age is 10,level is 13

全局變量

全局不用捕獲的,訪問的時候直接訪問。咱們來測試下

int age = 10;
static int level = 12;
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void(^block)(void) = ^(void){
            NSLog(@"age is %d,level is %d",age,level);
        };
        age = 20;
        level = 13;
        block();
    }
    return 0;
}
複製代碼

轉化成c/c++

int age = 10;
static int level = 12;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_45cab9_mii_0,age,level);
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        level = 13;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
複製代碼

能夠看出來編譯以後僅僅是多了兩行int age = 10; static int level = 12;,結構體__main_block_impl_0內部和構造函數並無專門來存儲值或者指針,緣由是當執行__main_block_func_0,能夠直接訪問變量agelevel,由於全局變量有效區域是全局,不會出了main函數就消失。 基本歸納來說就是超出執行區域與可能消失的會捕獲,必定不會消失的不會捕獲。

咱們再看下更復雜的狀況,對象類型的引用是如何處理的?

@interface FYPerson : NSObject
@property (nonatomic,copy) NSString * name;
@end

@implementation FYPerson
- (void)test{
    void (^block)(void) = ^{
        NSLog(@"person is %@",self);
    };
    
    void (^block2)(void) = ^{
        NSLog(@"name is %@",_name);
    };
}
@end




struct __FYPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __FYPerson__test_block_desc_0* Desc;
  FYPerson *self;
  __FYPerson__test_block_impl_0(void *fp, struct __FYPerson__test_block_desc_0 *desc, FYPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __FYPerson__test_block_func_0(struct __FYPerson__test_block_impl_0 *__cself) {
  FYPerson *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_0,self);
    }
static void __FYPerson__test_block_copy_0(struct __FYPerson__test_block_impl_0*dst, struct __FYPerson__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __FYPerson__test_block_dispose_0(struct __FYPerson__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __FYPerson__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __FYPerson__test_block_impl_0*, struct __FYPerson__test_block_impl_0*);
  void (*dispose)(struct __FYPerson__test_block_impl_0*);
} __FYPerson__test_block_desc_0_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_0), __FYPerson__test_block_copy_0, __FYPerson__test_block_dispose_0};

struct __FYPerson__test_block_impl_1 {
  struct __block_impl impl;
  struct __FYPerson__test_block_desc_1* Desc;
  FYPerson *self;
  __FYPerson__test_block_impl_1(void *fp, struct __FYPerson__test_block_desc_1 *desc, FYPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __FYPerson__test_block_func_1(struct __FYPerson__test_block_impl_1 *__cself) {
  FYPerson *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_FYPerson$_name)));
    }
static void __FYPerson__test_block_copy_1(struct __FYPerson__test_block_impl_1*dst, struct __FYPerson__test_block_impl_1*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __FYPerson__test_block_dispose_1(struct __FYPerson__test_block_impl_1*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __FYPerson__test_block_desc_1 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __FYPerson__test_block_impl_1*, struct __FYPerson__test_block_impl_1*);
  void (*dispose)(struct __FYPerson__test_block_impl_1*);
} __FYPerson__test_block_desc_1_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_1), __FYPerson__test_block_copy_1, __FYPerson__test_block_dispose_1};

static void _I_FYPerson_test(FYPerson * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__FYPerson__test_block_impl_0((void *)__FYPerson__test_block_func_0, &__FYPerson__test_block_desc_0_DATA, self, 570425344));

    void (*block2)(void) = ((void (*)())&__FYPerson__test_block_impl_1((void *)__FYPerson__test_block_func_1, &__FYPerson__test_block_desc_1_DATA, self, 570425344));
}
複製代碼

blockblock2都是結構體__FYPerson__test_block_impl_1內部引用了一個FYPerson對象指針,FYPerson對象屬於局部變量,須要捕獲。第2個block訪問_name捕捉的也是FYPerson對象,訪問_name,須要先訪問FYPerson對象,而後再訪問_name,本質上是訪問person.name,因此捕捉的是FYPerson對象。

驗證block是對象類型:

//ARC環境下
void(^block)(void)=^{
			NSLog(@"Hello, World!");
		};
		NSLog(@"本身class:%@ 它爹class:%@ 它爺爺class:%@ 它老爺爺的tclass:%@",[block class],[[block class] superclass],[[[block class] superclass]superclass],[[[[block class] superclass]superclass] superclass]);
		//輸出是:本身class:__NSGlobalBlock__ 它爹class:__NSGlobalBlock  它爺爺class:NSBlock 它老爺爺的tclass:NSObject
複製代碼

能夠了解到block是繼承與基類的,因此block也是OC對象。

block的分類

block有3種類型,以下所示,能夠經過調用class方法或者isa指針查看具體類型,最終都是繼承來自NSBlock類型。

  • NSGlobalBLock(_NSConcreteGLobalBlock)
  • NSStackBlock(_NSConcreteStackBlock)
  • NSMallocBLock(_NSConcreteMallocBlock)

在應用程序中內存分配是這樣子的:

---------------
程序區域 .text區
---------------
數據區域 .data區     <--------- _NSConcreteGlobalBlock(存儲全局變量)
---------------
堆                  <--------- _NSConcreteMallocBlock(動態申請釋放內存區域)
---------------
棧                  <--------- _NSConcreteStackBlock(存儲存局部變量)
---------------

複製代碼
block類型 環境
NSGlobalBLock 沒有訪問auto變量
NSStackBlock 訪問auto變量
NSMallocBLock NSStackBlock 調用copy

驗證須要設置成MRC,找到工程文件,設置project->Object-C Automatic Reference Counting=NO

int age = 10;

void(^block1)(void)=^{
	NSLog(@"block1");
};
void(^block2)(void)=^{
	NSLog(@"block2 %d",age);
};
void(^block3)(void)=[block2 copy];
NSLog(@"block1:%@ block2:%@ block3:%@ ",[block1 class],[block2 class],[block3 class]);

//輸出
block1:__NSGlobalBlock__   
block2:__NSStackBlock__ 
block3:__NSMallocBlock__
複製代碼

沒有訪問auto變量的block屬於__NSGlobalBlock__,訪問了auto變量的是__NSStackBlock__,手動調用了copyblock屬於__NSMallocBlock____NSMallocBlock__是在堆上,須要程序員手動釋放[block3 release];,不釋放會形成內存泄露。

每一種類型的block調用copy後的結果以下

block類型 副本源的配置存儲域 複製效果
NSGlobalBLock 從棧複製到堆
NSStackBlock 程序的數據區域 什麼也不作
NSMallocBLock 引用計數+1

在ARC環境下,編譯器會根據自身狀況自動將棧上的block複製到堆上,好比下列狀況

  • block做爲函數返回值時
  • 將block賦值給__strong指針時
  • block做爲Cocoa API中方法名含有usingBlock的方法參數時
  • block做爲GCD API的方法參數時

在ARC環境下測試:

typedef void (^FYBlock)(void);
typedef void (^FYBlockInt)(int);
FYBlock myBlock(){
	return ^{
		NSLog(@"哈哈");
	};
};
FYBlock myBlock2(){
	int age = 10;
	return ^{
		NSLog(@"哈哈 %d",age);
	};
};
int main(int argc, const char * argv[]) {
	@autoreleasepool {
		FYBlock block = myBlock();
		FYBlock block2 = myBlock2();
		int age = 10;
		FYBlock block3= ^{
			NSLog(@"強指針block %d",age);
		};
		NSLog(@"沒訪問變量:%@ 訪問佈局變量:%@ 強指針:%@",[block class],[block2 class],[block3 class]);
	}
	return 0;
}
//輸出
沒訪問變量:__NSGlobalBlock__ 
訪問局部變量:__NSMallocBlock__ 
強指針:__NSMallocBlock__
複製代碼

arc環境下,沒訪問變量的block__NSGlobalBlock__,訪問了局部變量是__NSMallocBlock__,有強指針引用的是__NSMallocBlock__,強指針系統自動執行了copy操做,由棧區複製到堆區,由系統管理改成開發者手動管理。

因此有如下建議:

MRC下block屬性的建議寫法

  • @property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法

  • @property (strong, nonatomic) void (^block)(void);
  • @property (copy, nonatomic) void (^block)(void);

對象類型數據和block交互

平時咱們使用block,對象類型來傳遞數據的比較多,對象類型讀取到block中用__block修飾符,會把對象地址直接讀取到block結構體內,__weak修飾的對象是弱引用,默認是強引用,咱們看下這段代碼

//FYPerson.h
@interface FYPerson : NSObject
@property (nonatomic,assign) int age;
@end

//FYPerson.m
@implementation FYPerson
@end

//main.m
typedef void (^FYBlock)(void);

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		FYBlock block ;
			FYPerson *person = [[FYPerson alloc]init];
			person.age = 10;
		__weak typeof(person) __weakPerson = person;
			block = ^{
				NSLog(@" %d",__weakPerson.age);
			};
		
		block();
	}
	return 0;
}
複製代碼

使用下面該命令轉化成cpp

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
複製代碼

摘取關鍵結構體代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  FYPerson *__weak __weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, FYPerson *__weak ___weakPerson, int flags=0) : __weakPerson(___weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  FYPerson *__weak __weakPerson = __cself->__weakPerson; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_7f0272_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)__weakPerson, sel_registerName("age")));
   }
複製代碼

FYPerson *__weak __weakPerson__weak修飾的對象 當block內部換成block = ^{ NSLog(@" %d",person.age); };,轉換源碼以後是

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  FYPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, FYPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

person默認是使用__storng來修飾的,arc中,block引用外界變量,系統執行了copy操做,將block copy到堆上,由開發者本身管理,轉c/c++中結構體描述爲

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)

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

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


複製代碼

有對象的使用,則有內存管理,既然是arc,則是系統幫開發者管理內存,函數void (*copy)void (*dispose)就是對block的引用計數的+1-1

若是block被拷貝到堆上

  • 會調用block內部的copy函數
  • copy函數內部會調用_Block_object_assign函數
  • _Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)作出相應的操做,造成強引用(retain)或者弱引用

若是block從堆上移除

  • 會調用block內部的dispose函數
  • dispose函數內部會調用_Block_object_dispose函數
  • _Block_object_dispose函數會自動釋放引用的auto變量(release,引用計數-1,若爲0,則銷燬)
函數 調用時機
copy函數 棧上的Block複製到堆時
dispose函數 堆上的Block被廢棄時

題目

person什麼時間釋放?

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",person.age);
});
}
複製代碼

3s後釋放,dispatchblock強引用,block強引用person,在block釋放的時候,person沒其餘的引用,就釋放掉了。

變換1:person什麼時間釋放

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak FYPerson *__weakPerosn = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",__weakPerosn.age);
});
複製代碼

__weak沒有對perosn進行強引用,咋執行完dispatch_block則立馬釋放,答案是當即釋放。 變換2:person什麼時間釋放

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",__weakPerson.age);
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		NSLog(@"---%d",person.age);
	});
});
複製代碼

person被內部block強引用,則block銷燬以前person不會釋放,__weakPerson執行完person不會銷燬,NSLog(@"---%d",person.age)執行完畢以後,person銷燬。答案是4秒以後NSLog(@"---%d",person.age)執行完畢以後,person銷燬。

變換3:person什麼時間釋放

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",person.age);
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		NSLog(@"---%d",__weakPerson.age);
	});
});
複製代碼

person被強引用於第一層block,第二層弱引用person,僅僅當第一層block執行完畢的時候,person釋放。

修改block外部變量

想要修改變量,首先要變量的有效區域,或者block持有變量的地址。 例子1:

int age = 10;
FYBlock block = ^{
    age = 20;//會報錯
};
複製代碼

報錯的緣由是age是值傳遞,想要不報錯只須要將int age = 10改爲static int age = 10,就由值傳遞變成地址傳遞,有了age的地址,在block的內部就能夠更改age的值了。或者將int age = 10改爲全局變量,全局變量在block中不用捕獲,block本質會編譯成c函數,c函數訪問全局變量在任意地方均可以直接訪問。

__block本質

__block本質上是修飾的對象或基本類型,編譯以後會生成一個結構體__Block_byref_age_0,結構體中*__forwarding指向結構體本身,經過 (age->__forwarding->age) = 20來修改變量的值。

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;//10
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_043d00_mi_0,(age->__forwarding->age));
        }
複製代碼

ageblock外部有一個,在block內部有一個,他們是同一個嗎?咱們來探究一下:

typedef   void (^FYBlock)(void);
struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;//10
};
struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age; // by ref
};

int main(int argc, const char * argv[]) {
	@autoreleasepool {
	    // insert code here...
	__block	int age = 10;
        NSLog(@" age1:%p",&age);
        FYBlock block = ^{
            age = 20;
            NSLog(@"age is %d",age);
        };
        struct __main_block_impl_0 *main= (__bridge struct __main_block_impl_0 *)block;
        NSLog(@" age1:%p age2:%p",&age,&(main->age->__forwarding->age));
	}
	return 0;
}
輸出:
age1:0x7ffeefbff548
age1:0x100605358 age2:0x100605358
複製代碼

通過__block修飾以後,以後訪問的age和結構體__Block_byref_age_0中的age地址是同樣的,能夠斷定age被系統copy了一份。

例子:

__block	int age = 10;
        NSLog(@" age1:%p",&age);
        NSObject *obj=[[NSObject alloc]init];
        FYBlock block = ^{
            
            NSLog(@"age is %d,obj is %p",age,&obj);
        };
複製代碼

使用命令編譯

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製代碼

摘錄主要函數:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *__strong obj;
  __Block_byref_age_0 *age; // by ref
};
複製代碼

__main_block_impl_0結構體對age進行了一個強引用並持有該結構體的地址,將age複製到了堆上,age轉化成__Block_byref_age_0對象,__main_block_impl_0能夠對__Block_byref_age_0->__forwarding->age進行賦值。__Block_byref_age_0既然是對象,就須要內存管理,__main_block_copy_0出現了_Block_object_assign_Block_object_dispose__Block_byref_age_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*/);
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 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->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
複製代碼

ageobj是一個對象結構體,obj只是一個強引用而沒有地址變換緣由是obj自己就在堆上,block也在堆上,故無需複製出新的obj來進行管理。

看一下循環引用是反面教材

typedef   void (^FYBlock)(void);
@interface FYPerson : NSObject

@property (nonatomic,copy) FYBlock blcok;
@end

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


int main(int argc, const char * argv[]) {
	@autoreleasepool {
        NSLog(@" age1:%p",&age);
        FYPerson *obj=[[FYPerson alloc]init];
		[obj setBlcok:^{
			NSLog(@"%p",&obj);
		}];
		NSLog(@"--------------");
	}
	return 0;
}

複製代碼

輸出是:

age1:0x7ffeefbff4e8
block 執行完畢--------------
複製代碼

obj經過copy操做強引用block,block經過默認__strong強制引用obj,這就是A<---->B,相互引用致使執行結束應該釋放的時候沒法釋放。 將main改爲

FYPerson *obj=[[FYPerson alloc]init];
		__weak typeof(obj) weakObj = obj;
		[obj setBlcok:^{
			NSLog(@"%p",&weakObj);
		}];
複製代碼

結果是

age1:0x7ffeefbff4e8
block 執行完畢--------------
-[FYPerson dealloc]
複製代碼

使用__weak__unsafe__unretain弱引用obj,在block執行完畢的時候,obj釋放,block釋放,無相互強引用,正常釋放。

__weak__unsafe__unretain

__weak__unsafe__unretain都是弱引用obj,都是不影響obj正常釋放,區別是__weak在釋放以後會將值爲nil,__unsafe__unretain不對該內存處理。 下面咱們來具體驗證一下該結論:

typedef   void (^FYBlock)(void);
@interface FYPerson : NSObject
@property (nonatomic,assign) int age ;
@end
@implementation FYPerson
-(void)dealloc{
	NSLog(@"%s",__func__);
}
@end
struct __Block_byref_age_0 {
	void *__isa;
	struct __Block_byref_age_0 *__forwarding;
	int __flags;
	int __size;
	int age;
};
struct __block_impl {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
};
struct __main_block_desc_0 {
	size_t reserved;
	size_t Block_size;
	void (*copy)(void);
	void (*dispose)(void);
};
struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0* Desc;
	FYPerson *__unsafe_unretained __unsafe_obj;
};

int main(int argc, const char * argv[]) {
	@autoreleasepool {
	    // insert code here...
		FYBlock block;
		{
			FYPerson *obj=[[FYPerson alloc]init];
			obj.age = 5;
			__weak typeof(obj) __unsafe_obj = obj;
			block = ^{
				
				NSLog(@"obj->age is %d obj:%p",__unsafe_obj.age,&__unsafe_obj);
			};
			struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block;
			NSLog(@"inside struct->obj:%p",suct->__unsafe_obj);//斷點1
		}
		struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block;
		NSLog(@"outside struct->obj:%p",suct->__unsafe_obj);//斷點2
		block();
		NSLog(@"----end------");
	}
	return 0;
}
複製代碼

根據文中提示斷點1處使用lldb打印obj命令

(lldb) p suct->__unsafe_obj->_age
(int) $0 = 5 //年齡5仍是存儲在這裏的
inside struct->obj:0x102929d80

複製代碼

在斷點2處再次查看obj的值,報錯不可讀取該內存

-[FYPerson dealloc]
outside struct->obj:0x0
p suct->__unsafe_obj->_age
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory 複製代碼

已經超出了obj的有效範圍,obj已經重置爲nil,也就是0x0000000000000000。 上文代碼__weak改成__unsafe_unretained再次在obj斷點1查看地址:

(lldb) p suct->__unsafe_obj->_age
(int) $0 = 5
inside struct->obj:0x10078c0c0

複製代碼

在斷點2出再次查看地址並查看age的值

-[FYPerson dealloc]
outside struct->obj:0x10078c0c0
(lldb) p suct->__unsafe_obj->_age
(int) $1 = 5
複製代碼

__unsafe_unretainedobj銷燬以後內存並無及時重置爲空。

當咱們離開某個頁面須要再執行的操做,那麼咱們改怎麼辦? 實際應用A:

-(void)test{
	__weak typeof(self) __weakself = self;
	[self setBlcok:^{
		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
			NSLog(@"perosn :%p",__weakself);
		});
	}];
	self.blcok();
}

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		{
        	FYPerson *obj=[[FYPerson alloc]init];
			[obj test];
			NSLog(@"block 執行完畢--------------");
		}
		NSLog(@"person 死了");
	}
	return 0;
}
輸出:
block 執行完畢--------------
-[FYPerson dealloc]
person 死了
複製代碼

猛的一看,哪裏都對!使用__weakself進行弱引用,不會致使死循環,在self死的時候,block也會死,就會致使一個問題,selfblock共存亡,可是這個須要3秒後再執行,3秒後,self已經死了,block也死了,顯然不符合咱們的業務需求。 那麼咱們剝離blockself的關係,讓block強引用self,self不持有block就能知足業務了。以下所示:

__block typeof(self) __weakSelf = self;//__block或者沒有修飾符
    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(2);
        NSLog(@"obj:%@",__weakSelf->_obj);
    });
//perosn :0x0
複製代碼

self不持用block的時候,block能夠強引用self,block執行完畢本身釋放,也會釋放self,當self持有blockblock必須弱引用self,則釋放self,block也會釋放,不然會循環引用。

總結

  • block本質是一個封裝了函數調用以及調用環境的結構體對象
  • __block修飾的變量會被封裝成結構體對象,以前在數據段的會被複制到堆上,以前在堆上的則不受影響,解決auto對象在block內部沒法修改的問題,在MRC環境下,__block不會對變量產生強引用.
  • block不使用copy則不會從全局或者棧區域移動到堆上,使用copy以後有由發者管理
  • 使用block要注意不能產生循環引用,引用不能變成一個環,主動使其中一個引用成弱引用,則不會產生循環引用。
  • __weak修飾的對象,block不會對對象強引用,在執行block的時候有可能會值已經被系統置爲nil,__unsafe_unretained修飾的銷燬以後內存不會及時重置爲空。

咱們看的cpp是編譯以後的代碼,runtime是否和咱們看到的一致呢?請聽下回分解。

資料下載

本文章之因此圖片比較少,我以爲仍是跟着代碼敲一遍,印象比較深入。


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索