对象模型
新建一个类:
#import <Foundation/Foundation.h>
@interface CustomClass : NSObject
@end
@implementation CustomClass
@end
int main(int argc, char * argv[]) {
CustomClass *customObject = [[CustomClass alloc] init];
}
在终端运行以下命令:
xcrun -sdk iphonesimulator12.0 clang -rewrite-objc CustomObject.m
.m文件被编译成C++文件,截取部分代码如下:
...
typedef struct objc_object CustomClass;
...
int main(int argc, char * argv[]) {
CustomClass *customObject = ((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CustomClass"), sel_registerName("alloc")), sel_registerName("init"));
}
...
可以看到我们定义的CustomClass
被定义成objc_object
结构体,在main函数里面的CustomClass *customObject
其实就是struct objc_object *customObject
,所以customObject
的本质就是一个指向objc_object
结构体的指针。
objc_object
的源码:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到objc_object有一个Class类型的isa
变量,这个isa是指向该实例所属的类的。而Class其实本质就是objc_class
:
typedef struct objc_class *Class;
objc_class
的源码:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
};
类继承自对象, 所有类其实也是对象,它的isa指向的类就是这个类的元类(meta-class)
。元类只有一个对象,就是类对象
。类方法就在类对象里面。
给一个对象发送消息,会到这个对象所属的类里面找对应的方法;给一个类发送消息,会到这个类的元类(也就是类对象所属的类)里面去找对应的方法。
而元类也是类,它也有isa指针,这个isa指针统一都直接指向NSObject的元类。而NSObject的元类的isa则指向自己。另外NSObject的元类的父类则指向NSObject。而其他子类的元类指向其父类的元类。
调用实例的方法,会到类里面找。调用类的方法,也就是元类的实例方法,因为类就是元类的实例,回到元类里面找,在元类里面找不到就会去元类的父类,最后来到NSObject元类,NSObject元类的父类就是NSObject。
objc_method
的定义:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
SEL
的定义:
typedef objc_selector * SEL;
可以理解为区分方法的 ID。
在Object-C中可以通过以下方法获得SEL:
SEL @selector(<selector)
SEL sel_registerName(const char * _Nonnull str)
IMP
的定义:
typedef id (*IMP)(id, SEL, ...);
指向最终实现程序的内存地址的指针。
消息机制
在Object-C里的函数调用其实都是发送消息,[receiver massage]
会被编译成
objc_msgSend(receiver, selector)
如果有参数,则为:
objc_msgSend(receiver, selector, arg1, arg2, ...)
objc_class 里面还有一个变量:struct objc_cache *cache
,它主要是为了调用的性能进行优化的。每当实例对象接收到一个消息时,它会先到cache中去找能够响应的方法,找不到再去方法列表里面找。找到之后就会保存到cache里面。已备下次再被调用。
如果对象接收到无法解读的消息,那将会调用其所属类的以下类方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
sel就是那个未知的选择子,返回的BOOL类型来表示这个类能否新增一个实例方法来处理这个选择子。所以本类有机会新增一个处理此选择子的方法。一般是在resolveInstanceMethod:方法里面调用class_addMethod方法来动态添加方法,但前提是实现代码已经提前写好了。
如果上述方法返回的是NO,那么当前接受者还有第二次机会,runtime还会调用该对象的以下方法,看能否将该消息转发给其他接受者处理:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果此时还是返回nil,那么会来到完整的消息转发。runtime会调用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
anInvocation包含了那条尚未处理的消息的全部细节,包括选择子、目标以及参数。这个步骤可以修改消息的内容,比如追加参数或者改变选择子等。如果本类不处理此消息,会调用超类的同名方法。最后传到NSObject时会调用“doesNotRecognizeSelector:”抛出异常。
Category
我们先给前面自定义的类实现一个分类:
@interface CustomClass : NSObject
@end
@implementation CustomClass
- (void)originMethod {
NSLog(@"originMethod");
}
@end
@interface CustomClass(MyAddition)<NSCopying>
@property (nonatomic, copy) NSString *customProperty;
@end
@implementation CustomClass(MyAddition)
+ (void)addClassMethod {
NSLog(@"addClassMethod");
}
- (void)addInstanceMethod {
NSLog(@"addInstanceMethod");
}
- (id)copyWithZone:(NSZone *)zone {
return nil;
}
@end
运行以下命令,得到一个C++文件
clang -rewrite-objc CustomClass.m
这个文件很长,先看其中的一部分:
static struct _category_t _OBJC_$_CATEGORY_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CustomClass",
0, // &OBJC_CLASS_$_CustomClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CustomClass_$_MyAddition,
};
可以看到这个就是分类的定义,其本质就是category_t,可以在runtime的源码中找到category_t的定义:
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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
其中定义了分类的名字、类、实例方法、类方法、实现的协议、属性。
对应起来OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition
则保存了所添加的实例方法:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"addInstanceMethod", "v16@0:8", (void *)_I_CustomClass_MyAddition_addInstanceMethod},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_CustomClass_MyAddition_copyWithZone_}}
};
method_list_t 里面包含了方法的大小、数目、方法数组。
OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition
保存了类方法;
OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition
是协议列表:
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
OBJC_$_PROP_LIST_CustomClass_$_MyAddition
属性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"customProperty","T@\"NSString\",C,N"}}
};
在DATA段我们还可以看到一个保存分类列表的数组:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_CustomClass_$_MyAddition,
};
Category被附加到类上面是在objc-os.mm的_objc_init方法里发生的,_objc_init里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的结尾,有以下的代码片段:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
1、通过_getObjc2CategoryList
拿到所有的分类;
2、对每个分类,拿到所属的类;
3、Register the category with its target class;addUnattachedCategoryForClass(cat, cls, hi)
;
4、Rebuild the class's method lists (etc) if the class is realized;remethodizeClass(cls)
;
来看addUnattachedCategoryForClass
的源码:
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
注释里说是把分类注册到类中,其实就是把类和category做一个关联映射。remethodizeClass
则真正在把方法添加到类中:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
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--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
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);
}
一个关键的代码:auto rw = cls->data()
;
根据objc_class的定义我们可以知道rw就是class_rw_t
,而class_rw_t里面还包含着class_ro_t
。事实上,类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中,而class_ro_t则存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。所以分类是把方法添加到class_rw_t当中的
。
分类本质上就是一个保存了一系列方法的结构体,在运行时分类的方法会被附加到类的方法前面。
KVO原理
KVO即Key-Value-Observer
键值观察,是通过isa-swizzling
技术实现的:
- 1、当类A的实例第一次被观察的时候,系统会在运行期动态地创建类A的派生类NSKVONotifying_A,其实就是
A的子类
; - 2、类NSKVONotifying_A会重写被观察的属性的setter方法,在修改值之前会调用
willChangeValueForKey:
方法,修改值之后会调用didChangeValueForKey:
方法,这两个方法最终都会被调用到observeValueForKeyPath:ofObject:change:context:
方法中; - 3、类NSKVONotifying_A重写
class
方法,返回类A,类NSKVONotifying_A还会重写dealloc方法释放资源; - 4、被观察对象的
isa
指针从指向原来的 A 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听。
KVO的使用其实比较容易崩溃,addObserver和removeObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。苹果官方推荐的方式是,在init的时候进行addObserver,在dealloc时removeObserver,这样可以保证add和remove是成对出现的,是一种比较理想的使用方式。
KVOController
是Facebook的一个KVO开源第三方框架,它具有原生KVO所有的功能,但规避了原生KVO的很多问题,而且支持block回调。
KVC
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
先查找setKey或_setKey方法存不存在,如果存在则直接调用设值,否则询问是否可以直接访问变量+ (BOOL)accessInstanceVariablesDirectly
,如果可以则直接设置,否则报错
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
先查找getKey、key、isKey、_isKey方法存不存在,如果存在则直接调用,否则询问是否可以直接访问变量+ (BOOL)accessInstanceVariablesDirectly
,如果可以则直接读取,否则报错。
被监听的实例一定有set方法,所以KVC会触发KVO。
strong、weak、assign、copy
有一个值为B
,它的内存地址为A
,现在把A赋值给C会发生什么呢?如下例子:
NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];
这里的A就是创建字符串@"B"时的值地址。引用计数的概念就是针对A的,但A赋值给其他变量或者指针设置为nil,如A=nil,都会导致引用计数有所增减。当内容区域引用计数为0的时候就会将数据B抹除。
使用copy、strong、retain、weak、assign区别就在:
- 是否对A有引用计数增加;
- 是否开辟新的内存。
@property (nonatomic, strong) NSMutableString *strongC;
@property (nonatomic, weak) NSMutableString *weakC;
@property (nonatomic, assign) NSMutableString *assignC;
@property (nonatomic, copy) NSMutableString *copyC;
...
NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];
self.strongC = C;
self.weakC = C;
self.copyC = C;
self.assignC = C;
strongC会增加A的引用计数;weakC不会增加A的引用计数;copyC也不会增加A的引用计数,它会开辟一块新的内存。
assign和weak类似,都不会增加A的引用计数,但是区别在于但weak指向的对象被释放后weak指针会被置为nil,而assign不会,就会导致野指针的问题。但assign修饰基本数据类型是安全的,而weak只能修饰对象类型。
retain和strong一样,会增加A的引用计数,但在MRC时会有差别。
copy NSString NSArray不会开辟新的内存,因为它们是不可变变量。
对不可变对象进行copy,是浅拷贝;其余的对不可变对象进行mutableCopy,对可变对象进行copy,对可变对象进行mutableCopy都是深拷贝。
copy之后得到的都是不可变对象,mutableCopy之后得到的都是可变对象。
@property (nonatomic, assign) float a;
是保存在它所在的对象所存在的堆上面的。
iOS中copy,strong,retain,weak和assign的区别
参考