百度工程师移动开发避坑指南Swift

北京中科医院电话 http://www.bdfzkyy.com/

作者

启明星小组

我们介绍了移动开发常见的内存泄漏问题,见《百度工程师移动开发避坑指南——内存泄漏篇》。本篇我们将介绍Swift语言部分常见问题。

对于Swift开发者,Swift较于OC一个很大的不同就是引入了可选类型(Optional),刚接触Swift的开发者很容易在相关代码上踩坑。

本期我们带来与Swift可选类型相关的几个避坑指南:可选类型要判空;避免使用隐式解包可选类型;合理使用Objective-C标识符;谨慎使用强制类型转换。希望能对Swift开发者有所帮助。

一、可选类型(Optional)要判空

在Objective-C中,可以使用nil来表示对象为空,但是使用一个为nil的对象通常是不安全的,如果使用不慎会出现崩溃或者其它异常问题。在Swift中,开发者可以使用可选类型表示变量有值或者没有值,可以更加清晰的表达类型是否可以安全的使用。如果一个变量可能为空,那么在声明时可以使用?来表示,使用前需要进行解包。例如:

varoptionalString:String?

在使用可选类型对象时,需要进行解包操作,有两种解包方式:强制解包与可选绑定。

强制解包使用!修饰一个可选对象,相当于告诉编译器『我知道这是一个可选类型,但在这里我可以保证他不为空,编译时请忽略此处的可空校验』,例如:

letunwrappedString:String=optionalString!//运行时报错:Thread1:Fatalerror:UnexpectedlyfoundnilwhileunwrappinganOptionalvalue

这里使用!进行了强制解包,如果optionalString为nil,将会产生运行时错误,发生崩溃。因此,在使用!进行强制解包时,必须保证变量不为nil,要对变量进行判空处理,如下:

ifoptionalString!=nil{

letunwrappedString=optionalString!

}

相较于强制解包的不安全性,一般而言推荐另一种解包方式,即可选绑定。例如:

ifletoptionalString=optionalString{

//这里optionalString不为nil,是已经解包后的类型,可以直接使用

}

综上,在对可选类型进行解包时应尽量避免使用强制解包,采用可选绑定替代。如果一定要使用强制解包,那么必须在逻辑上完全保证类型不为空,并且做好注释工作,以增加后续代码的可维护性。

二、避免使用隐式解包可选类型(ImplicitlyUnwrappedOptionals)

由于可选类型每次使用之前都需要进行显式解包操作,有时变量在第一次赋值之后,就会一直有值,如果每次使用都显式解包,显得繁琐,Swift引入了隐式解包可选类型,隐式解包可选类型可以使用!来表示,并且使用时不需要显式解包,可以直接使用,例如:

varimplicitlyUnwrappedOptionalString:String!="implicitlyUnwrappedOptionalString"

varimplicitlyString:String=implicitlyUnwrappedOptionalString

上述例子的隐式解包,在编译和运行过程中都不会发生问题,但如果在两行代码中间插入一行implicitlyUnwrappedOptionalString=nil将会产生运行时错误,发生崩溃。

在我们实际项目中,一个模块通常由多人维护,通常很难保证变量在第一次赋值之后一直不为nil或者只有在第一次正确赋值之后使用,从安全角度考虑,在使用隐式解包类型之前也要进行判空操作,但这样就和使用可选类型没有区别。对于可选类型(?),不经过解包直接使用编译器会报告错误,对于隐式解包类型,则可直接使用,编译器无法帮助我们做出是否为空的检查。因此,在实际项目中,不推荐使用隐式解包可选类型,如果一个变量是非空的,则选择非空类型,如果不能保证是非空的,则选择使用可选类型。

三、合理使用Objective-C标识符

与Swift不同的是,OC是一种动态类型语言,对于OC而言没有optional这个概念,无法在编译期间检查对象是否可空。苹果在Xcode6.中引入了一个Objective-C的新特性:NullabilityAnnotations,允许编码时使用nonnull、nullable、null_unspecified等标识符告诉编译器对象是否是可空或者非空的,各标识符含义如下:

nonnull,表示对象是非空的,有__nonnull和_Nonnull等价标识符。

nullable,表示对象可能是空的,有__nullable和_Nullable等价标识符。

null_unspecified,不知道对象是否为空,有__null_unspecified等价标识符。

OC标识符标注的对象类型和Swift类型对应关系如下:

除了以上标识符外,现在通过Xcode创建的头文件默认被NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END包住,即在这之间声明的对象默认标识符是nonnull的。

在Swift与OC混编场景,编译器会根据OC标识符将OC的对象类型转换成Swift类型,如果没有显式的标识,默认是null_unspecified。例如:

interfaceExampleOCClass:NSObject

//没有指定标识符,且没有被NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END包裹,标识符默认为null_unspecified

+(ExampleOCClass*)getExampleObject;

end

implementationExampleOCClass

+(ExampleOCClass*)getExampleObject{

returnnil;//OC代码直接返回nil

}

end

classViewController:UIViewController{

overridefuncviewDidLoad(){

super.viewDidLoad()

let_=ExampleOCClass.getExampleObject().description//报错:Thread1:Fatalerror:UnexpectedlyfoundnilwhileimplicitlyunwrappinganOptionalvalue

}

}

在上面例子中,Swift代码调用OC接口获取一个对象,编译器隐式的将OC接口返回的对象转换为隐式解包类型来处理。由于隐式解包类型可以不显式解包直接使用,使用者往往会忽略OC返回的是隐式解包类型,不通过判空而直接使用。但当代码执行时,由于OC接口返回了一个nil,导致Swift代码解包失败,发生运行时错误。

在实际编码中,推荐显式指定OC对象为nonnull或者nullable,针对上述代码进行修改后如下:

interfaceExampleOCClass:NSObject

///获取可空的对象

+(nullableExampleOCClass*)getOptionalExampleObject;

///获取不可空的对象

+(nonnullExampleOCClass*)getNonOptionalExampleObject;

end

implementationExampleOCClass

+(ExampleOCClass*)getOptionalExampleObject{

returnnil;

}

+(ExampleOCClass*)getNonOptionalExampleObject{

return[[ExampleOCClassalloc]init];

}

end

classViewController:UIViewController{

overridefuncviewDidLoad(){

super.viewDidLoad()

//标注nullable后,编译器调用接口时,会强制加上?

let_=ExampleOCClass.getOptionalExampleObject()?.description

//标注nonnull后,编译器将会把接口返回当做不可空来处理

let_=ExampleOCClass.getNonOptionalExampleObject().description

}

}

在OC对象加上nonnull或者nullable标识符后,相当于给OC代码增加了类似Swift的『静态类型语言的特性』,使得编译器可以对代码进行可空类型检测,有效的降低了混编时崩溃的风险。但这种『静态特性』并不对OC完全有效,例如以下代码,虽然声明返回类型是nonnull的,但是依然可以返回nil:

implementationExampleOCClass

+(nonnullExampleOCClass*)getNonOptionalExampleObject{

returnnil;//接口声明不可空,但实际上返回一个空对象,可以通过编译,如果Swift当作非空对象使用,则会发生崩溃

}

end

classViewController:UIViewController{

overridefuncviewDidLoad(){

super.viewDidLoad()

ExampleOCClass.getNonOptionalExampleObject().description

}

}

基于以上例子,依然会产生运行时错误。从安全性的角度上来说,似乎Swift最好在使用所有OC的接口时都进行判空处理。但实际上这将导致Swift的代码充斥着大量冗余的判空代码,大大降低代码的可维护性,同时也违背了『暴露问题,而非隐藏问题』的编码原则,并不推荐这么做,合理的做法是在OC侧做好安全校验,OC对返回类型应做好检验,保证返回类型的正确性,以及返回值和标识符能够对应。

综合来看,OC侧标识符最好遵循如下使用原则:

1、不推荐使用NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END,因为默认修饰符是nonnull的,在实际开发中很容易忽略返回的对象是否为空。返回空则会导致Swift运行时错误。推荐所有涉及混编的OC接口都需要显式使用相应的标识符修饰。

2、OC接口要谨慎使用nonnull修饰,必须确保返回值不可能是空的情况下使用,任何不能确定不可空的接口都需要标注为nullable。

、为避免Swift侧不必要的类型、判空等校验(违背Swift设计理念),在理想状态下需在OC侧进行类型的校验,保证返回对象和标注的标识符完全正确,这样Swift则可以完全信赖OC返回的对象类型。

4、在Swift调用OC代码时,要


转载请注明:http://www.aierlanlan.com/cyrz/8457.html

  • 上一篇文章:
  •   
  • 下一篇文章: 没有了