面試題一:考察實(shí)例方法和類(lèi)方法
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
LGLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
LGLog(@"LGPerson say : Happy!!!");
}
@end
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); // 1
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); // 0
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy)); // 0
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));// 1
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));
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);
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));
LGLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
LGLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
LGLog(@"Hello, World!");
}
return 0;
}
對(duì)于這道題首先一點(diǎn)我們要知道OC里面方法的存儲(chǔ)是跟isa的走位圖緊密相連的;
1月匣、OC對(duì)象的
實(shí)例方法
存儲(chǔ)類(lèi)對(duì)象
2钻洒、OC對(duì)象的類(lèi)方法
存儲(chǔ)在元類(lèi)對(duì)象
這的兩個(gè)方法為
- (void)sayHello: 實(shí)例方法
+ (void)sayHappy:類(lèi)方法
為了不誤導(dǎo)大家我先直接打印答案看看
結(jié)合isa走位圖,根據(jù)答案分析锄开,
-
1素标、 在函數(shù)
lgInstanceMethod_classToMetaclass(Class pClass)
中-
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
能找到方法method1
,符合預(yù)期 -
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
不能找到方法method2
萍悴,符合預(yù)期 -
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
不能找到方法method3
头遭,符合預(yù)期 -
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
能找到方法method4
符合預(yù)期
-
-
2、在函數(shù)
lgClassMethod_classToMetaclass(Class pClass)
中-
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
不能找到方法method1
癣诱,符合預(yù)期 -
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
不能找到方法method2
计维,符合預(yù)期 -
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
能找到方法method3
, 符合預(yù)期 -
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
在這里我需要先指出正確答案: 能找到方法;奇了怪撕予,但是按照我們 isa的走位分析鲫惶,這里應(yīng)該去元類(lèi)的元類(lèi)(根元類(lèi))里面找sayHappy
了啊,應(yīng)該找不到啊嗅蔬,我們不妨看看class_getClassMethod
源碼
-
//objc-class.mm
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//.....
//objc_rumtime-new.h
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
我們可以清晰的看到class_getClassMethod
方法其實(shí)實(shí)質(zhì)上是調(diào)用的class_getInstanceMethod
,并且是找Class
的元類(lèi)
;而元類(lèi)的搜索邏輯是:如果當(dāng)前類(lèi)已經(jīng)是元類(lèi)
,則返回當(dāng)前類(lèi)剑按。這就很好解釋了為什么在元類(lèi)里面找類(lèi)方法
能找到了。
- 3澜术、 在函數(shù)
lgIMP_classToMetaclass(Class pClass)
中-
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
能找到方法的實(shí)現(xiàn)imp1
艺蝴,符合預(yù)期 -
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
居然找到的方法的實(shí)現(xiàn),我們前面都在函數(shù)lgInstanceMethod_classToMetaclass
中分析打印了Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
的結(jié)果:不能找到方法∧穹希現(xiàn)在怎么找到了這里方法的實(shí)現(xiàn)imp2
呢猜敢?我們看看class_getMethodImplementation
源碼
-
//objc_class.mm 文件
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;
}
// ...
// message.h 文件
OBJC_EXPORT void
_objc_msgForward(void /* id receiver, SEL sel, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
// ...
//objc-msg-arm.s 文件
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
在這里我們可以很清晰的看見(jiàn):***當(dāng)lookUpImpOrNil
找不到方法實(shí)現(xiàn)的時(shí)候,直接返回_objc_msgForward
的函數(shù)指針,而這個(gè)·_objc_msgForward`的實(shí)現(xiàn)是在匯編里面的,所以必定存在;
-
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
,根據(jù)前面的分析缩擂,能找到方法imp3
, 應(yīng)該為_objc_msgForward
的實(shí)現(xiàn) -
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
能找到方法實(shí)現(xiàn)imp4
;
面試題二:考察iskindof鼠冕,isMemberof理解
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
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(@"\n 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(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
首先我們來(lái)看看結(jié)果
首先我們需要對(duì)
isKindOfClass
和isMemberOfClass
這兩個(gè)方法有所了解我們先來(lái)看看因?yàn)榻忉?p>
-
- (BOOL)isKindOfClass:(Class)aClass;
Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.大意: 判斷當(dāng)前
接收者
(這里可以理解為調(diào)用者)是否是給出類(lèi)
(這里可以理解為傳入的aClass
參數(shù))或者其子類(lèi)的實(shí)例。 - (BOOL)isMemberOfClass:(Class)aClass;
Returns a Boolean value that indicates whether the receiver is an instance of a given class.
大意:判斷當(dāng)前接收者
是否是給出類(lèi)
的實(shí)例胯盯。
對(duì)于類(lèi)方法
- 【1】懈费、
[(id)[NSObject class] isKindOfClass:[NSObject class]];
我們知道[NSObject class]
會(huì)調(diào)用類(lèi)方法+class
;我們查看源碼得知
- (Class)class {
return object_getClass(self);
}
+ (Class)class {
return self;
}
返回的是當(dāng)前類(lèi)自己
Note:其實(shí)在真正運(yùn)行的時(shí)候,
llvm
針對(duì)id
類(lèi)型會(huì)做一層處理調(diào)用的真正的實(shí)現(xiàn)函數(shù)是如下邏輯,當(dāng)發(fā)現(xiàn)cls->hasCustomCore()
為false
時(shí),直接返回當(dāng)前obj
或者obj->isa
指向的Class
// 源碼文件NSObject.mm文件中可以找到
// Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
if (slowpath(!obj)) return nil;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return cls->isMetaClass() ? obj : cls;
}
#endif
return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}
所以當(dāng)我們調(diào)用實(shí)例方法
或類(lèi)方法
class
時(shí)返回的都是當(dāng)前的類(lèi)對(duì)象
;
接下到我們的isKindof
這個(gè)實(shí)例方法
;為什么是實(shí)例方法呢博脑?因?yàn)槲覀儗⑺鼜?qiáng)轉(zhuǎn)成了id類(lèi)型,所以我們看看源碼
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Note:
1憎乙、我們?cè)谠创a文件NSObject.mm
發(fā)現(xiàn)了+isKindOfClass
的類(lèi)方法,這個(gè)NSObject
類(lèi)方法是存貯在根元類(lèi)
里面的, 在我們面試題中叉趣,其本質(zhì)是Class
類(lèi)型泞边,只是強(qiáng)轉(zhuǎn)成了id
類(lèi)型,當(dāng)調(diào)用isKindOfClass
方法時(shí),其實(shí)是會(huì)去Class
指向的元類(lèi)
里面查找方法疗杉, 即會(huì)調(diào)用+isKindOfClass
阵谚;不過(guò)在正式環(huán)境中我們是看不到這個(gè)類(lèi)方法
的,僅能看到實(shí)例方法
烟具。
2梢什、注意重點(diǎn):其實(shí)這個(gè)方法也是忽悠人的,經(jīng)過(guò)查看llvm
源碼發(fā)現(xiàn)iskindOfclass
經(jīng)過(guò)了優(yōu)化處理净赴,其真實(shí)會(huì)調(diào)用的方法是
// 源碼文件NSObject.mm文件中可以找到
// 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);
}
很顯然當(dāng)?shù)谝粊?lái)的時(shí)候obj
是NSObject
的類(lèi)對(duì)象
,cls
和tcls
是根元類(lèi)
,很明顯當(dāng)前是tcls == otherClass
不成立绳矩,當(dāng)我們第二次來(lái)的時(shí)候tcls
去取他的父類(lèi)賦值到tcls
,根據(jù)isa走位圖
,我們知道根元類(lèi)的父類(lèi)是NSObject的類(lèi)對(duì)象;所以此時(shí)tcls == otherClass
成立返回為true
玖翅。
- 【2】翼馆、
[(id)[NSObject class] isMemberOfClass:[NSObject class]];
對(duì)于isMemberOf
這個(gè)方法源碼去看看;
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
這里就相當(dāng)簡(jiǎn)單了:直接判斷當(dāng)前的實(shí)例的類(lèi)對(duì)象是否等于傳入的cls
。
當(dāng)我們調(diào)用isMemberOfClass
方法時(shí)金度,self
就是[NSObject class]
經(jīng)過(guò)強(qiáng)轉(zhuǎn)之后的id
類(lèi)型(其本質(zhì)就是NSObject類(lèi)對(duì)象),當(dāng)經(jīng)過(guò)[self class]
取class操作之后应媚,會(huì)變?yōu)?code>NSObject元類(lèi)對(duì)象和NSObject類(lèi)對(duì)象
比較,所以為false
猜极;
- 【3】中姜、[(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 這里最終是
LGPerson類(lèi)對(duì)象
和LGPerson元類(lèi)對(duì)象
或者其父類(lèi)
的比較,為false
; - 【4】 跟伏、
[(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
【3】都是false丢胚,這里還是它的子集,更加為false
; - 【5】受扳、
[(id)[NSObject alloc] isKindOfClass:[NSObject class]];
這里簡(jiǎn)單分析true
; - 【6】携龟、
[(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
這里簡(jiǎn)單分析true
; - 【7】、
[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
這里簡(jiǎn)單分析true
; - 【8】勘高、
[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
這里簡(jiǎn)單分析true
;
兩道面試題的總結(jié):
需要充分理解isa的走位圖:方法的存儲(chǔ)和類(lèi)型的繼承峡蟋。