iOS原理篇(四) block原理

Block:能够捕获它所在函数内部的变量的匿名函数

一、block的本质

block本质上也是一个OC对象,它内部也有一个isa指针
将block内部代码会放到_block_func_0函数中,函数地址保存在FuncPtr中
执行block内部代码时是通过FuncPtr找到函数地址进行调用

block的底层结构如下:

int main(int argc, char * argv[]) {
    void (^test)() = ^(){
    };
    test();
}
//__block_imp:  这个是编译器给我们生成的结构体,每一个block都会用到这个结构体
struct __block_impl {
  void *isa;         //对于本文可以忽略
  int Flags;          //对于本文可以忽略
  int Reserved;       //对于本文可以忽略       
  void *FuncPtr;       //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0
};
/*__main_block_impl_0: 
是编译器给我们在main函数中定义的block
void (^test)() = ^(){
};
生成的对应的结构体
*/
struct __main_block_impl_0 {
struct __block_impl impl;  //__block_impl 变量impl
 //__main_block_desc_0 指针,指向编译器给我们生成的结构体变量__main_block_desc_0_DATA 
struct __main_block_desc_0* Desc;   
 //结构体的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 
    impl.isa = &_NSConcreteStackBlock;  //说明block是栈blockimpl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;}
};
//__main_block_func_0: 编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    }
//__main_block_desc_0: 编译器根据block代码生成的block描述,
//主要是记录下__main_block_impl_0结构体大小
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 
 //这里就生成了__main_block_desc_0的变量__main_block_desc_0_DATA
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//这里就是main函数了
int main(int argc, char * argv[]) {
//__main_block_impl_0()创建了一个__main_block_impl_0结构体的一个实例
//&取地址
//((void (*)())转成函数
//那么这整句就是说定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。注意创建这个实例时构选函数传的两个参数,
//正是编译器帮我们生成的静态函数__main_block_func_0及__main_block_desc_0的变量__main_block_desc_0_DATA
    void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 
    ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);                          //下面单独讲
}

当我们声明一个block变量a并为它赋值时,其实就是创建一个函数指针FuncPtr,再根据block a赋值的代码生成一个静态函数,而指针FuncPtr就指向这个静态函数。block a调用时就是使用函数指FuncPtr调用生成的静态函数。

二、block的变量捕获机制

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

基本变量类型

iOS原理篇(四) block原理_第1张图片
1、block内部访问auto变量时会将auto变量捕获到block内部,block外部修改auto变量的值并不会影响block内部,所以是值捕获 如下 age变量

2、block访问static变量时会将static变量捕获到block内部,block外部修改static变量的值会影响block内部,所以是指针捕获 如下 height变量

    /*auto:自动变量,离开作用域就会销毁。
    它是默认的关键字,所以通常情况下是省略的。*/
    auto int age = 10;
    static int height = 10;
    void (^block)(void) = ^{
        NSLog(@"age is %d, height is %d", age, height);
    };
    age = 20;
    height = 20;
    block();

 iOS原理篇(四) block原理_第2张图片

3、block访问全局变量时不会将全局变量捕获到block内部,而是直接访问。 

总结:局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获

注意:对象的局部auto变量捕获是指针捕获不是值捕获,所以捕获的内容会受外部变量的影响

三、block的类型

Block有三种类型:

  • 全局Block:__NSGlobalBlock,
  • 堆区Block:__NSMallocBlock,
  • 栈区Block:__NSStackBlock

都继承于NSBlock

iOS原理篇(四) block原理_第3张图片

1、NSGlobalBlock:存放在数据段

没有访问auto类型变量,但是可以访问静态变量或者全局变量。它存储在数据区。
NSGlobalBlock调用copy还是NSGlobalBlock

//MRC下 没有访问auto变量
void (^block)(void) = ^ {
    NSLog(@"Hello world!");
};
NSLog(@"%@", [block class]);  // __NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]);  // NSBlock
NSLog(@"%@", [[[block class] superclass] superclass]);  // NSObject

2、NSStackBlock:存放在栈区

访问auto类型变量的block类型。它存储在栈区。
PS:栈区的数据在调用之后会自动销毁。
NSStackBlock调用copy,会从栈复制到堆。ARC下NSStackBlock当返回值

//MRC下
// 访问了auto变量
int num = 10;
void (^block)(void) = ^ {
    NSLog(@"num = %d", num);
};
NSLog(@"%@", [block class]);  // __NSStackBlock__
//ARC下
- (void)viewDidLoad {
    [super viewDidLoad];
    [self test:^{
       NSLog(@"%@",self.object);
    }];
}
- (void)test:(void(^)(void))block {
    NSLog(@"%@",block); //log: <__NSStackBlock__: 0x7fff5ef49b88>
}

3、NSMallocBlock:存放在堆区
NSMallocBlock调用copy,引用计数增加。对stack类型的block进行copy操作,得出的堆类型的block变量。堆数据需要手动管理内存,需要手动释放。

// NSStackBlock调用了copy
int num = 10;
void (^block)(void) = [^ {
    NSLog(@"num = %d", num);
} copy];
NSLog(@"%@", [block class]);  // __NSMallocBlock__

4、block的自动copy 

在ARC环境下,编译器会根据情况自动将__NSStackBlock转成__NSMallocBlock,比如以下情况:
1、__NSStackBlock作为函数的返回值

typedef void (^TestBlock)(void);
TestBlock globalBlock(){
    return ^{
        NSLog(@"Hello, globalBlock!");
    };
}
TestBlock mallocBlock(){
    int value = 100;
    return ^{
        NSLog(@"Hello, mallocBlock!%d",value);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TestBlock block1 =globalBlock();
        TestBlock block2 =mallocBlock();
        NSLog(@"%@",block1);//日志:<__NSGlobalBlock__: 0x100004038>
        NSLog(@"%@",block2);//日志:<__NSMallocBlock__: 0x100650910>
        block1();//日志:Hello, globalBlock!
        block2();//日志:Hello, mallocBlock!100
    }
    return 0;
}


2、将block赋值给__strong指针时

__weak:弱引用变量修饰词,引用计数不会+1,本身可以避免循环引用的问题,但是其会导致外部对象释放了之后,Block内部也访问不到这个对象的问题,我们可以通过在Block内部申明一个__strong的变量来指向weakObj,使外部对象既能在Block内部保持住,又能避免循环引用的问题。

3、block作为Cocoa API中方法名含有usingBlock的方法参数时,例如

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, 
NSUInteger idx, BOOL * _Nonnull stop) {     
}];

4、block作为GCD API的方法参数时,例如:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 
(int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{      
});

四、对象类型的auto变量

ARC环境下:personaotu变量,传入的block的变量同样为person,即block有一个强引用引用person,所以block销毁后,peroson才会销毁。

//ARC环境
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"------block内部%d",person.age);//对象变量捕获是指针捕获
            };//栈区自动copy到堆区,强引用
        } // 执行完毕,person没有被释放
        NSLog(@"--------");
    } // person 释放
    return 0; 
}
//MRC环境下代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"------block内部%d",person.age);
            };//栈区
            [person release];
        } // person被释放
        NSLog(@"--------");
    }
    return 0;
}

五、__weak,__strong,__block和循环引用

1、__block可以用于解决block内部无法修改auto变量值的问题

编译器会将__block修饰的基本变量类型,包装成一个对象。

block对基本变量类型 捕获就变成了对象变量的捕获,就是指针捕获。之后block内就能修改auto变量值了。

2、block中的循环引用

//self强引用block
self.block = ^{
        NSLog(@"%ld",self.age);
};//栈区自动copy堆区,blcok强引用self对象。

打破任何一方的强引用,就能解决循环引用

 __weak typeof(self) weakSelf = self;
    self.quoteBlock = ^{
        NSLog(@"%ld",weakSelf.age);
    };

在self对象前加了个__weak修饰,block内部就不会对self产生强引用,当block被释放时__weak会自动为nil,不需要block再去释放self了。

1、self释放时通知quoteBlock释放
2、quoteBlock因为没有强引用self,所以无需通知self并等待self先释放,quoteBlock接收到释放通知后就立马释放
3、quoteBlock释放后,self也跟着释放,所以就解决了循环引用的问题

你可能感兴趣的:(iOS开发原理篇,网络)