iOS 底層學(xué)習(xí)5

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_setPropertyaddress 卻沒有呢?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 memorydirty 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_rwclass_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?

  • 我的思路就是找到isKindOfClassisMemberOfClass 在底層是如何實(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 ClassLGPerson 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 -> isaNSObject Class 肯定是不一樣的捏顺,因此 res2 的結(jié)果是false
  • res4 的結(jié)果是false纬黎。也是因?yàn)?當(dāng)前類LGPerson-> isaLGPerson

  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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市煤禽,隨后出現(xiàn)的幾起案子铐达,更是在濱河造成了極大的恐慌,老刑警劉巖檬果,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓮孙,死亡現(xiàn)場(chǎng)離奇詭異唐断,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)杭抠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門脸甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偏灿,你說我怎么就攤上這事丹诀。” “怎么了翁垂?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵铆遭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我沿猜,道長(zhǎng)枚荣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任啼肩,我火速辦了婚禮橄妆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祈坠。我一直安慰自己害碾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布颁虐。 她就那樣靜靜地躺著蛮原,像睡著了一般。 火紅的嫁衣襯著肌膚如雪另绩。 梳的紋絲不亂的頭發(fā)上儒陨,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音笋籽,去河邊找鬼蹦漠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛车海,可吹牛的內(nèi)容都是我干的笛园。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侍芝,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼研铆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起州叠,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤棵红,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咧栗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆甜,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虱肄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了交煞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咏窿。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖素征,靈堂內(nèi)的尸體忽然破棺而出集嵌,到底是詐尸還是另有隱情,我是刑警寧澤稚茅,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布纸淮,位于F島的核電站,受9級(jí)特大地震影響亚享,放射性物質(zhì)發(fā)生泄漏咽块。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一欺税、第九天 我趴在偏房一處隱蔽的房頂上張望侈沪。 院中可真熱鬧,春花似錦晚凿、人聲如沸亭罪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽应役。三九已至,卻和暖如春燥筷,著一層夾襖步出監(jiān)牢的瞬間箩祥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國打工肆氓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袍祖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓谢揪,卻偏偏與公主長(zhǎng)得像蕉陋,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拨扶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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