新浦京81707con > 软件下载 > initialize

原标题:initialize

浏览次数:80 时间:2020-04-15

从初叶化的类方法initialize起头:为何要说它?因为那么些办法很懒。不相同于load方法,在main函数实行前就早就奉行,initialize格局只在类第叁遍被加载的时候调用。以下是代码示例:

最早的文章链接: http://draveness.me/initialize/

关注饭馆,及时获取立异:iOS-Source-Code-Analyze
Follow: Draveness · Github

因为 ObjC 的 runtime 只好在 Mac OS 下技艺编写翻译,所以随笔中的代码都以在 Mac OS,也正是 x86_64 结构下运营的,对于在 arm64 中运作的代码会特意表达。

NSObject类中有多少个独出机杼形式:loadinitialize,那七个办法有如何界别吧?怎样利用啊?调用途景是何等?

  initialize { [super initialize]; NSLog(@"initialize");}- (instancetype)init { self = [super init]; if  return nil; NSLog; return self;}

写在前头

那篇小说也许是对 Objective-C 源代码拆解解析种类小说中最短的一篇了,在 Objective-C 中,大家连年会同不平日间想到 loadinitialize 那七个类措施。而那三个措施也一时在联合签字比较:

在上一篇介绍 load 方法的文章中,已经对 load 方法的调用机缘、调用顺序实行了详细地剖析,所以对于 load 方法,这里就不在赘述了。

那篇小说会假设你知道:举个例子你是 iOS 开垦者。

本文子禽首要介绍:

  1. initialize 方法的调用为什么是惰性的
  2. 那货能干啥

正文重视的代码:SuperLoad类

调节台打字与印刷如下:图片 1image.png

initialize 的调用栈

在言之有序其调用栈此前,首先来解释一下,什么是惰性的。

这是 main.m 文件中的代码:

#import <Foundation/Foundation.h>

@interface XXObject : NSObject @end

@implementation XXObject

  (void)initialize {
    NSLog(@"XXObject initialize");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool { }
    return 0;
}

主函数中的代码为空,假若大家运营那几个顺序:

objc-initialize-print-nothing

您会开采与 load 方法分化的是,就算大家在 initialize 方法中调用了 NSLog。但是程序运营之后并没有别的输出。

比如,大家在电动释放池中进入以下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __unused XXObject *object = [[XXObject alloc] init];
    }
    return 0;
}

再运路程序:

objc-initialize-print-initialize

您会发掘,纵然我们从不直接调用 initialize 方法。不过,这里也打字与印刷出了 XXObject initialize 字符串。

initialize 只会在对应类的措施第叁回被调用时,才会调用

我们在 initialize 方法中打一个断点,来查看那一个法子的调用栈:

objc-initialize-breakpoint

0  [XXObject initialize]
1 _class_initialize
2 lookUpImpOrForward
3 _class_lookupMethodAndLoadCache3
4 objc_msgSend
5 main
6 start

一贯来看调用栈中的 lookUpImpOrForward 方法,lookUpImpOrForward 方法只会在向指标发送音信,况且在类的缓存中尚无找到新闻的选用丑时才会调用,具体可以看那篇文章,从源代码看 ObjC 中国国投息的出殡。

在此边,大家精通 lookUpImpOrForward 方法是 objc_msgSend 触发的就够了。

objc-initialize-print-selecto

在 lldb 中输入 p sel 打字与印刷选用子,会发现脚下调用的不二秘诀是 alloc 方法,也正是说,initialize 方法是在 alloc 方法从前调用的,alloc 的调用以致了前面一个的执行。

其中,使用 if (initialize && !cls->isInitialized()) 来决断当前类是不是起头化过:

bool isInitialized() {
   return getMeta()->data()->flags & RW_INITIALIZED;
}

近些日子类是不是开始化过的音讯就保存在元类的 class_rw_t 构造体中的 flags 中。

这是 flags 中保存的音讯,它记录着跟当前类的元数据,个中第 16-34人好似下的功效:

objc-initialize-class_rw_t_-bits-flag

flags 的第 29 位 RW_INITIALIZED 就封存了近期类是不是开首化过的音信。

#import "SuperLoad.h"

@implementation SuperLoad

 (void)load {
    NSLog(@"我是 superLoad,我被触发了");
}

 (void)initialize {
    NSLog(@"我是 superInitialize,我被触发了");
}

@end

多次多当下类举行开首化,不过initialize办法,只在第三次成立该类的实例时调用了。但有叁个疑点,那该类的子类在开马上,是不是也不会再调用initialize办法了呢?

_class_initialize 方法

initialize 的调用栈中,直接调用其艺术的是上边的这一个 C 语言函数:

void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // 1. 强制父类先调用 initialize 方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    {
        // 2. 通过加锁来设置 RW_INITIALIZING 标志位
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }

    if (reallyInitialize) {
        // 3. 成功设置标志位,向当前类发送  initialize 消息
        _setThisThreadIsInitializingClass(cls);

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        // 4. 完成初始化,如果父类已经初始化完成,设置 RW_INITIALIZED 标志位,
        //    否则,在父类初始化完成之后再设置标志位。
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        return;
    } else if (cls->isInitializing()) {
        // 5. 当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            monitor_locker_t lock(classInitLock);
            while (!cls->isInitialized()) {
                classInitLock.wait();
            }
            return;
        }
    } else if (cls->isInitialized()) {
        // 6. 初始化成功后,直接返回
        return;
    } else {
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

办法的显要职能自然是向未开始化的类发送 initialize 音信,但是会抑遏父类头阵送 initialize

  1. 强制未起先化过的父类调用 initialize 方法

    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
  2. 通过加锁来设置 RW_INITIALIZING 标志位

    monitor_locker_t lock(classInitLock);
    if (!cls->isInitialized() && !cls->isInitializing()) {
        cls->setInitializing();
        reallyInitialize = YES;
    }
    
  3. 打响安装标识位、向当前类发送 initialize 消息

    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    
  4. 成就开始化,要是父类已经初叶化达成,设置 RW_INITIALIZED 标记位。不然,在父类伊始化完结之后再安装标志位

    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
    
  5. 假定当前线程正在起头化当前类,直接回到,不然,会等待别的线程开端化甘休后,再回去,确认保证线程安全

    if (_thisThreadIsInitializingClass(cls)) {
        return;
    } else {
        monitor_locker_t lock(classInitLock);
        while (!cls->isInitialized()) {
            classInitLock.wait();
        }
        return;
    }
    
  6. 初阶化成功后,直接回到

    return;
    

SubLoad类

于是乎三番三回示举例下:

治本起初化队列

因为大家平昔要确认保障父类的早先化方法要在子类在此以前调用,所以大家供给保险二个 PendingInitializeMap 的数据构造来囤积如今的类开头化必要哪些父类先初阶化完毕

PendingInitializeMap

本条数据构造中的新闻会被三个点子改造:

if (!supercls  ||  supercls->isInitialized()) {
  _finishInitializing(cls, supercls);
} else {
  _finishInitializingAfter(cls, supercls);
}

分别是 _finishInitializing 以及 _finishInitializingAfter,先来看一下继承者是怎么贯彻的,相当于在父类未有造成最初化的时候调用的秘诀:

static void _finishInitializingAfter(Class cls, Class supercls)
{
    PendingInitialize *pending;
    pending = (PendingInitialize *)malloc(sizeof(*pending));
    pending->subclass = cls;
    pending->next = (PendingInitialize *)NXMapGet(pendingInitializeMap, supercls);
    NXMapInsert(pendingInitializeMap, supercls, pending);
}

因为脚下类的父类未有伊始化,所以会将子类出席贰个数据架构 PendingInitialize 中,这几个数据结构其实就贴近于八个封存子类的链表。那几个链表会以父类为键存款和储蓄到 pendingInitializeMap 中。

NXMapInsert(pendingInitializeMap, supercls, pending);

而在父类已经调用了开端化方法的景观下,对应方法 _finishInitializing 的贯彻就有一点有个别复杂了:

static void _finishInitializing(Class cls, Class supercls)
{
    PendingInitialize *pending;

    cls->setInitialized();

    if (!pendingInitializeMap) return;
    pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
    if (!pending) return;

    NXMapRemove(pendingInitializeMap, cls);

    while (pending) {
        PendingInitialize *next = pending->next;
        if (pending->subclass) _finishInitializing(pending->subclass, cls);
        free(pending);
        pending = next;
    }
}

第一,由于父类已经完毕了初步化,在这一贯将近期类标识成已经起先化,然后递归地将被日前类 block 的子类标志为已初步化,再把那个当类移除 pendingInitializeMap

#import "SubLoad.h"

@implementation SubLoad

 (void)load {
    NSLog(@"我是 SubLoad,我被触发了");
}

 (void)initialize {
    NSLog(@"我是 SubInitialize,我被触发了");
}

@end
@interface SubViewController : ViewController@end

// Appdelegate代码如下- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.rootViewController = SubViewController.new; [self.window makeKeyAndVisible]; return YES;}

小结

到这里,我们对 initialize 方法的钻研基本春天经终止了,这里会总括一下有关其形式的特性:

  1. initialize 的调用是惰性的,它会在首先次调用当前类的章程时被调用
  2. load 不同,initialize 方法调用时,全体的类都早已加载到了内部存款和储蓄器中
  3. initialize 的运作是线程安全的
  4. 子类会继承父类的 initialize 方法

而其作用也极度局限,平时大家只会在 initialize 方法中实行部分常量的起头化。

main函数

调整台消息如下:图片 2image.png

仿效资料

  • What is a meta-class in Objective-C?
  • NSObject load and initialize - What do they do?

关注饭馆,及时获得改善:iOS-Source-Code-Analyze
Follow: Draveness · Github

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main函数调用");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

再次创下造三个SubViewController的子类,调节台新闻如下:

本文的demo地址

图片 3image.png

1、区别

方今,能够知晓成,子类中的initialize艺术也会奉行壹遍,也足以知道成,父类的initialize被调用了壹回。

1.1 load方法和initialize调用机缘

运转代码之后,可以知道调控台打字与印刷为:

2018-03-20 10:43:47.923316 0800 我是 superLoad,我被触发了
2018-03-20 10:43:51.014019 0800 我是 SubLoad,我被触发了
2018-03-20 10:44:11.318210 0800 main函数调用
2018-03-20 10:44:11.393657 0800 我是 superInitialize,我被触发了
2018-03-20 10:44:13.431475 0800 我是 SubInitialize,我被触发了

有鉴于此,load方法是在main函数以前调用,initialize函数是在main函数之向外调拨运输用。

疑问

1.2 load方法和initialize方法在这里起彼伏关系中的差别

将SuperLoad类中的load方法注释,SubLoad类中的initialize方法注释,运营代码,可以知道调整台打字与印刷:

2018-03-20 11:29:24.933753 0800 我是 SubLoad,我被触发了
2018-03-20 11:29:24.934582 0800 main函数调用
2018-03-20 11:29:25.011858 0800 我是 superInitialize,我被触发了
2018-03-20 11:29:25.011961 0800 我是 superInitialize,我被触发了

将SuperLoad类中的load方法张开,SubLoad类中的load方法注释,运营代码,可以预知调控台打字与印刷:

2018-03-20 11:29:24.933753 0800 我是 superLoad,我被触发了
2018-03-20 11:29:24.934582 0800 main函数调用
2018-03-20 11:29:25.011858 0800 我是 superInitialize,我被触发了
2018-03-20 11:29:25.011961 0800 我是 superInitialize,我被触发了

总的来说,load和initialize方法都毫无展现的调用父类的办法而是自行调用,固然子类没有initialize方法也会调用父类的点子,而load方法规不会调用父类。

怎么确定保证此类或该类的子类只会调用一回initialize主意吧?测验代码如下:

1.3 各自行使的气象

load方法日常用来实行Method Swizzle,initialize方法日常用于早先化全局变量或静态变量

  initialize { if (self == [ViewController self]) { // ... do the initialization ... NSLog(@"initialize"); }}

1.4 当增添categroy时,load和initialize的调用机制是如何的

加上如下代码

@implementation SuperLoad(Category)
 (void)load {
    NSLog(@"我是 CategoryLoad,我被调用了");
}

 (void)initialize {
    NSLog(@"我是 CategoryInitialize,我被触发了");
}
@end

调整台打字与印刷:

2018-03-21 15:04:33.234859 0800 我是 superLoad,我被触发了
2018-03-21 15:04:33.235951 0800 我是 SubLoad,我被触发了
2018-03-21 15:04:33.236106 0800 我是 CategoryLoad,我被调用了
2018-03-21 15:04:33.236282 0800 main函数调用
2018-03-21 15:04:33.336564 0800 我是 CategoryInitialize,我被触发了
2018-03-21 15:04:33.336682 0800 我是 SubInitialize,我被触发了

世家能够见到,load的调用顺序为:父类load ---> 子类load ---> Category的load;而Category的initialize直接就覆盖了父类的initialize。

图片 4image.png

2、结合runtime源码来领悟一下为何会促成上述的分别和差异

本文由新浦京81707con发布于软件下载,转载请注明出处:initialize

关键词: 新浦京81707con initialize iOS 源

上一篇:源代码管理工具,git的终端操作命令

下一篇:没有了