本篇文章將從幾個面試題出發(fā)坞淮,探究方法的歸屬以及isa與Superclass瓮恭。
objc_object 與對象的關系兰吟,objc_object 與 NSObject的關系
- 所有
對象
在底層都是以objc_object
為模版繼承
來的踊兜。 - 所有
對象
都是繼承自NSObject
(根類
)犹撒,而在底層中NSObject
是一個objc_object
(C/C++
)結構體跋选。
所以結論:objc_object
與對象
之間是繼承
關系锁孟。
屬性裸卫、成員變量仿贬、實例變量
-
屬性
:帶下劃線
的成員變量 +setter
+getter
,以@property
開頭定義的變量 -
成員變量
:定義在類.h文件的{}
中的墓贿,不帶下劃線的變量 -
實例變量
:特殊的成員變量茧泪,經過實例化的成員變量對象,例如UIButton
聋袋、UILabel
等 -
成員變量
中除去基本數(shù)據類型及{}中定義的NSString
類型變量队伟,剩下的都是實例化
后的實例變量,實例變量可理解為是擁有屬性
的對象
幽勒。
方法的歸屬分析
下面通過一個例子來探究實例方法及類方法的歸屬問題:
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayHello{
NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
@end
定義繼承于NSObject的LGPerson類嗜侮,并添加一個實例方法sayHello,一個類方法sayHappy啥容,下面通過幾個打印情況來分析:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 0x0000000100000000
// LGTeacher *teacher = [LGTeacher alloc];
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
方法從上而下的定義如下面:
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//獲取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元類 為什么有 sayHappy 類方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
下面我們先給出打印結果棘钞,然后逐個分析:
lgObjc_copyMethodList
object_getClass
獲取當前實例對象person
的類對象
。
此方法參數(shù)為Class
類型的pClass
,一個類對象
干毅,class_copyMethodList
獲取當前類對象中的方法列表宜猜,遍歷此類對象所有的實例方法,根據打印情況只打印sayHello
實例方法硝逢,可以驗證類對象里面存放實例方法
姨拥,類方法不存放在類對象中绅喉。
lgInstanceMethod_classToMetaclass
方法中的metaClass
為當前方法參數(shù)類對象pClass
的元類,由objc_getMetaClass
根據類獲取到元類叫乌,class_getInstanceMethod 獲取實例方法的源碼分析后可知柴罐,在傳入類及傳入類的父類一級一級找下去直到找到返回,沒找到返回null憨奸,下面分別分析幾個打印情況:
- method1:
0x1000031b0
因為
sayHello
是實例方法革屠,當前pClass
剛好也是存放sayHello
的LGPerson
類,所以能找到方法并打印
- method2:
0x0
因為
metaClass
為元類排宰,元類
(LGPerson
)--->根元類
(NSObject
)--->NSObject
--->nil
,發(fā)現(xiàn)找不到當前的sayHello
方法似芝,說明返回null
,打印為0x0
- method3:
0x0
當前需要找方法
sayHappy
板甘,類
(LGPerson
)--->根類
(NSObject
)--->nil
,沒找到sayHappy
方法党瓮,所以返回null
,打印0x0
- method4:
0x100003148
尋找類方法
sayHappy
盐类,metaClass
為當前類的元類寞奸,類方法存在類的元類中,所以此時在metaClass
中就找到sayHppy
方法在跳,即可返回并打印0x100003148
lgClassMethod_classToMetaclass
跟上面一樣枪萄,方法參數(shù)pClass
類對象,metaClass
為當前類的元類
猫妙,class_getClassMethod
獲取類方法源代碼如下:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
class_getClassMethod
源碼可見瓷翻,獲取類的類方法
就是去獲取當前類的元類的實例方法
,getMeta()
就是返回當前類的元類(如果getMeta()
方法當前調用者為元類
時吐咳,直接返回自己
,不是的話就返回當前調用者的元類
)元践,下面分別分析打印情況:
- method1:
0x0
sayhello
實例方法韭脊,class_getClassMethod
當前傳入為類,不是元類
单旁,拿到元類
沪羔,然后class_getInstanceMethod
方法尋找,元類
--->根元類
--->NSObject
--->nil
,沒找到sayHello
象浑,返回null
蔫饰,打印0x0
- method2:
0x0
class_getClassMethod
當前傳入為metaClass
元類,直接進行class_getInstanceMethod
方法查找當前元類
--->根元類
--->NSObject
--->nil
,沒找到實例方法sayHello
方法愉豺,所以返回null
,打印0x0
- method3:
0x100003148
當前查找方法為類方法
sayHappy
篓吁,在pClass
類中尋找,所以class_getClassMethod
中要拿到元類
蚪拦,然后進行查找杖剪,發(fā)現(xiàn)在元類中找到類方法sayHappy
冻押,所以能打印方法地址0x100003148
- method4:
0x100003148
class_getClassMethod
傳入即為類的元類,所以直接進行class_getInstanceMethod
方法查找盛嘿,在當前元類找到了sayHappy
類方法洛巢,所以返回找到的方法
lgIMP_classToMetaclass
class_getMethodImplementation
返回方法的具體實現(xiàn)
:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
如果在當前類對象里沒找到對應的方法實現(xiàn),就會觸發(fā)底層的消息轉發(fā)次兆,所以打印結果是四個都有值稿茉,而不是0x0
- method1:
0x100001d10
sayHello
實例方法,在當前pClass
類中能找到當前方法芥炭,所以method1
返回的是sayHello
方法地址指針
- method2:
0x7fff6cd17580
在
metaClass
元類中尋找實例方法sayHello
漓库,是找不到的,所以進行了底層的消息轉發(fā)
蚤认,返回的不是sayHello
的實現(xiàn)地址
- method3:
0x7fff6cd17580
sayHappy
類方法米苹,因為類方法是存儲在元類里的,所以在類pClass
中找不到sayHappy
方法實現(xiàn)地址砰琢,所以觸發(fā)了底層消息轉發(fā)
- method4:
0x100001d40
在
metaClass
元類 中找到了sayHappy
方法實現(xiàn)蘸嘶,所以返回的就是真正的sayHappy
實現(xiàn)地址
isKindOfClass & isMemberOfClass
同樣,利用上面的類LGPerson
進行下面探究:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
首先理清一下isKindOfClass
方法及isMemberOfClass
方法:
-
isKindOfClass
方法:+ (BOOL)isKindOfClass:(Class)cls { // 類 vs 元類 // 根元類 vs NSObject // NSObject vs NSObject // LGPerson vs 元類 (根元類) (NSObject) for (Class tcls = self->ISA(); 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; }
+isKindOfClass
比較線路:元類
--->根元類
--->NSObject
--->nil
與當前cls
類比較训唱。實例方法-isKindOfClass
比較路線:對象的類
--->父類
--->根類
--->nil
與cls
比較。
注意(有坑V吭?鲈觥!)
你以為就是這樣了训挡?當然不是澳骤,isKindOfClass
方法在llvm
編譯器在編譯期做了編譯優(yōu)化,isKindOfClass
在底層走的方法是objc_opt_isKindOfClass
澜薄,打開源碼为肮,搜索objc_opt_isKindOfClass
打斷點調試,發(fā)現(xiàn)調用isKindOfClass
類方法及實例方法肤京,走的都是這個方法:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
可見颊艳,無論是實例方法還是類方法調用,比較的對象鏈:元類
---> 根元類
---> NSObject
---> nil
忘分,也就是說isKindOfClass
就是遵循isa
走向的規(guī)則
- isMemberOfClass方法:
+ (BOOL)isMemberOfClass:(Class)cls { return self->ISA() == cls; }
類方法- (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
+isMemberOfClass
判斷的是當前類
是否是元類
棋枕,實例方法-isMemberOfClass
判斷是否是當前對象的類
。
類方法:re1
妒峦、re2
重斑、re3
、re4
實例方法:re5
肯骇、re6
绸狐、re7
卤恳、re8
-
re1
:1
首先拿到前者
NSObject
的元類(根元類
),與后者NSObject
類比較寒矿,不相等
突琳,找根元類的父類,也就是NSObject
類符相,比較結果為相等拆融,所以返回YES
,打印1
-
re2
:0
NSObject
元類與NSObject
類當然不相等,所以返回NO
-
re3
:0
LGPerson
類 與LGPerson
元類不想等啊终,與LGPerson
根元類不想等镜豹,與NSObject
類不想等,與nil
不想等,所以返回NO
-
re4
:0
LGPerson
類與LGPerson
元類不相等,返回NO
-
re5
:1
NSObject
實例對象的類NSObject
當然與NSObject
類相等蓝牲,返回YES
-
re6
:1
NSObject
實例對象的類NSObject
當然與NSObject
類相等
-
re7
:1
LGPerson
實例對象的類LGPerson
與LGPerson
類相等
-
re8
:1
LGPerson
實例對象的類LGPerson
與LGPerson
類相等