【iOS】Block底层分析

目录


前言

Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,因为它内部有isa指针

Block的基本使用请看这两篇文章:

  • k
  • l

本篇文章着重探究Block这些特性的底层原理

Block底层结构

声明一个最简单的块并调用:

objectivec 复制代码
void (^block)(void) = ^{
    NSLog(@"Hello World!");
};
block();

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:

objectivec 复制代码
// 原本的代码有各种强制转换,目前不重要,先删去从简

// 声明并实现一个block
// void (*block)(void) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);


// 调用执行block
// ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
block->FuncPtr(block);
// __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里

这些穿插了许多下划线的符号实际上是不同的结构体变量,Block本质就是struct __main_block_impl_0类型的结构体,下图清晰地说明了block的底层结构:


__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的(相当于直接把__block_impl里的值都放到__main_block_impl_0里)

所以block.impl->FuncPtr(block)就相当于block->FuncPtr(block)

Block捕获变量原理

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

捕获局部变量(auto、static)

auto:自动变量,离开作用域就自动销毁,只存在于局部变量
static:静态局部变量

objectivec 复制代码
// 不加关键字默认是auto变量
/*auto*/ int age = 10;
static int height = 175;

void (^block)(void) = ^{
    // age、height的值捕获进来(capture))
    NSLog(@"age is %d, height is %d", age, height);
};

// 修改局部变量的值
age = 20;
height = 180;

block();
NSLog(@"%d %d", age, height);

打印结果:

可以看到age仍为修改前的值,而height确确实实被修改了

将以上代码转换成C++代码来看一下:

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *height;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • Block结构体的变量多了两个,分别是ageheight,这说明外部的变量被捕获到了Block的内部
  • 构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来保存

声明实现Block调用析构函数:

objectivec 复制代码
int age = 10;
static int height = 175;

block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));

age = 20;
height = 180;

而后调用Block,实际调用__main_block_func_0

objectivec 复制代码
block->FunPtr(block)
objectivec 复制代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}

此时的age是值传递,打印的只是Block初始化时传进去的 ,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址一直所指向那块内存的值

全局变量

objectivec 复制代码
int age_ = 10;
static int height_ = 175;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            NSLog(@"age_ is %d, height_ is %d", age_, height_);
        };

        age_ = 20;
        height_ = 180;
        
        block();
       
    }
    return 0;
}

全局变量一直在内存中,打印的一直是最新的值,不用捕获

为什么会有这样的差异呢?

auto和static:因为作用域的问题,自动变量的内存随时可能被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它一直在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值

全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),使用的时候再从Block中取出,而全局变量是直接访问

捕获实例self

objectivec 复制代码
- (void)testSelf {
    void (^block)(void) = ^{
    // NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);
        NSLog(@"--------%p", self);
        /*
        NSLog(@"--------%p", self->_name);
        相当于NSLog(@"--------%p", _name);
        也会捕获进去
        */
    };
    block();
}

看了它的C++实现后,发现self也会被捕获进去

实际上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd

objectivec 复制代码
void testSelf(Person* self, SEL _cmd, ) {
	// ...
}

即然self是参数,参数也是局部变量,它被捕获进Block也就能解释得通了

Block类型

上面提到Block是OC对象,因为它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?

首先运行以下代码:

objectivec 复制代码
void (^block)(void) = ^{
    NSLog(@"Hello!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
/*
 __NSGlobalBlock__
 NSBlock
 NSObject
 */

可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象

不同操作对应的Block类型不同

objectivec 复制代码
// Global:没有访问auto变量,跟static变量无关
void (^block1)(void) = ^{
          NSLog(@"Hello");
};

// 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
// 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖

// Stack:访问了auto变量
int age = 21;
void (^block2)(void) = ^{
    NSLog(@"Hello - %d", age);
};
// ARC下打印Malloc?MRC下确实是Stack

NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
    NSLog(@"%d", age);
} class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__

// 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
// 首先肯定以运行时的结果为准,Block确实有三种类型,可能会通过Runtime动态修改类型
  • 没有访问自动变量的Block类型是__NSGlobalBlock__,存储在数据段

    其实Global不常用,既然不访问变量,那么将代码块封装成函数一行直接调用才显得更为简洁

  • 访问了自动变量的Block类型是__NSStackBlock__,存储在栈区

    以上代码是在MRC下运行的

  • __NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__,存储在堆区

    若是在ARC下运行,即使不用copy修饰编译器也会自动对__NSStackBlock__进行copy操作,block2的类型将会是Malloc类型

手动对每种类型的Block调用copy后的结果如下图所示

Block的copy

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

放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理

Block作为返回值

objectivec 复制代码
typedef void(^BBlock)(void);

BBlock myBlock(void) {
    int age = 21;
    return ^{
        NSLog(@"----------%d", age);
    };
}

BBlock bblock = myBlock();
bblock();
NSLog(@"%@", [bblock class]); // __NSMallocBlock__
//BBlock myBlock(void) {
//    return [^{
//        NSLog(@"----------");
//    } copy];
//}

由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很危险,如果在MRC下运行上述代码,编译器会提示报错:

ARC 下不必担心此问题,编译器会自动对返回的Block进行copy操作(如注释所写),返回拷贝到堆上的Block

将Block赋值给__strong指针

objectivec 复制代码
int age = 21;
/*__strong*/ BBlock bblock = ^{
    NSLog(@"--------%d", age);
};
NSLog(@"%@", [bblock class]);  // ARC:__NSMallocBlock__

// 没有被强指针指着
NSLog(@"%@", [^{
    NSLog(@"--------%d", age);
} class]); // __NSStackBlock__

Block作为Cocoa API中方法名含有usingBlock的方法参数

objectivec 复制代码
NSArray* array = @[@"one", @2, @{@"seven" : @7}];
// 遍历数组并调用Block
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
}];

Block作为GCD API的方法参数

objectivec 复制代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
  
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  
});

Block属性的写法

因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性

objectivec 复制代码
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

Block访问对象类型的auto变量

Block在栈上

只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用

Block被拷贝到堆上

如果Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作

objectivec 复制代码
BBlock bblock;
{
    __strong Person* person = [[Person alloc] init];
    // __weak Person* person = [[Person alloc] init];
    person.age = 21;
    bblock = ^{
        // 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
        NSLog(@"-%d-", person.age);
    };
    
    // MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
    //[bblock release];
}
NSLog(@"--------------");

将上面代码文件转换成C++文件:

objectivec 复制代码
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;
  }
};

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

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

Block内部的__main_block_desc_0结构体会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

Block从堆上移除

如果Block从堆上移除,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动release引用的auto变量

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

注:

  • 只有在引用对象类型的变量时,才会生成copydispose函数
  • 如果引用的是static修饰的对象类型,那么捕获的变量在C++代码中将会是Person *__strong *person;
  • 代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

使用GCD API验证Block对外部变量的强弱引用(Github Demo):

objectivec 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person* person = [[Person alloc] init];
    
    __weak Person* weakPerson = person;
    
    // 强引用了,Block调用完毕释放了person才会释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", person);
//    });
    
    // 弱引用,调用Block之前person已经释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", weakPerson);
//    });
    
    // 编译器已经检查到会有强引用
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---1%@", weakPerson);
//        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            NSLog(@"---2%@", person);
//        });
//    });
    
    // 不会等到弱引用就释放了
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"---1%@", person);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"---2%@", weakPerson);
        });
    });
    
    NSLog(@"Screen Touched");
}

修饰符__block

如果在Block内部修改捕获的auto变量值,编译器将会报错:

objectivec 复制代码
int age = 21;
BBlock block = ^{
    age = 20;
    NSLog(@"%d", age);
};
block();

从底层可看出在这里修改变量的值,实际上是通过改变__main_block_fun_0函数里的局部变量达到改变main函数里的变量,这是两个独立的函数,显然不可能

1. 使用static修饰变量

static来修饰age属性,底层用指针访问,block内部引用的是age的地址值,函数间会传递变量的地址,可以根据地址去修改age的值,修改的就是同一块内存

但不好的是age属性会一直存放在内存中不销毁,造成多余的内存占用 ,而且会改变age属性的性质,不再是一个auto变量

2. 使用__block修饰变量

__block来修饰属性,底层会生成__Block_byref_age_0类型的结构体对象,里面存储着age的真实值

转换成C++文件来查看内部结构,经__block修饰后,会根据__main_block_impl_0里生成的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值

objectivec 复制代码
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;  // 指向结构体本身
 int __flags;
 int __size;
 int age;
};

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, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
		
		// 传进去的是age的地址
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        
        Block 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);
    }
    return 0;
}

总结

  • __block可以用于解决block内部无法修改auto变量值的问题
  • 编译器会将__block变量包装成一个对象
  • 其实修改的变量是__block生成的对象里面存储的变量的值,而不是外面的auto变量,但是内部生成的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量
  • __block不能修饰全局变量、静态变量(static)

__block内存管理

程序编译时,block__block都是在栈中的,这时并不会对__block变量产生强引用

因为__block也会包装成 OC对象 ,所以block底层也会生成copy函数dispose函数

objectivec 复制代码
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

Block复制到堆上

blockcopy到堆时,会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)

实际上,这时__block修饰的变量因为被包装成了OC对象,所以也会被拷贝到堆上,如果再有block强引用__block,由于__block变量已经拷贝到堆上了,就不会再拷贝了

Block从堆上移除

block从堆中移除时,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)

如果有多个block同时持有着__block变量,那么只有所有的block都从堆中移除了,__block变量才会被释放

__block和OC对象在block中的区别

__block生成的对象就是强引用,而NSObject对象会根据修饰符__strong或者__weak来区分是否要进行retain操作

注意:__weak不能修饰基本数据类型,编译器会报__weak' only applies to Objective-C object or block pointer types; type here is 'int'警告

__forwarding指针

objectivec 复制代码
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;
}
  • 在栈中,__block中的__forwarding指针指向自己的内存地址
  • 复制到堆中之后,__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block
  • 这样的目的都是为了不论访问的__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量

保证20被存储在堆中Block所引用的变量

__block修饰对象类型

情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong__weak修饰

__block对象在栈上时,不会对指向的对象产生强引用

__block对象被copy到堆上时,也会生成一个新的结构体对象,并且只会被block进行强引用,会根据不同的修饰符__strong__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用

objectivec 复制代码
struct __Block_byref_weakPerson_0 {
	void __isa;
	__Block_byref_weakPerson_0 __forwarding;
	int __flags;
	int __size;
	void (__Block_byref_id_object_copy)(void, void);
	void (__Block_byref_id_object_dispose)(void*);
	Person *__weak weakPerson;
};


static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src + 40), 131);

// __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
__attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};

注:在MRC环境下即使用__block修饰,对于结构体对象的成员变量,__block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,__block修饰的变量就已经释放了,这点和在ARC环境下不同

Block循环引用

两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放

objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        person.age = 21;
        person.block = ^{
            NSLog(@"%d", person.age);
        };
    }
    NSLog(@"111111111111");
    return 0;
}

结果就是person对象不会释放,因为没有调用dealloc方法

person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放

objectivec 复制代码
@implementation Person

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

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        person.age = 21;
        [person test];
    }
    return 0;
    NSLog(@"111111111111");
}

block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)selfself又持有block,同样会造成循环引用

解决办法

  • 使用__weak__unsafe_unretainedBlock指向对象的引用变为弱引用

    objectivec 复制代码
    //    __unsafe_unretained typeof(self)weakSelf = self;
    __weak typeof(self)weakSelf = self;
      
    self.block = ^{
        NSLog(@"%d", weakSelf.age);
    };
  • __block解决,用__block修饰对象会造成三者相互引用造成循环引用,需要手动调用block

    objectivec 复制代码
    __block Person* person = [[Person alloc] init];
    person.age = 21;
    person.block = ^{
        NSLog(@"%d", person.age);
        person = nil;
    };
    person.block();

    block内部也需要手动将person置空,这个person__block内部生成的指向Person对象的变量

  • block传参,将self作为参数传入block中,进行指针拷贝,并没有对self进行持有

    objectivec 复制代码
    // Person.m
    self.block = ^(Person * _Nonnull person) {
        NSLog(@"%d", person.age);
    };
    self.block(self);
  • MRC下不支持__weak,只能使用__unsafe_unretained

    MRC下直接使用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:

    objectivec 复制代码
    __block Person *person = [[Person alloc] init];
    person.age = 10;
    
    person.block = [^{
    	NSLog(@"age is %d", person.age);
    } copy];
    
    [person release];

强弱共舞

这种情况虽没有引起循环引用,但block延迟执行2秒,等person释放后,就无法获取其age,很不合理

objectivec 复制代码
__weak typeof(person) weakPerson = person;
person.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%d", weakPerson.age);
    });
};
person.block();

改进一下:

objectivec 复制代码
__weak typeof(person) weakPerson = person;
person.block = ^{
    __strong __typeof(weakPerson)strongPerson = weakPerson;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%d", strongPerson.age);
    });
};
person.block();

通过运行结果发现,完全解决了以上self中途被释放的问题,这是为什么呢?分析如下:

  • 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self
  • weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放

总结

Block在iOS开发中极为重要,非常适合处理异步操作、回调、集合操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案

相关推荐
用户0911 小时前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan11 小时前
iOS26适配指南之UIColor
ios·swift
权咚1 天前
阿权的开发经验小集
git·ios·xcode
用户091 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸1 天前
macOS自带截图命令ScreenCapture
macos
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918411 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
TESmart碲视2 天前
Mac 真正多显示器支持:TESmart USB-C KVM(搭载 DisplayLink 技术)如何实现
macos·计算机外设·电脑
00后程序员张2 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h2 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa