类的原理(下)

233 阅读5分钟

引入

前一篇文章我们通过源码查看了类的结构有隐藏的isa指针,superclass cache bits 我们先来分析bits.

WWDC对于类的优化

Clean Memory 与 Dirty Memory

Clean Memory

  • 是指加载后不会发生更改的内存
  • class_ro_t 他就属于Clean Memory,因为他是只读的
  • Clean Memory 是可以移除的,节省更多的内存空间,当需要用到时候系统可以从磁盘中重新加载。

Dirty Memory

  • 是指进程运行时会发生更改的内存,类结构一经使用就会变成Dirty Memory 运行时会向他写入新的数据

  • dirty memory要比clean memory昂贵的多,只要进程运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory

class_rw_t 优化

  • 当一个类首次被使用时运行时会额外的分配存储容量,这个运行时分配的存储容量是class_rw_t 用于读取和编写数据

4.png

疑问

  • class_rw_t中有Methods,Properties,Protocols.但为什么方法属性和协议在class_ro_t中也有?

  • 在运行时中他们是可以更改的。当categray被加载时,它可以向类中添加新的方法,可以动态去添加,因为class_to_t是只读的,所有我们在calss_rw_t中去追踪它们。这样的结果会导致会占用相当多的内存。在设备中有很多类在使用,如何去缩小class_rw_t的结构大小?

  • 我们类在读取和编写只是部分有做修改,大部分类是没有动态的去改变它的方法。Demangled Name 这个字段只有Swift才会使用这个字段,并且Swift并不需要这一字段,除非有东西询问他们Objective_C名称才需要,所有我们可以拆分那些平时不用的部分。

5.png

总结

class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。

类的结构探索

Class结构如下:

typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa __attribute__ ((deprecated));
};

结构体指针大小是 8字节

分析 cache_t cache

结构体计算成员的大小 方法static修饰的变量在堆区我们只要计算如下:

2.jpg

1.jpg

结论我们只需要得到类的地址在平移8+8+16字节就能得到bits

lldb打印bits

    @autoreleasepool {
    
      LGPerson *p = [LGPerson alloc] ;
        NSLog(@"%@",p);
    }
    return 0;
}

3.jpg

class_rw_t与properties与methods

class_rw_t* data() const {

        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
const method_array_t methods() const {

        **auto** v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;

        } else {

            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};

        }

    }
    const property_array_t properties() const {

        auto v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;

        } else {

            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};

        }

    }

\


    const protocol_array_t protocols() const {

        auto v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;

        } else {

            **return** protocol_array_t{v.get<**const** class_ro_t *>(&ro_or_rw_ext)->baseProtocols};

        }

    }
/
    }

从源码中可以得出class_rw_t中存放了属性列表与及方法列表和协议列表

lldb 打印相关属性列表

2021年11月3日凌晨1:15.png

lldb 打印方法列表

2.png

lldb 打印类方法

4.png

lldb 打印成员变量

3.png

Runtime方式验证

通过类的方法列表获取方法名

- (void)sayHello;
+ (void)sayHelloword;
@end

@implementation LGPerson
- (void)sayHello { NSLog(@"sayHello");}
+ (void)sayHelloword{NSLog(@"sayHelloword");}
@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
    objc_copyMethodList(LGPerson.class);
    
    //获取元类
    Class pMetaClass = object_getClass(LGPerson.class);
    objc_copyMethodList(pMetaClass);
    
    }
    return 0;
}

void objc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method * methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString * key = NSStringFromSelector(method_getName(method));
        NSLog(@"Method----- name: %@", key);
    }
    free(methods);
}
 testClass[10554:463640] Method----- name: sayHello
 testClass[10554:463640] Method----- name: sayHelloword

方法名判断

    @autoreleasepool {
    objc_copyMethodList(LGPerson.class);
  
    objc_MethodClass(LGPerson.class);
    
    }
    return 0;
}

void objc_MethodClass(Class pClass){
    const char * className = class_getName(pClass);
    //获取元类
    Class metaClass = objc_getMetaClass(className);
    //判断类中是否有sayHello方法
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    //判断元类中是否有sayHello方法
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
    //判断类中是否有sayHelloword方法
    Method method3 = class_getInstanceMethod(pClass, @selector(sayHelloword));
    //判断元类中是否有sayHelloword方法
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHelloword));
 
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
}
testClass[11096:489432] 0x100008110-0x0-0x0-0x1000080a8

sayHello 方法是在中,sayHelloword 在元类

总结

  • 实例方法在类中,类方法元类
  • 编译器自动生成元类,目的是存放类方法

SELIMP关系

  • SEL:方法编号
  • IMP:函数指针地址
  • SEL 相当于书本的目录名称
  • IMP 相当于书的页码

编码

5.jpg

类型编码图的获取途径:打开xcode--> command+shift+0--> 搜索ivar_getTypeEncoding--> 点击Type Encodings

代码实现类型编码

我们可以通过编译器指令@encode()来获取一个给定类型的编码字符串,下表列举了各种类型的类型编码

 NSLog(@"char --> %s",@encode(char));
 NSLog(@"int --> %s",@encode(int));
 NSLog(@"short --> %s",@encode(short));
 NSLog(@"long --> %s",@encode(long));
 NSLog(@"long long --> %s",@encode(long long));
 testClass[12583:564098] char --> c
 testClass[12583:564098] int --> i
 testClass[12583:564098] short --> s
 testClass[12583:564098] long --> q
 testClass[12583:564098] long long --> q

setter方法底层探索

@interface LGPerson : NSObject
{
    NSString *hobby; // 字符串
    int a;
    NSObject *objc;  
}
@property (nonatomic, copy) NSString *nickName;

@property (atomic, copy) NSString *acnickName;

@property (nonatomic) NSString *nnickName;

@property (atomic) NSString *anickName;

@property (nonatomic, strong) NSString *name;

@property (atomic, strong) NSString *aname;

查看当前工程main的cpp文件查看源码实现

// macos命令行项目: clang -rewrite-objc main.m -o main.cpp clang -rewrite-objc // 引用UIKit库 clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot 
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.5.sdk main.m // xcrun xcode 命令: xcrun -sdk iphonesimulator clang -arch -arm64 -rewrite-objc main.m -o main.m-arm64.cpp // 模拟器 xcrun -sdk iphoneos clang -arch -arm64 -rewrite-objc main.m -o main.m-arm64.cpp // 真机

问题引入

5.png

为什么都是属性,但底层中提供的set赋值方式却不同,是什么原因决定了赋值方式的不同?

  • set的方法和get方法在编译期已经确定,我们可以通过函数表确认。
  • 项目中每个类都有很多属性,如果在运行时去处理是相当费时的。

6.jpg

LLVM定位objc_setProperty

11.png

  • 全局搜索getSetPropertyFn()

12.png

调用getSetPropertyFn()的中间层是GetPropertySetFunction(),因为需要判断是否走getSetPropertyFn(),加一个中间层过度。全局搜索GetPropertySetFunction()

  • 全局搜索GetPropertySetFunction()

15.png

根据switch条件PropertyImplStrategy类型调用GetPropertySetFunction()PropertyImplStrategy类型有两种GetSetProperty或者SetPropertyAndExpressionGet,下一步只要知道什么时候给策略赋值

16.jpg

18.jpg

  • 如果我们有一个拷贝属性,我们总是要使用setProperty
  • 如果属性是原子性的,我们需要使用getProperty,但在非原子的情况下,我们可以直接使用表达式。

getter方法底层探索

1.png

相同方式查看LLVM全局搜objc_getProperty 相同方式

2.jpg

3.jpg

copy+atomic修饰的属性使用objc_getProperty方式实现 。retainatomic修饰的属性也是使用objc_getProperty方式实现

总结

  • ARCatomiccopy修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现
  • MRCatomicretain修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现

面试题