例子Demojavascript
Objc Runtime使得C具备了面向对象能力,在程序运行时建立,检查,修改类、对象和它们的方法。Runtime是C和汇编编写的,这里www.opensource.apple.com/source/objc…html
Method
An opaque type that represents a method in a class definition.
Declaration
typedef struct objc_method *Method;java
表明类定义中的方法的不透明类型。git
Class
An opaque type that represents an Objective-C class.
Declaration
typedef struct objc_class *Class;github
表明Objective-C中的类编程
An opaque type that represents an instance variable.
Declaration
typedef struct objc_ivar *Ivar;缓存
表明实例变量网络
IMP
A pointer to the start of a method implementation.架构
指向方法实现的开始的内存地址的指针。app
SEL
Defines an opaque type that represents a method selector.
Declaration
typedef struct objc_selector *SEL;
表明方法的选择器
Example : 在category 中添加对象
//.h
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface UIView (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
//.m
#import "UIView+AssociatedObject.h"
@implementation UIView (AssociatedObject)
static char kAssociatedObjectKey;
- (void)setAssociatedObject:(id)associatedObject {
objc_setAssociatedObject(self, &kAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, &kAssociatedObjectKey);
}复制代码
objc_setAssociatedObject
Sets an associated value for a given object using a given key and association policy.
Declaration
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
Parameters
object
The source object for the association.
key
The key for the association.
value
The value to associate with the key key for object. Pass nil to clear an existing association.
policy
The policy for the association. For possible values, see Associative Object Behaviors.
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) 或 @property (unsafe_unretained) | 指定一个关联对象的弱引用。 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 指定一个关联对象的强引用,不能被原子化使用。 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 指定一个关联对象的copy引用,不能被原子化使用。 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 指定一个关联对象的强引用,能被原子化使用。 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 指定一个关联对象的copy引用,能被原子化使用。 |
objc_getAssociatedObject
Returns the value associated with a given object for a given key.
Declaration
id objc_getAssociatedObject(id object, const void *key);
Parameters
object
The source object for the association.
key
The key for the association.
Return Value
The value associated with the key key for object.
返回给定对象的key的关联值
objc_removeAssociatedObjects
Removes all associations for a given object.
Declaration
void objc_removeAssociatedObjects(id object);
Parameters
object
An object that maintains associated objects.
Discussion
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 objc_setAssociatedObject with a nil value to clear an association.
删除给定对象的全部关联。
添加私有属性用于更好地去实现细节。当扩展一个内建类的行为时,保持附加属性的状态可能很是必要。注意如下说的是一种很是教科书式的关联对象的用例:AFNetworking在 UIImageView 的category上用了关联对象来保持一个operation对象,用于从网络上某URL异步地获取一张图片。
添加public属性来加强category的功能。有些状况下这种(经过关联对象)让category行为更灵活的作法比在用一个带变量的方法来实现更有意义。在这些状况下,能够用关联对象实现一个一个对外开放的属性。回到上个AFNetworking的例子中的 UIImageView category,它的 imageResponseSerializer方法容许图片经过一个滤镜来显示、或在缓存到硬盘以前改变图片的内容。
建立一个用于KVO的关联观察者。当在一个category的实现中使用KVO时,建议用一个自定义的关联对象而不是该对象自己做观察者。ng an associated observer for KVO**. When using KVO in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.
当值不须要的时候创建一个关联对象。一个常见的例子就是在view上建立一个方便的方法去保存来自model的属性、值或者其余混合的数据。若是那个数据在以后根本用不到,那么这种方法虽然是没什么问题的,但用关联到对象的作法并不可取。
当一个值能够被其余值推算出时创建一个关联对象。例如:在调用 cellForRowAtIndexPath: 时存储一个指向view的 UITableViewCell 中accessory view的引用,用于在 tableView:accessoryButtonTappedForRowWithIndexPath: 中使用。
用关联对象替代X,这里的X能够表明下列含义:
Example:
- (IBAction)addMethod:(id)sender {
[self addMethodForPerson];
if ([self.xjy respondsToSelector:@selector(speakMyName)]) {
[self.xjy performSelector:@selector(speakMyName)];
} else {
NSLog(@"未添加成功");
}
}
- (void)addMethodForPerson {
class_addMethod([self.xjy class], @selector(speakMyName), (IMP)speakMyName, "v@:*");
}
void speakMyName(id self,SEL _cmd) {
NSLog(@"添加成功啊QAQ");
}复制代码
class_addMethod
Adds a new method to a class with a given name and implementation.
Declaration
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
Parameters
cls
The class to which to add a method.
name
A selector that specifies the name of the method being added.
imp
A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
types
An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
Return Value
YES if the method was added successfully, otherwise NO (for example, the class already contains a method implementation with that name).
给一个类添加方法
class_addMethod添加实现将覆盖父类的实现,但不会替换此类中的现有实现。 要更改现有实现,请使用method_setImplementation。
Objective-C方法只是一个C函数,至少须要两个参数 - self和_cmd。 例如,给定如下函数:
void myMethodIMP(id self,SEL _cmd)
{
// implementation ....
}}复制代码
你能够动态地将它添加到类做为一个方法(称为resolveThisMethodDynamically)像这样:
class_addMethod([self class],@selector(resolveThisMethodDynamically),(IMP)myMethodIMP,“v @:”);复制代码
Type Encodings
To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector.
为了辅助运行时系统,编译器对字符串中每一个方法的返回和参数类型进行编码,并将字符串与方法选择器相关联。 它使用的编码方案在其余上下文中也颇有用,所以能够经过@encode()编译器指令公开得到。 当给定类型规范时,@encode()返回该类型的字符串编码。 类型能够是基本类型,例如int,指针,标记结构或联合,或类名 - 实际上能够用做C sizeof()运算符的参数的任何类型。
具体内容参见 Objective-C Runtime Programming Guide
Example:
#import "UIViewController+LogTracking.h"
#import <objc/runtime.h>
@implementation UIViewController (LogTracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xjy_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xjy_viewWillAppear:(BOOL)animated {
[self xjy_viewWillAppear:animated];
NSLog(@"viewWillAppear : %@",self);
}
@end复制代码
swizzling应该只在+load中完成。 在 Objective-C 的运行时中,每一个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,而且只有在方法被实现的状况下才会被调用。
swizzling 应该只在 dispatch_once 中完成
因为 swizzling 改变了全局的状态,因此咱们须要确保每一个预防措施在运行时都是可用的。原子操做就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不一样的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 知足了所须要的需求,而且应该被当作使用 swizzling 的初始化单例方法的标准。
method_getImplementation
Returns the implementation of a method.
Declaration
IMP method_getImplementation(Method m);
Parameters
method
The method to inspect.
Return Value
A function pointer of type IMP.
返回方法的实现
method_getTypeEncoding
Returns a string describing a method's parameter and return types.
Declaration
const char * method_getTypeEncoding(Method m);
Parameters
method
The method to inspect.
Return Value
A C string. The string may be NULL.
返回一个C 字符串,描述方法的参数和返回类型.
class_replaceMethod
Replaces the implementation of a method for a given class.
Declaration
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
Parameters
cls
The class you want to modify.
name
A selector that identifies the method whose implementation you want to replace.
imp
The new implementation for the method identified by name for the class identified by cls.
types
An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
Return Value
The previous implementation of the method identified by name for the class identified by cls.
替换指定方法的实现
此函数以两种不一样的方式运行:
method_exchangeImplementations
Exchanges the implementations of two methods.
Declaration
void method_exchangeImplementations(Method m1, Method m2);
交换两个方法的实现.
原子版本的实现:
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);复制代码
在 Objective-C 的运行时中,selectors, methods, implementations 指代了不一样概念,然而咱们一般会说在消息发送过程当中,这三个概念是能够相互转换的。 下面是苹果 Objective-C Runtime Reference中的描述:
理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另外一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。
不少人认为交换方法实现会带来没法预料的结果。然而采起了如下预防措施后, method swizzling 会变得很可靠:
经过Method Swizzling能够把事件代码或Logging,Authentication,Caching等跟主要业务逻辑代码解耦。这种处理方式叫作Cross Cutting Concernsen.wikipedia.org/wiki/Cross-… 用Method Swizzling动态给指定的方法添加代码解决Cross Cutting Concerns的编程方式叫Aspect Oriented Programming en.wikipedia.org/wiki/Aspect… 目前有些第三方库能够很方便的使用AOP,好比Aspects github.com/steipete/As… 这里是使用Aspects的范例github.com/okcomp/Aspe…
// 第一步
// 在没有找到方法时,会先调用此方法,可用于动态添加方法
// 返回 YES 表示相应 selector 的实现已经被找到并添加到了类中,不然返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;
}
// 第二步
// 若是第一步的返回 NO 或者直接返回了 YES 而没有添加方法,该方法被调用
// 在这个方法中,咱们能够指定一个能够返回一个能够响应该方法的对象
// 若是返回 self 就会死循环
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(xxx:)){
return self.alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
// 第三步
// 若是 `forwardingTargetForSelector:` 返回了 nil,则该方法会被调用,系统会询问咱们要一个合法的『类型编码(Type Encoding)』
// 若返回 nil,则不会进入下一步,而是没法处理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 当实现了此方法后,-doesNotRecognizeSelector: 将不会被调用
// 若是要测试找不到方法,能够注释掉这一个方法
// 在这里进行消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 咱们还能够改变方法选择器
[anInvocation setSelector:@selector(notFind)];
// 改变方法选择器后,还须要指定是哪一个对象的方法
[anInvocation invokeWithTarget:self];
}
- (void)notFind {
NSLog(@"没有实现 -mysteriousMethod 方法,而且成功的转成了 -notFind 方法");
}复制代码
部份内容引用和翻译自
nshipster.cn/method-swiz…
nshipster.cn/associated-…
developer.apple.com/library/con…
github.com/ming1016/st…
第二篇 深刻理解OC Runtime