Bài này được mình dịch từ đây: alisoftware.github.io

Bài này là 1 phần của series. Bạn có thể đọc các phần còn lại ở đây: phần 1, phần 1 bổ sung, phần 2, phần 3, phần 4

Nhắc lại phần trước

Trong phần 2, chúng ta đã biết cách sử dụng mapflatMap trên array để tránh các biến trung gian khi biến đổi và thay vào đó chúng ta đã sử dụng functional programming. [1]

Đây là code ở cuối của phần 2:

swift
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
class ListItem {
    var icon: UIImage?
    var title: String = ""
    var url: NSURL!
    
    static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
        guard let nonNilJsonData = jsonData,
            let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
            let jsonItems = json as? Array<NSDictionary>
            else {
                return []
        }
        
        return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
            guard let title = itemDesc["title"] as? String,
                let urlString = itemDesc["url"] as? String,
                let url = NSURL(string: urlString)
                else { return nil }
            let li = ListItem()
            if let icon = itemDesc["icon"] as? String {
                li.icon = UIImage(named: icon)
            }
            li.title = title
            li.url = url
            return li
        }
    }
}

Hôm nay chúng ta chỉ thay đổi đơn giản nhưng sẽ làm cho code thinner và Swift-er hơn.

Struct vs Class

Một lỗi khác mà người-mới-tìm-hiểu swift mắc phải trong đoạn code trên là bắt đầu với một class. Đó là điều dễ hiểu, bởi vì trong ObjC chúng ta sử dụng class ở khắp mọi nơi.

Không có gì sai với class. Bạn vẫn có thể sử dụng chúng ở Swift. Nhưng trong Swift, sử dụng struct là một phương pháp mạnh mẽ: nó không chỉ giới hạn trong 1 vùng nắm giữ values.

Struct ở Swift có khả năng tương tự như class - ngoại trừ thừa kế - nhưng thay vào đó là value-types trong khi class là reference-types, passed by reference thay vì bị sao chép, như trong Objective-C (và dấu * xấu xí ở khắp nơi)

Tôi sẽ không nói đến việc sử dụng struct và value types đối đầu với class và reference types: Tôi đề nghị bạn xem vấn đề này từ Andy Matuschak. Tôi không thể làm tốt hơn so với Andy!

Chuyển đổi class sang struct

Trong trường hợp của chúng ta, một struct có vẻ thích hợp hơn vì nó mang những giá trị, và không có ý định thay đổi (và sử dụng sao chép hơn là tham chiếu).

Ngoài ra, lợi thế của chuyển đổi sang một struct ở đây là nó có một constructor ngầm theo mặc định nếu bạn không khởi tạo giá trị cho nó. Như vậy chúng ta có thể dễ dàng xây dựng một ListItem sử dụng constructor mặc định của ListItem(icon: …, title: …, url: …)

Cuối cùng, bây giờ, chúng ta không thể tạo ra một ListItem bị nil bởi vì chúng ta loại bỏ các vấn đề hư hỏng dữ liệu, chúng ta có thể loại bỏ các giá trị mặc định "" cho title, nhưng quan trọng hơn là chúng ta có thể cứu pony cuối cùng 🐴 bằng cách chuyển đổi NSURL! sang NSURL. [2]

Code bây giờ sẽ như thế này:

swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct ListItem {
    var icon: UIImage?
    var title: String
    var url: NSURL
    
    static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
        guard let nonNilJsonData = jsonData,
            let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
            let jsonItems = json as? Array<NSDictionary> else { return [] }
        
        return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
            guard let title = itemDesc["title"] as? String,
                let urlString = itemDesc["url"] as? String,
                let url = NSURL(string: urlString)
                else { return nil }
            let iconName = itemDesc["icon"] as? String
            let icon = UIImage(named: iconName ?? "")
            return ListItem(icon: icon, title: title, url: url)
        }
    }
}

Bây giờ chúng ta chỉ cần tạo ra instance của ListItem như là bước cuối cùng. Bởi vì init mặc định của struct sẽ tạo giá trị mặc định cho các property, nếu bạn không tạo ra 1 func init riêng. Chúng ta có thể làm điều tương tự với class, nhưng với class chúng ta phải khai báo các init riêng.

Coalescing operator

Trong ví dụ trên, tôi cũng sử dụng một thủ thuật mới, sử dụng ?? operator để cung cấp cho một giá trị mặc định trong trường hợp iconNamenil.

operator ?? có một chút tương tự như opt ?: val của ObjC, ta đã biết opt ?? val sẽ trả về giá trị của opt nếu nó non-nil, và bằng val nếu optnil. Điều đó có nghĩa rằng nếu opt là kiểu T?, thì val phải là kiểu T.

Vì vậy, iconName ?? "" sẽ trả về "" khi iconNamenil, và kéo theo UIImage cũng là nil.

⚠️ Lưu ý ⚠️: Đây không phải là cách tốt nhất và ngắn gọn để xử lý một nil iconNamenil UIImage. Trong thực tế, nên sử dụng một hình ảnh trống. Nhưng đây là dịp để cho thấy sự tồn tại của ?? operator …

Kết luận

Chúng ta đã không làm được gì nhiều trong phần 3 này, chỉ thay đổi một class thành một struct.

Nhưng chúng ta đã cứu pony cuối cùng bằng cách loại bỏ !NSURL! 🎉 . Và bạn đã có nhiều thứ để đọc và học hỏi bằng cách xem bài viết của Andy về «Value Types»😃.

Phần 4, sẽ được về mapflatMap một lần nữa, nhưng lần này là trên Optionals.


[1]. Đúng là bạn đã sử dụng functional programming mà bạn không hề biết

[2]. NSURL! này tôi đã không giải quyết ngay từ đầu bởi vì tôi biết chắc rằng ta sẽ giải quyết nó ở phần này.