继承自NSObject的不常用又很有用的函数

来源:互联网 发布:matlab for mac多少钱 编辑:程序博客网 时间:2024/06/05 18:56


初始化阶段 —— load 和 initialize

load函数

原型:

1 +(void)load

当类被引用进程序的时候会执行这个函数。

在一个程序开始运行之前(在main函数开始执行之前),在库开始被程序加载,load函数就会开始被执行。

我们开发的程序都可以认为是一个库,但是库又不会独立存在(我们的程序还会引用其他库,也可能被其他函数引用),所以库的初始化顺序可以如下:

  1. 初始化我们引用的库
  2. 执行我们自己库的Objective-C的load函数
  3. 执行C++和C的static初始化变量
  4. 初始化引用我们库的其他库

在我们的编写的库中,会有很多类重写load函数,他们之间的执行顺序是不确定的。

当父类和子类都实现load函数时,父类的load函数会被先执行。load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行

在Category中写load函数是不会替换原始类中的load函数的,原始类和Category中的load函数都会被执行,原始类的load会先被执行,再执行Category中的load函数。当有多个Category都实现了load函数,这几个load函数执行顺序不确定。

Initialize函数

原型:

1 + (void)initialize

当类第一次被执行到的时候这个函数会被执行。

如果类包含继承关系,父类的initialize函数会比子类先执行。由于是系统自动调用,也不需要显式的调用父类的initialize,否则父类的initialize会被多次执行

假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。

Load or Initialize

这两个函数没有交集,也没有执行的先后顺序,他们各自遵循着各自的调用原则。因此在写逻辑的时候,不能有逻辑依赖load函数比initialize函数先行调用

来看下面这种情况:

复制代码
 1 @interface NoneClass : NSObject 2  3   4  5 @end 6  7   8  9 @implementation NoneClass10 11 +(void)load12 13 {14 15     NSLog(@"NoneClass _cmd: %@", NSStringFromSelector(_cmd));16 17     MyTestObject *test = [[MyTestObject alloc] init];18 19 }20 21 @end22 23  24 25 @interface MyTestObject26 27  28 29 @end30 31  32 33 @implementation MyTestObject34 35  36 37 + (void) load38 39 {40 41     NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));42 43 }44 45  46 47 + (void)initialize48 49 {50 51     NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));52 53 }54 55  56 57 @end
复制代码

 输出的结果:

2014-04-11 12:45:44.297 ObjectTest[1617:60b] NoneClass _cmd: load2014-04-11 12:45:44.300 ObjectTest[1617:60b] MyTestObject _cmd: initialize2014-04-11 12:45:44.301 ObjectTest[1617:60b] MyTestObject _cmd: load

结果分析:

由于在执行NonoClass的load函数中调用了MyTestObject的构造函数,这样会触发MyTestObject的initialize函数调用。而此时MyTestObject的load函数还没有被调用。

 

使用场景:

将针对于类修改放在intialize中,将针对Category的修改放在load中。

但是假如我们是修改系统的类,一般会通过添加Category来添加功能,但是如果修改initialize会导致原生的intialize不会执行,所以放在load中会比较妥当。

 

函数调用

Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。Selector相当于门牌号,而Implement才是真正的住户(函数实现)。

和现实生活一样,门牌可以随便发(@selector(XXX)),但是不一定都找得到住户,如果找不到系统会给程序几次机会来程序正常运行,实在没出路了才会抛出异常。下图是objc_msgSend调用时,查找SEL的IML的过程。咱们以这个流程为例看看其中涉及的很有用的函数。

 

 

图:运行时查找函数的流程

resolveInstanceMethod函数

原型:

+ (BOOL)resolveInstanceMethod:(SEL)name

这个函数在运行时(runtime),没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。

根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。

实现的例子:

复制代码
//全局函数void dynamicMethodIMP(id self, SEL _cmd){    // implementation ....}@implementation MyTestObject////类函数+ (BOOL) resolveInstanceMethod:(SEL)aSEL{    if (aSEL == @selector(resolveThisMethodDynamically))    {          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");          return YES;    }    return [super resolveInstanceMethod:aSel];}//@end
复制代码

注意事项:

根据Demo实验,这个函数返回的BOOL值系统实现的objc_msgSend函数并没有参考,无论返回什么系统都会尝试再次用SEL找IML,如果找到函数实现则执行函数。如果找不到继续其他查找流程。

forwardingTargetForSelector:

原型:

- (id)forwardingTargetForSelector:(SEL)aSelector

流程到了这里,系统给了个将这个SEL转给其他对象的机会

返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。

实现示例:

复制代码
//转发目标类@interface NoneClass : NSObject@end@implementation NoneClass+(void)load{    NSLog(@"NoneClass _cmd: %@", NSStringFromSelector(_cmd));}- (void) noneClassMethod{    NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));}@end@implementation MyTestObject////将消息转出某对象- (id)forwardingTargetForSelector:(SEL)aSelector{    NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));    NoneClass *none = [[NoneClass alloc] init];    if ([none respondsToSelector: aSelector]) {        return none;    }        return [super forwardingTargetForSelector: aSelector];}//@end
复制代码

当执行MyTestObject对象执行[myTestObject nonClassMethod]函数时,消息会抛到NoneClass对象中执行。

 

methodSignatureForSelector:

原型:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

这个函数和后面的forwardInvocation:是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行

forwardInvocation:

原型:

- (void)forwardInvocation:(NSInvocation *)anInvocation

真正执行从methodSignatureForSelector:返回的NSMethodSignature。在这个函数里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(forwardingTargetForSelector只能以Selector的形式转向一个对象)

下面这个示例代码,诠释了这种实现优势:

复制代码
#import <Foundation/Foundation.h> @interface Book : NSObject{    NSMutableDictionary *data;}//声明了两个setter/getter@property (retain) NSString *title; @property (retain) NSString *author;@end @implementation Book@dynamic title, author; //不自动生成实现 - (id)init{    if ((self = [super init])) {        data = [[NSMutableDictionary alloc] init];        [data setObject:@"Tom Sawyer" forKey:@"title"];        [data setObject:@"Mark Twain" forKey:@"author"];    }    return self;} - (void)dealloc{    [data release];    [super dealloc];} - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector{    NSString *sel = NSStringFromSelector(selector);    if ([sel rangeOfString:@"set"].location == 0) {        //动态造一个 setter函数        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];    } else {        //动态造一个 getter函数        return [NSMethodSignature signatureWithObjCTypes:"@@:"];    }} - (void)forwardInvocation:(NSInvocation *)invocation{    //拿到函数名    NSString *key = NSStringFromSelector([invocation selector]);    if ([key rangeOfString:@"set"].location == 0) {        //setter函数形如 setXXX: 拆掉 set和冒号         key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];        NSString *obj;        //从参数列表中找到值        [invocation getArgument:&obj atIndex:2];        [data setObject:obj forKey:key];    } else {        //getter函数就相对简单了,直接把函数名做 key就好了。        NSString *obj = [data objectForKey:key];        [invocation setReturnValue:&obj];    }} @end
复制代码

doesNotRecognizeSelector:

原型:

- (void)doesNotRecognizeSelector:(SEL)aSelector

作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。

虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。

使用场景

在一个函数找不到时,Objective-C提供了三种方式去补救:

1、调用resolveInstanceMethod给个机会让添加这个实现这个函数

2、调用forwardingTargetForSelector让别的对象去执行这个函数

3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。

文章参考

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/

https://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html

http://blog.csdn.net/yiyaaixuexi/article/details/8970734

 

补充:respondsToSelector

原型:

+ (BOOL)respondsToSelector:(SEL)aSelector

这个函数大家再熟悉不过了,用来检查对象是否实现了某函数。

此函数通常是不需要重载的,但是在动态实现了查找过程后,需要重载此函数让对外接口查找动态实现函数的时候返回YES,保证对外接口的行为统一。

示例代码(接forwardInvocation的例子):

复制代码
@implementation Book//- (BOOL) respondsToSelector:(SEL)aSelector{    if (@selector(setTitle:) == aSelector ||        @selector(title) == aSelector ||        @selector(setAuthor:) == aSelector ||        @selector(author) == aSelector)    {        return YES;    }        return [super respondsToSelector: aSelector];} //@end
复制代码

 

参考:

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/

0 0
原创粉丝点击