2017-10-19 14:38:01 Keep_Moving31038 阅读数 231
  • iOS高级程序员进阶——语法雷区篇

    无论是Objective-C语言还是Swift语言,其都有非常巧妙的设计思想与丰富的语法特性。在编写程序时,编程语言是最基础的工具,工具是否用的顺手会直接影响到所编写程序的效率与质量。还有很多时候,一种场景我们知道应该如何编写代码却从来没有深入想过为什么需要这样写,这时我们对代码的把控能力将会大大降低,产生了异常也将极难排查。并且,在参加面试时,很多语言上的细节问题也常常会问倒应聘者,这便造成了“老师傅栽跟头”,应聘者虽然编程经验丰富,却并没有取得自己理想的面试成绩。 本套课程我们将着重从这些语言语法细节处着手,更深入的为大家讲解其工作机制与内部原理。帮助大家在编写代码时做到知其然,也知其所以然。对于iOS相关岗位面试常考的语法细节,本套课程也会尽量全面的进行讲解。其中包括Obejctive-C语言中的属性修饰符,引用计数、动态性与运行时等相关内容,也包括Swift语言中的Optional类型、闭包、协议与泛型等。

    27 人正在学习 去看看 张益珲

Swift 引入的 Optional,很好的解决了 Objective-C 时代 “nil or not nil” 的问题,配合 Type-Safe 特性,帮我们减少了很多隐藏的问题。下面让我们通过源码来了解一下 Optional 具体的实现逻辑。

初识 Optional

Swift 中的 Optional 想要表达的涵义更多是“有没有”,而非“空不空”,因此在 Swift 中,对 nil 的使用也就不像 Objective-C 中那么随意。我们需要将其声明为 Optional(可能没有),才能将其赋值为 nil(没有),也正因为表达的是“有没有”,所以可选类型也就不局限于对象类型了,基本数据类型同样可以声明 Optional,并赋值为 nil。

基础用法

Swift 声明一个 Optional 变量方法如下:

1
2
3
Swift
 
var optionalValue: Optional

另外,还有一个更常见的声明方式,即 ?:

1
2
Swift
var optionalValue: T?

上面两种方式是等价的,我们得到了一个类型为 T 的可选变量,如果我们不初始化,则可选变量的初始值为 nil,即,该变量没有值。

读取可选变量时,通过 ! 运算符进行强制解包(Forced Unwrapped),不过使用前需要先判断变量是否有值,否则会得到一个 runtime 错误:

1
2
3
4
5
6
7
8
9
10
Swift
var optionalValue_1: OptionaloptionalValue_1 = 5
if optionalValue_1 != nil {
    print(optionalValue_1!) // "5"
else {
    print("optionalValue_1 has no value")
}
var optionalValue_2: Int?
// fatal error: unexpectedly found nil while unwrapping an Optional value
print(optionalValue_2!)

隐式解包(Implicitly Unwrapped)

声明了一个可选变量后,每次读取都需要使用 ! 进行强制解包,写起来比较麻烦,为了简便起见,我们可以将声明中的 ? 替换为 !,这样,使用时系统会对可选变量进行隐式解包,就无需再添加 !:

1
2
3
4
5
6
7
8
Swift
var optionalString: String!
optionalString = "Hello, Implicitly Unwrapped"
if optionalString != nil {
    print(optionalString) // "Hello, Implicitly Unwrapped"
else {
    print("optionalString has no value")
}

如此用起来方便多了,跟 Objective-C 也类似了,可是,这看上去和一个非可选变量的使用完全一样,很容易让人误解为是一个正常变量,一定有值,不需要对其检查就可以使用,这不是有违 Swift 的安全的原则吗?为什么以安全著称的 Swift 会加入隐式解包并允许这种危险的写法呢?王巍(@onevcat)在其《100个Swift开发必备Tip》一书中隐式解包 OPTIONAL章节进行了探讨,以下摘录片段:

一切都是历史的错。因为 Objective-C 中 Cocoa 的所有类型变量都可以指向 nil 的,有一部分 Cocoa 的 API 中在参数或者返回时即使被声明为具体的类型,但是还是有可能在某些特定情况下是 nil,而同时也有另一部分 API 永远不会接收或者返回 nil。在 Objective-C 时,这两种情况并没有被加以区别,因为 Objective-C 里向 nil 发送消息并不会有什么不良影响。在将 Cocoa API 从 Objective-C 转为 Swift 的 module 声明的自动化工具里,是无法判定是否存在 nil 的可能的,因此也无法决定哪些类型应该是实际的类型,而哪些类型应该声明为 Optional。

在这种自动化转换中,最简单粗暴的应对方式是全部转为 Optional,然后让使用者通过 Optional Binding 来判断并使用。虽然这是最安全的方式,但对使用者来说是一件非常麻烦的事情,我猜不会有人喜欢每次用个 API 就在 Optional 和普通类型之间转来转去。这时候,隐式解包的 Optional 就作为一个妥协方案出现了。使用隐式解包 Optional 的最大好处是对于那些我们能确认的 API 来说,我们可直接进行属性访问和方法调用,会很方便。但是需要牢记在心的是,隐式解包不意味着 “这个变量不会是 nil,你可以放心使用” 这种暗示,只能说 Swift 通过这个特性给了我们一种简便但是危险的使用方式罢了。

王巍(@onevcat)隐式解包 OPTIONAL

总之,只要我们在使用时坚持对可选变量进行“为空判断”,即可安全的使用隐式解包特性。

可选绑定(Optional Binding)

可选绑定(Optional Binding)是用来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量,通常用于 if 和 while 语句中:

1
2
3
4
5
6
7
8
Swift
var optionalString: String!
optionalString = "Hello, Optional Binding"
if let tempString = optionalString {
    print(tempString) // "Hello, Optional Binding"
else {
    print("optionalString has no value")
}

Optional 源码解析

在掌握了 Optional 的基本使用方法之后,我们通过 Swift 源码来了解一下 Optional 的实现细节。为了方便理解,我将分定义、解包、运算符和扩展方法 4 个部分进行讲述。

Optional 定义

打开 Optional 源代码(路径:swift/stdlib/public/core/Optional.swift),或通过 Xcode 查看 Optional 定义,可以看到 Optional 其实是一个 enum,具体代码如下(删除注释):

1
2
3
4
5
6
7
8
9
10
11
Optional.swift
public enum Optional : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
     
    public init(_ some: Wrapped)
    public func map(_ transform: (Wrapped) throws -> U) rethrows -> U?
    public func flatMap(_ transform: (Wrapped) throws -> U?) rethrows -> U?
    public init(nilLiteral: ())
    public var unsafelyUnwrapped: Wrapped { get }
}

通过定义可以得到以下信息:

  • 包含 none 和 some(Wrapped) 两个 case,分别代表可选类型“没有值”和“有值”两种情况

既然是枚举类型,那么我们就可以通过判断 case 是否匹配来读取可选变量的值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Swift
var optionalValue: String?
// 方式 1
switch optionalValue {
case .none:
    print("optionalString has no value")
default:
    print(optionalValue!)
}
// 方式 2
if optionalValue == .none {
    print("optionalString has no value")
else {
    print(optionalValue!)
}
// 方式 3,推荐
if optionalValue == nil {
    print("optionalString has no value")
else {
    print(optionalValue!)
}

以上三种方式效果等价,不过官方注释里说 In code, the absence of a value is typically written using the `nil` literal rather than the explicit `.none` enumeration case,即“没有值”时推荐使用 nil,而非 .none,因此上述方法 3 为推荐方式,也就是我们通常所用的方式。

  • 包含一个名为 init(_ some: Wrapped) 的初始化方法

init(_ some: Wrapped) 是 Optional 提供的初始化方法,可以通过这个初始化方法来创建一个有初始值的可选变量,因此以下三种方式是等效的:

1
2
3
4
Swift
var optionalValue = Optional(5)
var optionalValue: Optional = 5
var optionalValue: Int? = 5

我们来看看 init(_ some: Wrapped) 的源码:

1
2
3
4
init(_ some: Wrapped)
public init(_ some: Wrapped) {
    self = .some(some)
}

即:将可选变量初始化为 .some(some),这就是一个 wrapped 的值,.some 可以理解为就是 Optional,因此当我们初始化后得到的就是 Optional(some),所谓的解包,其实就是将 some 从 Optional(some) 中解出来:

1
2
3
Swift
var optionalValue = Optional(5)
print(optionalValue) // Optional(5)
  • 包含两个 Map 相关方法

这部分将在本文 Map 章节进行说明。

  • 实现了 protocol ExpressibleByNilLiteral 协议中的 init(nilLiteral: ()) 方法

协议 ExpressibleByNilLiteral 中包括一个 init(nilLiteral: ()) 方法,功能是使用 nil(Optional 中为:.none)进行初始化,源码如下:

1
2
3
4
init(nilLiteral: ())
public init(nilLiteral: ()) {
    self = .none
}

这个方法是不能直接调用的,使用 var i: Index? = nil 的方式会自动调用该方法。

  • 包含一个名为 unsafelyUnwrapped 的只读变量

这部分将在本文 Unwrapped 章节进行说明。

Unwrapped

上面讲到初始化一个可选变量后得到的是一个 wrapped 的值,使用时需要进行 unwrapped,即解包。我们都知道解包只需要在可选变量后面加上 ! 即可。

那么 ! 具体是如何工作的呢?我们从源码(路径:swift/stdlib/public/core/Policy.swift)中找到它的定义:

1
2
3
4
5
Policy.swift
// Optional unwrapping operator is built into the compiler as a part of
// postfix expression grammar.
//
// postfix operator !

原来,解包符号 ! 作为后缀表达式被放入了编译器中,我们无法得到其具体实现,不过这并不妨碍我们理解,因为还有另外一条线索,就是前面提到的名为 unsafelyUnwrapped 的只读变量。

在日常开发中,我们一般不会用到 unsafelyUnwrapped,但其实它与 ! 在一定程度上是等价的,我们先来看看这个变量的源代码:

1
2
3
4
5
6
7
8
9
10
Optional.swift
public var unsafelyUnwrapped: Wrapped {
    @inline(__always)
    get {
        if let x = self {
            return x
        }
        _debugPreconditionFailure("unsafelyUnwrapped of nil optional")
    }
}

unsafelyUnwrapped 的 get 方法和我们常写的可选绑定语法很近似,这看上去非常安全,为什么变量名称是 “unsafely” 呢?稍安勿躁,我们来看看这个变量的官方注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Optional.swift
/// The wrapped value of this instance, unwrapped without checking whether
/// the instance is `nil`.
///
/// The `unsafelyUnwrapped` property provides the same value as the forced
/// unwrap operator (postfix `!`). However, in optimized builds (`-O`), no
/// check is performed to ensure that the current instance actually has a
/// value. Accessing this property in the case of a `nil` value is a serious
/// programming error and could lead to undefined behavior or a runtime
/// error.
///
/// In debug builds (`-Onone`), the `unsafelyUnwrapped` property has the same
/// behavior as using the postfix `!` operator and triggers a runtime error
/// if the instance is `nil`.
///
/// The `unsafelyUnwrapped` property is recommended over calling the
/// `unsafeBitCast(_:)` function because the property is more restrictive
/// and because accessing the property still performs checking in debug
/// builds.
///
/// - Warning: This property trades safety for performance.  Use
///   `unsafelyUnwrapped` only when you are confident that this instance
///   will never be equal to `nil` and only after you've tried using the
///   postfix `!` operator.

大意如下:

unsafelyUnwrapped 就是读取可选变量所包裹的值,不检查是否为 nil;

unsafelyUnwrapped 和 ! 提供相同的值,但在编译优化设置为 (-O) 时,不会做任何检查,因此,直接访问可选变量的 unsafelyUnwrapped 会导致未知结果;

在编译优化设置为 (-Onone) 时,unsafelyUnwrapped 和 ! 表现相同,访问 nil 会引发 runtime 错误;

unsafelyUnwrapped 比调用 unsafeBitCast(_:) 方法更推荐,因为 debug 环境会进行安全检查;

警告:unsafelyUnwrapped 以牺牲安全性来换取更好的性能,因此需要在确保非 nil 的情况下使用。

下面我们分段解读:

对于 1、2、3,来看以下代码:

1
2
3
4
Swift
var optionalValue = Optional(5)
print(optionalValue!) // 5
print(optionalValue.unsafelyUnwrapped) // 5

可选变量有值的情况下,两种解包方式效果相同,再来看下面的代码:

1
2
3
Swift (`-Onone`)
var optionalValue: Optionalprint(optionalValue!) // fatal error: unexpectedly found nil while unwrapping an Optional value
print(optionalValue.unsafelyUnwrapped) // fatal error: unsafelyUnwrapped of nil optional

上述代码是在 (-Onone) 下执行的,两种方式得到不同表述但相同涵义的错误,然后我们将编译优化切换至 (-O):

1
2
3
Swift (`-O`)
var optionalValue: Optionalprint(optionalValue!) // EXC_BAD_INSTRUCTION
print(optionalValue.unsafelyUnwrapped) // 0

这时,使用 ! 解包出现错误,但 unsafelyUnwrapped 却正常输出了 0,而这个 0 对我们来说并不应该出现,因为可能导致程序出现无法预知的错误,这也就是为什么变量名中包含一个 “unsafely” 的原因。

第 4 点中提到了一个名为 unsafeBitCast(_:) 的方法,并说明不推荐使用,为什么呢?我们来看看这个方法的具体源码(路径:swift/stdlib/public/core/Builtin.swift):

1
2
3
4
5
6
7
8
9
10
11
12
13
Builtin.swift
/// Returns the bits of `x`, interpreted as having type `U`.
///
/// - Warning: Breaks the guarantees of Swift's type system; use
///   with extreme care.  There's almost always a better way to do
///   anything.
///
@_transparent
public func unsafeBitCast(_ x: T, to: U.Type) -> U {
  _precondition(MemoryLayout.size == MemoryLayout.size,
    "can't unsafeBitCast between types of different sizes")
  return Builtin.reinterpretCast(x)
}

再来看一个使用的例子:

1
2
3
4
Swift
let array = NSArray(object: "obj")
let str = unsafeBitCast(CFArrayGetValueAtIndex(array, 0), to: CFString.self)
print(str) // obj

可以看出,unsafeBitCast(_:) 会将一个指针指向的内存强制按位转换为目标的类型,并且只进行了简单的 size 判断。这是非常危险的操作,因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,所以使用上需要非常注意。

也是因为上述原因,unsafelyUnwrapped 比 unsafeBitCast(_:) 更安全,因为在 debug 环境下,我们能够在不安全使用的情况下得到错误。

第 5 点警告也是我们日常使用可选变量时应该注意的,无论是使用 ! 或是 unsafelyUnwrapped。

运算符

通过 Xcode 进入 Optional 可以看到很多可选变量的运算符,但除了 ?? 以外均没有注释,下面我们还是通过源码来了解一下吧。

??

?? 是 Swift 中一个非常有用的操作符,用来对 nil 进行快速判断,?? 可以判断输入并在当左侧的值是非 nil 的 Optional 值时返回其值,左侧是 nil 时返回右侧的值。

1
2
3
4
5
6
7
8
Swift
var optionalValue: Int?
var aValue = 5
let result = optionalValue ?? aValue
print(result) // 5
optionalValue = 1
result = optionalValue ?? aValue
print(result) // 1

在 Optional 中有两个 ?? 方法,源码如下(删除注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Optional.swift
public func ?? (optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
     
    switch optional {
        case .some(let value):
            return value
        case .none:
            return try defaultValue()
    }
}
public func ?? (optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
     
    switch optional {
        case .some(let value):
            return value
        case .none:
            return try defaultValue()
    }
}

我们可以得到以下信息:

两个方法的实现完全一样,只是第二个参数允许接收 T 和 T? 两种类型;

?? 右侧的参数 defaultValue 被封装为了 () -> T 和 () -> T?。为什么这样做呢?下面再次摘录王巍(@onevcat)在其《100个Swift开发必备Tip》一书中@AUTOCLOSURE 和 ??章节部分片段进行解释:

可能你会有疑问,为什么这里要使用 autoclosure,直接接受 T 作为参数并返回不行么,为何要用 () -> T 这样的形式包装一遍,岂不是画蛇添足?其实这正是 autoclosure 的一个最值得称赞的地方。如果我们直接使用 T,那么就意味着在 ?? 操作符真正取值之前,我们就必须准备好一个默认值传入到这个方法中,一般来说这不会有很大问题,但是如果这个默认值是通过一系列复杂计算得到的话,可能会成为浪费 – 因为其实如果 optional 不是 nil 的话,我们实际上是完全没有用到这个默认值,而会直接返回 optional 解包后的值的。这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional 判定为 nil 之后。

王巍(@onevcat)@AUTOCLOSURE 和 ??

 

我们通过源码也可以看出,当满足 case .none 时,直接返回 defaultValue(),正是因为这一巧妙的用法,克服了性能上的开销和使用上的不便,以如此优雅的方式返回默认值。

==

在 Optional 中存在三个 == 运算符的实现,用于对比可选变量的相等关系,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Optional.swift
public func == (lhs: T?, rhs: T?) -> Bool {
    switch (lhs, rhs) {
        case let (l?, r?):
            return l == r
        case (nil, nil):
            return true
        default:
            return false
    }
}
public func == (lhs: T?, rhs: _OptionalNilComparisonType) -> Bool {
    switch lhs {
        case .some(_):
            return false
        case .none:
            return true
    }
}
public func == (lhs: _OptionalNilComparisonType, rhs: T?) -> Bool {
    switch rhs {
        case .some(_):
            return false
        case .none:
            return true
    }
}

第一个方法中,== 被用来对比两个遵循 protocol Equatable 的可选变量的等价关系,通过源码可以看出,以下两种情况会返回 true:

两个可选变量均为 nil;

两个可选变量均非 nil,且所包裹的值相等。

不满足以上条件的情况均返回 false,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
Swift
let group1 = [1, 2, 3, 4, 5]
let group2 = [1, 3, 5, 7, 9]
if group1.first == group2.first {
    print("Start the same."// Start the same.
}
let left: Int? = nil
let right: Int? = nil
if left == right {
print("Equal!"// Equal!
}

另外,我们还可以将可选变量与非可选变量或常量进行比较,系统会将非可选变量或常量先 “包裹” 成一个 Optional 变量再进行上述比较:

1
2
3
4
5
6
7
8
9
Swift
let left: Int = 5
let right: Int? = 5
if left == right {
    print("Equal!"// Equal!
}
if 5 == right {
    print("Equal Too!"// Equal Too!
}

第二个和第三个方法我们也经常用到,它允许我们将可选变量与 nil 进行直接比较,与第一个方法的区别在于这里的可选变量即使不遵循 protocol Equatable 也可以进行比较,方法实现与判断可选变量是否有值的方式很类似。

参数中 _OptionalNilComparisonType 的定义源码如下:

1
2
3
4
5
6
7
Optional.swift
public struct _OptionalNilComparisonType : ExpressibleByNilLiteral {
    /// Create an instance initialized with `nil`.
    @_transparent
    public init(nilLiteral: ()) {
    }
}

使用方法如下:

1
2
3
4
5
Swift
var str: Data? = nil
if str == nil {
    print("Data is nil."// Data is nil.
}

!=

!= 用于可选变量的 “不等于” 判断,实现方法共有三个,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Optional.swift
public func != (lhs: T?, rhs: T?) -> Bool {
    return !(lhs == rhs)
}
public func != (lhs: T?, rhs: _OptionalNilComparisonType) -> Bool {
    switch lhs {
        case .some(_):
            return true
        case .none:
            return false
    }
}
public func != (lhs: _OptionalNilComparisonType, rhs: T?) -> Bool {
    switch rhs {
        case .some(_):
            return true
        case .none:
            return false
    }
}

有了 == 的经验,!= 就很好理解了,只是对 == 进行逻辑取反,这里不再进行赘述。

~=

在 Optional 中,还有一个 ~= 运算符,源码为:

1
2
3
4
5
6
7
8
9
Optional.swift
public func ~= (lhs: _OptionalNilComparisonType, rhs: T?) -> Bool {
    switch rhs {
        case .some(_):
            return false
        case .none:
            return true
    }
}

奇怪,这个方法的实现与 == 中第三个方法相同,为什么要多加一个 ~= 呢?

~= 被称为模式匹配运算符(Pattern Matching),在可选变量的判断上与 == 确实有相同的功效,但是在使用上官方给出如下解释:

1
2
3
4
5
Optional.swift
/// - Note: To test whether an instance is `nil` in an `if` statement, use the
///   equal-to operator (`==`) instead of the pattern-matching operator. The
///   pattern-matching operator is primarily intended to enable `case`
///   statement pattern matching.

原来,~= 主要是被用于 case 语句中的模式匹配,正常的可选变量判断,我们仍然需要使用 ==。

Optional Extensions

在 Optional 中,还存在两个 extension,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Optional.swift
extension Optional : CustomDebugStringConvertible {
    /// A textual representation of this instance, suitable for debugging.
    public var debugDescription: String {
        switch self {
            case .some(let value):
                var result = "Optional("
                    debugPrint(value, terminator: "", to: &result)
                    result += ")"
                return result
            case .none:
                return "nil"
        }
    }
}
extension Optional : CustomReflectable {
    public var customMirror: Mirror {
        switch self {
            case .some(let value):
                return Mirror(
                    self,
                    children: [ "some": value ],
                    displayStyle: .optional)
            case .none:
                return Mirror(self, children: [:], displayStyle: .optional)
        }
    }
}

debugDescription 变量输出其实就是我们常常看到的 Optional(some),主要用于 debug:

1
2
3
Swift
var optionalValue: Int? = 5
print(optionalValue.debugDescription) // Optional(5)

第二个 extension 中的 customMirror 其实是创建了一个当前可选变量的 Mirror,Mirror 是 Swift 中定义的一个用于表示子结构的结构体类型(可以参考:Mirror API Reference),这里不再展开,后续有时间我们再详细论述。

Optional 补充

感谢您坚持阅读到这里,经过上述的讨论,我们已经对 Optional 有了很详细的了解,不过还有两个相对比较重要的概念需要进行说明,就是可选链(Optional Chaining)和 Map,由于篇幅所限,以下仅进行简单的讨论,详细内容大家可以查阅官方文档或是文末提供的参考资料。

可选链(Optional Chaining)

对于可选链,官方文档给出了如下定义:

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.

简而言之,就是当我们需要通过一条链(链上各个环节都可能返回 nil)访问某个属性、方法和下标时,可以通过可选链的方式代替强制解包,以此简化代码。

以官方代码为例:

1
2
3
4
5
6
7
8
9
10
11
Swift
class Person {
    var residence: Residence?
}
  
class Residence {
    var numberOfRooms = 1
}
let john = Person()
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

由于访问链路径上 residence 为 nil,因此访问出错,这时,我们可以通过可选链方式避免这样的错误:

Swift

1
2
3
4
5
6
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."

通过上述方式,告知 Swift 帮我们在 residence 有值时解析出 numberOfRooms,从而避免访问出错,或是写繁琐的判断代码。

Map

Optional 中包括两个 Map 相关方法,其源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Optional.swift
public func map(_ transform: (Wrapped) throws -> U)
    rethrows -> U? {
    switch self {
        case .some(let y):
            return .some(try transform(y))
        case .none:
            return .none
    }
}
public func flatMap(_ transform: (Wrapped) throws -> U?)
    rethrows -> U? {
    switch self {
        case .some(let y):
            return try transform(y)
        case .none:
            return .none
    }
}

Optional Map 提供了一种便捷的对可选变量进行变换(transform)的方法,使用 map 方法时,传入一个 transform 闭包,内部可以将接收到可选变量解包后的值,然后在闭包内完成“变换”,再返回。如下例:计算一个可选变量的平方:

1
2
3
4
5
6
7
8
9
Swift
// Sample 1
let possibleNumber: Int? = Int("42")
let possibleSquare = possibleNumber.map { $0 * $0 }
print(possibleSquare) // Optional(1746)
// Sample 2
let noNumber: Int? = nil
let noSquare = noNumber.map { $0 * $0 }
print(noSquare) // nil

Sample 1 闭包中的 $0 即可选变量解包后的值(42,非 Optional(42))。如果可选变量没有值,那么返回即为 nil,如 Sample 2。

那么 flatMap 方法呢?

从声明上看,flatMap 返回的是 U?,说明该方法可以返回 Optional;从源码上看,flatMap 与 map 的区别在于满足 case .some(let y) 时的返回值的不同,map 要求返回 .some(try transform(y)),说明必须是 Optional “包裹”的值类型,其实就是非 Optional 类型,而 flatMap 返回 transform(y),其实是将返回值类型交给 transform() 决定,Optional 或非 Optional 均可。

flatMap 测试例子如下:

1
2
3
4
5
6
Swift
let optionalArray: [String?] = ["A""B""C"];
var optionalResult = optionalArray.flatMap{ $0 }
print(optionalResult) // ["A", "B", "C"]
optionalResult = optionalArray.map{ $0 }
print(optionalResult) // error: cannot convert value of type 'String?' to closure result type 'String'

参考资料

2014-06-12 10:17:29 ydj213 阅读数 713
  • iOS高级程序员进阶——语法雷区篇

    无论是Objective-C语言还是Swift语言,其都有非常巧妙的设计思想与丰富的语法特性。在编写程序时,编程语言是最基础的工具,工具是否用的顺手会直接影响到所编写程序的效率与质量。还有很多时候,一种场景我们知道应该如何编写代码却从来没有深入想过为什么需要这样写,这时我们对代码的把控能力将会大大降低,产生了异常也将极难排查。并且,在参加面试时,很多语言上的细节问题也常常会问倒应聘者,这便造成了“老师傅栽跟头”,应聘者虽然编程经验丰富,却并没有取得自己理想的面试成绩。 本套课程我们将着重从这些语言语法细节处着手,更深入的为大家讲解其工作机制与内部原理。帮助大家在编写代码时做到知其然,也知其所以然。对于iOS相关岗位面试常考的语法细节,本套课程也会尽量全面的进行讲解。其中包括Obejctive-C语言中的属性修饰符,引用计数、动态性与运行时等相关内容,也包括Swift语言中的Optional类型、闭包、协议与泛型等。

    27 人正在学习 去看看 张益珲

本文摘自onevcat文章的一段,原文:http://onevcat.com/2014/06/walk-in-swift/


幽灵一般的 Optional

Swift 引入的最不一样的可能就是 Optional Value 了。在声明时,我们可以通过在类型后面加一个? 来将变量声明为 Optional 的。如果不是 Optional 的变量,那么它就必须有值。而如果没有值的话,我们使用 Optional 并且将它设置为 nil 来表示没有值。

//num 不是一个 Int
var num: Int?  
//num 没有值
num = nil  //nil  
//num 有值
num = 3    //{Some 3}  

Apple 在 Session 上告诉我们,Optinal Value 其实就是一个盒子,你盒子里可能装着实际的值,也可能什么都没装。

我们看到 Session 里或者文档里天天说 Optional Optional,但是我们在代码里基本一个 Optional 都没有看到,这是为什么呢?而且,上面代码中给 num 赋值为 3 的时候的那个输出为什么看起来有点奇怪?其实,在声明类型时的这个 ? 仅仅只是 Apple 为了简化写法而提供的一个语法糖。实际上我们是有 Optional 类型的声明,就这里的 num 为例,最正规的写法应该是这样的:

//真 Optional 声明和使用
var num: Optional<Int>  
num = Optional<Int>()  
num = Optional<Int>(3)  

没错,num 不是 Int 类型,它是一个 Optional 类型。到底什么是 Optional 呢,点进去看看:

enum Optional<T> : LogicValue, Reflectable {  
    case None
    case Some(T)
    init()
    init(_ some: T)

    /// Allow use in a Boolean context.
    func getLogicValue() -> Bool

    /// Haskell's fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?
    func getMirror() -> Mirror
}

你也许会大吃一惊。我们每天和 Swift 打交道用的 Optional 居然是一个泛型枚举 enum,而其实我们在使用这个枚举时,如果没有值,我们就规定这个枚举的是 .None,如果有,那么它就是Some(value)(带值枚举这里不展开了,有不明白的话请看文档吧)。而这个枚举又恰好实现了LogicValue 接口,这也就是为什么我们能使用 if 来对一个 Optinal 的值进行判断并进一步进行 unwrap 的依据。

var num: Optional<Int> = 3  
if num {       //因为有 LogicValue,  
               //.None 时 getLogicValue() 返回 false
               //.Some 时返回 true
   var realInt = num!
   realInt     //3
}

既然 var num: Int? = nil 其实给 num 赋的值是一个枚举的话,那这个 nil 到底又是什么?它被赋值到哪里去了?一直注意的是,Swift 里的 nil 和 objc 里的 nil 完全不是一回事儿。objc 的 nil 是一个实实在在的指针,它指向一个空的对象。而这里的 nil 虽然代表空,但它只是一个语意上的概念,确是有实际的类型的,看看 Swift 的 nil 到底是什么吧:

/// A null sentinel value.
var nil: NilType { get }  

nil 其实只是 NilType 的一个变量,而且这个变量是一个 getter。Swift 给了我们一个文档注释,告诉我们 nil 其实只是一个 null 的标记值。实际上我们在声明或者赋值一个 Optional 的变量时,? 语法糖做的事情就是声明一个 Optional<T>,然后查看等号右边是不是 nil 这个标记值。如果不是,则使用 init(_ some: T) 用等号右边的类型 T 的值生成一个 .Some 枚举并赋值给这个 Optional 变量;如果是 nil,将其赋为 None 枚举。

所以说,Optional背后的故事,其实被这个小小的 ? 隐藏了。

我想,Optional 讨论到这里就差不多了,还有三个小问题需要说明。

首先,NilType 这个类型非常特殊,它似乎是个 built in 的类型,我现在没有拿到关于它的任何资料。我本身逆向是个小白,现在看起来 Swift 的逆向难度也比较大,所以关于 NilType 的一些行为还是只能猜测。而关于 nil 这一 NilType 的类型的变量来说,猜测的话,它可能是Optional.None 的一种类似多型表现,因为首先它确实是指向 0x0 的,并且与 Optional.None 的 content 的内容指向一致。但是具体细节还要等待挖掘或者公布了。

其次,Apple 推荐我们在 unwrap 的时候使用一种所谓的隐式方法,即下面这种方式来 unwrap:

var num: Int? = 3  
if let n = num {  
    //have a num
} else {
    //no num
}

最后,这样隐式调用足够安全,性能上似乎应该也做优化(有点忘了..似乎说过),推荐在 unwrap 的时候尽可能写这样的推断,而减少直接进行 unwrap 这种行为。

最后一个问题是 Optional 的变量也可以是 Optinal。因为 Optional 就相当于一个黑盒子,可以知道盒子里有没有东西 (通过 LogicValue),也可以打开这个盒子 (unwrap) 来拿到里面的东西 (你要的类型的变量或者代表没有东西的 nil)。请注意,这里没有任何规则限制一个 Optional 的量不能再次被 Optional,比如下面这种情况是完全 OK 的:

var str: String? = "Hi"         //{Some "Hi"}  
var anotherStr: String?? = str  //{{Some "Hi"}}  

这其实是没有多少疑问的,很完美的两层 Optional,使用的时候也一层层解开就好。但是如果是 nil 的话,在这里就有点尴尬...

var str: String? = nil  
var anotherStr: String?? = nil  

因为我们在 LLDB 里输出的时候,得到了两个 nil

two nils

如果说 str 其实是 Optional<String>.None,输出是 nil 的话还可以理解,但是我们知道 (好吧,如果你认真读了上面的 Optional 的内容的话会知道),anotherStr 其实是Optional<Optional<String>>.Some(Optional<String>.None),这是其实一个有效的非空Optional,至少第一层是。而如果放在 PlayGround 里,anotherStr 得到的输出又是正确的{nil}。What hanppened? Another Apple bug?

答案是 No,这里不是 bug。为了方便观察,LLDB 会在输出的时候直接帮我们尽可能地做隐式的 unwrap,这也就导致了我们在 LLDB 中输出的值只剩了一个裸的 nil。如果想要看到 Optional 本身的值,可以在 Xcode 的 variable 观察窗口点右键,选中 Show Raw values,这样就能显示出 None 和 Some 了。或者我们可以直接使用 LLDB 的 fr v -R 命令来打印整个 raw 的值:

LLDB-frv

可以清楚看到,anotherStr 是 .Some 包了一个 .None

(这里有个自动 unwrap 的小疑问,就是写类似 var anotherStr: String? = str 这样的代码也能通过,应该是 ? 语法在这里有个隐式解包,需要进一步确认)

? 那是什么??,! 原来如此!!

问号和叹号现在的用法都是原来 objc 中没有的概念。说起来简单也简单,但是背后也还是不少玄机。原来就已经存在的用法就不说了,这里把新用法从浅入深逐个总结一下吧。

首先是 ?

  • ? 放在类型后面作为 Optional 类型的标记

这个用法上面已经说过,其实就是一个 Optional<T> 的语法糖,自动将等号后面的内容 wrap 成 Optional。给个用例,不再多说:

var num: Int? = nil        //声明一个 Int 的 Optional,并将其设为啥都没有  
var str: String? = "Hello" //声明一个 String 的 Optional,并给它一个字符串  
  • ? 放在某个 Optional 变量后面,表示对这个变量进行判断,并且隐式地 unwrap。比如说:
foo?.somemethod()  

相比起一般的先判断再调用,类似这样的判断的好处是一旦判断为 nil 或者说是 false,语句便不再继续执行,而是直接返回一个 nil。上面的写法等价于

if let maybeFoo = foo {  
    maybeFoo.somemethod()
}

这种写法更存在价值的地方在于可以链式调用,也就是所谓的 Optional Chaining,这样可以避免一大堆的条件分支,而使代码变得易读简洁。比如:

if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {  
    println("John's uppercase building identifier is \(upper).")
}

注意最后 buildingIdentifier 后面的问号是在 () 之后的,这代表了这个 Optional 的判断对象是buildingIdentifier() 的返回值。

  • ? 放在某个 optional 的 protocol 方法的括号前面,以表示询问是否可以对该方法调用

这中用法相当于以前 objc 中的 -respondsToSelector: 的判断,如果对象响应这个方法的话,则进行调用。例子:

delegate?.questionViewControllerDidGetResult?(self, result)  

中的第二个问号。注意和上面在 () 后的问号不一样,这里是在 () 之前的,表示对方法的询问。

其实在 Swift 中,默认的 potocol 类型是没有 optional 的方法的,因为基于这个前提,可以对类型安全进行确保。但是 Cocoa 框架中的 protocol 还是有很多 optional 的方法,对于这些可选的接口方法,或者你想要声明一个带有可选方法的接口时,必须要在声明 protocol 时再其前面加上@objc 关键字,并在可选方法前面加上 @optional

@objc protocol CounterDataSource {
    @optional func optionalMethod() -> Int
    func requiredMethod() -> Int
    @optional var optionalGetter: Int { get }
}

然后是 ! 新用法的总结

  • ! 放在 Optional 变量的后面,表示强制的 unwrap 转换:
foo!.somemethod()  

这将会使一个 Optional<T> 的量被转换为 T。但是需要特别注意,如果这个 Optional 的量是 nil 的话,这种转换会在运行时让程序崩溃。所以在直接写 ! 转换的时候一定要非常注意,只有在有必死决心和十足把握时才做 ! 强转。如果待转换量有可能是 nil 的话,我们最好使用 if let 的语法来做一个判断和隐式转换,保证安全。

  • ! 放在类型后面,表示强制的隐式转换。

这种情况下和 ? 放在类型后面的行为比较类似,都是一个类型声明的语法糖。? 声明的是Optional,而 ! 其实声明的是一个 ImplicitlyUnwrappedOptional 类型。首先需要明确的是,这个类型是一个 struct,其中关键部分是一个 Optional<T> 的 value,和一组从这个 value 里取值的 getter 和 方法:

struct ImplicitlyUnwrappedOptional<T> : LogicValue, Reflectable {  
    var value: T?
    //...
    static var None: T! { get }
    static func Some(value: T) -> T!
    //...
}

从外界来看,其实这和 Optional 的变量是类似的,有 Some 有 None。其实从本质上来说,ImplicitlyUnwrappedOptional 就是一个存储了 Optional,实现了 Optional 对外的方法特性的一个类型,唯一不同的是,Optional 需要我们手动进行进行 unwrap (不管是使用 var! 还是let if 赋值,总要我们做点什么),而 ImplicitlyUnwrappedOptional 则会在使用的时候自动地去 unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。

为什么要这么设计呢?主要是基于 objc 的 Cocoa 框架的两点考虑和妥协。

首先是 objc 中是有指向空对象的指针的,就是我们所习惯的 nil。在 Swift 中,为了处理和 objc 的 nil 的兼容,我们需要一个可为空的量。而因为 Swift 的目的就是打造一个完全类型安全的语言,因此不仅对于 class,对于其他的类型结构我们也需要类型安全。于是很自然地,我们可以使用 Optional 的空来对 objc 做等效。因为 Cocoa 框架有大量的 API 都会返回 nil,因此我们在用 Swift 表达它们的时候,也需要换成对应的既可以表示存在,也可以表示不存在的 Optional

那这样的话,不是直接用 Optional 就好了么?为什么要弄出一个 ImplicitlyUnwrappedOptional呢?因为易用性。如果全部用 Optional 包装的话,在调用很多 API 时我们就都需要转来转去,十分麻烦。而对于 ImplicitlyUnwrappedOptional 因为编译器为我们进行了很多处理,使得我们在确信返回值或者要传递的值不是空的时候,可以很方便的不需要做任何转换,直接使用。但是对于那些 Cocoa 有可能返回 nil,我们本来就需要检查的方法,我们还是应该写 if 来进行转换和检查。

比如说,以下的写法就会在运行时导致一个 EXC_BAD_INSTRUCTION

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
let soon = now.dateByAddingTimeInterval(5.0) // EXC_BAD_INSTRUCTION  

因为 dateFromString 返回的是一个 NSDate!,而我们的输入在原来会导致一个 nil 的返回,这里我们在使用 now 之前需要进行检查:

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
if let realNow = now {  
    realNow.dateByAddingTimeInterval(5.0)
} else {
    println("Bad Date")
}

这和以前在 objc 时代做的事情差不多,或者,用更 Swift 的方式做

let formatter = NSDateFormatter()  
let now = formatter.dateFromString("not_valid")  
let soon = now?.dateByAddingTimeInterval(5.0)  

如何写出正确的 Swift 代码

现在距离 Swift 发布已经接近小一周了。很多开发者已经开始尝试用 Swift 写项目。但是不管是作为练习还是作为真正的工程,现在看来大家在写 Swift 时还是带了浓重的 objc 的影子。就如何写出带有 Swift 范儿的代码,在这里给出一点不成熟的小建议。

  1. 理解 Swift 的类型组织结构。Swift 的基础组织非常漂亮,主要的基础类型大部分使用了sturct 来完成,然后在之上定义并且实现了各种接口,这样的设计模式其实是值得学习和借鉴的。当然,在实际操作中可能会有很大难度,因为接口比之前灵活许多,可以继承,可以放变量等等,因此在定义接口时如何保持接口的单一性和扩展性是一个不小的考验。
  2. 善用泛型。很多时候 Swift 的 Generic 并不是显式的,类型推断帮助我们做了很多的事情,因此 Generic 这个概念可能被忽视的比较多。关于泛型这个强大的工具,因为原来 objc 中是没有的,而泛型的一个代表语言 C# 虽然平时有写,但很多时候只是当作类型安全的保证在用,我自己也没有太多心得。但是在日常开发中还是多思考和总结,相信会很有进步。
  3. 尽快养成符合 Swift 的语法和习惯,比如 if let,比如对常量习惯性地用 let 而不要用var,在上下文明确的时候省掉原来习惯写的 self,枚举只使用 .,合适地使用 _ 这样的符号来增加可读性等等。既然写 Swift,就应该入乡随俗,尊重这门语言的规范,这样不管在之后和别人的讨论交流上,还是自我的长期发展上,都会很有帮助。
  4. 安心等 Apple 进一步完善。现在 Swift 还处在相对很早期的阶段,很多东西虽然已经基本定型了,但是也有不少可塑性。编译器和调试器现在感觉还不太好用(当然,因为还在 beta,也不是说责怪什么),而且对于原来基于 objc 写的 Cocoa 框架还是有很多水土不服的地方。我个人来说,现在的水平使用 Swift 写还凑合 app 这样的级别应该问题不大,在这篇文章之后我暂时不会再进一步深挖 Swift,而是打算等待正式版出来之后再看情况使用。现在 Swift 仅在String 上可以和 Cocoa 框架完美对接,而对于像 Array 这样的类型,虽然通过一些巧妙的方式完成了桥接,但是在实际使用上可能还是需要借助大量的 NSArray,在转换上略显麻烦。按照现在来看,Apple 应该至少会将 Cocoa 框架另外几个重要的类迅速适配 Swift 的语言习惯,如果能找到 一种很方便地使用 Cocoa 框架的方法的话,objc 程序员转型 Swift 就应该相对容易一些了。


2014-10-24 13:56:05 jinzhongjunqianyan 阅读数 360
  • iOS高级程序员进阶——语法雷区篇

    无论是Objective-C语言还是Swift语言,其都有非常巧妙的设计思想与丰富的语法特性。在编写程序时,编程语言是最基础的工具,工具是否用的顺手会直接影响到所编写程序的效率与质量。还有很多时候,一种场景我们知道应该如何编写代码却从来没有深入想过为什么需要这样写,这时我们对代码的把控能力将会大大降低,产生了异常也将极难排查。并且,在参加面试时,很多语言上的细节问题也常常会问倒应聘者,这便造成了“老师傅栽跟头”,应聘者虽然编程经验丰富,却并没有取得自己理想的面试成绩。 本套课程我们将着重从这些语言语法细节处着手,更深入的为大家讲解其工作机制与内部原理。帮助大家在编写代码时做到知其然,也知其所以然。对于iOS相关岗位面试常考的语法细节,本套课程也会尽量全面的进行讲解。其中包括Obejctive-C语言中的属性修饰符,引用计数、动态性与运行时等相关内容,也包括Swift语言中的Optional类型、闭包、协议与泛型等。

    27 人正在学习 去看看 张益珲

swift 中有一种类型是optional类型的,比如像下面这样:

var string:NSString? = "中国人"

如果你直接打印string的值

println("\(string)")

你会得到Optional(中国人)这样的结果,正确的做法是:

println("\(string!)") //需要加上感叹号(!)

现在你可以得到正确的结果了:

中国人

当你使用 NSUserDefault等进行数据的存储和读取的时候,也要注意类似的问题,你应该在使用optional对象的时候,进行拆包操作


2015-04-16 09:20:29 colouful987 阅读数 796
  • iOS高级程序员进阶——语法雷区篇

    无论是Objective-C语言还是Swift语言,其都有非常巧妙的设计思想与丰富的语法特性。在编写程序时,编程语言是最基础的工具,工具是否用的顺手会直接影响到所编写程序的效率与质量。还有很多时候,一种场景我们知道应该如何编写代码却从来没有深入想过为什么需要这样写,这时我们对代码的把控能力将会大大降低,产生了异常也将极难排查。并且,在参加面试时,很多语言上的细节问题也常常会问倒应聘者,这便造成了“老师傅栽跟头”,应聘者虽然编程经验丰富,却并没有取得自己理想的面试成绩。 本套课程我们将着重从这些语言语法细节处着手,更深入的为大家讲解其工作机制与内部原理。帮助大家在编写代码时做到知其然,也知其所以然。对于iOS相关岗位面试常考的语法细节,本套课程也会尽量全面的进行讲解。其中包括Obejctive-C语言中的属性修饰符,引用计数、动态性与运行时等相关内容,也包括Swift语言中的Optional类型、闭包、协议与泛型等。

    27 人正在学习 去看看 张益珲

0000_0000_0000_0101 PNChart-swift出现crash解决

Optional Swift 博客正式开张,团队成员:PMST、星夜暮晨、Jim、ICE。地址:optionalswift.cn。国外访问比较慢,你懂的。

这几天一直忙于项目的事情,由于要使用绘制折线图功能实现流量随时间的一个走势,因此我最后选用了PNChart-swfit。有两种方式可以使用:

  • Pod 安装。

  • 下载swift文件,直接导入引用即可。

我选择了后者,操作简便,此外由于是swift写的,使用起来灰常顺手。

至于框架代码方面,可能是因为刚写的,所以还有很多BUG,我就遇到一个ORZ….

bug说明

这和y轴的值有关,假如你的y轴的值是[10,32,12,17],那么最大值Max=32,Min=10,也就是Y轴上坐标从10开始到32结束,这个很好理解,因为你的数据区间就是在[Min,Max]之间,那么坐标值范围也应该是这个。

现在讨论这个情况。y轴值是[10,10,10,10],那么最大值Max=10,Min=10,两者相等,似乎没有什么事情,按照我们的想法是y轴就一个点喽,难道这是bug?显然我说的不是这个。而是在框架中会有一步除法。
public func strokeChart()函数中找到如下代码。

innerGrade = (yValue! - yValueMin) / (yValueMax - yValueMin)//除数≠0才可以 假如=0 就是NAN了无效 程序崩掉

解决方法

现在找到属性public var chartData:NSArray = []yValueMin = yMin;
yValueMax = yMax;
这一行 在之后添加判断代码:

if yValueMax == yValueMin
{
   yValueMax += 1  //加上1 当然也可以自己修改喽
}

最后

打算自己在这个框架基础上写个绘图框架,目前有这打算

2017-04-24 10:32:48 mardax 阅读数 130
  • iOS高级程序员进阶——语法雷区篇

    无论是Objective-C语言还是Swift语言,其都有非常巧妙的设计思想与丰富的语法特性。在编写程序时,编程语言是最基础的工具,工具是否用的顺手会直接影响到所编写程序的效率与质量。还有很多时候,一种场景我们知道应该如何编写代码却从来没有深入想过为什么需要这样写,这时我们对代码的把控能力将会大大降低,产生了异常也将极难排查。并且,在参加面试时,很多语言上的细节问题也常常会问倒应聘者,这便造成了“老师傅栽跟头”,应聘者虽然编程经验丰富,却并没有取得自己理想的面试成绩。 本套课程我们将着重从这些语言语法细节处着手,更深入的为大家讲解其工作机制与内部原理。帮助大家在编写代码时做到知其然,也知其所以然。对于iOS相关岗位面试常考的语法细节,本套课程也会尽量全面的进行讲解。其中包括Obejctive-C语言中的属性修饰符,引用计数、动态性与运行时等相关内容,也包括Swift语言中的Optional类型、闭包、协议与泛型等。

    27 人正在学习 去看看 张益珲

optional

optional是swift特有特征(oc没有),定义对象为optional表示对象可能有值,可能无值
  • optional:可能有值,可能无值
  • non-optional:任何时候都有值(即使值无意义),不存在无值

nil

  • 在oc中,nil表示指针,指向不存在对象,只能赋值给指针对象
  • 在swift中,nil表示optional无值,只能赋值给optional
注:未初始化optional默认为nil

forced unwrap optional

func forced_unwrap()
{
    var food1: Int? = 5
    var food2: Int? = nil
        
    print("food1 nil: \(food1 == nil), food1 not nil: \(food1 != nil)")
    print("food2 nil: \(food2 == nil), food2 not nil: \(food2 != nil)")
    print("food1:\(food1)")
    print("food1!: \(food1!)")
    print("food2:\(food2)")
    print("food2!: \(food2!)")
}
output:
food1 nil: false, food1 not nil: true
food2 nil: true, food2 not nil: false
food1:Optional(5)
food1!: 5
food2:nil
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
总结:
  • 可通过!=或==与nil比较确认optional有值或无值
  • 可通过惊叹号(!)获取optional值,这个行为称为forced unwrap optional,即强制性打开
  • optional有值,forced unwrap optional获取optional值,程序正常运行,optional无值,forced unwrap optional则runtime error,程序crash

optional binding

func optional_binding()
{
    let food1: Int? = 5
    let food2: Int? = nil
    print("food1: \(food1)")
    print("food2: \(food2)")
        
    let rice = food1
    let meat = food2
    print("rice: \(rice)")
    print("meat: \(meat)")
        
    if let rice = food1
    {
        print("rice: \(rice)")
    }
    else
    {
        print("rice value absent")
    }
        
    if let meat = food2
    {
        print("meat: \(meat)")
    }
    else
    {
        print("meat value absent")
    }
        
    let food3: Int? = 8
    let food4: Int? = 18
    let food5: Int? = 28
        
    if let rice = food3, let meat = food4 where rice != meat, let fruit = food5
    {
        print("rice: \(rice) meat: \(meat), fruit: \(fruit)")
    }
    else
    {
        print("some value absent")
    }
}
output:
food1: Optional(5)
food2: nil
rice: Optional(5)
meat: nil
rice: 5
meat value absent
rice: 8 meat: 18, fruit: 28
总结:
  • let(var) rice = food1成为语句和成为条件表达式一部分意义完全不同
  • let(var) rice = food1成为语句等同于let(var) rice: Int? = food1,只是简单定义和初始化,rice还是optional
  • let(var) rice = food1成为条件表达式一部分等同于若optional food1有值,let(var) rice:Int = food1!,且表达式值为true,若optional无值,表达式值为false,此即为optional binding,用代码表示Bool ret = (rice != nil ? rice = food1!,  true : false)
  • 多个optional binding可并存于条件表达式中,optional binding之间用逗号(,)分隔,optional binding和non-optional binding表达式之间用where分隔,non-optional binding和optional binding之间用逗号(,)分隔,此处逗号(,)等同于&&,where亦等同于&&,因此let rice = food3, let meat = food4 where rice != meat, let fruit = food5等同于let rice = food3 && let meat = food4 && rice != meat && let fruit = food5

implicitly unwrap optional

func implicit_unwrap()
{
    let food1:Int? = 5;
    let food2:Int! = 8;
    
    print("food1:\(food1)")
    print("food1!:\(food1!)")
    print("food2:\(food2)")
    print("food2!:\(food2!)")
}
output:
food1:Optional(5)
food1!:5
food2:8
food2!:8
总结:
  • 如果能确认optional一直有值,可定义为implicitly unwrap optional,这样可省略每次获取optional值时unwrap optional
  • implicitly unwrap optional定义使用惊叹号(!),而非问好(?),这是implicitly unwrap optional和normal unwrap optional的外观区别
  • implicitly unwrap optional本质还是normal unwrap optional,因此仍可把implicitly unwrap optional当normal unwrap optional使用,包括forced unwrap optional和optional binding
  • implicitly unwrap optional只是假设一直有值,但不确保一定有值,implicitly unwrap optional仍可能无值,若implicitly unwrap optional无值,获取implicitly unwrap optional值时仍会runtime error

Swift中的Optional解释

博文 来自: feng2qing

swift-Optional

阅读数 361

没有更多推荐了,返回首页