iOS 底層 -- alloc與init

[TOC]

1镇防、 什么是runtime

runtime 是C活玲、C++股囊、匯編實現(xiàn)的一套API袜匿,目的是為 OC增加運行時功能

2、 關(guān)于alloc與init到底在底層做了什么

看以下打印信息

JEObject *obj = [JEObject alloc];
JEObject *obj1 = [obj init];
JEObject *obj2 = [obj init];
NSLog(@"\n obj - %p\n obj1 - %p\n obj2 - -%p",obj,obj1,obj2);

打印結(jié)果為:

 obj - 0x600003218320
 obj1 - 0x600003218320
 obj2 - -0x600003218320

結(jié)果很奇怪,三個對象的地址都完全一樣稚疹。既然很奇怪居灯,那就 來看看源碼,直接在xcode中是看不到源碼的内狗,我們需要在opensource中去下載涉及到的源碼 ,在MacOS中的最新版本(當(dāng)前是10.14.5)

opensource_macos.png

點開之后 搜索objc 得到2個搜索結(jié)果 下載objc4-xxxx(后面的代表版本號怪嫌,會隨著版本的更新而變化)
objc4.png

這里有一個問題,在opensource中柳沙,有如此多的源碼岩灭,如何知道 要下載objc的這份源碼呢,這里提供2種方式

方法一赂鲤、匯編:

將斷點打在 JEObject *obj = [JEObject alloc];這一行
菜單欄 -> Debug -> Debug workflow -> Always show disassembly 勾選這個選項(也就是開啟匯編模式)
程序運行起來噪径,進入斷點的時候出現(xiàn)這樣的代碼:(不同版本的Xcode柱恤,內(nèi)容會稍有區(qū)別)

匯編模式(xcode 10.3)

匯編模式 Xcode 11.0

在這里我們大致可以猜一下 明顯的雙引號中間的紅色文字,應(yīng)該表示的意思就是方法名熄云,當(dāng)然在這里膨更,這些可以不去理會。

這里需要重點關(guān)注callq (更老版本的Xcode 這里是 bl
callq 表示的是調(diào)用了方法 缴允。在當(dāng)前斷點下面最近的那個callq那一行打上斷點 (也就是 objc_msgSend 或者 objc_alloc)荚守,繼續(xù)走,來到斷點時练般, 按住control鍵矗漾, 注意看框起來的部分 框起來的那個地方發(fā)生了變化

進入objc_msgSend內(nèi)部

點擊 框起來的那個按鈕,(control 不松開)薄料,進去之后 會出現(xiàn)很精簡的匯編內(nèi)容
control 進入之后

繼續(xù)點擊 step into 按鈕 就會出現(xiàn)我們需要的內(nèi)容


最終結(jié)果

這樣說就拿到了

方法二: 符號斷點法

添加 符號斷點(可能很多人只添加過全局斷點敞贡,還不知道符號斷點是啥??)

添加符號斷點

步驟一:在需要探究的方法上打斷點


在需要的地方打斷點

步驟二:當(dāng)程序運行到此斷點的時候 添加符號斷點

寫上當(dāng)前調(diào)用的方法名(關(guān)鍵詞) 比如 ‘a(chǎn)lloc’ ‘init’ ‘setimage:’等
為符號斷點寫上方法名

符號斷點添加成功之后(可能會小卡一下)讓斷點直接走(下一曲圖標(biāo)的那個)就出現(xiàn)了我們需要的內(nèi)容
需要的結(jié)果

有了這個結(jié)果,就可以去opensource搜索源碼了摄职。

方法三誊役、偶然發(fā)現(xiàn),不知道是否正確

進入API的系統(tǒng)聲明谷市,就是按住command ->jump to definition,
箭頭所指即為所需要的結(jié)果蛔垢,拿這個信息去opensource搜索

偶然發(fā)現(xiàn)

??:這個地方如果是文件夾,是有源碼可找的迫悠,如果是framework 那么就是沒有源碼可看的

2.1 alloc的底層實現(xiàn)

打開源碼文件鹏漆,搜索 alloc { (系統(tǒng)的方法 一般都是在方法最后加一個空格,然后才跟上大括號)進去之后 查看具體的實現(xiàn)创泄,其經(jīng)過了2次調(diào)用 alloc -> _objc_rootAlloc -> callAlloc 在callAlloc中得到了返回實際的對象艺玲。

alloc

_objc_rootAllc
callAlloc

2.2 init的底層實現(xiàn)

同樣的方法, 搜索 init {,進去之后 只調(diào)用了一個方法 init -> _objc_rootInit 在 rootInit方法中 只做了一件事情鞠抑, 就是 retrun obj饭聚。

init

objc_rootInit

到了這里 就知道了 為何上面 alloc之后 2次init 打印的地址都是一樣的了。

2.3 new的底層實現(xiàn)

既然都已經(jīng)進來了搁拙,那我們就來驗證另外一個問題 new秒梳,我們經(jīng)常說 new方法 其實就相當(dāng)于是 alloc init,那么我們進入源碼看看他們之間究竟是否有差別感混,
同樣的方法, 搜索 new { 進入之后 查看實現(xiàn) 里面只有一次方法的實現(xiàn)

new 方法的實現(xiàn)

在new方法里面就是調(diào)用alloc的實現(xiàn)(callAlloc)后 進行了init操作礼烈,由此可見弧满,[Class new] 完全等價于 [[Class alloc] init]

問題來了

上面說,init沒有任何操作此熬, 由此引出另外一個問題:既然init沒有做任何操作庭呜,為何還要有init這個步驟滑进?

要想回答這個問題,先想想募谎,init是在什么時候會用到扶关?
重寫方法的時候! 在初始化一個類的時候数冬,如果這個類的參數(shù)需要默認的值节槐, 一般我們都會選擇在 init方法中初始化這個值(比如封裝一個倒計時按鈕,一般都會在init中初始化 time = 60)拐纱。
父類不實現(xiàn)铜异,交給子類根據(jù)需求去實現(xiàn),

3 LLVM(編譯器)

1秸架、 補充 sieof()知識

sizeof() 是獲取類型的大小

32位系統(tǒng) 64位系統(tǒng)
BOOL 1 1
char 1 1
short 2 2
float 4 4
CGFloat 8 8
int 4 4
long 4 8
double 8 8
long long 8 8
void *(指針) 4 8
結(jié)構(gòu)體指針(struct */Class) 4 8
結(jié)構(gòu)體 最大屬性內(nèi)存的倍數(shù) 詳細算法參考 同32位

結(jié)構(gòu)體的sizeof() 長度 用 補揍庄、偏、長的方式 是一個很好理解和計算的方法

  • 舉例1---簡單型結(jié)構(gòu)體:
struct JEObject_IMPL {
    NSString    *x; // 補0  偏0  長8
    int         a;  // 補0  偏8  長12
    char        y;  // 補0  偏12  長13
    char        t;  // 補0  偏13  長14     14不是8(最長是NSString * 的8位)的倍數(shù) 內(nèi)存對齊為16
};
  • 舉例2---復(fù)合型結(jié)構(gòu)體:
struct JETemp_IMPL {
    NSString *a;     // 補0  偏0  長8
    int b;           // 補0  偏8  長12      12不是8的倍數(shù)  內(nèi)存對齊為16
};
struct JEObject_IMPL {    
    struct JETemp_IMPL x; // 補0  偏0  長16
    char y;               // 補0  偏16  長17
    char t;               // 補0  偏17  長18
    CGSize  z;            // 補6  偏24  長40     40是8(最長是JETemp_IMPL中NSString * 的8位)的倍數(shù)
                           // 這里為什么是補6 东抹?  因為CGSize是結(jié)構(gòu)體蚂子,里面最長位是8  按照8的倍數(shù)來補齊
};

如果使用 sizeof(obj) 得到的實際只是 指針的大小 也就是 4/8
想要知道實際上obj的內(nèi)存酝枢,繼續(xù)下面的問題講解

2卡睦、 malloc_size() ——sizeof() —— class_getInstanceSize()

定義這樣一個對象JEObject

@interface JEObject : NSObject {
    @public
    int _age;
    int _height;
    NSString *_name;
}
@end
    JEObject *obj = [JEObject alloc];
    NSLog(@"%zu",class_getInstanceSize([obj class]));
    NSLog(@"%lu",sizeof(obj));

注意和sizeof()的區(qū)別
問題:創(chuàng)建一個JEObject

  • 對象內(nèi)存占多少?
  • 系統(tǒng)為其分配了多少內(nèi)存

這里涉及到對象的本質(zhì)犹赖,
NSobject的本質(zhì)就是一個包含一個指針(這個指針是指向結(jié)構(gòu)體的指針)的結(jié)構(gòu)體
可以通過一個命令將OC代碼轉(zhuǎn)成C

cd 到待轉(zhuǎn)換文件的路徑
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc JEObject.m -o out.cpp
//命令解釋:最后的 JEObject.m 是待轉(zhuǎn)換文件的文件名试浙, out.cpp 是輸出文件的文件名
// 最后得到一個 out.cpp 的文件

在out.cpp中可以搜索JEObject_IMPL就能看到其具體結(jié)構(gòu)

//NSObject
struct NSObject_IMPL {
    Class isa;
// typedef struct objc_class *Class; ---> 指向結(jié)構(gòu)體的指針
};
struct JEObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
    NSString *_name;
};

有3個函數(shù)是獲取大小的董瞻,但是具體表示的意義不一樣
malloc_size()

Returns size of given ptr
系統(tǒng)為其分配的內(nèi)存大小

sizeof()

獲取對象所占內(nèi)存大小
這是一個運算符 而不是方法,在編譯的時候就是一個確定的數(shù)據(jù)

class_getInstanceSize()

Returns the size of instances of a class
獲取類的實例對象的成員變量所占用的大小

alloc的關(guān)鍵步驟:


alloc關(guān)鍵步驟

其中 instanceSize()方法的代碼

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

內(nèi)存對齊的算法:

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
// 相當(dāng)于    (x + 7)  >> 3 << 3;

/*  8字節(jié)對具體算法 田巴,同理可得 16字節(jié)對其
 詳細步驟
 (8 + 7) &  ~7

 二字節(jié)方式表述:
 15:       0000 1111
 7:       0000 0111
~7:        1111 1000   (取反) 
~7 & 15    0000 1000 (相同為1 否則為0)
最后得到的就是  0000 1000  也就是8 

‘|’ 運算  有1得1  否則為0 
*/
}

3钠糊、類所占內(nèi)存的大小

對于類所占用的內(nèi)存要分情況來看,上面提到過壹哺,類的本質(zhì)是結(jié)構(gòu)體抄伍,那么類所占用的內(nèi)存大小 就是這個結(jié)構(gòu)體的內(nèi)存大小,編譯之后具體是怎樣的結(jié)構(gòu)體,要看具體是用什么方式聲明的屬性
1管宵、 內(nèi)部類的方式

@interface JEObject ()
{
    int _age;
    NSString *_name;
    int _height;
}
@end

??對于這種方式截珍,其轉(zhuǎn)換為C代碼之后(前面有提到OC轉(zhuǎn)C的命令)變量的順序不變,在最前面加上了isa指針箩朴,所以用補岗喉、偏、長的方法來計算這個JEObject的 class_getInstanceSize()為32 malloc_size()為32

2炸庞、用@property的方式聲明屬性

@interface JEObject ()
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int height;
@end

??對于這種方式钱床,其轉(zhuǎn)換為C代碼之后(前面有提到OC轉(zhuǎn)C的命令)變量的順序發(fā)生了變化(最前面的加上isa指針之后,<以最優(yōu)的內(nèi)存方式埠居?> 查牌、< 以單個屬性從小到大的方式事期?>),所以用補纸颜、偏兽泣、長的方法來計算這個JEObject的 class_getInstanceSize()為24 malloc_size()為32
具體轉(zhuǎn)換的結(jié)果如下??:

struct JEObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
    NSString *_name;
};

3、既有內(nèi)部類胁孙、又有修飾符唠倦、還有.m聲明

JEObject.h

@interface JEObject : NSObject
{
    NSString    *_strb;
    NSString    *_stra;
    NSString    *_stringc;
}
@property (nonatomic, strong) NSString *b;
@property (nonatomic, assign) NSString *a;
@property (nonatomic, assign) char c;
@end

JEObject.m

@interface JEObject ()
{
    NSInteger   intIn;
}
@property (nonatomic, assign) char cIn;
@end
@implementation JEObject
@end

??對于這種方式,其轉(zhuǎn)換為C代碼之后變量的順序發(fā)生了變化(最前面的加上isa指針之后浊洞,.h中的內(nèi)部類中的屬性先排在前面牵敷,再排.m中的內(nèi)部類屬性,再將.h法希、.m 中的修飾符屬性按照最優(yōu)的方式排列
具體轉(zhuǎn)換的結(jié)果如下??:

struct JEObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_strb;
    NSString *_stra;
    NSString *_stringc;
    NSInteger intIn;
    char _c;
    char _cIn;
    NSString * _Nonnull _b;
    NSString * _Nonnull _a;
};
//class_getInstanceSize
//malloc_size   用法

// class_getInstanceSize(Class _Nullable cls)  參數(shù)是Class
size_t size = class_getInstanceSize([p class]);

//malloc_size(const void *ptr); 參數(shù)是指針  不過需要橋接  可以根據(jù)報錯信息 自動Fix
size_t t1 = malloc_size((__bridge const void *)(p));

總結(jié):
malloc_size()class_getInstanceSize() 區(qū)別
文字理解:

malloc_size() 系統(tǒng)創(chuàng)建時 系統(tǒng)為對象分配了多少內(nèi)存枷餐,
class_getInstanceSize() 對象實際利用了多少內(nèi)存

代碼層次的理解:

malloc_size () 可以認為是在 class_getInstanceSize() 之后 進行了一次16位內(nèi)存對齊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市苫亦,隨后出現(xiàn)的幾起案子毛肋,更是在濱河造成了極大的恐慌,老刑警劉巖屋剑,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件润匙,死亡現(xiàn)場離奇詭異,居然都是意外死亡唉匾,警方通過查閱死者的電腦和手機孕讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巍膘,“玉大人厂财,你說我怎么就攤上這事∠啃福” “怎么了璃饱?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肪康。 經(jīng)常有香客問我荚恶,道長,這世上最難降的妖魔是什么磷支? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任谒撼,我火速辦了婚禮,結(jié)果婚禮上雾狈,老公的妹妹穿的比我還像新娘廓潜。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布茉帅。 她就那樣靜靜地躺著,像睡著了一般锭弊。 火紅的嫁衣襯著肌膚如雪堪澎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天味滞,我揣著相機與錄音樱蛤,去河邊找鬼。 笑死剑鞍,一個胖子當(dāng)著我的面吹牛昨凡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚁署,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼便脊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了光戈?” 一聲冷哼從身側(cè)響起哪痰,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎久妆,沒想到半個月后晌杰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡筷弦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年肋演,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烂琴。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡爹殊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出监右,到底是詐尸還是另有隱情边灭,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布健盒,位于F島的核電站绒瘦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扣癣。R本人自食惡果不足惜惰帽,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望父虑。 院中可真熱鬧该酗,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爵嗅,卻和暖如春娇澎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睹晒。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工趟庄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伪很。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓戚啥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锉试。 傳聞我的和親對象是個殘疾皇子猫十,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

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