消息转发机制入门篇

一.何时处发消息转发机制?

解:当对象接收到无法解读的消息后,就会启动“消息转发”(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

如:-[__NSCFNumber lowercaseString] :unrecognized selector sent to instance 0x87

上面这段异常信息是由NSObjc 的”doesNotRecognizeSelector”方法所抛出的,此异常表明:消息接收者的类型是_ _NSCFNumber,而该接收者无法理解名为lowercaseString的选择子。

二.为什么要用消息转发?

解:1.Obj-C用起来真是各种happy,比如现在有这样一种情况:有一个类,我们希望它能响应一个消息(message),但是这个类没有相应的方法(method),而你又偏偏不能重写/继承这个类。这时我们可能会想到,能不能动态地给类添加一个方法呢?感谢Obj-C,仅需简单几步就能实现。

2.当用@property定义,用@dynamic实现的话,会用的消息转发。

3.当某个方法没有实现时,会调用消息转发机制。

调用前:

2016-04-17 00:05:42.074 MessageForwarding[927:70955] -[MessageForwarding running]: unrecognized selector sent to instance 0x7fee615169c0

2016-04-17 00:05:42.076 MessageForwarding[927:70955] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException‘, reason: ‘-[MessageForwarding running]: unrecognized selector sent to instance 0x7fee615169c0‘

*** First throw call stack:

(

0   CoreFoundation                      0x00000001106dfe65 __exceptionPreprocess + 165

1   libobjc.A.dylib                     0x0000000110158deb objc_exception_throw + 48

2   CoreFoundation                      0x00000001106e848d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205

3   CoreFoundation                      0x000000011063590a ___forwarding___ + 970

4   CoreFoundation                      0x00000001106354b8 _CF_forwarding_prep_0 + 120

5   MessageForwarding                   0x000000010fc590d3 -[ViewController viewDidLoad] + 99

6   UIKit                               0x0000000110c22f98 -[UIViewController loadViewIfRequired] + 1198

7   UIKit                               0x0000000110c232e7 -[UIViewController view] + 27

8   UIKit                               0x0000000110af9ab0 -[UIWindow addRootViewControllerViewIfPossible] + 61

9   UIKit                               0x0000000110afa199 -[UIWindow _setHidden:forced:] + 282

10  UIKit                               0x0000000110b0bc2e -[UIWindow makeKeyAndVisible] + 42

11  UIKit                               0x0000000110a84663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131

12  UIKit                               0x0000000110a8acc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760

13  UIKit                               0x0000000110a87e7b -[UIApplication workspaceDidEndTransaction:] + 188

14  FrontBoardServices                  0x000000011345b754 -[FBSSerialQueue _performNext] + 192

15  FrontBoardServices                  0x000000011345bac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45

16  CoreFoundation                      0x000000011060ba31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17

17  CoreFoundation                      0x000000011060195c __CFRunLoopDoSources0 + 556

18  CoreFoundation                      0x0000000110600e13 __CFRunLoopRun + 867

19  CoreFoundation                      0x0000000110600828 CFRunLoopRunSpecific + 488

20  UIKit                               0x0000000110a877cd -[UIApplication _run] + 402

21  UIKit                               0x0000000110a8c610 UIApplicationMain + 171

22  MessageForwarding                   0x000000010fc59a1f main + 111

23  libdyld.dylib                       0x0000000112e1b92d start + 1

)

libc++abi.dylib: terminating with uncaught exception of type NSException

调用后:

2016-04-17 00:09:22.577 MessageForwarding[950:73397] message.string=昆昆,message.number=29,message.date=2033-04-25 08:58:42 +0000

三.消息转发的过程?

1.过程

解:第一阶段:先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”(unknown selector)这叫做动态方法解析(dynamic method resolution)。

第二阶段:涉及“完整的消息转发机制”(full forwarding mechanism)如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法来响应包含该选择子的消息了。此时,运行期系统会请求接收者以其他手段来处理与消息相关的方法调用。这细分两小步,首先,请接收者看看有没有其他对象能处理这消息。若有,则运行期系统会

把消息转给那个对象,于是消息转发结束,一切如常。若没有“备援的接收者”(replacement recever),则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到NSInvocation 对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

2.方法如下:

+ (BOOL)resolveInstanceMethod:(SEL)sel;

该方法的参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。

假如尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另个一个方法,该方法与+ (BOOL)resolveInstanceMethod:(SEL)sel类似叫做:+(BOOL)resolveClassMethod:(SEL)sel。

3.使用前提

相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。此方案常用来实现@dynamic 属性,比如要访问CoreData框架中NSManagedObjects 对象属性时就可以这么做,因为实现这些属性所需的存取方法在编译期就能确定。

4.代码先行

下面代码演示了如何用“resolveInstanceMethod:”来实现@dynamicnt属性。

#import <Foundation/Foundation.h>

@interface MessageForwarding : NSObject

id autoDictionaryGetter(id self,SEL _cmd);              //setter 方法
void autoDictionarySetter(id self,SEL _cmd,id value);   //getter 方法

/**
 *  动态解析方法
 *
 *  @param sel 未知的选择子
 *
 *  @return 这个类是否能新增一个实例方法用以处理此选择子
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel;

@property (nonatomic, strong) NSString   *string;
@property (nonatomic, strong) NSNumber   *number;
@property (nonatomic, strong) NSDate     *date;
@property (nonatomic, strong) id         opaqueObject;

@end

#import "MessageForwarding.h"
#import <objc/runtime.h>
@interface MessageForwarding()
@property(nonatomic, strong)NSMutableDictionary *backingStore;
@end
@implementation MessageForwarding

@dynamic string,date,number,opaqueObject;

#pragma mark getter and setter
id autoDictionaryGetter(id self,SEL _cmd){

    //Get the backing store from the object
    MessageForwarding *typeSelf = (MessageForwarding *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;

    //the key is simply the selector name
    NSString *key = NSStringFromSelector(_cmd);
    //return the value
    return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self,SEL _cmd,id value){

    //back the backing store from the object
    MessageForwarding *kunkunSelf = (MessageForwarding *)self;
    NSMutableDictionary *backStore = kunkunSelf.backingStore;
    /**
     The selector will be for example ,"setOpaqueObject" //以这个选择器为例,“setopaqueobject”
     we need to remove the "set",":" and lowercase the first //我们必须删除set和:和第一个字母小写
     letter of the remainder
     */
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    //Remove the : at the end //在最后删除:
    [key deleteCharactersInRange:NSMakeRange(key.length -1, 1)];
    //Remove the "set" prefix
    [key deleteCharactersInRange:NSMakeRange(0, 3)];

    //lowercase the first character
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    if (value) {
        [backStore setObject:value forKey:key];
    }else{
        [backStore removeObjectForKey:key];
    }
}
#pragma init
- (instancetype)init{
    if (self = [super init]) {
        _backingStore = @{}.mutableCopy;
    }
    return self;
}
#pragma mark message forwarding 消息转发机制
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    //消息转化为字符串
    NSString *selectorString = NSStringFromSelector(sel);
    // if(/**selector is from a @dynamic property*/){

    if ([selectorString hasPrefix:@"set"]) {
        class_addMethod (self,sel,(IMP)autoDictionarySetter,"[email protected]:@");
    }else{
        class_addMethod(self, sel,(IMP)autoDictionaryGetter,"@@:");
    }
    return YES;
    //  }//
    //    return [super resolveInstanceMethod:sel];
}
@end

5.备援接收者

当前接收者还有第二次机会能处理未知的选择子,在这一步中,运行期系统会问它:能不能把这条消息转发给其他接收者处理,方法如下:

/**

 *  备援接收者,当第一次未处理时,才会调用它未调用时resolveInstanceMethod:(SEL)sel,请注意,我们无法操作由这一步所转发的消息,若是想在发送给备援接收者之前先修改消息内容,那就得通过完整的消息转发机制来做了。

 *

 *  @param aSelector 消息

 *

 *  @return 若当前接收者能找到备援对象,则将其返回,若找不到,就返回nil

 */

6.完整的消息转发

- (id)forwardingTargetForSelector:(SEL)aSelector;

/**

 *  完整消息转发,在触发NSInvocation 对象时,“消息派发系统”(message dispatch system)将亲自出马,把消息指派给目标对象

 *

 *  @param anInvocation <#anInvocation description#>

 */

消息转发全流程

resolveInstanceMethod  —>返回YES -->消息已处理

              —> forwardingTargetForSelector -->接收相应的值 --->消息已处理

                              -->返回nil-->forwardInvocation -->消息已处理

                                              --->消息未处理

时间: 2024-11-04 14:29:08

消息转发机制入门篇的相关文章

iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识,从原理到实践.由于包括内容过多分为下面五篇文章详细解说.可自行选择须要了解的方向: 从runtime開始: 理解面向对象的类到面向过程的结构体 从runtime開始: 深入理解OC消息转发机制 从runtime開始: 理解OC的属性property 从runtime開始: 实践Category加入属

Effective Objective-C 2.0 — 第12条:理解消息转发机制

11 条讲解了对象的消息传递机制 12条讲解对象在收到无法解读的消息之后会发生什么,就会启动“消息转发”(message forwarding)机制, 若对象无法响应某个选择子,则进入消息转发流程. 1,通过运行期的动态方法解析功能,可以在需要用到某个方法时再将其加入类中. 2,对象可以把其无法解读的某些选择子转交给其他对象来处理. 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制. 动态方法解析 对象在收到无法解读的消息之后,首先调用其所属类的下列类方法 + (BOOL)

第12条:理解消息转发机制

要点 若对象无法响应某个选择子,则进入消息转发流程. 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中. 对象可以把其无法解读的某些选择子转交给其他对象来处理. 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制.

iOS消息转发机制

iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确定类中到底会不会有某个方法的实现.当对象接收到无法解读的消息时,就会启动“消息转发”机制,我们可以经由此过程告诉对象应该如何处理未知消息. 消息转发分为两个阶段.第一阶段先征询接收者所属的类,看其是否能动态添加方法,已处理当前这个“未知的选择

关于OC中消息转发机制的理解以及在项目中的实际应用

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">关于OC中的消息转发机制想必大家都很了解,现在来温习一下:</span> 一.什么是消息转发? @selector 是什么? 1一种类型 SEL 2代表你要发送的消息(方法), 跟字符串有点像, 也可以互转.: NSSelectorFromString()   /   NSS

《Effective Objective-C 2.0》—(第11-14条)—运行时动态绑定、objc_msgSend、消息转发机制

第11条:理解objc_msgSend的作用 在对象上调用方法是OC中经常使用的功能.用OC术语来说这叫做:"传递消息"(pass a message).消息有"名称"(name)或者"选择子"(selector),可以接收参数,而且可能还有返回值. 由于OC是C的超集,所以最好理解C语言的函数调用方式.C语言使用"静态绑定",就是说在编译期就能决定运行时所应调用的函数.以下列代码为例: #import <stdio.h

理解消息转发机制

消息转发分为两大阶段.第一阶段先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个"未知的选择子"(unknown selector),这叫做"动态方法解析"(dynamic method resolution).第二阶段涉及"完整的消息转发机制".如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了.此时,运行期系统会请求接收者以其他手段来处理与消息相关的方法调用.这又细分为两小步

runtime消息转发机制

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制.而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库.它是 Objective-C 面向对象和动态机制的基石. Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象.进行消息传递和转发.理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的

swizzle method 和消息转发机制的实际使用

我的工程结构,如图 1-0 图  1-0 在看具体实现以前,先捋以下 实现思路. ViewController 中有一个-(void)Amethod;A方法. -(void)Amethod{ NSLog(@"Amethod"); } 1.swizzle method 在ViewController 的 -(void)viewDidLoad中调用 Amentohd:运行输出                   图 1-1 创建一个ViewController 的Category,重写+(