class_copyIvarList & class_copyPropertyList区别
要想清楚这个问题,首先要知道@property
做了什么事情。在对象中增加一个成员变量有两种方式
@interface ViewController ()
{
NSString *_ivar1;
NSString *_ivar2;
}
@property (nonatomic, copy) NSString *ivar3;
@end
其中的区别是通过@property
修饰的成员变量,编译器在编译时会主动添加set
和get
方法。同时会生成一个名为_ivar3
的实例变量。通过clang
来将上面的代码编译成为cpp
文件来验证下。
static struct _class_ro_t _OBJC_CLASS_RO_$_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct ViewController, _ivar1), sizeof(struct ViewController_IMPL),
(unsigned int)0,
0,
"ViewController",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_ViewController,
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_ViewController,
0,
0,
};
_OBJC_$_INSTANCE_METHODS_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"viewDidLoad", "v16@0:8", (void *)_I_ViewController_viewDidLoad},
{(struct objc_selector *)"ivar3", "@16@0:8", (void *)_I_ViewController_ivar3},
{(struct objc_selector *)"setIvar3:", "v24@0:8@16", (void *)_I_ViewController_setIvar3_}}
};
_OBJC_$_INSTANCE_VARIABLES_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
3,
{{(unsigned long int *)&OBJC_IVAR_$_ViewController$_ivar1, "_ivar1", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_ViewController$_ivar2, "_ivar2", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_ViewController$_ivar3, "_ivar3", "@\"NSString\"", 3, 8}}
};
可以看出生成的var_list
有_ivar1
_ivar2
_ivar3
,证明通过@property
修饰的成员变量编译器会协助生成ivar
,同时能看出有生成ivar3
和setIvar3
函数,能够证明通过@property
修饰的成员变量编译器会帮助生成set
和get
方法。
接下来看下 class_copyIvarList
和objc_property_t
的源码,
Ivar *
class_copyIvarList(Class cls, unsigned int *outCount)
{
const ivar_list_t *ivars;
Ivar *result = nil;
unsigned int count = 0;
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
assert(cls->isRealized());
// 这里可以看到 ivars 是从 read only 数据段中取出的
if ((ivars = cls->data()->ro->ivars) && ivars->count) {
result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
for (auto& ivar : *ivars) {
if (!ivar.offset) continue; // anonymous bitfield
result[count++] = &ivar;
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
objc_property_t *
class_copyPropertyList(Class cls, unsigned int *outCount)
{
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
assert(cls->isRealized());
auto rw = cls->data();
property_t **result = nil;
unsigned int count = rw->properties.count();
if (count > 0) {
result = (property_t **)malloc((count + 1) * sizeof(property_t *));
count = 0;
for (auto& prop : rw->properties) {
result[count++] = ∝
}
result[count] = nil;
}
if (outCount) *outCount = count;
return (objc_property_t *)result;
}
可以看出ivars
是通过cls->data()->ro->ivars
得到的,而ro
的意思是 read only
。而property_t
是通过cls->data()->rw->properties
来获取的,他们的区别是ivars
是一个只读的数据,在类被创建完成之后就不允许再更改,获取的是全部的实例成员变量。而properties
是一个可读可写的数据,获取的只是通过@property
修饰的成员变量,而在分类中@property
修饰的成员变量并不会真正的生成ivar
。
class_rw_t 和 class_ro_t 的区别
class_ro_t
的数据是编译期就确定的。class_rw_t
是运行期间确定的。
category如何被加载的
通过runtime
入口函数_objc_init
查看,运行过程是map_images->_read_images
,而在_read_images
函数中有以下代码,通过代码可以看出加载category
时只添加了instanceMethods
protocols
instanceProperties
和 classMethods
_classProperties
// Discover categories.
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);
}
}
}
}
两个category的同名方法的加载顺序
通过上面的代码了解到,分类函数添加到类里面的实现的第一步是addUnattachedCategoryForClass
函数。通过查看源码实现可以看到是将所有的分类都顺序的添加到一个已class
为key
的全局的NXMapTable
中。
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc( (*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);
}
第二步是remethodizeClass
,而它调用了attachCategories
函数。
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)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
通过 attachCategories
的代码查看,逆序的将分类中的method
property
protocol
添加进入cls->data()->rw
中。
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);
}
category的load方法的加载顺序
通过_objc_init->load_images
查看源码会发现主要调用了两个函数prepare_load_methods
和call_load_methods
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
其中prepare_load_methods
主要做了以下动作
- 获取所有类,将所有类的
load
函数添加进loadable_classes
中。如果有父类优先添加父类的load
函数。 - 获取所有的未加载的
categorylist
,此时获取的list
的顺序为编译期就已经确定好的顺序。将所有分类实现的load
函数添加进loadable_categories
中
1 |
|
call_load_methods
做了哪些事情可以通过注释来解释
- 调用所有的类和分类的
+load
函数 - 优先调用父类的
- 分类的调用在父类调用之后
1 |
|
category & extension区别
先说结论extension
是编译期做的事情,category
是运行期做的事情。写在extension
里面的函数等等在编译期就已经确定,在运行时初始化的时候就存在。category
是在runtime
运行时才将函数等添加进类或者元类当中。
能给 NSObject 添加 Extension 吗
结论是可以,并且在Extension
实现的函数在编译期间就会被添加进结构体中。首先编写以下代码
@implementation NSObject
- (void)testFuntion
{
}
@end
通过clang
将其便以为cpp
来查看下。可以看到以下结构
static struct _class_ro_t _OBJC_METACLASS_RO_$_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
3, sizeof(struct _class_t), sizeof(struct _class_t),
(unsigned int)0,
0,
"NSObject",
0,
0,
0,
0,
0,
};
static struct _class_ro_t _OBJC_CLASS_RO_$_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2, __OFFSETOFIVAR__(struct NSObject, isa), sizeof(struct NSObject_IMPL),
(unsigned int)0,
0,
"NSObject",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_NSObject,
(const struct _objc_protocol_list *)&_OBJC_CLASS_PROTOCOLS_$_NSObject,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_NSObject,
0,
0,
};
_OBJC_$_INSTANCE_METHODS_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"testFuntion", "v16@0:8", (void *)_I_NSObject_testFuntion}}
};
IMP、SEL、Method的区别和使用场景
这个没什么好说的
- IMP可以认为是持有了函数地址的指针,可以直接调用
- SEL可以认为是函数名的
hash
值,全局只存在一份。 - Method内部持有了
IMP
函数名 和 函数返回类型。
load、initialize方法的区别什么?在继承关系中他们有什么区别
load
上面有写过,在_objc_init
时调用,调用顺序:父类 > 子类 > 分类,一个类只会调用一次。而initialize
则是在对象第一次接受到消息的时候调用,具体源码可以查看lookUpImpOrForward
。这个函数是由objc_msgSend
调用的。其中有这样的代码片段
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
逐步跟随initializeAndLeaveLocked
-> initializeAndMaybeRelock
-> initializeNonMetaClass
的调用链可以看到如下代码片段实现。
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(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()) {
initializeNonMetaClass(supercls);
}
...
...
...
{
callInitialize(cls);
}
...
...
...
}
根据代码分析得出结论为如果有父类同时父类有实现initialize
同时父类并未调用过initialize
时优先调用父类的initialize
。