iOS 底層第5
天的學(xué)習(xí)更米。越學(xué)到后面越覺得自己不懂的知識(shí)點(diǎn)實(shí)在太多了,我會(huì)繼續(xù)按照kc老師教的方法和思路摸索下去毫痕。
成員變量&屬性
@interface Person : NSObject {
NSString *nickName;
int age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *address;
@end
@implementation Person
@end
- 我們進(jìn)行
clang -rewrite-objc main.m -o main.cpp
一下 生成cpp
文件代碼看下源碼
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *nickName;
int age;
NSString *_name;
NSString *_address;
};
// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, strong) NSString *address;
/* @end */
// @implementation Person
static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
static NSString * _I_Person_address(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_address)); }
static void _I_Person_setAddress_(Person * self, SEL _cmd, NSString *address) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_address)) = address; }
這里有個(gè)疑問同樣是屬性征峦,為什么
name
會(huì)有objc_setProperty
而address
卻沒有呢?objc_setProperty
在什么時(shí)候去進(jìn)行重定向的消请?
- 接下來就要去探索
llvm
源碼了栏笆。 - 搜索
objc_setProperty
找到如下代碼
llvm::FunctionCallee getSetPropertyFn() {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
CanQualType Params[] = {
IdType,
SelType,
Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(),
IdType,
Ctx.BoolTy,
Ctx.BoolTy};
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
}
-繼續(xù)探索,到底是哪里調(diào)用了這個(gè)方法 getSetPropertyFn
臊泰,繼續(xù)搜索 getSetPropertyFn
蛉加,找到如下代碼
llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {
return ObjCTypes.getSetPropertyFn();
}
- 原來
getSetPropertyFn()
這個(gè)只是一個(gè)中間方法。
我們?yōu)槭裁匆粩嗳ヌ剿髂兀刻剿鞯哪康挠质鞘裁茨兀?/p>
- 最終的目的就是為了要找到
objc_setProperty
到底是根據(jù)哪些條件進(jìn)行創(chuàng)建的针饥。繼續(xù)搜索GetPropertySetFunction
PropertyImplStrategy strategy(CGM, propImpl);
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
... 省略部分代碼
case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet: {
...
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
}
- 找到了
GetPropertySetFunction
調(diào)用厂抽,往上看 發(fā)現(xiàn)原來 是一個(gè)Switch
條件,根據(jù)strategy.getKind()
獲取策略的類型 丁眼,當(dāng)策略類型是PropertyImplStrategy::SetPropertyAndExpressionGet
就會(huì)去創(chuàng)建筷凤。 - 原來是一個(gè)策略,策略的類型 來自于
PropertyImplStrategy
繼續(xù)搜索PropertyImplStrategy
苞七,找到是在哪里進(jìn)行賦值的呢藐守。
/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl) {
const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
IsAtomic = prop->isAtomic();
HasStrong = false; // doesn't matter here.
// Evaluate the ivar's size and alignment.
ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
QualType ivarType = ivar->getType();
auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
IvarSize = TInfo.Width;
IvarAlignment = TInfo.Align;
// If we have a copy property, we always have to use getProperty/setProperty.
// TODO: we could actually use setProperty and an expression for non-atomics.
if (IsCopy) {
Kind = GetSetProperty;
return;
}
...
}
找到
PropertyImplStrategy
的初始化方法,如果IsCopy
那么Kind
=GetSetProperty
.結(jié)論:只要屬性有
copy
在底層就會(huì)有objc_setProperty
方法
那么還有一個(gè)疑問點(diǎn)就是
objc_getProperty
在什么時(shí)候會(huì)創(chuàng)造蹂风,調(diào)用的條件是什么卢厂?
- 在 llvm 中 根據(jù)
objc_getProperty
最終找到了generateObjCGetterBody
void
CodeGenFunction::generateObjCGetterBody(const ObjCImplementationDecl *classImpl,
const ObjCPropertyImplDecl *propImpl,
const ObjCMethodDecl *GetterMethodDecl,
llvm::Constant *AtomicHelperFn) {
....
// Pick an implementation strategy.
PropertyImplStrategy strategy(CGM, propImpl);
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
.....
}
case PropertyImplStrategy::GetSetProperty: {
llvm::FunctionCallee getPropertyFn =
CGM.getObjCRuntime().GetPropertyGetFunction();
if (!getPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C getter requiring atomic copy");
return;
}
-
Obj-C getter requiring atomic copy
我看到了這句話,立馬去把 屬性變成atomic copy
// .m 代碼
@property (atomic,copy) Book *myBook;
// .cpp 代碼
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static Book * _I_Person_myBook(Person * self, SEL _cmd) { typedef Book * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct Person, _myBook), 1); }
static void _I_Person_setMyBook_(Person * self, SEL _cmd, Book *myBook) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _myBook), (id)myBook, 1, 1); }
- 疑問點(diǎn)硫眨? 為什么設(shè)置了
atomic
就是出現(xiàn)objc_getProperty
- 難道是因?yàn)?
atomic
是原子性的足淆,原子性并不能保證多線程安全,只是能保證數(shù)據(jù)的完整性, 這個(gè)完整性體現(xiàn)在, 為了能夠讓使用者總能取到完整的值 礁阁?
編碼類型
- 繼續(xù)看代碼
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[8];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
8,
{{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
}
我們發(fā)現(xiàn)
"@16@0:8"
巧号,"v24@0:8@16"
是啥意思?
-
我們可以
command + shift + 0
如下圖姥闭,搜索一下ivar_getTypeEncoding
進(jìn)入 Type Encoding
找到編碼之后丹鸿,我們就能知道
"@16@0:8"
這里的@
就是An object
符號(hào) | 解釋 |
---|---|
@ | id |
16 | 占用內(nèi)存 (8 + 8 ) |
@ | id self |
0 | 從0號(hào)位置開始 |
: | A method selector (SEL) 8字節(jié) |
8 | 從8號(hào)位置開始 |
- 繼續(xù)分析
v24@0:8@16"
符號(hào) | 解釋 |
---|---|
v | A void |
24 | 占用內(nèi)存 (16 + 8 ) |
@ | id self |
0 | 從0號(hào)位置開始 |
: | A method selector (SEL) 8字節(jié) |
8 | 從8號(hào)位置開始 |
@ | setName 的參數(shù) |
16 | 從16號(hào)位置開始 |
2020 WWDC Objective-C runtime 三個(gè)變化
class data structures changes
Relative methods list
Tagged pointer farmat changes
先來分析下
class data structures changes
有哪些變化呢?
-
類運(yùn)行時(shí)的磁盤變化,在磁盤上你 app 的二進(jìn)制文件如下圖
- 它還有一個(gè)指向更多數(shù)據(jù)的指針棚品,存儲(chǔ)額外信息的地方
class_ro_t
,ro
-> 只讀
- 當(dāng)類第一次從磁盤加載到內(nèi)存時(shí)靠欢,它們一開始也是這樣,但它們一經(jīng)使用铜跑,就會(huì)發(fā)生變化门怪。但在了解這些變化之前,我們必須先了解一下
clean memory
和dirty memory
的區(qū)別
-
clean memory
運(yùn)動(dòng)時(shí)內(nèi)容不會(huì)發(fā)生變化-
class_ro_t
就是clean memory
,因?yàn)樗蛔x
-
-
dirty memory
在進(jìn)程運(yùn)行時(shí)內(nèi)存會(huì)發(fā)生變化锅纺。
Xnip2021-06-20_15-46-48.jpg 類結(jié)構(gòu)一經(jīng)使用就是變成
dirty memory
掷空,因?yàn)檫\(yùn)行時(shí)會(huì)被寫入新的數(shù)據(jù)。dirty memory
要比clean memory
更加昂貴囤锉,這是為什么呢坦弟?因?yàn)橹灰M(jìn)程一運(yùn)行,它就必須存在官地,而另一方面
clean memory
可以被移除從而節(jié)省更多的內(nèi)存空間(why酿傍?)-> 因?yàn)槟阈枰?clean memory
系統(tǒng)可以從磁盤中重新加載。
那為什么系統(tǒng)不能像
clean memory
一樣驱入,需要dirty memory
時(shí)也去加載呢赤炒?
當(dāng)然系統(tǒng)也可以選擇喚出
dirty memory
氯析,但iOS
不使用 swap 這代價(jià)是非常大。因此
dirty memory
會(huì)被切分成兩部分的原因可霎。-
這個(gè)運(yùn)行時(shí)被分配內(nèi)容容量是
class_rw_t
用于讀取編寫數(shù)據(jù)
如上圖魄鸦,籃色區(qū)域里方法和屬性在
class_ro_t
中存在,為什么class_rw_t
中還要有方法和屬性呢?
- 因?yàn)榭梢栽谶\(yùn)行時(shí)被更改癣朗,當(dāng)
category
被加載時(shí)拾因,你可以在類中添加新的方法。你可以使用運(yùn)行時(shí)API
動(dòng)態(tài)添加旷余。
但這樣做會(huì)占用更多的內(nèi)存在
class_rw_t
绢记,那我們應(yīng)該如何縮小這些內(nèi)存結(jié)構(gòu)呢?
-
在進(jìn)行設(shè)備實(shí)際商用情況時(shí)正卧,我們發(fā)現(xiàn)只有
10%
的類真正更改了他們的方法蠢熄。 所以我們拆分那些平時(shí)不用的部分。
class_rw_ext_t
這將使clsss_rw_t
的大小減少了一半炉旷。-
用
heap
進(jìn)行測(cè)試
我們可以看 實(shí)際上用到
ext
只有124
,但節(jié)省了大約有1/4
的內(nèi)容签孔。
總結(jié): 只有在
category
加載的時(shí)候,才有會(huì)class_rw
和class_ro
的區(qū)別窘行。如果沒有category
只會(huì)有class_ro
的數(shù)據(jù)饥追。
思考
void lgKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
- 打印結(jié)果
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
why?
- 我的思路就是找到
isKindOfClass
,isMemberOfClass
在底層是如何實(shí)現(xiàn)的罐盔。
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
- 分析源碼發(fā)現(xiàn)
isKindOfClass
在底層會(huì)走objc_opt_isKindOfClass
, 根據(jù) 類的isa
= 元類 循環(huán)去getSuperclass()
最終找到Root class
->NSObject Class
=NSObject Class
,因此res1
的結(jié)果是true
但绕。 - 而
res3
的結(jié)果 是false
,是因?yàn)?NSObject Class
≠LGPerson Class
。
- 分析
isMemberOfClass
源碼
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
// lldb
(lldb) p/x self
(Class) $1 = 0x000000010036a140 NSObject
(lldb) x/4gx self
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x0000000101236970 0x0001801000000003
(lldb) po 0x000000010036a0f0 // 0x000000010036a140 , 地址不同
NSObject
- 分析源碼惶看,
isMemberOfClass
是當(dāng)前類NSObject Class
->isa
和NSObject Class
肯定是不一樣的捏顺,因此res2
的結(jié)果是false
。 - 而
res4
的結(jié)果是false
纬黎。也是因?yàn)?當(dāng)前類LGPerson
->isa
≠LGPerson
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
- 繼續(xù)分析
[(id)[NSObject alloc] isKindOfClass:[NSObject class]];
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
// lldb 分析
(lldb) x/4gx obj
0x1012328d0: 0x011d80010036a141 0x0000000000000000
0x1012328e0: 0x00007fffc02c7134 0x00007fffc02c9cc8 // 0x011d80010036a141 isa
(lldb) p/x tcls
(Class) $2 = 0x000000010036a140 NSObject
(lldb) p/x otherClass
(Class) $4 = 0x000000010036a140 NSObject
(lldb) p/x 0x011d80010036a141 & 0x00007ffffffffff8. // -> 元類
(long) $5 = 0x000000010036a140
-
isKindOfClass
在底層也會(huì)走objc_opt_isKindOfClass
,objc isa
->元類 = NSObject Class
幅骄,故res5
結(jié)果返回true
,res7
=true
.
- 繼續(xù)分析
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
- 發(fā)現(xiàn)
alloc
后的NSObject
在調(diào)用isMemberOfClass
走的方法和之前類的方法不是同一個(gè)。
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
-
self class
->NSObject
=cls
->NSObject
,故res6
=true
,同理res8
=true
本今。
- ps:在底層沒有所謂的類方法拆座,都是對(duì)象方法,萬物皆對(duì)象诈泼。
參考
WWDC 2020