MJ_block(2)

怎甘沉沦 提交于 2019-12-09 13:58:32

1.block的copy

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

  • block作为函数的返回值
typedef void (^MJBlock)(void);
MJBlock myblock(int age)
{
    return ^{
        NSLog(@"---------%d",age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        int age = 10;
        NSLog(@"%@", [myblock(age) class]);
    }
    return 0;
}

//打印:__NSMallocBlock__
//当然,如果myblock没有捕获age的话,打印__NSGlobalBlock__
  • 将block赋值给__strong指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        typedef void (^MJBlock)(void);
        MJBlock block = ^{
            NSLog(@"---------%d", age);
        };
        
        NSLog(@"赋值给__strong指针:%@", [block class]);
        
       NSLog(@"未赋值给__strong指针:%@", [^{
            
        } class]);
  }
  return 0;
}

打印:赋值给__strong指针:__NSMallocBlock__
     未赋值给__strong指针:__NSGlobalBlock__
  • block作为Cocoa API中方法名含有usingBlock的方法参数时

比如数组遍历 

   [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
    }];
  • block作为GCD API的方法参数时
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"-------");
        });

此时的打印操作已经是属于堆上的block。

如果MRC环境下需要手动调用copy函数才能将block从栈复制到堆上

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        typedef void (^MJBlock)(void);
        MJBlock block = [^{
            NSLog(@"---------%d", age);
        } copy];
        
        NSLog(@"赋值给__strong指针:%@", [block class]);
        
       NSLog(@"未赋值给__strong指针:%@", [^{
            
        } class]);
  }
  return 0;
}

当然也需要对应的release操作 

 

2.对象类型的auto变量

  • 如果在栈上的block内部访问了对象类型的auto变量时,不会对auto变量产生强引用

   

{


 Person *p = [[Person alloc] init];
    ^{
        NSLog(@"-------%@",p);
    }();

}
NSLog(@"===================");
打印:
-------<Person: 0x6000028143f0>

Person - dealloc

===================

可以看到栈上的block正常捕获对象类型auto变量,但是Person会在作用域结束后打印@"=============="前销毁

  • 如果是堆上的block内部访问了对象类型的auto变量时,会对auto变量产生强引用
   typedef void(^Myblock)(void);
    Myblock block;
    {
        
        Person* p = [[Person alloc] init];
        block = ^{
               NSLog(@"-------%@",p);
        };
        block();
    }
    NSLog(@"=======");

打印:
-------<Person: 0x6000027fc2f0>

=======

MJPerson - dealloc

没有在作用域结束之后立即释放。

原理:

1.如果block被拷贝到堆上,会调用block内部的copy(__main_block_copy_)函数,copy函数会调用_Block_object_assign函数,

_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。

2.如果block从堆上移除,会调用block内部的dispose(__main_block_dispose_)函数, dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release)

注意:copy函数与dispose函数都是在_main_block_desc_结构体中,当捕获了对象类型的auto变量,无论weak还是strong都会出现这两个函数,添加在size_t   reserved  和  size_t  Block_size后边

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.__block修饰符底层

  • __block可以用于解决block内部无法修改auto变量值得问题
  • __block无法修饰全局变量和静态变量(static)

底层原理是:编译器会将__block变量包装成一个对象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      __block int age = 10;
      ^{
        age = 30;
        NSLog(@"age is %d", age);
       }();
   }
return 0;
}


//这段代码的底层结构(包括main函数)
int main(int argc, const char * argv[]) {
    

//包装外面的age变量
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        
//捕获包装过的age变量
        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
   

    }

//block底层结构体实现
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

  __Block_byref_age_0 *age; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//__Block_byref_age_0结构体的底层结构
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

可以看到先把即将要捕获的 __block修饰过的age变量包装成一个对象struct  __Block_byref_age_0,然后将这个对象捕获到block中,struct __main_block_impl_0中的age变量已经不是未修饰过的时候的 int age类型,而是__Block_byref_age_0 *age类型,也证实确实是捕获的一个对象类型的变量。

变量值的更改:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
           (age->__forwarding->age) = 20;
            
        }

通过包装过的对象中的forwarding指针修改age(forwarfing指针其实就是指向这个对象自己)。

注意:如果我们在block后边打印age的地址,那么这个age是__Block_byref_age_0的地址呢,还是__Block_byref_age_0结构体里的int age地址呢

typedef void (^MyBlock) (void);

struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};

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

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        MyBlock block = ^{
            age = 20;
            NSLog(@"age is %d", age);
        };
        
//将block转换成结构体
        struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
        
        NSLog(@"%p---%p", &age,blockImpl->age);
    }
    return 0;
}

用命令行p/x blockImpl-->age和 p/x &(blockImpl-->age-->age)分别打印age结构体和结构体里的int age地址,发现与结构体中的int age地址相等。

4.__weak

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MJPerson *p = [[MJPerson 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);
      
    });
    

    NSLog(@"touchesBegan:withEvent:");
}

打印结果:
2019-12-09 12:34:51.821036+0800 Interview03-测试[6028:400633] touchesBegan:withEvent:
2019-12-09 12:34:51.821209+0800 Interview03-测试[6028:400633] Person - dealloc
2019-12-09 12:34:52.918856+0800 Interview03-测试[6028:400633] 1-------(null)

由于Person是weak修饰的,打印时已经释放了 

 

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    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);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", p);
        });
    });
    
   
    NSLog(@"touchesBegan:withEvent:");
}

打印结果:
2019-12-09 12:32:49.854488+0800 Interview03-测试[5975:392337] touchesBegan:withEvent:
2019-12-09 12:32:50.854661+0800 Interview03-测试[5975:392337] 1-------<Person: 0x600001793a40>
2019-12-09 12:32:52.855110+0800 Interview03-测试[5975:392337] 2-------<Person: 0x600001793a40>
2019-12-09 12:32:52.855286+0800 Interview03-测试[5975:392337] Person - dealloc

会发现Person在3秒后才释放。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!