我是靠谱客的博主 幽默斑马,最近开发中收集的这篇文章主要介绍oc 协议 回调 静态成员_Blogs/Objectvie-C学习笔记1-KVO:KVC:Category:关联对象:Block.md at master · BrooksWon/Blogs · Gi...,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

[TOC]

Objectvie-C学习笔记1-KVO/KVC/Category/关联对象/Block

准备工作:

①Core Foundation源码

②Runtime源码

③GNUstep源码

GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍。虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值。

面向对象

Objectvie-C的本质

我们平时编写的Objective-C代码,底层实现其实都是CC++代码。如下图:

所以Objective-C的面向对象都是基于CC++的数据结构实现的,Objective-C的对象、类主要是基于CC++的结构体实现的。

我们可以通过命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件将Objective-C代码转换为CC++代码;如果需要链接其他框架,使用 -framework 参数。比如 -framework UIKit 。

Objectvie-C对象的本质

NSObject的底层实现。

系统Foundation框架对NSObject的定义如下:

@interface NSObject {

Class isa;

}

@end

通过定义可以看出:NSObject对象中只有1个成员变量isa。

然后、我们通过重写OC代码为cpp可以看看出NSObject的C/C++实现如下:

struct NSObject_IMPL {

Class isa;

};

因此可以得出结论:NSObject 对象是基于 C/C++的结构体struct实现的,且只有1个成员变量isa。

如果执行下面代码:

NSObject *obj = [[NSObject alloc] init];

那么、obj里面存储的显然是NSObject_IMPL结构体的内存地址,也就是isa的内存地址。下图可证明该结论。

下面继续来看一下存在继承的情况:Person 继承自 NSObject。代码如下:

@interface Person : NSObject {

@public

NSInteger _no;

NSInteger _age;

}

@end

通过clang rewrite之后Person的结构如下:

struct Person_IMPL {

struct NSObject_IMPL NSObject_IVARS;

NSInteger _no;

NSInteger _age;

};

下面使用Person,代码如下:

Person *p = [[Person alloc] init];

p->_no = 4;

p->_age = 5;

struct Person_IMPL *p2 = (__bridge struct Person_IMPL *)p;

可以将结构体Person_IMPL的成员值打印一下。如下图:

或者、通过VIew Memory窥探一下内存状态。如下图:

总结:通过结构体 Person_IMPL 打印 或者 内存窥探,可以看出:Person 的实例对象p中存储着成员变量 isa、_no、_age的值 0x102016600、4、5;至于isa的值指向哪里?后面会学到(指向本类的类对象)。即类的实例对象的成员变量的值在类的实例对象中存储。

1个对象占用多少内存空间呢?

可以使用如下2个函数查看某个实例对象的内存:

创建一个实例对象,至少需要多少内存?

#import

class_getInstanceSize([NSObject class]);

创建一个实例对象,实际上分配了多少内存?

#import

malloc_size((__bridge const void *)obj);

举例:Student继承Person,Person继承NSObject。clang rewrite 之后关系图如下:

从上图可以分析出:(内存分配是16的整数倍)

NSObject对象实际占用内存:8个字节;内存分配16个字节。

Person对象实际占用内存:8+8 = 16 个字节;内存分配16个字节。

Person对象实际占用内存:8+8+8 = 24 个字节;内存分配32个字节。

通过下面测试代码可以验证结论:

NSObject *o = [[NSObject alloc] init];

Person *p = [[Person alloc] init];

Student *s = [[Student alloc] init];

NSLog(@"%zu", class_getInstanceSize([o class])); //8

NSLog(@"%zu", class_getInstanceSize([p class])); //16

NSLog(@"%zu", class_getInstanceSize([s class])); //24

NSLog(@"%zu", malloc_size((__bridge const void *)(o)));//16

NSLog(@"%zu", malloc_size((__bridge const void *)(p)));//16

NSLog(@"%zu", malloc_size((__bridge const void *)(s)));//34

或者、使用 View Memory 实时查看内存数据。这里不再演示了。

OC对象的种类

Objective-C中的对象,简称OC对象,主要可以分为3种

instance对象(实例对象)。

class对象(类对象)。

meta-class对象(元类对象) 。

instance 对象

instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

如下示例;

NSObject *obj1 = [[NSObject alloc] init];

NSObject *obj2 = [[NSObject alloc] init];

obj1、obj2是NSObject的instance对象(实例对象)。

它们是不同的2个对象,分别占据着2块不同的内存。

instance对象在内存中存储的信息包括:

isa指针;

其他成员变量。

如下示例:

@interface Person : NSObject {

@public

NSInteger _age;

}

@end

Person *p1 = [[Person alloc] init];

p1->_age = 3;

Person *p2 = [[Person alloc] init];

p2->_age = 4;

上面代码对应的内存结构,如下图所示:

class 对象

如下示例:

NSObject *obj1 = [[NSObject alloc] init];

NSObject *obj2 = [[NSObject alloc] init];

Class objectClass1 = [obj1 class];

Class objectClass2 = [obj2 class];

Class objectClass3 = [NSObject class];

Class objectClass4 = object_getClass(obj1);

Class objectClass5 = object_getClass(obj2);

NSLog(@"%p %p %p %p %p",

objectClass1,

objectClass2,

objectClass3,

objectClass4,

objectClass5);

//0x7fff91851140 0x7fff91851140 0x7fff91851140 0x7fff91851140 0x7fff91851140

objectClass1 ~ objectClass5都是NSObject的class对象(类对象)。

它们是同一个对象。每个类在内存中有且只有1个class对象。

下图可证明:

从图中可以看出,objectClass1 ~ objectClass5的指针都指向同一块内存空间 0x00007fff91851140。这块内存空间存储的就是NSObject的类对象。

class对象在内存中存储的信息主要包括:

isa指针

superclass指针

类的属性信息(@property)、

类的对象方法信息(instance method)

类的协议信息(protocol)

类的成员变量信息(ivar)

......

如下图所示:

meta-class 对象

如下示例:

Class objectMetaClass1 = object_getClass([NSObject class]);

Class objectMetaClass2 = object_getClass([NSObject class]);

NSLog(@"%p %p", objectMetaClass1, objectMetaClass2);

//0x7fff918510f0 0x7fff918510f0

objectMetaClass是NSObject的meta-class对象(元类对象)。

每个类在内存中有且只有一个meta-class对象。

meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括 :

isa指针

superclass指针

类的类方法信息(class method)

......

如下图所示:

注意

以下代码获取的objectClass是class对象,并不是meta-class对象。

Class objecClass = [[NSObject class] class];

查看是否为meta-class.

BOOL result1 = class_isMetaClass([NSObject class]);

BOOL result2 = class_isMetaClass(object_getClass([NSObject class]));

NSLog(@"%@ %@", @(result1), @(result2));

//0 1

isa和superClass

isa指针

直接看下图:

instance的isa指向class。

当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用。

class的isa指向meta-class。

调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用。

class对象的superclass指针

有如下2个类:

@interface Person: NSObject

@interface Student: Person

当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用。

meta-class对象的superclass指针

有如下2个类:

@interface Person: NSObject

@interface Student: Person

当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用。

isa、superclass总结

直接看下图:

isa总结:

instance的isa指向class。

class的isa指向meta-class

meta-class的isa指向基类的meta-class。

superclass总结:

class的superclass指向父类的class 。

如果没有父类,superclass指针为nil。

meta-class的superclass指向父类的meta-class。

基类的meta-class的superclass指向基类的class。

方法调用轨迹:(方法缓存部分、动态方法解析和消息转发机制后面再说~)

instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类。

class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类。

Class的本质

isa指针

从64bit开始,isa需要进行一次位运算,才能计算出真实地址。

(位运算使用到的 ISA_MASK 可以在objc源码的objc-private.h文件中找到,如下:)

# if __arm64__

# define ISA_MASK 0x0000000ffffffff8ULL

# elif __x86_64__

# define ISA_MASK 0x00007ffffffffff8ULL

# endif

如下图:

class、meta-class对象的本质结构都是struct objc_class

如下图:

窥探struct objc_class的结构

可以在objc源码的objc-runtinme-new.h文件中梳理出 struct objc_class 结构图。如下所示:

后面会继续深入源码分析一下isa、class_rw_t、method_list_t、method_t、class_ro_t等结构~。

KVO

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。

KVO的基本使用

如下示例:

#import "Person.h"

@interface ViewController ()

@property (strong, nonatomic) Person *person1;

@property (strong, nonatomic) Person *person2;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

self.person1 = [[Person alloc] init];

self.person1.age = 1;

self.person2 = [[Person alloc] init];

self.person2.age = 2;

// 给person1对象添加KVO监听

NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;

[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

self.person1.age = 20;

self.person2.age = 20;

}

- (void)dealloc {

[self.person1 removeObserver:self forKeyPath:@"age"];

}

// 当监听对象的属性值发生改变时,就会调用

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{

NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);

}

@end

示例解释:

给Person1对象通过方法:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

添加KVO监听。当通过person1的setAge方法赋值时,会触发

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;

方法。

流程如下图所示:

KVO的本质

上述的示例中,person2未使用KVO,当person2调用setAge时:通过person2的isa找到Person的class对象,然后找到class对象中的setAge方法完成调用;当person2调用age的getter方法时也是如此。如下图所示:

上述的示例中,person1使用了KVO监听自己age属性的变化:因此、系统会利用Runtime API动态生成一个Person的子类NSKVONotifying_Person,并且让person1的isa指向这个全新的子类。该类NSKVONotifying_Person 重写了父类Person的setAge方法、具体实现伪代码如下:

@implementation NSKVONotifying_Person

- (void)setAge:(int)age

{

_NSSetIntValueAndNotify();

}

// 伪代码

void _NSSetIntValueAndNotify()

{

[self willChangeValueForKey:@"age"];

[super setAge:age];

[self didChangeValueForKey:@"age"];

}

- (void)didChangeValueForKey:(NSString *)key

{

// 通知监听器,某某属性值发生了改变

[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];

}

@end

上述的示例中,当person1调用setAge时:通过person1的isa找到NSKVONotifying_Person的class对象(使用了KVO之后,person1的isa已经指向NSKVONotifying_Person的类对象了、而不是之前的Person的类对象),然后找到class对象中的setAge方法(该方法会调用Foundation的_NSSetIntValueAndNotify函数,从而出发KVO回调函数)、完成调用。如下图所示:

备注:NSKVONotifying_Person 也会重写父类的一些其他方法做些必要的事情。比如: 重写class方法,用来隐藏KVO生成子类这件事情,重写dealloc方法、做一些资源释放相关的事情。等等。

总结

KVO的本质就是:利用Runtime API动态生成一个子类,并且让instance对象的isa指向这个全新的子类。当修改instance对象的属性时,会调用Foundation的**_NSSet*ValueAndNotify**函数。该函数内部做了如下几件事儿:

调用 willChangeValueForKey;

调用父类原来的setter;

调用 willChangeValueForKey、该方法内部会触发监听器(Observer)的监听方法(observeValueForKeyPath: ofObject: change: context:)。

备注:当然、也可以手动调用 willChangeValueForKey: 和 willChangeValueForKey:出发KVO。

KVC

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。

常见的API有:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

- (void)setValue:(id)value forKey:(NSString *)key;

- (id)valueForKeyPath:(NSString *)keyPath;

- (id)valueForKey:(NSString *)key;

KVC的基本使用

如下示例:

@interface Cat : NSObject

@property (assign, nonatomic) int weight;

@end

@interface Person : NSObject

@property (assign, nonatomic) int age;

@property (assign, nonatomic) Cat *cat;

@end

//示例代码

Person *person = [[Person alloc] init];

// 通过KVC修改属性

[person setValue:@10 forKey:@"age"];

[person setValue:@10 forKeyPath:@"cat.weight"];

//通过KVC访问属性

NSLog(@"%@", [person valueForKey:@"age"]);

NSLog(@"%@", [person valueForKeyPath:@"cat.weight"]);

基本使用很简单,没什么好说的,继续研究下KVC的设值原理~

KVC的设值原理

当使用KVC调用setValue: forKey:方法设值时,实际调用流程如下图:

注意:图中 accessInstanceVariablesDirectly 是NSObject中的方法,默认返回YES。意思是:是否允许直接访问没有getter、setter方法的成员变量。

//NSOBject.h

@property(class, readonly) BOOL accessInstanceVariablesDirectly;//The default returns YES.

注意⚠️:通过KVC设值是可以出发KVO监听方法的。从上图可以看出:确实会调用对应的setter方法。另外:即使KVC是通过上图中直接访问成员变量赋值的、也会触发KVO的监听方法(可以自己代码验证下);这也反证了:即使KVC是通过上图中直接访问成员变量赋值时调用了 willChangeValueForKey: 和 willChangeValueForKey:。

KVC的取值原理

当使用KVC调用valueForKey:方法取值时,实际调用流程如下图:

Categoty

Category的基本使用

没啥好说的~

Category的底层结构

Category的底层结构是struct category_t, 定义在objc-runtime-new.h中。如下:

struct category_t {

const char *name;

classref_t cls;

struct method_list_t *instanceMethods;

struct method_list_t *classMethods;

struct protocol_list_t *protocols;

struct property_list_t *instanceProperties;

// Fields below this point are not always present on disk.

struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {

if (isMeta) return classMethods;

else return instanceMethods;

}

property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

};

Category的加载处理过程

通过Runtime加载某个类的所有Category数据。

把所有Category的方法、属性、协议数据,合并到一个大数组中;后面参与编译的Category数据,会在数组的前面。

将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。

下面通过源码来分析下~

在objc-os.mm中找到_objc_init方法,如下:

void _objc_init(void)

{

...省略...

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

}

然后通过map_images进入

void map_images(unsigned count, const char * const paths[],

const struct mach_header * const mhdrs[])

{

...省略...

return map_images_nolock(count, paths, mhdrs);

}

然后通过map_images_nolock进入

void map_images_nolock(unsigned mhCount, const char * const mhPaths[],

const struct mach_header * const mhdrs[])

{

...省略...

_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);

...省略...

}

然后通过_read_images进入

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)

{

...省略...

// Discover categories.

for (EACH_HEADER) {

//拿到类的所有的category,category对应的结构是category_t。

//数组中存储的结构类似 [category_t, category_t]

category_t **catlist =

_getObjc2CategoryList(hi, &count);

//遍历上面拿到的category_t数组

for (i = 0; i < count; i++) {

//拿到某个category_t

category_t *cat = catlist[i];

//拿到这个category_t对应的class

Class cls = remapClass(cat->cls);

...省略...

//拿到类别的方法列表/协议列表/属性列表

if (cat->instanceMethods || cat->protocols

|| cat->instanceProperties)

{

addUnattachedCategoryForClass(cat, cls, hi);

if (cls->isRealized()) {

//重新组织类对象

remethodizeClass(cls);

classExists = YES;

}

...省略...

}

//拿到类别的方法列表/协议列表/属性列表

if (cat->classMethods || cat->protocols

|| (hasClassProperties && cat->_classProperties))

{

addUnattachedCategoryForClass(cat, cls->ISA(), hi);

if (cls->ISA()->isRealized()) {

//重新组织元类对象

remethodizeClass(cls->ISA());

}

...省略...

}

}

}

...省略...

}

然后通过remethodizeClass进入

static void remethodizeClass(Class cls)

{

category_list *cats;

...省略...

attachCategories(cls, cats, true /*flush caches*/);

...省略...

}

然后通过attachCategories进入

//参数cls表示: 类对象或者元类对象

//参数cats表示:分类列表,如:[category_t, category_t]

static void attachCategories(Class cls, category_list *cats, bool flush_caches)

{

if (!cats) return;

if (PrintReplacedMethods) printReplacements(cls, cats);

bool isMeta = cls->isMetaClass();

//方法数组、是2维数组,如下:

/*

[

[method_t, method_t],

[method_t, method_t]

]

*/

method_list_t **mlists = (method_list_t **)

malloc(cats->count * sizeof(*mlists));

//属性数组、是2维数组,如下:

/*

[

[property_t, property_t],

[property_t, property_t]

]

*/

property_list_t **proplists = (property_list_t **)

malloc(cats->count * sizeof(*proplists));

//协议数组、是2维数组,如下:

/*

[

[protocol_t, protocol_t],

[protocol_t, protocol_t]

]

*/

protocol_list_t **protolists = (protocol_list_t **)

malloc(cats->count * sizeof(*protolists));

// Count backwards through cats to get newest categories first

int mcount = 0;

int propcount = 0;

int protocount = 0;

int i = cats->count;

bool fromBundle = NO;

while (i--) {//递减循环

//取出某个分类category_t

auto& entry = cats->list[i];//因为外层是递减循环,所以此处是从后往前取出元素

//取出分类中的实例方法列表(如果是元类对象、则取出分类中的类方法列表)

method_list_t *mlist = entry.cat->methodsForMeta(isMeta);

if (mlist) {

//将方法列表添加进上面malloc的2维数组中。

mlists[mcount++] = mlist;//从前往后添加。

fromBundle |= entry.hi->isBundle();

}

//取出分类中的属性列表(如果是元类对象、则取出分类中的属性列表)

property_list_t *proplist =

entry.cat->propertiesForMeta(isMeta, entry.hi);

if (proplist) {

proplists[propcount++] = proplist;

}

//取出分类中的协议列表(如果是元类对象、则取出分类中的协议列表)

protocol_list_t *protolist = entry.cat->protocols;

if (protolist) {

protolists[protocount++] = protolist;

}

}

//取出类对象的class_rw_t,其中存储着类对象的实例方法列表、协议列表、属性列表等信息。(或者 取出元类类对象的class_rw_t,其中存储着元类对象的类方法列表、协议列表、属性列表等信息。)

auto rw = cls->data();

prepareMethodLists(cls, mlists, mcount, NO, fromBundle);

//将所有分类的对象方法附加到类对象的对象方法列表中。(或者 将所有分类的类方法附加到元类对象的类方法列表中)

rw->methods.attachLists(mlists, mcount);

free(mlists);

if (flush_caches && mcount > 0) flushCaches(cls);

//取出属性列表

rw->properties.attachLists(proplists, propcount);

free(proplists);

//取出协议列表

rw->protocols.attachLists(protolists, protocount);

free(protolists);

}

然后通过attachLists进入

//参数addedLists表示调用者传进来的2维数组:方法数组、协议数组 或者属性数组。如下结构:

/*

[

[method_t, method_t],

[method_t, method_t]

]

*/

//参数addedCount表示e2维数组的长度。

void attachLists(List* const * addedLists, uint32_t addedCount) {

if (addedCount == 0) return;

if (hasArray()) {

// many lists -> many lists

//oldCount:原来方法列表的长度

uint32_t oldCount = array()->count;

//newCount:现在需要的长度

uint32_t newCount = oldCount + addedCount;

setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));

array()->count = newCount;

//array()->lists:原来的方法列表

//将原来的方法列表向后挪动到【array()->lists + addedCount】这个地址

memmove(array()->lists + addedCount,

array()->lists,

oldCount * sizeof(array()->lists[0]));

//addedLists:所有分类的方法列表

//将所有分类的方法列表拷贝到【array()->lists】这个地址

memcpy(array()->lists, addedLists,

addedCount * sizeof(array()->lists[0]));

/* 显然这顿操作之后:

分类的方法列表合并到类的方法列表合中了、且分类的方法列表位于类的方法列表前面。所以当分类定义了与类相同的方法时,会出现分类覆盖类的方法的效果;实际上并为覆盖父类的方法、只不过在方法寻址的过程中,在方法列表中先找到的是分类的方法就返回了而已。

*/

}

...省略...

};

+load方法

+load方法会在runtime加载类、分类时调用。每个类、分类的+load,在程序运行过程中只调用一次。

调用顺序如下:

先调用类的+load。

按照编译先后顺序调用(先编译,先调用 )。

调用子类的+load之前会先调用父类的+load。

再调用分类的+load。

按照编译先后顺序调用(先编译,先调用)。

下面通过源码来分析下~

进入load_images

void load_images(const char *path __unused, const struct mach_header *mh)

{

...省略...

//为待调用的load方法做一些准备工作

prepare_load_methods((const headerType *)mh);

// 调用load方法

call_load_methods();

}

先说prepare_load_methods里面干了些什么❓

进入prepare_load_methods

void prepare_load_methods(const headerType *mhdr)

{

//把所有类的load方法安排一下

schedule_class_load(remapClass(classlist[i]));

//把所有类的所有类别的load方法添加到loadable_categories中 ⭐️提醒: 【分析完类的load方法调用过程,再来分析这个】

add_category_to_loadable_list(cat);

}

然后进入schedule_class_load

//将load方法递归添加到loadable_classes中,先添加父类的load方法、再添加子类的load方法

static void schedule_class_load(Class cls)

{

//当类对象为nil时,退出递归、清栈

if (!cls) return;

...省略...

//递归调用父类

schedule_class_load(cls->superclass);

//将load方法递归添加到loadable_classes中

add_class_to_loadable_list(cls);

...省略...

}

然后进入add_class_to_loadable_list

void add_class_to_loadable_list(Class cls)

{

IMP method;

loadMethodLock.assertLocked();

method = cls->getLoadMethod();

if (!method) return; // Don't bother if cls has no +load method

//loadable_classes中存储的是loadable_class,struct loadable_class的定义如下:

/*

struct loadable_class {

Class cls; // may be nil

IMP method;

};

*/

if (loadable_classes_used == loadable_classes_allocated) {

loadable_classes_allocated = loadable_classes_allocated*2 + 16;

loadable_classes = (struct loadable_class *)

realloc(loadable_classes,

loadable_classes_allocated *

sizeof(struct loadable_class));

}

//将类对象赋值给loadable_class的cls

loadable_classes[loadable_classes_used].cls = cls;

//将load方法的IMP赋值给loadable_class的method

loadable_classes[loadable_classes_used].method = method;

loadable_classes_used++;

}

再说call_load_methods里面干了些什么❓

进入call_load_methods

void call_load_methods(void)

{

...省略...

do {

while (loadable_classes_used > 0) {

//1.内层循环调用类的load方法

call_class_loads();

}

//2.外层循环调用类别的load方法

more_categories = call_category_loads();//⭐️提醒: 【分析完类的load方法调用过程,再来分析这个】

// 3.直到没有更多的可被调用的类的load方法 或者 没有更多类别可悲调用的类别的load方法,就退出循环。

} while (loadable_classes_used > 0 || more_categories);

...省略...

}

然后先进入调用类的load方法:call_class_loads

static void call_class_loads(void)

{

...省略...

//classes表示数据结构为loadable_class的数组

//loadable_class的数据结构如下:

/*

struct loadable_class {

Class cls; // may be nil

IMP method;

};

cls:类对象(元类是一种特殊的类对象)

method:类对象的load方法的IMP

*/

struct loadable_class *classes = loadable_classes;

//循环调用所有类的load方法

for (i = 0; i < used; i++) {

//拿到类对象

Class cls = classes[i].cls;

//load_method_t的定义是:typedef void(*load_method_t)(id, SEL);

//拿到loadable_class的load方法的函数指针

load_method_t load_method = (load_method_t)classes[i].method;

//重点:直接使用函数地址调用load方法,并不是objc_msgSend方式。

(*load_method)(cls, SEL_load);

}

...省略...

}

第一段结论

此时、类的load方法调用分析完毕。

有几个需要注意的点:

类对象和load方法作为loadable_class这种数据元素是通过递归加载进数组中的:先添加的父类、后添加的子类;所以数组中从前到后存储的是【父->子->孙->...】。

最终调用时是从前到后直接从上述数组中取出loadable_class中的类对象cls和IMP作为函数指针的实参,直接调用的load方法。

总结:

存储:先父类、后子类。

调用:顺序直接使用函数地址调用。

还有标记了【⭐️提醒】的类别的load方法的调用流程没有分析,下面继续:

先进入add_category_to_loadable_list

void add_category_to_loadable_list(Category cat)

{

IMP method;

//取出类别的load方法的IMP

method = _category_getLoadMethod(cat);

// Don't bother if cat has no +load method

if (!method) return;

//loadable_categories中存储的是loadable_category,struct loadable_category的定义如下:

/*

struct loadable_category {

Category cat; // may be nil

IMP method;

};

*/

if (loadable_categories_used == loadable_categories_allocated) {

loadable_categories_allocated = loadable_categories_allocated*2 + 16;

loadable_categories = (struct loadable_category *)

realloc(loadable_categories,

loadable_categories_allocated *

sizeof(struct loadable_category));

}

//将类对象赋值给loadable_category的cat

loadable_categories[loadable_categories_used].cat = cat;

//将load方法的IMP赋值给loadable_category的method

loadable_categories[loadable_categories_used].method = method;

loadable_categories_used++;

}

然后进入call_category_loads

static bool call_category_loads(void)

{

...省略...

struct loadable_category *cats = loadable_categories;

//cats表示数据结构为loadable_category的数组

//loadable_category的数据结构如下:

/*

struct loadable_category {

Category cat; // may be nil

IMP method;

};

cat:类对象(元类是一种特殊的类对象)

method:类对象类别的load方法的IMP

*/

//循环调用所有类别的load方法

for (i = 0; i < used; i++) {

//拿到类别的数据结构category_t

Category cat = cats[i].cat;

//load_method_t的定义是:typedef void(*load_method_t)(id, SEL);

//拿到loadable_category的load方法的函数指针

load_method_t load_method = (load_method_t)cats[i].method;

Class cls;

if (!cat) continue;

//通过类别的数据结构category_t,取出类对象

cls = _category_getClass(cat);

// 如果类对象存在且该类别的load方法从未被调用过,则进入调用

if (cls && cls->isLoadable()) {

//重点:直接使用类别load方法的函数地址调用load方法,并不是objc_msgSend方式。

(*load_method)(cls, SEL_load);

//擦除loadable_category中的category_t

cats[i].cat = nil;

}

}

...省略...

}

第二段结论

此时、类别的load方法调用分析完毕。

有几个需要注意的点:

类别数据结构category_t和类别的load方法作为loadable_category这种数据元素直接加载进数组中的;元素的加载顺序取决于编译顺序:先编译先加载、后编译后加载。

最终调用时是从前到后,从上述数组中取出loadable_category中的cat、然后通过cat取出类对象,和类别的load方法的IMP作为函数指针的实参,直接调用的load方法。

总结:

存储:按照类别的编译顺序直接加载进数组。

调用:顺序直接使用函数地址调用。

+initialize方法

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

调用顺序如下:

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

下面通过源码来分析下~

上面说过、initiallize方法会在类第1次接受到消息的时候调用。那么我们可以猜测:initialize方法的调用可能是走的runtime消息机制。既然是走的消息机制、要调用initialzed方法就需要找到这个方法,该怎么找呢?我们知道runtime api里有个class_getInstanceMethod方法,我们看下能否通过这个方法的源码找看出什么端倪~

在源码objc-runtime-new.mm中通过class_getInstanceMethod找到下面方法

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,

bool initialize, bool cache, bool resolver)

{

...省略...

//如果需要initialize且还未initialize,则进入initialize流程

if (initialize && !cls->isInitialized()) {

//进入initialize流程

_class_initialize (_class_getNonMetaClass(cls, inst));

}

}

然后进入_class_initialize

void _class_initialize(Class cls)

{

...省略...

//取得当前类对象的父类

supercls = cls->superclass;

//如果父类需要initialize且还未initialize,则父类优先进入initialize流程

if (supercls && !supercls->isInitialized()) {

_class_initialize(supercls);

}

...省略...

//当前类对象进入initialize流程

callInitialize(cls);

...省略...

}

然后进入callInitialize

void callInitialize(Class cls)

{

//显然,这里是走的objc_msgSend。刚好验证了我们的猜测:initialize的调用是走的消息机制。

((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

asm("");

}

至此、我们的猜测得到证实。

+initialize和+load的区别

+initialize和+load的区别有以下几处:

调用方式上

load跟是根据函数地址直接调用的。

initialize是通过objc_msgSend调用的,走的是消息机制。

调用时机上

load是runtime加载类、分类的时候调用(只会调用1次)。

initialize是类第1次收到消息的时候调用,每一个类只会调用initialize 1次(父类的initialize方法可能会被调用多次)。

调用顺序上

load方法

先调用类的load。

先编译的类优先调用。

调用子类的load之前,会先调用父类的load。

再调用分类的load。

先编译的分类,优先调用。

initialize方法

先调用初始化父类。

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

关联对象

从上一节Category的底层结构struct category_t可以看出: 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。

但可以通过关联对象来间接实现,关联对象提供了以下API:

//添加关联对象

void objc_setAssociatedObject(id object, const void * key,

id value, objc_AssociationPolicy policy)

//获得关联对象

id objc_getAssociatedObject(id object, const void * key)

//移除所有的关联对象

void objc_removeAssociatedObjects(id object)

key的常见用法

static void *MyKey = &MyKey;

//添加关联对象

objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)

//获得关联对象

objc_getAssociatedObject(obj, MyKey)

static char MyKey;

//添加关联对象

objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)

//获得关联对象

objc_getAssociatedObject(obj, &MyKey)

使用属性名作为key

//添加关联对象

objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

//获得关联对象

objc_getAssociatedObject(obj, @"property");

使用get方法的@selecor作为key

//添加关联对象

objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)

//获得关联对象

objc_getAssociatedObject(obj, @selector(getter))

objc_AssociationPolicy

关联对象的原理

下面通过源码来分析下~

在源码objc-references.mm找到下面方法

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {

//创建一个 ObjcAssociation 对象

ObjcAssociation old_association(0, nil);

//根据内存策略拿到value并赋值给newValue(acquireValue会根据policy,对value进行retain 或者 copy)

id new_value = value ? acquireValue(value, policy) : nil;

{

AssociationsManager manager;

//拿到AssociationsManager的AssociationsHashMap(如果不存在就创建返回、存在则取出)

AssociationsHashMap &associations(manager.associations());

//根据object生成disguised_ptr_t,disguised_ptr_t实际作用为AssociationsHashMap的key

//意思就是:宿主对象object会被当做AssociationsHashMap的key存储东西,那么存储的是什么呢❓继续往下看

disguised_ptr_t disguised_object = DISGUISE(object);

if (new_value) {

//根据上面生成的key(disguised_object),从AssociationsHashMap取出对应的东西,即ObjectAssociationMap

AssociationsHashMap::iterator i = associations.find(disguised_object);

if (i != associations.end()) {

ObjectAssociationMap *refs = i->second;

//根据外部传进来的关联对象的key, 从ObjectAssociationMap取出ObjcAssociation

ObjectAssociationMap::iterator j = refs->find(key);

if (j != refs->end()) {

//从迭代器中取出ObjcAssociation对象赋值给最开始创建的old_association

old_association = j->second;

//创建一个最新的、封装了policy和关联对象值的ObjcAssociation,并赋值给ObjectAssociationMap

j->second = ObjcAssociation(policy, new_value);

} else {

(*refs)[key] = ObjcAssociation(policy, new_value);

}

} else {

// create the new association (first time).

ObjectAssociationMap *refs = new ObjectAssociationMap;

associations[disguised_object] = refs;

(*refs)[key] = ObjcAssociation(policy, new_value);

object->setHasAssociatedObjects();

}

} else {

// 如果传进来的关联对象的value为nil,就移除这个关联对象。

AssociationsHashMap::iterator i = associations.find(disguised_object);

if (i != associations.end()) {

ObjectAssociationMap *refs = i->second;

ObjectAssociationMap::iterator j = refs->find(key);

if (j != refs->end()) {

old_association = j->second;

refs->erase(j);//擦除key对应的ObjectAssociationMap这一条记录

}

}

}

}

// 释放旧的关联对象

if (old_association.hasValue()) ReleaseValue()(old_association);

}

从源码中可以看出,实现关联对象技术的核心类有如下几个:

class AssociationsManager {

static AssociationsHashMap *_map;

};

class AssociationsHashMap : public unordered_map {};

class ObjectAssociationMap : public std::map {};

class ObjcAssociation {

uintptr_t _policy;

id _value;

};

这几个类的关系如下图:

如上图所示:

当调用方法 objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy) 时:

AssociationsManager管理着AssociationsHashMap;

AssociationsHashMap使用宿主对象object作为key( DISGUISE(object) )、存储着ObjectAssociationMap;

ObjectAssociationMap使用关联对象的key(外部传进来的key)存储着ObjcAssociation的实例对象;

ObjcAssociation实例对象中存储着关联对象的policy和value。

总结:

关联对象并不是存储在被关联对象本身内存中。

关联对象存储在全局的统一的一个AssociationsManager中。

设置关联对象为nil,就相当于是移除关联对象。

Block

Block的本质

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

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

block的底层结构如下图所示:

Block变量捕获

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

如下图所示:

下面具体分析一下各种变量类型的捕获~

变量的捕获

先分析下局部auto类型的变量的捕获

通过如下代码示例分析

int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d", age);

};

将上述示例代码clang rewrite之后,block的结构如下:

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

int age;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

通过clang rewrite之后的结构可以看出:block对应的结构是 __main_block_impl_0,而这个结构里又包含了

__block_impl 和 __main_block_desc_0 。这两个成员的结构如下:

struct __block_impl {

void *isa;

int Flags;

int Reserved;

void *FuncPtr;

};

static struct __main_block_desc_0 {

size_t reserved;

size_t Block_size;

}

那么,很明显能看出这3个结构之间的关系,如下:

从上面关系图可以得出:咱们示例代码中定义的block中最终结构包含了:isa、FuncPtr(block被包成函数之后的函数指针)、age(被捕获的auto变量)、Block_size等内容。并且从struct __main_block_impl_0的构造函数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

继续看一下那几句示例代码的对应的clang write生成:

int age = 20;

void (*block)(void) = &__main_block_impl_0(__main_block_func_0,

&__main_block_desc_0_DATA,

//直接将age的值传递进入

age));

可以看出:age直接将值传递给__main_block_impl_0结构体的构造函数,该构造函数将实参的值赋值给了block捕获的变量age。

综上、可得出结论:局部auto变量会被block捕获,且会将变量的值传递给block。

也可以从另外一个角度理解这个结论,因为局部auto变量超出作用域后会被释放,而block有可能会在这个变量被释放后才调用、如果block内部访问了该变量且变量已经被释放了,肯定达不到对变量访问的预期效果了。因此、如果block内部需要访问auto局部变量,则需要捕获该变量、并且该变量的值赋给捕获后的变量的值。

再分析下局部static类型变量的捕获

通过如下代码示例分析

static int age = 20;

void (^block)(void) = ^{

NSLog(@"age is %d", age);

};

其实只是将上个代码示例的 int age变量,变成了局部static变量类型。

通过clang rewrite之后、和上个代码示例生成的结构类似,不同的地方在下面????几处:

block对应的结构

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

//指针

int *age;

//结构体__main_block_impl_0的构造函数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

可以看出:block将外部的static变量捕获为结构体内指针的成员。

示例代码对应的生成

static int age = 20;

void (*block)(void) = &__main_block_impl_0(__main_block_func_0,

&__main_block_desc_0_DATA,

//很明显这里传递的是age的地址

&age));

可以看出:是将age的地址传递给__main_block_impl_0结构体的构造函数,该构造函数将实参的值赋值给了block捕获的变量age。

综上、可得出结论:局部static变量会被block捕获,且会将变量的地址传递给block。

也可以从另外一个角度理解这个结论,因为局部static变量的生命周期是整个程序的生命周期、只是访问收到作用域的限制而已,换句话说就是—只要拿到这个变量的内存地址、就可以访问这个变量存储的内容。因此、如果block内部需要访问static局部变量,只需要在block的内部结构声明一个变量、用来接收外部传进来的地址,就可以在需要的时候对变量进行访问。

再分析下全局变量的捕获

通过对上面两种类型变量的分析可以猜测出:block不会对全局变量进行捕获,而是直接在block内部直接访问全局变量即可。没必要捕获。

下面验证一下猜测,示例代码如下:

int age = 20;

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

void (^block)(void) = ^{

NSLog(@"age is %d", age);

};

return 0;

}

clang rewrite 之后

int age = 20;

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;

}

};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

NSLog(&__NSConstantStringImpl__var_folders_3c_82br3g013d52vzh11684t5cm0000gn_T_main_3889ba_mi_0,

//直接访问成员变量

age);

}

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 argc, const char * argv[]) {

void (*block)(void) = &__main_block_impl_0(__main_block_func_0,

&__main_block_desc_0_DATA));

return 0;

}

结果显示:并未捕获全局变量。和我们猜测一致。

Block的类型

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

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )

__NSStackBlock__ ( _NSConcreteStackBlock )

__NSMallocBlock__ ( _NSConcreteMallocBlock )

这3种类型对应的内存区域是:

这3中类型和环境的关系如下:

每一种类型的block调用copy后的结果如下所示

Block的copy

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

block作为函数返回值时。

将block赋值给__strong指针时。

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

block作为GCD API的方法参数时。

对象类型的auto变量

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

通过示例代码来分析下,代码如下:

typedef void(^Block)(void);

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

Block block;

{

Person *anyone = [[Person alloc] init];

anyone.age = 20;

block = ^{

NSLog(@"anyone age is %@", anyone);

};

}

NSLog(@"_________________");

return 0;

}

从上面的代码可以得出如下结论:

anyone是__strong修饰的auto变量,所以会被block捕获;

block是__strong修饰的auto变量,因此该block会被拷贝到堆上。

那么block捕获了anyone对象后做了些什么呢?继续看clang rewrite后的代码:

typedef void(*Block)(void);

//block类型数据结构

struct __block_impl {

void *isa;

int Flags;

int Reserved;

void *FuncPtr;

};

//【block类型数据结构组合】

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

//对应外部__strong修饰符

Person *__strong anyone;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _anyone, int flags=0) : anyone(_anyone) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

//

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

Person *__strong anyone = __cself->anyone; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_3c_82br3g013d52vzh11684t5cm0000gn_T_main_8f3dd7_mi_0, anyone);

}

//比基本数据类型auto变量生成的代码多出来的函数

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {

/*

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

如果外部变量是__strong修饰的对象类型的auto变量、该函数内则进行强引用;如果外部变量是__weak或者_unsafe_unretained修饰的对象类型的auto变量、该函数内部则进行弱引用。

*/

_Block_object_assign((void*)&dst->anyone, (void*)src->anyone, 3/*BLOCK_FIELD_IS_OBJECT*/);

}

//比基本数据类型auto变量生成的代码多出来的函数

static void __main_block_dispose_0(struct __main_block_impl_0*src) {

/*

_Block_object_dispose函数_Block_object_dispose函数会自动释放引用的auto变量(release)

*/

_Block_object_dispose((void*)src->anyone, 3/*BLOCK_FIELD_IS_OBJECT*/);

}

//【block类型数据结构组合】描述

static struct __main_block_desc_0 {

size_t reserved;

size_t Block_size;

/*

比基本数据类型auto变量生成的代码多出来的函数

很明显2个函数是用来给对象类型的auto变量做内存管理的

*/

//当block被拷贝到堆上时,会调用这个copy函数,最终会调用到_Block_object_assign函数

void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);

//当block从堆上移除时,会调用这个dispose函数,最终会调用到_Block_object_dispose函数

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

//main函数

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

Block block;

{

Person *anyone = objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

objc_msgSend(anyone, sel_registerName("setAge:"), 20);

block = &__main_block_impl_0(__main_block_func_0,

&__main_block_desc_0_DATA,

anyone,

570425344));

}

NSLog(&__NSConstantStringImpl__var_folders_3c_82br3g013d52vzh11684t5cm0000gn_T_main_97327a_mi_1);

return 0;

}

从上面代码的注释可以得出:__strong修饰的对象类型的auto变量被堆上的block强引用了。可以带过下面的打印记过看出来

在断点处并为打印anyone的dealloc方法,说明anyone还有强引用在。其实就是被block强引用了。

如果是下面????的代码,anyone会不会被强引用呢?

typedef void(^Block)(void);

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

Block block;

{

Person *anyone = [[Person alloc] init];

anyone.age = 20;

__weak typeof(anyone) weakAnyone = anyone;

block = ^{

NSLog(@"anyone age is %@", weakAnyone);

};

}

NSLog(@"此时Person应该被释放了");

NSLog(@"_________________");

return 0;

}

结论是:不会被强引用。看下图:

如果又是下面????的代码,anyone会不会被强引用呢?

typedef void(^Block)(void);

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

Block block;

{

Person *anyone = [[Person alloc] init];

anyone.age = 20;

__unsafe_unretained typeof(anyone) unsafeAnyone = anyone;

block = ^{

NSLog(@"anyone age is %@", unsafeAnyone);

};

}

NSLog(@"此时Person应该被释放了");

NSLog(@"_________________");

return 0;

}

结论是:不会被强引用。看下图:

上个几个示例证明了我们最开始的总结:被copy到堆上的block、会根据对象类型auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。

__block修饰符

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

__block不能修饰全局变量、静态变量(static)。

注意:编译器会将**__block变量**包装成一个对象。怎么证明这个呢?

根据如下代码分析一下:

__block int age = 10;

^{

age += 5;

NSLog(@"age is %d", age);

}();

clang rewrite

//编译器会将__block变量生成的对象的结构

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 void __main_block_func_0(struct __main_block_impl_0 *__cself) {

__Block_byref_age_0 *age = __cself->age; // bound by ref

//通过__Block_byref_age_0变量的指针的__forwarding拿到age、并赋值。为什么不直接访问age呢?

(age->__forwarding->age) += 5;

NSLog(&__NSConstantStringImpl__var_folders_3c_82br3g013d52vzh11684t5cm0000gn_T_main_deca2d_mi_0,

//通过__Block_byref_age_0变量的指针的__forwarding取出agec的值。为什么不直接访问age呢?

(age->__forwarding->age));

}

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

//编译器会将__block变量包装成一个对象__Block_byref_age_0

__Block_byref_age_0 age = {

0,

(__Block_byref_age_0 *)&age,

0,

sizeof(__Block_byref_age_0),

10

};

&__main_block_impl_0(__main_block_func_0,

&__main_block_desc_0_DATA,

(__Block_byref_age_0 *)&age,

570425344)();

return 0;

}

上面代码中main函数中的age指针和__Block_byref_age_0结构体的关系图如下:

至此、上面的结论得以证明。(编译器确实会将**__block变量**包装成一个对象)

上面关系图中有个问题是:__Block_byref_age_0 结构体中为什么会搞个指向自己的**__forwarding** 指针呢?以及代码注释中的疑问:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

__Block_byref_age_0 *age = __cself->age; // bound by ref

//通过__Block_byref_age_0变量的指针的__forwarding拿到age、并赋值。为什么不直接访问age呢?

(age->__forwarding->age) += 5;

NSLog(&__NSConstantStringImpl__var_folders_3c_82br3g013d52vzh11684t5cm0000gn_T_main_deca2d_mi_0,

//通过__Block_byref_age_0变量的指针的__forwarding取出agec的值。为什么不直接访问age呢?

(age->__forwarding->age));

}

下面来找寻下答案~

block的__forwarding指针

为什么需要 __forwarding 指针呢?

分2种情况:

如果block在栈上,通过栈上的__Block_byref_age_0 对象的age指针访问到的还是栈上__Block_byref_age_0 对象的的age。

/*

age:栈上指向__Block_byref_age_0对象的age指针

__forwarding:指向栈上自己的指针

age:age本身

*/

age->__forwarding->age//通过栈上指针最终访问到的是栈上的age

如果block被拷贝到了堆上,会让栈上的__Block_byref_age_0对象中的__forwarding指向堆上的__Block_byref_age_0 对象。

此时通过栈上__Block_byref_age_0 对象的age指针访问到的就是堆上__Block_byref_age_0 对象的age。

/*

age:栈上指向__Block_byref_age_0对象的age指针

__forwarding:指向堆上__Block_byref_age_0对象的指针

age:age本身

*/

age->__forwarding->age//通过栈上指针最终访问到的是堆上的age

堆上__Block_byref_age_0 对象的age指针访问到的是它自己的age。

/*

age:堆上指向__Block_byref_age_0对象的age指针

__forwarding:指向堆上自己的指针

age:age本身

*/

age->__forwarding->age//通过堆上指针最终访问到的是堆上的age

如下图所示:

__block的内存管理

当block在栈上时:

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

当block被copy到堆时:

【????第1层copy】

会调用block内部的copy函数;

copy函数内部会调用_Block_object_assign函数;

_Block_object_assign函数会对__block变量形成强引用(retain)。

注意1:被__block修饰的变量无论是基本类型、还是对象类型,都会被编译器包装成1个新对__Block_byref_XX对象

注意2:被__block修饰的变量无论是基本类型、还是对象类型,当变量所处的block被copy到堆上时、都会被强引用。(这里的被强引用指的是:编译器包装之后的对象__Block_byref_XX,而不是原始数据类型变量)。

如下图所示:

当block从堆中移除时

【????第1层移除】

会调用block内部的dispose函数;

dispose函数内部会调用_Block_object_dispose函数;

_Block_object_dispose函数会自动释放引用的__block变量(release)。

如下图所示:

被__block修饰的对象类型

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

当__block变量被copy到堆时:

基于上面????的【????第1层copy】进行【????第2层copy】

会调用__block变量__Block_byref_XX 内部对象的copy函数;

copy函数内部会调用`__Block_byref_XX 内部对象的_Block_object_assign函数;

_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

当 __block变量从堆上移除时:

在进行上面????的【????第1层移除】之前、先进行【????第2层移除】。

【????第2层移除】如下:

会调用__block变量 __Block_byref_XX 内部对象的dispose函数;

dispose函数内部会调用**__Block_byref_XX** 内部对象的_Block_object_dispose函数;

_Block_object_dispose函数会自动释放指向的对象(release) 。

这块有点绕,需要仔细消化下。

可以这么理解:

① 被**__block**修饰的变量、编译器会把原始变量包装成一个新的对象、并且block一定会对这个新的对象强引用;

② 这个新的对象里面如果包装的是对象类型变量,那么这个变量也会被新的对象强引用或者弱引用,至于是强、还是弱?取决于:对象类型变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)。

对象类型的auto变量、__block变量

如下图所示:

Block循环引用

block容易造成循环引用问题,如下图所示:

或者

那么,怎么解决循环引用问题呢?

如下几种方式来解决循环引用问题:

ARC下解决Block循环引用

用__weak、__unsafe_unretained解决。

// __weak

__weak typeof(self) weakSelf = self;

self.block = ^{

NSLog(@"%@", weakSelf);

}

// __unsafe_unretained

__unsafe_unretained id unsafeSelf = self;

self.block = ^{

NSLog(@"%@", unsafeSelf);

}

用__block解决(必须要调用block,才能解除循环引用)。

// __block

__block id blockSelf = self;

self.block = ^{

NSLog(@"%@", blockSelf);

blockSelf = nil;

}

MRC下解决Block循环引用

用__unsafe_unretained解决。

__unsafe_unretained id unsafeSelf = self;

self.block = ^{

NSLog(@"%@", unsafeSelf);

}

用__block解决。

__block id blockSelf = self;

self.block = ^{

NSLog(@"%@", blockSelf);

}

最后

以上就是幽默斑马为你收集整理的oc 协议 回调 静态成员_Blogs/Objectvie-C学习笔记1-KVO:KVC:Category:关联对象:Block.md at master · BrooksWon/Blogs · Gi...的全部内容,希望文章能够帮你解决oc 协议 回调 静态成员_Blogs/Objectvie-C学习笔记1-KVO:KVC:Category:关联对象:Block.md at master · BrooksWon/Blogs · Gi...所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部