Dev With Ethan

Blog Lập trình cùng Ethan

[Swift] Kiểu giá trị Optional: Các trường hợp nâng cao

… tiếp theo bài viết [Swift] Kiểu giá trị Optional - Các cách khai báo & sử dụng

5. Method chaining

Trong Swift cũng như các ngôn ngữ hiện đại như Ruby, Python,… các lập trình viên rất thích sử dụng 1 cú pháp gọi là method chaining. Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
extension Int {
  func add(otherNum: Int) -> Int {
    return self + otherNum
  }
  
  func multiply(otherNum: Int) -> Int {
    return self * otherNum
  }
}

var number: Int = 10
number.add(2).multiply(5)                   // (10 + 2) * 5 = 60
Swift code snippet 1
Method chaining

Ở đây, ta viết thêm 2 phương thức add()multiply() cho kiểu Int để cộng và nhân 1 số nguyên với 1 số nguyên khác, kết quả trả ra 1 số nguyên (cách làm này gọi là viết Extension, tôi sẽ đề cập ở 1 bài khác). Sau khi khai báo 2 hàm, với mỗi số nguyên kiểu Int, ta có thể gọi add()mutiply() theo cú pháp number.add() hoặc number.multiply().

Điểm đặc biệt là do kết quả trả về vẫn là Int, nên với các công thức gồm nhiều phép tính, ta có thể gọi liên tiếp add()multiply() mà không cần phải sử dụng đến 1 biến để lưu trữ các kết quả trung gian.

6. Optional chaining

Giờ ta thêm 1 hàm nữa tên là divide(), có chức năng thực hiện phép chia:

1
2
3
4
5
6
7
8
9
10
11
extension Int {
  func divide(otherNum: Int) -> Int? {
    if otherNum == 0 {
      return nil
    }

    return self / otherNum
  }
}

number.divide(0)?.add(10)
Swift code snippet 2
Thêm hàm divide()

Phép chia cần 1 điều kiện là số chia (divisor) cần phải khác 0. Các trường hợp số chia bằng 0 đều dẫn đến lỗi chia cho 0 hoặc vô cùng. Nếu muốn trả về 1 kết quả nil, ta khai báo kiểu trả về của phương thức divide()Int?, tức là Int nhưng có thể nhận giá trị nil.

Lúc này, nếu muốn dùng method chaining, ta có 2 cách:

Cách thứ 1: Kiểm tra xem kết quả có bằng nil hay không và sử dụng unwrap:

1
2
3
4
5
var result = number.divide(0)

if result != nil {
  result = result!.add(10)
}
Swift code snippet 3
Unwrap bình thường

Cách thứ 2: Sử dụng optional chaining

1
2
3
number = 10
number.divide(0)?.add(10).multiply(5)         // nil
number.divide(3)?.add(10).multiply(5)         // 65
Swift code snippet 4
Optional chaining

Lúc này, Swift sẽ kiểm tra xem divide() có trả về giá trị nil hay không:

  • Nếu có, lập tức trả lại kết quả là nil, không quan tâm các hàm gọi sau có điều kiện là gì.
  • Nếu không, unwrap để lấy 1 giá trị Int và gọi tiếp các hàm ở sau.

7. Value casting

Như đã nói trong các bài trước, Swift là 1 ngôn ngữ hướng đối tượng, vậy nên nó có tính kế thừa, tức là 1 class có thể kế thừa từ 1 class khác. Ta cũng biết NSArrayNSDictionary đều là các class kế thừa từ NSObject:

1
2
3
4
5
6
7
public class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration {
  // ...
}

public class NSDictionary : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration {
  // ...
}
Swift code snippet 5
Khai báo NSArray & NSDictionary

Như vậy, ta hoàn toàn có thể khai báo 1 Dictionary như sau mà không gặp lỗi:

1
2
3
4
5
var json: [String: NSObject?] = [
  "array": NSArray(),
  "dict": NSDictionary(),
  "nil": nil
]
Swift code snippet 6
Khởi tạo Dictionary với giá trị nil

Khi đó, nếu gọi json["array"], ta sẽ nhận được 1 giá trị kiểu NSObject. Ta cần đổi nó về NSArray để gọi được các phương thức dành riêng cho NSArray, ví dụ như count(). Lúc này, ta phải làm 1 động tác gọi là ép kiểu (tiếng Anh: value casting):

1
2
let optionalArray = json["array"] as? NSArray
let unwrappedArray = json["array"] as! NSArray
Swift code snippet 7
Ép kiểu

Lúc này, optionalArray có kiểu NSArray?, còn unwrappedArray có kiểu NSArray. Tất nhiên, trong trường hợp cố gắng unwrap json["nil"]:

1
json["nil"] as! NSArray
Swift code snippet 8
Cố tình unwrap giá trị nil

ta sẽ gặp lỗi

Casting nil value error
Lỗi Casting nil value

Mời các bạn tải về file Optionals.playground để cùng xem các ví dụ về Optional trong Swift.