runtime 详解

rumtime基础知识

Objective-C提供了编译运行时,只要有可能,它都可以动态地运行,这意味着不仅需要编译器,还需要运行时系统执行编译的代码,运行时系统充当Objective-C语言的操作系统,有了它才能运行。

运行时系统所提供功能是非常强大的,在实际开发中是经常使用到的。比如,苹果不允许我们给Category追加扩展属性,是因为它不会自动生成成员变量,那么我们通过运行时就可以很好的解决这个问题。另外,常见的模型转字典或者字典转模型,对象归档等。

于runtime交互

Objective-C程序有三种方式与runtime系统交互:

1.通过Objective-C原代码,编译器党编译包含Objective-C类和方法的代码时,编译器会创建实现语言动态特性的数据结构和函数调用。

2.通过foundation库中定义的NSObject提供的方法,NSObject提供了一些动态监测方法

3.直接通过调用runtime方法,直接调用#import <objc/runtime.h> 和#import <objc/objc.h>头文件里的函数

消息(Message)

typedef struct objc_class *Class;

struct objc_class {

  Class isa; // 指向metaclass

   

  Class super_class ; // 指向其父类

  const char *name ; // 类名

  long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取

  long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;

  long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);

  struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址

  struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;

  struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;

  struct objc_protocol_list *protocols; // 存储该类遵守的协议

    }

我们可以看到,对于一个Class类中,存在很多东西,下面我来一一解释下:

Class isa:指向metaclass,也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通的成员变量和对象方法(-开头的方法),普通的Class中的isa指针指向静态的Class,静态的Class中存储static类型的成员变量和类方法(+开头的方法)

Class super_class指向父类,如果这个类是根类,则为NULL。

下面一张图片很好的描述了类和对象的继承关系:



注意:所有metaclass中isa指针都指向跟metaclass。而跟metaclass则指向自身。Root metaclass是通过继承Root class产生的。与root class结构体成员一致,也就是前面提到的结构。不同的是Root metaclass的isa指针指向自身。

runtime关联属性

在开发中经常需要给已有的类扩展添加方法和属性,但是Objective-C是不允许给已有类扩展属性的,因为类扩展是不会自动生成成员变量的,但是,苹果提供了runtime,我们可以通过runtime使用。

关联API介绍
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

关联策略

/* Associative References */

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

我们说明一下各个值的作用:

OBJC_ASSOCIATION_ASSIGN:表示弱引用关联,通常是基本数据类型,如int、float
OBJC_ASSOCIATION_RETAIN_NONATOMIC:表示强(strong)引用关联对象
OBJC_ASSOCIATION_COPY_NONATOMIC:表示关联对象copy
OBJC_ASSOCIATION_RETAIN:表示强(strong)引用关联对象,但不是线程安全的
OBJC_ASSOCIATION_COPY:表示关联对象copy,但不是线程安全的

runtime模型与字典互转

objc_property_t *properties = class_copyPropertyList

objc_property_t property = properties[i];
const void *propertyName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propertyName];

runtime自动归档/解档

unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);

// 获取成员变量名
const void *name = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:name];
// 去掉成员变量的下划线
ivarName = [ivarName substringFromIndex:1];

Runtime Message Forwarding

其流程是这样的:

第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel实现方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过class_addMethod函数动态地添加方法,消息得到处理,此流程完毕。
第二步:在第一步返回的是NO时,就会进入- (id)forwardingTargetForSelector:(SEL)aSelector方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三步。若返回某个对象,则会调用该对象的方法。
第三步:若第二步返回的是nil,则我们首先要通过- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法签名,若返回nil,则表示不处理。若返回方法签名,则会进入下一步。
第四步:当第三步返回方法方法签名后,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等
第五步:若没有实现- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么会进入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示打不到响应的方法。到此,动态解析的流程就结束了。

runtime objc_msgSend

objc_msgSend:其它普通的消息都会通过该函数来发送
objc_msgSend_stret:消息中需要有数据结构作为返回值时,会通过该函数来发送消息并接收返回值
objc_msgSendSuper:与objc_msgSend函数类似,只是它把消息发送给父类实例
objc_msgSendSuper_stret:与objc_msgSend_stret函数类似,只是它把消息发送给父类实例并接收数组结构作为返回值

创建并初始化NSObject

创建对象

NSObject *obj = (NSObject *(*)(id,SEL)objc_msgSend)((id)[NSObject class],@selector(alloc));

初始化对象

obj = (NSObject *(*)(id,SEL)objc_msgSend)((id)msg,@selector(init));

runtime Method

Method类型

Method类型是一个objc_method结构体指针,而结构体objc_method有三个成员:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name;        // 方法名称
    char *method_typesE;    // 参数和返回类型的描述字串
    IMP method_imp;         // 方法的具体的实现的指针
} 

Method所有方法

// 函数调用,但是不接收返回值类型为结构体
method_invoke
// 函数调用,但是接收返回值类型为结构体
method_invoke_stret
// 获取函数名
method_getName
// 获取函数实现IMP
method_getImplementation
// 获取函数type encoding
method_getTypeEncoding
// 复制返回值类型
method_copyReturnType
// 复制参数类型
method_copyArgumentType
// 获取返回值类型
method_getReturnType
// 获取参数个数
method_getNumberOfArguments
// 获取函数参数类型
method_getArgumentType
// 获取函数描述
method_getDescription
// 设置函数实现IMP
method_setImplementation
// 交换函数的实现IMP
method_exchangeImplementations

class_copyMethodList 获取方法列表

Type Encoding

编码值	含意
c	代表char类型
i	代表int类型
s	代表short类型
l	代表long类型,在64位处理器上也是按照32位处理
q	代表long long类型
C	代表unsigned char类型
I	代表unsigned int类型
S	代表unsigned short类型
L	代表unsigned long类型
Q	代表unsigned long long类型
f	代表float类型
d	代表double类型
B	代表C++中的bool或者C99中的_Bool
v	代表void类型
*	代表char *类型
@	代表对象类型
#	代表类对象 (Class)
:	代表方法selector (SEL)
[array type]	代表array
{name=type…}	代表结构体
(name=type…)	代表union
bnum	A bit field of num bits
^type	A pointer to type
?	An unknown type (among other things, this code is used for function pointers)

Runtime Method Swizzling

苹果的源码是闭源的,我们只有类名和类属性,方法等声明,却看不到实现,这时候我们若想改变其中一个方法的实现,就有三种方案:

1.通过继承。

2.通过类别重写。

3.swizzling。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值