Runtime 基础

Posted by Maqy on November 1, 2018

iOS对象创建后在内存里的结构是啥样的?图1

oc的定义结构是啥样的?

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

Example

假设我们定义一个如下的类

@interface IsaStructClass : NSObject {
    NSUInteger _index;
}
@property (nonatomic, copy) NSString *name;
@end

@implementation IsaStructClass
- (void)testPrint {}
+ (void)printClass {}
@end

@interface IsaStructClass (Cate)
@property (nonatomic, copy) NSString *age;
@end

@implementation IsaStructClass (Cate)
- (void)catePrint {}
@end

那么当我们声明一个实例的时候,实际上就是图1里的Instance of root class,我们知道每个类都有一个isa指针,实际上当我们调用一个对象的class方法的时候就是通过调用isa来获取的类名,然后class结构体实例里还有一个isa,这个isa指向他的元类(meta class)

类和元类的区别

我们创建一个 IsaStructClass 的实例,来验证下类与元类的区别:

Method *list = class_copyMethodList([IsaStructClass class], &count);
for (unsigned int i = 0; i<count; i++) {
    Method method = list[i];
    SEL mthodName = method_getName(method);
    NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName));
}
free(list);

list = class_copyMethodList(objc_getMetaClass("IsaStructClass"), &count);
for (unsigned int i = 0; i<count; i++) {
    Method method = list[i];
    SEL mthodName = method_getName(method);
    NSLog(@"MetaMethodName(%d): %@",i,NSStringFromSelector(mthodName));
}
free(list);

通过上述方法查看类内部的方法及结构,得到结果如下:

2020-07-01 22:21:00.145895+0800 test[68192:3089644] MethodName(0): testPrint
2020-07-01 22:21:00.146004+0800 test[68192:3089644] MethodName(1): catePrint
2020-07-01 22:21:00.146215+0800 test[68192:3089644] MethodName(2): .cxx_destruct
2020-07-01 22:21:00.146486+0800 test[68192:3089644] MethodName(3): name
2020-07-01 22:21:00.146840+0800 test[68192:3089644] MethodName(4): setName:

2020-07-01 22:21:00.147307+0800 test[68192:3089644] MetaMethodName(0): printClass

可以看到,类内存储的全部是非class方法,而元类存储的是类方法

内存结构

通过上边的输出,我们可以得出一个结论,子类的方法列表是不包含父类的方法列表的,因为你可以看到输出里面并没有任何他的父类的信息

由此我们可以推断,当我们调用一个对象的方法的时候,实际上会调用objc_msgSend(void /* id self, SEL op, ... */ ),可以推断方法内部的实现逻辑大概是:

获取当前类方法列表,如果响应则send,没有则找到父类看是否响应,以此类推,都不响应则调用基类的消息转发;如果是class方法则走元类的方法

属性 & 变量

unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([IsaStructClass class], &count);
for (unsigned int i = 0; i<count; i++) {
    const char *propertyName = property_getName(propertyList[i]);
    NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]);
}
free(propertyList);

Ivar *ivarList = class_copyIvarList([IsaStructClass class], &count);
for (int i= 0; i<count; i++) {
    Ivar ivar = ivarList[i];
    const char *ivarName = ivar_getName(ivar);
    NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);

查看内存结构输出如下:

2020-07-01 22:21:00.145134+0800 test[68192:3089644] PropertyName(0): age
2020-07-01 22:21:00.145253+0800 test[68192:3089644] PropertyName(1): name

2020-07-01 22:21:00.145553+0800 test[68192:3089644] Ivar(0): _index
2020-07-01 22:21:00.145669+0800 test[68192:3089644] Ivar(1): _name

得出结论如下:

1.直接声明的var不会增加property列表,ivar列表是类内所有用于存储数据的集合

2.分类内声明一个property是不会增加ivar的,因为他只是声明一个property,甚至不会新增property的方法,要实现分类内增加property,需要自己实现方法,并用关联对象来实现。

3.关联对象的存储是在字典里,并不会增加ivar

动态增加方法

Method method = class_getInstanceMethod([self class], @selector(print3));
    class_addMethod([IsaStructClass class], @selector(print3), method_getImplementation(method), method_getTypeEncoding(method));

运行时增加方法是动态修改的methodlist,所以获取的method里会有这个方法