Home iOS - 关于Block
Post
Cancel

iOS - 关于Block

定义

  1. Apple 在C语言的基础上添加的拓展功能

  2. 能持有作用域变量的匿名函数

    匿名函数: 没有名称的函数

    能持有作用域变量 : Block所在作用域的变量包括:局部变量,函数的参数,静态的局部变量,静态的全局变量,全局变量

如何声明及使用

参考:

How Do I Declare A Block in Objective-C?

Block

谈Objective-C block的实现

block没那么难(一):block的实现

block没那么难(二):block和变量的内存管理

block没那么难(三):block和对象的内存管

正确使用Block避免Cycle Retain和Crash

深入理解 weak-strong dance

1. 声明为变量

1
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    
void (^selectedBlock)(void)=^{
};
    
//无参数可写为
void (^selectedBlock)()=^{

};
//有参数
void (^selectedBlock)(NSString *param)= ^(NSString *param) {

};
//有参数并要返回值
BOOL (^selectedBlock)(NSString *param)= ^(NSString *param) {

    return YES;
};
    

2. 声明为属性

1
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

示例

1
2
3
@property(nonatomic, copy) void (^selectedBlock)(void);
@property(nonatomic, copy) void (^selectedBlock)(NSString *param);//带参数
@property(nonatomic, copy) BOOL (^selectedBlock)(NSString *param);//带参数及返回值

3. 声明为传入的参数

- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

示例

1
2
3
- (void)startRequesting:(void (^)(void))callBlack;
- (void)startRequesting:(void (^)(NSString *param))callBlack;//带参数
- (void)startRequesting:(BOOL (^)(NSString *param))callBlack;//带参数和返回值

4. 作为传参时

1
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

示例

1
2
3
4
5
6
7
8
9
10
11
12
[self startRequesting:^{

}];
[self startRequesting:^(NSString *param) {
        
}];
    
[self startRequesting:^(NSString *param) {
    return YES;
}];
    

5. Typedef时

1
2
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

示例

1
2
3
typedef void (^ViewControllerSelectBlock)(void);

@property(nonatomic, copy) ViewControllerSelectBlock selectBlock;

6. 指针类型变量

1
2
3
4
5
6
typedef int (^MyBlock)(int);
MyBlock aBlock = ^int(int a) {
	return a + 1;
};
MyBlock* aBlockPointer = &aBlock;
int result = (*aBlockPointer)(10); // result = 11.

使用场景

  • Eumeration (枚举/遍历,如NSArray的遍历)
  • View Animations (动画)
  • Sorting (排序)
  • Notification (某些事件被触发时,执行Block)
  • Error Handlers (错误处理时)
  • Completion Handlers (任务完成时的回调,如网络请求后的回调)
  • Multithreading (配合GCD/NSOperation时)

Block的实现

1. 一个简单的Block 的实现

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
	void (^blk)(void) = ^{
		int tag = 8;
		printf("Block, %d\n", tag);
	};
	blk();
	return 0;
}

转换获取C代码后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
	__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
		impl.isa = &_NSConcreteStackBlock;
		impl.Flags = flags;
		impl.FuncPtr = fp;
		Desc = desc;
	}
};
// 这里对应的就是 block 的匿名函数。参数 __cself 为指向 block 值的指针,类似 Objective-C 中实例方法中指向对象自身的变量 self。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	int tag = 0;
	printf("Block, %d\n", tag);
 }
static struct __main_block_desc_0 {
	size_t reserved;
	size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
	void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
	((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
	return 0;
}

结论:

  • Block经过转换,变成一个C语言函数
  • 是通过使用函数指针来调用函数
  • Block其实就是一个Objetive-C对象

2. 一个自动获取Block作用域外变量的Block 的实现

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
	int dummy = 256;
	int tag = 10;
	const char *say = "tag = %d\n";
    void (^blk)(void) = ^{
        printf(say, tag);
    };
    
    blk();
    
    return 0;
}

经过转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
    // 这里可以看到,这里的两个 Block 结构体的成员变量与外面的两个自动变量完全相同,这是等会要来拷贝用的。
    const char *say;
    int tag;
    // 下面是结构体的构造函数。
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_say, int _tag, int flags=0) : say(_say), tag(_tag) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
// 这里可以看到,只是把自动变量的值拷贝进来了,并且只拷贝了 Block 里用到的自动变量,如:say 和 tag,并没有拷贝 dummy。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    const char *say = __cself->say; // bound by copy
    int tag = __cself->tag; // bound by copy
    
    printf(say, tag);
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
    int dummy = 256;
    int tag = 10;
    const char *say = "tag = %d\n";
    
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, say, tag);
    
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
    return 0;
}

结论

  • Block 只会获取它用到的外部变量
  • 转换后,Block的结构体内的变量与它用到的外部变量的类型一致
  • 获取外部变量的过程就是在执行Block的时候,将外部变量的值Copy到Block对应的成员变量上。

引申

所以,以下的方式(使用C语言的数组)是不可行的:

1
2
3
4
5
6
7
8
9
10
// 这样是有问题的:
const char text[] = "hello";
void (^blk)(void) = {
	printf("%c\n", text[2]);
}
// 这样是没问题的:
const char* text = "hello";
void (^blk)(void) = {
	printf("%c\n", text[2]);
}

因为转化后是:

1
2
3
4
5
6
7
8
void func(const char text[]) {
	const char text2[] = text; // 这种赋值方式是不符合 C 语言规范的。
	printf("%c\n", text[2]);
}
int main() {
	cosnt char text[] = "hello";
	func(text);
}

3. 一个使用静态,全局变量的Block的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int global_val = 1;
static int static_global_val = 2;
int main() {
	static int static_val = 3;
    void (^blk)(void) = ^{
        global_val = 100;
        static_global_val = 200;
        static_val = 300;
    };
    
    blk();
    
    return 0;
}

转换成Cpp后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;
    // 可以看到,对于静态变量,在 Block 中是用其指针,所以改变其值也是没问题的。
    int *static_val;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy
    // 可以看到,对于全局的变量(静态或非静态),不用截获,直接用就行了。对于局部静态变量,用到了指针,改变其值是没问题的。因为静态变量是存储在静态存储区,生命周期是整体程序运行期间,所以可以这样用。
    global_val = 100;
    static_global_val = 200;
    (*static_val) = 300;
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
    static int static_val = 3;
    
    
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val);
    
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
    return 0;
}

结论

  • Block中使用全局变量、全局静态变量时与C语言一致,直接使用即可
  • Block中使用局部静态变量时,转换后的函数__main_block_func_0 通过静态变量的指针对其进行访问。
  • 采用指针的方式不可用于普通的外部变量,这是因为转化后的函数__main_block_func_0在含有Block语法的函数之外,其作用域结束后,外部变量的生命周期也结束了,不可再使用。而静态变量的作用域虽然在函数内,但它是被存储在静态区域的,其生命周期是整个程序运行期间,所以可以用指针访问。

4. __block 修饰符的实现

当在Block内需要改变外部变量时,需使用__block 修饰变量。 这中间,__block 对变量做了些什么呢?

__block: __block 存储域类说明符

C语言中,其他的存储类说明符有:typedef、extern、static、auto、register,这些说明符用于指定将变量设置存储到哪个存储域中,如,auto:存储在栈上,static:存储静态变量在数据区等等。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main() {
	int __block val = 3;
    void (^blk)(void) = ^{
		val = 300;
	};
    
    blk();
    
    return 0;
}

以上代码转换后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
// 这个结构体是用来定义原来的自动变量 val 的。
struct __Block_byref_val_0 {
    void *__isa;
    __Block_byref_val_0 *__forwarding; // 这个成员变量是一个指向自己实例的指针。
    int __flags;
    int __size;
    int val; // 这个成员变量将用来存原来自动变量的值。
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    // 原来的自动变量被重新定义成了一个结构体,val 成了指向这个结构体的一个实例的指针。
    __Block_byref_val_0 *val; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_val_0 *val = __cself->val; // bound by ref
    
    // 下面的赋值可以看出,是把原自动变量变成了 __main_block_impl_0 结构体的实例的指针。再通过指针去访问这个实例的成员变量。其中成员变量 __forwarding 指向实例自己,成员变量 __val 存储值。
    (val->__forwarding->val) = 300;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
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() {
	// 可以看到,变量 val 竟然成为了结构体 __Block_byref_val_0 的实例。
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
    
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344);
    
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
    return 0;
}

结论

  • __block修饰的变量,会转换成一个结构体,原来的外部变量val变成结构体内的成员变量,然后在block内通过指针访问这个变量结构体。
  • 变量结构体可以保证到多个block使用同一个__block变量

核心

__block 的核心设计是解决当超过外部变量的作用域时,调用Block,并且可以访问和修改这个外部变量的的场景的问题。

问题

  • 超过外部变量作用域时,block如何访问和修改这个外部变量?
  • 在__Block_byref_val_0结构体内,为什么会有一个指针是指向自己的? (下方内存管理中会有说明)

Block的内存管理(存储域)

3种存储域
  • _NSConcreteGlobalBlock (存储在程序的数据区,与全局变量一致)
  • _NSConcreteStackBlock (存储在栈上)
  • _NSConcreteMallocBlock (存储在堆上)

1. _NSConcreteGlobalBlock

在两种情况下,Block会存储为GlobalBlock ,一是使用的全局变量,二是没有使用到任何变量。

2. _NSConcreteStackBlock

除了以上所属的两种情况外,其他生成的Block都是StackBlock,存储在栈上。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 此 Block 是 _NSConcreteGlobalBlock 类型的。此 Block 结构体实例的内容不会依赖执行时的状态,所以整个程序中只需要一个实例。因此将其放在与全局变量相同的数据区域即可。
void (^blk)(void) = ^{printf("Global Block\n");};
int main() {}
// 此 Block 需要截获自动变量,截获的自动变量的值会根据执行时的状态变化,下面每次 for 循环 Block 截获的自动变量的值都不一样。所以需要把对应的 Block 结构体的实例放在调用栈中,让其对应不同调用上下文的状态。
typedef int (^MyBlock)(int);
for (int i = 0; i < 10; i++) {
	MyBlock blk = ^(int c) {
		return i * c;
	};
}
// 此 Block 虽然不是放在全局代码位置,但是它在实现中没有使用任何自动变量,Block 结构体的实例在每次循环中都是一个。虽然通过 clang 转换代码看到的是 _NSConcreteStackBlock 类型,但实现上却有不同。它还是会被放在全局数据区域。
typedef int (^MyBlock)(int);
for (int i = 0; i < 10; i++) {
	MyBlock blk = ^(int c) {
		return c;
	};
}

3. _NSConcreteMallocBlock

Block语法可以将Block从栈复制到堆上,这样子,即使__block标记的变量生命周期结束了,堆上的Block还存在着。另外,堆上的Block将NSConcreteMallocBlock类对象写入了Block结构体的isa成员变量,而指向自己的指针__forwaring成员变量就可以被访问,无论从栈上还是堆上。

_NSConcreteGlobalBlock和_NSConcreteStackBlock可以从语法上进行判断,而_NSConcreteMallocBlock更像是运行时的类型。

Block内存管理时需注意到的问题

ARC的情况下,编译器大多时候会自动把Block从栈上复制到堆上。

然而有时编译器是无法判断的,这时候,就需要我们手动Copy。

例如,将Block作为参数传入方法或函数中 ,此时编译器是无法作出判断的。 这时候就需要手动Copy一下。

以下方法或函数则不需要手动复制:

  • CocoaFoudation中出现usingBlock的
  • GCD的API

三种类型block的copy结果:

Block类型原始存储域Copy后
_NSConcreteStackBlock
_NSConcreteGlobalBlock程序的数据区域什么也不做
_NSConcreteMallocBlock引用计数增加

结论: 不论Block的存储区域在哪里,使用Copy方法都不会引起问题,不确定的话就调用下Copy就好了

__block变量存储域

使用__block 的变量在block从栈上复制到堆上时,__block 也会受到影响.

__block 变量原来的存储区域Block 从栈复制到堆对其截获的 __block 变量的影响
从栈上复制到堆上并被 Block 持有
被 Block 持有

效果

  • 当block被拷贝到堆上时,__block 变量也被拷贝到堆上,并且block会持有该变量。
  • 当多个block使用同一个__block变量时,新增的复制到对上的Block会持有该__block变量,该变量引用计数+1。同理,当一个Block被废弃时,该__block变量的引用计数-1

回应之前的问题

  • 超过外部变量作用域时,block如何访问和修改这个外部变量?
  • 在__Block_byref_val_0结构体内,为什么会有一个指针是指向自己的?

当使用__block时,Block和__block修饰的变量都被复制到堆上,从而实现Block内可访问和修改该变量,并且变量生命周期不会过期。而__forwarding指针就是用于在变量被复制到堆上时还可被访问的问题。

最后

  1. 总结下ARC下需要手动Copy和不需要手动Copy的情况
  • 不需要手动Copy的情况:
    • 将Block作为返回值时
    • 将Block赋值给__strong修饰的id类型的变量或Block类型的成员变量时。
    • GCD的API 或 Cocoa Foudation的usingBlock方法
  • 需要手动Copy的情况:
    • 向方法或函数传入Block的参数时,除非方法或函数内有进行Copy操作(正如上述第三点的UsingBlock和GCD API)
    • 其他不明情况
  1. 各种类型的变量
    • 实例变量 instance varible,在 Block 里是可读可写的。
    • 静态变量在 Block 里是可读可写的。
    • 全局变量在 Block 里是可读可写的。
  2. __weak / __strong / __block

__weak

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ViewController.m
#define PrintObject(prefix, obj) { NSLog(@"%@: (指针内存地址: %p, 指针值: %p, 指向的对象值: %@)", prefix, &obj, obj, obj); }
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    typedef void (^MyBlock)();
    
    MyBlock blk;
    
    {
        MyObject *obj = [[MyObject alloc] init];
        obj.text = @"I-am-an-obj.";
        PrintObject(@"obj", obj);
        
        typeof(obj) __weak weakObj = obj;
        PrintObject(@"weakObj", weakObj);
        
        blk = ^() {
            PrintObject(@"weakObj in Block", weakObj);
        };
        
        blk();
    } // obj、weakObj 的作用域结束。
    
    blk();	    
}
  • 此时,obj,block外的weakObj,block内的weakObj是三个指针,并同指向同一个对象。
  • __weak修饰的weakObj 对对象弱引用持有。而,当obj的作用域结束后,obj指向的对象没有被强引用,会被立即释放。所以,这时的block中持有的weakObj显示为nil

__strong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    typedef void (^MyBlock)();
    
    MyBlock blk;
    
    {
        MyObject *obj = [[MyObject alloc] init];
        obj.text = @"I-am-an-obj.";
        PrintObject(@"obj", obj);
        
        typeof(obj) __strong strongObj = obj;
        PrintObject(@"strongObj", strongObj);
        
        blk = ^() {
            PrintObject(@"strongObj in Block", strongObj);	        };
        
        blk();
    } // obj、strongObj 的作用域结束。
    
    blk();
}
  • 此时,obj,block外的strongObj,block内的strongObj是三个指针,并同指向同一个对象。
  • __strong修饰的strongObj对对象强引用持有。 当obj,block外的strongObj作用域结束时,其指向的对象依然被block持有,所以不会立即释放。

__block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    typedef void (^MyBlock)();
    MyBlock blk;
    
    {
        MyObject *obj = [[MyObject alloc] init];
        obj.text = @"I-am-an-obj.";
        PrintObject(@"obj", obj);
        
        typeof(obj) __block blockObj = obj;
        PrintObject(@"blockObj", blockObj);
        
        blk = ^() {
            PrintObject(@"blockObj in Block", blockObj);
        };
        
        blk();
    } // obj、blockObj 的作用域结束。
    
    blk();
    
    
}
  • 在ARC 的情况下,__block 的表现与__strong无区别,对block中的对象进行了强引用持有。注意的时, 非ARC的情况下,对__block的对象,block不会进行retain。

__weak/__strong dance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    typedef void (^MyBlock)();
    
    MyBlock blk;
    
    {
        MyObject *obj = [[MyObject alloc] init];
        obj.text = @"I-am-an-obj.";
        PrintObject(@"obj", obj);
        
        typeof(obj) __weak weakObj = obj;
        PrintObject(@"weakObj", weakObj);
        
        blk = ^() {
            typeof(weakObj) __strong strongObj = weakObj;
            PrintObject(@"weakObj in Block", weakObj);
            PrintObject(@"strongObj in Block", strongObj);
        };
        
        blk();
    } // obj、weakObj 的作用域结束。
    
    blk();
}
  • 此时,obj,block外的weakObj,block内的strongObj 和weakObj 都是四个不同的指针,都指向同一个对象。
  • block获取weakObj持有的对象是对其进行弱引用持有。而strongSelf是想对weakObj的对象进行强引用持有。

避免强引用循环

  • 默认情况下,block中使用的对象,都对该对象增加一个强引用持有。如:
1
2
3
4
5
6
- (void)configureBlock {
    self.block = ^{
    	// capturing a strong reference to self, creates a strong reference cycle
        [self doSomething];
    };
}

此时,就会有一个强引用循环:

1
2
3
graph LR
self-->block
block-->self
  • 此时,可以用过weak弱引用持有self来解决,__weak修饰的变量被释放后,对应的指针会置为nil。
1
2
3
4
5
6
7
- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
    	// capture the weak reference to avoid the reference cycle
        [weakSelf doSomething];
    }
}

鉴于对早期版本的兼容,还需了解以下情况:

ARC & iOS 5.0+ : 可以用__weak解决强引用循环的问题。

ARC & iOS 5.0- : 可以用__unsafe_unretained 解决强引用循环的问题。注意的是,__unsafe_unretained修饰的变量相当于一个普通的指针,其所指的对象被释放后,该指针不会置为nil,会成为野指针,此时不可用。

MRC : 可以用__block 解决强引用循环的问题。注意的时,MRC环境下,不会对对象retain,从而导致一个问题也就是,其所指的对象释放后,该指针也不会置为nil。所以在某些异步操作时,执行到block中时,会出现野指针的问题。

可以通过malloc_zone_from_ptr判断指针是否是野指针。

1
2
3
4
5
6
7
8
9
10
11
// MRC.
#import <malloc/malloc.h>
- (void)configureBlock {
	XYZBlockKeeper * __block blockSelf = self;
	self.block = ^{
		if (!malloc_zone_from_ptr(blockSelf)) {
			return;
		}
		[blockSelf doSomething];
	}
}
  • 使用__weak可以解决强引用持有的问题,但在某些应用场景下,会有另一个问题: 异步操作中,当对象释放后,异步方法回调到block中时,对象已为空,此时就会导致程序崩溃。此时,就需要用到__weak/__strong dance.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#import "TestBlockViewController.h"
#import "TestService.h"
@interface TestBlockViewController ()
@property (nonatomic, strong) NSString *tag;
@property (nonatomic, copy) void (^myBlock)(void);
@end
@implementation TestBlockViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Init properties.
    self.tag = @"tag is OK.";
    
    // Init TestService's block.
    typeof(self) __weak weakSelf = self;
    self.myBlock = ^{
        typeof(weakSelf) __strong strongSelf = weakSelf;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"strongSelf is OK.");
            NSLog(@"%@", strongSelf.tag);
            //NSLog(@"%@", self.tag); // Retain cycle.
        });
    };
    
}
- (IBAction)callService:(id)sender {    
    self.myBlock();
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)dealloc {
    NSLog(@"TestBlockViewController dealloc.");
}
@end
  • 此时,strongSelf强引用了weakSelf,从而延长了其生命周期。打印结果如:
1
2
3
strongSelf is OK.
tag is OK.
TestBlockViewController dealloc.
This post is licensed under CC BY 4.0 by the author.

iOS - About ARC

iOS - 一些常用的宏

Trending Tags