美文网首页
iOS开发之Runtime

iOS开发之Runtime

作者: 爱看书de图图 | 来源:发表于2018-10-10 11:09 被阅读51次

  在学习Runtime之前,我们了解到,OC是一门动态性比较强的语言,这跟CC++有很大的不同。而OC动态运行时的本质就是RuntimeOC的底层实现都是基于Runtime来实现的,Runtime是开源的可以下载源码,底层由CC++以及汇编语言来实现。知道了这些,我们可以利用Runtime来做很多事情,现在让我们来详细的了解Runtime,以及如何在项目中使用Runtime
  在了解Runtime之前,首先我们要了解一个概念,什么是ISA,了解Runtime在底层常用的数据结构,我们先来看一下什么是ISA

ISA详解

在arm64架构之前,isa就是个普通指针,存储着Class,Meta-Class对象的内存地址
在arm64结构开始,对isa进行了优化,变成了一个共用体(union)结构,还是用位域来存储更多的信息

在源码中我们可以找到这个共用体:

union isa_t
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
}

这些字段代表的含义:

nonpointer
0、代表普通指针,存储着Class、Meta-Class对象的内存地址
1、代表优化过,使用位域存储更多的信息
has_assoc
是否设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx.destruct),如果没有,释放时更快
shiftcls
存储着Class、Meta-Class对象的内存地址
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
extra_rc
里面存储的值是引用计数器减1

想要对共用体以及isa位域等信息有一个详细的了解,需要大家知道位运算的基本知识,在这里不做过多的介绍。大概的思想就是通过位运算,我可以取出特定的位,这样本来需要一个字节(也就是8位)来存储一个信息,现在我用到了位运算,就可以用一位来存储某个信息,这样对计算机的内存有一个优化。想要知道Runtime,我们需要对类的结构有个了解,接下来我们了解一下Class的结构。

struct objc_class {
    Class  isa;
    Class  super_class;
    struct objc_cache * cache;//方法缓存
    struct objc_data_bits_t bits;//用于获取具体的类信息
};
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t methods;//方法列表
    property_list_t properties;//属性列表
    protocol_list_t protocols;//协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;//类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};
class_rw_t
  • class_rw_t里面的methods、properities、protocols是二维数组,包含了类的初始内容、分类的内容
class_ro_t
  • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

baseMethodList里包含的数据类型是method_t

method_t

IMP代表函数的具体实现
SEL代表方法\函数名,一般叫选择器
1、可以通过@selector()和sel_registerName()活的
2、可以通过sel_registerName()和NSStringFromSelector()转成字符串
3、不同类中相同的名字的方法,所对应的方法选择器是相同的
types包含了函数返回值、参数编码字符串

struct method_t {
    SEL name;//函数名
    const char *types;//编码(返回值类型,参数类型)
    IMP imp;//函数地址
    };
};

方法缓存

Class内部有个方法缓存Cache_t,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度

struct cache_t {
    struct bucket_t *_buckets;//散列表
    mask_t _mask;//散列表的长度
    mask_t _occupied;//已经缓存的方法数量
}
struct bucket_t {
    cache_key_t _key;//SEL作为可以
    IMP _imp;//函数的内存地址
}

  现在大家对类的底层实现,有了详细的了解,接下来,我们一起研究一下objc_msgSend方法调用的流程

objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数调用,objc_msgSend的执行流程可以分为三大阶段
1、消息发送
2、动态方法解析
3、消息转发

objc_msgSend执行流程 - 源码跟读

objc-msg-arm64.s
ENTRY_objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY _objc_msgSend_uncached
.macro MethodTableLookup
_class_lookupMethodAndLoadCache3
objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache

cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache

_class_resolveInstanceMethod
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY_objc_msgForward_impcache
ENTRY_objc_msgForward
Core Foundation

forwarding(不开源)

   2、如果消息发送不成功,就会进入动态解析方法,开发者可以实现以下方法,来动态添加方法。动态解析过后,会重新走消息发送的流程,从receiverClasscache中查找放方法这一步重新开始执行。

+(BOOL)resolveClassMethod:(SEL)sel
+(BOOL)resolveInstanceMethod:(SEL)sel

   3、如果消息发送,以及动态方法解析都不成功,那么开始走最后一步,消息转发阶段,消息转发调用Runtime的方法:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [super methodSignatureForSelector:aSelector];
}
//封装了一个方法调用,包括:方法调用者,方法名,方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//    anInvocation.target方法调用者
//    anInvocation.selector方法名
//    [anInvocation getArgument:NULL atIndex:0]
}

Runtime的使用

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
    //注册一个类(要在类注册之前添加成员变量)
    objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);
    //销毁一个类
    objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);
    //获取isa指向的class
    objc_getClass(const char * _Nonnull name);
    //设置isa指向的class
    object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);
    //判断一个OC对象是否为class
    object_isClass(id  _Nullable obj);
    //判断一个OC对象是否为meta-class
    class_isMetaClass(Class  _Nullable __unsafe_unretained cls);
    //获取父类
    class_getSuperclass(Class  _Nullable __unsafe_unretained cls);
    //获取一个实例变量
    class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    //copy实例变量列表(最后需要调用free释放)
    class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //设置和获取成员变量的值
    object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);
    //动态添加成员变量(已经注册的类是不能动态添加成员变量的)
    class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
    //获取成员变量的相关信息
    ivar_getName(Ivar  _Nonnull v);
    ivar_getTypeEncoding(Ivar  _Nonnull v);
    //获取一个属性
    class_getProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    //拷贝属性列表
    class_copyPropertyList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //动态添加属性
    class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
    //动态替换属性
    class_replaceProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
    //获取属性的一些信息
    property_getName(objc_property_t  _Nonnull property);
    property_getAttributes(objc_property_t  _Nonnull property);
    //获得一个实例方法、类方法
    class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    //方法实现相关操作
    class_getMethodImplementation(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp);
    method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2);
    //拷贝方法发列表(最后需要调用free释放)
    class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //动态添加方法
    class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    //动态替换方法
    class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    //获取方法的相关信息(带有copy的需要调用free释放)
    method_getName(Method  _Nonnull m);
    method_getImplementation(Method  _Nonnull m);
    method_getTypeEncoding(Method  _Nonnull m);
    method_copyReturnType(Method  _Nonnull m);
    method_copyArgumentType(Method  _Nonnull m, unsigned int index);

  Runtime还有许多API,在这里就不一一列举了,有兴趣的可以自己搜索,有很多相关的文章可以借鉴。利用这些API,我们可以在项目中利用关联对象,给分类添加属性、也可以遍历所有类的成员变量(修改textfield的站占位文字颜色,字典转模型,自动归结档等)、交换方法实现等一系列操作。

相关文章

网友评论

      本文标题:iOS开发之Runtime

      本文链接:https://www.haomeiwen.com/subject/gezsgftx.html