特別備注
本系列文章總結(jié)自MJ老師在騰訊課堂開(kāi)設(shè)的OC底層原理課程便脊,相關(guān)圖片素材均取自課程中的課件。
面試題 – 面向?qū)ο?/h2>
對(duì)象的isa指針指向哪里隙券?
- instance對(duì)象的isa指向class對(duì)象
- class對(duì)象的isa指向meta-class對(duì)象
- meta-class對(duì)象的isa指向基類的meta-class對(duì)象
OC的類信息存放在哪里?
- 對(duì)象方法、屬性茁裙、成員變量弓乙、協(xié)議信息末融,存放在class對(duì)象中
- 類方法,存放在meta-class對(duì)象中
- 成員變量的具體值暇韧,存放在instance對(duì)象
isa指針
OC對(duì)象分為三類
instance對(duì)象
- 成員變量
- 特殊的成員變量 isa指針
class對(duì)象
勾习,用來(lái)描述instance對(duì)象
- isa指針
- superclass指針
- 屬性信息
- 對(duì)象方法信息(-方法)
- 協(xié)議信息
- instance對(duì)象的成員變量的描述信息
meta-class對(duì)象
,用來(lái)存放類方法
- isa指針
- superclass指針
- 類方法信息(+方法)
對(duì)一個(gè)類來(lái)說(shuō)懈玻,它的instance
巧婶、class
、mete-class
對(duì)象之間涂乌,一定是有某種聯(lián)系的艺栈。假設(shè)這種聯(lián)系不存在,我們看看會(huì)碰到什么問(wèn)題湾盒。比如我調(diào)用一個(gè)instance對(duì)象的方法
MJPerson *person = [[MJPerson alloc] init];
person->_age = 10;
[person personInstanceMethod];
它的底層是
通過(guò)xcrun編譯轉(zhuǎn)化成cpp文件
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
objc_msgSend([MJPerson class], @selector(personClassMethod))
objc_msgSend(instanceObj, @sel_registerName("instanceObjMethod"));
也就是給instanceObj對(duì)象發(fā)消息湿右。
在instance對(duì)象
不跟外界關(guān)聯(lián)的情況下,它內(nèi)部只有一些成員變量信息罚勾,是不可能完成方法調(diào)用的毅人,因?yàn)閷?duì)象方法是存放在class對(duì)象
里面的吭狡,對(duì)class對(duì)象
調(diào)用+方法
的時(shí)候也是一樣,必須有辦法跟meta-class對(duì)象
關(guān)聯(lián)起來(lái)堰塌,才能完成對(duì)+方法
的調(diào)用赵刑。所以isa指針,就只它們之間的關(guān)聯(lián)场刑“愦耍可以通過(guò)下圖來(lái)理解isa的作用。
大致可以歸納為
-
instance對(duì)象
的isa
指針指向class對(duì)象
牵现。當(dāng)調(diào)用對(duì)象方法(-方法)時(shí)铐懊,通過(guò)instance
的isa
找到class
,然后在class
的方法列表里面找到對(duì)應(yīng)的實(shí)現(xiàn)進(jìn)行調(diào)用瞎疼。 -
class對(duì)象
的isa指針指向meta-class對(duì)象
科乎。當(dāng)調(diào)用類方法(+)方法時(shí),通過(guò)class
的isa
找到meta-class
贼急,最后在meta-class
的方法列表找到對(duì)應(yīng)的實(shí)現(xiàn)進(jìn)行調(diào)用茅茂。
那么通過(guò)isa的橋接作用,我們應(yīng)該能更近一步地理解OC消息發(fā)送以及方法調(diào)用的過(guò)程了太抓。
superclass指針
顯而易見(jiàn)空闲,從字面意思,我們就能知道走敌,superclass就是父類的意思碴倾。
假定我們有以下幾個(gè)類
// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
我們知道superclass
指針存在于class對(duì)象
和meta-class對(duì)象
里面。我們根據(jù)接下來(lái)的圖示來(lái)闡述一下:
一個(gè)類Student
的class對(duì)象
里面的superclass指針
指向該類的父類的class對(duì)象
當(dāng)Student
的instance對(duì)象
要調(diào)用Person
的對(duì)象方法時(shí)掉丽,會(huì)先通過(guò)isa
找到Student
的class對(duì)象
跌榔,然后通過(guò)這個(gè)class對(duì)象
的superclass
找到Person(Student的父類)
的class對(duì)象
,最后找到相應(yīng)的對(duì)象方法(-方法)的實(shí)現(xiàn)進(jìn)行調(diào)用
一個(gè)類的meta-class
里面的superclass
指針指向該類的父類的meta-class
對(duì)象
當(dāng)Student
的class對(duì)象
要調(diào)用Person
的類方法時(shí)捶障,會(huì)先通過(guò)isa
找到Student
的meta-class對(duì)象
僧须,然后通過(guò)這個(gè)meta-class對(duì)象
的superclass
找到Person(Student的父類)
的meta-class對(duì)象
,最后找到相應(yīng)的類方法(+方法)的實(shí)現(xiàn)進(jìn)行調(diào)用
isa残邀、superclass總結(jié)
上圖完整描述了isa、superclass指針的作用芥挣,為了更加便于理解驱闷,我們?cè)诤竺娴膱D例中用Student
代替subclass,Person
代替superclass空免,NSObject
代替Rootclass空另。
-
instance
的isa
指向class
-
class
的isa
指向meta-class
-
meta-class
的isa
指向基類的meta-class
-
class
的superclass
指向父類的class
,如果沒(méi)有父類蹋砚,superclass
指針為nil
-
meta-class
的superclass
指向父類的meta-class
,基類的meta-class
的superclass
指向基類的class
instance調(diào)用對(duì)象方法的軌跡
我們以[student abc];
為例扼菠,student
是Student
類的實(shí)例對(duì)象,調(diào)用軌跡如下圖
img對(duì)于
student
來(lái)說(shuō)摄杂,并不知道abc
方法在哪里,唯一知道的就是可以去它的class對(duì)象
里面找循榆,
- 于是先通過(guò)
isa
指針進(jìn)入Student
類的class對(duì)象
析恢,如果在其中找到了abc
就直接進(jìn)行調(diào)用,調(diào)用過(guò)程結(jié)束秧饮,- 沒(méi)找到的話映挂,就通過(guò)
class對(duì)象
的superclass
指針進(jìn)入Student
類的父類,也就是Person
類的class對(duì)象
盗尸,重復(fù)上一步的查找邏輯- 以此類推柑船,一層一層往上尋找,如果最終到了基類泼各,也就是
NSObject
類的class對(duì)象
里面鞍时,還沒(méi)找到的話,由于它的superclass
為nil
扣蜻,最終就會(huì)碰到一個(gè)經(jīng)典的報(bào)錯(cuò)[ERROR: unrecognized selector sent to instance]
,調(diào)用軌跡結(jié)束
class調(diào)用類方法的軌跡
我們以[MJStudent abc];
為例調(diào)用軌跡圖如下
img對(duì)與
Student類
來(lái)說(shuō)逆巍,abc
在哪也是不知道的,我們知道類方法被規(guī)定放在meta-class對(duì)象
里面莽使,所以
- 首先蒸苇,通過(guò)
Student
的class對(duì)象
的isa指針
找到其meta-class對(duì)象
,然后在方法列表里面尋找是否有abc
吮旅,有的話就調(diào)用,調(diào)用邏輯結(jié)束味咳。- 沒(méi)有的話庇勃,就通過(guò)
meta-class對(duì)象
的superclass指針
找到Student
的父類Person
的meta-class對(duì)象
,然后查找abc
方法槽驶,找到就調(diào)用责嚷,結(jié)束調(diào)用軌跡- 沒(méi)有的話,就通過(guò)
Person
的meta-class對(duì)象
的superclass指針
掂铐,重復(fù)上一步的流程- 一次類推罕拂,通過(guò)
meta-class對(duì)象
的superclass指針
,一層層往上查找- 如果到了基類(
NSObject
)的meta-class
還沒(méi)能夠找到abc
全陨,此時(shí)比較特殊爆班,接下來(lái)的superclass指針
會(huì)找到NSObjec
t的class對(duì)象
,你可能會(huì)奇怪辱姨,我們調(diào)用一個(gè)類方法柿菩,怎么跑到class對(duì)象
里面來(lái)了,先保留你的疑問(wèn)雨涛,只需記住枢舶,蘋果確實(shí)是這么設(shè)計(jì)的懦胞,此時(shí)會(huì)繼續(xù)在NSObjec
t的class對(duì)象
里面,尋找abc
凉泄,如果真的找到了abc
躏尉,就會(huì)調(diào)用
- 如果還沒(méi)有找到,由于此時(shí)的superclass是nil后众,最終系統(tǒng)將給出報(bào)錯(cuò)
[ERROR: unrecognized selector sent to instance]
,調(diào)用軌跡結(jié)束
#import "NSObject+Test.h"
@implementation NSObject (Test)
+ (void)test
{
NSLog(@"+[NSObject test] ++++++ %p", self);
}
- (void)test
{
NSLog(@"-[NSObject test] -------- %p", self);
}
@end
@interface MJPerson : NSObject
+ (void)test;
@end
@implementation MJPerson
+ (void)test
{
NSLog(@"+[MJPerson test] ++++++++ %p", self);
}
@end
測(cè)試instance調(diào)用對(duì)象方法的軌跡及class調(diào)用類方法的軌跡
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test];
}
return 0;
}
輸出
2021-04-08 11:29:59.749350+0800 Interview02-isa和superclass[2891:125121] [MJPerson class] - 0x100004260 2021-04-08 11:29:59.749897+0800 Interview02-isa和superclass[2891:125121] [NSObject class] - 0x7fff8faa3118 2021-04-08 11:29:59.749951+0800 Interview02-isa和superclass[2891:125121] +[MJPerson test] ++++++++ 0x100004260 2021-04-08 11:29:59.749974+0800 Interview02-isa和superclass[2891:125121] +[NSObject test] ++++++ 0x7fff8faa3118
當(dāng)我們把NSobject的test方法去掉
@implementation NSObject (Test)
- (void)test
{
NSLog(@"-[NSObject test] -------- %p", self);
}
@end
此時(shí)調(diào)用軌跡右會(huì)發(fā)生神馬變化胀糜?
isa指針指向哪里?
根據(jù)我們上面的梳理和總結(jié)吼具,我們可以得出結(jié)論
isa(of instance) --> isa(of class) --> isa(of meta-class)
下面我們通過(guò)代碼來(lái)驗(yàn)證一下
// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation MJPerson
- (void)test
{
}
- (void)personInstanceMethod
{
}
+ (void)personClassMethod
{
}
- (id)copyWithZone:(NSZone *)zone
{
return nil;
}
@end
// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation MJStudent
- (void)test
{
}
- (void)studentInstanceMethod
{
}
+ (void)studentClassMethod
{
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
Class personClass = [MJPerson class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
}
輸出
2021-04-08 11:52:08.970783+0800 Interview03-isa[3039:136373] 0x100415cf0 0x1000044c8 0x1000044a0
我們?cè)诖a中加入斷點(diǎn)僚纷,通過(guò)控制臺(tái)查看一下person
的isa
信息。
通過(guò)p/x
命令來(lái)打印指針拗盒,/
后面是打印參數(shù)怖竭,x
參數(shù)表示用16進(jìn)制數(shù)輸出。因?yàn)槲覀冎?code>person這個(gè)instance
的結(jié)構(gòu)體的包含一個(gè)isa
成員變量陡蝇,person
本身就是指針痊臭,所以可以通過(guò)person->isa
訪問(wèn)isa
的值。
代碼里面登夫,personClass
是Person類
的class對(duì)象
广匙,輸出結(jié)果顯示,
person的isa = 0x001d8001000044c9
personClass = 0x00000001000044c8 它倆恼策。鸦致。。并不相等;量7滞佟! 這是什么情況狮斗?不是說(shuō)好了
instance對(duì)象的
isa指向
class`對(duì)象嘛绽乔?
其實(shí)在64位機(jī)器出現(xiàn)之前,instance對(duì)象
的isa
確實(shí)是直接指向class對(duì)象
的碳褒,
也就是
person->isa == personClass
折砸,
從64bit開(kāi)始,isa
需要進(jìn)行一次為運(yùn)算沙峻,才能計(jì)算出真實(shí)的class對(duì)象
地址睦授,系統(tǒng)給我們提供了一個(gè)ISA_MASK,這個(gè)可以在objc4源碼里面找到。我先直接貼出來(lái)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
大家請(qǐng)看清這里是分了arm64和x86_64的专酗,分別對(duì)應(yīng)的是移動(dòng)設(shè)備開(kāi)發(fā)和mac開(kāi)發(fā)睹逃。我的代碼是一個(gè)mac命令行工程,所以我們用x86的這個(gè)值來(lái)試一下
可以看到,結(jié)果就顯而易見(jiàn)了沉填。通過(guò)和ISA_MASK進(jìn)行一次&運(yùn)算疗隶,我們得到了personClass的地址。同樣翼闹,我們來(lái)試一下personClass的isa指針斑鼻。
結(jié)果我試圖通過(guò)personClass->isa
先打印出其isa
指針的時(shí)候,得到了錯(cuò)誤提示猎荠,告訴我們說(shuō)personClass
的類型Class
不是一個(gè)結(jié)構(gòu)體坚弱,看不太明白,那就先查看一下Class
的定義关摇,typedef struct objc_class *Class;
荒叶,然后在往下看一下objc_class
的細(xì)節(jié)
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
雖然這個(gè)結(jié)構(gòu)體里面有isa指針,但是尾部的OBJC2_UNAVAILABLE;
提示我們输虱,這已經(jīng)是過(guò)時(shí)的API了些楣。
不過(guò)我們知道class對(duì)象里面第一個(gè)成員變量確實(shí)是一個(gè)isa指針
struct mj_objc_class {
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
Class personClass = [MJPerson class];
struct mj_objc_class *personClass2 = (__bridge struct cl_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
我們自定義一個(gè)struct,包含一個(gè)isa指針宪睹,然后再借助這個(gè)結(jié)構(gòu)體類型來(lái)讀取personClass里面的內(nèi)容愁茁,如上代碼,我們用personClass2在來(lái)嘗試一次
ok亭病,結(jié)果顯示鹅很,class對(duì)象
的isa指針
直接指向了mete-class對(duì)象
的地址。
到此罪帖,上面的面試題相信大家已經(jīng)可以完整回答了促煮。在用一個(gè)圖來(lái)總結(jié)一下就是
你會(huì)許還會(huì)問(wèn),那么superclass指針呢整袁,是不是也需要一個(gè)什么mask轉(zhuǎn)換污茵?答案是不需要的,可以用上面相同的方法進(jìn)行驗(yàn)證葬项。總之isa指針稍微特殊一點(diǎn)點(diǎn)迹蛤,特別記住一下關(guān)于ISA_MASK的細(xì)節(jié)就行民珍。
struct mj_objc_class {
Class isa;
Class superclass;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// MJPerson類對(duì)象的地址:0x00000001000014c8
// isa & ISA_MASK:0x00000001000014c8
// MJPerson實(shí)例對(duì)象的isa:0x001d8001000014c9
struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);
NSLog(@"1111");
}
}
特別備注
本系列文章總結(jié)自MJ老師在騰訊課堂開(kāi)設(shè)的OC底層原理課程,相關(guān)圖片素材均取自課程中的課件盗飒。