我们都知道 Category 是在编译期决定结构,在运行时将内容附加到Class、MetaClass中,并且 Category 不能添加成员变量。 那么问题来了, 1.这个过程都经历了哪些步骤? 2.又是怎么将内容附加到Class、MetaClass中的呢? 3.成员变量这么好用的东西,为啥不让加呢?
实现原理
首先我们看看 Category 编译期都做啥了 先搞一个测试类和他的分类
// TYObject.h
@interface TYObject : NSObject
@end
// TYObject+Test.h
@interface TYObject (Test)
@property (strong, nonatomic) NSString *testStr;
@property (assign, nonatomic) NSInteger testInt;
- (void)test;
+ (void)test;
@end
使用 clang 编译器指令将其转为C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc TYObject+Test.m
TYObject+Test.cpp
struct _category_t {
const char *name; // 类名
struct _class_t *cls;
const struct _method_list_t *instance_methods; // 实例方法列表
const struct _method_list_t *class_methods; // 类方法列表
const struct _protocol_list_t *protocols; // 协议列表
const struct _prop_list_t *properties; // 属性列表
};
static struct _category_t _OBJC_$_CATEGORY_TYObject_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"TYObject",
0, // &OBJC_CLASS_$_TYObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TYObject_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_TYObject_$_Test,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TYObject_$_Test,
};
// 实例方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_TYObject_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_TYObject_Test_test}}
};
// 类方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_TYObject_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_C_TYObject_Test_test}}
};
// 属性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_TYObject_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"testStr","T@\"NSString\",&,N"},
{"testInt","Tq,N"}}
};
由c++代码可知,编译期分类里的东西只放到 _category_t 的结构体里面,并没有合并到类里面去。
将 _category_t 里内容附加到 Class、MetaClass中的流程可以查看 Apple 关于objc的开源代码来了解。
首先查看 objc-os.mm 文件,这个是 runtime 的入口类。
(主要查看我添加中文注释的部分)
// 运行时的初始化
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 主要在 map_images 方法里 image这里不是图片的意思,是镜像、模块的意思
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
// 继续进到这个里面查看
return map_images_nolock(count, paths, mhdrs);
}
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// ...
if (hCount > 0) {
// 相关逻辑在这里, 加载模块
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
// ...
}
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// ...
// Discover categories. (从这行注释开始就是有关gategory的逻辑)
// ...
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) {
// 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
|| (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);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
// ...
}
// 进到核心代码 remethodizeClass() 里查看
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
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)" : "");
}
// 主要逻辑在这里,附加Categories
// cls:类对象
// cats:分类对象数组
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
// 真正的添加步骤就在这里了
// cls = [TYOject class]
// cats = [category_t(TYOject+Test的), category_t(TYOject+其它的)]
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_t, method_t],[method_t, method_t]])
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];
// 取出分类中方法列表(根据isMeta参数判断是实例方法还是类方法)
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 将分类中的对象方法数组放到mlists(上面创建的方法数组)中
// mlists中方法的顺序:[后编译分类的方法,先编译分类的方法]
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
// 这里存的是类本身的方法、属性等数据
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 将mlists(所有分类的对象方法)附加到类对象的方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 将proplists(所有分类的属性)附加到类对象的属性列表中
rw->properties.attachLists(proplists, propcount);
free(proplists);
// protolists(所有分类的协议)附加到类对象的协议列表中
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
// 这里使用methods.attachLists的实现举例
// 向类对象中添加方法函数的内部实现
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
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 是所有分类的方法列表
// 将 addedLists 内部 addedCount 个元素拷贝到 array()->lists 的位置
memcpy(array()->lists,
addedLists,
addedCount * sizeof(array()->lists[0]));
// 由上述逻辑可知,类和分类中的方法同名,就会先调用分类中的。
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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]));
}
}
到这里,附加信息的逻辑就结束了。
总结:
当分类中方法数组附加到类对象中后,类对象中方法列表结构如下
后编译的分类方法列表 后编译 = [method_t, method_t]
先编译的分类方法列表 先编译 = [method_t, method_t]
类原有的方法列表 原有 = [method_t, method_t]
附加后的顺序 [后编译,先编译,原有]
编译顺序可在XCode项目中 TARGETS -> Build Phases -> Compile Sources 中拖动位置修改。(在上面的先编译,在下面的后编译)
成员变量
关于不能添加成员变量的问题:
根据clang编译器转出的c++代码可以看出
category_t结构体内部是没有成员变量的,所以我们如果强行手动在分类内部添加成员变量的话就会报错:
Instance variables may not be placed in categories
那苹果为什么要这么设计呢? 我觉得要从几个角度去理解 1.OC对象的内存布局 2.加载时机
OC的实例对象底层实现都是结构体,结构体内部成员地址都是连续的,所以其结构在编译期就定好了,无法修改。 可以使用以下方法加以证明: 我们在测试用的自定义类中加入一个 int 数据类型的成员变量以供调试
@interface TYObject : NSObject
{
@public int _age;
}
@end
在 main 函数中使用
int main(int argc, char * argv[]) {
@autoreleasepool {
TYObject *obj = [[TYObject alloc] init];
obj->_age = 10;
}
return 0;
}
转成c++代码为
extern "C" unsigned long OBJC_IVAR_$_TYObject$_age;
struct TYObject_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
TYObject *obj = ((TYObject *(*)(id, SEL))(void *)objc_msgSend)((id)((TYObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TYObject"), sel_registerName("alloc")), sel_registerName("init"));
(*(int *)((char *)obj + OBJC_IVAR_$_TYObject$_age)) = 10;
}
return 0;
}
注意(*(int *)((char *)obj + OBJC_IVAR_$_TYObject$_age)) = 10;这行赋值的代码,OBJC_IVAR_$_TYObject$_age是unsigned long类型,在我们64位系统中占8个字节。获取到obj的地址,加上8个字节刚好就是age成员变量所在的地址,然后赋值。
这也刚好就证明了,OC实例对象的成员变量内容是存在结构体内的,并且编译期就已经决定好结构体的内容都有什么。而category_t里的内容是在运行时附加到类里面的,如果在运行时时期去改变内存结构那将是件多可怕的事啊!
我们也可以从objc源码了解它的结构 ivars 是位于 class_ro_t 结构体内的 并且使用 const 来修饰
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;
}
所以介于以上原因category是不可以添加成员变量的