iOS中 性能优化之浅谈load与initialize 韩俊强的博客

来源:互联网 发布:JAVA远程连接db2数据库 编辑:程序博客网 时间:2024/05/17 12:51

一. +load

源码分析

extern bool hasLoadMethods(const headerType *mhdr);extern void prepare_load_methods(const headerType *mhdr);voidload_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    {        rwlock_writer_t lock2(runtimeLock);        prepare_load_methods((const headerType *)mh);    }    // Call +load methods (without runtimeLock - re-entrant)    call_load_methods();}

在runtime源码中,我们可以看到,+load方法是在load_images中通过call_load_methods调用的。
更具体的来说是在运行时加载镜像时,通过prepare_load_methods方法将+load方法准备就绪,而后执行call_load_methods,调用+load方法。

1. prepare_load_methods

void prepare_load_methods(const headerType *mhdr){    size_t count, i;    runtimeLock.assertWriting();    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        realizeClass(cls);        assert(cls->ISA()->isRealized());        add_category_to_loadable_list(cat);    }}

prepare_load_methods方法中,分为两个步骤:
一是,获取了所有类后,遍历列表,将其中有+load方法的类加入loadable_class
二是,获取所有的类别,遍历列表,将其中有+load方法的类加入loadable_categories.

另外值得注意的一点是schedule_class_load方法的实现:

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    schedule_class_load(cls->superclass);    add_class_to_loadable_list(cls);    cls->setInfo(RW_LOADED); }

在该方法中会首先通过schedule_class_load(cls->superclass)确保父类中的 +load方法被加入loadable_class(如果父类有+load方法的话),从而保证父类的+load方法总是在子类之前调用。
也因此,在覆写+load方法时,不需要调用super方法。

2. call_load_methods

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;}

call_load_methods方法注释写得非常明了,首先调用类的load方法,在call_class_loads方法中通过在第一步读取prepare_load_methods步骤里的loadable_classes,遍历列表并调用+load方法,然后类似的调用类别的+load方法,第三步算是处女座的处理,处理异常。

3. call_class_loads

static void call_class_loads(void){    int i;    //1.获取列表    struct loadable_class *classes = loadable_classes;    int used = loadable_classes_used;    loadable_classes = nil;    loadable_classes_allocated = 0;    loadable_classes_used = 0;    //2.循环调用load方法    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;         (*load_method)(cls, SEL_load);    }    // 3. 释放列表    if (classes) free(classes);}

这里我们需要特别注意的是第二部分中:

  (*load_method)(cls, SEL_load);

这段代码也就是说+load方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend来发送消息.

也正是这句代码造就了+load方法的最大特点:类,父类与分类之间+load方法的调用是互不影响的.也就是,子类不会主动调用父类的+load方法,如果类与分类都实现了+load',那么两个+load`方法都会被调用.

小结


+load方法的调用顺序图

总得来说:

  1. +load方法是在main函数之前调用的;
  2. 遵从先父类后子类,先本类后列类别的顺序调用;
  3. 类,父类与分类之间的调用是互不影响的.子类中不需要调用super方法,也不会调用父类的+load方法实现;
  4. 无论该类是否接收消息,都会调用+load方法;

二. initialize

源码分析

在NSObject文件中,initialize的实现如下:

+ (void)initialize {}

然后找到了class_initialize方法,注释表明,当调用class_initialize方法时,就会给当前未初始化的类发送一条 +initialize消息。就是它了。
通过查看caller,我们会看到熟悉的lookUpImpOrForward,也就是消息转发

/************************************************************************ class_initialize.  Send the '+initialize' message on demand to any* uninitialized class. Force initialization of superclasses first.**********************************************************************/void _class_initialize(Class cls){    assert(!cls->isMetaClass());    Class supercls;    bool reallyInitialize = NO;    supercls = cls->superclass;    if (supercls  &&  !supercls->isInitialized()) {        _class_initialize(supercls);    }    {        monitor_locker_t lock(classInitLock);        if (!cls->isInitialized() && !cls->isInitializing()) {            cls->setInitializing();            reallyInitialize = YES;        }    }    if (reallyInitialize) {                _setThisThreadIsInitializingClass(cls);        @try {            callInitialize(cls);        }        @catch (...) {            if (PrintInitializing) {                _objc_inform("INITIALIZE: +[%s initialize] threw an exception",                             cls->nameForLogging());            }            @throw;        }        @finally {            if (!supercls  ||  supercls->isInitialized()) {                _finishInitializing(cls, supercls);            } else {                _finishInitializingAfter(cls, supercls);            }        }        return;    }    else if (cls->isInitializing()) {        if (_thisThreadIsInitializingClass(cls)) {            return;        } else {            waitForInitializeToComplete(cls);            return;        }    }    else if (cls->isInitialized()) {        return;    }    else {        _objc_fatal("thread-safe class init in objc runtime is buggy!");    }}

_class_initialize方法实现看起来比较长,但其实关键步骤也就只有两步:

  • 确保当前类的父类supercls已经初始化完成 -- 如果没有则通过_class_initialize(supercls)重新进入_class_initialize方法,初始化父类。

  • 处理当前类的初始化状态。

    第一步比较简单,不再赘述,只针对第二个步骤作分析.

    状态处理

当进入第二步,会首先根据当前类的初始化状态决定是否要发送初始化消息.

  • 未初始化
    1) 如果当前类未初始化,则会向它发送一个setInitializing消息,将该类的元类的信息更改为CLS_INITIALIZING,并通过reallyInitialize标识来与Initializing区分.
    2) 成功设置CLS_INITIALIZING后,_setThisThreadIsInitializingClass记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待.
    3) 通过callInitialize调用initialize方法:

  • void callInitialize(Class cls){ ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); asm("");}
  • 也就是说与+load不同该方法是通过objc_msgSend发送消息实现的,因此也拥有objc_msgSend带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类.

  • 4) 完成initialize方法后,更新当前类的状态.如果父类已经完成初始化,则_finishInitializing立马更新,否则通过_finishInitializingAfter等父类完成后再更新.

首先来看父类没有完成初始化时的处理 - _finishInitializingAfter :

static void _finishInitializingAfter(Class cls, Class supercls){    PendingInitialize *pending;    classInitLock.assertLocked();    if (PrintInitializing) {        _objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]",                     cls->nameForLogging(), supercls->nameForLogging());    }    if (!pendingInitializeMap) {        pendingInitializeMap =             NXCreateMapTable(NXPtrValueMapPrototype, 10);        // fixme pre-size this table for CF/NSObject +initialize    }    pending = (PendingInitialize *)malloc(sizeof(*pending));    pending->subclass = cls;    pending->next = (PendingInitialize *)        NXMapGet(pendingInitializeMap, supercls);    NXMapInsert(pendingInitializeMap, supercls, pending);}

在该方法中,通过声明一个PendingInitialize类型的结构体pending来存储当前类与父类信息,并以父类supercls为key值,以pending为value存储在pendingInitializeMap链表中.

而如果父类完成了初始化则进入_finishInitializing处理:

static void _finishInitializing(Class cls, Class supercls){    PendingInitialize *pending;    classInitLock.assertLocked();    assert(!supercls  ||  supercls->isInitialized());    if (PrintInitializing) {        _objc_inform("INITIALIZE: %s is fully +initialized",                     cls->nameForLogging());    }    // mark this class as fully +initialized    cls->setInitialized();    classInitLock.notifyAll();    _setThisThreadIsNotInitializingClass(cls);    // mark any subclasses that were merely waiting for this class    if (!pendingInitializeMap) return;    pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);    if (!pending) return;    NXMapRemove(pendingInitializeMap, cls);    // Destroy the pending table if it's now empty, to save memory.    if (NXCountMapTable(pendingInitializeMap) == 0) {        NXFreeMapTable(pendingInitializeMap);        pendingInitializeMap = nil;    }    while (pending) {        PendingInitialize *next = pending->next;        if (pending->subclass) _finishInitializing(pending->subclass, cls);        free(pending);        pending = next;    }}

在该方法中会首先将当前类标记为已完成初始化状态Initialized,然后去读取pendingInitializeMap,如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化.

  • 正在初始化
    如果是当前线程在进行初始化,则不做处理.
    如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全.

  • 已完成初始化
    如果已经完成初始化,则不做处理.

init

提到初始化方法,不可避免的会想到-init方法.

- (id)init {    return _objc_rootInit(self);}id_objc_rootInit(id obj){    return obj;}

由源码可以看到,-init方法并没有次数的限制,这也符合我们之前的认知.

那么+ initialize-init两者的调用顺序又是怎样的呢?
按上文的分析,我推测,由于首先向类发送了alloc消息,此时会触发+ initialize,然后才发送init消息,所以应该是先执行的+ initialize.
通过demo也证实了这一想法.

小结


+initialize流程图

总得来说:
1.+initialize方法是在main函数之后调用的;
2.+initialize方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;
3.子类中不需要调用super方法,会自动调用父类的方法实现;
4.+initialize只调用一次,init可多次调用.

三. + load与+ initialize的异同





更多:每周更新关注新浪微博!iOS开发者交流群:446310206

2 1
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 英雄联盟经常未响应怎么办 英雄联盟总是无响应怎么办 英雄联盟新客户端太卡怎么办 win10英雄联盟fps低怎么办 lol登游戏闪退怎么办 lol读取界面很慢怎么办 玩lol卡死黑屏怎么办 lol黑屏退不出来怎么办 电脑分辨率调高了黑屏怎么办 电脑设置分辨率黑屏了怎么办 分辨率调高了黑屏怎么办 电脑调分辨率黑屏了怎么办 科沃斯cr120遥控器丢了怎么办 买了kl色的钻戒怎么办 qq旋风没有蓝钻怎么办 手机桌面短信图标不见了怎么办 手机桌面qq音乐图标不见了怎么办 电脑显示器图标变大了怎么办 手机卡信号好但是网络不好怎么办 陌陌功能被限制怎么办 陌陌设备封了怎么办 荣耀v8手机开机键不灵怎么办 联通积分换的腾讯会员怎么办 小米6手机变卡了怎么办 微信绑定银行卡次数太多怎么办 银行卡绑定太多微信了怎么办 怎样给qq设密码怎么办 吃了心悦胶囊上火怎么办 qq暂时被冻结了怎么办 部落群审核未通过怎么办 qq被限制解封该怎么办 微信提现成功但没到账怎么办 拍拍贷登录不上怎么办 京东店铺出租保证金怎么办 所选地区无货怎么办 闲鱼七天没发货怎么办 续贷密码忘了怎么办 微店商家不发货怎么办 微店商家不退钱怎么办 维修车辆被拍违章停车怎么办 网贷申请平台太多怎么办