漫谈Objective-C在语法上的改进

Objective-C 2.0从2006年正式发布至今已经有10年了。Apple在此期间也不断地为其注入新的语法特性,比如Blocks、NSNumber literal、NSArray literal、NSDictionary literal、@() compund literal、Object subscripting、instancetype、lightweight generics等等。然而,其核心语法变化不大。

本人从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比较令人不快的特性以及其改进建议以及新增若干语法特性。

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(parameter-list)

上述代码可写为:

NSString *str = NSString.alloc().initWithFormat(@"%d", 100).autorelease();

整个调用过程就显得更为清晰。而之前方法的声明与定义形式依旧不变,所以,如果方法里有多于1个形参,那么后续形参也需要带有自己的标签。比如:

    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSString *path = [[fileMgr URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:NULL] path];
    NSLog(@"The path is: %@", path);

// 可写为以下形式:
NSFileManager *fileMgr = NSFileManager.defaultManager();
NSString *path = fileMgr.URLForDirectory(NSCachesDirectory, inDomain:NSUserDomainMask, appropriateForURL:nil, create:NO, error:NULL).path();

这看上去与Swift的方法调用差不多。

那么现在估计各位可能会有这么一个问题,使用了点语法作为消息发送(方法调用)之后,如何与property加以区分呢?

如果 . 操作符后面的标识符后面没有圆括号,说明使用的是property,如果带有括号,说明使用的是方法。比如:

NSUInteger len = @"Hello".length;    // 这里使用的是property

NSUInteger len = @"Hello".length();  // 这里显式地调用property的getter方法,相当于[@"Hello" length]

此外,由于Objective-C已经有了强大灵活的消息机制,所以它不需要再包含类似于C++中指向类成员函数指针这种特性,所以用点语法其实完全能行得通,而且也能与现有的C/C++的语法完全兼容。

2、类静态成员变量与类property:

我们知道,现在Objective-C类有类方法,但是木有类属性。其实,加入类成员变量和类属性不仅能使得Objective-C在语法体系上更加完备,而且也能提供更好的模块化编程形式。

类静态成员变量非常简单,只需要在@interface或@implementation的 { }作用域中使用static存储类说明符即可。比如:

@interface MyObject : NSObject
{
@public

    int n;         // 对象成员变量

    static int s;  // 类静态成员变量
}

@end

对类静态成员变量的访问也是通过 -> 操作符。比如:

MyObject *obj = MyObject().alloc().init();
int a = obj->n;    // 访问对象成员
a += MyObject->s;  // 访问类静态成员

而要定义类property的方法也同样简单,跟定义对象property一样,只是在前面添加 + 号。比如:

@interface MyObject : NSObject
{
@public

    int n;          // 对象成员变量

    static int  s;  // 类静态成员变量
}

- @property (nonatomic, retain) NSString *objStr;      // 定义了对象property
+ @property (nonatomic, retain) NSString *classStr;    // 定义了类property

@end

在synthesize的时候,对象property与类property需要分开。在做类property的synthesize的时候,前面也是加 + 号进行指定。比如:

@implementation MyObject

- @synthesize objStr;      // 综合对象property
+ @synthesize classStr;    // 综合类property

@end

这么做可以使得对象property与类property可以有完全相同的属性名。上述代码片段中,@synthesize objStr相当于编译器给MyObject类定义了 - (void)setObjStr:(NSString*, retain)str与- (NSString*)objStr这一对setter和getter方法,同时为MyObject增添了私有对象成员变量NSString*objStr。而@synthesize classStr相当于编译器给MyObject类自动定义了+ (void)setClassStr:(NSString*, retain)str与+ (NSString*)classStr这一对setter和getter方法,同时为MyObject增添了私有类静态成员变量NSString *classStr。

这里要再提的一点是,引入了类静态property之后,实例property可以在@property前加 - 号,@synthesize之前也可加 - 号。否则对于某些星座的人来说,看代码没对齐难受得慌,呼哈哈~如果 - 缺省,那么默认为实例property。

下面我简单介绍一下类静态成员变量与类属性的实现方式。因为Objective-C完全基于C语言,可以看作是C语言的一套马甲~所以我们完全可以把Objective-C代码的编译过程理解为先将源代码完全翻译成C代码,然后再对C代码进行编译。所以,这里对于新增语法的特性也不能违背此重要原则。对于Objective-C编译器如果遇到了类静态成员以及/或类属性,那么可以把此类定义为一个全局外部对象,对象名可以诸如_objc_class_<class name>_<category name>。然后,runtime库也需要增加对类属性的访问接口。

3、带方法形式说明的SEL类型

我们在使用Objective-C时,常常会发现SEL类型参数没有具体说明,此时我们就可能需要进一步查找各种文档资料确定这个selector传多少个参数,每个参数需要是什么类型的。在Objective-C 3.0中,我们将给SEL加上方法类型说明,包括方法的返回类型以及参数类型。比如,我们常用的用于定时器消息回调的参数可以写作为:

SEL<void* (NSTimer*)>

这里之所以使用<>是因为,一来这样可以跟函数参数的圆括号进行明显区分开;二来这种表达更类似protocol,表示这个selector实参需要满足<>内的返回类型与参数列表形式。

以上就是我目前所想到的内容。各位感觉何如呢?呼呼~