OC對(duì)象的探究(上篇)

大家好泛源!我是Tony,一個(gè)熱愛(ài)技術(shù),希望運(yùn)用技術(shù)改變生活的的追夢(mèng)男孩。閑話不多說(shuō),下面帶大家認(rèn)識(shí)一下Objective-C的對(duì)象从藤。

從OC的角度看對(duì)象本質(zhì)

OC是一門面向?qū)ο蟮恼Z(yǔ)言,我們從一開(kāi)始接觸OC的時(shí)候锁蠕,肯定都接觸過(guò)NSObject對(duì)象夷野,和其他語(yǔ)言一樣,如Java的Object荣倾,是整個(gè)OC的根類型悯搔,通俗的講就是OC中所有的類都是直接或者間接的繼承于此類,下面我們先移步到NSObject的頭文件舌仍,看看里面有哪些內(nèi)容鳖孤。

NSObject協(xié)議
@protocol NSObject
//判斷對(duì)象相等的方法
- (BOOL)isEqual:(id)object;
//獲取對(duì)象的hash值
@property (readonly) NSUInteger hash;
//父類的類對(duì)象
@property (readonly) Class superclass;
- (Class)class ;
//自身指針
- (instancetype)self;
//直接向?qū)ο蟀l(fā)送消息的方法,繞過(guò)編譯器的檢測(cè)(運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)去找方法抡笼,在編譯時(shí)候不做任何校驗(yàn))苏揣,
//Cocoa支持在運(yùn)行時(shí)向某個(gè)類添加方法,即方法編譯時(shí)不存在推姻,但是運(yùn)行時(shí)候存在平匈,這時(shí)候必然需要使用performSelector去調(diào)用。
//為了程序的健壯性,會(huì)使用檢查方法- (BOOL)respondsToSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
//自省方法 用來(lái)判斷一個(gè)對(duì)象是不是某一個(gè)類的對(duì)象(包括子類)
- (BOOL)isKindOfClass:(Class)aClass;
//自省方法 用來(lái)判斷一個(gè)對(duì)象是不是某一個(gè)類的對(duì)象(不包括子類)
- (BOOL)isMemberOfClass:(Class)aClass;
//判斷對(duì)象是否尊守某個(gè)協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//判斷方法是否存在
- (BOOL)respondsToSelector:(SEL)aSelector;
//內(nèi)存管理相關(guān)的方法
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@end
NSObject類

這里僅留下了方法調(diào)用相關(guān)的方法

//方法查找相關(guān)的方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
//第一步:在方法查找過(guò)程中增炭,未處理掉的方法就會(huì)來(lái)到此處忍燥,詢問(wèn)是否在運(yùn)行時(shí)增加該方法的IMP
//類方法決議(動(dòng)態(tài)方法解析),調(diào)用resolveClassMethod給個(gè)機(jī)會(huì)讓類添加這個(gè)實(shí)現(xiàn)這個(gè)函數(shù)
+ (BOOL)resolveClassMethod:(SEL)sel 
//類實(shí)例方法決議(動(dòng)態(tài)方法解析)隙姿,調(diào)用resolveInstanceMethod給個(gè)機(jī)會(huì)讓類添加這個(gè)實(shí)現(xiàn)這個(gè)函數(shù)
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//第二步:快速方法轉(zhuǎn)發(fā)梅垄,讓其他類處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
//第三步:快速方法轉(zhuǎn)發(fā),讓其他類處理
//對(duì)象方法簽名输玷,用于生成NSInvocation對(duì)象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector队丝;
//實(shí)例方法簽名,用于生成NSInvocation對(duì)象
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation ;
//第四步:方法找不到時(shí)欲鹏,來(lái)到此方法机久,拋出異常
- (void)doesNotRecognizeSelector:(SEL)aSelector;

NSObject類干的事情有hash、equal赔嚎、對(duì)象的原型膘盖、自省、內(nèi)存管理尤误、方法查找侠畔、動(dòng)態(tài)方法解析、方法的快速轉(zhuǎn)發(fā)等损晤,
在頭文件中能夠看到和對(duì)象本質(zhì)相關(guān)聯(lián)的就是Class,isa等關(guān)鍵詞软棺,下面我們將繼續(xù)學(xué)習(xí)Class和isa

Class的本質(zhì)

Class的定義是typedef struct objc_class *Class;,在objc4中objc_class是繼承objc_object,所以我們先看看objc_object的定義:

struct objc_object {
private:
    isa_t isa;
public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // object may have associated objects?
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();
    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();
    ..........
};

可以看見(jiàn)objc_object的定義非常簡(jiǎn)單,內(nèi)部有一個(gè)isa成員沉馆。
下面我們看看objc4中objc_class的結(jié)構(gòu)

image.png

下面是objc4中objc_class的部分定義代碼

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    bool isMetaClass() {
        ASSERT(this);
        ASSERT(isRealized());
#if FAST_CACHE_META
        return cache.getBit(FAST_CACHE_META);
#else
        return data()->ro->flags & RO_META;
#endif
    }

    // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        return bits.safe_ro()->flags & RO_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    ...........
};
  • isa(繼承自objc_object)指向元類
  • superclass指向父類
  • cache方法緩存,當(dāng)調(diào)用一次方法后就會(huì)緩存進(jìn)vtable中,加速下次調(diào)用
  • bits這是今天的主角,就是存儲(chǔ)類的方法码党、屬性和遵循的協(xié)議等信息的地方
    我們從Class的結(jié)構(gòu)中可以看見(jiàn)德崭,類對(duì)象其實(shí)存儲(chǔ)了實(shí)例的變量和方法斥黑,及相關(guān)的協(xié)議;對(duì)象則是存儲(chǔ)實(shí)例變量的值眉厨;我們經(jīng)常會(huì)使用類的類方法锌奴,在Class的結(jié)構(gòu)中我們并沒(méi)有看見(jiàn)類方法的存儲(chǔ)位置,類對(duì)象的isa其實(shí)指向的就是metaclass,而metaclass中存儲(chǔ)了類方法憾股。

看了這些是不是還有很多疑惑鹿蜀,如objc_cache方法緩存原理是啥?什么時(shí)候使用到服球?為什么要設(shè)計(jì)metaclass?方法的調(diào)用過(guò)程是什么茴恰?不著急后面將一一解答

從C/C++的角度看看對(duì)象本質(zhì)

我們通過(guò)創(chuàng)建OC對(duì)象,并將OC文件轉(zhuǎn)化為C++文件來(lái)探尋OC對(duì)象的本質(zhì)斩熊,OC如下代碼

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSObject *object = [[NSObject alloc] init];
        NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//objc對(duì)象實(shí)際需要的內(nèi)存大小: 8
        NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//objc對(duì)象實(shí)際分配的內(nèi)存大小: 16
    }
    return 0;
}

使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 代碼
clang -rewrite-objc main.m -o main.cpp
或者使用 XCode 工具 xcrun 進(jìn)行轉(zhuǎn)換
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
打開(kāi)main.cpp定位到main函數(shù)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
//簡(jiǎn)化版本
        NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"),sel_registerName("alloc")),sel_registerName("init"))
    }
    return 0;
}

這里可以看到往枣,objc_msgSend先向NSObject發(fā)送了alloc,然后發(fā)送了init消息;此時(shí)對(duì)象就被初始化完畢,下面我們打印一下內(nèi)存占用情況分冈,代碼如下

 NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:8
 NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16

是不是很疑惑圾另,對(duì)象僅僅使用了8字節(jié)為什么會(huì)分配16個(gè)字節(jié)的大小呢?對(duì)于這個(gè)問(wèn)題我們可以通過(guò)閱讀objc4的源代碼來(lái)找到答案雕沉。通過(guò)查看跟蹤obj4中alloc和allocWithZone兩個(gè)函數(shù)的實(shí)現(xiàn)集乔,會(huì)發(fā)現(xiàn)這個(gè)連個(gè)函數(shù)都會(huì)調(diào)用一個(gè)instanceSize的函數(shù):

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16bytes.
        if (size < 16) size = 16;
        return size;
    }

是不是豁然開(kāi)朗,對(duì)象的內(nèi)存大小最低消費(fèi)就是16字節(jié)坡椒。
下面我們看看對(duì)象中有成員變量時(shí)扰路,對(duì)象的內(nèi)存占用變化

@interface Person : NSObject
@property (nonatomic,assign) int age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *object = [[Person alloc] init];
        NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:16
        NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16
    }
    return 0;
}

此時(shí)對(duì)象的實(shí)際內(nèi)存大小和分配內(nèi)存大小都是16字節(jié),我們知道int類型是占4個(gè)字節(jié)肠牲,而8+4=12幼衰,這應(yīng)該才是實(shí)際內(nèi)存占用的大小,為什么是16字節(jié)呢缀雳?
這里就需要引入內(nèi)存地址對(duì)齊的問(wèn)題渡嚣,地址對(duì)齊意思就是內(nèi)存的增加必須滿足是8字節(jié)的倍數(shù),所以12字節(jié)不滿16肥印,則分配16字節(jié)识椰,其中增加了4字節(jié)的占位
有了這個(gè)概念后,如果我為Person對(duì)象再增加一個(gè)int類型number的屬性深碱,實(shí)際內(nèi)存大小是否會(huì)改變呢腹鹉?下面我們看一下打印結(jié)果:

@interface Person : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int number;
@end
@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *object = [[Person alloc] init];
        NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:16
        NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16
    }
    return 0;
}

可以看見(jiàn)打印結(jié)果都是16,說(shuō)明內(nèi)存大小并無(wú)增加敷硅,此時(shí)number的內(nèi)存剛好填補(bǔ)了之前的4字節(jié)占位內(nèi)存功咒,不會(huì)造成內(nèi)存的浪費(fèi)
上篇文章先到此,感謝大家的閱讀绞蹦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末力奋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子幽七,更是在濱河造成了極大的恐慌景殷,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澡屡,死亡現(xiàn)場(chǎng)離奇詭異猿挚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)驶鹉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門绩蜻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人室埋,你說(shuō)我怎么就攤上這事办绝√ざ担” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵八秃,是天一觀的道長(zhǎng)碱妆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)昔驱,這世上最難降的妖魔是什么疹尾? 我笑而不...
    開(kāi)封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮骤肛,結(jié)果婚禮上纳本,老公的妹妹穿的比我還像新娘。我一直安慰自己腋颠,他們只是感情好繁成,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著淑玫,像睡著了一般巾腕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上絮蒿,一...
    開(kāi)封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天尊搬,我揣著相機(jī)與錄音,去河邊找鬼土涝。 笑死佛寿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的但壮。 我是一名探鬼主播冀泻,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜡饵!你這毒婦竟也來(lái)了弹渔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤验残,失蹤者是張志新(化名)和其女友劉穎捞附,沒(méi)想到半個(gè)月后巾乳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體您没,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年胆绊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氨鹏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡压状,死狀恐怖仆抵,靈堂內(nèi)的尸體忽然破棺而出跟继,到底是詐尸還是另有隱情,我是刑警寧澤镣丑,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布舔糖,位于F島的核電站,受9級(jí)特大地震影響莺匠,放射性物質(zhì)發(fā)生泄漏金吗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一趣竣、第九天 我趴在偏房一處隱蔽的房頂上張望摇庙。 院中可真熱鬧,春花似錦遥缕、人聲如沸卫袒。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夕凝。三九已至,卻和暖如春户秤,著一層夾襖步出監(jiān)牢的瞬間迹冤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工虎忌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泡徙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓膜蠢,卻偏偏與公主長(zhǎng)得像堪藐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挑围,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容