Swift时代下的Objective-C新特性

前言

自从苹果在WWDC2014上推出Swift语言后,国外iOS开发社区纷纷转向苹果推崇的iOS开发语言新贵. 而由于Swift语言存在时间不长,其所对应的iOS开源库远不及Objective-C的开源库多,因此在iOS平台开发语言的过渡中,Objective-C与Swift混编的项目也越来越多,而为了能让Objective-C和Swift更安全地兼容在iOS应用中,苹果不断优化Xcode编译器LLVM,为Objective-C语言增加了不少新特性,使其桥接到Swift代码时更加安全.

接下来就来看看Swift出现之后的这些Objective-C新特性,以及如何更好应用到自己的Objective-C代码中吧(下文都以OC简称)**.

  • Objective-C新增的Nullability声明
  • 数组,字典等集合类的轻量级泛型声明
  • Objective-C 类属性的使用

Objective-C Nullability类型声明

针对引用对象,OC语言都将其可以设置nil,而常常对nil的对象访问和调用方法时会就出现问题,严重地还会直接造成应用Crash; 而Swift为引用对象引入Optional类型的概念,只有Optional类型的对象才能被设置为nil,否则就会编译为类型错误,当对Optional类型的对象为nil值的情况下仍强行访问该对象就会导致程序的Crash,也就是强制解包Opional类型对象值为nil的情况,这也是写Swift代码中要极力避免的.

为了保证OC程序的安全性以及更安全地向Swift进行桥接,OC出现nonnull,_Nonnull和nullable,_Nullable等一系列Nullability关键字,而这也对应在Swift中表示了非Optional和Optional的对象.现在(Xcode 7之后)无论声明属性还是声明变量,使用新增的这几个关键字,就能显示告诉编译器哪个对象可以被赋为nil,哪个不能被赋值为nil.

首先来看看是使用在代码中使用这些关键字的吧.

@interface Employee : NSObject
@property (nonnull, nonatomic, copy)NSString *name;
@property (nullable, nonatomic, copy)NSString *company;

- (void)workInPlace:(NSString * _Nonnull)place;
- (nullable instancetype)initWithName:(NSString * _Nonnull)name company:(NSString * _Nullable)company;
@end

这里nonnull和nullable是针对属性,返回值声明Nullability的关键字,声明时与nonatomic和copy一样都为属性的修饰符;而_Nonnull和_Nullable是针对局部变量, 参数变量来声明时使用的,注意的是该类关键字********声明顺序一定要在指针符号*********的后面********, 否则编译器无法识别该关键字.

这样声明后一旦声明了nonull/_Nullable的属性被赋值为nil时,编译器就给警告提示,告诉开发者哪些变量属性被要求不能传nill.借此,我们可以对明确清楚不会为nil的对象使用该关键字,来可以更加保证程序的安全性以及语言规范.


Swift时代下的Objective-C新特性_第1张图片
screenshot.png

除此之外,属性除了设置nonnull和nullable外,还提供null_resettable关键字,而这个是用来表示该属性在setter时可设为nil,而在getter时是不为nil的,比如UIViewController的View视图就是用这个关键字声明的.当控制器的view被设置为nil时,使用getter方法访问就会触发loadView方法,创建一个视图对象返回.

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

有了Nullability关键字声明后,官方推荐将属性尽可能声明为nonnull,少量根据需要设置为nullable,并且现在系统SDK也是这么做的,只不过将大量属性都添加声明nonnull时显得十分麻烦,官方巧妙地提供一对宏NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END,来表示默认两个宏之间的属性声明都为nonnull,若需要额外声明为nullable的属性直接进行声明为nullable,覆盖宏默认提供的nonnull声明.而使用宏之后对nonnull修饰的属性设置nil时也一样会警告提示的作用.

NS_ASSUME_NONNULL_BEGIN
@interface Employee : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *home;
@property (nonatomic, copy)NSString *skill;
@property (nullable, nonatomic, copy)NSString *company;

- (void)workInPlace:(NSString * _Nonnull)place;
- (nonnull instancetype)initWithName:(NSString * _Nonnull)name company:(NSString * _Nullable)company;
@end
NS_ASSUME_NONNULL_END


集合类的轻量级泛型声明

Xcode 7之后由于编译器LLVM7.0的升级支持,在集合相关的声明和使用上,如NSArray,NSDictionary等提供了轻量泛型的新语法,这样使用集合对象,而从该集合里取出的对象类型也不再是id, 而是具体的对象类型.

//局部变量声明
NSArray *programmer = @[
       [[Employee alloc]initWithName:@"A" company:nil],
       [[Employee alloc]initWithName:@"B" company:nil],
       [[Employee alloc]initWithName:@"C" company:nil],
       [[Employee alloc]initWithName:@"D" company:nil],       
       ];

// 属性声明
@property (nonatomic, strong) NSMutableArray *employees       

由于明确知道了集合内部元素的对象类型,系统还提供了该对象所属类的相关属性和方法的代码提示,这样就方便直接使用该对象.这样一来不但更加明确代码的语义,让开发者看见集合声明就能明白内部的对象类型,而且减少了id出现,避免重复的id类型显示转换操作以及访问id类型对象的安全问题,可谓是一举多得.


Swift时代下的Objective-C新特性_第2张图片
screenshot.png

使用了轻量泛型特性的集合对象在添加其他类型的对象时,就会引起编译器的警告,来提示开发者传的参数类型不一致: 你真的打算把这个对象放入这个集合吗???]


screenshot.png

轻量泛型的语法还提供一个比较有用的修饰符:****__kindof****,在官方SDK中不少用到了这个新修饰符,首先看看系统是如何声明使用它的.

// UIView.h
@property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *subviews;
- (nullable __kindof UIView *)viewWithTag:(NSInteger)tag;

这样声明之后, 无论从属性还是方法获得的对象都会得到与该类型相关的子类或者就是该类型,对应上面就是UIView类型或者其子类,而不再是以前的id类型,这样避免了不必要的显示类型转换,直接访问也不会任何类型警告,这一优点用在UITableview的dequeueReusableCellWithIdentifier:方法上更加明显.

Swift时代下的Objective-C新特性_第3张图片
screenshot.png


Objective-C 类属性的使用

在OC中,我们都会将有重要的成员变量作为属性声明来使用,而Xcode 8之后(目前Xcode8还是测试版本)为OC提供支持类属性的声明和使用,并且与Swift混编时该属性桥接成Swift的类属性.(比较坑的是光声明了类属性后,编译器并不会去自动生成setter和getter方法,这些方法都需要开发者自己实现)
首先声明类属性时,需要添加上class关键字修饰该属性,接下来就是在.m文件进行setter和getter方法的实现了.

@interface User : NSObject
@property (class, nonatomic, assign, readonly) NSInteger userCount;
@end
@implementation User
static NSInteger _userCount = 1;

+ (NSInteger)userCount {
  return _userCount;
}

+ (void)setUserCount:(NSInterger)userCount {
 _userCount = userCount;
}

由于类对象的特殊性,其setter和getter方法本质就是类方法,就直接无法使用成员变量,因此只能在类中用static声明对应属性的全局变量,只初始化一次,来保证类属性值生命周期.
在通过类属性的声明以及setter和getter方法的实现后,我们就可以向像Swift一样使用点语法访问类属性了.(分明是向Swift靠拢...)

int main(int argc, char * argv[]) {
 [User setUserCount: 2];
 NSLog(@"User count: %ld",(long)User.userCount); // 2
}

小结

自Swift发布之后,苹果官方不断改建这个新语言的同时,为了让原有大量的OC项目能够更好向Swift方向迁移,在编译器优化上提供越来越多的特性来帮助OC程序更加安全,有效地桥接到Swift项目上,而利用这些新的特性,不但可以帮助OC程序员更无痛地迁移到Swift中去,而且也更是让我们加强在OC编程中对类型安全的重视,尝试写出更安全,可读性更高的OC代码.


参考资料

Using nullable to annotate Objective-C code
2015 Objective-C 新特性
WWDC 2016 Session 405 What’s New in LLVM

你可能感兴趣的:(Swift时代下的Objective-C新特性)