引入
前一篇文章我们通过源码查看了类的结构有隐藏的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 用于读取和编写数据
疑问
-
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名称才需要,所有我们可以拆分那些平时不用的部分。
总结
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修饰的变量在堆区我们只要计算如下:
结论:我们只需要得到类的地址在平移8+8+16字节就能得到bits
lldb打印bits
@autoreleasepool {
LGPerson *p = [LGPerson alloc] ;
NSLog(@"%@",p);
}
return 0;
}
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 打印相关属性列表
lldb 打印方法列表
lldb 打印类方法
lldb 打印成员变量
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 在元类中
总结
- 实例方法在类中,
类方法在元类中 - 编译器自动生成
元类,目的是存放类方法
SEL和IMP关系
SEL:方法编号IMP:函数指针地址SEL相当于书本的目录名称IMP相当于书的页码
编码
类型编码图的获取途径:打开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 // 真机
问题引入
为什么都是属性,但底层中提供的set赋值方式却不同,是什么原因决定了赋值方式的不同?
- set的方法和get方法在编译期已经确定,我们可以通过函数表确认。
- 项目中每个类都有很多属性,如果在运行时去处理是相当费时的。
LLVM定位objc_setProperty
-
全局搜索
objc_setProperty
- 全局搜索
getSetPropertyFn()
调用getSetPropertyFn()的中间层是GetPropertySetFunction(),因为需要判断是否走getSetPropertyFn(),加一个中间层过度。全局搜索GetPropertySetFunction()
- 全局搜索
GetPropertySetFunction()
根据switch条件PropertyImplStrategy类型调用GetPropertySetFunction(),PropertyImplStrategy类型有两种GetSetProperty或者SetPropertyAndExpressionGet,下一步只要知道什么时候给策略赋值
如果我们有一个拷贝属性,我们总是要使用setProperty。如果属性是原子性的,我们需要使用getProperty,但在非原子的情况下,我们可以直接使用表达式。
getter方法底层探索
相同方式查看LLVM全局搜objc_getProperty 相同方式
copy+atomic修饰的属性使用objc_getProperty方式实现 。retain+ atomic修饰的属性也是使用objc_getProperty方式实现
总结
ARC:atomic+copy修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现MRC:atomic+retain修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现