iOS中 性能优化之浅谈load与initialize

一. +load

源码分析

extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

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
    {
        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

时间: 2024-10-27 12:31:54

iOS中 性能优化之浅谈load与initialize的相关文章

android App性能优化技巧浅谈

Android App性能优化,安卓App性能优化技巧,无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的.那么,如何能开发出更高性能的Android App?相信是软件开发公司以及广大程序员们头疼的一大难题.今天,就给大家提供几个提高Android App性能的技巧. 高效地利用线程1.在后台取消一些线程中的动作 我们知道App运行过程中所有的操作都默认在主线程(UI线程)中进行的,这样App的响应速度就会受

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

iOS 程序性能优化

前言 转载自:http://www.samirchen.com/ios-performance-optimization/ 程序性能优化不应该是一件放在功能完成之后的事,对性能的概念应该从我们一开始写代码时就萦绕在我们脑子里.了解 iOS 程序性能优化的相关知识点,从一开始就把它们落实到代码中是一种好的习惯. 初级技巧 使用复用机制 在我们使用 UITableView 和 UICollectionView 时我们通常会遇到「复用 Cell」这个提法,所谓「复用 Cell」就是指当需要展示的数据条

ASP.NET中Session的个人浅谈

看到博客园的一个哥们写的面试经历,想到了面试中常问到的Session,一时手痒就谈下自己对Session的理解,这东西最开始在用户登录登出的时候用到过,后来一直没怎么用过,里面还是有很多知识点值得注意的.先简单的说下吧,Session是分为客户端Session和服务端Session: 客户端Session Session称为会话状态,是Web系统中最常用的状态,用于维护和当前浏览器实例相关的一些信息,当用户首次与Web服务器建立连接的时候,服务器会给当前访问用户分发一个 SessionID作为标

对kotlin和java中的synchronized的浅谈

synchronized在java中是一个关键字,但是在kotlin中是一个内联函数.假如分别在java和kotlin代码锁住同一个对象,会发生什么呢,今天写了代码试了试.首先定义people类 12345678910111213 public class { public void () { for (int i = 0; i < 10; i ++) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStac

iOS的性能优化建议

1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露.它自动为你管理retain和release的过程,所以你就不必去手动干预了.忘掉代码段结尾的release简直像记得吃饭一样简单.而ARC会自动在底层为你做这些工作.除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存.   2. 在正确的地方使用 reuseIdentifier

ios 常见性能优化

1. 用ARC管理内存 2. 在正确的地方使用reuseIdentifier 3. 尽可能使Views透明 4. 避免庞大的XIB 5. 不要block主线程 6. 在Image Views中调整图片大小 7. 选择正确的Collection 8. 打开gzip压缩 9. 重用和延迟加载Views 10. Cache, Cache, 还是Cache! 11. 权衡渲染方法 12. 处理内存警告 13. 重用大开销的对象 14. 使用Sprite Sheets 15. 避免反复处理数据 16. 选

iOS App 性能优化总结

今天简单总结一些clientapp 优化的方案和方向. 我相信开发一个app大部分团队都能够完毕,可是性能久不一样啦,和我们都写一个冒泡算法一样,我相信每一个人写的冒泡算法都不一样,这些区别就带来了性能的区别. 所以一个好的app 不止看设计.和创意 ,还要看性能. 以下我就简单说几点性能优化点: 一.首页启动速度 启动过程中做的事情越少越好(尽可能将多个接口合并) 不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新节目) 在合适的时机開始后台任务(比如在用户指引节目就能够

iOS app性能优化(转)

iPhone上面的应用一直都是以流畅的操作体验而著称,但是由于之前开发人员把注意力更多的放在开发功能上面,比较少去考虑性能的问题,可能这其中涉及到objective-c,c++跟lua,优化起来相对复杂一些,导致应用在比如touch等较低端的产品上,光从启动到进入页面就花了将近一分钟的时间,页面之间的切换没有那种很流畅的感觉,内存也居高不下,比较影响应用的用户体验,所以很有必要进行一些优化,下面记录一下我在优化的过程中的一些心得: 1 instruments ??在iOS上进行性能分析的时候,首