Objective-C基于C语言加入了面向对象特性和消息转发,Objective-C 的消息转发需要运行时系统来动态创建类和对象,从而进行消息发送和转发.
objc_msgSend
C语言在编译阶段决定运行时调用的函数,会生成直接调用函数的指令,相当于静态绑定.作为C语言的超集所有调用的函数直到运行期才能确定,对象收到消息后,具体调用哪个方法由运行期决定,甚至可以在运行的时候改变,所以Objective-C也被称之为动态语言.
Objective-C正常调用实例对象方法:
[course fetchData];
以上代码会被转换成标准的C语言函数调用,等价于以下代码:
Course *courseClass = objc_msgSend([Course class], sel_registerName("alloc"));
courseClass = objc_msgSend(courseClass, sel_registerName("init"));
SEL sel = sel_registerName("fetchData");
objc_msgSend(courseClass,sel);
头文件导入:
#import
#import
编译出错,需要修改设置选项:
objc_msgSend定义如下:
/**
* Sends a message with a simple return value to an instance of a class.
*
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method.
*
* @note When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
/**
SEL是映射到方法的C字符串,可以通过Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器.
所以下面的代码和的代码执行结果一致:
Course *courseClass = objc_msgSend([Course class], @selector(alloc));
courseClass = objc_msgSend(courseClass, @selector(init));
SEL sel = @selector(fetchData);
objc_msgSend(courseClass,sel);
Objective-C类是由Class类型来表示的,它是一个指向objc_class结构体的指针,objc_class定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
类对象也是一个实例,类对象中的isa指针指向的是元类(meta class),当我们进行类方法查询的时候就会通过isa指针找到元类查询.元类最终会指向根元类,形成查找闭环.
super_class指向父类,如果类对象本身无法实现,可以通过父类进行查找实现.
如果每次方法查询都需要通过methodlist来查找实现,那么效率很低,所以查找过一次会通过cache保存其实现,提升方法查找效率.
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
消息转发
Objective-C中经常遇到找到实现方法的问题,解决这个问题有三种方案,动态方法解析,重定向接受者和完整的消息转发.
动态方法解析,可以通过系统提供resolveClassMethod和resolveInstanceMethod来实现.
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
Course类中定义一个calculatData方法,但是不实现.
@interface Course : NSObject
- (void)fetchData;
- (void)calculatData;
@end
调用:
Course *course = [Course new];
[course calculatData];
报错信息:
[Course calculatData]: unrecognized selector sent to instance 0x608000013570
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Course calculatData]: unrecognized selector sent to instance 0x608000013570'
重写resolveInstanceMethod方法:
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(calculatData)) {
class_addMethod([self class], sel, (IMP)resolveMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void resolveMethodIMP(id self, SEL _cmd) {
NSLog(@"resolveInstanceMethod---resolveMethodIMP");
}
重置接收者,当前接受者如果不能处理对象,可以交由其他对象处理,类似于web端的重定向,也可以通过这种组合的方式实现多继承.
定义未实现的方法:
- (void)fetchMethod;
定义新的接收者:
@implementation Teacher
- (void)fetchMethod {
NSLog(@"Teacher---fetchMethod");
}
@end
forwardingTargetForSelector实现:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(fetchMethod)) {
return [Teacher new];
}
return [super forwardingTargetForSelector:aSelector];
}
调用:
Course *course = [Course new];
[course calculatData];
[course fetchMethod];
结果:
Teacher---fetchMethod
最后一步保证是完成的消息转发机制,需要重写methodSignatureForSelector和forwardInvocation方法.
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
Course类中的方法签名重写和执行:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%@",NSStringFromSelector(_cmd));
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([Teacher instancesRespondToSelector:aSelector]) {
signature = [Teacher instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@",NSStringFromSelector(_cmd));
if ([Teacher instanceMethodForSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[Teacher new]];
} else {
[super forwardInvocation:anInvocation];
}
}
Teacher类中重写forwardMethod方法:
- (void)forwardMethod {
NSLog(@"Teacher---forwardMethod");
}
方法调用:
Course *course = [Course new];
[course forwardMethod];
属性与方法
Runtime中最常见的方法就是属性和方法的获取,属性获取通过class_copyPropertyList实现.
/**
* Describes the properties declared by a class.
*
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If \e outCount is \c NULL, the length is not returned.
*
* @return An array of pointers of type \c objc_property_t describing the properties
* declared by the class. Any properties declared by superclasses are not included.
* The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
*
* If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
*/
OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
Course *course = [[Course alloc] init];
Class class = [course class];
unsigned int procount = 0;
objc_property_t * properties = class_copyPropertyList(class, &procount);
for (int i = 0; i < procount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSLog(@"属性名称: %s", propertyName);
}
free(properties);
class_copyIvarList获取对象的变量:
/**
* Describes the instance variables declared by a class.
*
* @param cls The class to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Ivar describing the instance variables declared by the class.
* Any instance variables declared by superclasses are not included. The array contains *outCount
* pointers followed by a NULL terminator. You must free the array with free().
*
* If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
*/
OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
unsigned int varcount = 0;
Ivar *varlist = class_copyIvarList(class, &varcount);
for (int i = 0; i < varcount; i++) {
Ivar var = varlist[i];
const char *varname = ivar_getName(var);
NSLog(@"变量名称: %s", varname);
}
free(varlist);
class_copyMethodList获取方法列表:
/**
* Describes the instance methods implemented by a class.
*
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Method describing the instance methods
* implemented by the class—any instance methods implemented by superclasses are not included.
* The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
*
* If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
*
* @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
* @note To get the implementations of methods that may be implemented by superclasses,
* use \c class_getInstanceMethod or \c class_getClassMethod.
*/
OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
unsigned int methodcount = 0;
Method *methods = class_copyMethodList(class, &methodcount);
for (int i = 0; i < methodcount; i++) {
Method method = methods[i];
SEL method_name = method_getName(method);
NSLog(@"方法名称:%@",NSStringFromSelector(method_name));
}
free(methods);
方法交换
Objective-C中方法交换(Method Swizzling)发生在运行时,在运行时将两个Method进行交换,可以通过方法交换的特性实现AOP(面向切面编程),可以在埋点统计中执行.
交换UIViewController的viewDidLoad方法,执行完新的方法之后,继续执行交换之前的方法,交换代码在load方法中执行.Method Swizzling本质上就是对IMP和SEL进行交换.
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(fe_viewDidload);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)fe_viewDidload {
NSLog(@"fe_viewDidload执行");
[self fe_viewDidload];
}
原始方法:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"viewDidload执行");
}
执行结果:
fe_viewDidload执行
viewDidload执行
isa-swizzling
iOS开发中常用的KVO(Key-Value Observing)键值观察是典型的isa
swizzling,官方文档介绍如下:
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa
pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa
pointer to determine class membership. Instead, you should use the class
method to determine the class of an object instance.
键值观察的主要方法:
@interface NSObject(NSKeyValueObserverRegistration)
/* Register or deregister as an observer of the value at a key path relative to the receiver. The options determine what is included in observer notifications and when they're sent, as described above, and the context is passed in observer notifications as described above. You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong.
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
KVO在调用addObserver方法之后,将isa指向到另外一个类,在新类里面重写被观察的对象的class,setter,dealloc,_isKVOA方法.
定义Teacher类:
@interface Teacher : NSObject
@property (copy, nonatomic) NSString *teacherName;
- (void)fetchMethod;
- (void)forwardMethod;
@end
Teacher *teacher = [Teacher new];
NSLog(@"Teacher->isa:%@",object_getClass(teacher));
NSLog(@"Teacher class:%@",[teacher class]);
NSLog(@"Teacher方法列表 = %@",ClassMethodNames(object_getClass(teacher)));
[teacher addObserver:self forKeyPath:@"courseName" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"Teacher->isa:%@",object_getClass(teacher));
NSLog(@"Teacher class:%@",[teacher class]);
NSLog(@"Teacher方法列表 = %@",ClassMethodNames(object_getClass(teacher)));
self.teacher = teacher;
static NSArray * ClassMethodNames(Class c)
{
NSMutableArray * array = [NSMutableArray array];
unsigned int methodCount = 0;
Method * methodList = class_copyMethodList(c, &methodCount);
unsigned int i;
for(i = 0; i < methodCount; i++) {
[array addObject: NSStringFromSelector(method_getName(methodList[i]))];
}
free(methodList);
return array;
}
执行结果isa指向了新的类NSKVONotifying_Teacher.
Teacher->isa:Teacher
Teacher class:Teacher
Teacher方法列表 = (
fetchMethod,
forwardMethod,
teacherName,
"setTeacherName:",
".cxx_destruct"
)
Teacher->isa:NSKVONotifying_Teacher
Teacher class:Teacher
Teacher方法列表 = (
class,
dealloc,
"_isKVOA"
)
重写setter方法:
/* Given a key that identifies a property (attribute, to-one relationship, or ordered or unordered to-many relationship), send -observeValueForKeyPath:ofObject:change:context: notification messages of kind NSKeyValueChangeSetting to each observer registered for the key, including those that are registered with other objects using key paths that locate the keyed value in this object. Invocations of these methods must always be paired.
The change dictionaries in notifications resulting from use of these methods contain optional entries if requested at observer registration time:
- The NSKeyValueChangeOldKey entry, if present, contains the value returned by -valueForKey: at the instant that -willChangeValueForKey: is invoked (or an NSNull if -valueForKey: returns nil).
- The NSKeyValueChangeNewKey entry, if present, contains the value returned by -valueForKey: at the instant that -didChangeValueForKey: is invoked (or an NSNull if -valueForKey: returns nil).
*/
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
关联对象
关联对象在类别中比较频繁,假设扩展UITextField,新加属性placeHolderColor.
@property (strong,nonatomic) UIColor *placeHolderColor;
-(UIColor *)placeHolderColor {
return objc_getAssociatedObject(self, placeHolderColorKey);
}
-(void)setPlaceHolderColor:(UIColor *)placeHolderColor {
[self setValue:placeHolderColor forKeyPath:@"_placeholderLabel.textColor"];
objc_setAssociatedObject(self,placeHolderColorKey, placeHolderColor, OBJC_ASSOCIATION_RETAIN);
}
归档和解档
Objective-C中实现自定义对象的归档和解档需要实现NSCoding协议.
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end
以Teacher类为例:
// 解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSLog(@"initWithCoder");
self = [super init];
if (self) {
self.teacherName = [aDecoder decodeObjectForKey:@"teacherName"];
self.age = [[aDecoder decodeObjectForKey:@"age"] integerValue];
}
return self;
}
// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(@"encodeWithCoder");
[aCoder encodeObject:self.teacherName forKey:@"teacherName"];
[aCoder encodeObject:@(self.age) forKey:@"age"];
}
归档和解档保存和读取:
- (IBAction)saveAction:(UIButton *)sender {
Teacher *teacher = [Teacher new];
teacher.teacherName = @"FlyElephant";
teacher.age = 27;
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"teacher.archiver"];
BOOL success = [NSKeyedArchiver archiveRootObject:teacher toFile:filePath];
if(success){
NSLog(@"归档保存成功");
}
}
- (IBAction)readAction:(UIButton *)sender {
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"teacher.archiver"];
Teacher *teacher = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"Teacher:%@---age:%ld",teacher.teacherName,teacher.age);
}
归档保存成功
Teacher:FlyElephant---age:27
属性如果过多会写的很烦躁,通过runtime来解决:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
unsigned int varCount = 0;
Ivar *vars = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int varCount = 0;
Ivar *vars = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}
同样的Runtime可以通过属性和变量的读取,实现字典和模型之间的转换,简易版比较容易。
参考链接:
动态绑定、objc_msgSend、消息转发机制
Objective-C Runtime 运行时之一:类与对象
Objective-C Runtime Programming Guide
KVO官方文档
神经病院Objective-C Runtime出院第三天——如何正确使用Runtime
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/