美文网首页
(一)OC语法05(Block)

(一)OC语法05(Block)

作者: cdd48b9d36e0 | 来源:发表于2018-09-05 14:19 被阅读8次
1、Block的本质

block本质上也是一个OC对象,它内部也有个isa指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^ {
            NSLog(@"Hello, World!");
        };
        
        block();
    }
    return 0;
}

把上面这段oc代码用clang(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m)编译为c++代码为:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

//这个结构体就是block的本质
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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 封装了block执行逻辑的函数,也就是执行代码块的函数,参数就是block结构体本身
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_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-> impl.FuncPtr(block)是因为struct __block_impl impl是结构体__main_block_impl_0的第一个元素,所以他们具有相同的地址
        block->FuncPtr(block);
    }
    return 0;
}
2、Block的变量捕获(capture)

局部变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // auto:自动变量,离开作用域就销毁,默认的就是这个,通常省略
        auto int age = 10;
        static int height = 10;

        void (^block)(void) = ^{
            // age的值捕获进来(capture)
            // 打印结果为age is 10, height is 20
            NSLog(@"age is %d, height is %d", age, height);
        };

        age = 20;
        height = 20;

        block();

 
    }
    return 0;
}

block会捕获局部变量,原因很简单,可以把捕获当做保存,否则当外面的中括号执行完来执行block时,局部变量已经释放,如果当初不进行捕获也可理解为保存操作,那么当block里面要用的时候值从哪里来呢
对上面这段代码,age是值传递,捕获的是10这个值,因为其内存在执行到block时已经销毁,所以后面再改变为20时已经无济于事;height是指针传递,这是static关键字起了作用,height的内存会一直存在不被销魂,所以这里捕获的是height的地址
同样的,用clang可以清晰的看到cpp源代码对以上论证的佐证

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  int *height;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    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 *height = __cself->height; // bound by copy


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
    }

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 height = 10;

    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));

    age = 20;
    height = 20;
}

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

全局变量

int age = 10;
static int height = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         
        void (^block)(int, int) = ^(int a, int b){
            //打印结果:Hello, World! - 20 20
            NSLog(@"Hello, World! - %d %d", a, b);
        };
        
        age = 20;
        height = 20;
        
        block(age, height);
        
    }
    return 0;
}

因为是全局变量,所以不需要捕获
实际应用
自定义一个Person类
Person.h

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

- (void)test;

- (instancetype)initWithName:(NSString *)name;
@end

Person.m

@implementation MJPerson

int age_ = 10;

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%d", [self name]);
    };
    block();
}

- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

@end

这里的self及其Person的成员变量name都是局部变量,所以都会被捕获。在IOS中,每个方法的底层实现都自带两个隐式参数,第一个就是调用者自己,第二个参数是方法名称,在这里就是(MJPerson * self, SEL _cmd),而所有的方法参数都是局部变量所以这里会被捕获
clang编译的cpp底层代码如下:

int age_ = 10;


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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_MJPerson_1027e6_mi_0, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
    }
static void __MJPerson__test_block_copy_0(struct __MJPerson__test_block_impl_0*dst, struct __MJPerson__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __MJPerson__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MJPerson__test_block_impl_0*, struct __MJPerson__test_block_impl_0*);
  void (*dispose)(struct __MJPerson__test_block_impl_0*);
} __MJPerson__test_block_desc_0_DATA = { 0, sizeof(struct __MJPerson__test_block_impl_0), __MJPerson__test_block_copy_0, __MJPerson__test_block_dispose_0};

static void _I_MJPerson_test(MJPerson * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__MJPerson__test_block_impl_0((void *)__MJPerson__test_block_func_0, &__MJPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}


static instancetype _I_MJPerson_initWithName_(MJPerson * self, SEL _cmd, NSString *name) {
    if (self = ((MJPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJPerson"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
}


static NSString * _I_MJPerson_name(MJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MJPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_MJPerson_setName_(MJPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MJPerson, _name), (id)name, 0, 1); }

小结
只要是局部变量,就要被捕获

3、Block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )
  • NSStackBlock ( _NSConcreteStackBlock )
  • NSMallocBlock ( _NSConcreteMallocBlock )


    每一种类型的block调用copy后的结果如下所示
4、Block的copy
5、对象类型的auto变量
5.1、当block内部访问(等同于使用,比如数组的add操作,但要区别赋值,也就是=号)了对象类型的auto变量时:

🌰小栗子
看看下面两段代码的输出
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MJPerson *p = [[MJPerson alloc] init];
    
    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", p);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", weakP);
        });
    });
    
    NSLog(@"touchesBegan:withEvent:");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MJPerson *p = [[MJPerson alloc] init];
    
    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", weakP);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", p);
        });
    });
    
    NSLog(@"touchesBegan:withEvent:");
}

简而言之,这种时候只需重点关注强引用

5.2、当block内部修改(也就是=号,赋值)了对象类型的auto变量时:

我们都知道办法是在前面添加__block(另外两种办法,一是声明为全局变量,一是在前面添加static,但是这两种都会改变该变量的存储位置让其一直存在内存中从而造成浪费)
底层实现原理:【编译器会将__block变量包装成一个对象】

typedef void (^MJBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        __block int age = 10;
        __block NSObject *obj = [[NSObject alloc]init];
        
        MJBlock block = ^{
            obj = nil;
            age = 20;
        };
        block();
//        NSLog(@"%p", &age);
    }
    return 0;
}

用clang编译后

typedef void (*MJBlock)(void);
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_1 *obj; // by ref
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_1 *_obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj->__forwarding), 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_obj_1 *obj = __cself->obj; // bound by ref
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            (obj->__forwarding->obj) = __null;
            (age->__forwarding->age) = 20;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

小细节:打开5.2第一段代码里的注释,打印出来的地址不是__main_block_impl_0的里__Block_byref_age_0 *age,而是__Block_byref_age_0的int age

5.3、关于访问和修改
#import "MJPerson.h"
typedef void (^MJBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    

        MJPerson *person = [[MJPerson alloc]init];
        person.age = 10;
        
        MJPerson *person2 = [[MJPerson alloc]init];
        person.age = 30;
        
        NSMutableArray *mArr = [[NSMutableArray alloc]init];
        
        MJBlock block = ^{
            
            person.age = 20;
            
            [mArr addObject:@"123"];
            
//            person = person2;
//            mArr = nil;
        };
        
    }
    return 0;
}

像上面这样是不需要加__block的,除非打开注释才需要,这是因为注释中的=直接修改了外部指针指向的地址的值,而person.age = 20[mArr addObject:@"123"]这两句其实只是使用了外部指针指向的地址,修改了这个地址后面的某个地址的值,并没有直接修改该地址的值

6、Block的内存管理

block内存管理其实就是指对它捕获的变量的内存管理

  • 当block在栈上时,并不会对__block变量产生强引用
  • 当block被copy到堆时
    1. 会调用block内部的copy函数
    2. copy函数内部会调用_Block_object_assign函数
    3. _Block_object_assign函数会对__block变量形成强引用(retain)
  • 当block从堆中移除时
    1. 会调用block内部的dispose函数
    2. dispose函数内部会调用_Block_object_dispose函数
    3. _Block_object_dispose函数会自动释放引用的__block变量(release)
关于forwarding指针

为什么复制到堆后栈上的forwarding指针会指向堆上的block变量结构体了?这是苹果的内部机制不用管,这样做的好处是不管当你访问的是栈上还是堆上的block变量结构体,只要通过forwarding指针来访问,都会指向堆上的这块内存

__block修饰的对象类型

我们知道,只要捕获的是被__block修饰的变量,则cpp底层代码内部都会生成该变量的结构体,block结构体内部是保存的该结构体指针,Desc里面会有一个copy和dispose函数负责内存管理(详情见上)
此外,如果捕获的__block变量是对象类型,则cpp底层代码生成__block变量变量的结构体里也会生成copy和dispose函数

  • 当__block变量在栈上时,不会对指向的对象产生强引用

  • 当__block变量被copy到堆时

    1. 会调用__block变量内部的copy函数
    2. copy函数内部会调用_Block_object_assign函数
    3. _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain
  • 如果__block变量从堆中移除

    1. 会调用__block变量内部的dispose函数
    2. dispose函数内部会调用_Block_object_dispose函数
    3. _Block_object_dispose函数会自动释放指向的对象(release)
被捕获的变量加与不加__block时的图示
加了__block
去掉__block
7、Block的循环引用
ARC

三种方法,__weak释放后会置为nil,是最安全和最常用的


MRC

这里用__block的原理和ARC不一样,是利用了第6要点中的斜体字部分

相关文章

  • (一)OC语法05(Block)

    1、Block的本质 block本质上也是一个OC对象,它内部也有个isa指针 把上面这段oc代码用clang(x...

  • OC语法 Block

    问题: Block的原理、本质是什么? Block的分类? Block的捕获机制? Blcok内部的内存管理? _...

  • 关于Block块的所有所有

    关于Block: 在我们使用OC进行iOS开发和Mac OS开发中,Block语法是我们最常见的语法之一,而且苹果...

  • Block 总结

    Block 是OC的一种语法,其用法如下: //使用block需要注意的问题://1.声明block类型的属性时,...

  • OC-Block语法

    block 语法 block语法 -> 块语法标准C里面没有Block, C语言的后期扩展版本, 加入了匿名函数;...

  • iOS block简单用法

    block 是iOS4.0之后出现的技术,block变量可以看成oc的对象,但block的语法和技术是更底层c的知...

  • Objective-c中block的声明和使用

    在oc中使用block时很普遍的,但是在使用时总会遇到会遇到各种报错的情况,现记录一下block语法。 block...

  • iOS

    OC语法 1. Block是如何实现的?Block对应的数据结构是什么样子的?__block的作用是什么?它对应的...

  • 总纲

    一句代码创建常用UI控件类与对象OC中面向对象的编程思想OC基础语法复习OC中的协议OC中的block学习导航栏按...

  • 谈谈Block(一)

    苹果在Mac OS X10.6 和iOS 4之后引入block语法,之后就大幅改变了OC 的编程方式。Block...

网友评论

      本文标题:(一)OC语法05(Block)

      本文链接:https://www.haomeiwen.com/subject/ctaowftx.html