類方法及成員變量存儲(chǔ)

補(bǔ)充說(shuō)明結(jié)構(gòu)體嵌套

typedef struct person{
    char a;
    int b;
    short c;
}myPerson;
struct p{
    int d;
    double e;
    char f;
    myPerson  g;
    short h;
}per;

結(jié)構(gòu)體嵌套所需開(kāi)辟的內(nèi)存空間是結(jié)構(gòu)體內(nèi)最大長(zhǎng)度數(shù)據(jù)成員(非結(jié)構(gòu)體)所占大小的整數(shù)倍像啼。

截屏2020-09-15 上午10.05.42.png

如圖所示:

  • myPerson本身作為結(jié)構(gòu)體付翁,遵循內(nèi)存對(duì)齊原則蓝厌,故而所占12個(gè)字節(jié)
  • per作為嵌套結(jié)構(gòu)體秦爆,加入myPerson結(jié)構(gòu)體時(shí)應(yīng)該是myPerson內(nèi)最大成員變量的整數(shù)倍開(kāi)始撞牢,由代碼可知,myPerson中最大為int類型轻猖,占4個(gè)字節(jié)帆吻,而myPerson的起始位置為17,根據(jù)min(17咙边,4)得出起始位置應(yīng)該為20,并且其自身占12個(gè)字節(jié)故而所占位數(shù)為(20-31)
  • 根據(jù)內(nèi)存規(guī)則次员,所占內(nèi)存必須為最大成員所占的整數(shù)倍败许,故而為8的整數(shù)倍,最小即為min(33,8)= 40
  • 總結(jié):結(jié)構(gòu)體嵌套時(shí)淑蔚,本身嵌套的結(jié)構(gòu)體都要滿足內(nèi)存對(duì)齊規(guī)則市殷,不足的自動(dòng)補(bǔ)齊

類的結(jié)構(gòu)中class_rw_tclass_ro_t的區(qū)別

class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    ...
  };
  • class_ro_t存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性方法以及遵循的協(xié)議刹衫,里面是沒(méi)有分類的方法的醋寝。那些運(yùn)行時(shí)添加的方法將會(huì)存儲(chǔ)在運(yùn)行時(shí)生成的class_rw_t中。
  • ro即表示read only带迟,是無(wú)法進(jìn)行修改的音羞。

class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    ...
    }
  • ObjC 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t中:

class_rw_t生成時(shí)機(jī)

class_rw_t生成在運(yùn)行時(shí)仓犬,在編譯期間嗅绰,class_ro_t結(jié)構(gòu)體就已經(jīng)確定,objc_class中的bitsdata部分存放著該結(jié)構(gòu)體的地址搀继。在runtime運(yùn)行之后窘面,具體說(shuō)來(lái)是在運(yùn)行runtimerealizeClass 方法時(shí),會(huì)生成class_rw_t結(jié)構(gòu)體叽躯,該結(jié)構(gòu)體包含了class_ro_t财边,并且更新data部分,換成class_rw_t結(jié)構(gòu)體的地址点骑。
類的realizeClass運(yùn)行之前如下圖所示:

20200427214831995.png

類的realizeClass運(yùn)行之后如下圖所示:
20200427215000605.png

由此可見(jiàn)酣难,class_rw_tclass_ro_t中的成員變量有一些是相同的谍夭,區(qū)別在于:class_ro_t存放的是編譯期間就確定的;而class_rw_t是在runtime時(shí)才確定鲸鹦,它會(huì)先將class_ro_t的內(nèi)容拷貝過(guò)去慧库,然后再將當(dāng)前類的分類的這些屬性、方法等拷貝到其中馋嗜。所以可以說(shuō)class_rw_tclass_ro_t的超集齐板,當(dāng)然實(shí)際訪問(wèn)類的方法、屬性等也都是訪問(wèn)的class_rw_t中的內(nèi)容

引自class_rw_t與class_ro_t的區(qū)別

之前我們?cè)诖蛴☆惖慕Y(jié)構(gòu)時(shí)葛菇,只能讀取到其中的屬性以及實(shí)例方法甘磨,但是其中的成員變量類方法均沒(méi)有讀取到,接下來(lái)我們就分析一下:

@interface LGPerson : NSObject
{
    NSString *hobby;
    NSObject *objc;
    id        age;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end

成員變量眯停、實(shí)例變量济舆、屬性之間的關(guān)系

  • 如上面代碼所示,{}中的都是成員變量莺债,而其中的objc就是實(shí)例變量滋觉,(id是OC特有的類,本質(zhì)上等于(void *))齐邦,所以age也是實(shí)例變量
    • 實(shí)例變量就是Class類通過(guò)實(shí)例化出來(lái)的對(duì)象椎侠,是一種特殊的成員變量實(shí)例變量+基本數(shù)據(jù)類型變量=成員變量
    • 成員變量一般用于類內(nèi)部措拇,不會(huì)生成setter我纪、getter方法,外界無(wú)法獲取到
  • 屬性變量就是自動(dòng)生成setter丐吓、getter方法浅悉,其他對(duì)象可以進(jìn)行訪問(wèn)

copy與strong的實(shí)現(xiàn)

static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

首先通過(guò)clang生成的main.cpp文件中可以看出通過(guò)copy修飾的屬性會(huì)含有objc_setProperty,而通過(guò)strong修飾的就沒(méi)有
copy是在編譯時(shí)就已經(jīng)進(jìn)行處理調(diào)用objc_setProperty方法

//通過(guò)調(diào)用GetOptimizedPropertySetFunction()方法去判斷對(duì)應(yīng)的屬性券犁,是否為atomic及是否為copy
llvm::FunctionType *FTy =
Types.GetFunctionType( Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    const char *name;
    if (atomic && copy)
      name = "objc_setProperty_atomic_copy";
    else if (atomic && !copy)
      name = "objc_setProperty_atomic";
    else if (!atomic && copy)
      name = "objc_setProperty_nonatomic_copy";
    else
      name = "objc_setProperty_nonatomic";
//根據(jù)對(duì)應(yīng)的屬性修飾查找
//通過(guò)runtime返回對(duì)應(yīng)的sel進(jìn)行value賦值
return CGM.CreateRuntimeFunction(FTy, name);

strong方法通過(guò)引用計(jì)數(shù)retain進(jìn)行查看

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    // 新值賦值
    objc_retain(obj);
    *location = obj;
    // 舊值釋放
    objc_release(prev);
}

通過(guò)llvm中進(jìn)行查找

case BlockCaptureEntityKind::ARCStrong: {
      llvm::Value *srcValue = Builder.CreateLoad(srcField, "blockcopy.src");
      // At -O0, store null into the destination field (so that the
      // storeStrong doesn't over-release) and then call storeStrong.
      // This is a workaround to not having an initStrong call.
      if (CGM.getCodeGenOpts().OptimizationLevel == 0) {
        auto *ty = cast<llvm::PointerType>(srcValue->getType());
        llvm::Value *null = llvm::ConstantPointerNull::get(ty);
        Builder.CreateStore(null, dstField);
        EmitARCStoreStrongCall(dstField, srcValue, true);
      } else {
        EmitARCRetainNonBlock(srcValue);
        if (!needsEHCleanup(captureType.isDestructedType()))
          cast<llvm::Instruction>(dstField.getPointer())->eraseFromParent();
      }
      break;
    }
    llvm::Value *CodeGenFunction::EmitARCStoreStrongCall(Address addr,
                                                     llvm::Value *value,
                                                     bool ignored) {
  assert(addr.getElementType() == value->getType());

  llvm::Function *&fn = CGM.getObjCEntrypoints().objc_storeStrong;
  if (!fn) {
    fn = CGM.getIntrinsic(llvm::Intrinsic::objc_storeStrong);
    setARCRuntimeFunctionLinkage(CGM, fn);
  }

  llvm::Value *args[] = {
    Builder.CreateBitCast(addr.getPointer(), Int8PtrPtrTy),
    Builder.CreateBitCast(value, Int8PtrTy)
  };
  EmitNounwindRuntimeCall(fn, args);

  if (ignored) return nullptr;
  return value;
}

當(dāng)case BlockCaptureEntityKind:為ARCStrong時(shí)术健,->EmitARCStoreStrongCall()->objc_stroeStrong方法,進(jìn)而可以找到上述底層編碼

獲取成員變量及類方法所在位置

截屏2020-09-15 下午3.26.18.png

首先讀取讀取LGperson類去獲取對(duì)應(yīng)的bits
截屏2020-09-15 下午3.27.43.png

根據(jù)上面class_rw_tclass_ro_t的區(qū)別得出族操,class_rw_t本身是包含ro的苛坚,所以我們通過(guò)上圖去讀取對(duì)應(yīng)的ro,得出其中除了基礎(chǔ)類型baseProperties外還有ivars色难,通過(guò)讀取ivars可以看出成員變量是存在中的泼舱,因此得出成員變量本身是不能被外界所讀取
截屏2020-09-15 下午3.33.52.png

我們通過(guò)查看LGPersonisa去查看對(duì)應(yīng)的元類信息枷莉,看出其中是包含LGPerson中的類方法 :say666娇昙,故而我們得出類方法是存在于其對(duì)應(yīng)的元類中。

方法簽名

截屏2020-09-15 上午11.56.33.png
  • 每一個(gè)方法都會(huì)包含sel(方法編號(hào))imp(函數(shù)指針笤妙,指向函數(shù)方法的實(shí)現(xiàn))
  • 第一個(gè)參數(shù):返回值 v(void) @(id)
  • 第二個(gè)參數(shù):開(kāi)辟的內(nèi)存總字節(jié)數(shù)
  • 第三個(gè)參數(shù):傳入的參數(shù)冒掌,位置從0開(kāi)始噪裕,占用(0-7)
  • 第四個(gè)參數(shù)::代表sel(方法編號(hào)),位置從8開(kāi)始占用(8-15)
    下面我們打印了一部分股毫,具體的對(duì)應(yīng)關(guān)系可以去蘋(píng)果官網(wǎng)Type Encoding 查看
    截屏2020-09-15 下午1.15.08.png

面試題解析

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);

首先我們通過(guò)方法查找到對(duì)應(yīng)的類方法實(shí)例方法

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
+ (Class)class {
    return self;
}

解析

  • 首先[NSObject class]為類膳音,需要調(diào)用對(duì)應(yīng)的類方法,根元類的superclass是指向NSObject铃诬,故而兩者相等
    • [LGPerson class]LGPerson祭陷,里面循環(huán)查找superclass,沒(méi)有與LGPerson相等的類趣席,故而返回NO
  • 根據(jù)isMemberOfClass方法源碼可以得出兵志,self->ISA()指向的是根元類,而NSObject并不等于根元類宣肚,所以返回NO
  • 根據(jù)isKindOfClass實(shí)例方法可以得出想罕,tcls == [self class]第一次循環(huán)時(shí)直接相等霉涨,返回YES
  • 根據(jù)isMemberOfClass實(shí)例方法得出按价,根據(jù)[self class]返回的是self,故而傳入的與返回的相等
  • 注:(如果元類笙瑟、父類俘枫、isa之間的走位不懂可以參考下面月月大神的流程圖)
    2251862-9c4ff5faa937e5ef.png

面試題類方法與實(shí)例方法存在位置

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    // - (void)sayHello;
    // + (void)sayHappy;
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    <!--lgInstanceMethod_classToMetaclass - 0x10000-->31b0-0x0-0x0-0x100003148
}

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
}

解析
* 1.class_getInstanceMethod是否存在對(duì)應(yīng)的實(shí)例方法,根據(jù)打印結(jié)果可以看出逮走,method1method4有值,而method2今阳、method3沒(méi)有值师溅,故而得出,實(shí)例方法存在對(duì)應(yīng)的類中盾舌,而類方法是存在對(duì)應(yīng)的元類中墓臭。
* 2.class_getClassMethod是否存在對(duì)應(yīng)的類方法,根據(jù)打印結(jié)果看出妖谴,method1窿锉、method2沒(méi)有值,method3膝舅、method4有值嗡载,故而得出類方法可以在自身類與對(duì)應(yīng)的元類中找到
* 3.class_getMethodImplementation查看實(shí)例方法與類方法對(duì)應(yīng)的imp,根據(jù)結(jié)果可以看出imp2,imp3是一致的仍稀,根據(jù)下面源碼得出洼滚,如果沒(méi)有對(duì)應(yīng)的imp即sel方法則會(huì)固定返回_objc_msgForward,否則返回imp技潘。

IMP class_getMethodImplementation(Class cls, SEL sel)
{
   IMP imp;

   if (!cls  ||  !sel) return nil;

   imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

   // Translate forwarding function to C-callable external version
   if (!imp) {
       return _objc_msgForward;
   }

   return imp;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遥巴,一起剝皮案震驚了整個(gè)濱河市千康,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铲掐,老刑警劉巖拾弃,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異摆霉,居然都是意外死亡豪椿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)斯入,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)砂碉,“玉大人,你說(shuō)我怎么就攤上這事刻两≡霾洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵磅摹,是天一觀的道長(zhǎng)滋迈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)户誓,這世上最難降的妖魔是什么饼灿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮帝美,結(jié)果婚禮上碍彭,老公的妹妹穿的比我還像新娘。我一直安慰自己悼潭,他們只是感情好庇忌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著舰褪,像睡著了一般皆疹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上占拍,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天略就,我揣著相機(jī)與錄音,去河邊找鬼晃酒。 笑死表牢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掖疮。 我是一名探鬼主播初茶,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恼布?” 一聲冷哼從身側(cè)響起螺戳,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎折汞,沒(méi)想到半個(gè)月后倔幼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爽待,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年损同,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟款。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膏燃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出何什,到底是詐尸還是另有隱情组哩,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布处渣,位于F島的核電站伶贰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏罐栈。R本人自食惡果不足惜黍衙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荠诬。 院中可真熱鬧琅翻,春花似錦、人聲如沸柑贞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凌外。三九已至,卻和暖如春涛浙,著一層夾襖步出監(jiān)牢的瞬間康辑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工轿亮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疮薇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓我注,卻偏偏與公主長(zhǎng)得像按咒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子但骨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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