iOS-Block的詳解

工具命令轉(zhuǎn)化C++
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

一. iOS代碼塊Block

1.1 概述

代碼塊Block是蘋果在iOS4開始引入的對C語言的擴(kuò)展,用來實現(xiàn)匿名函數(shù)的特性,Block是一種特殊的數(shù)據(jù)類型,其可以正常定義變量、作為參數(shù)、作為返回值,特殊地,Block還可以保存一段代碼,在需要的時候調(diào)用,目前Block已經(jīng)廣泛應(yīng)用于iOS開發(fā)中,常用于GCD、動畫、排序及各類回調(diào)

注: Block的聲明與賦值只是保存了一段代碼段,必須調(diào)用才能執(zhí)行內(nèi)部代碼

1.2 Block變量的聲明、賦值與調(diào)用
1.2.1 Block變量的聲明
Block變量的聲明格式為: 返回值類型(^Block名字)(參數(shù)列表);

// 聲明一個無返回值,參數(shù)為兩個字符串對象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形參變量名稱可以省略,只留有變量類型即可
void(^aBlock)(NSString *, NSString *);

1.2.2 Block變量的賦值
Block變量的賦值格式為: Block變量 = ^(參數(shù)列表){函數(shù)體};

aBlock = ^(NSString *x, NSString *y){
    NSLog(@"%@ love %@", x, y);
};
注: Block變量的賦值格式可以是: Block變量 = ^返回值類型(參數(shù)列表){函數(shù)體};,不過通常情況下都將返回值類型省略,
因為編譯器可以從存儲代碼塊的變量中確定返回值的類型
1.2.3 聲明Block變量的同時進(jìn)行賦值
int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果沒有參數(shù)列表,在賦值時參數(shù)列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};
1.2.4 Block變量的調(diào)用
// 調(diào)用后控制臺輸出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 調(diào)用后控制臺輸出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 調(diào)用后控制臺輸出"I am a aVoidBlock"
aVoidBlock();

二. 使用typedef定義Block類型

在實際使用Block的過程中,我們可能需要重復(fù)地聲明多個相同返回值相同參數(shù)列表的Block變量,如果總是重復(fù)地編寫一長串代碼來聲明變量會非常繁瑣,所以我們可以使用typedef來定義Block類型

// 定義一種無返回值無參數(shù)列表的Block類型
typedef void(^SayHello)();

// 我們可以像OC中聲明變量一樣使用Block類型SayHello來聲明變量
SayHello hello = ^(){
    NSLog(@"hello");
};

// 調(diào)用后控制臺輸出"hello"
hello();

三. Block作為函數(shù)參數(shù)

3.1 Block作為C函數(shù)參數(shù)
// 1.定義一個形參為Block的C函數(shù)
void useBlockForC(int(^aBlock)(int, int))
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 2.聲明并賦值定義一個Block變量
int(^addBlock)(int, int) = ^(int x, int y){
    return x+y;
};

// 3.以Block作為函數(shù)參數(shù),把Block像對象一樣傳遞
useBlockForC(addBlock);

// 將第2點和第3點合并一起,以內(nèi)聯(lián)定義的Block作為函數(shù)參數(shù)
useBlockForC(^(int x, int y) {
    return x+y;
});
3.2 Block作為OC函數(shù)參數(shù)
// 1.定義一個形參為Block的OC函數(shù)
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 2.聲明并賦值定義一個Block變量
int(^addBlock)(int, int) = ^(int x, int y){
    return x+y;
};

// 3.以Block作為函數(shù)參數(shù),把Block像對象一樣傳遞
[self useBlockForOC:addBlock];

// 將第2點和第3點合并一起,以內(nèi)聯(lián)定義的Block作為函數(shù)參數(shù)
[self useBlockForOC:^(int x, int y){
    return x+y;
}];
3.3 使用typedef簡化Block
// 1.使用typedef定義Block類型
typedef int(^MyBlock)(int, int);

// 2.定義一個形參為Block的OC函數(shù)
- (void)useBlockForOC:(MyBlock)aBlock
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 3.聲明并賦值定義一個Block變量
MyBlock addBlock = ^(int x, int y){
    return x+y;
};

// 4.以Block作為函數(shù)參數(shù),把Block像對象一樣傳遞
[self useBlockForOC:addBlock];

// 將第3點和第4點合并一起,以內(nèi)聯(lián)定義的Block作為函數(shù)參數(shù)
[self useBlockForOC:^(int x, int y){
    return x+y;
}];

四 Block內(nèi)訪問局部變量

4.1 在Block中可以訪問局部變量
// 聲明局部變量global
int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 100"
myBlock();
4.2 在聲明Block之后、調(diào)用Block之前對局部變量進(jìn)行修改,在調(diào)用Block時局部變量值是修改之前的舊值
// 聲明局部變量global
int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 100"
myBlock();
4.3 在Block中不可以直接修改局部變量
// 聲明局部變量global
int global = 100;

void(^myBlock)() = ^{
    global ++; // 這句報錯
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 100"
myBlock();

五. Block內(nèi)訪問__block修飾的局部變量

5.1 在局部變量前使用下劃線下劃線block修飾,在聲明Block之后、調(diào)用Block之前對局部變量進(jìn)行修改,在調(diào)用Block時局部變量值是修改之后的新值
// 聲明局部變量global
__block int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 101"
myBlock();
5.2 在局部變量前使用下劃線下劃線block修飾,在Block中可以直接修改局部變量
// 聲明局部變量global
__block int global = 100;

void(^myBlock)() = ^{
    global ++; // 這句正確
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 101"
myBlock();

六. Block內(nèi)訪問全局變量

6.1 在Block中可以訪問全局變量
// 聲明全局變量global
int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 100"
myBlock();
6.2 在聲明Block之后、調(diào)用Block之前對全局變量進(jìn)行修改,在調(diào)用Block時全局變量值是修改之后的新值
// 聲明全局變量global
int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 101"
myBlock();
6.3 在Block中可以直接修改全局變量
// 聲明全局變量global
int global = 100;

void(^myBlock)() = ^{
    global ++;
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 101"
myBlock();

七. Block內(nèi)訪問靜態(tài)變量

7.1 在Block中可以訪問靜態(tài)變量
// 聲明靜態(tài)變量global
static int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 100"
myBlock();
7.2 在聲明Block之后、調(diào)用Block之前對靜態(tài)變量進(jìn)行修改,在調(diào)用Block時靜態(tài)變量值是修改之后的新值
// 聲明靜態(tài)變量global
static int global = 100;

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 101"
myBlock();
7.3 在Block中可以直接修改靜態(tài)變量
// 聲明靜態(tài)變量global
static int global = 100;

void(^myBlock)() = ^{
    global ++;
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 101"
myBlock();

8. Block在MRC及ARC下的內(nèi)存管理

8.1 Block在MRC下的內(nèi)存管理
默認(rèn)情況下,Block的內(nèi)存存儲在棧中,不需要開發(fā)人員對其進(jìn)行內(nèi)存管理
// 放Block變量出了作用域,Block的內(nèi)存會被自動釋放
void(^myBlock)() = ^{
    NSLog(@"------");
};
myBlock();

在Block的內(nèi)存存儲在棧中時,如果在Block中引用了外面的對象,不會對所引用的對象進(jìn)行任何操作

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();

[p release]; // Person對象在這里可以正常被釋放

如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,這時需要開發(fā)人員對其進(jìn)行release操作來管理內(nèi)存

void(^myBlock)() = ^{
    NSLog(@"------");
};
myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行一次retain操作,即使在Block自身調(diào)用了release操作之后,Block也不會對所引用的對象進(jìn)行一次release操作,這時會造成內(nèi)存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

[p release]; // Person對象在這里無法正常被釋放,因為其在Block中被進(jìn)行了一次retain操作

如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行一次retain操作,為了不對所引用的對象進(jìn)行一次retain操作,可以在對象的前面使用下劃線下劃線block來修飾

__block Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

[p release]; // Person對象在這里可以正常被釋放

如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用

  • 情況一
@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end


@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");

    Block_release(_myBlock);
    [super dealloc];
}

@end


Person *p = [[Person alloc] init];

p.myBlock = ^{
    NSLog(@"------%@", p);
};
p.myBlock();

[p release]; // 因為myBlock作為Person的屬性,采用copy修飾符修飾(這樣才能保證Block在堆里面,以免Block在棧中被系統(tǒng)釋放),所以Block會對Person對象進(jìn)行一次retain操作,導(dǎo)致循環(huán)引用無法釋放
  • 情況二
@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end


@implementation Person

- (void)resetBlock
{
    self.myBlock = ^{
        NSLog(@"------%@", self);
    };
}

- (void)dealloc
{
    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];
}

@end


Person *p = [[Person alloc] init];
[p resetBlock];
[p release]; // Person對象在這里無法正常釋放,雖然表面看起來一個alloc對應(yīng)一個release符合內(nèi)存管理規(guī)則,但是實際在resetBlock方法實現(xiàn)中,Block內(nèi)部對self進(jìn)行了一次retain操作,導(dǎo)致循環(huán)引用無法釋放

如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用,解決循環(huán)引用的辦法是在對象的前面使用下劃線下劃線block來修飾,以避免Block對對象進(jìn)行retain操作

情況一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end


@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");

    Block_release(_myBlock);
    [super dealloc];
}

@end


__block Person *p = [[Person alloc] init];

p.myBlock = ^{
    NSLog(@"------%@", p);
};
p.myBlock();

[p release]; // Person對象在這里可以正常被釋放

情況二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end


@implementation Person

- (void)resetBlock
{
    // 這里為了通用一點,可以使用__block typeof(self) p = self;
    __block Person *p = self;
    self.myBlock = ^{
        NSLog(@"------%@", p);
    };
}

- (void)dealloc
{
    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];
}

@end


Person *p = [[Person alloc] init];
[p resetBlock];
[p release]; // Person對象在這里可以正常被釋放

八: Block的實質(zhì)

  • 1: block本質(zhì)上也是一個OC對象,它內(nèi)部也有個isa指針
  • 2: block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象
  • 3: block是封裝函數(shù)及其上下文的OC對象
8.1 Block最簡單情形的分析
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void(^mainBlock)(void) = ^{
            NSLog(@"mainBlock");
        };
        mainBlock();
    }
    return 0;
}

將這塊代碼轉(zhuǎn)為C++代碼 使用一下命令

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

我們可以找到
1: mainBlock將會轉(zhuǎn)化為如下結(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;
  }
};

**2. mainBlock包含兩個結(jié)構(gòu)體元素__block_impl__main_block_desc_0

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_desc_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)};

3. mainBlock結(jié)構(gòu)體的構(gòu)造方法會對兩個子結(jié)構(gòu)體進(jìn)行賦值

  __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;
  }

4. 在main.m的執(zhí)行過程中是

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*mainBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)mainBlock)->FuncPtr)((__block_impl *)mainBlock);
    }
    return 0;
}

轉(zhuǎn)化關(guān)系比較復(fù)雜 我們進(jìn)行簡化一下

   void(*mainBlock)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
        
        
        (mainBlock->FuncPtr)(mainBlock);
8.2 帶參數(shù)Block的分析
#import <Foundation/Foundation.h>

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

將這塊代碼轉(zhuǎn)為C++代碼

1: 查看當(dāng)前的block結(jié)構(gòu)體結(jié)構(gòu)和不帶參數(shù)的一樣

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;
  }
};

**2. __main_block_impl_0包含兩個結(jié)構(gòu)體元素__block_impl__main_block_desc_0

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_desc_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)};

3: block函數(shù)發(fā)生了變化 增加了兩個參數(shù)

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_z9c5vw_x6h16tszlbnmscg_80000gn_T_main_6550f6_mi_0, a, b);
        }

4: main方法的具體體現(xiàn)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*mainBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *, int, int))((__block_impl *)mainBlock)->FuncPtr)((__block_impl *)mainBlock, 10, 20);
    }
    return 0;
}

簡化后
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*mainBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

(mainBlock->FuncPtr)(mainBlock, 10, 20);
    }
    return 0;
}
8.3 引用Block外部的局部變量的分析
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^mainBlock)(void) = ^{
            NSLog(@"main--Block---%d", age);
        };
        mainBlock();
    }
    return 0;
}

將這塊代碼轉(zhuǎn)為C++代碼

1: 查看當(dāng)前的block結(jié)構(gòu)體結(jié)構(gòu)和以上兩種情況都不一樣

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __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;
  }
};

: age(_age) C++語法在構(gòu)造函數(shù)中會講_age賦值給結(jié)構(gòu)體元素中的age

**2. __main_block_impl_0包含三個結(jié)構(gòu)體元素__block_impl__main_block_desc_0age

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_desc_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)};

**3: block函數(shù)發(fā)生了變化 在函數(shù)中取出結(jié)構(gòu)體的數(shù)據(jù) 進(jìn)行初始化一個變量 **

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

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

4: main方法的具體體現(xiàn)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*mainBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *, int, int))((__block_impl *)mainBlock)->FuncPtr)((__block_impl *)mainBlock, 10, 20);
    }
    return 0;
}

簡化后
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*mainBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

(mainBlock->FuncPtr)(mainBlock, 10, 20);
    }
    return 0;
}

結(jié)論: 我們在初始化結(jié)構(gòu)體的時候 會講外部的age傳入結(jié)構(gòu)體的構(gòu)造函數(shù),結(jié)構(gòu)體有對應(yīng)的age作為對應(yīng), 會講外界的10賦值給結(jié)構(gòu)體的age, 當(dāng)我們執(zhí)行block的時候 取出結(jié)構(gòu)體的age數(shù)據(jù)10進(jìn)行操作

8.4 引用Block外部的static局部變量的分析
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        static int num = 20;
        void (^mainBlock)(void) = ^{
            NSLog(@"main--Block---%d--%d", age, num);
        };
        mainBlock();
    }
    return 0;
}

將這塊代碼轉(zhuǎn)為C++代碼

1: 查看當(dāng)前的block結(jié)構(gòu)體結(jié)構(gòu)和以上兩種情況都不一樣

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

: age(_age) C++語法在構(gòu)造函數(shù)中會講_age賦值給結(jié)構(gòu)體元素中的age, 此時會講外部的num的地址賦值給結(jié)構(gòu)體元素 int *num

**2. __main_block_impl_0包含三個結(jié)構(gòu)體元素__block_impl__main_block_desc_0、agenum

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_desc_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)};

**3: block函數(shù)發(fā)生了變化 在函數(shù)中取出結(jié)構(gòu)體的數(shù)據(jù) 進(jìn)行初始化兩個變量 **

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *num = __cself->num; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_z9c5vw_x6h16tszlbnmscg_80000gn_T_main_e699a8_mi_0, age, (*num));
        }

4: main方法的具體體現(xiàn)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        static int num = 20;
        void (*mainBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &num));
        ((void (*)(__block_impl *))((__block_impl *)mainBlock)->FuncPtr)((__block_impl *)mainBlock);
    }
    return 0;
}

簡化后
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        static int num = 20;
        void (*mainBlock)(void) =&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age, &num));
(mainBlock->FuncPtr)(mainBlock);
    }
    return 0;
}

結(jié)論: 我們在初始化結(jié)構(gòu)體的時候 會講外部num地址傳入結(jié)構(gòu)體的構(gòu)造函數(shù),結(jié)構(gòu)體有對應(yīng)的指針 int *num作為對應(yīng), 會講外界的數(shù)據(jù)賦值給結(jié)構(gòu)體的數(shù)據(jù), 當(dāng)我們執(zhí)行block的時候 取出結(jié)構(gòu)體的int * num通過尋址處理數(shù)據(jù)

8.5 引用Block外部的全局變量的分析

int age = 10;
static int num = 20;

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void (^mainBlock)(void) = ^{
            NSLog(@"main--Block---%d--%d", age, num);
        };
        age = 20;
        num = 40;
        mainBlock();
    }
    return 0;
}

將這塊代碼轉(zhuǎn)為C++代碼

1: 查看當(dāng)前的block結(jié)構(gòu)體結(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;
  }
};

**2. __main_block_impl_0包含三個結(jié)構(gòu)體元素__block_impl__main_block_desc_0

// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 結(jié)構(gòu)體 __main_block_desc_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)};

**3: block函數(shù)發(fā)生了變化 在函數(shù)中取出結(jié)構(gòu)體的數(shù)據(jù) 進(jìn)行初始化兩個變量 **

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_z9c5vw_x6h16tszlbnmscg_80000gn_T_main_bceaeb_mi_0, age, num);
        }

4: main方法的具體體現(xiàn)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*mainBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        num = 40;
        ((void (*)(__block_impl *))((__block_impl *)mainBlock)->FuncPtr)((__block_impl *)mainBlock);
    }
    return 0;
}

簡化后
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*mainBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        num = 40;
(mainBlock->FuncPtr)(mainBlock);
    }
    return 0;
}

結(jié)論: 因為全局變量一直存在 不會捕獲到block中 直接使用調(diào)用就可以了

全局變量不捕獲到block, 局部變量捕獲到block
作用域的問題

九. Block在ARC下的內(nèi)存管理

9.1 在ARC默認(rèn)情況下,Block的內(nèi)存存儲在堆中,ARC會自動進(jìn)行內(nèi)存管理,程序員只需要避免循環(huán)引用即可
// 放Block變量出了作用域,Block的內(nèi)存會被自動釋放
void(^myBlock)() = ^{
    NSLog(@"------");
};
myBlock();

在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行強(qiáng)引用,但是在Block被釋放時會自動去掉對該對象的強(qiáng)引用,所以不會造成內(nèi)存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();

// Person對象在這里可以正常被釋放
9.2
如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用

情況一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end


@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end


Person *p = [[Person alloc] init];

p.myBlock = ^{
    NSLog(@"------%@", p);
};
p.myBlock();

// 因為myBlock作為Person的屬性,采用copy修飾符修飾(這樣才能保證Block在堆里面,以免Block在棧中被系統(tǒng)釋放),所以Block會對Person對象進(jìn)行一次強(qiáng)引用,導(dǎo)致循環(huán)引用無法釋放

情況二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end


@implementation Person

- (void)resetBlock
{
    self.myBlock = ^{
        NSLog(@"------%@", self);
    };
}

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end


Person *p = [[Person alloc] init];
[p resetBlock];

// Person對象在這里無法正常釋放,在resetBlock方法實現(xiàn)中,Block內(nèi)部對self進(jìn)行了一次強(qiáng)引用,導(dǎo)致循環(huán)引用

如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用,解決循環(huán)引用的辦法是使用一個弱引用的指針指向該對象,然后在Block內(nèi)部使用該弱引用指針來進(jìn)行操作,這樣避免了Block對對象進(jìn)行強(qiáng)引用

情況一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end


@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end


Person *p = [[Person alloc] init];
__weak typeof(p) weakP = p;

p.myBlock = ^{
    NSLog(@"------%@", weakP);
};
p.myBlock();

// Person對象在這里可以正常被釋放

情況二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end


@implementation Person

- (void)resetBlock
{
    // 這里為了通用一點,可以使用__weak typeof(self) weakP = self;
    __weak Person *weakP = self;
    self.myBlock = ^{
        NSLog(@"------%@", weakP);
    };
}

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end


Person *p = [[Person alloc] init];
[p resetBlock];

// Person對象在這里可以正常被釋放

十. Block在ARC下的內(nèi)存管理的官方案例

在MRC中,我們從當(dāng)前控制器采用模態(tài)視圖方式present進(jìn)入MyViewController控制器,在Block中會對myViewController進(jìn)行一次retain操作,造成循環(huán)引用

MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];

在MRC中解決循環(huán)引用的辦法即在變量前使用下劃線下劃線block修飾,禁止Block對所引用的對象進(jìn)行retain操作

__block MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];

但是上述方法在ARC下行不通,因為下劃線下劃線block在ARC中并不能禁止Block對所引用的對象進(jìn)行強(qiáng)引用,解決辦法可以是在Block中將myController置空(為了可以修改myController,還是需要使用下劃線下劃線block對變量進(jìn)行修飾)

__block MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;
};
[self presentViewController:myController animated:YES completion:^{}];

上述方法確實可以解決循環(huán)引用,但是在ARC中還有更優(yōu)雅的解決辦法,新創(chuàng)建一個弱指針來指向該對象,并將該弱指針放在Block中使用,這樣Block便不會造成循環(huán)引用

MyViewController *myController = [[MyViewController alloc] init];
// ...
__weak MyViewController *weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{}];

雖然解決了循環(huán)引用,但是也容易涉及到另一個問題,因為Block是通過弱引用指向了myController對象,那么有可能在調(diào)用Block之前myController對象便已經(jīng)被釋放了,所以我們需要在Block內(nèi)部再定義一個強(qiáng)指針來指向myController對象

MyViewController *myController = [[MyViewController alloc] init];
// ...
__weak MyViewController *weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController)
    {
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
    }
    else
    {
        // Probably nothing...
    }
};
[self presentViewController:myController animated:YES completion:^{}];

這里需要補(bǔ)充一下,在Block內(nèi)部定義的變量,會在作用域結(jié)束時自動釋放,Block對其并沒有強(qiáng)引用關(guān)系,且在ARC中只需要避免循環(huán)引用即可,如果只是Block單方面地對外部變量進(jìn)行強(qiáng)引用,并不會造成內(nèi)存泄漏

** 注: 關(guān)于下劃線下劃線block關(guān)鍵字在MRC和ARC下的不同 **

__block在MRC下有兩個作用
1. 允許在Block中訪問和修改局部變量 
2. 禁止Block對所引用的對象進(jìn)行隱式retain操作

__block在ARC下只有一個作用
1. 允許在Block中訪問和修改局部變量
最后編輯于
?著作權(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)容

  • iOS代碼塊Block 概述 代碼塊Block是蘋果在iOS4開始引入的對C語言的擴(kuò)展,用來實現(xiàn)匿名函數(shù)的特性,B...
    smile刺客閱讀 2,466評論 2 26
  • Block使用場景,可以在兩個界面的傳值,也可以對代碼封裝作為參數(shù)的傳遞等。用過GCD就知道Block的精妙之處。...
    Coder_JMicheal閱讀 814評論 2 1
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 10,085評論 10 69
  • 一、Block的簡單介紹 Block 就是匿名函數(shù) 它是封裝了一個代碼塊,這個代碼塊在什么時候都可以執(zhí)行; 使用B...
    tigger丨閱讀 817評論 2 3
  • 一: iOS Block的基本概念 1.1 概述 代碼塊Block是蘋果在iOS4開始引入的對C語言的擴(kuò)展,用來實...
    iYeso閱讀 301評論 0 0

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