IOS底層探索運行時類的變化

類的數據結構在運行時中的變化

WWDC 2020視頻中有詳細介紹html

Class on disk

圖片.png 在磁盤上,App二進制文件中類如圖中MYClass同樣,包含指向元類、超類、方法緩存的指針以及指向存儲額外信息的指針class_ro_tro表明只讀,它包括像類的名稱、方法、協議、實例變量的信息,SwiftObjective-C共享這一數據結構。c++

Class in memory (Clean Memory & Dirty Memory)

圖片.png

  • Clean Memory是指加載後不會發生更改的內存,class_ro_t就是Clean Memory,它是隻讀的。
  • Dirty Memory是指在進程運行時發生更改的內存,類結構一經使用就會變成Dirty Memory,由於運行時會向它寫入新的數據,例如建立一個新的方法緩存並從類中指向它。
  • Dirty MemoryClean Memory要昂貴得多,只要進程在運行,它就必須一直存在。
  • Clean Memory能夠進行移除從而節省更多的內存空間,由於若是你須要Clean Memory系統能夠從磁盤中從新加載。
  • MacOS能夠選擇換出Dirty Memory 但由於iOS不使用swap因此Dirty MemoryiOS中的代價很大。

當一個類首次被使用,運行時會爲他分配額外的存儲容量,這個運行時分配的存儲容量是class_rw_t用於讀取-編寫數據。 圖片.png 例如全部的類都會連接生成一個樹狀結構,這是經過使用First SubclassNext Sibling Class 指針實現的,這容許運行時遍歷當前使用的全部類,這對於使方法緩存無效很是有用。面試

爲何方法和屬性在只讀數據class_ro_t中,class_rw_t還要方法和屬性呢?express

由於它們能夠在運行時更改。當Category被加載時 他能夠向類中添加新的方法,由於class_ro_t是隻讀的,咱們須要在class_rw_t中追蹤這些東西。緩存

可是class_rw_t佔用的是Dirty Memory,這樣作的話會佔用至關多的內存。如何縮小這些結構呢?markdown

能夠拆掉那些平時不用的部分,這樣class_rw_t就減小了一半。 圖片.png 對於那些確實須要額外信息的類,咱們能夠分配這些擴展記錄中的一個,並把它劃到類中供其使用。大約90%的類歷來不須要這些擴展數據。數據結構

圖片.png 注:只有Swift類會使用這個Demangled Name字段,而且Swift類並不須要這一字段,除非有東西詢問他們的Objective-C名稱時才須要。app

setter方法的兩種方式

@interface ABPerson : NSObject
{
    //成員變量
    NSString *hobby;
    //實例變量(特殊的成員變量)
    NSObject *objc;
}
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,strong) NSString *name;
@property (atomic,strong) NSString *aname;
@end

@implementation ABPerson

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        ABPerson *p = [ABPerson alloc];
        NSLog(@"Hello, World!");
    }
    return 0;
}
複製代碼

編譯成.cpp文件命令:ide

clang -rewrite-objc main.m -o main.cpp
複製代碼

ABPerson結構體oop

struct ABPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS; //ISA
	NSString *hobby;
	NSObject *objc;
	NSString *_nickName;
	NSString *_name;
	NSString *_aname;
};
複製代碼

在結構體中,屬性前面被加上了下劃線,成員變量沒有變。

nickNamesetter方法:

static void _I_ABPerson_setNickName_(ABPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ABPerson, _nickName), (id)nickName, 0, 1); }
複製代碼

namesetter方法:

static void _I_ABPerson_setName_(ABPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)) = name; }
複製代碼

對比兩個屬性的setter方法,nickNamesetter方法會調用objc_setProperty,而namesetter是直接經過內存平移賦值。

objc_setProperty

objc_setProperty至關於一個封裝,將尋找屬性的代碼封裝起來,再調用具體的底層實現。

那麼當調用setter方法爲何會定位到objc_setProperty呢?這多是在編譯階段就完成的工做。

打開LLVM工程搜索objc_setProperty

圖片.png 往上依次找方法的調用 圖片.png 查看GetSetPropertyFn的實現 圖片.png 返回了setPropertyFn

圖片.png 在這裏被調用,那麼調用的條件是什麼呢?

void CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl, const ObjCPropertyImplDecl *propImpl, llvm::Constant *AtomicHelperFn) {
  省略部分代碼
  PropertyImplStrategy strategy(CGM, propImpl);//獲取策略
  switch (strategy.getKind()) {
  case PropertyImplStrategy::Native: {
    // We don't need to do anything for a zero-size struct.
    if (strategy.getIvarSize().isZero())
      return;

    //下面這一段是在作內存平移
    Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());

    LValue ivarLValue =
      EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
    Address ivarAddr = ivarLValue.getAddress(*this);

    // Currently, all atomic accesses have to be through integer
    // types, so there's no point in trying to pick a prettier type.
    llvm::Type *bitcastType =
      llvm::Type::getIntNTy(getLLVMContext(),
                            getContext().toBits(strategy.getIvarSize()));

    // Cast both arguments to the chosen operation type.
    argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
    ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);

    // This bitcast load is likely to cause some nasty IR.
    llvm::Value *load = Builder.CreateLoad(argAddr);

    // Perform an atomic store. There are no memory ordering requirements.
    llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
    store->setAtomic(llvm::AtomicOrdering::Unordered);
  
    return;
  }

  case PropertyImplStrategy::GetSetProperty:
  //省略不少個case
 }
複製代碼

條件是找到匹配的策略調用相應的case

enum StrategyKind {
      /// The 'native' strategy is to use the architecture's provided
      /// reads and writes.
      Native,

      /// Use objc_setProperty and objc_getProperty.
      GetSetProperty,

      /// Use objc_setProperty for the setter, but use expression
      /// evaluation for the getter.
      SetPropertyAndExpressionGet,

      /// Use objc_copyStruct.
      CopyStruct,

      /// The 'expression' strategy is to emit normal assignment or
      /// lvalue-to-rvalue expressions.
      Expression
    };
複製代碼
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
 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;
 //當是copy是使用GetSetProperty策略
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }
 省略部分代碼
複製代碼

當修飾屬性爲copy時會調用objc_setProperty

方法編碼

main.cpp文件中,咱們看到了:

圖片.png@16@0:8爲例,符號依次介紹: @:是id類型,是返回值類型 16:這個編碼所佔用內存數 @:方法參數,指方法的默認參數是id self 0:表明self0號位置開始 ::表明SEL 8:表明SEL8號位置開始

不清楚對應符號什麼意思能夠查詢Objective-C type encodings

圖片.png

面試題isKindOfClass & isMemberOfClass

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(@"\n 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(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
複製代碼

isKindOfClass會調用objc_opt_isKindOfClass 圖片.png

//x爲真得可能性比較大
#define fastpath(x) (__builtin_expect(bool(x), 1)) 
//x爲假的可能性比較大
#define slowpath(x) (__builtin_expect(bool(x), 0))

BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
//objc2.0使用下面實現
#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
//不知足調整,調用isKindOfClass
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
複製代碼

分析:re1

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
 obj是NSObject的類對象
 cls是NSObject的元類,即根元類
 tcls根元類的父類就是NSObject
 otherClass是NSObject
 因此 tcls == otherClass 爲YES
  
複製代碼

分析:re3

BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 
 obj是LGPerson的類對象
 cls是LGPerson的元類
 tcls是元類的父類就是NSObject的元類,即根元類
 otherClass是LGPerson
 因此 tcls == otherClass 爲NO
  
複製代碼

分析:re5

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
 obj是NSObject的實例對象
 cls是NSObject的類對象
 tcls=cls即NSObject的類對象
 otherClass是NSObject
 因此 tcls == otherClass 爲YES
  
複製代碼

分析:re7

BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
 obj是LGPerson的實例對象
 cls是LGPerson的類對象
 tcls=cls即LGPerson的類對象
 otherClass是LGPerson
 因此 tcls == otherClass 爲YES
  
複製代碼

分析:re二、 re4

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
複製代碼
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 
 NSObject的元類是根元類和NSObject不是一個東西,返回NO
 同理:
 re4也是NO
複製代碼

分析:re6

BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; 
 NSObject實例對象的類對象就是NSObject
 因此re6爲YES
複製代碼

分析:re8

re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
 LGPerson實例對象的類對象就是LGPerson
 因此re8爲YES
複製代碼

打印結果:

圖片.png

總結 BOOL

  • 類在被首次使用時,運行時會給其分配存儲容量class_rw_t,用於讀取編寫數據,經過拆分class_rw_t,將不經常使用的放到class_rw_ext_t,當須要的時候加進去,從而達到減小內存的做用
  • setter方法有兩種方式,一種是經過內存平移賦值,一種是經過調用objc_setProperty
  • 當修飾屬性爲copy時會調用objc_setProperty
相關文章
相關標籤/搜索