详解load 函数

详解load 函数

因为下载了一个可以调试的runtime代码,所以重新阅读以下runtime的一些代码,写点栗子跑一跑

众所周知,load 的调用时机是这个class加载的时候就调用的,这篇文章主要弄清楚

  1. load调用的具体时机
  2. 父类子类的load调用 顺序
  3. 分类 和 原始类的 load 调用顺序
  4. 子类分类load 的调用顺序

暂时就这么多。ok,我们随便建立一个工程,

@implementation ViewController

+ (void)load {
    NSLog(@"ViewController");
}

@end


@interface sonViewController : ViewController
@end


@implementation sonViewController

+ (void)load {
    NSLog(@"sonViewController");
}

@end

@implementation ViewController (ll)

+ (void)load {
    NSLog(@"ViewController (ll)");
}

@end

@implementation sonViewController (ll)

+ (void)load {
    NSLog(@"sonViewController (ll)");
}

@end

我们看看打印结果:


ViewController
sonViewController
ViewController (ll)
sonViewController (ll)

我们可以清楚的看到,调用顺序是这样子的。我们可以通过其他手段来看看,为什么是这种调用顺序。从git 上下载一份可以调试的runtime源码。

https://github.com/0xxd0/objc4

运行,没有问题的话我们就可以巴拉巴拉这个代码了。

重点在这个函数

从函数命名可以看出,是调用load 函数的。

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

从这个函数可以看出, 在一个 do {}while 循环里面,做的事情,
不断的尝试调用 call_class_loads ,全局有一个列表,来标示,稍后分析。

然后才调用分类的load函数。

more_categories = call_category_loads();

调用分类的load 函数。。

此处,我们能得出结论,先调用所有已经加载类的load函数,然后调用分类的load.那么子类和父类的调用关系在哪里看呢?

我们查看调用类load 方法的函数

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方法
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

从这里,我们可以看到,就是全局有一个loadable_classes 数组,然后顺序遍历这个数组,调用对应的load方法。所以,父类子类的关系,肯定是父类子类放入表中的时机不一样。我们重点关注一下,放入先后顺序问题上。

调用顺序如下:

{
    load_images
         prepare_load_methods
            schedule_class_load
            add_category_to_loadable_list
}

通过 prepare_load_methods 函数,可以看出,先把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); 
}

把一个类add_class_to_loadable_list 之前,先尝试 superView schedule_class_load(cls->superclass);

如果superView 已经加载过了就return;

这就是为什么super的load 比子类的load调用时机早

分类的load方法调用顺序,完全取决于 _getObjc2NonlazyCategoryList 获取到的分类顺序啦!

我们来理一理重要函数的代码模块

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

runtime 一开始会执行这个函数,这个函数做了两件事情,初始化工作,和 注册事件,通过 _dyld_objc_notify_register 注册了 load_images 函数,如果有新的镜像被加入,load_images 函数就会执行。那么load_images 函数里干了什么事情?

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover +load methods
    prepare_load_methods((const headerType *)mh);

    // Call +load methods (without classLock - re-entrant)
    call_load_methods();
}

Mach-O是OSX和iOS上的可执行二进制文件格式:Mach-Object 这里struct mach_header 是 Mach-Object 的header

prepare_load_methods 字面意思上看,就是发现类有没有实现load函数。
call_load_methods 调用上面发现的load函数。

load 的代替方案,

使用initialize 代替

initialize,可以延迟调用时机,如果app中大量使用了load 有可能导致启动慢

使用

__attribute__ 

<code>attribute</code> 是GNU C特色之一,在iOS用的比较广泛。如果你没有用过,那系统库你总用过,在Foundation.framework中有很多地方用到attribute特性。attribute 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)

比如我们下面的代码

@interface TDDObj : NSObject
@end

@implementation TDDObj
+ (void)load {
    NSLog(@"1111");
}
@end

__attribute((constructor)) static void beforeMainCall(void) {
    NSLog(@"11111");
}


int main(int argc, char * argv[]) {
    @autoreleasepool {
    @try
        {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
        @catch(NSException* exception)
        {
            PTVLog(@"exception:%@", exception);
        }
    }
}

我们通过断点或者观察输出log,可以看到,TDDObj 的 load 函数先调用,然后 beforeMainCall 调用,接着,main 函数调用了。
通过这种手段,可以做一些有趣的事情,比如遍历所有类,如果实现了谋改革协议或者方法做些事情,如果所有的类load里面做完事情,要进一步在 main 之前处理,就可以通过这种手段。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,672评论 0 9
  • 之前在写《Category你真的懂吗?》那篇简书收集资料的时候,看了很多load和initialize的资料,加深...
    一剑孤城阅读 2,651评论 3 24
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,513评论 18 399
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,525评论 33 466
  • 冥想很有用,但估计没几个人能为了这个效果,坚持数年、甚至数十年的冥想,心理学家对冥想的时效性做过研究: 研究员找来...
    董董在这里阅读 274评论 0 0