isa 指针
isa 里面的数据结构
我们知道,当对一个对象发送消息的时候,对象会使用自己的 isa 指针找到所属的类,然后在类里面查找方法实现。所以 isa 指针应该是存储了和类地址相关信息的,但是如果查看 objc 源代码(我这里使用的是 objc4-781)会发现对象数据结构里面的 isa 指针是这样的:
objc-private.h 文件里面:
struct objc_object {
private:
isa_t isa; // isa 指针,是一个 union 类型
public:
// more ...
// more ...
// more ...
isa_t 的定义:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; // 类信息
uintptr_t bits; // 存放具体的内容
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
在 isa.h 文件里面查看 isa 具体的 ISA_BITFIELD 解释信息(目前拷贝出来的是 arm64 架构的类型)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
我们知道在arm64架构上,isa 指针的大小是 64 位,也就是8字节。但是我们在 isa_t 的结构中又看到里面 结构体struct { ISA_BITFIELD; // defined in isa.h }; 似乎存放了很多的内容,它哪放的下呢?这是因为 isa 使用的是 union 数据结构,
简单的来说就是它有共有内存的意思,然后在64位的内存中,里面的存储以位为单位,比如 nonpointer 在isa指针的64位内存中占用1位,而且 结构体 struct { ISA_BITFIELD; // defined in isa.h }; 里面它实际是不存内容的,它的目的主要是告诉你里面的内存布局是怎样的。 具体的 isa 信息存放在 uintptr_t bits 这个家伙里面。当对具体的数据进行存储或者取出的时候使用类似 &(与),|(或),!(非)的操作,再取出在64位内存里面某个范围里面的信息。
isa 里面存储的内容
-
nonpointer如果为 0,那么表示isa指针此时直接指向类或者元类;1表示isa指针经过优化,里面使用位域存放更多信息 -
has_assoc表示是否有设置过关联对象,如果没有,内存释放的时候会更快(主要是对象内存释放的时候会判断一下,这个如果要看在原代码里面也是能看到的) -
has_cxx_dtor释放有c++析构函数 -
shiftcls存储这 Class 或者 Meta_Class的内存地址 -
magic调试的时候分辨对象是否未完成初始化 -
weakly_referenced是否有设置过弱引用 -
deallocating对象是否正在释放中 -
extra_rc存放引用计数(目前分配的是 19 位,所以其实还是能存储很多的) -
has_sidetable_rc引用计数的值释放已经在isa指针里面存满了,如果为1,那么引用计数会存放在一个SideTable类的属性里面
所以在 isa 这个 union 共用体里面其实存放了很多的信息。
方法的查找
在 Objective-C 里面,消息的发送大致有3个阶段:
-
方法查找
-
消息动态解析
-
消息转发
一个经典的方法查找图:
类里面相关数据结构
在文件中objc-runtime-new.h
可以看到方法底部是一个这样的结构体:
struct method_t {
SEL name; // 函数名
const char *types; // 编码(返回值类型,参数类型)
MethodListIMP imp; // 函数实现地址
};
方法的编码类型可以在这查看详情:Type Encodings
Class 类里面存储的信息:
struct objc_class : objc_object {
Class ISA; // isa 指针
Class superclass; // 父类指针
cache_t cache; // 存放缓存方法等
class_data_bits_t bits; // 具体类信息
// ... more
}
class_data_bits_t 结构体:
class_rw_t // 可读可写信息
class_ro_t // 只读信息
class_rw_t:
method_array_t // 方法列表相关(是一个二维数组,包含分类的方法和基本类的方法)
property_array_t // 属性列表相关
protocol_array_t // 协议列表相关
// ... more
class_ro_t 存放基本类信息:
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成员变量列表(可以看出成员变量是在一个只读的数据结构中,这也是为什么类被注册到内存中以后就不能添加成员变量了)
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// ... more
方法查找流程
在 Objective-C 里面,对一个对象发送消息的流程是这样的:
方法缓存 cache_t 的数据结构
在上面我们看到,对象的方法查找有一个 cache_t 的东西,它缓存了对象调用过的相关方法,它使用散列表提高查找效率
cache_t 大致的数据结构:
<struct bucket_t *> _buckets // 散列表 [bucket_t, bucket_t, bucket_t...]
<mask_t> _mask // 散列表长度-1
uint16_t _occupied // 已经缓存方法的数量
bucket_t:
_sel // 方法名为key
_imp // 方法实现地址为value
在objc源代码中,可以找到缓存方法的插入:
如果缓存容器满了,会直接把之前的缓存方法清空,缓存容量内存加倍。
动态方法解析
我们知道,对一个对象发送消息,它的方法查找流程是这样的 object -> superClass - > superClass ... -> NSObject 。但是如果直到 NSObject 都没有找到的话,那么它会进入方法动态解析
在动态方法解析阶段,开发者可以动态为方法添加实现。
动态方法解析包括:
-
+ (BOOL)resolveInstanceMethod:(SEL)sel对象方法的动态解析 -
+ (BOOL)resolveClassMethod:(SEL)sel类方法的动态解析
实现动态解析一个对象方法的例子:
@implementation Cat
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat)) {
Method method = class_getInstanceMethod(self, @selector(lotOfFish));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); // 动态指定 eat 方法的实现地址
return true;
}
return [super resolveInstanceMethod:sel];
}
- (void)lotOfFish{
NSLog(@"%s", __func__);
}
@end
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
[cat performSelector:@selector(eat)]; // cat 没有 eat 方法,或者没有实现
}
return 0;
}
输出:
2021-01-17 11:18:07.157671+0800 Runtime[66738:1460934] -[Cat lotOfFish]
可以看到,由于 cat 对象没有 eat 方法,所以执行 eat 方法的时候方法查找没找到,执行对象的动态方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel,给对象添加了对于方法的实现。
类方法的动态解析流程和对象的是一样的,添加自定义的方法实现就好了。
(需要注意的是,添加了动态方法解析以后,对象会再走一遍消息查找)
大致的流程图如下所示:
消息转发
那如果动态方法解析没有实现呢?此时就会来到消息转发阶段了。在消息转发阶段,开发者可以:
-
把方法转发给其它的对象来处理;
-
捕获方法,添加方法签名,之后还可以再方法转发(或者什么都不做)。
把方法转发给其它的对象来处理消息
添加一个 Dog 类:
@interface Dog : NSObject
- (void)eat;
@end
@implementation Dog
// Dog 包含 eat 方法
- (void)eat {
NSLog(@"Eat dog food");
}
@end
Cat 类里面实现消息转发:
@implementation Cat
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(eat)) {
// 消息转发给 dog 对象处理
return [[Dog alloc] init];
}
// 其它的逻辑不变
return [super forwardingTargetForSelector:aSelector];
}
@end
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
// Cat 没有 eat 方法
[cat performSelector:@selector(eat)];
}
return 0;
}
输出:
2021-01-17 11:49:20.044035+0800 Runtime[67857:1479538] Eat dog food
可以看到,cat 对象执行 eat 方法的时候,由于 cat 对象没有该方法,而且它的动态方法解析阶段也没过添加自己的方法实现,所以此时会来到消息转发阶段的 - (id)forwardingTargetForSelector:(SEL)aSelector。在这个方法里面可以把捕捉的方法转发给其它对象来执行。
添加方法签名 & 处理
如果在第一步的消息转发阶段没有实现 - (id)forwardingTargetForSelector:(SEL)aSelector方法,那么之后会调用方法:
-
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector -
- (void)forwardInvocation:(NSInvocation *)anInvocation
Cat 测试类添加实现代码:
@implementation Cat
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(eat)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"]; // 参数类型可以查看苹果的官网介绍
}
return [super methodSignatureForSelector:aSelector];
}
// 签名后的查看
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 这里可以拦截所有这个类的找不到的方法,这样就可以消息找不到的时候也不崩溃了
NSLog(@"没有实现方法: %s", anInvocation.selector);
}
@end
测试:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
[cat performSelector:@selector(eat)];
}
return 0;
}
输出:
2021-01-17 11:57:40.963118+0800 Runtime[68203:1486085] 没有实现方法: eat
消息转发的大致流程如下:
一些 objc API 的介绍
类相关
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
获取isa指向的Class
Class object_getClass(id obj)
设置isa指向的Class
Class object_setClass(id obj, Class cls)
判断一个OC对象是否为Class
BOOL object_isClass(id obj)
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
获取父类
Class class_getSuperclass(Class cls)
一个简单的测试:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class Cat = objc_allocateClassPair([NSObject class], "Cat", 0); // 注册一个类和它的元类
class_addIvar(Cat, "_age", 4, 1, @encode(int)); // 添加成员变量(必须在类的内存注册之前)
objc_registerClassPair(Cat); // 注册到内存
id cat = [[Cat alloc] init];
[cat setValue:@5 forKey:@"_age"];
NSLog(@"cat age = %@", [cat valueForKey:@"_age"]); // 输出 cat age = 5
}
return 0;
}
成员变量
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
属性
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
方法
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
补充
isKindOfClass 和 isMemberOfClass 的区别
这个我们其实可以到源代码里面查看一下内部实现:
- 对于类对象:
+ (BOOL)isMemberOfClass:(Class)cls 是找到自己的 isa 指针的指向类,也就是自己元类
+ (BOOL)isKindOfClass:(Class)cls 是找到自己的 isa 指针的指向类,也就是自己元类,然后如果找不到,再遍历自己的父类。所以它会一直到 NSObject
- 对于实例对象:
- (BOOL)isMemberOfClass:(Class)cls 是找自己的 class 属性得到所属类,然后对比当前类
- (BOOL)isKindOfClass:(Class)cls 是找自己的 class 属性得到所属类,然后对比当前类,如果不是,再遍历父类,直到 NSObject
测试一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 类
NSLog(@"NSObject isKindOfClass NSObject: %d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"NSObject isMemberOfClass NSObject: %d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"Cat class isKindOfClass Cat: %d", [Cat isKindOfClass:[Cat class]]); // 0
NSLog(@"Cat class isMemberOfClass Cat: %d", [Cat isMemberOfClass:[Cat class]]); // 0
NSLog(@"------------------");
// 对象
// 继承关系 Cat: NSObject
Cat *cat = [[Cat alloc] init];
NSLog(@"cat instance isKindOfClass NSObject: %d", [cat isKindOfClass:[NSObject class]]); // 1
NSLog(@"cat instance isMemberOfClass NSObject: %d", [cat isMemberOfClass:[NSObject class]]); // 0
NSLog(@"cat instance isKindOfClass Cat: %d", [cat isKindOfClass:[Cat class]]); // 1
NSLog(@"cat instance isMemberOfClass Cat: %d", [cat isMemberOfClass:[Cat class]]); // 1
}
return 0;
}
输出:
2021-01-17 20:10:12.286409+0800 Runtime[76526:1658908] NSObject isKindOfClass NSObject: 1
2021-01-17 20:10:12.286671+0800 Runtime[76526:1658908] NSObject isMemberOfClass NSObject: 0
2021-01-17 20:10:12.286703+0800 Runtime[76526:1658908] Cat class isKindOfClass Cat: 0
2021-01-17 20:10:12.286723+0800 Runtime[76526:1658908] Cat class isMemberOfClass Cat: 0
2021-01-17 20:10:12.286741+0800 Runtime[76526:1658908] ------------------
2021-01-17 20:10:12.286760+0800 Runtime[76526:1658908] cat instance isKindOfClass NSObject: 1
2021-01-17 20:10:12.286780+0800 Runtime[76526:1658908] cat instance isMemberOfClass NSObject: 0
2021-01-17 20:10:12.286798+0800 Runtime[76526:1658908] cat instance isKindOfClass Cat: 1
2021-01-17 20:10:12.286815+0800 Runtime[76526:1658908] cat instance isMemberOfClass Cat: 1
super 关键字
添加如下测试:
// --------------- Animal ---------------
@interface Animal : NSObject
- (void)eatFood;
@end
@implementation Animal
- (void)eatFood {
NSLog(@"Animal eat");
}
@end
// --------------- Cat ---------------
@interface Cat : Animal
@end
@implementation Cat
- (void)eatFood {
[super eatFood];
NSLog(@"Cat eat");
}
@end
测试运行:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
[cat eatFood];
}
return 0;
}
输出:
2021-01-17 14:10:42.039886+0800 Super[71153:1538568] Animal eat
2021-01-17 14:10:42.040163+0800 Super[71153:1538568] Cat eat
这只是一个很简单的继承和方法的调用,符合预期的执行效果。
再看下面情况:
@interface Cat : Animal
- (void)sleep;
@end
- (void)sleep {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[super superclass] = %@", [super superclass]);
}
测试:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
[cat sleep];
}
return 0;
}
输出:
2021-01-17 14:14:14.480170+0800 Super[71285:1540874] [self class] = Cat
2021-01-17 14:14:14.480781+0800 Super[71285:1540874] [self superclass] = Animal
2021-01-17 14:14:14.480816+0800 Super[71285:1540874] [super class] = Cat
2021-01-17 14:14:14.480838+0800 Super[71285:1540874] [super superclass] = Animal
什么情况? 在 Cat 类中输出情况是:[super class] = Cat 和 [super superclass] = Animal。
我们知道,在 runtime 的消息发送走的是 objs_msgSend 这个家伙。 一个对象的消息发送流程是如果一个对象在所属类中找不到方法,那么会到父类里面查找,然后一直到 NSObject为止。那么为什么在cat对象的方法调用里面会是:[super class] = Cat 而不是:[super class] = Animal 呢?
简化代码,查看 [super eatFood] 干了什么:
- (void)eatFood {
[super eatFood];
}
执行命令 clang -rewrite-objc Cat.m -o Cat.cpp, 转成 c++ 代码,查看底层实现(当然,具体的OC代码的底层肯定不是这样的,但是它基本的逻辑实现是差不多的)
在 c++ 的代码中,找到 eatFood 方法的实现,可以发现是这样一个东西:
static void _I_Cat_eatFood(Cat * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Cat"))},
sel_registerName("eatFood"));
}
我们发现执行 super 方法调用执行的是 objc_msgSendSuper 方法,方法里面接收两个参数 __rw_objc_super 结构体和 eatFood 方法,__rw_objc_super 结构体接收了两个参数:第一个是当前对象,在我们的例子中也就是 cat 对象,第二个参数是当前类的父类,也就是Cat类的父类,为Animal类。
__rw_objc_super 的数据额结构:
struct __rw_objc_super {
struct objc_object *object; // 消息接收者
struct objc_object *superClass; // 消息接收者的父类
};
所以在Cat类的对象方法里面调用 [super class] 的消息接收者还是为 cat 对象,只不过它的消息查找从父类开始,跳过了Cat类。所以我们会发现:
NSLog(@"[super class] = %@", [super class]); // 输出:[super class] = Cat
NSLog(@"[super superclass] = %@", [super superclass]); // 输出:[super superclass] = Animal
当然我们也可以在 objc 原代码,查看里面的 objc_msgSendSuper 方法的实现:
(我这边下载的是 objc4-781 源代码)
objc_msgSendSuper 方法的接口介绍:
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
objc_super 结构体:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
可以看出,里面的实现虽然和我们的c++代码有点差异,当时它具体的方法接收者还是当前的对象,然后方法的查找是从父类开始查找的。