已经有好几个人跟我抱怨过为什么 swift 里面有那么多问号(?
)还有叹号(!
)了。恰恰哈, 在刚刚开始写 swift 的时候, 我也面临着这种问题。
昨天一个朋友发了我一行代码, 让我看看应该怎么写:
let pageiid = (self.pageid?.intValue)! + 1
这段代码看起来很操蛋, 但是糟心的是, 在刚刚写 swift 的时候, 我写过更恶心的代码。
既然大家在刚刚开始写 swift 的时候都遇到了这个问题。今天就来看看, 这样的代码应该怎样写才能让我们更爽。
我们都知道, 有问号(?
)和叹号(!
)的原因是什么?—Optional
相信准备要试试 swift 的人,或多或少都看到过, 或者听说过这是 swift 相较于 oc 很大的区别。在我看来,除了语法上的变化以外, swift 和 oc 最大的区别就在 于optional
了。为了照顾到实在是太新的新手(毕竟我自己也是新手)。还是简单的讲讲这个东西吧!
什么是 Optional
这一点就没什么可说的,在 swift 中 Optional 实际上是一个枚举。如果要自己实现一个类似的东西的话, 核心的代码应该是这样的:
// 这段代码不重要
public enum SYOptional<Wrapped> {
case none
case some(Wrapped)
}
这段代码只是要告诉你, 这个枚举只有两个 case, 一个是 .none
代表这个 optional 是没有值的, 也就是说他是 nil。另外一个值 .some
代表这个 optional 是有值的。
苹果为什么要引入 optional 这个概念, 在这个地方就不打算赘述了。看下面一段话:
“Optional 可以说是 Swift 的一大特色,它完全解决了 “有” 和 “无” 这两个困扰了 Objective-C 许久的哲学概念,也使得代码安全性得到了很大的增加。”
摘录来自: 王巍 (onevcat). “Swifter - Swift 必备 Tips (第四版)”。 iBooks.
个人认为 optional 确实是 swift 中非常好的新特性了。
接下来, 我们看看 Optional 的那几个操作符号: ??
/ !
/?
这些无非就是一些 swift 中的语法糖而已。具体什么意思,我们来往下看。
?
在声明一个变量或者属性的时候:
var optionalString: String?
最后的?
表示这个 optionalString
是一个可选类型(optional)。这里的 String?
就是 Optional<String>
的意思。
在使用变量的时候:
如果这样写编译器是会报错的,
optionalString.lowercased()
这时候编译器会提示你在 optionalString
后面添加一个?
这个问号就是告诉编译器这个 optionalString
是一个可选类型。
class Person {
var name: String!
var son: Son!
}
class Son {
var name: String?
}
var p: Person?
print(p?.son.name)
// Playground: nil
这段代码表示,如果在一行代码里的某个地方出现 nil 着, 这行代码也将会返回 nil。(这一点有点类似 oc 中给 nil 发送消息)。这样写有一个好处,就是在维护代码的时候, 看到 ?就知道这个东西是可选类型了。也就是告诉我们在程序运行期间这个东西是可能为空的。
!
接下来就是 !
了。这个东西跟?
一样。
在声明一个变量或者属性的时候:
var something: String!
这个用法有一个专门的叫法:隐式解包可选类型。 这是一个特殊的可选类型,在对他的成员或者是方法进行访问的时候,编译器会自动的帮我们自动解包。也就说说编译器会自动帮我们加上!
这个符号。换成我们自己的话可以这样理解:
在声明一个变量或者属性的时候,如果我们明确的知道在程序运行过程中访问到这个变量或者属性的时候,他的值一定不为空。那么就可以使用隐式解包可选类型。如果要举例的话: 我想我会举 XIB 的例子。从 SB 或者 XIB 中拖出来的控件, 都是这样子声明的。
在访问变量或者属性的时候:
对于一个可选类型来说, 有些时候编译器会提醒我们在这个对象后面添加!
就像最开始我朋友发给我的代码一样:
let pageiid = (self.pageid?.intValue)! + 1
如果一个方法需要传入的是一个不可选类型作为参数。这时候如果强制传入一个可选类型的话。编译器就会报错,并且提醒我们在这个可选类型变量后面添加一个!
。 这个做法就是强制解包。 相当于是直接访问这个可选类型的 .some
。
??
这个只需要花一句话就能够讲清楚了,这是给这个 optional 默认值。
var optionalString: String?
var defaultValue = optionalString ?? "defaultValue"
如果 optionalString
有值的话 defaultValue
就是 optionalString
的值。反之就是 "defaultValue"
。
怎么写好 Optional
大概讲完了一些基本的概念。下面就来说说如何避免在代码中出现各种 ?
\ !
的情况。其实对新手来说。几乎都是因为编译器提示,然后自动加上去的各种 ?
和 !
。 不得不说,这样的代码是非常丑陋的。要解决这个问题, 知道 optional 的原理当然是最重要的。
避免使用 Optional
写了一段时间之后,我发现很多时候 optional 的使用都是没有什么意义的。就像我朋友给我的例子一样。我们可以通过设置初始值的方法来避免使用 optional
var pageid: Int = 0
通过这种方法就能够避免使用到 optional, 也就不会有下面的事情了。当然还可以使用懒加载:
lazy var tableView = UITableView()
保证在第一次使用这个属性的时候这个属性是肯定被初始化出来了的。
当然还可以通过使用隐式解包可选类型去避免之后的代码中出现 ?
\ !
但是这个其实是不被鼓励的。
默认不要隐式解包可选类型。 在大多数场景中你都可能会忘掉这件事情。但是在一些特殊情况下应该这样做来减少编译器的压力。而且我们也需要去理解这件事情背后的逻辑。
如何访问 Optional
既然设计出来的 Optional 肯定在编码的过程中不可避免的要使用到它。那么在使用 Optional 的时候怎么去避免出现像最开始的那种情况呢?
还是来看这行代码:
let pageiid = (self.pageid?.intValue)! + 1
在这里如果 pageid
为空的话, 强制解包是肯定会崩溃的。这种情况应该怎么写呢?除了最开始说的声明的时候设置初始值,还有就是给默认值。另外还有 Optional Map 这种方法来访问 Optioal:
if let optionalVal = optional {
// do someThing
}
// 等价于
optional.map{ // do someThing }
另外还有一些我在网上搜集的 snippet 也能够很舒服的解决一些问题:给 Optional 加一个 extension:
extension Optional { }
添加一些方法:基本上都来自 GitHub 这个库
require
可以强制要求某个 Optional 在当前行不为空,为空的话会跑出异常。这个相当于是优化了强制解包的异常信息:
/// 强制要求这个 optional 不为空
///
/// 这个方法返回 optional 的值,或者在optional 为空的时候触发 error
///
///
/// - Parameters:
/// - hint: 为空抛出的错误信息
///
/// - Returns: optional 的值.
func require(hint hintExpression: @autoclosure() -> String? = nil,
file: StaticString = #file,
line: UInt = #line) -> Wrapped {
guard let unwrapped = self else {
var message = "required value was nil \(file), at line \(line)"
if let hint = hintExpression() {
message.append(". Debugging hit: \(hint)")
}
#if !os(Linux)
let exception = NSException(name: .invalidArgumentException,
reason: message,
userInfo: nil)
exception.raise()
#endif
preconditionFailure(message)
}
return unwrapped
}
or
/// 用来代替 ?? 操作符, 这样写可读性高些
///
/// - Sample:
// var a: String? = nil
// let res = a.or("b")
func `or`(value: Wrapped?) -> Optional {
return self ?? value
}
hasSome
/// 用来判断这个 Optional 是不是为空了。
var hasSome: Bool {
switch self {
case .none: return false
case .some: return true
}
}
ifSome ifNone
// 如果 optional 不为空的话,执行闭包, 并返回这个 Optional
@discardableResult
func ifSome(_ handler: (Wrapped) -> Void) -> Optional {
switch self {
case .some(let wrapped): handler(wrapped); return self;
case .none: return self
}
}
// 如果 optional 为空的话,执行闭包, 并返回这个 Optional
@discardableResult
func ifNone(_ handler: () -> ()) -> Optional {
switch self {
case .some: return self;
case .none: handler(); return self
}
}
总结
为了避免写出满是 ?
和 !
的代码, 掌握 Swift 中可选类型的基本知识是很必要的。另外也需要去了解 Swift 中为什么要引入可选类型这个概念。在编写 Swift 代码的时候,需要我们程序员时刻知道程序的逻辑是怎么样的,在设计一个类的时候, 要清楚它的属性在其生命周期中那些是可能为空的。在没有必要的时候,尽量的避免是用 Optional 减少 Optional 的使用,一方面能让你的代码逻辑更可控,一方面也能让你的代码更漂亮。至少不会再被编译器一步一步的搞出那些恶心的东西。想清楚逻辑, 合理的规避, 加上一些小手段, 让代码更漂亮, 是一件很幸福的事情。 Optional 能够让代码逻辑更明确, 减少很多不必要的crash,如果不当使用, crash 也不会少哦。
最后我一直觉得,掌握了 Optional 是怎么回事, 以及 Optional 怎么用最好, 基本上就算是入门了 Swift 了。
对了朋友的代码:
var pageiid: NSString? // 这是属性声明
let pageiid = (self.pageid?.intValue)! + 1 // 这是某个方法里面的代码
- 为什么要用 NSString?
- 为什么要 Optional
- 为什么要强制解包?
为什么要 NSString?这个我真不知道, 他只说了接口要一个字符串;为什么要 Optional?这个我也不知道;为什么要强制解包?这我知道,肯定是 Xcode 自动帮他改的啊😂
然后我给他改成了这样:
var pageid: Int = 0
self.pageid +=1
"\(self.pageid)"
初始化的时候默认初始值为0,避免 Optional 的使用也避免了后面的强制解包。
使用 Int 代替 NSString。第一是不想用 NSString, 第二是,在业务逻辑中,这个值应该就是 int 类型的
在接口组装参数的地方,将 int 转换成字符串。这个逻辑应该是接口的事情,不应该拿业务层的逻辑去将就它。