消息机制
引入#import
objc_msgSend(id self, SEL op, ...),消息发送函数,对象动态调用自身方法的函数。
参数1:调用函数的对象。
参数2:方法选择器。
参数3~:方法参数,多个参数依次为 参数4 参数5… 。
Id, 是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针可以找到对象的类。
Id 与instancetype,都可以用来表示一个未知类型的对象,instancetype可以检测对象类型从而调用对象的方法,id不能检测;instancetype只能用作方法返回参数类型,不能用来定义变量。
Class, 类。一个指向objc_class结构体指针,定义:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class ,//父类
const char *name ,//类名
long version ,
long info ,
long instance_size ,
struct objc_ivar_list *ivars ,//成员变量列表
struct objc_method_list **methodLists ,//方法列表
struct objc_cache *cache ,//常用方法缓存
struct objc_protocol_list *protocols ,//协议列表
#endif
} OBJC2_UNAVAILABLE;
** isa**,Class本身也是一个对象,具有对应的super_class,相对于super_class来说Class就是一个对象,isa表示Class这个对象的Class。每个Class都有isa指针指向唯一元类(Meta class),直到最后指向根类,如NSObject,NSObject没有父类,其父类指向nil。
Method,一个指向objc_method结构体指针,存储了方法名、方法类型、IMP。
Ivar,一个指向objc_ivar结构体指针,类中的实例变量,包含了变量名、变量类型等信息。
SEL,方法选择器,根据方法名获取对应方法,是一个指向objc_selector的结构体指针。
IMP,一个函数指针,指向方法的实现。 SEL与IMP的关系
_cmd,表示当前调用方法。
methodLists,方法列表,可以动态添加方法,category便是据此实现,所以category中不能直接添加属性。
cache,常用方法缓存列表。为了提升方法调用效率,当对象receiver调用方法message时,根据对象isa指针找到对应的类,首先在cache中查找对应方法,如果没找到再去methodLists中查找,如果还是没找到就去父类中查找,依次查找到根类,如果最后没找到Crash,抛出unrecognized selector sent to…类似这样的异常信息,而在Crash前我们还可以做的操作:
1.使用 Method Resolution 动态实现方法。
"对象调用了没有实现的方法时会执行此方法"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sendMessage:)) {
void (^block)(void) = ^ {
NSLog(@"log");
};
"c语言函数默认参数(id self, SEL _cmd)
参数"v@:": v,表示函数返回值类型void @,表示OC对象参数 :,表示SEL"
class_addMethod([self class], sel, imp_implementationWithBlock(block), "v@:");
}
return YES;
}
2.如果 Method Resolution 返回NO,运行时执行下一步:消息转发(Message Forwarding)。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [MessageForwarding new];//MessageForwarding类需要自定义实现,并在其中实现方法sendMessage:
}
return nil;
}
3.如果没有实现forwardingTargetForSelector消息转发,可以使用methodSignatureForSelector进行消息转发,如果返回nil则Crash,如果返回一个签名函数会生成一个NSInvocation对象,并调用-forwardInvocation:方法。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];//获取方法参数和返回值,生成一个方法签名对象
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwarding new];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
三种方法的选择:
Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?
Method Resolution,由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。
Fast Forwarding,它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
Normal Forwarding,它跟Fast Forwarding一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects
在Category中动态合成属性可以使用以下函数:
//利用消息发送机制生成属性setter方法
void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
参数1:添加属性的对象.
参数2:成员变量地址。
参数3:成员变量。
参数4:修饰属性关键字。
//利用消息发送机制生成属性getter方法
id objc_getAssociatedObject (id object, const void *key )
//利用消息发送机制生删除关联值
void objc_removeAssociatedObjects (id object )
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. */
};
Method Swizzling
用一个方法的实现替换另一个方法,一般用在+(void)load中。load方法解析
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);
}