已经有好几我的跟我抱怨过为何 swift 里面有那么多问号(?
)还有叹号(!
)了。偏偏哈, 在刚刚开始写 swift 的时候, 我也面临着这种问题。git
昨天一个朋友发了我一行代码, 让我看看应该怎么写:程序员
let pageiid = (self.pageid?.intValue)! + 1
复制代码
这段代码看起来很操蛋, 可是糟心的是, 在刚刚写 swift 的时候, 我写过更恶心的代码。github
既然你们在刚刚开始写 swift 的时候都遇到了这个问题。今天就来看看, 这样的代码应该怎样写才能让咱们更爽。swift
咱们都知道, 有问号(?
)和叹号(!
)的缘由是什么?—Optional安全
相信准备要试试 swift 的人,或多或少都看到过, 或者据说过这是 swift 相较于 oc 很大的区别。在我看来,除了语法上的变化之外, swift 和 oc 最大的区别就在 于optional
了。为了照顾到实在是太新的新手(毕竟我本身也是新手)。仍是简单的讲讲这个东西吧!闭包
这一点就没什么可说的,在 swift 中 Optional 其实是一个枚举。若是要本身实现一个相似的东西的话, 核心的代码应该是这样的:app
// 这段代码不重要
public enum SYOptional<Wrapped> {
case none
case some(Wrapped)
}
复制代码
这段代码只是要告诉你, 这个枚举只有两个 case, 一个是 .none
表明这个 optional 是没有值的, 也就是说他是 nil。另一个值 .some
表明这个 optional 是有值的。优化
苹果为何要引入 optional 这个概念, 在这个地方就不打算赘述了。看下面一段话:ui
“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
var pageid: Int = 0
复制代码
经过这种方法就可以避免使用到 optional, 也就不会有下面的事情了。固然还可使用懒加载:
lazy var tableView = UITableView()
复制代码
保证在第一次使用这个属性的时候这个属性是确定被初始化出来了的。
固然还能够经过使用隐式解包可选类型去避免以后的代码中出现 ?
\ !
可是这个实际上是不被鼓励的。
默认不要隐式解包可选类型。 在大多数场景中你均可能会忘掉这件事情。可是在一些特殊状况下应该这样作来减小编译器的压力。并且咱们也须要去理解这件事情背后的逻辑。
既然设计出来的 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 这个库
能够强制要求某个 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
}
复制代码
/// 用来代替 ?? 操做符, 这样写可读性高些
///
/// - Sample:
// var a: String? = nil
// let res = a.or("b")
func `or`(value: Wrapped?) -> Optional {
return self ?? value
}
复制代码
/// 用来判断这个 Optional 是否是为空了。
var hasSome: Bool {
switch self {
case .none: return false
case .some: return true
}
}
复制代码
// 若是 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?这个我也不知道;为何要强制解包?这我知道,确定是 Xcode 自动帮他改的啊😂
而后我给他改为了这样:
var pageid: Int = 0
self.pageid +=1
"\(self.pageid)"
复制代码
初始化的时候默认初始值为0,避免 Optional 的使用也避免了后面的强制解包。
使用 Int 代替 NSString。第一是不想用 NSString, 第二是,在业务逻辑中,这个值应该就是 int 类型的
在接口组装参数的地方,将 int 转换成字符串。这个逻辑应该是接口的事情,不该该拿业务层的逻辑去将就它。