认识OC中的Block

block是什么?
1. block本质是一个OC对象,内部也有isa指针
2. block是封装了函数调用以及函数调用环境的OC对象 (block内部用到block外部对象也会封装到block内部)

a).block本质探究
在main.m中写一个block,并在arm64下编译成c++代码,方便查看实现如下

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int num = 3;
        void (^block2)(int) = ^(int a){
            NSLog(@"用变量保存起来的 block, a = %d, num = %d", a, num);
        };
        block2(222);
    }
    return 0;
}

利用clang编译器把objc代码转成c++代码,终端命令如下:

//在main.n同目录下生成main.cpp的文件
/xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

查看编译后的main,cpp文件,可以看到main函数中block相关代码如下:

 //声明block,删除强制转化代码,即
 //声明
 void (*block2)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
 //调用
 void (*block2)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
 
//以上代码删除强制转换修饰部分,如下
//__main_block_impl_0函数返回的地址即是block,函数的参数分别为__main_block_func_0和__main_block_desc_0_DATA以及num
void (*block2)(int) = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA, num);
//调用block部分即 block2->FuncPtr(block, 222) ,即block2的FuncPtr函数指针调用
((void (*)(__block_impl *, int))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2, 222);
        

其中的block是名称为__main_block_impl_0的结构体,文件搜索可以看出结构体如下

//__block_impl结构体
struct __block_impl {
  void *isa; //isa指针
  int Flags; 
  int Reserved; //保留字段
  void *FuncPtr; //存储的函数指针,指向block内部的实现部分
};

//__main_block_desc_0结构体
static struct __main_block_desc_0 {
  size_t reserved; //保留字段
  size_t Block_size; //block大小
}

//__main_block_impl_0结构体
struct __main_block_impl_0 {
  struct __block_impl impl; //__block_impl结构体(见上面)
  struct __main_block_desc_0* Desc; //__main_block_desc_0结构体指针
  int num; //block对象内部存在外部的变量封装
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
忽略__main_block_impl_0结构体中c++部分构造函数__main_block_impl_0,实际上main()函数中的block结构如下
struct __main_block_impl_0 {
  struct __block_impl impl; //__block_impl结构体(见上面)
  struct __main_block_desc_0* Desc; //__main_block_desc_0结构体指针
  int num; //block对象内部存在外部的变量封装
};

b).测试block如何捕获局部变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num_auto = 10;
        //static 实际传递的是地址,若对象用static修饰,则是**
        static int num_static = 10;
        void (^block)(void) = ^(){
            //num的值捕获进来了
            NSLog(@"block内部打印,num_auto =%d, num_static=%d", num_auto, num_static);
        };
        num_auto = 20;
        num_static = 20;
        block();
    }
    return 0;
}

上述block执行完毕后,输出结果为10, 20。对于上述代码编译成成cpp文件如下
😄
b).测试block如何捕获全局变量

int num1 = 10;
static int num2 = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^(){
            //num的值捕获进来了
            NSLog(@"block内部打印,num1 =%d, num2=%d", num1, num2);
        };
        num1 = 20, num2 = 20;
        block();
    }
    return 0;
}

在这里插入图片描述
c).测试block如何捕获self
相关测试代码如下,

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)test;
@end

@implementation Person
- (void)test{
    void(^block)(void) = ^(){
        NSLog(@"------%@", _name);
    };
    block();
}
@end

编译成cpp代码分析

block内部对于当前成员变量的访问,其实也是对于当前block实例捕获后,再通过当前对象访问对应的成员变量或者属性的。

block捕获变量总结

变量类型是否捕获到block内部访问方式
局部变量auto值传递
static地址传递
全局变量直接访问

d).block类型分析
测试代码如下

void blockSuperclass() {
    void(^block)(void) = ^(){ };
    NSLog(@"%@", [block class]);
    NSLog(@"%@", [[block class] superclass]);
    NSLog(@"%@", [[[block class] superclass] superclass]);
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
};
//通过以上代码分析继承关系,log结果为:__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
 
//block可能类型测试代码,打印输出
void blockType() {
    void(^block1)(void) = ^(){};
    int num = 10;
    void(^block2)(void) = ^(){
        NSLog(@"num=%d", num);
    };
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
        NSLog(@"%d", num);
    } class]);
}
//分析以上代码方法调用后的log输出结果为__NSGlobalBlock__  __NSMallocBlock__  __NSStackBlock__
//如果用clang编译成cpp文件,则对应类型如下 //对象clang中的_NSConcreteGlobalBlock  _NSConcreteMallocBlock  _NSConcreteStackBlock
//故可以看出block共有3中类型存在

//*切换MRC环境*,具体分析代码如下
int num = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int num1 = 10;
        int num2 = 10;
        void (^block1)(void) = ^{
            NSLog(@"block1 没有访问auto变量--------");
        };
        void(^block2)(void) = ^{
            NSLog(@"block3 访问了全局变量-------%d", num);
        };
        void (^block3)(void) = ^{
            NSLog(@"block3 访问了static变量--------%d", num1);
        };
        void(^block4)(void) = ^{
            NSLog(@"block4 访问了auto变量-------%d", num2);
        };
        
        
        NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class], [block3 class], [block4 class]);
        //block没有访问外部变量,一般用的较少
        //MRC下输出结果:__NSGlobalBlock__ __NSGlobalBlock__ __NSGlobalBlock__ __NSStackBlock__
        //ARC下输出结果:__NSGlobalBlock__ __NSGlobalBlock__ __NSGlobalBlock__ __NSMallocBlock__

    }
    return 0;
}
//捕获了auto变量的在栈上内存的__NSStackBlock__,若再次访问捕获的变量,此时变量可能被释放了,所以可能引起异常
//注意static类型的block调用了copy后,结果仍然是global类型

总结如下:

应用程序的内存分配block存储位置示例(从上到下,内存地址递增)存储内容
程序区域.text区程序代码段
数据区域.data区NSGlobalBlock (_NSConcreteGlobalBlock)全局变量存储区域
NSMallocBlock (_NSConcreteMallocBlock)程序员手动开辟与释放
NSStackBlock (_NSConcreteStackBlock)垃圾回收机制自动管理
类型原存储位置新存储位置
NSGlobalBlock_程序的数据区域什么也不做
NSStackBlock_从栈复制到堆
NSMallocBlock_仅仅引用计数增1

关于block进行copy操作的验证代码

typedef void(^Block)(void);

Block getBlock1() {
    //MRC下 返回的block是局部变量,栈上的block再次调用会很危险,此时需要手动进行copy操作
    return ^(){
        NSLog(@"无参数 无返回值的block");
    };
}

Block getBlock2() {
    int num = 2;
    return ^(){
        NSLog(@"无参数 无返回值的block, %d", num);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num = 10;
        
        //ARC下此时拿到的block已经在堆区间了, 此时执行完毕后不用关心释放,系统也会帮你自动释放
        Block block = getBlock1();
        
        //没有引用auto修饰的变量,则默认为__NSGlobalBlock__
        NSLog(@"%@", [block class]);
        
        //有强指针指向,此时block由__NSStackBlock__,变为__NSMallocBlock__
        Block block2 = getBlock2();
        NSLog(@"getBlock2----%@", [block2 class]);
        
        //有访问auto变量,默认为__NSStackBlock__,又没有强指针__strong指向,所以不会copy到堆上,还是__NSStackBlock__
        NSLog(@"%@", [^{
            NSLog(@"----------%d", num);
        } class]);

    }
    return 0;
}

总结就是,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
1.block作为函数返回值时
2.将block赋值给__strong指针时
3.block作为Cocoa APi中方法名含有usingBlock的方法参数时
4.block作为GCD APi的方法参数时

e).对象类型auto变量与block的关系
默认情况下,方法调用完毕后,方法内部的对象释放,测试代码

typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {
            Person *per = [[Person alloc] init];
            per.age = 10;
        }

        Block block;
        {
            Person *per = [[Person alloc] init];
            per.age = 20;
            block = ^{
                NSLog(@"---此时没有立即释放,因为ARC下此时block在堆空间了,block又引用了per.所以per没有释放---%d", per.age);
            };
        }
        NSLog(@"对象类型的auto变量测试 log打印顺序");
    }
}

若对于上述的Person对象per进行弱引用修饰 的话,

__weak Person *weakPerson = per;
            block = ^{
                NSLog(@"---此时没有立即释放,因为ARC下此时block在堆空间了,block又引用了per.所以per没有释放---%d", weakPerson.age);
            };
//此时per对象会立即进行释放

上述Person对象会立即进行释放,执行如下命令,编译成cpp文件查看

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

😂
上述的block内部成员Person对象变成了弱引用,所以Person可以正常释放. 查看编译成的cpp代码
block强弱引用
相关总结如下:
当block内部访问了对象类型的auto变量时:
如果block是在栈上,那么将不会对auto变量产生强引用,
若在堆上,则根据对象的修饰符(__strong、__weak、__unsafe_unretained)判断释放为强引用还是弱引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值