OC--load、initialize、一些NSObject方法

load、initialize

Load和Initialize往死了问是一种怎样的体验?
懒惰的 initialize 方法

+ load与+ initialize的异同

load流程

load.png

initialize代码

1、先调用父类superclass的initialize
2、再调用当前类的initialize

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

initalize.png

initialize与main的先后有特殊情况:在A类的load 方法中调用了B类的类方法

@implementation Father
+ (void)load {
    NSLog(@"father==> load===%@", [Dog class]);
}

+(void)initialize {
    NSLog(@"Father===>initialize");
}
@end

#warning 打印结果如下
2017-08-09 11:19:09.838 tests[34274:8415363] Dog===>initialize
2017-08-09 11:19:09.839 tests[34274:8415363] father==> load===Dog
2017-08-09 11:19:09.839 tests[34274:8415363] Dog==> load
2017-08-09 11:19:09.840 tests[34274:8415363] child==> load
2017-08-09 11:19:09.840 tests[34274:8415363] child + hahha==> load
2017-08-09 11:19:09.840 tests[34274:8415363] main

在 Initialize 方法内部可以进行一些不方便在编译期进行初始化的静态变量的赋值

#warning Person.m
// int 等基本类型可以在编译期进行赋值
static int numCount = 0; 
// 对象无法在编译器进行赋值
static NSMutableArray *dataSource;

+ (void)initialize {
    if (self == [Person class]) {
        // 不能在编译期赋值的对象在这里进行赋值
        dataSource = [[NSMutableArray alloc] init];
    }
}

避免父类的initialize被调用多次(如果当前类没有initialize方法就会调用父类的),可以通过类名判断

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

总结:

1、load 方法内部一般用来实现 Method Swizzle
2、Initialize方法一般用来初始化全局变量或者静态变量(对象类型)

一些NSObject方法

各种performSelector方法

/**
 向接收方发送一条指定的消息,并返回消息的结果
 @param aSelector 需要执行的方法(方法允许您发送在运行时才确定的消息。这意味着您可以传递一个变量选择器作为参数)
 @param object1 参数1
 @param object2 参数2
 @return 返回消息的结果
 */

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

/**
 在当前线程中调用一个方法,这个方法是异步的,必须在主线程调用,子线程无效,子线程可以用dispatch_after来代替
 注意:内部大概是创建一个定时器NSTimer

 @param aSelector 需要执行的方法
 @param anArgument 传递的参数
 @param delay 指定的时间之后
 @param modes Runloop模式(默认NSDefaultRunLoopMode)
 */
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

/**
 取消方法调用请求
 (对于使用performSelector:withObject:afterDelay:方法(仅限于此方法)注册的执行请求)
(不过仅限于当前run loop,而不是所有的)

 @param aTarget 指定对象
 @param aSelector 指定方法
 @param anArgument 指定参数
 */
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;


/**
 主线程上执行某个对象的方法

 @param aSelector 需要执行的方法
 @param arg 方法参数
 @param wait YES,则当前线程被阻塞
 @param array Runloop模式(默认NSDefaultRunLoopMode)
 */
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/**
 指定线程线程上执行某个对象的方法
 
 @param aSelector 需要执行的方法
 @param thr 指定线程
 @param arg 方法参数
 @param wait YES,则当前线程被阻塞
 @param array Runloop模式(默认NSDefaultRunLoopMode)
 */
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

/**
 后台线程中调用接收者的方法
(这个方法会在程序中创建一个新的线程。由aSelector表示的方法必须像程序中的其它新线程一样去设置它的线程环境)

 @param aSelector 需要执行的方法
 @param arg 方法参数
 */
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;

methodForSelector

NSObject类提供了两个方法来获取一个selector对应的方法实现的地址,如下所示:

- (IMP)methodForSelector:(SEL)aSelector
+ (IMP)instanceMethodForSelector:(SEL)aSelector

获取到了方法实现的地址,我们就可以直接将IMP以函数形式来调用。

对于methodForSelector:方法,如果接收者是一个对象,则aSelector应该是一个实例方法;如果接收者是一个类,则aSelector应该是一个类方法。

对于instanceMethodForSelector:方法,其只是向类对象索取实例方法的实现。如果接收者的实例无法响应aSelector消息,则产生一个错误。

instancesRespondToSelector与respondToSelector的区别

1、 instancesRespondToSelector只能写在类名后面,respondsToSelector可以写在类名和实例名后面。
2、[类 instancesRespondToSelector]判断的是该类的实例是否包含某方法,等效于:[该类的实例respondsToSelector]。
3、[类 respondsToSelector]用于判断是否包含某个类方法。

+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (BOOL)respondsToSelector:(SEL)aSelector;

-(BOOL)conformsToProtocol 与 +(BOOL)conformsToProtocol 的区别

// 当前类检测不到,不会检查父类
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 当前类检测不到,会检查父类
+ (BOOL)conformsToProtocol:(Protocol *)protocol;

class、superclass、isKindOfClass、isMemberOfClass

// 该方法返回类对象
+ (Class)class;
// 获取接收者的父类类对象
+ (Class)superclass;
// 查看一个类是否是另一个类的子类,
+ (BOOL)isSubclassOfClass:(Class)aClass;
isKindOfClass、isMemberOfClass
  • 联系:两者都能检测一个对象是不是某个类的成员
  • 区别:isKindOfClass可以检测到父类,如:ClassA *a = [ClassA alloc] init];,[a isKindOfClass:[NSObject class]] 可以检查出 a 是否是 NSObject派生类
+ (Class)class、- (Class)class区别

BBObj *obj = [BBObj new];
1、[BBObj class] = BBObj类对象
2、[obj j class] = object_getClass(obj) = obj->isa,就是BBObj类对象

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
isKindOfClass、isMemberOfClass

源码

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

例子:

    BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [[UIButton class] isKindOfClass:[UIButton class]];
    BOOL res4 = [[UIButton class] isMemberOfClass:[UIButton class]];
    
    NSLog(@"%d %d %d %d", res1, res2, res3, res4);//输出 1 0 0 0

解析:


Runtime 各种Class关系.jpg

(1)+ (BOOL)isKindOfClass:(Class)cls方法内部,会先去获得object_getClass的类,而object_getClass的源码实现是去调用当前类的obj->getIsa(),最后在ISA()方法中获得meta class的指针。(+ (BOOL)isKindOfClass:(Class)cls是用meta class来对比cls)

(2)接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。

(3)[NSObject class]执行完之后调用isKindOfClass,第一次判断先判断NSObject 和 NSObject的meta class是否相等,之前讲到meta class的时候放了一张很详细的图,从图上我们也可以看出,NSObject的meta class与本身不等。接着第二次循环判断NSObject与meta class的superclass是否相等。还是从那张图上面我们可以看到:Root class(meta) 的superclass 就是 Root class(class),也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES。
其他三个同样这么撸下去,全是0

特殊的

    NSArray *arr = [[NSArray alloc] init];
    BOOL a = [arr isMemberOfClass:[NSArray class]];// a = NO;因为arr是__NSArray0, NSArray是一个抽象的基类。这种模式就是了[类簇模式](http://blog.csdn.net/u013016828/article/details/41720353).

自定义对象的归档与解档大神终结入口1----大神终结入口2

NSData *cityData = [NSKeyedArchiver archivedDataWithRootObject:model];// model要现实NSCoding协议,initWithCoder:和encodeWithCoder
model = [NSKeyedUnarchiver unarchiveObjectWithData:data];

// 1.遵循NSCoding协议
@interface Person : NSObject <NSCoding>
// 2.设置属性
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end

@implementation Person
// 解档
- (id)initWithCoder:(NSCoder *)aDecoder {
    // 父类现实就调用[super initWithCoder:aDecoder]
    // 父类没有现实[super init]
    if (self = [super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}
// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
     // 父类现实就调用[super encodeWithCoder:aCoder];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容