Objective-C Runtime是一个Runtime库,基本上是用C和汇编完成的,使C具有了面向对象的能力。这也使得Objective-C语言成为了一个动态语言。
这动态语言和传统的面向过程语言(C语言)、面向对象语言(C++, OOP)有比较大的区别:函数调用在编译期不能确定内存地址,只有到了运行时才能做出跳转。也决定了Objective-C语言将 决定 尽可能的从编译和链接时推迟到运行时。并且只要有可能,Objective-C总是用动态的方式来解决问题。Objective-C是基于Runtime完成了面向对象和动态的特性,这里Runtime系统扮演的角色类似于Objective-C语言的操作系统。
Runtime库是开源的,所以我们可以在这里下载源码,LZ 用的是最新的 objc4-706
这个版本. 废话不多说,下面开始进入源码分析~~我们可以看到源码的目录大概是这么个结构:
我们点开 NSObject.h
文件,首先会看到一个 叫NSObject
的协议。这个协议里有这么些方法
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
- (BOOL)isEqual:(id)object;
- (Class)class;
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
这些方法其实都是平时我们在开发时使用到的方法以及属性,主要都是一些用于自我检查、消息传递的一些方法,还有就是用于内存管理的方法,不过在ARC情况下,都不需要用到。由于这些方法都写在了NSObject
协议里,而我们所有的类也都是继承自 NSObject
(除了NSProxy),而 NSObject
则是实现了 NSObject
协议,也就是说,继承自NSObject
的对象都可以使用到这些方法。
接着我们着重分析下下面四个方法:
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
+ (BOOL)isKindOfClass:(Class)aClass;
+ (BOOL)isMemberOfClass:(Class)aClass;
我们查看下方法的源码:
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
inline Class objc_object::getIsa() {
return ISA();
}
inline Class objc_object::ISA() {
assert(!isTaggedPointer());
return isa.cls;
}
+ (BOOL)isKindOfClass:(Class)cls
内部会先去获得object_getClas
s的类,而object_getClass
的源码实现是去调用当前类的obj->getIsa()
,最后在ISA()
方法中获得meta class
的指针。接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。
判断两个class是不是相等的步骤大概是这样的:某类的Meta Class与某类是不是相等,如果不等则判断某类的 Meta Class的super class, 指向的是 NSObject Meta Class, 和某类相比,如果不相等,则继续,NSObject Meta Class的super class指向的是NSObject Class,和 某类相比还 不相等,则继续进行比较,NSObject Class 的super class 指向 nil, 和 某类不相等,则退出循环。
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
第二行isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等,isa指向某类的Meta Class,和某类也不等
我们在来看看NSObject
这个类,
+ (instancetype)new;
+ (instancetype)allocWithZone:(struct _NSZone *)zone;
+ (instancetype)alloc;
- (void)dealloc;
- (void)finalize;
- (id)copy;
- (id)mutableCopy;
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector ;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
这个类除了提供了对象的创建方法之外,还有一些用于消息转发处理的的方法。
我们打开 objc.h
这个文件会看到 NSObject
类有这个一个成员变量
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
在Objc2.0之前,objc_class源码如下:
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
在这里可以看到,在一个类中,有超类的指针,类名,版本的信息。
ivars是objc_ivar_list成员变量列表的指针;methodLists是指向objc_method_list指针的指针。*methodLists是指向方法列表的指针。这里如果动态修改 *methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。
然后在2006年苹果发布Objc 2.0之后,objc_class的定义就变成下面这个样子了:
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
从上述源码中,我们可以看到,Objective-C 对象都是 C 语言结构体实现的,在objc2.0中,所有的对象都会包含一个isa_t类型的结构体。
objc_object被源码typedef成了id类型,这也就是我们平时遇到的id类型。这个结构体中就只包含了一个isa_t类型的结构体。objc_class继承于objc_object。所以在objc_class中也会包含isa_t类型的结构体isa。可以得出结论:Objective-C 中类也是一个对象。
在objc_class中,除了isa之外,还有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个这个类的实例方法链表。
当一个对象的实例方法被调用的时候,会通过isa找到相应的类,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现。
但是在我们调用类方法的时候,类对象的isa里面是什么呢?
这里为了和对象查找方法的机制一致,遂引入了元类(meta-class)的概念。
在引入元类之后,类对象和对象查找方法的机制就完全统一了。
对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。
meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
PS:图中实线是 super_class指针,虚线是isa指针
接下来我们来研究下 isa_t
结构体的具体实现
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
我去。。
isa_t
是一个联合体,但是这一大坨都是什么玩意?其实用一句话概括就是 :对64位的设备对象进行类对象指针的优化,利用合理的bit(arm64设备为32位)存储类对象的地址,其他位用来进行内存管理。这种优化模式被称为Tagged Pointer。用在isa_t的实现中称作IndexedIsa.
我们来看下每个元素都是什么意思
- nonpointer:标记是否启动指针优化
- has_assoc:是否有关联对象
- has_cxx_dtor:是否有析构器
- shiftcls:类对象指针
- magic:标记初始化完成
- weakly_refrenced:是否弱引用
- deallocating:是否正在释放
- extra_rc:引用计数(但是比retaincount小1)
- has_sidetable_rc:对象的引用计数太大了,存不下
研究完isa_t之后,我们再回过头来看 objc_object
的方法 :
Class ISA() //不支持tagged pointer时候获取Class的函数
Class getIsa() //支持tagged pointer时候获取Class的函数
这些是初始化isa的一些方法:
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
最后你会发现这些方法其实最后都是调了这么个方法:
inline void objc_object::initIsa(Class cls) {
assert(!isTaggedPointer());
isa = (uintptr_t)cls;
}
这个方法可以用来改变一个对象所指向的Class
Class changeIsa(Class newCls);
接下来就是一些内存管理的方法:
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
看一下 id retain()
的实现:
inline id objc_object::retain() {
// UseGC is allowed here, but requires hasCustomRR.
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
inline id objc_object::rootRetain() {
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
id objc_object::sidetable_retain() {
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
解释下这段函数就是:如果使用GC但是并没有custom的retain/release方法则会直接断言掉,如果支持tagged pointer就会直接断言掉。接下来的流程就是如果没有custom的retain/release方法就会调用rootRetain()。最后会调用sidetable_retain ()
方法
接下来研究cache_t
的实现:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
- mask:分配用来缓存bucket的总数。
- occupied:表明目前实际占用的缓存bucket的个数。
- bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
- bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。
Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。
最后更新: 2023年03月25日 22:39:55
本文链接: http://aeronxie.github.io/post/c3cade62.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!