Category的底层结构
- 在objc源码objc-runtime-new.h中可以找到
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; // 属性列表
// Fields below this point are not always present on disk.
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);
};
Category的实现原理
- Category编译之后的底层结构是
struct Category_t
,里面存储着类的对象方法,类方法,属性,协议信息
- 在程序运行的时候,
runtime
会将Category的数据,合并到类信息中(类对象、元类对象)
- Category在编译时,产生的C++源码, 可知,在编译时并没有将LeePerson的分类LeePerson+Eat合并
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_LeePerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_LeePerson_Eat_test}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_LeePerson;
static struct _category_t _OBJC_$_CATEGORY_LeePerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LeePerson",
0, // &OBJC_CLASS_$_LeePerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LeePerson_$_Eat,
0,
0,
0,
};
static void OBJC_CATEGORY_SETUP_$_LeePerson_$_Eat(void ) {
_OBJC_$_CATEGORY_LeePerson_$_Eat.cls = &OBJC_CLASS_$_LeePerson;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_LeePerson_$_Eat,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_LeePerson_$_Eat,
};
- 查看
objc
的源码,运行时,发生了什么
- 源码解读顺序
- objc-os.mm
- _objc_init
- map_images
- map_images_nolock
- objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
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, 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()) {
// 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;
// 将合并之前的列表的内存数据往后移动 addedCount,为新的列表腾出位置
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将新的添加的列表的内存,拷贝到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]));
}
}
void *memcpy(void *__dst, const void *__src, size_t __n);
void *memmove(void *__dst, const void *__src, size_t __len);
Category和Class Extension的区别是什么
-
Class Extension
在编译的时候,它的数据已经包含在类信息中
-
Category
是在运行时,才会将数据合并类信息中
Category中load方法什么调用?load方法能继承吗?
- load方法在rumtime加载类,分类的时候调用
- 每个类,分类的+load方法在程序运行过程中只调用一次
- load方法可以继承,但是在一般情况下不会主动调用load方法,都是让系统自动调用
+load方法调用顺序
- 先调用类的
+load
方法
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的
+load
之前会先调用父类的+load
- 再调用分类的
+load
查看objc
源码,证明调用顺序
- objc源码解读过程:objc-os.mm
- _objc_init
- load_images
- prepare_load_methods
- schedule_class_load
- add_class_to_loadable_list
- add_category_to_loadable_list
- call_load_methods
- call_class_loads
- call_category_loads
- (*load_method)(cls, SEL_load)
-
+load
方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
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();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
// 获取 load 方法
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 执行所有的load方法
call_load_methods();
}
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
// 找到所有类的 load 方法
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 找到所有类的分类 load 方法
// 从小到大的方式编译,意味着先编译的先执行
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
// 递归获取父类的load方法
schedule_class_load(cls->superclass);
// 将cls添加到loadable_classes数组的最后面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
// 1. 先执行类中的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
// 2. 执行分类中的load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// 执行load方法,call_category_loads中类似
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
+initialize方法
-
+inittialize
方法在类第一次接收到消息时调用,比如[Person alloc]init]
,调用 alloc方法时就相当于``objc_msgsend([Person class], @selector(alloc)), 接收了消息,此时就会调用initiallize
- 调用顺序
- 先调用父类的
+initialize
,再调用子类的+initialize
(相当于先初始化父类,再初始化子类,每个类只会初始化一次)
-
+initialize
和+load
的最大区别是,+initialize
是通过objc_msgSend
进行调用
- 如果子类没有实现
+initialize
会调用父类的+initialize
(所以父类的+initialize
可能会被调用多次)
- 如果分类实现了
+initialize
,就会覆盖类本身的+initialize
调用
- 伪代码如下:
BOOL sutdentInitialized = NO;
BOOL personInitialized = NO;
BOOL teacherInitialized = NO;
[MJStudent alloc];
if (!sutdentInitialized) {
if (!personInitialized) {
objc_msgSend([MJPerson class], @selector(initialize));
personInitialized = YES;
}
objc_msgSend([MJStudent class], @selector(initialize));
sutdentInitialized = YES;
}
[MJTeacher alloc];
if (!teacherInitialized) {
if (!personInitialized) {
objc_msgSend([MJPerson class], @selector(initialize));
personInitialized = YES;
}
objc_msgSend([MJTeacher class], @selector(initialize));
teacherInitialized = YES;
}
===== 等价于======
// MJPerson (Test2) +initialize
objc_msgSend([MJPerson class], @selector(initialize));
// MJPerson (Test2) +initialize
objc_msgSend([MJStudent class], @selector(initialize));
// MJPerson (Test2) +initialize
objc_msgSend([MJTeacher class], @selector(initialize));
// isa -> 类对象\元类对象,寻找方法,调用
// superclass -> 类对象\元类对象,寻找方法,调用
// superclass -> 类对象\元类对象,寻找方法,调用
// superclass -> 类对象\元类对象,寻找方法,调用
// superclass -> 类对象\元类对象,寻找方法,调用
objc4源码解读过程
- objc-msg-arm64.s
- objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
// 如果没有初始化,就先初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
// 调用initialize方法
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
// 如果父类没有初始化,则先调用父类的初始化方法
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
// 调用初始化方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
load,initialize方法的区别?
- 调用方式不同
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
- 调用时刻不同
- load是runtime加载类,分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用。每一个类只会initialize一次
load, initialize的调用顺序
- load
- 先调用类的load,先编译的类,优先调用load;调用子类的load之前,会调用父类的load
- 再调用分类的load,先编译的分类,优先调用load
- initialize
- 先初始化父类
- 再初始化子类(可能最终掉哟经父类的initialize方法)
不同分类中同名方法的调用顺序
- 同名方法的调用顺序取决于编译的顺序,最后编译的分类,则最先调用该分类中的方法
- 通
objc
的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;
// 将合并之前的列表的内存数据往后移动 addedCount,为新的列表腾出位置
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将新的添加的列表的内存,拷贝到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]));
}
}
网友评论