下面代碼輸出什么?
self super
@implementation Son : Father
- (id)init
{
? ? ? self = [super init];
? ? ? ?if (self){
? ? ? ? ?NSLog(@"%@", NSStringFromClass([self class]));
? ? ? ? ? NSLog(@"%@", NSStringFromClass([super class]));
? ? ? ?}
return self;
}
@end
這道面試題,主要是考察self與super的
OC中調(diào)用方法,會(huì)被轉(zhuǎn)成消息發(fā)送機(jī)制的函數(shù) objc_msgSend(id self, SEL cmd, ...),因此,方法內(nèi)self與objc_msgSend()函數(shù)中的self是等價(jià)的,就是調(diào)用時(shí)傳入的消息的接受者(Objective-C高級(jí)編程 p94)。
super是一個(gè)編譯器指示符
當(dāng)使用 self 調(diào)用方法時(shí),會(huì)從當(dāng)前類的方法列表中開始找,如果沒有,就從父類中再找;而當(dāng)使用 super 時(shí),則從父類的方法列表中開始找。這種機(jī)制到底底層是如何實(shí)現(xiàn)的?
例如,當(dāng)調(diào)用[super class]時(shí),會(huì)轉(zhuǎn)為 objc_msgSendSuper(),而非objc_msgSend(),看下 objc_msgSendSuper 的函數(shù)定義:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
struct objc_super {
? ? id receiver;
? ? Class superClass;
};
當(dāng)編譯器遇到 Son 里 init 方法里的 [super class] 時(shí),開始做這幾個(gè)事:
構(gòu)建 objc_super 的結(jié)構(gòu)體變量,此時(shí)該變量的第一個(gè)成員變量 receiver 就是當(dāng)前方法內(nèi)的self(Son *)而第二個(gè)成員變量 superClass 就是指當(dāng)前方法所屬類的父類 Father
調(diào)用 objc_msgSendSuper ,將結(jié)構(gòu)體變量和 @selector(class)?傳過去
objc_msgSendSuper函數(shù)里面在做的事情類似這樣:從 objc_super 結(jié)構(gòu)體指向的 superClass 的方法列表開始找 @selector(class)?,找到后再以 objc_super->receiver 去調(diào)用這個(gè) selector,可能也會(huì)使用 objc_msgSend 這個(gè)函數(shù)
所以,當(dāng)調(diào)用[self class]時(shí),此時(shí)的self,就是init方法的接受者Son *。因此,[self class]轉(zhuǎn)為objc_msgSend(),第一個(gè)參數(shù)是Son *,第二個(gè)參數(shù)是@selector(class)。根據(jù)isa先從Son類開始找,沒有,然后到 Son的父類 Father中去找,也沒有,再去 Father 的父類 NSObject 去找,一層一層向上找之后,在 NSObject 的類中發(fā)現(xiàn)這個(gè) class 方法
- (Class)class {
? ? ?return object_getClass(self);
}
Class object_getClass(id obj)
{
? ? ? if (obj) return obj->getIsa();
? ? ? ?else return Nil;
}
self就是消息的接受者Son*,因此輸出 Son
當(dāng)使用 [super class] 時(shí),這時(shí)要轉(zhuǎn)換成 objc_msgSendSuper 的方法。先構(gòu)造 objc_super 的結(jié)構(gòu)體變量,第一個(gè)成員變量就是 self(Son *)第二個(gè)成員變量是 Father,然后要找@selector(class)?。先去 superClass 也就是 Father 中去找,沒有,然后去 Father 的父類中去找,結(jié)果還是在 NSObject 中找到了。然后內(nèi)部使用函數(shù) objc_msgSend(objc_super->receiver, @selector(class))??去調(diào)用,此時(shí)已經(jīng)和我們用 [self class] 調(diào)用時(shí)相同了,此時(shí)的 receiver 還是Son *,所以這里輸出的還是Son
其實(shí)很好理解。例如,在Son init方法中,調(diào)用[super init];,消息的接受者還是self,不然給誰初始化?只是,查找方法從父類開始了。
isKindOfClass 與 isMemberOfClass
下面代碼輸出什么?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
? ? ? ?@autoreleasepool {
? ? ? ? ? ? BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
? ? ? ? ? ? BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; ?
? ? ? ? ? ? ?BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
? ? ? ? ? ? ?BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
? ? ? ? ? ? ?NSLog(@"%d %d %d %d", res1, res2, res3, res4);
? ? ? }
? ? ? ?return 0;
}
先來分析一下源碼這兩個(gè)函數(shù)的對象實(shí)現(xiàn)
類對象調(diào)用class方法,直接返回類本身
+ (Class)class {
? ? ?return self;
}
實(shí)例對象調(diào)用class方法,返回對象的isa
- (Class)class {
? ? ?return object_getClass(self);
}
Class object_getClass(id obj)
{
? ? ?if (obj) return obj->getIsa();
? ? ?else return Nil;
}
inline Class objc_object::getIsa()
{
? ? ?if (isTaggedPointer()) {
? ? ? ? uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
? ? ? ? ?return objc_tag_classes[slot];
? ? ? }
? ? ?return ISA();
}
inline Class objc_object::ISA()
{
? ? ?assert(!isTaggedPointer());
? ? ?return (Class)(isa.bits & ISA_MASK);
}
無論是實(shí)例對象還是類對象,object_getClass(obj),均返回對象isa
類對象調(diào)用這個(gè)與元類比較,由于元類同樣的繼承關(guān)系,只要是類對象的元類是所比較元類或其子類都返回真
+ (BOOL)isKindOfClass:(Class)cls {
? ? ?//tcls 等價(jià) 于tcls != nil 因?yàn)楦惖母割愂荖SObject,NSObject的父類是nil
? ? ? ?for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
? ?//能這么比較 tcls == cls,比較地址,也說明了 元類對象的唯一
? ? ? ? ? ? ? if (tcls == cls) return YES;
? ? ? ?}
? ? ?return NO;
}
實(shí)例對象調(diào)用與類對象比較,只要是實(shí)例對象的類是所比較元類或其子類都返回真
- (BOOL)isKindOfClass:(Class)cls {
? ? ? ?for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
? ? ? ? ? if (tcls == cls) return YES;
? ? ? }
return NO;
//能這么比較 tcls == cls,比較地址,也說明了 類對象的唯一
}
類對象與元類對象比較,只有類對象的元類與所比較的元類相同才為真
+ (BOOL)isMemberOfClass:(Class)cls {
? ? ? return object_getClass((id)self) == cls;
}
實(shí)例對象與類對象比較,只有實(shí)例對象的類與所比較的類相同才為真
- (BOOL)isMemberOfClass:(Class)cls {
? ? ? ?return [self class] == cls;
}
這道題,除了考察這幾個(gè)方法還是考察了類與元類的繼承體系,尤其是NSObject
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等。循環(huán),根元類的父類是NSObject,相等。res1 = YES
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
[Sark class]還是Sark,Sark的元類與Sark不等。循環(huán),Sark元類的父類是根元類,與Sark不等。循環(huán),根元類的父類是NSObject,與Sark不等。NSObject的父類是nil,循環(huán)結(jié)束。
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
Sark的元類與Sark不等
總結(jié),isMember比較傲嬌,調(diào)用者只取一次isa不等就是不等。isKindOf調(diào)用者取一次isa 不等取其父類
Class與內(nèi)存地址
下面的代碼會(huì)?Compile Error / Runtime Crash / NSLog…?
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end
@implementation Person
- (void)speak {
? ? ? NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
? ? ?[super viewDidLoad];
? ? ?id cls = [Person class];
? ? ? void *obj = &cls;
? ? ? [(__bridge id)obj speak];
}
@end
首先編譯能不能通過?其次,調(diào)用輸出什么?
答案是可以編譯通過,輸出 my name is
這道題,涉及到實(shí)例變量的內(nèi)存結(jié)構(gòu)和類與對象的關(guān)系
cls指向Sark類的指針,而obj是一個(gè)指針,存儲(chǔ)著cls的地址,也就指向obj的指針,類似一個(gè)二級(jí)指針。然后調(diào)用實(shí)例方法。這是怎么回事呢?
首先,將C語言的指針類型轉(zhuǎn)為OC的id類型,但方法也不能隨便調(diào)用,這個(gè)方法存在當(dāng)前類或者導(dǎo)入頭文件的聲明中,而我們導(dǎo)入了#import "Person.h",編譯通過。
然后,cls指向了Person類,而我們定義一個(gè)對象Person *p = [Person new]; 對象的isa也指向Person類,而實(shí)例對象的第一個(gè)成員是isa,因此實(shí)例對象的地址與isa的地址是相同的。obj指向cls,如同指向了實(shí)例對象的isa,如同指向了對象。那么,也就是cls如同一個(gè)實(shí)例對象,obj類似一個(gè)指向?qū)嵗龑ο蟮闹羔槪磒。
Person *person = [[Person alloc] init];
NSLog(@"%@",person); ?//
NSLog(@"%p %p",person,&person); ?//0x6000000098c0 0x7fff5a353a88
一個(gè)是指針?biāo)笇ο蟮刂罚粋€(gè)是指針地址
我們說過,方法最終轉(zhuǎn)為函數(shù),而函數(shù)默認(rèn)兩個(gè)參數(shù),一個(gè)是消息的接受者,一個(gè)是選擇子,對應(yīng)的是參數(shù)名是self與_cmd。我們在viewDidLoad打印幾個(gè)地址:
NSLog(@"%p",self); //0x7fe5e2e0b570
NSLog(@"%@",self); //
NSLog(@"%p??%p",&self,&_cmd); //0x7fff58e08aa8??0x7fff58e08aa0
Class cls = [Person class];
void *p = &cls;
NSLog(@"%p %p",cls,&cls);? //0x106df8140 0x7fff58e08a88
NSLog(@"%p %p",p,&p); ?//0x7fff58e08a88 0x7fff58e08a80
首先是ViewController實(shí)例對象的地址,然后是指向?qū)嵗龑ο螅赶蜻x擇子的指針地址,
NSLog(@"%p %p",cls,&cls);??打印的是cls指向的Person類的地址和cls的地址
NSLog(@"%p %p",p,&p);??打印的是p指向的cls的地址和p的地址
cls如同Person的實(shí)例對象,而p如同指向?qū)嵗龑ο蟮闹羔?,調(diào)用Person的方法
- (void)speak {
? ? ?NSLog(@"%p",&_cmd);//0x7fff58e08a40
? ? ?NSLog(@"%p??%p",self,&self);//0x7fff58e08a88??0x7fff58e08a48
? ? ?NSLog(@"my name's %@", self.name);
}
我們看到speak方法內(nèi),self指向的地址是0x7fff58e08a88,正是實(shí)例變量cls的地址。但是很明顯,這個(gè)地址7fff與Person類地址106明顯不像,一個(gè)是棧上的地址,一個(gè)是堆上的地址。
我們知道內(nèi)存,對內(nèi)存進(jìn)行編制后,由下到上,地址越來越大,??臻g在上,分配時(shí)由上到下,越后分配的地址越小,而堆空間在下,分配時(shí)由下而上,越后分配的地址越大
cls的棧地址也說明了,它不是一個(gè)真正的分配在堆上的對象,或許這是披了一件外衣。但是,只有我們知道。
打印self.name時(shí)。前面說過實(shí)例對象在類中的內(nèi)存結(jié)構(gòu)。
Person對象
isa
*name
當(dāng)一個(gè)類被編譯時(shí),實(shí)例變量的布局也就形成了,訪問類的實(shí)例變量。從對象頭部開始,實(shí)例變量依次根據(jù)自己所占空間而產(chǎn)生偏移量。
查找self.name也就是在實(shí)例變量的起始地址,偏移8個(gè)字節(jié)(64位下isa 8個(gè)字節(jié)),就得到name的首地址,而實(shí)例變量的首地址是 0x7fff58e08a88,偏移8個(gè)字節(jié)。(按字節(jié)編制,每個(gè)地址八位)
90???name
8f
8e
8d
8c
8b
8a
89
88 Person *
要去0x7fff58e08a90 查找name指針指向的字符串對象??赡敲礊槭裁磿?huì)輸出viewController相關(guān)的呢?
我們回到viewDidLoad方法,
NSLog(@"%p??%p",&self,&_cmd); //0x7fff58e08aa8??0x7fff58e08aa0
我們調(diào)用函數(shù),參數(shù)入棧,self,_cmd的地址是a8,a0,方法內(nèi),[super viewDidLoad]; 我們前面說過super是個(gè)指示符,需要構(gòu)造結(jié)構(gòu)體作為函數(shù)參數(shù)
struct objc_super {
? ? id receiver;
? ? Class superClass;
};
superClass指針是self所在類的父類 98,receiver指針指向self所指的實(shí)例對象 90,,至于為什么_cmd沒分配,我也不知道。接下來的代碼就是 Class cls = [Person class]; cls是88,void *p = &cls; p是80
這也就符合我們的打印結(jié)果,那么,我們上面尋找的90就是self了。因此,打印 my name's %@ 就是self所指的對象ViewController了。