美文网首页iOS底层原理
小码哥底层原理笔记:Block的类型

小码哥底层原理笔记:Block的类型

作者: chilim | 来源:发表于2020-06-16 16:35 被阅读0次

Block一共有三种类型:

  • NSGlobalBlock ( _NSConcreteGlobalBlock )全局Block
  • NSStackBlock ( _NSConcreteStackBlock )栈Block:系统分配内存
  • NSMallocBlock ( _NSConcreteMallocBlock )堆Block:动态分配内存,需要程序员申请,也需要程序员自己管理内存
    这三种类型都继承自NSBlock。
    三种类型的Block在内存中的位置分别是这样:
    Block类型

我们定义一个简单的Block

        void (^block)(void) = ^(){
            NSLog(@"hello world");
        };
        //block是一个OC对象,它的基类也是NSObject
        NSLog(@"%@",[block class]);//__NSGlobalBlock__
        NSLog(@"%@",[[block class] superclass]);//__NSGlobalBlock
        NSLog(@"%@",[[[block class] superclass] superclass]);//NSBlock
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);//NSObject

由以上代码知道NSGlobalBlock继承自NSBlock,而NSBlock最终继承自NSObject。我们之前也提到Block其实也是一个OC对象,这里确实是这样,因为Block最终是继承自NSObject。

接下来看看其他类型的Block
我们已经知道下面这个是NSGlobalBlock

 void (^block)(void) = ^(){
      NSLog(@"hello world");
 };

Block访问auto变量

如果我们在Block中访问一个auto变量

int age = 10;
void (^block2)(void) = ^(){
     NSLog(@"hello world -%d",age);
};
NSLog(@"%@",[block2 class]);//MRC:__NSStackBlock__,ARC:__NSMallocBlock__

我们看到在ARC下是NSMallocBlock,在MRC下是NSStackBlock。我们继续在MRC下加个copy实践一下:

int age = 10;
void (^block2)(void) = [^(){
     NSLog(@"hello world -%d",age);
} copy];
NSLog(@"%@",[block2 class]);//MRC:__NSMallocBlock__

可以看到这个block变成了NSMallocBlock。因此我们可以知道,在ARC下Block访问auto变量时系统默认帮我们进行了copy操作,同时也说明一个NSGlobalBlock访问了auto变量时会变成NSStackBlock,当NSStackBlock进行copy操作后会变成NSMallocBlock

Block访问static变量

如果我们不访问auto变量呢。

static int age = 10;
void (^block2)(void) = ^(){
     NSLog(@"hello world -%d",age);
};
NSLog(@"%@",[block2 class]);//__NSGlobalBlock__

可以知道访问非auto变量仍然是NSGlobalBlock

我们再来看一个常用的情形。新建一个Person类

@interface Person : NSObject
@property (nonatomic, copy) void (^block)();

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.block = ^{
            
        };
}}

这是开发中很常用的一段代码,我们知道给p赋值的那个Block并不是NSMallocBlock,如果是这样的话那么p.block就有可能在调用的时候被销毁。我们何时调用person里面的那个block是不知道的,所以为了让这个block不被销毁,应该把它放到堆上,成为MallocBlock,在MRC中可以通过copy来实现,在ARC中如果Person里面的block属性是copy,则赋值的时候自动进行了copy,不需要再copy了。

总结就是:

1、没有访问auto变量—— NSGlobalBlock
2、访问了auto变量—— NSStackBlock
3、NSStackBlock调用了copy—— NSMallocBlock
4、NSGlobalBlock调用了copy依旧是NSGlobalBlock
5、NSMallocBlock调用了copy依旧是NSMallocBlock,只是引用计数加1

Block的copy操作

那么在什么情况下ARC环境下,编译器会自动将栈上的Block拷贝到堆上。
1、Block作为函数返回值

Block myblock(){
    int age = 10;
    return ^{
        NSLog(@"--------%d",age);
    };//ARC会做一次copy操作
}

2、Block赋值给__strong指针时

int age = 10;
        Block block = ^{
            NSLog(@"--------%d",age);
        };//__strong强引用
         NSLog(@"%@",[block class]);//ARC下是__NSMallocBlock__,如果MRC环境是StackBlock,说明在ARC下自动进行了copy操作

3、Block作为cocoa API中方法名含有usingBlock的方法参数时

NSArray *array = @[];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];//这种也是放到堆上的,是__NSMallocBlock__

4、Block作为GCD api的方法参数时

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        });//这种GCD里面的也是__NSMallocBlock__

对象类型的auto变量捕获

如果auto变量是一个对象类型,那么在堆Block中会持有这个对象,栈Block并不会持有
新建一个Person类:

@interface Person : NSObject

@property (nonatomic, assign) int age;

@end
@implementation Person
- (void)dealloc{
    NSLog(@"person dealloc");
}
@end
Block block;
{
       Person *person = [[Person alloc] init];
       person.age = 10;
       block = ^{
           NSLog(@"--------%d", person.age);
       };//block持有person,person不会释放 
}
block();//--------10

我们对Person进行weak操作

Block block;
{
       Person *person = [[Person alloc] init];
       person.age = 10;
__weak Person *weakPerson = person;//在执行Block之前person对象已经释放掉了
       block = ^{
           NSLog(@"--------%d", weakPerson.age);
       };//person已经释放 
}
block();
打印
person dealloc
--------0

很明显对在执行Block之前weak类型的person已经被释放了,所以person.age是0

接下来我们单独运行这段代码:

Person *p = [[Person alloc] init];//立即释放
打印:person dealloc

1、如果我们使用GCD延迟执行打印p操作,如下

    Person *p = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",p);
    });//3秒后释放
打印:
2020-06-16 16:26:28.366679+0800 XMGTestProject[34613:2397597] <Person: 0x281824510>
2020-06-16 16:26:28.366912+0800 XMGTestProject[34613:2397597] person dealloc

我们知道GCD的这个Block是堆Block,所以对p进行了一次强引用,三秒后打印p然后才释放p。
2、如果我们对p进行weak操作

Person *p = [[Person alloc] init];
   __weak Person *weakP = p;
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       NSLog(@"%@",weakP);
   });//弱引用立即释放
打印:
2020-06-16 16:29:23.845047+0800 XMGTestProject[34629:2398404] person dealloc
2020-06-16 16:29:27.140156+0800 XMGTestProject[34629:2398404] (null)

因为对p进行弱引用,所以在block执行之前p已经被释放了,所以block打印null
3、如果我们用两个block分别打印weakP和p

Person *p = [[Person alloc] init];
   __weak Person *weakP = p;
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       NSLog(@"1--------%@",weakP);//因为编译器判断整体里面还有个对p的强引用,所以这里也并不会释放
       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
           NSLog(@"2--------%@",p);
       });
   });//并不会立即释放
打印:
2020-06-16 16:31:53.093971+0800 XMGTestProject[34648:2399427] 1--------<Person: 0x283d10840>
2020-06-16 16:31:55.235412+0800 XMGTestProject[34648:2399427] 2--------<Person: 0x283d10840>
2020-06-16 16:31:55.235611+0800 XMGTestProject[34648:2399427] person dealloc

可以看到即使是weakP也能在1秒后打印出来,因为系统判定整体的Block里面有一个强引用,所以即便是weakP也不会立即释放。
4、如果我们把上面的顺序反一下,先打印强引用的,后打印弱引用的

Person *p = [[Person alloc] init];
   __weak Person *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);//因为上面的强引用已经执行完了,所以释放掉了
       });
   });//1、不会释放,2会释放
打印:
2020-06-16 16:34:02.094482+0800 XMGTestProject[34661:2399999] 1--------<Person: 0x2822a84d0>
2020-06-16 16:34:02.094642+0800 XMGTestProject[34661:2399999] person dealloc
2020-06-16 16:34:04.168365+0800 XMGTestProject[34661:2399999] 2--------(null)

因为强引用的那个在前面已经执行完了,然后就释放了,所以weakP也就释放了。

相关文章

网友评论

    本文标题:小码哥底层原理笔记:Block的类型

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