技术分享-swift防御编程
公司每两周会组织一次团队内的技术学习会议,大家轮流分享自己开发中,一些遇到的问题,或则一些好的技术,会议的宗旨希望团队的大家共同进步。 前面开了很多次,但是我都没有做一个记录,主要是当时听懂了,觉得没必要记录,但是后面的开发中发现有些东西虽然当时听懂了,但是过后会比较容易忘记。所以决定后面的每一次技术交流会,我都会记录下来,方便后面忘记可以有资料找。
今天技术交流会的主题是swift防御编程
1 前言
为了开发可靠的软件,我们需要设计系统中的每个组件,已其尽可能的保护自己。防御性编程使我们能尽早的发现较小的问题,而不是等到它发展成为大灾难的时候才发现?
由于没有人能保证自己的程序是百分百没有bug,所以适度的防御会降低调试bug的时间。
- 方法一:一旦碰到约定的异常,程序必须兼容处理,一定不能让程序Crash
- 方法二: 一旦碰到预定异常,就抛出去,如果上层没有处理,则Crash
2 防御性编程的习惯
奥卡姆剃刀原理
:**如无必须,勿增实体。**不要浪费更多的东西去做,你可以用更少的东西做同样的事情,更多的变量,需要更多的精力维护,也更容易出问题。不要仓促的编写代码
:写每一行代码需要三思而后行,考虑可能会出现什么样的错误?考虑所有可能出现的逻辑分支尽可能处理掉所有编译警告
: 处理编译警告是一种优秀的习惯,编译警告可能隐含着某种错误检查所有返回值
:检查所有的返回值,包括自己写的代码,不要一些异常污染正常执行审慎地进行强制转换
:强制转换需要谨慎处理,不要盲目转换,对转换尽可能的说明清楚检查数值的边界
:对数值的范围要做周密的判断,有多少历史教训是由边界造成的
3. swift中一些需要注意的点
目前swift的趋势已经很大了, 公司的项目已经在一年前,开始转向swift编程。所以对于swift编程,有一些需要注意的点。
3.1 可选类型 Optional Type
- 尽量避免声明隐式可选类型,除非能确定其使用时一定有值,例如:
// 结合@IBOutlet使用时可以用隐式可选类型
@IBOutlet weak var tableView: UITableView!
// 能确定其使用时一定有值,可以用隐式可选类型
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "cell"
var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: identifier)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: identifier)
}
cell.textLabel?.text = "Row \(indexPath.row)"
return cell
}
- 避免使用
as!
和try!
,如果对象没有值,使用!
,会导致程序直接Crash - 慎对可选变量,使用强制解包,也就是谨慎使用
!
可选变量的值,可能是nil
,如果对可选变量使用强制解包,需要明确上下文环境,是否会出现nil
.特别可能变量是类的储存属性,并且在多线程环境下使用,请务必不要使用强制解包。
var test : String?
print(test) //nil
print(test!) //error
更好的使用方式,是使用前判定或则赋值给let变量(显示解包)
//使用前判定
if test == nil {
}
else{
}
//赋值给let变量
if let constant = optionVariable{
}
//多个可选变量
if let constant = optionaVar1 , constant2 = optionVar2{
}
3.2 OC默认非空声明对Swift的影响
很多的公司原先可能都是OC的代码,后面慢慢转到swift变成,在这个过程中就有一个混编的过程。那么在混编的过程中我们对一些变量的使用也需要注意
OC如果整个文件都没有显示指定nullable
或者nonnull
,则所有属性、参数和返回值在swift中都默认为隐式可选类型
,其值可能为nil,如果直接使用可能会知道程序直接崩溃(注意:公司项目发生过一起该问题的生产事件)
@interface tztStockInfo : NSObject
@property(nonatomic, strong) NSString *stockCode;
@property(nonatomic, strong) NSString *stockName;
@property(nonatomic, assign) int stockType;
@property(nonatomic, strong) NSString *stockProp;
- (NSMutableDictionary*)GetStockDict;
@end
如下面代码,在stockName为nil时会崩溃
extension tztStockInfo {
/// 股票完整性检查
func isIntegral() -> Bool {
return self.stockType != 0 && self.stockName.count > 0 && self.stockProp.count > 0;
}
}
OC转换成隐式可选类型,也需要判断非nil
,或则使用if let
来进行可选绑定来使用
// OC代码未使用nullable、nonnull声明,在Swift中会自动转化为隐式可选类型(OC使用nullable、nonnull,在Swift分别会转化为`可选类型`、`非可选类型`)
@property(nonatomic, strong) NSString *stockCode;
// OC转换的隐式可选类型,必须判断非nil
if stockInfo.stockCode != nil {
params = [
"stockCode": stockInfo.stockCode,
]
}
// OC转换的隐式可选类型,通过可选绑定使用
if let stockCode = stockInfo.stockCode {
params = [
"stockCode": stockCode,
]
}
新开发的OC代码建议通过宏NS_ASSUME_NONNULL_BEGIN
配合nullable
使用严格规范可返回值为nil的情况
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
#endif
如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull
,因此我们只需要去指定那些nullable的指针
3.3 多个运算符结合时,请使用括号显式进行结合
print(true || false && false)
print((true || false) && false)
print(true || (false && false))
第一行:看不出谁的运算级谁高
第二行:把||用括号括起来,增强或优先级
第三行:把&&用括号括起来,增强与优先级
打印结果:
true
false
true
可以看出&&的优先级高
再来看另一个案例:
public func stockKey(stockCode: String?, stockType: Int32) -> String {
return stockCode ?? "" + "|\(stockType)"
}
let key0 = stockKey(stockCode: nil, stockType: 1) //|1
let key1 = stockKey(stockCode: "600061", stockType: 1) //600061 不符合预期
正确的代码应该是:
public func stockKey(stockCode: String?, stockType: Int32) -> String {
return (stockCode ?? "") + "|\(stockType)"
}
let key0 = stockKey(stockCode: nil, stockType: 1) //|1
let key1 = stockKey(stockCode: "600061", stockType: 1) //600061|1 符合预期
3.4 闭包中调用self要避免循环引用
将捕获列表[weak self]放在闭包参数前
myFunctionWithEscapingClosure() { [weak self] error in
// 可以这样使用
self?.doSomething()
// 或者这样使用
guard let strongSelf = self else {
return
}
strongSelf.doSomething()
}
PS: Swift4.2之后可选绑定中的self不再作为保留关键字,可以光明正大这么写 guard let self = self else { return }
swift中还有一个字段[unowned self]
也可以解决引用循环,但是不建议使用,因为swift中unowned
子段相当于OC中的unsafe_unretained
,如果对象被设置为nil,unowned
表示的指针不会清空, 但是如果你继续访问的话,就会报野指针错误。
3.5 Swift与OC混编时反射问题
@objc public class TestClass: NSObject {
}
OC代码中通过反射获取这个TestClass这个类对象第一种方式输出的cls是为空的,第二种方式是在类名前期需求拼接上Module名称我们看到拿到cls实例
在Swift的类中@ojbc后面加上自定义的类名,代码如下:
@objc(TestClass)
public class TestClass: NSObject {
}
加上@objc(TestClass)之后,OC中反射时获取Swift的Class我们就不需要关心Module名,底层处理方式和OC一样,这样就抹平了底层的差异性。
我们来验证下:
但是这样就有一个问题, swift支持在不同模块下面,创建名称一样的 文件,如果你这样修改之后,就不能这样做了。相当于放弃了swift这个特性
3.6 Swift中String.count与OC中NSString.length不总是相同
Swift中String的count属性返回的字符计数并不总是与包含相同字符的NSString的length属性相同。 虽然我们目前没有碰到过这个问题,但是我们需要有这个意思,知道这两种获取的值不一定相同
在Swift中如果希望得到NSString.length相同的长度,可以使用String.utf16.count
// é可以用单一的Unicode的标量U+00E9来表示,也可以通过e(U+0065)与 ́(U+0301)的Unicode的标量组合来表示。
let eAcute = "\u{E9}" // é
let combinedEAcute = "\u{65}\u{301}" // e后面拼接上 ́
print(combinedEAcute.count) // 1
print((combinedEAcute as NSString).length) // 2
print(combinedEAcute.utf16.count) // 2
4 swift中的一些规范
- 尽可能使用
let
(能使用let
就不要使用var
) - 在声明类方法和属性时,优先使用
static
而非calss
,仅当需要在子类中覆盖该函数或属性的功能时才使用 class - 如果该函数没有参数,没有
副作用
,只返回一些对象或则值,建议使用计算属性
struct Square {
var side: CGFloat = 1.0
// 推荐
var girth: CGFloat {
return side * 4
}
// 不推荐
// func girth() -> CGFloat {
// return side * 4
// }
// 推荐
func halfGirth() -> CGFloat {
self.side = side * 0.5
return side * 4
}
// 不推荐
// var halfGirth: CGFloat {
// self.side = side * 0.5
// return side * 4
// }
}
- 使用有限集合(enum)的switch语句时,不要包含default。反之,switch语句的末尾用default涵盖其他情况。
- 因为swift的枚举支持很多类型,比如字符串,我们在使用字符串的作为type时,字符串可以表示无限的情况,这时候我们应该加上
default
来涵盖其它情况 - 如果
type
表示的是一个有限的枚举类型,我们可以不用加上default
在末尾,这样如果你在拓展新功能,修改了枚举,而忘记修改switch语句,那么系统会报错,引导我们去修改对应的代码
- 因为swift的枚举支持很多类型,比如字符串,我们在使用字符串的作为type时,字符串可以表示无限的情况,这时候我们应该加上
- 展开可选项时,推荐使用
guard
语句,而不是if
语句,已减少代码中的嵌套缩进的数据,这样也可以增加我们代码的可读性
// 推荐
guard let monkeyIsland = monkeyIsland else {
return
}
bookVacation(on: monkeyIsland)
// 不推荐
if let monkeyIsland = monkeyIsland {
bookVacation(on: monkeyIsland)
}
// 极其不推荐
if monkeyIsland == nil {
return
}
bookVacation(on: monkeyIsland!)
总结:虽然讲的内容比较浅,但是大多都是我们开发中经常遇到的,一个优秀的程序员需要从细节做起。