Skip to main content

Struct vs Classes

Khi làm việc trong Swift có khi nào bạn tự hỏi khi nào thì sử dụng Struct, khi nào thì sử dụng Classes không ?

bài viết trên medium:
https://medium.com/better-programming/struct-vs-classes-in-swift-the-differences-explained-1e164a22efa6

Cannot assign to property: function call returns immutable value.

Bạn có lẽ không phải là người đầu tiên quay lại sử dụng class khi gặp phải lỗi trên. Một structs dường như quá khó để làm việc cùng. Hy vọng sau khi đọc xong bài viết này bạn sẽ làm việc với structs dễ dàng hơn.

Class trong Swift là gì?
Một class là một kiểu tham chiếu (reference type) trong Swift. Nó có thể bao gồm:

. properties
. methods
. subscripts
. initializers
. protocol conformances
. extensions

Nó thường được miêu tả như một định nghĩa mẫu cho một đối tượng, như khởi tạo Article sau đây:

class ArticleClass {
    let title: String
    let url: URL
    var readCount: Int = 0

    init(title: String, url: URL) {
        self.title = title
        self.url = url
    }
}

Struct trong Swift là gì?
Một struct trong Swift là một kiểu tham trị (value type), giống như class nó cũng bao gồm:

. properties
. methods
. subscripts
. initializers
. protocol conformances
. extensions

Nó cũng có thể  tạo một định nghĩa cho một đối tượng, như sau:

struct ArticleStruct {
    let title: String
    let url: URL
    var readCount: Int = 0
}

Sự khác nhau giữa Class và Struct : 
Trong vị dụ trên xem ra Struct và Class hầu hết là giống nhau. Đó chính là vấn đề giữa chúng, nhìn chúng tương tự nhau. Tuy vậy có khá nhiều sự khác nhau quan trọng mà chúng ta phải nhận thức được

Kiểu tham chiếu và tham trị (value vs reference types)
Sự khác biệt quan trọng nhất là struct là tham trị trong khi class là tham chiếu. Sẽ có một bài viết riêng cho vấn đề này, nhưng một giải thích ngắn cũng đủ hiểu được vấn đề:

Tham chiếu tới một khởi tạo class sẽ chia sẻ dữ liệu, có nghĩa là một vài thay đổi trong class đó sẽ làm thay đổi trên các tham chiếu khác. Như trong ví dụ dưới đây:

let articleClass = ArticleClass(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!)
let articleClassCopy = articleClass

articleClass.readCount = 10
print(articleClassCopy.readCount) // Prints: 10

Một struct là một tham trị và sẽ tạo ra một copy duy nhất cho mỗi tham chiếu mới. Bạn có thể thấy sự khác nhau quan trọng như là readCount sẽ chỉ được thay đổi trong tham chiếu khởi tạo:

var articleStruct = ArticleStruct(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!, readCount: 0)
var articleStructCopy = articleStruct
 This could create nasty bugs which are hard to debug.
articleStruct.readCount = 10
print(articleStructCopy.readCount) // Prints: 0

Lợi ích việc đột biến có lợi
Với điều này, struct là đột biến an toàn, bạn có thể tin rằng sẽ không có phần nào khác trong ứng dụng của bạn thay đổi dữ liệu cùng lúc. Điều này làm cho việc sự dụng code có lợi ích đặc biệt trong môi trường multi-threaded nơi mà những thread khác nhau có thể thay đổi dữ liệu cùng lúc. Điều này có thể tạo lên những lỗi khó chịu và rất khó để debug.
Không có đột biến này thì structs và classes sẽ hoàn toàn giống nhau.

Structs và Constants
Một loại tham trị khác được sử dụng là Constants. Nếu bạn lanh lợi, bạn có thể thấy articleStruct đã được định nghĩa như là một biến thay thế let giống như chúng ta làm với articleClass. Nếu bạn sử dụng let để khai báo một struct, sẽ xảy ra lỗi như sau:


Một struct chỉ có thể được thay đổi nếu nó được định nghĩa như là một biến và nó chỉ update trong tham chiếu đầu tiên.

Structs có một hàm khởi tạo sẵn có 
Nếu bạn quay lại và so sánh 2 đoạn code trên, bạn sẽ thấy lớp ArticleClass có một hàm khởi tạo, cái được yêu cầu cho class. Structs sẽ có sẵn một hàm khởi tạo của nó.
Điều này trở lên tốt hơn với SE-242, cái đã được implemented trong Swift 5.1 và thêm thành phần khởi tạo cho struct. Điều này có nghĩa như sau:

// Before Swift 5.1 Memberwise initializers:
// Generated memberwise init: init(title: String, url: URL, readCount: Int)
let article = ArticleStruct(title: "", url: URL(string: "")!, readCount: 0)

// After Swift 5.1 Memberwise initializers, using the default 0 for read count
// Generated memberwise init: init(title: String, url: URL, readCount: Int = 0)
let article = ArticleStruct(title: "", url: URL(string: "")!)

Class cho phép kế thừa
Class có thể kế thừa từ class khác và với điều đó, nó hoạt động như một class abstract. Một ví dụ phổ biến là một custom view controller loại kế thừa từ UIViewController.
Nhưng với protocols trong Swift, kế thừa trở lên không cần thiết nữa, nó có thể thay thế với protocols. Protocols có thể sử dụng với cả hai classes và structs, trong khi kế thừa chỉ có thể sử dụng trong class mà thôi.

Class có thể huỷ bỏ 
Một class cho phép xử lý huỷ nó bằng cách sử dụng phương thức method deinit(). Khi bạn định nghĩa một phương thức tương tự deinit trong struct bạn sẽ gặp một thông báo lỗi như sau:
| Deinitializer may only be declared within a class

Vậy khi nào nên sử dụng class và khi nào thì nên sử dụng struct? 
Trong document Swift đã miêu tả như sau:
Những khả năng bổ xung cái mà classes hỗ trợ sẽ làm tăng thêm chi phí bộ nhớ. Như một cách tổng quát, structures sẽ tốt hơn và chỉ sử dụng classes khi chúng ta cảm thấy thích hợp hoặc cần thiết. Trong thực tế, điều này có nghĩa là hầu hết các trường hợp loại custom data mà bạn định nghĩa sẽ là structures và enumerations.

Điều này giải thích hầu hết các topics chúng ta tham khảo ở trên. Vì vậy, khi làm việc cùng Cocoa classes, bạn sẽ thường yêu cầu subclass từ NSObject cái mà yêu cầu bạn sử dụng một class.

Một danh sách gạch đầu dòng sẽ làm cho quyết định dễ dàng hơn.
Bạn nên sử dụng class khi mà:
_ So sánh hàm khởi tạo là cần thiết sử dụng ===
_ Chia sẻ dữ liệu thay đổi được yêu cầu
_ Khả năng tương tác Object-C được yêu cầu

Bạn nển sử dụng struct khi mà: 
_ So sánh hàm khởi tạo là cần thiết sử dụng ==
_ Bản copy là duy nhất với một trạng thái độc lập được yêu cầu
_ Dữ liệu được sử dụng trong multiple threads

Bất kì lời khuyên vàng bạn cần tới ?
Yes, tôi có! Có gắng sử dụng struct như là mặc định. Struct sẽ làm cho code của bạn trở lên dễ dàng hơn cả khi làm việc trong môi trường đa luồng, cái mà chúng ta thường sử dụng khi phát triển Swift.

Nếu bạn quyết định sử dụng class, hãy xem xét làm nó như là final và giúp đỡ trình duyệt bằng cách bảo nó là không có classes nào khác kế thừa từ class mà bạn định nghĩa.

Tổng kết 
Hy vọng rằng bạn đã có thể chọn lựa để sử dụng giữa class và struct. Sử dụng Struct không phải lúc nào cũng dễ dàng và có thể sử dụng, nhưng nó nên là một mặc định trong trương chình của bạn. Khi sử dụng struct thường xuyên hơn, bạn sẽ dần cảm thấy dễ dàng.

Comments

Popular posts from this blog

MVVM và VIPER: Con đường trở thành Senior

Trong bài viết trước chúng ta đã tìm hiểu về MVC và MVP để ứng dụng cho một iOS App đơn giản. Bài này chúng ta sẽ tiếp tục ứng dụng 2 mô hình MVVM và VIPER . Nhắc lại là ứng dụng của chúng ta cụ thể khi chạy sẽ như sau: Source code đầy đủ cho tất cả mô hình MVC, MVP, MVVM và VIPER các bạn có thể download tại đây . MVVM MVVM có thể nói là mô hình kiến trúc được rất nhiều các cư dân trong cộng đồng ưa chuộng. Điểm tinh hoa của kiến trúc này là ở ViewModel , mặc dù rất giống với Presenter trong MVP tuy nhiên có 2 điều làm nên tên tuổi của kiến trúc này đó là: ViewModel không hề biết gì về View , một ViewModel có thể được sử dụng cho nhiều View (one-to-many). ViewModel sử dụng Observer design pattern để liên lạc với View (thường được gọi là binding data , có thể là 1 chiều hoặc 2 chiều tùy nhu cầu ứng dụng). Chính đặc điểm này MVVM thường được phối hợp với các thư viện hỗ trợ Reactive Programming hay Event/Data Stream , đây là triết lý lập trình hiện đại và hiệu

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession can be found within the s

Fileprivate vs private: Giải thích sự khác biệt

Fileprivate vs private in Swift: The differences explained Fileprivate and private are part of the access control modifiers in Swift. These keywords, together with internal, public, and open, make it possible to restrict access to parts of your code from code in other source files and modules. The private access level is the lowest and most restrictive level whereas open access is the highest and least restrictive. The documentation of Swift will explain all access levels in detail to you, but in this blog post, I’m going to explain the differences between two close friends: fileprivate and private. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! Open access is the highest (least restrictive) access level and private access is the lowest (most restrictive) access level. This will improve readability and mak