Runtime是什么?
Runtime顾名思义为运行时,他其实是一套底层纯C语言的API,OC的代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言的基础,他使OC具备了面向对象性和动态性。
所谓动态性是基于c/c++这样的静态语言区别的,在c/c++中我们编译成什么就会执行什么,且不实现定义时会报错,而OC则不会,我们甚至可以使用方法调换技术,改变我们编译时执行的函数A,令其执行函数B。
那么我们之前写的OC代码是怎么和Runtime交互的,大概分为三种层次,OC源代码,NSObject方法,Runtime函数,交互程度是由高到低的。 下面探索一下OC对象的实质,他底层是怎么样的c语言代码。
类和对象本质
这里的话需要下载一下runtime源码——Runtime 我们找到源码中关于类和对象的定义可以看到。 类和对象的本质都是结构体。
对象本质
我们点进去对象这个结构体。
struct objc_object {
private:
isa_t isa;
public:
}
可以看到这块一个系统自带的变量isa指针,点进去发现他是个共用体。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD;
};
#endif
};
# 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; \
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
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; \
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
可见对象的本质是一个objc_object 结构体,该结构体的内部有一个固定的成员变量isa ,在64位操作系统以后对isa 做了优化,它不再是一个指针,而是一个isa_t 的共用体,其占用8个字节64位,在不同架构(arm64,x86)下各位段长度不一样,在arm64中,33位被用来存储对象所属类的地址信息,19位存储对象的引用计数-1这一操作,存不下的放到引用计数表里(存值范围为0-255),1位表示是否有若引用,其他位也有各种标记信息。
类的本质
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
可以看出类的本质还是一个 objc_class 类型结构体,只不过其内部结构套了两层rw 和ro 。 class_ro_t :只读信息,内部存储着经过编译后一个类本身定义的所有实例方法,属性,协议,成员变量。 class_rw_t 是在运行时系统生成的,把ro里本身定义的信息复制到rw里,并把分类相关的信息合并到rw中,rw是可读可写的,这就解释了分类为什么不能给类增加成员变量。
元类本质
所谓元类,是指一个类所属的类,我们每创建一个类,系统就会自动帮我们创建好该类所属的类——即元类。因此在OC里对象其实分为实例对象、类对象、元类对象三类,我们开发中经常说的“对象”其实是指狭义的对象一实例对象,知道了这一点就好理解了,实例对象有它所属的类-即一个类对象,类对象也有它所属的类–即一个元类对象,元类对象也有它所属的类–即基类的元类对象。
其实元类和类的本质都是objc_class结构体,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法信息,而元类的methods成员变量里存储着该类所有的类方法信息。
所有元类都使用根元类作为他们的类,根元类的isa指针指向了它自己。
三者的关系。 使用最常见的这张图进行分析 instance(实例) Subclass(子类)
每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类)。
分类的本质
分类是OC的一个高级特性,我们一般用它给系统的类或者第三方库的类扩展方法,属性和协议,或者把一个类不同功能分散到不同的模块中实现。 在runtime中查看一下分类的源码。
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;
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);
};
就是一个结构体,然后可以看到里面是没有成员变量列表存在的,这样可以解释分类为什么不能添加成员变量。
分类的实现原理
通过上面我们可以分析得到,我们知道一个类的实例方法存储在类里面,所有的类方法在元类里面,而对象调用方法isa指针先到相应的类的和元类,然后在其方法列表中查找,那么分类是这么实现这一功能的。
因为isa指针指向了分类的所属类,所以肯定不是这一套,正确的应该是编译器在运行时利用Runtime动态把分类的数据合并到类和元类,分类的数据在类本身数据之前,越晚编译的分类越在前面,所以如果分类里面有和类里面同名的方法,就会优先调用分类里的方法,如果多个分类有同名的方法,会优先调用后编译方法里面的方法,我们可以去Compile里控制分类编译顺序。
那么Runtime具体是怎么实现的? 运行时,系统读取镜像阶段,会读取所有的类,如果发现分类,遍历所有的分类,根据分类的cls指针找到它所属的类,重新组织一下这个类内部的结构——即合并分类的数据。 以下是runtime.new中的read_images方法,这个方法是处理镜像,这里取出了关于分类的部分。
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
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
|| (hasClassProperties && 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.addUnattachedCategoryForClass,这个函数就是对比当前分类是否被合并
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();
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);
}
2.remethodizeClass,这个方法就是合并数据的方法。
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
if ((cats = unattachedCategoriesForClass(cls, false))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true );
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();
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));
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, entry.hi);
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);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
list = addedLists[0];
}
else {
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
这样就完成了将所有分类的数据合并到类的操作。最终类的方法列表,分类的在前面,而原来的方法列表在后面。
|