JHHK

欢迎来到我的个人网站
行者常至 为者常成

Blocks(三)

目录

Block的类型

Block的本质是OC对象,那么我们通过下面代码看下它的类及继承关系

void(^blk)(void)= ^(void){
    
};

NSLog(@"[blk class]= %@",[blk class]);
NSLog(@"[[blk class] superclass]= %@",[[blk class] superclass]);
NSLog(@"[[[blk class] superclass] superclass]= %@",[[[blk class] superclass] superclass]);
NSLog(@"[[[[blk class] superclass] superclass] superclass]= %@",[[[[blk class] superclass] superclass] superclass]);

打印日志如下

[blk class]= __NSGlobalBlock__
[[blk class] superclass]= __NSGlobalBlock
[[[blk class] superclass] superclass]= NSBlock
[[[[blk class] superclass] superclass] superclass]= NSObject

可知继承关系为:__NSGlobalBlock:__NSGlobalBlock:NSBlock:NSObject

通过代码来看下Block的类型有几种,为了更精确的反应实际情况,我们暂时把arc关掉

int weight = 80;
int main(int argc, char * argv[]) {
    @autoreleasepool {

        //1.没有访问auto变量: __NSGlobalBlock__
        void(^blk00)(void)= ^(void){
        };
        NSLog(@"[blk00 class]= %@",[blk00 class]);
        


        //2.访问全局变量,没有访问auto变量: __NSGlobalBlock__
        void(^blk01)(void)= ^(void){
           NSLog(@"weight=%d",weight);
        };
        NSLog(@"[blk01 class]= %@",[blk01 class]);
        


        //3.访问静态变量,没有访问auto变量: __NSGlobalBlock__
        static int height = 180;
        void(^blk02)(void)= ^(void){
           NSLog(@"height=%d",height);
        };
        NSLog(@"[blk02 class]= %@",[blk02 class]);



        //4.访问auto变量: __NSStackBlock__
        int age = 10;
        void(^blk1)(void)= ^(void){
            NSLog(@"age=%d",age);
        };
        NSLog(@"[blk1 class]= %@",[blk1 class]);



        //5.访问auto变量,并执行了copy操作: __NSMallocBlock__
        NSLog(@"[blk2 class]= %@",[[^(void){NSLog(@"age=%d",age);} copy] class]);
        
    }
    return 0;
}

打印日志如下:

[blk00 class]= __NSGlobalBlock__
[blk01 class]= __NSGlobalBlock__
[blk02 class]= __NSGlobalBlock__
[blk1 class]= __NSStackBlock__
[blk2 class]= __NSMallocBlock__

没有捕获auto变量:__NSGlobalBlock__
捕获auto变量:__NSStackBlock__
__NSStackBlock__执行copy后: __NSMallocBlock__

Block的内存分配图 img

每一种类型的Block调用copy后的结果如下图所示: img

Block的Copy

一、在ARC环境下,Block作为函数返回值,会默认执行copy操作。

看下代码

typedef void(^blk)(void);

//返回值为block,且访问了auto变量,所以类型为  __NSStackBlock__;
blk test(void){
    int age = 10;
    return ^{
        NSLog(@"age = %d",age);
    };
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blk b = test();

        //b位于栈空间,当test的函数调用结束后,应当销毁但实际上还可以调用。
        b();

        //打印类型为__NSMallocBlock__ 证明进行了copy操作,复制到了堆上
        NSLog(@"[b class] = %@",[b class]);
    }
    return 0;
}

打印日志如下:

age = 10
[b class] = __NSMallocBlock__

二、在ARC环境下,有强指针指向Block,会默认执行copy操作。

看下代码

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        blk b = ^{
            NSLog(@"age = %d",age);
        };
        
        NSLog(@"[b class] = %@",[b class]);
    }
    return 0;
}

打印日志

[b class] = __NSMallocBlock__

我们对代码做下改动再来看下

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        NSLog(@"[b class] = %@",[^{
            NSLog(@"age = %d",age);
        } class]);
    }
    return 0;
}

打印日志

[b class] = __NSStackBlock__

可以看到有强指针指向的Block类型为__NSMallocBlock__,而没有强指针指向的为__NSStackBlock__

三、Block作为Cocoa API中方法名含有usingBlock的方法参数时,会默认执行copy操作。

int age = 10;

NSArray * array = [NSArray new];

//该block参数位于堆空间
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"age = %d",age);
}];

四、Block作为GCD API方法参数时,会默认执行copy操作。

int age = 10;
//该block参数,位于堆空间
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"age = %d",age);
});

以上举例中的Block执行copy操作后,全部位于内存的堆空间

Block访问对象类型的auto变量

上面分析可知,访问了auto变量的Block类型为__NSStackBlock__。对__NSStackBlock__类型的Block执行copy操作后类型变为__NSMallocBlock__。那我们从这两种类型开始分析。

一、NSStackBlock 与 对象类型的auto变量

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {

        __weak blk b = nil;
        
        {
            Person * person = [[Person alloc] init];
            person.age = 10;
            b=^(){
                NSLog(@"person.age = %d",person.age);
            };
        }
        
        NSLog(@"----- %@",[b class]);
    }
    return 0;
}

日志打印

-[Person dealloc]
----- __NSStackBlock__

在打印Block类型之前,Person对象释放了,__NSStackBlock__类型的Block不会对对象类型auto变量产生强引用。不管变量是__weak修饰还是__strong修饰

二、NSMallocBlock 与 对象类型的auto变量

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {

        blk b = nil;
        
        {
            Person * person = [[Person alloc] init];
            person.age = 10;
            b=^(){
                NSLog(@"person.age = %d",person.age);
            };
        }
        
        NSLog(@"----- %@",[b class]);
    }
    return 0;
}

日志打印

----- __NSMallocBlock__
-[Person dealloc]

Person对象在离开它的作用域时并没有释放,而是在Block对象b离开它的作用域销毁时才释放的。

对代码做下改动试下,将Person对象的修饰符改为__weak;

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {

        blk b = nil;
        
        {
            __weak Person * person = [[Person alloc] init];
            person.age = 10;
            b=^(){
                NSLog(@"person.age = %d",person.age);
            };
        }
        
        NSLog(@"----- %@",[b class]);
    }
    return 0;
}

日志打印

-[Person dealloc]
----- __NSMallocBlock__

Person对象在离开它的作用域时释放了,证明Block没有对齐产生强引用。

三、结论

1、如果Block在栈上,不会对对象类型auto变量产生强引用。
2、如果Block在堆上,会根据对象类型auto变量的修饰符 __strong、 __weak、 __unsafe_unretained做出相应的操作,形成强引用或若引用。

四、分析

源码如下:

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        Person * person = [[Person alloc] init];
        person.age = 10;
        blk b=^(){
            NSLog(@"person.age = %d",person.age);
        };
        
    }
    return 0;
}

执行指令并精简代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

typedef void(*blk)(void);

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

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Person *__strong person = __cself->person;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_dbzn9w0x6h121vpvtg1vfcf80000gn_T_main_292be7_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}


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


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



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, char * argv[]) {

    Person * person = objc_msgSend(
                                   objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")),
                                   sel_registerName("init")
                                   );
    
    objc_msgSend(person, sel_registerName("setAge:"), 10);
    
    
    blk b=&__main_block_impl_0(
                               __main_block_func_0,
                               &__main_block_desc_0_DATA,
                               person,
                               570425344
                               );

    }
    return 0;
}

注意到在结构体__main_block_impl_0内对变量person的捕获是Person *__strong person;
另外在结构体__main_block_desc_0_DATA内还多出了两个参数,这两个参数是函数指针分别指向__main_block_copy_0和__main_block_dispose_0。

1、Block被拷贝到堆上会调用Block内部的copy函数,copy函数内部会调用__main_block_copy_函数,__main_block_copy_内部的_Block_object_assign函数会根据auto变量的修饰符 __strong、 __weak、 __unsafe_unretained做出相应的操作,形成强引用或若引用。

2、Block从堆上移除会调用Block内部的dispose函数,dispose函数内部会调用__main_block_dispose_0函数,__main_block_dispose_0内部的_Block_object_dispose函数会自动释放引用auto变量。

我们在看下__weak 修饰Person对象时,生成的C++源码。

源码

typedef void(^blk)(void);
int main(int argc, char * argv[]) {
    @autoreleasepool {
        __weak Person * person = [[Person alloc] init];
        person.age = 10;
        blk b=^(){
            NSLog(@"person.age = %d",person.age);
        };
        
    }
    return 0;
}

执行指令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

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

可以看到 Person *__weak person;修饰符变为了__weak,所以__NSMallocBlock__类型的Block内部对__weak修饰的Person对象不会产生强引用。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.