您的位置:首页 > 移动开发 > Objective-C

objective-C 编程全解-第15章 消息发送模式 中

2016-07-07 11:45 549 查看
第15章 消息发送模式 中

1:方法名相同,即使在不同的类当中@selector()得到的值也是相同的,如下:

InvocationTest类:

NSLog(@"%ld",@selector(invocationTestFuncB));//4365084524,4548896620,4529563500

ViewController类:

NSLog(@"%ld",@selector(invocationTestFuncB));//4365084524,4548896620,4529563500

且每次运行结果不相同。

2:
InvocationTest与ViewController中都实现了下面方法:

- (void)invocationTestFuncA;

- (void)invocationTestFuncB;



   
//NSMethodSignature记录了方法的参数个数,参数类型和返回值类型等信息,这个类的实例也叫作方法签名。方法通过消息选择器被调用,选择器并不包括参数和返回值信息。

    NSMethodSignature * methodSig1 = [self
methodSignatureForSelector:@selector(invocationTestFuncB)];

    NSLog(@"%@",methodSig1);//<NSMethodSignature: 0x7fd711609b30>

    

    InvocationTest *test = [[InvocationTest
alloc]init];

    NSMethodSignature * methodSig2 = [test
methodSignatureForSelector:@selector(invocationTestFuncB)];

    NSLog(@"%@",methodSig2);//<NSMethodSignature: 0x7fd711609b30>

    

    NSMethodSignature * methodSig3 = [test
methodSignatureForSelector:@selector(invocationTestFuncA)];

    NSLog(@"%@",methodSig3);//<NSMethodSignature: 0x7fd711609b30>

    

    NSMethodSignature * methodSig4 = [self
methodSignatureForSelector:@selector(init)];

    NSLog(@"%@",methodSig4);//<NSMethodSignature: 0x7fd7117932a0>

可以看出对于具有相同类型相同数量的返回值和参数的方法的NSMethodSignature全是相同的。这个类的对象就使用类记录一个方法的返回值、参数等信息。

15.5 消息转送

15.5.1 消息转送的构成

将消息发送给没有实现该消息方法对象的时,通常会出现运行时错误。但是,我们可以将不能被处理的消息转送给其他对象。

若某个对象收到不能处理的消息



1:可以转发给其他对象处理

- (void)forwardInvocation:(NSInvocation *)anInvocation

2:可以自己处理

- (void)doesNotRecognizeSelector:(SEL)aSelector;

如果此消息没有转发给其他对象的话上面的处理会被调用,自己处理错误,但程序依然会崩溃(其实这个方法主要用于子类禁止调用父类中的某个方法)。

15.5.2 消息转送所需要的信息

如接口所示- (void)forwardInvocation:(NSInvocation *)anInvocation的参数anInvocation中携带了消息转送所需要的信息,有目标、选择器、参数等

@property (nullable,
assign) id target;

@property SEL selector;

@property (readonly,
retain) NSMethodSignature *methodSignature;

也可以自定义NSInvocation接口,或重定义选择器,参数,返回值。

15.5.3 消息转送的定义

需要指出的是消息转送只能转送参数个数确定的方法,参数个数不确定的方法则不能转送。

再有,为了使运行时系统能够使用传送目的地的对象信息生成NSInvocation实例,必须重新定义返回的方法签名(method signature)对象(因为生成NSInvocation对象需要目标、选择器、参数等信息,而NSMethodSignature包含着参数返回值等信息(因此也可以预料到,此方法会先于forwardInvocation:方法被调用))。

此外,使用

转送方法处理的消息不能被respondsToSelector:等调用。但需要知道该消息是否为目标对象可处理的消息时,必须重新定义respondsToSelector:等方法。

15.5.4 禁止使用消息

如超类中使用的信息在子类中不能使用时:

//子类中实现:

- (void)setSize:(NSSize *)size

{

    [self doesNotRecognizeSelector:_cmd];

}

15.5.5 程序示例

//

//  InvocationTest.h

//  HTMLTest

//

//  Created by ranzhou on 16/7/1.

//  Copyright © 2016年 ranzhouee. All rights reserved.

//

#import <Foundation/Foundation.h>

@interface InvocationTest :
NSObject

- (void)invocationTestFuncA; 
//有实现

- (void)invocationTestFuncB;  
//有实现

- (void)noThisFun; 
//此方法没有实现

@end

//

//  InvocationTest.m

//  HTMLTest

//

//  Created by ranzhou on 16/7/1.

//  Copyright © 2016年 ranzhouee. All rights reserved.

//

#import "InvocationTest.h"

#import <objc/runtime.h>

#import <objc/message.h>

@implementation InvocationTest

-(instancetype)init

{

    self = [super init];

    if (self) {

    }

    return
self;

}

- (void)invocationTestFuncA

{

    NSLog(@"%@ %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));

}

- (void)invocationTestFuncB

{

    NSLog(@"%@ %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));

}

/*

 - (void)doesNotRecognizeSelector:(SEL)aSelector

 Description:

 Handles messages the receiver doesn’t recognize.

 The runtime system invokes this method whenever an object receives an aSelector message
it can’t respond to or forward. This method, in turn, raises an NSInvalidArgumentException, and generates an error message.

 Any doesNotRecognizeSelector: messages are generally sent only by the runtime system. However, they can be used in program code to prevent a method from being inherited. For example, an NSObject subclass might renounce the copy or init method
by re-implementing it to include a doesNotRecognizeSelector: message as follows:

       - (id)copy

       {

            [self doesNotRecognizeSelector:_cmd];

       }

 The _cmd variable is a hidden argument passed to every method that is the current selector; in this example, it identifies the selector for the copy method. This code prevents instances of the subclass from responding to copy messages or superclasses
from forwarding copy messages—although respondsToSelector: will still report that the receiver has access to a copy method.

 If you override this method, you must call super or raise an NSInvalidArgumentException exception at the end of your implementation. In other words, this method must not return normally; it must always result in an exception being thrown.

 

 Parameters:

 aSelector

 A selector that identifies a method not implemented or recognized by the receiver.

 */

/*

 没啥好说的,看注释,主要用于be used in program code to prevent a method from being inherited.Although respondsToSelector: will still report that the receiver has access
to the  method.

 */

- (void)doesNotRecognizeSelector:(SEL)aSelector

{

    //[super doesNotRecognizeSelector:aSelector];//此方法要放在最后调用,或者下最后手动抛出NSInvalidArgumentException异常。

    NSLog(@"%@,%@",NSStringFromSelector(_cmd),NSStringFromSelector(aSelector));

    //[NSException raise:NSInvalidArgumentException  format:@"崩溃原因:doesNotRecognizeSelector"];

    [super doesNotRecognizeSelector:aSelector];

}

@end

//

//  ViewController.h

//  HTMLTest

//

//  Created by ranzhou on 16/6/29.

//  Copyright © 2016年 ranzhouee. All rights reserved.

//

#import <UIKit/UIKit.h>

@interface ViewController :
UIViewController

- (void)VCinvocationTestFuncA;
//没有实现

- (void)invocationTestFuncA;   
//没有实现

- (void)invocationTestFuncB;   
//有实现

@end

- (void)viewDidLoad {

    [super
viewDidLoad];

    [self
invocationTest];

}

- (void)invocationTestFuncB

{

    NSLog(@"%@ %@",NSStringFromClass([self
class]),NSStringFromSelector(_cmd));

}

/*

 class NSMethodSignature

 Description

 An NSMethodSignature object records type information for the return value and parameters of a method. It is used to forward messages that the receiving object does not respond to—most notably in the case of distributed objects.

 Overview

 Important:Important

 This is a preliminary document for an API or technology in development. Although this document has been reviewed for technical accuracy, it is not final. This Apple confidential information is for use only by registered members of the applicable
Apple Developer program. Apple is supplying this confidential information to help you plan for the adoption of the technologies and programming interfaces described herein. This information is subject to change, and software implemented according to this document
should be tested with final operating system software and final documentation. Newer versions of this document may be provided with future seeds of the API or technology.

 You typically create an NSMethodSignature object using the NSObjectmethodSignatureForSelector: instance method (on OS X 10.5 and later you can also use signatureWithObjCTypes:). It is then used to create an NSInvocation object, which is passed
as the argument to a forwardInvocation: message to send the invocation on to whatever other object can handle the message. In the default case, NSObject invokes doesNotRecognizeSelector:, which raises an exception. For distributed objects, the NSInvocation
object is encoded using the information in the NSMethodSignature object and sent to the real object represented by the receiver of the message.

 Type Encodings

 An NSMethodSignature object is initialized with an array of characters representing the string encoding of return and argument types for a method. You can get the string encoding of a particular type using the @encode() compiler directive. Because
string encodings are implementation-specific, you should not hard-code these values.

 A method signature consists of one or more characters for the method return type, followed by the string encodings of the implicit arguments self and _cmd, followed by zero or more explicit arguments. You can determine the string encoding and
the length of a return type using methodReturnType and methodReturnLength properties. You can access arguments individually using the getArgumentTypeAtIndex: method and numberOfArguments property.

 For example, the NSString instance method containsString: has a method signature with the following arguments:

 @encode(BOOL) (c) for the return type

 @encode(id) (@) for the receiver (self)

 @encode(SEL) (:) for the selector (_cmd)

 @encode(NSString *) (@) for the first explicit argument

 See Type Encodings in Objective-C Runtime Programming Guide for more information.

 Availability iOS (2.0 and later)

 Declared In NSMethodSignature.h

 Reference NSMethodSignature Class Reference

 */

- (void)invocationTest

{

    self.myTest = [[InvocationTest
alloc]init];

    //[self.myTest noThisFun];

    /*

     Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '崩溃原因:doesNotRecognizeSelector'.

     详细分析见InvocationTest的doesNotRecognizeSelector:.

     */

    

    [self
description]; 
//此方法不会引起methodSignatureForSelector:和forwardInvocation:被调用。

    [self
invocationTestFuncA];   
//self没有实现此方法,但self.myTest实现了这个方法,methodSignatureForSelector:和forwardInvocation:被先后调用。

    //[self VCinvocationTestFuncA]; //self没有实现此方法,self.myTest也没有实现这个方法,methodSignatureForSelector:被调用,返回nil,随后崩溃。

    [self
invocationTestFuncB];

    /*

     2016-07-02 13:18:54.684 HTMLTest[1767:84870] ViewController methodSignatureForSelector: invocationTestFuncA result:<NSMethodSignature: 0x7f9820d06490>

     2016-07-02 13:18:55.490 HTMLTest[1767:84870] ViewController invocationTestFuncA

     2016-07-02 13:18:55.490 HTMLTest[1767:84870] [anInvocation invokeWithTarget:self.myTest]

     2016-07-02 13:18:56.138 HTMLTest[1767:84870] InvocationTest invocationTestFuncA

     2016-07-02 13:18:57.220 HTMLTest[1767:84870] ViewController invocationTestFuncB

     */

}

/*

 - (void)forwardInvocation:(NSInvocation *)anInvocation

 Description

 Overridden by subclasses to forward messages to other objects.

 When an object is sent a message for which it has no corresponding(correspond |ˌkɒrɪˈspɒnd|) method, the runtime
system gives the receiver an opportunity(opportunity |ˌɒpəˈtjuːnəti|
n 机遇) to delegate the message to another receiver. It delegates the message by creating an NSInvocation object representing the message and sending the receiver a forwardInvocation: message containing this NSInvocation
object as the argument. The receiver’s forwardInvocation: method can then choose to forward the message to another object. (If that object can’t respond to the message either, it too will be given a chance to forward it.)

 The forwardInvocation: message thus(thus |ðʌs| 因此)
allows an object to establish relationships with other objects that will, for certain messages, act on its behalf. The forwarding object is, in a sense, able to “inherit” some of the characteristics of the object it forwards the message to.

 Important:Important

 To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector:
to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.

 An implementation of the forwardInvocation: method has two tasks:

 To locate an object that can respond to the message encoded in anInvocation. This object need not be the same for all messages.

 To send the message to that object using anInvocation. anInvocation will hold the result, and the runtime system will extract and deliver this result to the original sender.

 In the simple case, in which an object forwards messages to just one destination (such as the hypothetical friend instance variable in the example below), a forwardInvocation: method could be as simple as this:

 - (void)forwardInvocation:(NSInvocation *)invocation

 {

 SEL aSelector = [invocation selector];

 

 if ([friend respondsToSelector:aSelector])

 [invocation invokeWithTarget:friend];

 else

 [super forwardInvocation:invocation];

 }

 The message that’s forwarded must have a fixed number of arguments; variable numbers of arguments (in the style of printf()) are not supported.

 The return value of the forwarded message is returned to the original sender. All types of return values can be delivered to the sender: id types, structures, double-precision floating-point numbers.

 Implementations of the forwardInvocation: method can do more than just forward messages. forwardInvocation: can, for example, be used to consolidate code that responds to a variety of different messages, thus avoiding the necessity of having
to write a separate method for each selector. A forwardInvocation: method might also involve several other objects in the response to a given message, rather than forward it to just one.

 NSObject’s implementation of forwardInvocation: simply invokes the doesNotRecognizeSelector: method; it doesn’t forward any messages. Thus, if you choose not to implement forwardInvocation:, sending unrecognized messages to objects will raise
exceptions.

 Parameters

 anInvocation

 The invocation to forward.

 Availability iOS (2.0 and later)

 Declared In NSObject.h

 Reference NSObject Class Reference

 */

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

    SEL sel = [anInvocation
selector];

    NSLog(@"%@ %@",NSStringFromClass([self
class]),NSStringFromSelector(sel));

    if ([self.myTest
respondsToSelector:sel])

    {

        NSLog(@"[anInvocation invokeWithTarget:self.myTest]");

        [anInvocation invokeWithTarget:self.myTest];

    }

    else

    {

        NSLog(@"[super forwardInvocation:anInvocation]");

        [super
forwardInvocation:anInvocation];

       
//如果这句话不被实现会如何?会什么要调用父类的此方法?因为父类也可能会转发此消息

    }

}

/*

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

 Description

 Returns an NSMethodSignature object that contains a description of the method identified by a given selector.

 An NSMethodSignature object that contains a description of the method identified by aSelector, or nil if the method can’t be found.

 Parameters

 aSelector

 A selector that identifies the method for which to return the implementation address. When the receiver is an instance, aSelector should identify an instance method; when the receiver is a class, it should identify a class method.

 Returns An NSMethodSignature object that contains a description of the method identified by aSelector, or nil if the method can’t be found.

 */

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector

{

    if ([super
respondsToSelector:aSelector]) {

        return [super
methodSignatureForSelector:aSelector];

    }

    NSMethodSignature *result = [self.myTest
methodSignatureForSelector:aSelector];

    NSLog(@"%@ %@ %@ result:%@",NSStringFromClass([self
class]),NSStringFromSelector(_cmd),NSStringFromSelector(aSelector),result);

    return result;

}

- (BOOL)respondsToSelector:(SEL)aSelector

{

//    if ([self respondsToSelector:aSelector]) {//千万别,会循环调用,使用下面的代码代替

//        return YES;

//    }

    if([self
methodForSelector:aSelector]!=(IMP)NULL)

    {

        return
YES;

    }

    else
if([super
respondsToSelector:aSelector])

    {

        return
YES;

    }

    else
if([self.myTest
respondsToSelector:aSelector])

    {

        return
YES;

    }

    else

    {

        return
NO;

    }

}

NSInvocation的基本用法

http://www.jianshu.com/p/03e7279a9916

在 iOS中可以直接调用某个对象的消息方式有两种:

一种是performSelector:withObject;

再一种就是NSInvocation。

第一种方式比较简单,能完成简单的调用。但是对于>2个的参数或者有返回值的处理,那performSelector:withObject就显得有点有心无力了,那么在这种情况下,我们就可以使用NSInvocation来进行这些相对复杂的操作

NSInvocation的基本使用

方法签名类

// 方法签名中保存了方法的名称/参数/返回值,协同NSInvocation来进行消息的转发

// 方法签名一般是用来设置参数和获取返回值的,
和方法的调用没有太大的关系

//1、根据方法来初始化NSMethodSignature

NSMethodSignature  *signature = [ViewController instanceMethodSignatureForSelector:@selector(run:)];

根据方法签名来创建NSInvocation对象

// NSInvocation中保存了方法所属的对象/方法名称/参数/返回值

//其实NSInvocation就是将一个方法变成一个对象

//2、创建NSInvocation对象

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

//设置方法调用者

invocation.target =
self;

//注意:这里的方法名一定要与方法签名类中的方法一致

invocation.selector =
@selector(run:);

NSString *way = @"byCar";

//这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd)

[invocation setArgument:&way atIndex:2];

//3、调用invoke方法

[invocation invoke];

//实现run:方法

- (void)run:(NSString *)method{

}

优化

但是上述方法有很多弊端,首先我们来一一解决

1、如果调用的方法不存在

//此时我们应该判断方法是否存在,如果不存在这抛出异常

if (signature == nil) {

//aSelector为传进来的方法

NSString *info = [NSString stringWithFormat:@"%@方法找不到",
NSStringFromSelector(aSelector)];

[NSException raise:@"方法调用出现异常" format:info, nil];

    }

2、方法的参数个数与外界传进来的参数数组元素个数不符

//此处不能通过遍历参数数组来设置参数,因为外界传进来的参数个数是不可控的

//因此通过numberOfArguments方法获取的参数个数,是包含self和_cmd的,然后比较方法需要的参数和外界传进来的参数个数,并且取它们之间的最小值

NSUInteger argsCount = signature.numberOfArguments -
2;

NSUInteger arrCount = objects.count;

NSUInteger count = MIN(argsCount, arrCount);

for (int i =
0; i < count; i++) {

    id obj = objects[i];

    // 判断需要设置的参数是否是NSNull,
如果是就设置为nil

    if ([obj isKindOfClass:[NSNull class]]) {

        obj = nil;

    }

[invocation setArgument:&obj atIndex:i + 2];

}

3、判断当前调用的方法是否有返回值

//方法一:

id res = nil;

if (signature.methodReturnLength !=
0) {//有返回值

    //将返回值赋值给res

    [invocation getReturnValue:&res];

}

return res;

//方法二:

//可以通过signature.methodReturnType获得返回的类型编码,因此可以推断返回值的具体类型
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: