写点什么

WWDC2012:Objective-C 的新特性

2012 年 7 月 11 日

接着上一篇文章说,Objective-C 在计算机编程语言中有悠久的历史,80 年代初Brad Cox 和Tom Love 发明了Objective-C,1988 年乔布斯的Next 公司获得了这门编程语言语言的授权,并开发出了Objective-C 的语言库和NEXTSTEP 的开发环境。NextStep 是以Mach 和BSD 为基础,Objective-C 是其语言和运行库。乔布斯回归苹果,NextStep 成了Max OS X 的基础,Objective-C 成了Apple 的当家语言,现在基本上是Apple 在维护Objective-C 的发展。与其他面向对象语言相比,Objective-C 采用了一个非常小的运行时库代替了虚拟机。

在苹果的AppStore 推出之前,Objective-C 一直相对小众,但是其优秀的语言特性似乎一直在为后面的爆发积蓄力量,当苹果平台级的应用出现之后,Objective-C 开始大放异彩,静态语言的效率和动态语言的特性得到众多程序员的喜爱,这一点在TIOBE 语言排行榜上表现的非常明显。2007 年iPhone 刚推出的时候,Objective-C 在TIOBE 上排名45,2011 年排名第5,2012 年7 月排名第3,成功超越了老牌编程语言C++,排在Objective-C 前面的是经典语言C、Java。

2007 年之前 Objective-C 小众是因为它只能开发苹果的操作系统和软件,后来之所以流行恰恰也是因为这一点,这不仅让人感慨这无常的人生。不过有一点是没有变,Objective-C 一直是一门优秀的编程语言,而且它一直在发展。从 1.0 到 2.0,从面向对象的 C 语言扩展,到内存引用计数管理,属性管理,引入块的概念,实现自动引用计数等等,几乎每年都会有新特性加入,这在其他编程语言中是很少见的,这些新特性的引入也不断为开发人员带来软件设计和研发上的便利。

不过对于编程语言之争,一直是程序员之间的一个槽点,大家没事就吐啊吐啊,你说一门语言优秀,那肯定有一帮人跳出来说这门语言烂。我的观点是,编程语言确实有设计思想、语法和效率上的优劣之分,尽可能去使用那些你觉得优秀的语言,这会帮助你发挥自己和这门语言的潜力。用它,就去发现它的优点,把一门编程语言发挥到极限,你也就离牛人不远了。

今年 2012 的 WWDC 上,苹果针对 Objective-C 和 LLVM 编译器都做了重大改进,我们来看一看 Objective-C 增加了哪些新特性。

1、方法顺序无关

Objective-C 类由声明文件 h 和实现文件 m 组成,所有的 public 方法都在 h 文件中声明,private 方法可以写在 m 文件中,但是在早期的编译环境中需要注意方法的顺序,例如下面的代码,在早期的编译环境会给出警告:

类和方法声明:

复制代码
@interface ObjcNewFeatures : NSObject
-(void)doSomething:(NSString *) text;
@end

实现:

复制代码
@implementation ObjcNewFeatures
-(void)doSomething:(NSString *)text{
    NSLog(@"%@", [text stringByAppendingFormat:[selfgetCode]]);
}
-(NSString *)getCode{
    return@"Unicode";
}
@end

早期编译器编译时会出现:warning: instance method ‘-getCode:’ not found…

这是因为根据编译顺序,编译器不知道在 doSomething 之后还有 getCode 方法,所以会给出警告。解决办法有多种,比如可以把 getCode 方法放到 doSomething 之前,也可以提前声明私有方法,如下: 在 m 文件中增加:

复制代码
@interfaceObjcNewFeatures()
-(NSString *)getCode;
@end

新版编译器在 LLVM 中增加了新特性,改变了顺序编译的方式,首先扫描方法声明,然后再对其实现部分进行编译。这样无论是 public 还是 private 方法,就变得顺序无关了。目前 XCode 的最新版本 4.3.3 采用的默认编译器是 Apple LLVM compiler 3.1,以上代码在最新的编译环境下正常运行。

2、枚举类型的改进

在 OS X v10.5 之前,我们如何在 Objective-C 中定义一个枚举类型呢?如下:

复制代码
typedef enum
    ObjectiveC,
    Java, 
    Ruby, 
    Python, 
    Erlang }
Language;

这种写法简单明了,用起来也不复杂,但是有一个问题,就是其枚举值的数据范围是模糊的,这个数值可能非常大,可能是负数,无法界定。

在 OS X v10.5 之后和 iOS 中,你可以这样写:

复制代码
enum {
    ObjectiveC,
    Java,
    Ruby,
    Python,
    Erlang
};
typedef NSUInteger Language;

这种写法的好处是,首先这个枚举的数据类型是确定的,无符号整数。其次由于我们采用了 NSUInteger,可以不用考虑 32 位和 64 位的问题。带来的问题是数据类型和枚举常量没有显式的关联。

在 XCode4.4 中,你可以这样写枚举了:

复制代码
typedef enum Language : NSUInteger{
    ObjectiveC,
    Java, 
    Ruby, 
    Python, 
    Erlang 
}Language;

在列出枚举内容的同时绑定了枚举数据类型 NSUInteger,这样带来的好处是增强的类型检查和更好的代码可读性。

当然,对于普通开发这来说,枚举类型可能不会涉及到复杂的数据,使用之前的两种写法也不会有什么大问题。无论如何,在 XCode4.4 发布之后,我们就可以尝试采用新的写法了。

3、属性合成

每个开发人员对 property 都很熟悉,我们需要为类定义属性,编写 getter 和 setter 方法。那么我们在 Objective-C 中是如何进行处理属性呢?很简单,首先在 h 文件中定义属性:

复制代码
@property (strong) NSString *name;

然后在 m 文件中使用 @synthesize 指令实现属性的 accessor 方法和定义实例变量 ivar:

复制代码
@synthesize name = _name;

@synthesize 的含义是,如果没有进行重载的情况下,编译器会根据读写属性自动为类实例变量 _name 生成 getter 和 setter 方法。当然,你也可以用 @dynamic 指令指定该属性的相关方法由开发人员实现。

这样看起来是不是已经很简单了?但是没有最简单只有更简单。在 XCode4.4 中,我们可以省略掉 @synthesize name = _name; 这一行,完全交给编译器去实现。也就是说在 h 文件中声明属性 name 后,就可以直接在实现文件中使用该属性的 getter 和 setter 方法,并使用实例变量 _name。并且编译器会根据属性的可读和可写自动判断是否提供 setter 方法。

那么在这种情况下,如果你声明了 @dynamic 的属性,编译器该如何处理呢?所有 synthesize 相关的特性将不再起作用,你需要自己去实现属性的相关方法。

总接一下属性合成的新特性:

除非明确说明,否则属性相关的 accessor 方法(getter 和 setter)将自动生成。
除非所有的 accessor 方法提供实例变量,否则实例变量(例如 _name)会自动生成。
如果使用了 @synthesize,并没有提供实力变量名的话,会自动生成。
如果使用了 @dynamic,那么自动合成无效,需要开发者自己实现。
Core Data 的 NSManagedObject 及其子类不使用默认的属性合成功能。

4、语法简化

很多刚从其他编程语言转到 Objective-C 的同学看到长长的函数名会感到崩溃,不过我在上一篇文章中也提到过,这种语法让消息的传递像一个英语句子,大大增强了可读性。比如你想初始化一个浮点数,需要这么写:

复制代码
NSNumber value = [NSNumber numberWithFloat:123.45f];

从这句中我们能够明确的知道代码的含义,但是,是否连简单的赋值语句也要这么处理呢?苹果在本次新特性中采用了折中的处理方式,针对很多基础类型采用了简写的方式,实现语法简化。简化以后,我们会发现从语法层面,这些简化的 Objective-C 更像 Python 和 Ruby 等动态语言的语法了。下面我们逐一介绍:

NSNumber

简化前的写法:

复制代码
NSNumber *value;
value = [NSNumber numberWithInt:12345];
value = [NSNumber numberWithFloat:123.45f];
value = [NSNumber numberWithDouble:123.45];
value = [NSNumber numberWithBool:YES];

简化后的写法:

复制代码
NSNumber *value;
value = @12345;
value = @123.45f;
value = @123.45;
value = @YES;

装箱表达式也可以采用类似的写法:

复制代码
NSNumber *piOverSixteen = [NSNumber numberWithDouble: ( M_PI / 16 )];
NSString *path = [NSString stringWithUTF8String: getenv("PATH")];

可以分别简写为:

复制代码
NSNumber *piOverSixteen = @( M_PI / 16 );
NSString *path = @( getenv("PATH") );

对于字符串表达式来说,需要注意的是表达式的值一定不能是 NULL,否则会抛出异常。

NSArray

对于 NSArray 的初始化来说,有非常多的写法,这里就不再一一罗列,我们直接看新的写法

复制代码
NSArray *array;
array = @[];               // 空数组
array = @[ a ];          // 一个对象的数组
array = @[ a, b, c ]; // 多个对象的数组

非常简单,再也不用记住初始化多个对象的数组时,后面还要跟一个倒霉的 nil。 现在我们看一下当声明多对象的数组时,编译器是如何处理的:

复制代码
array = @[ a, b, c ];
编译器生成的代码:
id objects[] = { a, b, c };
NSUInteger count = sizeof(objects)/ sizeof(id);
array = [NSArray arrayWithObjects:objects count:count];

好吧,编译器已经为我们把这些简单重复的工作都做了,我们就可以安心解决真正的问题了:)不过有一点要注意,如果 a,b,c 对象有 nil 的话,运行时会抛出异常,这点和原来的处理方式不同,编码的时候要多加小心。

NSDictionary

同样,对于字典这个数据结构来说,有很多种初始化的方式,我们来看新的写法:

复制代码
NSDictionary *dict;
dict = @{};     // 空字典
dict = @{ k1 : o1 };     // 包含一个键值对的字典
dict = @{ k1 : o1, k2 : o2, k3 : o3 }; // 包含多个键值对的字典

最后我们总接一下容器类数据结构简化的限制: 采用上述写法构建的容器都是不可变的,如果需要生成可变容器,可以传递 -mutableCopy 消息。例如

复制代码
NSMutableArray *mutablePlanets = [@[
   @"Mercury", @"Venus", @"Earth",
   @"Mars", @"Jupiter", @"Saturn",
   @"Uranus", @"Neptune"
 ] mutableCopy];
 

不能对常量数组直接赋值,解决办法是在类方法 (void)initialize 进行赋值,如下:

复制代码
@implementation MyClass
static NSArray *thePlanets;
+ (void)initialize {
  if (self == [MyClass class]) {
    thePlanets = @[
      @"Mercury", @"Venus", @"Earth",
      @"Mars", @"Jupiter", @"Saturn",
      @"Uranus", @"Neptune"
    ];
} }

没有常量字典

5、对象下标

容器的语法简化让我们不难想到,可以通过下标的方式存取数组和字典的数据。 比如对于数组:

复制代码
NSArray *array = @[ a, b, c ];

我们可以这样写:

复制代码
id obj = array[i];     // 通过下标方式获取数组对象,替换原有写法:array objectAtIndex:i];
array[i] = newObj;     // 也可以直接为数组对象赋值。替换原有写法:[array replaceObjectAtIndex:i withObject:newObj];

对于字典:

复制代码
NSDictionary *dict = @{ k1 : o1, k2 : o2, k3 : o3 };

我们可以这样写:

复制代码
id obj = dict[k2];     // 获取 o2 对象,替换原有写法:[dic objectForKey:k2];
dic[k2] = newObj;  // 重新为键为 k2 的对象赋值,替换原有写法:[dic setObject:newObj forKey:k2]

同时,我们自己定义的容器类,只要实现了规定的下标方法,就可以采用下标的方式访问数据。要实现的方法如下:

数组类型的下标方法

复制代码
- (elementType)objectAtIndexedSubscript:(indexType)idx; 
- (void)setObject:(elementType)object atIndexedSubscript:(indexType)idx; 
字典类型的下标方法
- (elementType)objectForKeyedSubscript:(keyType)key; 
- (void)setObject:(elementType)object forKeyedSubscript:(keyType)key;

其中需要注意的是 indexType 必须是整数,elementType 和 keyType 必须是对象指针。

总结

关于语言的新特性这次就介绍到这里,使用新版的 Objective-C,你就可以获得这些新特性,编写更简洁的代码,同时避免一些常见的陷阱。One more thing,这些语法特性是完全向下兼容,使用新特性编写出来的代码,经过编译后形成的二进制程序可以运行在之前发布的任何 OS 中。

由于苹果公司 Mac、iPhone 和 iPad 销量的突飞猛进,App Store 上的应用形成了一个庞大的 App 帝国,这个完整的生态圈同时又有力的推进了开发语言 Objective-C 的高速发展,这门语言既保持了面向对象、消息传递和动态语言的特性,同时也在不断的做减法,语法的简化会大大提升开发者的效率,让开发者的精力更多的放在创意和 App 的实现上。

每一门语言的大放异彩都离不开其特定的环境土壤,一如当年的 Lisp,Smalltalk,C,C++,Java,C#,好吧,现在是——Objective-C。

相关文章: Objective-C——消息、Category 和 Protocol


感谢崔康对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 7 月 11 日 06:477457

评论 2 条评论

发布
暂无评论
发现更多内容

7个获取访问者真实IP的方法,速学!!!

华为云开发者社区

nginx 前端 网站 IP 服务器

主宰操作系统的经典算法

cxuan

后端 操作系统

API接口限流

Bruce Duan

分布式限流 单体限流 限流算法

Kafka两个高性价比的参数调优

大数据学徒

Java 大数据 kafka

week8 作业

Shawn

ARTS 第 5 周

乌拉里

Redis系列(七):缓存只是读写回种这么简单吗?如果是,那么请你一定看看这篇文章!

z小赵

redis 分布式 高并发系统设计

架构师那些不能碰的禁忌

曲水流觞TechRill

架构师

国产开源流媒体SRS4.0对视频监控GB28181的支持

潇湘落木

音视频 云直播 短视频 流媒体

华为云FusionInsight MRS融合大数据平台进阶之路

FI洞见

大数据 新特性 FusionInsight 华为云 智能数据湖

推荐一款技术人必备的接口测试神器:Apifox

狂师

测试 测试驱动开发实战营 接口测试 测试框架

节约60%成本!虎牙直播云端大数据是怎么做到的?

小小的一朵云

一个好用的工作生活平衡方式

泰稳@极客邦科技

[POJ 1002] 487-3279 C++解题报告

一直AC一直爽

POJ ACM

正则表达式基础详解

懒猫

Java 正则表达式 前端 前端开发 正则

Go: 通过代码学习 Map 的设计 — Part II

陈思敏捷

go golang map

LeetCode 1052. Grumpy Bookstore Owner

liu_liu

算法 LeetCode

职场求生攻略答疑篇之 1 —— 加班沉思录

臧萌

程序员 加班

架构师训练营第八周作业

张明森

干货分享丨玩转物联网IoTDA服务系列四-智能网关

华为云开发者社区

物联网 智能设备 应用场景 华为云 mqtt

Nginx 限流配置

Bruce Duan

Nginx限流

一文带你了解Zookeeper所有核心概念

小隐乐乐

zookeeper 分布式 分布式架构

上海首批金融科技“监管沙盒”应用名单出炉 区块链技术备受青睐

CECBC区块链专委会

金融科技 金融监管 创新与安全 智能多元化

IO系列——UNIX五种IO模型

Java联盟

io 多路复用 异步IO

HTTPS详解

Bruce Duan

https 对称加密 非对称加密

实战案例丨ModelArts在数据标注、数据过滤上的应用技巧:自动分组

华为云开发者社区

人工智能 数据 图像识别 图片 分类

一文了解JDK12 13 14 GC调优秘籍-附PDF下载

程序那些事

GC JDK14 秘籍 JDK12 JDK13

Linux服务器存在某进程CPU过高如何追溯其问题根源?

Nick

Java Linux centos

如何消灭飞机的“黑色十分钟”,AI来帮忙

华为云开发者社区

华为 AI 智能时代 模型 华为云

Spring Boot + Vue前后端分离项目,Maven自动打包整合

xcbeyond

maven 前后端分离 springboot 部署

Java架构-Java代码规范那些事

我是苞谷

Java

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

WWDC2012:Objective-C的新特性-InfoQ