歡迎閱讀iOS探索系列(按序閱讀食用效果更加)c++
本文涉及的面試題以下:面試
isKindOfClass
和isMemberOfClass
的區別[self class]
和[super class]
的區別這是一道涉及isa走位圖
的面試題,大膽猜想下結果bash
#import <Foundation/Foundation.h>
#import "FXPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];//1
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];// 0
BOOL re3 = [(id)[FXPerson class] isKindOfClass:[FXPerson class]];//0
BOOL re4 = [(id)[FXPerson class] isMemberOfClass:[FXPerson class]];// 0
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]];//1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];// 1
BOOL re7 = [(id)[FXPerson alloc] isKindOfClass:[FXPerson class]];//1
BOOL re8 = [(id)[FXPerson alloc] isMemberOfClass:[FXPerson class]];// 1
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
複製代碼
這裏先不揭曉答案,先來探索一下isKindOfClass
和isMemberOfClass
的實現函數
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
複製代碼
for (int i = 0; i < 3; i ++)
的for循環
object_getClass
獲得當前類對象的類——元類
,初始化tcls
tcls
有值就能夠繼續循環,即當tcls
爲nil
時結束for循環tcls
的父類,做爲它的新值,繼續下一次循環tcls == cls
,返回YES
NO
結論一:+isKindOfClass是元類及其父類 vs 類post
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
複製代碼
object_getClass
獲得當前類對象的類——元類
,和類自己cls
進行比較+isKindOfClass
少了父類的比較,所以+isMemberOfClass
爲YES時能夠獲得+isKindOfClass
爲YES結論二:+isMemberOfClass是元類 vs 類ui
isa走位圖
(實線爲父類走向)能夠得出前面四個打印結果:
NSObject元類
與NSObject類
不相等,NSObject元類的父類
(指向NSObject類
)與NSObject類
相等——YESNSObject元類
與NSObject類
不相等——NOFXPerson元類
與FXPerson類
不相等,FXPerson元類的父類
與FXPerson類
不相等——NOFXPerson元類
與FXPerson類
不相等——NO換成實例對象調用-isKindOfClass
和-isMemberOfClass
atom
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
複製代碼
同理可得:-isMemberOfClass
是拿實例對象的類(即當前類)和cls
做比較,-isKindOfClass
多了一步for循環類對象的父類spa
結論三:-isKindOfClass是類自己及其父類 vs 類設計
結論四:-isMemberOfClass是類自己 vs 類3d
後面四個結果分析以下:
NSObject類
與NSObject類
相等——YESNSObject類
與NSObject類
相等——YESFXPerson類
與FXPerson類
相等——YESFXPerson類
與FXPerson類
相等——YESFXSon
繼承於FXFather
,主程序初始化FXSon
,求問打印內容以及思路
#import "FXSon.h"
@implementation FXSon
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"[self class] = %@", NSStringFromClass([self class]));
NSLog(@"[super class] = %@", NSStringFromClass([super class]));
NSLog(@"[self superclass] = %@", NSStringFromClass([self superclass]));
NSLog(@"[super superclass] = %@", NSStringFromClass([super superclass]));
}
return self;
}
@end
複製代碼
打印結果以下
[self class]
點進去來到
NSObject.mm
文件查看源碼
class
方法返回類superclass
方法返回類的父類+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
複製代碼
從這段代碼能解釋[self class]
和[self superclass]
,可是另外兩個又怎麼解釋呢?
終端clang編譯代碼獲得super.cpp
,就能看到初始化的底層代碼了
clang -rewrite-objc FXSon.m -o super.cpp
複製代碼
objc_msgSend
,那
objc_msgSendSuper
也是發送消息嗎?
查看源碼中對objc_msgSendSuper
的定義,註釋中提示了objc_super
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/// Specifies the superclass of an instance.
struct objc_msgSendSuper {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
複製代碼
從以上源碼得出,使用objc_msgSendSuper
向objc_super
發送消息,而objc_super
在objc2.0
下有兩個元素——id
類型的receiver
和Class
類型的super_class
其實早在iOS探索 方法的本質和消息查找流程中就提過這個方法,而後筆者進行了仿寫
記得導入<objc/message.h>
,報錯Too many arguments
就去修改編譯期配置
[super class]
同樣的結果了
那蘋果爲何要這麼設計呢?把消息查找
和isa走位圖
聯繫起來就明白了!
son實例對象
的
實例方法
存在
FXSon類
中
[self class]
就是son照着FXSon->FXFather->NSObject
順序問老爸要-class方法
[super class]
就是son跳過FXSon
,直接經過FXFather->NSObject
查找[super class]
更快找到class方法的寫法
[self class]
改成[super class]
,直接找到NSObjct
補充: 當結構體中[self class]
改成[FXFather class]
時,由於類方法存在元類中,會按FXFather元類->NSObject元類->NSObject根元類
找+class
方法,最後也是會輸出FXSon
結論:
[self class]
就是發送消息objc_msgSend
,消息接收者是self
,方法編號是class
[super class]
就是發送消息objc_msgSendSuper
,消息接收者是self
,方法編號是class
,只不過objc_msgSendSuper
會跳過self
的查找這是一道比較經典的「喪心病狂」的內存偏移面試題,若是你沒有研究過,大機率很難答上來
程序可否運行嗎?是否正常輸出?
#import "ViewController.h"
@interface FXPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation FXPerson
- (void)printMyProperty {
NSLog(@"當前打印內容爲%s", __func__);
}
@end
//——————————————————————————————————————//
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [FXPerson class];
void *obj= &cls;
[(__bridge id)obj printMyProperty];
FXPerson *p = [FXPerson new];
[p printMyProperty];
}
@end
複製代碼
運行結果與普通初始化對象如出一轍,可面試的時候不可能只說能或不能,還要說出個因此然來
正常初始化:指針p->實例對象isa->類對象
objc_object
,第一個元素爲isa
指針p
存儲着FXPerson類
實例出來的對象內存地址,因此指針p
指向對象的首地址——p->實例對象isa
實例對象
的isa指向類對象
——實例對象isa->類對象
騷操做:指針obj->指針cls->類對象
id cls = [LGPerson class]
獲取到類對象指針void *obj= &cls
獲取到指向該類對象cls的對象obj
修改打印方法printMyProperty
——不但打印方法,同時打印屬性name
- (void)printMyProperty {
NSLog(@"當前打印內容爲%s——————%@", __func__, self.name);
}
複製代碼
從新運行代碼,獲得結果以下
當前打印內容爲-[FXPerson printMyProperty]——————<ViewController: 0x7fc72cd09450>
當前打印內容爲-[FXPerson printMyProperty]——————(null)
複製代碼
爲何屬性name尚未賦值,卻打印出了ViewController
的內存地址?
先入後出
,viewDidLoad
入棧先拉伸棧空間,而後依次放入self、_cmd
局部變量[super viewDidLoad]
,繼續放入super_class、self
self
修改viewDidLoad
——在obj
前面加個臨時字符串變量
- (void)viewDidLoad {
[super viewDidLoad];
NSString *temp = @"1";
id cls = [FXPerson class];
void *obj= &cls;
[(__bridge id)obj printMyProperty];
FXPerson *p = [FXPerson alloc];
[p printMyProperty];
}
複製代碼
從新運行代碼,獲得結果以下
當前打印內容爲-[FXPerson printMyProperty]——————1
當前打印內容爲-[FXPerson printMyProperty]——————(null)
複製代碼
一樣道理,在obj
入棧前已經有了temp
變量,此時訪問self.name
就會訪問到temp
去掉臨時變量,FXPerson類
新增字符串屬性hobby
,打印方法改成打印hobby
,運行
ViewController
就是
obj
偏移16字節拿到的
super_class
([super viewDidLoad]壓棧進去的)
①去掉[super viewDidLoad]
,運行
②臨時變量改爲NSInteger類型
這兩種狀況就是野指針
——指針偏移的offset不正確,獲取不到對應變量的首地址
int a = 1;
int b = 2;
int c = 3;
int d = 4;
NSLog(@"\na = %p\nb = %p\nc = %p\nd = %p\n",&a,&b,&c,&d);
複製代碼
打印結果
a = 0x7ffee0ebd1bc
b = 0x7ffee0ebd1b8
c = 0x7ffee0ebd1b4
d = 0x7ffee0ebd1b0
複製代碼
局部變量的存放順序,是根據定義的前後順序,從函數棧底(高地址)開始,一個一個排列
關於這題還沒搞明白的能夠看下Runtime筆記(八)—— 面試題中的Runtime裏面的圖很形象
面試題是面試官用知識點變着法玩你的一種手段,同時也能表現出你掌握知識的熟練度。只有在平時多練習多研究,才能在面試的時候給面試官留下一個好的印象