Objective-C 2.0从2006年正式发布至今已经有8年了。Apple在此期间也不断地为其注入新的语法特性,比如Blocks、NSNumber literal、NSNumber literal、NSArray literal、NSDictionary literal、Object subscripting等等。然而,其核心语法变化不大。
本人从2009年夏季开始接触Objective-C,一开始总是不习惯其[object message]这种语法形式,不过随着Xcode自身智能感知的不断加强,编辑也逐步方便,所以也渐渐地习惯了,呵呵~现在用下来,感觉Objective-C在面向对象编程方面有其独到之处,而且灵活性相当大,编译器负担也很小,增加的仅仅是运行时库。不过相对于Java这种需要虚拟机的运行时来说要小得多。因此,它是对C++与Java的折衷。C++太过依赖编译器,Java的运行时太过庞杂,而且这两种编程语言对于类型的限制太过重,导致原本一些本应该十分灵活强大的语法特性也受到很多制约。所以,本人也时常跟朋友、同事之间开玩笑地说,“用C++来做项目,总是先考虑其各种语法糖以及各种坑,而往往不能开门见山地进行软件设计”。而Java新增的很多语法也是比较鸡肋,比如泛型就是其中之一,还有Java 8所引入的Lambda表达式。这货跟Java 1.4所引入的匿名类对象没太大不同,反而是Method Reference更有用些,呼呼~
当然,Objective-C也不能说完全没有缺陷,下面我就谈谈目前Objective-C比较令人不快的特性以及其改进建议。当然,各位请注意,以下所谈及的改进语法特性是在建立在当前Objective-C 2.0语法体系结构之上的新增语法。也就是说,如果这些语法机制被GCC以及/或Clang接受,那么程序员既可以用现有的语法机制,也可以使用俺所新增的语法机制,所以这并不会完全代替现存的语法表达方式。
1、消息发送机制:
一开始,Brad Cox引入[object message]的机制确实不错,这个对已有的C语言语法没有任何冲突,因此Objective-C至今对于C/C++的兼容性都能做到100%。不过,这种写法对于编写代码来说有个很大的弊端——当嵌套的方法调用过多时,由于需要往前加[,因此,对于使用不带智能感知的编辑器来说就相当讨厌了。比如:
NSString *str = [[[NSString alloc] initWithFormat:@"%d", 100] autorelease];
上述代码嵌套了多层方法调用。但是对于程序员来说,写代码时都是按照线性思维进行的。也就是说,我一开始总是会想到先分配一个NSString对象,然后调用init初始化方法对其初始化,最后想到用autorelease方法来省去后面手工release的麻烦。而用[]机制,那么你每写好一个方法调用就需要回过去加[,这显然十分麻烦~
而本人这里所提供的改进意见是,使用 <| 这个操作符(operator)来表示消息发送机制:
object <| (message)
表示将message发送给object。本来想用<:这个符号作为消息发送的操作符,因为它离<更近,所以打起来也更方便,呼呼~但是在C语言标准里已经将此双字符符号作为特殊字符[来使用,所以比较遗憾,只能使用离<更远点的|符号了。
上述代码可写为:
NSString *str = NSString <| (alloc) <| (initWithFormat:@"%d", 100) <| (autorelease);
整个调用过程就显得更为清晰。为何方法调用需要用()包裹?我们就看上述例子,如果没有括号,那么initWithFormat:@"%d", 100 <| autorelease直接就会导致歧义。由于<:的优先级与C语言中的 . 操作符、-> 操作符一样,都是左结合的。因此,如果这里没有括号,加上逗号优先级是最小的,那么词法解析器就会解析为 100 <| autorelease,显然不是我们所想要的。
这里,(message)是一个整体,括号里不能放其它东西,比如:(a++, alloc)这种就是非法表达式。当然,我们可以这么写:
NSUInteger a = 10; a = (a++, @"Hello" <| (length));
这样,就相当于先做a++操作,然后把@"Hello"的长度给a。
2、取代[NSObject respondsToSelector:]:
有了上述的object <| (message)机制,那么我们就完全可以扩展 <|,引入 <? 操作符来取代原本NSObject里的respondsToSelector:方法以及instancesRespondToSelector:方法。
<? 操作符声明为:id <? SEL
其中,id是一个指向Objective-C类对象的引用,也可以是Objective-C类;SEL则是一个selector的引用。
比如:
/** 以下是当前Objective-C的使用方式 */ BOOL result = [NSString respondsToSelector:@selector(stringWithString:)]; result = [NSString instancesRespondToSelector:@selector(length)]; result = [@"Hello" respondsToSelector:@selector(length)]; /** 以下是建议所使用的新的方式 */ int result = NSString <? @selector(stringWithString:); // 返回1 result = NSString <? @selector(length); // 返回-1,表示仅其实例对象才能响应此消息 result = @"Hello" <? @selector(length); // 返回1 result = @"Hello" <? @selector(stringWithString:); // 返回-1,表示仅此实例对象所属的类才能响应此消息 result = @"Hello" <? @selector(hello); // 返回0,压根不能响应此消息
3、取代[NSObject performSelector]:
Objective-C中最最灵活,最最强大的功能就是消息转发机制,而这其中就是通过NSObject类中的performSelector方法来实现的。也就是说,消息转发机制其实是一个不折不扣的运行时操作。只需要一个消息签名(signature)和发送目标即可。因此,Objective-C中的消息(即selector)与C++的指向成员函数的指针以及Java 8中所引入的Method Reference都不同。后两者都有严格的类型检查,并且需要指定方法所属的类。而Objective-C都没有这些限制,只需要特定的消息签名即可。因此,Objective-C的消息转发看重的是消息本身,而不关心这条消息所属的类以及消息发送者等。而对于发送目标,如果程序员不确定它是否能获得响应,则可以通过调用respondsToSelector:方法进行判断。
不过,通过NSObject的performSelector进行操作仍然有一个不方便之处,就是其返回值。由于这消息完全是运行时的操作,因此要判定返回状态就会麻烦些。在当前的Objective-C中,若想获得执行selector之后的返回值,倘若不是id类型,也不是int这种基本类型,那么只能借助NSInvocation,比如:
NSNumber *num = @10.25; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[NSNumber class] instanceMethodSignatureForSelector:@selector(doubleValue)]]; [invocation setTarget:num]; [invocation setSelector:@selector(doubleValue)]; [invocation invoke]; double d; [invocation getReturnValue:&d]; NSLog(@"The double value is: %f", d);
我们看到上述代码,非常繁琐,如果你想直接通过[num performSelector:@selector(doubleValue)];肯定是行不通的。因为,整个performSelector方法调用将返回1。
然而,通过NSInvlocation还是可以获得正确结果的,因此并不是说消息转发机制就不能正确获得返回值了。这里,我们将借助一点编译时的特性来更方便地执行消息转发。我这里将引入一个新的Objective-C关键字——@forward
用法:object <| @forward(SEL, <return type>, ...)
其中,...表示<variable arguments>。用于传递selector方法的实参。
比如: @"hello" <| @forward(@selector(length), NSUInteger);
这里,我们再度使用了 <| 操作符。而这次与直接的消息发送不同,由于selector的具体签名在这里可能是不知道的(SEL仅仅是一个指针而已),因此它其实也是一个运行时动作,不过object <| @forward()整个表达式最终返回的是<return type>类型,因此能够获得任意类型的返回结果。当然,@forward()本身仍然是一条消息,与(message)一样,不具有任何返回类型,且不能脱离对象单独使用。下面举个更为详细的例子:
NSString *str = @"abcd"; // 直接消息发送 NSComparisonResult result = str <| (compare:@"AbCd" options:NSCaseInsensitiveSearch); SEL aSel = @selector(compare:options:); // 通过消息转发 result = str <| @forward(aSel, NSComparisonResult, @"aBcD", NSCaseInsensitiveSearch);
当然,这里的<return type>所对应的参数也可以写typeof()表达式。不过,这里为了代码编写简单,也就不需要显式地去写typeof()了。
这样,编译器就能根据返回类型来正确处理返回结果了。当然,在实现上仍然可以借助当前运行时库的objc_msgSend、objc_msgSend_fp2ret、objc_msgSend_stret等函数。不过像NSObject的performSelector:withObject:afterDelay:等具有延迟或在指定线程上执行的消息则不能使用此语法,而只能使用NSObject提供的这类方法进行执行。当然,它们的返回类型都是void。
以上就是我目前所想到的内容。各位感觉何如呢?呼呼~