Runtime补充

class_copyIvarList & class_copyPropertyList区别

要想清楚这个问题,首先要知道@property做了什么事情。在对象中增加一个成员变量有两种方式

@interface ViewController ()
{
    NSString *_ivar1;
    NSString *_ivar2;
}
@property (nonatomic, copy) NSString *ivar3;
@end

其中的区别是通过@property修饰的成员变量,编译器在编译时会主动添加setget方法。同时会生成一个名为_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,同时能看出有生成ivar3setIvar3函数,能够证明通过@property修饰的成员变量编译器会帮助生成setget方法。

接下来看下 class_copyIvarListobjc_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 instancePropertiesclassMethods _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函数。通过查看源码实现可以看到是将所有的分类都顺序的添加到一个已classkey的全局的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_methodscall_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}

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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}

call_load_methods做了哪些事情可以通过注释来解释

  • 调用所有的类和分类的+load函数
  • 优先调用父类的
  • 分类的调用在父类调用之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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
while (loadable_classes_used > 0) {
call_class_loads();
}

// 2. Call category +loads ONCE
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;
}

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