【iOS】Block底层(二)

348 阅读3分钟

Block的类型

  • block有3种类型:

    • NSGlobalBlock (NSConcreteGlobalBlock)
    • NSStackBlock (NSConcreateStackBlock)
    • NSMallocBlock (NSConcreateMallocBlock)
  • 可以通过class或者isa指针查看具体类型,最终都是继承自NSBlock类型

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

2020-06-03 21:42:23.579757+0800 Interview01-Block的本质[56222:9306988] __NSGlobalBlock__
2020-06-03 21:42:23.581321+0800 Interview01-Block的本质[56222:9306988] __NSGlobalBlock
2020-06-03 21:42:23.581831+0800 Interview01-Block的本质[56222:9306988] NSBlock
2020-06-03 21:42:23.585482+0800 Interview01-Block的本质[56222:9306988] NSObject

因为其继承自NSObject对象,所以block都isa指针,所以也可以把block看成一个对象

打印并且编译一下三种block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        
        int age = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d", age);
        };
        
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
            NSLog(@"%d", age);
        } class]);
        return 0;
    }
}

打印结果

2020-06-03 21:48:28.625843+0800 Interview01-Block的本质[56274:9310988] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

编译结果($ xcrun -sdk iphoneos clang --arch arm64 rewrite-objc main.m)

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

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

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

发现这里都是_NSConcreteStackBlock。和运行时不一样,但是一切以运行时的结果为主,因为运行时可能进行了处理,而且编译完的代码未必是真正的oc代码


Block的内存分配

应用程序的内存地址分类是从低地址向高地址的(栈区最高)

  • .text段: 代码
  • .data段:全局变量
  • 堆:动态分配内存,程序员申请内存,管理内存
  • 栈:局部变量,离开大括号就自动销毁
int age_ = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        NSLog(@"数据段:age_ %p", &age_);
        NSLog(@"栈:a %p", &a);
        NSLog(@"堆:obj %p", [[NSObject alloc] init]);
        NSLog(@"数据段:class %p", [RLPerson class]);
            }
    return 0;
}

打印结果:

2020-06-29 14:09:54.578086+0800 TestBlock[3428:10823423] 数据段:age_ 0x1000011d0
2020-06-29 14:09:54.578513+0800 TestBlock[3428:10823423] 栈:a 0x7ffeefbff51c
2020-06-29 14:09:54.578605+0800 TestBlock[3428:10823423] 堆:obj 0x100502f80
2020-06-29 14:09:54.578705+0800 TestBlock[3428:10823423] 数据段:class 0x1000011a8

可以知道RLPerson地址和age_地址相似,所以是类是存存放在数据段


MRC下的Block

首先关闭ARC: Target -> Build Setting -> Objctive-C Automatic Reference Counting 设置为NO

先观察访问是否auto变量的block类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Global:没有访问auto变量
        void (^block1)(void) = ^{
            NSLog(@"block1---------");
        };
        
        // Stack:访问了auto变量
        int age = 10;
        void (^block2)(void) = ^{
            NSLog(@"block2---------%d", age);
        };
        
        NSLog(@"%@-%@", [block1 class], [block2 class]);
    }
    return 0;
}

打印结果:

2020-06-03 22:03:52.335076+0800 Interview01-Block的本质[56499:9322480] __NSGlobalBlock__-__NSStackBlock__

可以发现访问了auto变量的为__NSStackBlock__类型 __NSStackBlock__的存在

void (^block)(void);
void test2()
{
   // NSStackBlock
   int age = 10;
   block = ^{
       NSLog(@"block---------%d", age);
   };
}

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

打印结果:

2020-06-03 22:08:45.533995+0800 Interview01-Block的本质[56553:9325730] block----------272632888

发现age的值不对。这里是因为栈上的block在执行完test2()后,这里栈上的内存就可能是垃圾数据,导致打印的值有问题

如何处理这种情况呢?就是将block变成_NSMallocBlock,这里只要将__NSStackBlock__copy之后就会变成_NSMallocBlock_了。就可以在堆中控制block的生命周期。

void (^block)(void);
void test2()
{
    // NSStackBlock
    int age = 10;
    block = [^{
        NSLog(@"block---------%d", age);
    } copy];
    [block release];
}

打印结果

2020-06-03 22:13:27.853082+0800 Interview01-Block的本质[56639:9329121] block---------10

小结


ARC环境下的Block

在ARC环境下,编译器会根据自身情况自动将栈上的block复制到堆上,比如以下情况

  • 作为函数返回值
  • 将block赋值给强指针_strong的时候
  • block作为Cocoa API中方法名含有usingBlock的方法参数时候
  • block最为GCD API的方法参数

作为函数返回值

typedef void (^RLBlock)(void);
RLBlock myblock()
{
    int a = 10;
    return ^{
        NSLog(@"---------%d", a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%@", [myblock() class]);
    }
    return 0;
}
2020-06-03 23:26:53.399930+0800 Interview01-block的copy[57002:9364496] __NSMallocBlock__
  1. 将block赋值给强指针_strong的时候
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        RLBlock block = ^{
            NSLog(@"---------%d", age);
        };

        NSLog(@"有强指针:%@", [block class]);
        NSLog(@"无强指针:%@", [^{
            NSLog(@"---------%d", age);
        } class]);
    }
    return 0;
}
有强指针:__NSMallocBlock__
2020-06-03 23:29:30.468301+0800 Interview01-block的copy[57030:9366298] 无强指针:__NSStackBlock__
  1. block作为Cocoa API中方法名含有usingBlock的方法参数时候,修改NSMutableArray也不用__block修饰
NSMutableArray *sorts = [NSMutableArray array];
[sorts enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
  1. block最为GCD API的方法参数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       });

对象类型的auto变量

创建类

 
@interface RLPerson : NSObject
@property(nonatomic, assign) NSInteger age;
@end

@implementation RLPerson

- (void)dealloc
{
//    [super dealloc];
    NSLog(@"RLPerson - dealloc");
}

@end

typedef void (^RLBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            RLPerson *person = [[RLPerson alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"---------%ld", (long)person.age);
            };
            NSLog(@"------%@", [block class]);
        }
        NSLog(@"-----");
    }
    return 0;
}

因为有强指针引用,所有block为__NSMallocBlock__ 类型

在ARC环境下打断点至NSLog(@"-----")处打印值为

2020-06-05 08:45:09.755390+0800 Interview01-block的copy[1125:71486] ------__NSMallocBlock__

在MRC环境下打断点至NSLog(@"-----")处打印值为

- (void)dealloc
{
    [super dealloc];
    NSLog(@"RLPerson - dealloc");
}

typedef void (^RLBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            RLPerson *person = [[RLPerson alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"---------%ld", (long)person.age);
            };
            [person release];
            NSLog(@"------%@", [block class]);
        }
        NSLog(@"-----");
    }
    return 0;
}


2020-06-07 12:15:50.813657+0800 Interview01-block的copy[1231:28255] RLPerson - dealloc
2020-06-07 12:16:10.955578+0800 Interview01-block的copy[1231:28255] ------__NSStackBlock__

对MRC环境下对block进行copy操作

block = [^{ 
             NSLog(@"---------%d", person.age);
          } copy];

打印结果

2020-06-07 12:18:58.412416+0800 Interview01-block的copy[1268:31279] ------__NSMallocBlock__

此时block不会销毁,block类型为__NSMallocBlock__,相当于[person retain],所以堆block可以强引用持有外界的对象,而栈block不能

ARC下继续进行研究 对person对象前加__weak修饰

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            RLPerson *person = [[RLPerson alloc] init];
            person.age = 10;
            __weak RLPerson *weakPerson = person;
            block = ^{
                NSLog(@"---------%ld", (long)weakPerson.age);
            };
            NSLog(@"------%@", [block class]);
        }
        NSLog(@"-----");
    }
    return 0;
}

打断点至NSLog(@"-----");打印结果,发现person被销毁

2020-06-07 12:38:46.093930+0800 Interview01-block的copy[1540:52404] RLPerson - dealloc
2020-06-07 12:38:46.094325+0800 Interview01-block的copy[1540:52404] ------__NSMallocBlock__

查看此时的c++源码 执行命令

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

会发现报错

/var/folders/zz/v5rdxc250h53xq3b41vnsd3c0000gn/T/main-802857.mi:28844:28: error: 
      cannot create __weak reference because the current deployment target does
      not support weak references
            __attribute__((objc_ownership(weak))) RLPerson *weakPerson = person;
                           ^
1 error generated.

这是因为此时要用到运行时编译,而不是静态编译,需要用以下命令编译

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

找到源码,发现 RLPerson *__weak weakPerson

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  RLPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, RLPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

如果block改成打印person

 block = ^{
           NSLog(@"---------%d", person.age);
           };

编译获取源码,发现RLPerson *__strong person;

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

MRC下捕获block变量的copy操作

观察以下三个代码的c++源码block的结构变化,主要Desc的结构体__main_block_desc_0内部变化

1.捕获auto对象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            RLPerson *person = [[RLPerson alloc] init];
            person.age = 10;
            block = [^{
                NSLog(@"---------%d", person.age);
            } copy];
        }
        NSLog(@"------");
    }
    return 0;
}

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), __main_block_copy_0, __main_block_dispose_0};

2.auto对象的copy操作

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            RLPerson *person = [[RLPerson alloc] init];
            person.age = 10;
            block = [^{
                NSLog(@"---------%d", person.age);
            } copy];
        }
        NSLog(@"------");
    }
    return 0;
}

查看源码

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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

3.捕获auto基本数据类型捕获copy操作

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLBlock block;
        {
            int age = 10;
            block = ^{
                NSLog(@"---------%d", age);
            };
        }
        
        NSLog(@"------");
        
    }
    return 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)};

可以看出,当block内部访问auto对象类型,Desc会多俩个方法执行copy和dispose操作,其中copy操作会调用 _Block_object_assign 函数,这里其实会根据变量前面的修饰词进行强引用你还是弱引用。dispose则会调用 _Block_object_dispose 函数进行释放操作


小结

  • 当block内部访问的对象类型的auto变量时
    • 如果block在栈上,就不会对auto变量产生强引用
    • 如果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)