通过枚举解决魔法数问题
enum Optional<Wrapped> {
some(Wrapped)
none
}
获取枚举关联值的唯一方式就是通过模式匹配, 就像switch或if case let 中使用的匹配方法一样。和哨岗值不同,除非你式并检查解包,否则不能获取Optional中包装的值。
哨岗值:返回了一个“魔法”数来表示其并没有返回真实的值。这样的值被称为“哨岗值 (sentinel values)
extension Collection where Element: Equatable {
func firstIndex(of element: Element) -> Optional<Index> {
var idx = startIndex
while idx != endIndex {
if self[idx] == element {
return .some(idx)
}
formIndex(after: &idx)
}
return .none
}
}
返回的是Optional<Index>类型,非哨岗值
-
Optional<Index>可以写成Index?
-
可选值遵循ExpressibleByNilLiteral协议,所以可以用nil表示.none
-
return .some(idx) 就可以直接写为 return idx
-
可选值就是一个普通的枚举值,完全可以自定义
let stringNumbers = ["1", "2", "three"]
let idx = stringNumbers.firstIndex(of: "1")
// 编译错误,remove(at:)接收Int,非Optional<Int>
stringNumbers.remove(at: idx)
必须解包
var stringNumbers = ["1", "2", "three"]
switch stringNumbers.firstIndex(of: "1") {
case .some(let idx):
stringNumbers.remove(at: idx)
break
case .none:
break
}
更简明的写法:
var stringNumbers = ["1", "2", "three"]
switch stringNumbers.firstIndex(of: "1") {
case let idx?:
stringNumbers.remove(at: idx)
break
case nil:
break
}
可选值概览
if let
var array = ["one", "two", "three", "four"]
if let idx = array.firstIndex(of: "four") {
array.remove(at: idx)
}
if let idx = array.firstIndex(of: "four"), idx != array.startIndex {
array.remove(at: idx)
}
let urlString = "https://www.objc.io/logo.png"
if let url = URL(string: urlString),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
let view = UIImageView(image: image)
PlaygroundPage.current.liveView = view
}
if let url = URL(string: urlString), url.pathExtension == "png",
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
let view = UIImageView(image: image)
}
while let
当一个条件返回nil时便终止循环
while let line = readLine() {
print(line)
}
while let line = readLine(), !line.isEmpty {
print(line)
}
- next()遍历
let array = [1, 2, 3]
var iterator = array.makeIterator()
while let i = iterator.next() {
print(i, terminator: " ")
}
- for..in + where
for i in 0..<10 where i % 2 == 0 {
print(i, terminator: " ")
} // 0 2 4 6 8
while+迭代器重写
var iterator2 = (0..<10).makeIterator()
while let i = iterator2.next() {
guard i % 2 == 0 else { continue }
print(i)
}
双重可选值
let stringNumbers = ["1", "2", "three"]
let maybeInts = stringNumbers.map { Int($0) }
for maybeInt in maybeInts {
// maybeInt 是一个 Int? 值
// 得到两个整数值和一个 `nil`
}
for...in 是 while 循环加上一个迭代器的简写方式。由于 next 方法会把序列中的每个元素包装成可选Optional<Optional<Int>>值,或者说是一个Int??,而while let 会解包检查这个值是不是nil,如果不是,则绑定解包的值并运行循环体部分
var iterator = maybeInts.makeIterator()
while let maybeInt = iterator.next() {
print(maybeInt, terminator: "")
}
当循环到"three"转换而来nil.从next()返回的是一个非nil的值,这个值是.some(nil).while let 将这个值解包,并将解包结果(也就是nil)绑定到maybeInt上。
如果只想对非nil的值做for循环,可以用case来做模式匹配
for case let i? in maybeInts {
// i 将是 Int 值,而不是 Int?”
print(i, terminator: "")
}
// 或者只对 nil 值进行循环
for case nil in maybeInts {
// 将对每个 nil 执行一次
print("No value")
}”
x?这个模式只会匹配非nil值。这个是.some(x)简写
for case let .some(i) in maybeInts {
print(i)
}
基于case的匹配模式可以让我们在switch的匹配中用到的规则应用到for, if和while上去.最有用的场景是结合可选值,但也有其它一些使用方式
let j = 5
if case 0..<10 = j {
print("\(j) 在范围内")
} // 5 在范围内”
if var and while var
使用var替代let,可以改变变量(值类型,不改变原来的类型)
let number = "1"
if var i = Int(number) {
i += 1
print(i)
} // 2
解包后可选值的作用域
func doStuff(withArray a: [Int]) {
guard let firstElement = a.first else {
return
}
// firstElement 在这里已经被解包了
}
**guard let ** 唯一的要求是必须离开当前的作用域,通常这意味着一条 return 语句,或抛出一个错误,亦或调用 fatalError (或者其他返回 Never 的方法)。如果你是在循环中使用 guard 的话,那么最后也可以是 break 或者 continue。
Never: 用于通知编译器它绝对不会返回。有俩类常见的函数会这么做:一种是像fatalError那样程序失败的函数,另一个像dispatchMain那样运行在整个程序生命周期的函数。编译器会使用这个信息来检查和确保控制流正确。举例来说,guard语句的else路径必须退出当前域或者调用一个不会返回的函数
Never又被叫做无人类型(uninhabited type)这种类型没有有效值,因此也不能被构建。在泛型环境里,把Never和Result结合在一起是非常有用的。例如,接受一个Result<A,E>(A,E都是泛型参数)的泛型API,你可以传入一个Result<..., Never>表示这个结果中永远都不会包含失败的情况,因为这种情况是构建不出来的。另外,一个声明为返回无人类型的函数也绝对不可能正常返回。
在Swift中,无人类型是通过一个不包含任意成员的enum实现的:
public enum Never {}
一般不会自定义返回Never的方法,除非你在为fatalError或者precondtionFailure写封装。一个很有意思的应用场景是,当你要创建一个很复杂的switch的语句,在case过程编译器会用空的case语句或者没有返回值轰炸你,而你又想集中处理某一个case语句的逻辑,这是放几个fatalError()就能编译器闭嘴。你还可以写一个unimplemented()方法,这样能够更好的表达这些调用是暂时没有实现的意思:
func unimplemented() -> Never { fatalError("This code path is not implemented yet") }
Swift区分无,除了Never和nil, 还有Void,Void是空组(tuple)的另一种写法
public typealias Void = ()
Void或者()最常见的用法是作为那些不返回任何东西函数的返回值,不过也还有其它使用场景。举例来说,
在一个响应式编程框架中,使用Observable<T>类型对事件进行建模,这里的T会提供事件中携带的内容类型。比如文本框发提供一个Observable<String>,在每次用户编辑文本时发送事件,按钮类似,不过没有附加内容Observable<()>
在字符串插值中使用可选值
我们在打印可选值时候会编译器会发出警告,主要是为了防止把Optional(...)或nil显示到文本,需要对其进行解包,但是所有类型是允许插入到字符串中的包括Optional类型,所以编译器发出的是警告
let bodyTemperature: Double? = 37.0
let bloodGlucose: Double? = nil
print(bodyTemperature) // Optional(37.0)
// 警告:表达式被隐式强制从 'Double?' 转换为 Any
print("Blood glucose level: \(bloodGlucose)") // Blood glucose level: nil
// 警告:字符串插值将使用调试时的可选值描述,
// 请确认这确实是你想要做的。
如果想去除警告可以有以下几种做法
- 使用as Any 进行转换
- 确定不为nil,用!强制解包
- String(describing:...)包装
- nil合并运算符提供默认值(a ?? "c")
最后一种做法的不足在于??俩侧的类型必须匹配,如果左侧为Double?类型,默认值也是Double类型,如果一开始能提供字符串类型就比较方便,我们可以自定义运算符进行处理
infix operator ???: NilCoalescingPrecedence
public func ???<T>(optional: T?, defaultValue:@autoclosure ()-> String) -> String {
switch optional {
case let value?: return String(describing: value)
case nil: return defaultValue()
}
}
@autoclosure表示只要在需要的时候才会对第二个表达式进行求值
let bodyTemperature: Double? = 37.0
let bloodGlucose: Double? = nil
print("Body temperature: \(bodyTemperature ??? "n/a")")
print("Blood glucose level: \(bloodGlucose ??? "n/a")")
Body temperature: 37.0
Blood glucose level: n/a
map
一种实现方式
extension Optional {
func map<U>(transform: (Wrapped)->U) -> U? {
guard let value = self else { return nil }
return transform(value)
}
}
let firstChar = characters.first.map { String($0) }
这里map只操作一个值,可选值为nil时候也会返回nil
当你想为数组提供一个变种的reduce方法,不接受初始值,由于Swift有重载,所以可以直接命名为reduce
extension Array {
func reduce(_ nextPartialResult:(Element, Element) -> Element) -> Element? {
guard let fst = first else { return nil }
return dropFirst().reduce(fst, nextPartialResult)
}
}
[1, 2, 3].reduce(+)
这里可以用map代替guard
func alt_reduce(_ nextPartialResult:(Element, Element) -> Element) -> Element? {
return first.map { return reduce($0, nextPartialResult) }
}
compactMap
一种实现方式
extension Sequence {
func alt_commpactMap<T>(_ transform: (Element) -> T?) -> [T] {
return lazy.map(transform).filter { $0 != nil }.map { $0! }
}
}
- filter { 0==1则不会
- lazy将数组的创建放在最后一刻,在处理较大的序列时候还是不错的,避免多个作为中间结果的数组的内存分配。实际*compactMap不是这么做的
可选值判等
let regex = "123"
if regex.first == "1" {}
上面的代码可以运行基于俩点,Wrapped类型实现了Equatable协议,Optional才会实现Equatable协议
extension Optional: Equatable where Wrapped: Equatable {
static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): return true
case let(x?, y?): return x == y
case (nil, _?), (_?, nil): return false
}
}
}
我们不一定需要写这样的代码
if regex.first == Optional("1") {}
当我们使用非可选值时候,如果需要匹配成可选值类型,Swift总会将他“升级”一个可选值类型
如果没有这个隐式转换,你需要实现是三个分开版本
// 两者都可选
func == <T: Equatable>(lhs: T?, rhs: T?) -> Bool
// lhs 非可选
func == <T: Equatable>(lhs: T, rhs: T?) -> Bool
// rhs 非可选
func == <T: Equatable>(lhs: T?, rhs: T) -> Bool
实际上,我们就只需要第一个版本。很多时候都依赖这个隐式转换,比如可选map时,我们将内部值进行转换并且返回。但是,我们的map返回值其实是个可选值,编译器自动帮我们完成转换,我们不需要写return Optional(transform(value))这样的代码。在字典中因为键可能不存在,所以返回值为可选值,如果没有隐式转换我们需要写myDict["key"] = Optional(someValue)的代码
如果想为字典赋值为nil
var dictWithNils: [String: Int?] = ["one": 1,
"two": 2,
"three": nil]
dictWithNils["two"] = nil 会把键移除
可以有以下做法
dictWithNils["two"] = .some(nil)
dictWithNils["two"] = Optional(nil)
dictWithNils["two"]? = nil
第三种方式使用了可选链的方式来获取成功后对值进行设置
对于不存在的值并没有值被更新或者插入
dictWithNils["four"]? = nil
print(dictWithNils) // ["one": Optional(1), "two": Optional(2), "three": nil]
强制解包的时机
当你确定某个值不可能是nil时可以用感叹号,你应当希望如果它是!,程序应当直接挂掉
extension Sequence {
func alt_commpactMap<T>(_ transform: (Element) -> T?) -> [T] {
return lazy.map(transform).filter { $0 != nil }.map { $0! }
}
}
这里filter已经把nil给过滤出去,所以map使用!完全没有问题
改进强制解包的错误信息
infix operator !!
func !!<T>(wrapped: T?, failtureText:@autoclosure () -> String) -> T {
if let x = wrapped { return x }
fatalError(failtureText())
}
let s = "foo"
let i = Int(s) !! "Expecting integer, got\"\(s)\""
在调试版本中使用断言
使用assert在进行断言,发布版本将替换为默认值
infix operator !?
func !?<T: ExpressibleByNilLiteral>(wrapped: T?, failureText: @autoclosure () -> String) -> T {
assert(wrapped != nil, failureText())
return wrapped ?? 0
}
如果需要提供一个显式的默认值,可以定义一个接受元组的参数,包含默认值和错误信息
infix operator !?
func !?<T: ExpressibleByNilLiteral>(wrapped: T?, nilDefault: @autoclosure () -> (value: T, text: String)) -> T {
assert(wrapped != nil, nilDefault().text)
return wrapped ?? nilDefault().value
}
// 调试版本中断言,发布版本中返回 5
Int(s) !? (5, "Expected integer")
对于返回Void的函数,使用可选链进行调用会返回Void?, 可以写一个非泛型的版本检测一个可选链调用碰到nil,且无操作的情况
infix operator !?
func !?(wrapped: ()?, failureText: @autoclosure () -> String) {
assert(wrapped != nil, failureText())
}
var output: String? = nil
output?.write("something") !? "wasn't expecting chained nil here"
网友评论