Overview

自 WWDC 2015 推出和开源 Swift 2.0 后,大家对 Swift 的热情又一次高涨起来,在羡慕创业公司的朋友们大谈
Swift 新特性的同时,也有很多像我一样工作上依然需要坚守着 Objective-C 语言的开发者们。今年的 WWDC 中介绍了几个
Objective-C 语言的新特性,还是在“与 Swift 协同工作”这种 Topic
里讲的,越发凸显这门语言的边缘化了,不过有新特性还是极好的,接下来,本文将介绍下面三个主要的新特性:

  • Nullability

  • Lightweight Generics *

  • __kindof

Nullability

然而 Nullability 并不算新特性了,从上一个版本的 llvm 6.1 (Xcode 6.3) 就已经支持。这个简版的
Optional ,没有 Swift 中 ? 和 ! 语法糖的支持,在 Objective-C 中就显得非常啰嗦了:


@property (nonatomic, strong, nonnull) Sark *sark;
@property (nonatomic, copy, readonly, nullable) NSArray *friends;
+ (nullable NSString *)friendWithName:(nonnull NSString *)name;

假如用来修饰一个变量,前面还要加双下划线,放到
block 里面就更加诡异,比如一个 Request 的 start 方法可以写成:


- (void)startWithCompletionBlock:(nullable void (^)(NSError * __nullable error))block;

除了这俩外,还有个 null_resettable 来表示
setter nullable,但是 getter nonnull,绕死了,最直观例子就是 UIViewController 中的
view 属性:


@property (null_resettable, nonatomic, strong) UIView *view;

它可以被设成 nil,但是调用 getter 时会触发 -loadView 从而创建并返回一个非 nil 的 view。

从 iOS9 SDK 中可以发现,头文件中所有 API 都已经增加了 Nullability 相关修饰符,想了解这个特性的用法,翻几个系统头文件就差不离了。接口中
nullable 的是少数,所以为了防止写一大堆 nonnull,Foundation 还提供了一对儿宏,包在里面的对象默认加
nonnull 修饰符,只需要把
nullable 的指出来就行,黑话叫 Audited Regions:


NS_ASSUME_NONNULL_BEGIN
@interface Sark : NSObject
@property (nonatomic, copy, nullable) NSString *workingCompany;
@property (nonatomic, copy) NSArray *friends;
- (nullable NSString *)gayFriend;
@end
NS_ASSUME_NONNULL_END

Nullability 在编译器层面提供了空值的类型检查,在类型不符时给出
warning,方便开发者第一时间发现潜在问题。不过我想更大的意义在于能够更加清楚的描述接口,是主调者和被调者间的一个协议,比多少句文档描述都来得清晰,打个比方:


+ (nullable instancetype)URLWithString:(NSString *)URLString;

NSURL 的这个 API 前面加了 nullable 后,更加显式的指出了这个接口可能因为 URLString
的格式错误而创建失败,使用时自然而然的就考虑到了判空处理。

不仅是属性和方法中的对象,对于局部的对象、甚至
c 指针都可以用带双下划线的修饰符,可以理解成能用
const 关键字的地方都能用 Nullability。

所以 Nullability 总的来说就是,写着丑B,用着舒服 - -

Lightweight
Generics

Lightweight Generics 轻量级泛型,轻量是因为这是个纯编译器的语法支持(llvm 7.0),和
Nullability 一样,没有借助任何 objc runtime 的升级,也就是说,这个新语法在 Xcode 7
上可以使用且完全向下兼容(更低的 iOS 版本)

带泛型的容器

这无疑是本次最重大的改进,有了泛型后终于可以指定容器类中对象的类型了:


NSArray *strings = @[@"sun", @"yuan"];
NSDictionary *mapping = @{@"a": @1, @"b": @2};

返回值的 id 被替换成具体的类型后,令人感动的代码提示也出来了:

假如向泛型容器中加入错误的对象,编译器会不开心的:

系统中常用的一系列容器类型都增加了泛型支持,甚至连 NSEnumerator 都支持了,这是非常 Nice 的改进。和
Nullability 一样,我认为最大的意义还是丰富了接口描述信息,对比下面两种写法:


@property (readonly) NSArray *imageURLs;
@property (readonly) NSArray *imageURLs;

不用多想就清楚下面的数组中存的是什么,避免了 NSString 和 NSURL 的混乱。

自定义泛型类

比起使用系统的泛型容器,更好玩的是自定义一个泛型类,目前这里还没什么文档,但拦不住我们写测试代码,假设我们要自定义一个 Stack
容器类:


@interface Stack : NSObject
- (void)pushObject:(ObjectType)object;
- (ObjectType)popObject;
@property (nonatomic, readonly) NSArray *allObjects;
@end

这个 ObjectType 是传入类型的 placeholder,它只能在 @interface
上定义(类声明、类扩展、Category),如果你喜欢用 T 表示也 ok,这个类型在 @interface 和 @end
区间的作用域有效,可以把它作为入参、出参、甚至内部 NSArray 属性的泛型类型,应该说一切都是符合预期的。我们还可以给
ObjectType 增加类型限制,比如:


// 只接受 NSNumber * 的泛型
@interface Stack : NSObject
// 只接受满足 NSCopying 协议的泛型
@interface Stack : NSObject

若什么都不加,表示接受任意类型 ( id );当类型不满足时编译器将产生 error。

实例化一个 Stack,一切工作正常:

对于多参数的泛型,用逗号隔开,其他都一样,可以参考 NSDictionary 的头文件。

协变性和逆变性

当类支持泛型后,它们的 Type 发生了变化,比如下面三个对象看上去都是 Stack,但实际上属于三个 Type:


Stack *stack; // Stack *
Stack *stringStack; // StackStack *mutableStringStack; // Stack

当其中两种类型做类型转化时,编译器需要知道哪些转化是允许的,哪些是禁止的,比如,默认情况下:

我们可以看到,不指定泛型类型的 Stack
可以和任意泛型类型转化,但指定了泛型类型后,两个不同类型间是不可以强转的,假如你希望主动控制转化关系,就需要使用泛型的协变性逆变性修饰符了:

__covariant - 协变性,子类型可以强转到父类型(里氏替换原则)

__contravariant - 逆变性,父类型可以强转到子类型(WTF?)

协变:


@interface Stack : NSObject

效果:

逆变:


@interface Stack : NSObject

效果:

协变是非常好理解的,像 NSArray 的泛型就用了协变的修饰符,而逆变我还没有想到有什么实际的使用场景。

__kindof

__kindof 这修饰符还是很实用的,解决了一个长期以来的小痛点,拿原来的
UITableView 的这个方法来说:


- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;

使用时前面基本会使用 UITableViewCell 子类型的指针来接收返回值,所以这个 API
为了让开发者不必每次都蛋疼的写显式强转,把返回值定义成了 id 类型,而这个 API 实际上的意思是返回一个
UITableViewCell 或 UITableViewCell 子类的实例,于是新的 __kindof
关键字解决了这个问题:


- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

既明确表明了返回值,又让使用者不必写强转。再举个带泛型的例子,UIView 的
subviews 属性被修改成了:


@property (nonatomic, readonly, copy) NSArray *subviews;

这样,写下面的代码时就没有任何警告了:


UIButton *button = view.subviews.lastObject;

Where to
go

有了上面介绍的这些新特性以及如 instancetype 这样的历史更新,Objective-C
这门古老语言的类型检测和类型推断终于有所长进,现在不论是接口还是代码中的 id
类型都越来越少,更多潜在的类型错误可以被编译器的静态检查发现。

同时,个人感觉新版的 Xcode
对继承链构造器的检测也加强了,NS_DESIGNATED_INITIALIZER 这个宏并不是新面孔,可以使用它标志出像
Swift 一样的指定构造器和便捷构造器。

最后,附上一段用上了所有新特性的代码,Swift 是发展趋势,如果你暂时依然要写 Objective-C
代码,把所有新特性都用上,或许能让你到新语言的迁移更无痛一点。

References

2015 Objective-C 三大新特性的更多相关文章

  1. Visual Studio 2015速递(3)——ASP.NET 新特性

    系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...

  2. 【转载】2015 Objective-C 三大新特性 | 干货

    Overview 自 WWDC 2015 推出和开源 Swift 2.0 后,大家对 Swift 的热情又一次高涨起来,在羡慕创业公司的朋友们大谈 Swift 新特性的同时,也有很多像我一样工作上依然 ...

  3. 2015 Objective-C 三大新特性

    http://www.cocoachina.com/ios/20150617/12148.html Overview 自 WWDC 2015 推出和开源 Swift 2.0 后,大家对 Swift 的 ...

  4. Visual Studio 2015 RC中的ASP.NET新特性和问题修正

    (此文章同时发表在本人微信公众号"dotNET每日精华文章") 微软在Build大会上发布了Visual Studio 2015 RC,这也预示着Visual Studio 201 ...

  5. 看一下“Dubbo 2.7”的三大新特性

    Dubbo 2.7.x 作为 Apache 的孵化版本,除了代码优化之外,还新增了许多重磅的新特性,本文将会介绍其中最典型的三个新特性: 一.异步化改造 二.三大中心改造 三.服务治理增强 一.异步支 ...

  6. Visual Studio 2015速递(1)——C#6.0新特性怎么用

    系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...

  7. Atitit mac os 版本 新特性 attilax大总结

    Atitit mac os 版本 新特性 attilax大总结 1. Macos概述1 2. 早期2 2.1. Macintosh OS (系统 1.0)  1984年2 2.2. Mac OS 7. ...

  8. Visual Studio 2015 速递(4)——高级特性之移动开发

    系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...

  9. Atitit j2ee5 jee5 j2ee6 j2ee7 jee6 jee7 新特性

    Atitit j2ee5 jee5 j2ee6 j2ee7 jee6 jee7 新特性 Keyword Java ee5 ,Java ee6,Java ee7  j2ee5 jee5 j2ee6 j2 ...

随机推荐

  1. 项目管理软件系列-Linux一键安装禅道

    linux用一键安装包 简介:本文介绍如何在linux下面使用禅道一键安装包搭建禅道的运行环境. linux一键安装包内置了apache, php, mysql这些应用程序,只需要下载解压缩即可运行禅 ...

  2. c++ public,protected,private

    基类的私有成员被继承后不可见(优先级最高) 公有继承不改变基类成员属性 保护继承(私有继承)把基类成员变为保护成员(私有成员) public 公开的 protected 受保护的 private 私有 ...

  3. C++笔记001:Microsoft Visual Studio 2010软件的安装与建立第一个cpp文件

    原创笔记,转载请注明出处! 点击[关注],关注也是一种美德~ 我学习C++使用软件为Microsoft Visual Studio 2010. 首先,软件的安装包 链接:https://pan.bai ...

  4. Node.js 逐行读取

    逐行读取 稳定性: 2 - 不稳定 使用 require('readline'),可以使用这个模块.逐行读取(Readline)可以逐行读取流(比如process.stdin) 一旦你开启了这个模块, ...

  5. Node.js OS 模块

    Node.js os 模块提供了一些基本的系统操作函数.我们可以通过以下方式引入该模块: var os = require("os") 方法 序号 方法 & 描述 1 os ...

  6. MongoDB 监控

    在你已经安装部署并允许MongoDB服务后,你必须要了解MongoDB的运行情况,并查看MongoDB的性能.这样在大流量得情况下可以很好的应对并保证MongoDB正常运作. MongoDB中提供了m ...

  7. Android源码解析——AsyncTask

    简介 AsyncTask 在Android API 3引入,是为了使UI线程能被正确和容易地使用.它允许你在后台进行一些操作,并且把结果带到UI线程中,而不用自己去操纵Thread或Handler.它 ...

  8. Matlab to OpenCV Mat

    convert Matlab matrix to OpenCV Mat. Support CV_32FC3 only currently. The Code int matlab2opencv(cv: ...

  9. logstash分析日志

    待处理日志格式如下: [totalCount: 298006556, count: 287347623, queryCount: 259027994, exeCount: 28319629, tota ...

  10. Android图表库MPAndroidChart(四)——条形图的绘制过程过程,隐隐约约我看到了套路

    Android图表库MPAndroidChart(四)--条形图的绘制过程过程,隐隐约约我看到了套路 在学习本课程之前我建议先把我之前的博客看完,这样对整体的流程有一个大致的了解 Android图表库 ...