iOS中load和initialize

首发于我的个人博客

+load方法

创建类和分类

  • 先创建类YZPerson类,然后创建它的两个分类

YZPerson.m类

1
2
3
4
5
6
7
8
9
10
#import "YZPerson.h"

@implementation YZPerson
+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end

YZPerson+test1.m类

1
2
3
4
5
6
7
8
9
10
11
#import "YZPerson+test1.h"

@implementation YZPerson (test1)

+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end

YZPerson+test2.m类

1
2
3
4
5
6
7
8
9
10
11
#import "YZPerson+test2.h"

@implementation YZPerson (test2)

+(void)run{
NSLog(@"%s",__func__);
}
+(void)load{
NSLog(@"%s",__func__);
}
@end

创建完之后,这几个类不主动调用,直接启动

  • 打印结果
1
2
3
CateogryDemo[29670:414343] +[YZPerson load]
CateogryDemo[29670:414343] +[YZPerson(test1) load]
CateogryDemo[29670:414343] +[YZPerson(test2) load]

这说明了。load方法,根本不需要我们自己调用,编译完成之后,就会调用。

疑问

但是有个疑问,因为,原来的类和分类中都写了load方法,为啥都调用呢?为什么不是只调用分类中的呢?

查看元类中的方法

利用runtime写个打印方法

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
void printMethodNamesOfClass(Class cls)
{
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);

// 存储方法名
NSMutableString *methodNames = [NSMutableString string];

// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}

// 释放
free(methodList);

// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}

调用,因为是要查看类方法,所以需要打印元类中的方法

1
2
3
4
- (void)viewDidLoad {
[super viewDidLoad];
printMethodNamesOfClass(object_getClass([YZPerson class]));
}

结果为

1
2
3
4
CateogryDemo[30112:420944] +[YZPerson load]
CateogryDemo[30112:420944] +[YZPerson(test1) load]
CateogryDemo[30112:420944] +[YZPerson(test2) load]
CateogryDemo[30112:420944] YZPerson load, run, load, run, load, run,

小结

看得出来,有三个load方法,三个run方法

也进一步验证了,前面查看源码分析的结论:合并分类的时候,其方法列表等,不会覆盖掉原来类中的方法,是共存的。

源码分析

同上,先找到初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

查看 load_images 方法,这个是加载镜像,模块的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 加载镜像,模块的方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}

查看加载load的代码

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
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;

loadMethodLock.assertLocked();

// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;

void *pool = objc_autoreleasePoolPush();

do {
// 1. Repeatedly call class +loads until there aren't any more
// 1. 调用类的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}

// 2. Call category +loads ONCE
// 2. 调用分类的load方法
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);

objc_autoreleasePoolPop(pool);

loading = NO;
}

可以看出,是先调用类的load方法,再调用分类的load方法

继续跟代码

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
static void call_class_loads(void)
{
int i;

// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;

// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// 关键代码
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;

if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}

// Destroy the detached list.
if (classes) free(classes);
}

跟着上面注释中的关键代码,继续看源码

1
typedef void(*load_method_t)(id, SEL);

发现是一个指向函数地址的指针

小结

  • 那么,答案就很清晰了。+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用。

有分类和子类的情况

当有分类,也有子类,并且都有load方法 的情况下

例如YZStudent是 YZPerson 的子类
YZStudent+test1 是 YZStudent的分类
调用结果为

1
2
3
4
5
CateogryDemo[31904:444099] +[YZPerson load]
CateogryDemo[31904:444099] +[YZStudent load]
CateogryDemo[31904:444099] +[YZStudent(test1) load]
CateogryDemo[31904:444099] +[YZPerson(test1) load]
CateogryDemo[31904:444099] +[YZPerson(test2) load]

源码分析

接着之前的源码分析,继续查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 加载镜像,模块的方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
// prepare 准备工作
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 定制,规划
schedule_class_load(remapClass(classlist[i]));
}

category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize

if (cls->data()->flags & RW_LOADED) return;

// Ensure superclass-first ordering
// 递归调用,传入父类
schedule_class_load(cls->superclass);
// 将cls 添加到 loadable_classes数组最后面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}

可以看到,load方法,是递归调用,传入父类。依次加入数组中,那么调用的时候,先调用父类的load,再调用子类的load,而且和编译顺序无关。

总结

+load方法会在runtime加载类、分类时调用

每个类、分类的+load,在程序运行过程中只调用一次
load的调用顺序

  1. 先调用类的load

    • 先编译的类,优先调用load
    • 调用子类的load之前,会先调用父类的load
  2. 再调用分类的load

    • 先编译的分类,优先调用load

+initialize方法

+initialize方法会在类第一次接收到消息时调用

调用顺序

先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)

源码解读

runtime源码
objc4源码解读过程

1
2
3
4
5
6
7
8
9
10
>1. objc-msg-arm64.s
- objc_msgSend

>2. objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)

具体来看代码

1
2
3
4
5
6
7
8
9
10

Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);


return _class_getMethod(cls, sel);
}

继续查看 lookUpImpOrNil

1
2
3
4
5
6
7
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}

lookUpImpOrForward 代码比较长,摘取关键代码如下

1
2
3
4
5
6
7
8
9
10
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
bool initialize, bool cache, bool resolver)
{
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();

}
}

继续查看关键代码

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
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());

Class supercls;
bool reallyInitialize = NO;

if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}

// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}

.....


callInitialize(cls);

}

这里可以看出,如果一个类,其父类没有初始化,就递归调用该方法进行初始化。最终调用callInitialize进行初始化

1
2
3
4
5
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}

最终,调用到objc_msgSend 方法,那我们继续看objc_msgSend源码,发现是汇编代码
截取部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
#endif

ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame

cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL

这也说明了,objc_msgSend性能高的原因,是因为直接操作汇编。

上述流程,用伪代码表示就是如下,其中
YZStudent 继承自 YZPerson

1
2
3
4
5
6
if (YZStudent没有初始化) {
if(YZPerson没有初始化){
objc_msgSend([YZPerson class],@selector(initialize));
}
objc_msgSend([YZStudent class],@selector(initialize));
}

注意点

+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点

  • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
  • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

对比 +load 和 + initialize 的总结

load、initialize方法的区别什么?

  1. 调用方式

    • load是根据函数地址直接调用
    • initialize是通过objc_msgSend调用
  2. 调用时刻

    • load是runtime加载类、分类的时候调用(只会调用1次)
    • initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
  3. load、initialize的调用顺序?

    1. load

      1. 先调用类的load

        • 先编译的类,优先调用load
        • 调用子类的load之前,会先调用父类的load
      2. 再调用分类的load

        • 先编译的分类,优先调用load
    2. initialize

      1. 先初始化父类
      2. 再初始化子类(可能最终调用的是父类的initialize方法)

本文相关代码github地址 github

本文参考资料:

runtime源码

iOS底层原理

bloc访问OC对象

代码分析

当block内部访问外面的OC对象的时候

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义block
typedef void (^YZBlock)(void);

int main(int argc, const char * argv[]) {
@autoreleasepool {

NSObject *obj = [[NSObject alloc]init];
YZBlock block = ^{
NSLog(@"%p",obj);
};
block();
}
return 0;
}

在终端使用clang转换OC为C++代码

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

因为是在ARC下,所以会copy,栈上拷贝到堆上,结构体__main_block_desc_0中有copydispose

1
2
3
4
5
6
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*);
}

copy会调用 __main_block_copy_0

1
2
3
static void __main_block_copy_0(struct __main_block_impl_0*dst, 
struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj,
(void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

其内部的_Block_object_assign会根据代码中的修饰符 strong或者weak而对其进行强引用或者弱引用。

查看__main_block_impl_0

1
2
3
4
5
6
7
8
9
10
11
12
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//strong 强引用
NSObject *__strong obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

可以看上修饰符是strong,所以,调用_Block_object_assign时候,会对其进行强引用。

前面深入理解block(三) __block可知

  • 当block在栈上时,并不会对__block变量产生强引用

  • 当block被copy到堆时

    • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会对__block变量形成强引用(retain)
  • 当block从堆中移除时

    • 会调用block内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放引用的__block变量(release)

拷贝

拷贝的时候,

  • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会对__block变量形成强引用(retain)

在上一节 深入理解block(三) __block 中我们知道,如下代码

1
2
3
4
5
__block int age = 10;
YZBlock block = ^{
age = 20;
NSLog(@"block内部修改之后age = %d",age);
};

局部变量age是在栈上的,在block内部引用age,但是当block从栈上拷贝到堆上的时候,怎么能保证下次block访问age的时候,能访问到呢?因为我们知道栈上的局部变量,随时会销毁的。

假设现在有两个栈上的block,分别是block0和block1,同时引用了了栈上的__block变量。现在对block0进行copy操作,我们知道,栈上的block进行copy,就会复制到堆上,也就是说block0会复制到堆上,因为block0持有__block变量,所以也会把这个__block变量复制到堆上,同时堆上的block0对堆上的__block变量是强引用,这样能达到block0随时能访问__block变量

还是上面的例子,刚才block0拷贝到堆上了,现在如果block1也拷贝到堆上,因为刚才变量已经拷贝到堆上,就不需要再次拷贝,只需要把堆上的block1也强引用堆上的变量就可以了。

释放

当释放的时候

  • 会调用block内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放引用的__block变量(release)

上面的代码中,如果在堆上只有一个block引用__block变量,当block销毁时候,直接销毁堆上的__block变量,但是如果有两个block引用__block变量,就需要当两个block都废弃的时候,才会废弃__block变量

其实,说到底,就是谁使用,谁负责

对象类型的auto变量__block变量

把前面的都放在一起整理一下,有 auto 变量 num , __block变量int, obj 和weakObj2如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 __block int age = 10;
int num = 8;
NSObject *obj = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
__weak NSObject *weakObj2 = obj2;
YZBlock block = ^{
NSLog(@"age = %d",age);
NSLog(@"num = %d",num);
NSLog(@"obj = %p",obj);
NSLog(@"weakObj2 = %p",weakObj2);
NSLog(@"block内部修改之后age = %d",age);
};

block();

执行终端指令

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

生成代码如下所示

被__block修饰的对象类型

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

  • __block变量被copy到堆时

    • 会调用__block变量内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会根据所指向对象的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
  • 如果__block变量从堆上移除

    • 会调用__block变量内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放指向的对象(release)

__block__forwarding指针

1
2
3
4
5
6
7
8
9
10
11
12
13
//结构体__Block_byref_obj_0中有__forwarding
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong obj;
};

// 访问的时候
age->__forwarding->age

为啥什么不直接用age,而是age->__forwarding->age呢?

这是因为,如果__block变量在栈上,就可以直接访问,但是如果已经拷贝到了堆上,访问的时候,还去访问栈上的,就会出问题,所以,先根据__forwarding找到堆上的地址,然后再取值

总结

  • 当block在栈上时,对它们都不会产生强引用

  • 当block拷贝到堆上时,都会通过copy函数来处理它们

    • __block变量(假设变量名叫做a)
  • _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • 当block从堆上移除时,都会通过dispose函数来释放它们
    __block变量(假设变量名叫做a)
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

循环引用问题

前面已经认识了block,这一篇探索一下block的循环引用问题。
看如下代码,有个Person类,里面两个属性,分别是block和age

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>

typedef void (^YZBlock) (void);

@interface YZPerson : NSObject
@property (copy, nonatomic) YZBlock block;
@property (assign, nonatomic) int age;
@end


#import "YZPerson.h"

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

main.m中如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, const char * argv[]) {
@autoreleasepool {

YZPerson *person = [[YZPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person.age--- %d",person.age);
};
NSLog(@"--------");

}
return 0;
}

输出只有

iOS-block[38362:358749] ——–

也就是说程序结束,person都没有释放,造成了内存泄漏。

循环引用原因

下面这行代码,是有个person指针,指向了YZPerson对象

1
YZPerson *person = [[YZPerson alloc] init];

执行完

1
2
3
person.block = ^{
NSLog(@"person.age--- %d",person.age);
};

之后,block内部有个强指针指向person,下面代码生成cpp文件

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

1
2
3
4
5
6
7
8
9
10
11
12
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//强指针指向person
YZPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

而block是person的属性

1
@property (copy, nonatomic) YZBlock block;

当程序退出的时候,局部变量person销毁,但是由于MJPerson和block直接,互相强引用,谁都释放不了。

__weak解决循环引用

为了解决上面的问题,只需要用__weak来修饰,即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZPerson *person = [[YZPerson alloc] init];
person.age = 10;

__weak YZPerson *weakPerson = person;

person.block = ^{
NSLog(@"person.age--- %d",weakPerson.age);
};
NSLog(@"--------");

}
return 0;
}

编译完成之后是

1
2
3
4
5
6
7
8
9
10
11
12
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// block内部对weakPerson是弱引用
YZPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

当局部变量消失时候,对于YZPseson来说,只有一个若指针指向它,那它就销毁,然后block也销毁。

__unsafe_unretained解决循环引用

除了上面的__weak之后,也可以用__unsafe_unretained来解决循环引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZPerson *person = [[YZPerson alloc] init];
person.age = 10;

__unsafe_unretained YZPerson *weakPerson = person;

person.block = ^{
NSLog(@"person.age--- %d",weakPerson.age);
};
NSLog(@"--------");

}
return 0;
}

对于的cpp文件为

1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
YZPerson *__unsafe_unretained weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__unsafe_unretained _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

虽然__unsafe_unretained可以解决循环引用,但是最好不要用,因为

  • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
  • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

__block解决循环引用

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block YZPerson *person = [[YZPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person.age--- %d",person.age);
//这一句不能少
person = nil;
};
// 必须调用一次
person.block();
NSLog(@"--------");
}
return 0;
}

上面的代码中,也是可以解决循环引用的。但是需要注意的是,person.block();必须调用一次,为了执行person = nil;.

对应的结果如下

  • 下面的代码,block会对__block产生强引用
1
2
3
4
5
6
__block YZPerson *person = [[YZPerson alloc] init];
person.block = ^{
NSLog(@"person.age--- %d",person.age);
//这一句不能少
person = nil;
};
  • person对象本身就对block是强引用
1
@property (copy, nonatomic) YZBlock block;
  • __block对person产生强引用
1
2
3
4
5
6
7
8
9
10
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
//`__block`对person产生强引用
YZPerson *__strong person;
};

所以他们的引用关系如图

当执行完person = nil时候,__block解除对person的引用,进而,全都解除释放了。
但是必须调用person = nil才可以,否则,不能解除循环引用

小结

通过前面的分析,我们知道,ARC下,上面三种方式对比,最好的是__weak

MRC下注意点

如果再MRC下,因为不支持弱指针__weak,所以,只能是__unsafe_unretained或者__block来解决循环引用

------ 本文结束感谢您的阅读 ------