RuntimeiOS开发知识汇总小知识点

Runtime窥探 (一)| 基本介绍

2017-09-25  本文已影响441人  Dely

前言

我无意间删除了一个微信好友,发现我的王者荣耀段位排名上升了一位,仿佛找到了新大陆,然后我就一直删阿删,终于到最后我的段位排在了第一名,心里尤为高兴啊。后来我打开微信一看,好友里就我自己了。

毛主席说过:说不好段子的程序员不是好产品。上面我是我自制段子,仅供娱乐。

好长时间不写博客了,手都生疏了,但是我玩王者荣耀的手没有生疏是咋回事啊?,我入农药坑半年多了,发现真是一个农药啊,当你星星满了将要升段的时候,是不是发现特别难,感觉对面都是代打嘛,结果肯定是输的,然后你就会连跪的节奏,然后你再打满星星,升段局又输,依次循环。。。你们有没有跟我一样的遭遇?你才会发现世界就是一个轮回,我TM不玩了。但是作为一个资深MOBA玩家的装逼犯的我怎么可能认输呢?我最后还是打上去了!果断弃坑。。还是学习最有意思啊

楼主段位镇楼

那就跟我一起来上车装逼吧。要发车了,下面代码较多,请自备晕车药。

装逼犯

正文

Runtime:Objective-C是基于C加入了面向对象特性和消息转发机制的动态语言。这意味着不仅需要编译器,还需要一个运行系统来执行编译后的代码。而这个运行系统就是runtime,与其说是运行时机制,不如称它为中间调度系统,来控制消息发送、消息转发、查看对象信息等等,更加灵活。

由于rutime知识点比较多,应用比较灵活,我会写一个系列供大家阅读。
本系列所有runtime源码均来自 objc4-709
在使用runtime的api时一定要导入头文件#import <objc/message.h>#import <objc/message.h>

本文主要是讲解runtime的基本知识点,目录如下:

1.部分常用API调用举例

这个例子不是只是介绍api该如何调用,实际应用肯定是组合使用才能出效果。

#pragma mark - -----类相关的方法-----
- (void)runtime_ClassMethod{
    
    Person *p = [[Person alloc] init];

    //获取对象的类
    Class class = object_getClass([Person class]);
    
    // 获取类的父类
    Class superClass = class_getSuperclass([NSObject class]);
    
    // 判断给定的Class是否是一个元类
    BOOL isMetaClass = class_isMetaClass(class);
    
    // 获取实例大小
    size_t size = class_getInstanceSize([p class]);
    
    //设置对象的类(将实例替换成另一个类)
    Class otherClass = object_setClass(p, [NSString class]);
    object_setClass(p, [Person class]);
    
    //获取对象的类名
    const char *c = object_getClassName(p);
    NSString *className = [[NSString alloc] initWithUTF8String:c];
    
    NSLog(@"class = %@ , className = %@",p,className);
    
    // 创建一个新类
    Class cls = objc_allocateClassPair([NSObject class], "newPerson",0);
    //动态添加属性、方法等
    //......
    //注册这个新类 给创建的新类添加属性 方法 协议 成员变量等都需要在注册之前完成
    objc_registerClassPair(cls);
}

#pragma mark - -----成员变量Ivar相关的方法-----
- (void)runtime_IvarMethod{
    
    Person *p = [Person new];
    p.name = @"Dely";
    p.age = 20;
    
    unsigned int count = 0;
    //获取成员变量列表 获取类对应的实例变量的Ivar指针以及个数
    Ivar *ivars = class_copyIvarList([p class], &count);
    
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        
        //获取成员变量名
        const void *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        
        //获取Ivar的类型编码,
        const char *type = ivar_getTypeEncoding(ivar);
        NSString *typeStr = [NSString stringWithUTF8String:type];
        
        //获取对应对象类和实例变量名的Ivar
        Ivar instanceIvar = class_getInstanceVariable([p class], name);
        
        //获取对应类和实例变量名的Ivar
        Ivar classIvar = class_getClassVariable([Person class], name);
        
        //object_getIvar这个方法中,当遇到非objective-c对象时,并直接crash
        //官方解释:The value of the instance variable specified by ivar, or nil if object is nil
        //非objective-c的时候,需要跳过执行。
        
        NSLog(@"成员变量名=%@  类型编码=%@",key,typeStr);
        
        if (![typeStr hasPrefix:@"@"]) {
            //@开头的是对象
            continue;
        }
        
        //获取实例对象中Ivar的值
        id obj = object_getIvar(p, ivar);
        
        //设置实例对象中Ivar的值
        object_setIvar(p, ivar, @"rename——Dely");
        
        // object_setInstanceVariable不能在ARC的模式下使用
        // object_getInstanceVariable不能在ARC的模式下使用
    }
    free(ivars);
    
    //添加成员变量(添加成员变量只能在运行时创建的类,且不能为元类,定义类是静态创建的类则无法添加)
    //第4个参数是对齐方式_Alignof或者log2sizeof
    //第5个参数是编码类型使用@encode()可获取,并不能完全获取有特例,下面有一个对应表
    BOOL add = class_addIvar([p class], "jiayao", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
    
    NSLog(@"p.name = %@     add = %@",p.name,add?@"成功":@"失败");
}

#pragma mark - -----属性Property相关的方法-----
- (void)runtime_PropertyMethod{
    
    Person *p = [Person new];
    //获取属性
    [self class_copyPropertyList:[p class]];
    
    //特性值(objc_property_attribute_t有value和name)
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "&", "" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "" };
    objc_property_attribute_t attrs[] = { type, ownership ,backingivar};
    

    //添加一个属性
    BOOL add = class_addProperty([p class], "newProperty", attrs, 3);
    
    //替换属性
    class_replaceProperty([p class],"tmpProperty", attrs, 3);
    
//    [self class_copyPropertyList:[p class]];
    
}

- (void)class_copyPropertyList:(Class)class {
    unsigned int count;
    //获取属性列表
    objc_property_t *propertyList = class_copyPropertyList(class,&count);
    
    for (int i = 0; i < count; i++) {
        
        objc_property_t property = propertyList[i];
        // 获取属性名
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        
        //获取属性的特性
        NSString *attributes = [NSString stringWithUTF8String:property_getAttributes(property)];
        NSLog(@"name = %@  attributes = %@",name,attributes);
        
        //获取属性特性数组
        unsigned int attCount = 0;
        objc_property_attribute_t *attList = property_copyAttributeList(property,&attCount);
        for (int j = 0; j < attCount; j++) {
            
            objc_property_attribute_t att = attList[j];
            
            NSLog(@"name = %s   value =%s", att.name, att.value);
            
            //获取属性中指定的特性value
            char *a = property_copyAttributeValue(property, att.name);
        }
        free(attList);

    }
    
    free(propertyList);
}

#pragma mark - -----选择器SEL相关的方法-----
- (void)runtime_SelMethod{
    Person *p = [Person new];
    
    //类实例能否响应这个SEL
    BOOL responds = class_respondsToSelector([p class], @selector(eat));
    
    
    SEL sel1 = @selector(eat);
    
    SEL sel2 = @selector(drink);
    
    //SEL转c字符串
    const char *cName = sel_getName(sel1);
    //根据str返回一个SEL变量
    SEL selName = sel_getUid(cName);
    //根据str注册一个SEL变量
    SEL registSel = sel_registerName(cName);
    //判断两个SEL变量是否相等
    BOOL equal = sel_isEqual(sel1, sel2);
    
    Method method = NULL;
    //Method转SEL变量
    SEL sel = method_getName(method);

    
}

#pragma mark - -----Method相关的方法-----
- (void)runtime_MethodMethod{
    
    Person *p = [Person new];
    
    //获取对象方法
    Method instanceMethod1 = class_getInstanceMethod([p class], @selector(eat));
    Method instanceMethod2 = class_getInstanceMethod([p class], @selector(drink));
    
    //获取类方法
    Method classMethod= class_getClassMethod([p class], @selector(eat));
    
    //交换方法
    method_exchangeImplementations(instanceMethod1, instanceMethod2);
    
    //其实在喝水
    [p eat];
    
    //给指定类添加新方法
    // class_addMethod(Class cls, SEL name, IMP imp,const char *types)
    [self addMethod];
    
    // 取代一个方法的实现,返回值是原方法的imp
    //IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
    [self replaceMethod];
    
    
    //获取方法
    [self getAllMethods];

}

- (void)addMethod{
    /* 必须用performSelector:调用。为甚么呢???
     因为performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用方法编译时就会报错*/
    Person *p = [Person new];
    
    class_addMethod([p class], @selector(testAddMethod), class_getMethodImplementation([self class], @selector(aMethod)), "v@:");
    
    [p performSelector:@selector(testAddMethod)];
    
}

- (void)aMethod{
    NSLog(@"给person动态添加了方法");
}



- (void)replaceMethod{
    Person *p = [Person new];
    class_replaceMethod([p class], @selector(run), class_getMethodImplementation([self class], @selector(rMethod)), "v@:");
    [p eat];
}


- (void)rMethod{
    NSLog(@"替换了person的run方法");
}

- (NSArray *)getAllMethods{
    
    unsigned int methodCount =0;
    Method* methodList = class_copyMethodList([Person class],&methodCount);
    NSMutableArray *methodsArray = [NSMutableArray arrayWithCapacity:methodCount];
    
    for(int i=0;i<methodCount;i++){
        Method temp = methodList[i];
        IMP imp = method_getImplementation(temp);
        SEL name_f = method_getName(temp);
        const char* name_s =sel_getName(method_getName(temp));
        //参数个数
        int arguments = method_getNumberOfArguments(temp);
        
        //类型编码
        const char* encoding =method_getTypeEncoding(temp);
        NSLog(@"方法名:%@,参数个数:%d,编码方式:%@",[NSString stringWithUTF8String:name_s],
              arguments,
              [NSString stringWithUTF8String:encoding]);
        [methodsArray addObject:[NSString stringWithUTF8String:name_s]];
    }
    free(methodList);
    return methodsArray;
}


#pragma mark - -----IMP相关的方法-----
- (void)runtime_IMPMethod{
    Person *p = [Person new];
    
    Method instanceMethod1 = class_getInstanceMethod([p class], @selector(eat));
    Method instanceMethod2 = class_getInstanceMethod([p class], @selector(drink));
    
    SEL sel = method_getName(instanceMethod1);
    
    //Method转IMP指针
    IMP imp = method_getImplementation(instanceMethod1);
    
    
    //设置一个Method的IMP指针 返回之前的IMP指针
    IMP imp1 = method_setImplementation(instanceMethod2,imp);
    imp1(p,sel);

}

#pragma mark - -----关联值相关的方法-----
- (void)runtime_AssociatedMethod{
    
    Person *p = [Person new];
    //给一个对象关联一个指定的key和关联方式
    static NSString *key = @"associatekey";
    objc_setAssociatedObject(p, &key, @"name", OBJC_ASSOCIATION_COPY_NONATOMIC);
    //获取关联值
    id obj = objc_getAssociatedObject(p, &key);
    //移除关联值
    objc_removeAssociatedObjects(p);
}
#pragma mark - -----block相关的方法-----
- (void)runtime_BlockMethod{
    
    //block转IMP
    IMP blockImp = imp_implementationWithBlock( ^(id instance, id param) {
        NSLog(@"instance = %@",instance);
    });
    //IMP转block
    id block = imp_getBlock(blockImp);
    //移出IMP中的block
    BOOL remove = imp_removeBlock(blockImp);
}

#pragma mark - -----Protocol相关的方法-----
- (void)runtime_ProtocolMethod{
    
    unsigned int count = 0;
    //获取协议列表
    Protocol * __unsafe_unretained* protocolList =  objc_copyProtocolList(&count);
    
    
    for (int i = 0; i < count; i++) {
        Protocol *protocol = protocolList[i];
        
        const char * protocolName = protocol_getName(protocol);
        NSLog(@"协议名=%s",protocolName);
        
        //返回指定的协议
        Protocol *tmpProtocol = objc_getProtocol(protocolName);
        
    }
    
    free(protocolList);
    
    // 创建新的协议实例
    Protocol *newProtocol = objc_allocateProtocol("Dely_newProtocol");
    
    // 在运行时中注册新创建的协议
    objc_registerProtocol(newProtocol);
    
    // 为协议添加方法
    protocol_addMethodDescription (newProtocol, @selector(newProtocolMethod), "v@:", YES, YES);
    }

- (void)newProtocolMethod{
    NSLog(@"为协议添加新方法");
}

2.部分常用API汇总

---------------------- 类Class ---------------------- 
//获取对象的类
object_getClass(id _Nullable obj) 

//获取类的父类
class_getSuperclass(Class _Nullable cls) 

//Class是否是一个元类
class_isMetaClass(Class _Nullable cls) 

// 获取实例大小(class也是对象)
class_getInstanceSize(Class _Nullable cls) 

//设置对象的类
object_setClass(id _Nullable obj, Class _Nonnull cls) 

//获取对象的类名
object_getClassName(id _Nullable obj)

//创建一个新类
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) 

//注册这个新类 给创建的新类添加属性 方法 协议 成员变量等都需要在注册之前完成
objc_registerClassPair(Class _Nonnull cls)   


---------------------- 成员变量Ivar ----------------------
//获取成员变量列表 获取类对应的实例变量的Ivar指针以及个数
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
 
//获取成员变量名
ivar_getName(Ivar _Nonnull v)

//获取Ivar的类型编码
ivar_getTypeEncoding(Ivar _Nonnull v) 

//获取对应对象类和实例变量名的Ivar
class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)

//获取对应类和实例变量名的Ivar
class_getClassVariable(Class _Nullable cls, const char * _Nonnull name)

//获取实例对象中Ivar的值
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)

//设置实例对象中Ivar的值
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)

//添加成员变量(添加成员变量只能在运行时创建的类,且不能为元类,定义类是静态创建的类则无法添加)
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size,uint8_t alignment, const char * _Nullable types)  


---------------------- 属性Property ----------------------  
//获取属性列表
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

//获取属性名
property_getName(objc_property_t _Nonnull property)

//获取属性的特性
property_getAttributes(objc_property_t _Nonnull property) 

//获取属性的特性列表
property_copyAttributeList(objc_property_t _Nonnull property,unsigned int * _Nullable outCount)

//获取属性中指定的特性value
property_copyAttributeValue(objc_property_t _Nonnull property, const char * _Nonnull attributeName)


---------------------- 选择器SEL ----------------------  
//类实例能否响应这个SEL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel)

//SEL转c字符串
sel_getName(SEL _Nonnull sel)

//根据str返回一个SEL变量
OBJC_EXPORT SEL _Nonnull sel_getUid(const char * _Nonnull str)

//根据str注册一个SEL变量
sel_registerName(const char * _Nonnull str)

//判断两个SEL变量是否相等
sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) 

//Method转SEL变量
method_getName(Method _Nonnull m) 


---------------------- Method方法 ----------------------  
//获取对象方法
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)

//获取类方法
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)

//交换方法
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

//方法列表
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 

//给指定类添加新方法
class_addMethod(Class cls, SEL name, IMP imp,const char *types)

//取代一个方法的实现
class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)


---------------------- IMP指针 ---------------------- 
//Method转IMP指针 
method_getImplementation(Method _Nonnull m)

//设置一个Method的IMP指针 返回之前的IMP指针
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 


---------------------- 关联值 ----------------------  
//给一个对象关联一个指定的key和关联方式
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)    

//获取关联值
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

//移除关联值
objc_removeAssociatedObjects(id _Nonnull object)


---------------------- block ----------------------
//block转IMP
imp_implementationWithBlock(id _Nonnull block)

//IMP转block
imp_getBlock(IMP _Nonnull anImp)

//移出IMP中的block
imp_removeBlock(IMP _Nonnull anImp)

---------------------- Protocol ---------------------- 
//获取协议列表
objc_copyProtocolList(unsigned int * _Nullable outCount)

//获取协议名
protocol_getName(Protocol * _Nonnull proto)

//返回指定的协议
objc_getProtocol(const char * _Nonnull name)

//创建新的协议实例
objc_allocateProtocol(const char * _Nonnull name) 
    
//在运行时中注册新创建的协议
objc_registerProtocol(Protocol * _Nonnull proto)
    
//为协议添加方法
protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                              const char * _Nullable types,
                              BOOL isRequiredMethod, BOOL isInstanceMethod)   
                  

注意事项:

上面一些举例只是让大家跟runtime的api混个脸熟,知道如何填入参数和简单调用,当你下次遇到了你会说:小伙又见到了啊。当然还有很多api没有列出来,你需要你自己可以去头文件中查看,在上面的了解中可能会有一些新的变量(比如:Ivar Method imp)或者参数的Type encodings不是很明白。下面会对这些新东西来讲解一下。

3.isa和class到底是什么鬼?

对象是怎么定义的?

我们oc中的基本上所有的类都是继承于NSOject,也就是说NSObject是根类,他没有父类了,他就是祖先。
我们来看下定义

//NSObject.h定义
@interface NSObject <NSObject> {
   Class isa  OBJC_ISA_AVAILABILITY;
}

//runtime.h定义
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

//runtime源码中定义
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    .......
}


由此可见:

Class是怎么定义的呢?

//runtime.h中
typedef struct objc_class *Class;

//runtime源码中
struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    .......
}

由此可见:

平时用的id类型到底是什么?

//id定义
typedef struct objc_object *id;

由此可见:

objc_object、objc_class、isa、isa_t之间的关系:

objc关系图

我们再来看下runtime.h头文件中objc_class结构体定义

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;
/* Use `Class` instead of `struct objc_class *` */

isa指针、对象、类、元类、根元类之间的关系:

类的关系图

object_getClass、class、class_getSuperclass:

//runtime源码
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

+ (id)class
{
    return self;
}

Class class_getSuperclass(Class cls)
{
    if (!cls) return nil;
    return cls->superclass;
}

由此可见:

举例说明:

  1. 新建一个类Father,继承于NSObject,对象方法-(void)objMethodF、类方法+(void)classMethodF

  2. 新建一个类Son,继承于Father,对象方法-(void)objMethodS、类方法+(void)classMethodS

  3. 实例化一个对象:Son *son = [Son new];

调用过程分析:

4.Ivar是什么?

Ivar:代表类中的成员变量

//runtime.h中
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}   

由此可见:

疑问:为什么要这样添加一个ivar_offset变量呢?为了健壮的实例变量

原因:当一个类被编译时,实例变量的内存布局就形成了,它表明访问类的实例变量的位置。实例变量依次根据自己所占空间而产生位移,那如果我们类的成员变量跟NSObject里的变量相同有冲突怎么办?如果不处理就会有问题,怎么处理呢?就是添加一个ivar_offset, runtime 系统检测到与超类有部分重叠时它会调整你新添加的实例变量的位移。

5.SEL是什么?

SEL 也就是我们平时所说的方法选择器,也称作为方法索引或者方法编号。经常通过@selector()或者NSSelectorFromString()返回

//runtime源码中
typedef struct objc_selector *SEL;

工作原理简单介绍:

说到底SEL就是帮助我们找到方法的中间产物---方法的映射

6.Method是什么?

Method表示类中的某个方法

//runtime源码
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

调用过程:
调用方法[p drink];会根据sel找到从methodlist查找到这个方法,而着这个方法就是Method类型,就会变成objc_msgSend(p,drinkMethod);因为drinkMethod包含了name/type/imp信息。进而根据这个信息继续执行。

说到底Method就是帮助我们找到方法的中间产物---方法的信息

7.IMP是什么?

IMP:方法的实现,其实本质上就是一个函数指针

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

上面说到objc_msgSend(p,drinkMethod); drinkMethod 包含了imp信息,也就是包含了函数的入口地址,所有会继续imp指向的函数

8.Cache是什么?

//runtime源码
typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

9.Type Encodings是什么?

在上面的很多Api中会有一个入参数:const char *types
比如:

//给指定类添加新方法
class_addMethod(Class cls, SEL name, IMP imp,const char *types)
   
// 取代一个方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)

Type Encodings: 为了帮助运行时系统,编译器对每个方法的返回和参数类型进行编码成一个字符串,并将字符串与方法选择器相关联的编码方案。在其他情况下也是有用的,通过@encode(type)的编译指令来获取它。
当给定一个类型时,@encode返回这个类型的字符串编码。比如int、指针、结构体等等,事实上只要能够用sizeof()操作参数的类型都可以用于@encode()来获取类型编码。

苹果开发文档中:Type Encodings专门对此作了介绍,也列出了所有的类型编码的映射表。

Objective-C type encodings

type encodings
NSLog(@"int        : %s", @encode(int));      --> i
NSLog(@"float      : %s", @encode(float));  --> f
NSLog(@"float *    : %s", @encode(float*));  --> ^f
NSLog(@"char       : %s", @encode(char));  --> c
NSLog(@"char *     : %s", @encode(char *));  -->  *
NSLog(@"BOOL       : %s", @encode(BOOL));  --> b
NSLog(@"void       : %s", @encode(void));  --> v
NSLog(@"void *     : %s", @encode(void *));  --> ^v

NSLog(@"NSObject * : %s", @encode(NSObject *));  --> @
NSLog(@"NSObject   : %s", @encode(NSObject));  --> #
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));  --> {NSObject=#}
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));  --> ^@

int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[]      : %s", @encode(typeof(intArray)));  --> [5i]

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[]    : %s", @encode(typeof(floatArray)));  --> [3f]

typedef struct _struct {
    short a;
    long long b;
    unsigned long long c;
} Struct;
NSLog(@"struct     : %s", @encode(typeof(Struct)));  --> {_struct=sqQ}

Objective-C method encodings

编译器内部有些关键字无法用 @encode() 返回,这些关键字也有自己的编码

method encoding
- (void)drink; //无参数无返回值===>"v@:"
- (NSString *)getName:(int)index table:(NSArray *):array;//有参数有返回值====>"@@:i@"

当我们使用const char * _Nullable method_getTypeEncoding(Method _Nonnull m);来获取一个方法的type,例如下面:

- (void)viewWillAppear:(BOOL)animated;  -> v20@0:8B16
- (NSArray *)friendsArray; -> "@16@0:8"

怎么理解这些:

当然我们可以从method encoding中获取每个参数的type encoding:

char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index);

了解这个Type Encodings对于runtime的很多api也会有进一步的认识,同时NSMethodSignature方法签名中也会有应用到Type Encodings

10.Declared Properties以及objc_property_attribute_t是什么?

Declared Properties

当编译器碰到property声明时,它会生成描述性的带有关于class, category或者protocol的metadata元数据。
你能通过函数来访问这这些元数据,通过class,protocol上的name来查找property。可以通过@encode来获取property的encode string。可以copy property的attributes list(C strings)

class和protocol都有一个properties list的值

Property Type and Functions

Property的定义如下:

typedef struct objc_property *Property;

可以通过class_copyPropertyListprotocol_copyPropertyList来遍历class(包括loaded categories)和protocol的property list

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

Property type string

Property type string

我们可以使用方法property_copyAttributeList来获取属性的特性列表,里面包含了很多objc_property_attribute_t,这就是属性的特性,其实是一个结构体,里面包含了一个name和value:

typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

使用property_getAttributes获得的描述是property_copyAttributeList能获取到的所有的name和value的总体描述,如 T@"NSDictionary",C,N,V_tmpDict

苹果官方文档中说了,这个是有顺序的

The string starts with a T followed by the @encode type and a comma, and finishes with a V followed by the name of the backing instance variable. Between these, the attributes are specified by the following descriptors, separated by commas:

我们可以看下苹果官网文档中Property type string中的例子对应特性列表值:

苹果官方文档Declared Properties以及atts例子介绍

结尾

看完这些是不是对开头的方法或者对象有新的认知,因为这是一个系列,所有后面还会有更精彩的内容,runtime对了解oc语言的调用机制有很大帮助、可见很巧妙的解决很多问题。很多优秀的开源库基本上都会用到runtime相关知识点。

有人会问学runtime有啥用?我的答案是:

学runtime不为了装逼,那跟咸鱼有什么区别?

喜欢的可以点个赞^_^

喜欢点个赞啊
上一篇下一篇

猜你喜欢

热点阅读