美文网首页
Objective-C:Block

Objective-C:Block

作者: zhouluyao | 来源:发表于2018-07-30 12:27 被阅读28次

Block

//在objc中,根据对象的定义,凡是首地址是*isa的结构体指针,都可以认为是对象(id)
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

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

block是封装了函数调用以及函数调用环境的OC对象

block有3种类型

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

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

Block_Type.png

block的copy:

@property (copy, nonatomic) void (^block)(void); //block属性的建议写法
每一种类型的block调用copy后的结果如下所示
Block_copy.png
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

1.block作为函数返回值时

2.将block赋值给__strong指针时

3.block作为Cocoa API中方法名含有usingBlock的方法参数时

4.block作为GCD API的方法参数时

block对变量的引用:

当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)

__block修饰符

1.__block可以用于解决block内部无法修改auto变量值的问题

void foo(){
    __block int i = 3;
    void(^myBlock)(void) = ^{
        i *= 2;
    };
    myBlock();
}

//就因为加了个__block,原本的int值的位置变成了一个struct(struct __Block_byref)。这个struct的首地址为同样为*isa。
struct Block_byref { //Block_private.h中的定义
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

正是如此,这个值才能被block共享、并且不受栈帧生命周期的限制、在block被copy后,能够随着block复制到堆上。

2.block不能修饰全局变量、静态变量(static)

  void test()
  {
  auto int a = 10;
  static int b = 10;
  block = ^{
  NSLog(@"age is %d, height is %d", a, b);
  };
  }
  //编译成底层的C++实现代码
  static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
  }

解决循环引用问题

// __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
// __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__weak typeof(self) weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
};

// MRC不支持__ weak的,才有了, __unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
};

//用__block解决(必须要调用block)
__block id weakSelf =self;
self.block = ^{
weakSelf = nil;
};
self.block();

//__weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。weak主要用于防止block中的循环引用

//如果捕获到的是当前对象的成员变量对象,同样也会造成对self的引用,同样也要避免
- (void)configureBlock {
    id tmpIvar = _ivar; //临时变量,避免了self引用
    self.block = ^{
        [tmpIvar msg];
    }
}

为什么weak 一般用来修饰对象,assign一般用来修饰基本数据类型?

原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。

捕获变量不能修改的原因:

当struct第一次被创建时,它是存在于该函数的栈帧上的,其Class是固定的_NSConcreteStackBlock。其捕获的变量是会赋值到结构体的成员上,所以当block初始化完成后,捕获到的变量不能更改。

void foo_(){
    int i = 2;
    NSNumber *num = @3;
    long (^myBlock)(void) = ^long() {
        return i * num.intValue;
    };
    long r = myBlock();
}
//使用Clang转换成C++
void foo(){
    int i = 2;
    NSNumber *num = @3;
 
    struct __foo_block_impl_0 myBlockT;
    struct __foo_block_impl_0 *myBlock = &myBlockT;
    myBlock->impl.isa = &_NSConcreteStackBlock;
    myBlock->impl.Flags = 570425344;
    myBlock->impl.FuncPtr = __foo_block_func_0;
    myBlock->Desc = &__foo_block_desc_0_DATA;
    myBlock->i = i;
    myBlock->num = num;
 
    long r = myBlock->impl.FuncPtr(myBlock);
}

在使用clang转换OC为C++代码时,可能会遇到以下问题,cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如ios-8.0.0

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

相关文章

网友评论

      本文标题:Objective-C:Block

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