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
相关文章
相关标签/搜索