下面代碼輸出什么?
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中調用方法,會被轉成消息發送機制的函數 objc_msgSend(id self, SEL cmd, ...),因此,方法內self與objc_msgSend()函數中的self是等價的,就是調用時傳入的消息的接受者(Objective-C高級編程 p94)。
super是一個編譯器指示符
當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。這種機制到底底層是如何實現的?
例如,當調用[super class]時,會轉為 objc_msgSendSuper(),而非objc_msgSend(),看下 objc_msgSendSuper 的函數定義:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
struct objc_super {
? ? id receiver;
? ? Class superClass;
};
當編譯器遇到 Son 里 init 方法里的 [super class] 時,開始做這幾個事:
構建 objc_super 的結構體變量,此時該變量的第一個成員變量 receiver 就是當前方法內的self(Son *)而第二個成員變量 superClass 就是指當前方法所屬類的父類 Father
調用 objc_msgSendSuper ,將結構體變量和 @selector(class)?傳過去
objc_msgSendSuper函數里面在做的事情類似這樣:從 objc_super 結構體指向的 superClass 的方法列表開始找 @selector(class)?,找到后再以 objc_super->receiver 去調用這個 selector,可能也會使用 objc_msgSend 這個函數
所以,當調用[self class]時,此時的self,就是init方法的接受者Son *。因此,[self class]轉為objc_msgSend(),第一個參數是Son *,第二個參數是@selector(class)。根據isa先從Son類開始找,沒有,然后到 Son的父類 Father中去找,也沒有,再去 Father 的父類 NSObject 去找,一層一層向上找之后,在 NSObject 的類中發現這個 class 方法
- (Class)class {
? ? ?return object_getClass(self);
}
Class object_getClass(id obj)
{
? ? ? if (obj) return obj->getIsa();
? ? ? ?else return Nil;
}
self就是消息的接受者Son*,因此輸出 Son
當使用 [super class] 時,這時要轉換成 objc_msgSendSuper 的方法。先構造 objc_super 的結構體變量,第一個成員變量就是 self(Son *)第二個成員變量是 Father,然后要找@selector(class)?。先去 superClass 也就是 Father 中去找,沒有,然后去 Father 的父類中去找,結果還是在 NSObject 中找到了。然后內部使用函數 objc_msgSend(objc_super->receiver, @selector(class))??去調用,此時已經和我們用 [self class] 調用時相同了,此時的 receiver 還是Son *,所以這里輸出的還是Son
其實很好理解。例如,在Son init方法中,調用[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;
}
先來分析一下源碼這兩個函數的對象實現
類對象調用class方法,直接返回類本身
+ (Class)class {
? ? ?return self;
}
實例對象調用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);
}
無論是實例對象還是類對象,object_getClass(obj),均返回對象isa
類對象調用這個與元類比較,由于元類同樣的繼承關系,只要是類對象的元類是所比較元類或其子類都返回真
+ (BOOL)isKindOfClass:(Class)cls {
? ? ?//tcls 等價 于tcls != nil 因為根元類的父類是NSObject,NSObject的父類是nil
? ? ? ?for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
? ?//能這么比較 tcls == cls,比較地址,也說明了 元類對象的唯一
? ? ? ? ? ? ? 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;
//能這么比較 tcls == cls,比較地址,也說明了 類對象的唯一
}
類對象與元類對象比較,只有類對象的元類與所比較的元類相同才為真
+ (BOOL)isMemberOfClass:(Class)cls {
? ? ? return object_getClass((id)self) == cls;
}
實例對象與類對象比較,只有實例對象的類與所比較的類相同才為真
- (BOOL)isMemberOfClass:(Class)cls {
? ? ? ?return [self class] == cls;
}
這道題,除了考察這幾個方法還是考察了類與元類的繼承體系,尤其是NSObject
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等。循環,根元類的父類是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不等。循環,Sark元類的父類是根元類,與Sark不等。循環,根元類的父類是NSObject,與Sark不等。NSObject的父類是nil,循環結束。
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
Sark的元類與Sark不等
總結,isMember比較傲嬌,調用者只取一次isa不等就是不等。isKindOf調用者取一次isa 不等取其父類
Class與內存地址
下面的代碼會?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
首先編譯能不能通過?其次,調用輸出什么?
答案是可以編譯通過,輸出 my name is
這道題,涉及到實例變量的內存結構和類與對象的關系
cls指向Sark類的指針,而obj是一個指針,存儲著cls的地址,也就指向obj的指針,類似一個二級指針。然后調用實例方法。這是怎么回事呢?
首先,將C語言的指針類型轉為OC的id類型,但方法也不能隨便調用,這個方法存在當前類或者導入頭文件的聲明中,而我們導入了#import "Person.h",編譯通過。
然后,cls指向了Person類,而我們定義一個對象Person *p = [Person new]; 對象的isa也指向Person類,而實例對象的第一個成員是isa,因此實例對象的地址與isa的地址是相同的。obj指向cls,如同指向了實例對象的isa,如同指向了對象。那么,也就是cls如同一個實例對象,obj類似一個指向實例對象的指針,即p。
Person *person = [[Person alloc] init];
NSLog(@"%@",person); ?//
NSLog(@"%p %p",person,&person); ?//0x6000000098c0 0x7fff5a353a88
一個是指針所指對象地址,一個是指針地址
我們說過,方法最終轉為函數,而函數默認兩個參數,一個是消息的接受者,一個是選擇子,對應的是參數名是self與_cmd。我們在viewDidLoad打印幾個地址:
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實例對象的地址,然后是指向實例對象,指向選擇子的指針地址,
NSLog(@"%p %p",cls,&cls);??打印的是cls指向的Person類的地址和cls的地址
NSLog(@"%p %p",p,&p);??打印的是p指向的cls的地址和p的地址
cls如同Person的實例對象,而p如同指向實例對象的指針,調用Person的方法
- (void)speak {
? ? ?NSLog(@"%p",&_cmd);//0x7fff58e08a40
? ? ?NSLog(@"%p??%p",self,&self);//0x7fff58e08a88??0x7fff58e08a48
? ? ?NSLog(@"my name's %@", self.name);
}
我們看到speak方法內,self指向的地址是0x7fff58e08a88,正是實例變量cls的地址。但是很明顯,這個地址7fff與Person類地址106明顯不像,一個是棧上的地址,一個是堆上的地址。
我們知道內存,對內存進行編制后,由下到上,地址越來越大,??臻g在上,分配時由上到下,越后分配的地址越小,而堆空間在下,分配時由下而上,越后分配的地址越大
cls的棧地址也說明了,它不是一個真正的分配在堆上的對象,或許這是披了一件外衣。但是,只有我們知道。
打印self.name時。前面說過實例對象在類中的內存結構。
Person對象
isa
*name
當一個類被編譯時,實例變量的布局也就形成了,訪問類的實例變量。從對象頭部開始,實例變量依次根據自己所占空間而產生偏移量。
查找self.name也就是在實例變量的起始地址,偏移8個字節(64位下isa 8個字節),就得到name的首地址,而實例變量的首地址是 0x7fff58e08a88,偏移8個字節。(按字節編制,每個地址八位)
90???name
8f
8e
8d
8c
8b
8a
89
88 Person *
要去0x7fff58e08a90 查找name指針指向的字符串對象??赡敲礊槭裁磿敵鰒iewController相關的呢?
我們回到viewDidLoad方法,
NSLog(@"%p??%p",&self,&_cmd); //0x7fff58e08aa8??0x7fff58e08aa0
我們調用函數,參數入棧,self,_cmd的地址是a8,a0,方法內,[super viewDidLoad]; 我們前面說過super是個指示符,需要構造結構體作為函數參數
struct objc_super {
? ? id receiver;
? ? Class superClass;
};
superClass指針是self所在類的父類 98,receiver指針指向self所指的實例對象 90,,至于為什么_cmd沒分配,我也不知道。接下來的代碼就是 Class cls = [Person class]; cls是88,void *p = &cls; p是80
這也就符合我們的打印結果,那么,我們上面尋找的90就是self了。因此,打印 my name's %@ 就是self所指的對象ViewController了。