[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)
點開之后 搜索objc 得到2個搜索結(jié)果 下載objc4-xxxx(后面的代表版本號怪嫌,會隨著版本的更新而變化)
這里有一個問題,在opensource中柳沙,有如此多的源碼岩灭,如何知道 要下載objc的這份源碼呢,這里提供2種方式
方法一赂鲤、匯編:
將斷點打在 JEObject *obj = [JEObject alloc];
這一行
菜單欄 -> Debug -> Debug workflow -> Always show disassembly 勾選這個選項(也就是開啟匯編模式)
程序運行起來噪径,進入斷點的時候出現(xiàn)這樣的代碼:(不同版本的Xcode柱恤,內(nèi)容會稍有區(qū)別)
在這里我們大致可以猜一下 明顯的雙引號中間的紅色文字,應(yīng)該表示的意思就是方法名熄云,當(dāng)然在這里膨更,這些可以不去理會。
這里需要重點關(guān)注callq
(更老版本的Xcode 這里是 bl
)
callq
表示的是調(diào)用了方法 缴允。在當(dāng)前斷點下面最近的那個callq那一行打上斷點 (也就是 objc_msgSend 或者 objc_alloc)荚守,繼續(xù)走,來到斷點時练般, 按住control
鍵矗漾, 注意看框起來的部分 框起來的那個地方發(fā)生了變化
繼續(xù)點擊 step into 按鈕 就會出現(xiàn)我們需要的內(nèi)容
這樣說就拿到了
方法二: 符號斷點法
添加 符號斷點(可能很多人只添加過全局斷點敞贡,還不知道符號斷點是啥??)
步驟一:在需要探究的方法上打斷點
步驟二:當(dāng)程序運行到此斷點的時候 添加符號斷點
符號斷點添加成功之后(可能會小卡一下)讓斷點直接走(下一曲圖標(biāo)的那個)就出現(xiàn)了我們需要的內(nèi)容
有了這個結(jié)果,就可以去opensource搜索源碼了摄职。
方法三誊役、偶然發(fā)現(xiàn),不知道是否正確
進入API的系統(tǒng)聲明谷市,就是按住command
->jump to definition
,
箭頭所指即為所需要的結(jié)果蛔垢,拿這個信息去opensource搜索
??:這個地方如果是文件夾,是有源碼可找的迫悠,如果是framework 那么就是沒有源碼可看的
2.1 alloc的底層實現(xiàn)
打開源碼文件鹏漆,搜索 alloc {
(系統(tǒng)的方法 一般都是在方法最后加一個空格,然后才跟上大括號)進去之后 查看具體的實現(xiàn)创泄,其經(jīng)過了2次調(diào)用 alloc -> _objc_rootAlloc -> callAlloc
在callAlloc中得到了返回實際的對象艺玲。
2.2 init的底層實現(xiàn)
同樣的方法, 搜索 init {
,進去之后 只調(diào)用了一個方法 init -> _objc_rootInit
在 rootInit方法中 只做了一件事情鞠抑, 就是 retrun obj饭聚。
到了這里 就知道了 為何上面 alloc之后 2次init 打印的地址都是一樣的了。
2.3 new的底層實現(xiàn)
既然都已經(jīng)進來了搁拙,那我們就來驗證另外一個問題 new
秒梳,我們經(jīng)常說 new方法 其實就相當(dāng)于是 alloc init,那么我們進入源碼看看他們之間究竟是否有差別感混,
同樣的方法, 搜索 new {
進入之后 查看實現(xiàn) 里面只有一次方法的實現(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)鍵步驟:
其中 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)存對齊