OC load、initialize、一些NSObject方法

韋家冰發表於2017-12-13

###load、initialize Load和Initialize往死了問是一種怎樣的體驗? 懶惰的 initialize 方法

+ load與+ initialize的異同

####load流程

load.png

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

複製程式碼

###一些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

複製程式碼

解析: (1)+ (BOOL)isKindOfClass:(Class)cls方法內部,會先去獲得object_getClass的類,而object_getClass的原始碼實現是去呼叫當前類的obj->getIsa(),最後在ISA()方法中獲得meta class的指標。

(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
複製程式碼

相關文章