Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on July 25, 2021

Property Wrapper trong 10 phút

iOS & Swift . Tutorials

Contents

  • Chuẩn bị
  • Property Wrapper
  • Khởi tạo
    • wrappedValue
    • Store Property
    • Computed Property
    • Init
    • Bổ sung
  • Sử dụng
    • Khai báo thuộc tính
    • Trong class/struct/enum
  • Property Wrapper với Generic
    • Khai báo
    • Sử dụng
  • projectedValue
    • Khai báo
    • Sử dụng
  • Lưu ý
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Bài viết này sẽ là một bài viết phụ đạo về kiến thức Swift cơ bản. Chủ đề lần này là Property Wrapper. Property Wrapper xuất hiện rất nhiều trong các bài viết về SwiftUI, Combine … Và bây giờ, chúng ta sẽ tìm hiểu về nó và cách sử dụng.

Đầu tiên, bạn cần phải biết được Swift cơ bản. Nếu bạn chưa biết hay đã quên rồi, thì có thể truy cập link dưới đây để tham khảo.

    • Basic Swift trong 10 phút

Nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Ngoài kiến thức lý thuyết cơ bản về Swift. Bạn cũng không cần chuẩn bị gì nhiều về mặt demo hay project. Bài viết chỉ tập trung vào code. Các ví dụ code bạn có thể sử dụng Playground để thực thi.

Để đảm bảo về mặt demo chạy tốt, thì bạn cần sử dụng Swift 5.1 trở lên.

Property Wrapper

Từ sự kiện WWDC 2019, Apple đã giới thiệu 2 framework SwiftUI & Combine. Và từ đó chúng ta thấy xuất hiện rất nhiều các từ khoá với tiền tố bắt đầu là @, dùng để khai báo biến thay thuộc tính. Ví dụ: @Published, @State, @Binding, @NSCopying … vâng vâng và mây mây. Tất cả chúng nó được gọi là,

Property Wrapper

Vậy nó là gì?

Đọc qua cái tên bạn cũng đã tự hiểu rồi. Chúng có ý nghĩa như sau:

  • Property : thuộc tính của class/struct/enum
  • Wrapper : vỏ bọc

Về cơ bản, Property Wrapper là một cấu trúc dữ liệu, nó sẽ đóng gói property, và bổ xung thêm một vài chức năng cho property đó. Property wrapper có thể là dạng struct, class hoặc enum.

Và từ Swift 5.1, Apple đã cho phép chúng ta có thể viết thêm các Custom Property Wrapper để phục vụ mục đích cá nhân của bạn.

Đây mới là mục đích chính của bài viết này. Nếu sử dụng các mẫu có sẵn thì chúng ta cũng không cần tìm hiểu gì thêm chi cho mệt não. Ahihi!

Khởi tạo

Tạm thời chia tay với lý thuyết, chúng ta bắt đầu học ngay khai báo và khởi tạo nó. Bạn chỉ cần sử dụng Playground là ổn rồi.

Property Wrapper

Như đã thấy ở trên, bạn sẽ bắt đầu bằng từ khoá @propertyWrapper , để khai báo 1 Property Wrapper. Còn lại bạn vẫn khai báo một struct như bình thường.

Ví dụ code như sau:

@propertyWrapper
struct Capitalized {
    
}

Mục đích của chúng ta sẽ là biến đổi 1 chuỗi String, thành 1 chuỗi String với các ký tự in hoa cho mỗi từ.

wrappedValue

Khi bạn đã hoàn thành khai báo rồi, với từ khoá @propertyWrapper nó sẽ yêu cầu bạn khai báo thêm một thuộc tính với tên là wrappedValue.

Bạn tiếp tục với ví dụ trên nhoé!

@propertyWrapper
struct Capitalized {

    var wrappedValue: String
    
}

Trong đó, kiểu dữ liệu của wrappedValue sẽ quyết định kiểu dữ liệu chung cho cả Property Wrapper. Trong ví dụ, chúng ta sử dụng kiểu dữ liệu là String.

Tuy nhiên, khai báo như thế này vẫn chưa đủ nha.

Bạn đã có được wrappedValue rồi, nó là một thuộc tính (Property) bình thường thôi. Tất nhiên, bạn sẽ có 2 loại thuộc tính trong Swift. Đó là:

  • Store Property
  • Computed Property

Chúng ta sẽ tiếp tục hoàn thiện thuộc tính này với 2 kiểu thiết kế như sau:

Store Property

Vì thuộc tính wrappedValue sẽ phải bị biển đổi trước khi sử dụng. Hay mỗi lần set/get thì bạn cũng phải xử lý dữ liệu cho đúng yêu cầu đề ra. Nên nếu sử dụng một Store Property để lưu trữ value thì bạn cân tạo thêm một thuộc tính nữa.

Xem ví dụ nha:

@propertyWrapper
struct Capitalized {
    
    private var value: String
    
    var wrappedValue: String

}

Trong đó,

  • value dùng để lưu trữ dữ liệu
  • wrapperdValue sẽ vẫn là thuộc tính chính để bên ngoài có thể truy cập và sử dụng.

Computed Property

Tình hình lúc này chúng ta phải biến wrappedValue từ Store Property thành Computed Property. Vì ta đã sử dụng value để lưu trữ. Ta xem qua ví dụ code tiếp nha.

@propertyWrapper
struct Capitalized {
    
    private var value: String
    
    var wrappedValue: String {
        get {
            value
        }
        set {
            value = newValue.capitalized
        }
    }

}

Init

Lúc này, Xcode vẫn báo lỗi, vì chúng ta thiếu một hàm khởi tạo để cung cấp giá trị ban đầu cho value. Bạn xem tiếp ví dụ nha.

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    private var value: String

    // MARK: -

    var wrappedValue: String {
        get {
            value
        }
        set {
            value = newValue.capitalized
        }
    }

    // MARK: - Initialization

    init(wrappedValue: String) {
        value = wrappedValue.capitalized
    }

}

Cũng khá là đơn giản, bạn chỉ cần nắm rõ các nguyên tắc khai báo một class/struct và cung cấp giá trị ban đầu. Hoặc các hàm khởi tạo để đảm bảo giá trị cho các thuộc tính.

Như vậy, bạn đã có một Property Wrapper rồi. EZ phải không nào!

Bổ sung

Cách trên chúng ta dùng wrappedValue là một Computed Property. Còn nếu sử dụng với Store Property thì chúng ta lợi dụng các Observer Property để thay đổi giá trị của nó.

Tham khảo code ví dụ sau:

@propertyWrapper
struct Capitalized2 {
    var wrappedValue: String {
        didSet {
            wrappedValue = wrappedValue.capitalized
        }
    }
}

Trong đó, chúng ta lợi dụng didSet để biến đổi ngay giá trị của wrappedValue. Cách này khá đơn giản, tuy nhiên sẽ có nhiều rủi ro. Nên bạn cũng phải cẩn thận & chú ý cách dùng này.

Sử dụng

Ở trên, bạn đã tạo được một Property Wrapper rồi. Tiếp theo, chúng ta sẽ dùng nó như thế nào.

Khai báo thuộc tính

Chúng ta dùng nó để khai báo một thuộc tính. Ví dụ như sau:

@Capitalized var name: String = "fx studio"

Bạn sẽ thấy:

  • @Capitalized được kết hợp từ @ và tên của Struct. Đó chính là tên của Property Wrapper của bạn.
  • Bạn đặt tên đó vào trước var hay let để bắt đầu khai báo một thuộc tính

Bạn để im Xcode 1 tí thì sẽ xuất hiện dòng lỗi này ngay.

Property wrappers are not yet supported in top-level code

Trong class/struct/enum

Vì hiện tại, Swift vẫn chưa hỗ trợ cho bạn khai báo các biến riêng hay các biến local trong function. Nên bạn phải sử dụng Property Wrapper ở trong khai báo của một class/struct/enum.

Bạn xem tiếp ví dụ nhoé:

struct Book {
    @Capitalized var author: String
    @Capitalized2 var title: String
}

Trong code trên, chúng ta lần lượt làm:

  • Define struct Book để sử dụng Property Wrapper.
  • Khai báo property author với kiểu String và wrapped trong Capitalized
  • Khai báo property title với kiểu String và wrapped trong Capitalized2

Sau đó, bạn hãy tạo một đối tượng Book và kiểm tra thử xem hoạt động hay không. Ví dụ như sau:

var book = Book(author: "fx studio", title: "a great book")

print(book.author)
print(book.title)

Thực thi và kết quả hiện ra không như mong đợi lắm.

Fx Studio
a great book

Đơn giản, vì với Capitalized2 thì bạn chưa cung cấp giá trị ban đầu cho nó. Do đó, didSet của bạn không hoạt động. Bạn cần phải lưu ý tới điểm chú này. Còn fix nó thì khá đơn giản.

var book = Book(author: "fx studio", title: "a great book")

print(book.author)
book.title.append("!")
print(book.title)

Property Wrapper với Generic

Nếu nâng cấp ví dụ của bạn lên đa năng và Property Wrapper của bạn không phụ thuộc vào một kiểu dữ liệu nhất định nào đó. Ta hãy sử dụng Generic trong khai báo.

Còn về Generic là gì, thì mình xin lỗi và hẹn bạn ở một bài viết nào đó sau này.

Khai báo

Ta hãy thử xem qua ví dụ sau, để tìm hiểu cách khai báo thêm Generic vào một Property Wrapper là như thế nào.

@propertyWrapper
struct MyDouble<T:Numeric> {
    private var value: T

    var wrappedValue: T {
        get {
            value * 2
        }
        set {
            value = newValue
        }
    }
    
    init(wrappedValue: T) {
        self.value = wrappedValue
    }
}

Trong đó:

  • Bạn sử dụng một Generic là <T:Numeric>, thì sẽ chấp nhận các kiểu dữ liệu là số (như Int, Float …)
  • Các khai báo các thuộc tính value & wrappedValue chúng ta sẽ dùng T
  • Hàm khởi tạo init cũng sẽ dùng T cho đồng bộ về mặt kiểu dữ liệu
  • Tất cả code logic thì tương tự như cách tạo ở trên

Sử dụng

Chúng ta xem qua ví dụ sau, để sử dụng Property Wrapper có Generic, vào trong khai báo của một class nhoé.

class SomeClass {
    @MyDouble var count: Float
    @MyDouble var total: Int = 10
    
    init(count: Float) {
        self.count = count
    }
}

var obj = SomeClass(count: 1)
print(obj.count)
print(obj.total)

obj.count = 19
print(obj.count)

Trong đó,

  • Chúng ta dùng một Property Wrapper để khai báo 2 thuộc tính với 2 kiểu dữ liệu khác nhau
  • count sẽ là Float và total sẽ là Int
  • Cách dùng thì vẫn như bình thường

Hãy thực thi đoạn code và cảm nhận kết quả nhoé.

projectedValue

Đây là một tính năng được thêm vào. Ngoài giá trị wrappedValue đã được bao bọc, thì Property Wrapper còn cung cấp thêm cho bạn một thuộc tính để bổ sung thêm giá trị. Họ gọi nó là projectedValue.

Nếu bạn tìm hiểu về SwiftUI thì sẽ thấy nó xuất hiện khá nhiều, tuy nhiên bạn sẽ không biết được nó như thế nào và có gì khác nhau. Ví dụ, bạn có 1 biến @State và khi bạn muốn truyền nó đi. Nhưng bên ngoài có thể truy cập và thay đổi được giá trị của nó. Bạn sẽ thêm một tiền đó $ vào trước tên biến @State đó.

Với việc thêm tiền tố $ , bạn đã xác nhận sử dụng một giá trị dự kiến của Property Wrapper. Đó là projectedValue.

Hai thuộc tính wrappedValue & projectedValue là khác nhau. Kể cả bản chất của chúng.

Khai báo

Ta sẽ lấy ví dụ ở trên và thêm một thuộc tính mới là projectedValue cho Struct Capitalized có sẵn như sau:

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    private var value: String

    // MARK: -
    var projectedValue = false
    var wrappedValue: String {
        get {
            value
        }
        set {
            value = newValue.capitalized
            projectedValue = (newValue.count >= 2 && newValue.count <= 32) ? true : false
        }
    }

    // MARK: - Initialization

    init(wrappedValue: String) {
        value = wrappedValue.capitalized
    }

}

Trong ví dụ trên,

  • projectedValue có kiểu dữ liệu là Bool
  • projectedValue sẽ kiểm tra xem giá trị chúng ta nhập vào có phù hợp với yêu cầu hay không. (số lượng kí tự từ 2 đến 32 kí tự)
  • Sau mỗi lần gán giá trị mới, ta lại tính toán và cập nhật lại giá trị của projectedValue

Đó cũng là cách bạn khai báo thêm một projectedValue vào Property Wrapper. Đặc tính nó như sau:

  • Mang ý nghĩa bổ trợ thêm giá trị cho wrappedValue
  • Có thể có cũng kiểu dữ liệu với wrappedValue hoặc khác kiểu dữ liệu
  • Cách khai báo thì vẫn giống như các thuộc tính bình thường khác.

Sử dụng

Bạn xem tiếp một ví dụ để sử dụng projectedValue. Vì nó cũng tương tự với wrappedValue nên chúng ta cũng nhanh chóng nắm bắt được cách sử dụng nó.

class NewBook {
    @Capitalized var author: String
    @Capitalized var title: String
    
    init(author: String, title: String) {
        self.author = author
        self.title = title
    }
}

var newbook = NewBook(author: "f", title: "a great book2")

print(newbook.author)
print(newbook.$author)

newbook.author.append("x studio")
print(newbook.author)
print(newbook.$author)

Trong đó,

  • Bạn khai báo class NewBook và các thuộc tính cần thiết author và title
  • Bạn tiến hành kiểm tra xem các giá trị wrappedValue & projectedValue
  • Sau đó thay đổi nội dung của author và tiến hành kiểm tra lại

Bạn hãy thực thi đoạn code và cảm nhận rõ hơn nha. Bây gì, các bạn đang tìm hiểu SwiftUI thì cũng cảm thấy được an tâm phần nào rồi nhoé.

Lưu ý

Chúng ta có vài lưu ý nhỏ khi sử dụng Property Wrapper như sau:

  • Không sử dụng với Error Handling.
  • Để tạo ra một typealias bằng tên Property Wrapper là không được. Bạn sẽ gặp khá nhiều vất vả khi cố dưa nó vào.
  • Có thể bạn sử dụng 2 Property Wrapper để khai báo cho một thuộc tính. Tuy nhiên, thứ tự khai báo sẽ ảnh hưởng tới giá trị của thuộc tính. Và không khuyến kích việc này.
  • Nó sẽ phụ thuộc vào Class/Struct/Enum. Bạn không thể khai báo các biến độc lập hay các biến cục bộ trong function.
  • Nó cũng không phải kiểu dữ liệu, nên bạn không dùng vào khai báo các tham số cho các function được.
  • Bạn sẽ khó tìm ra các documents liên quan tới mỗi Property Wrapper. Hầu như tất cả chúng đều là phần hỗ trợ thêm từ các Class/Struct/Enum có sẵn. Giúp bạn tiện sử dụng mà thôi.
  • Nó sẽ tăng thêm thời gian Complicate của Swift. Vì Swift sẽ phải nội suy khá nhiều cho các khai báo này.

Tạm kết

  • Khái niệm về Property Wrapper
  • Cách khai báo & cách sử dụng
  • Phân biệt giữa wrappedValue & protectedValue
  • Các lưu ý khi sử dụng

 

Okay! Tới đây, mình xin kết thúc bài viết về Property Wrapper này. Bài viết mang tính chất cơ bản và giới thiệu cho bạn làm quen mà thôi. Còn có rất nhiều các tác dụng mà nó mang lại. Hi vọng bài viết sẽ giúp ý được ít nhiều cho bạn. Và nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bạn có thể checkout code tại đây.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares53

Related Posts:

  • Image View trong 10 phút - SwiftUI Notes #26
    Image View trong 10 phút - SwiftUI Notes #26
  • Generics trong 10 phút - Swift
    Generics trong 10 phút - Swift
  • Text View trong 10 phút - SwiftUI Notes #25
    Text View trong 10 phút - SwiftUI Notes #25
  • Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
    Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
Tags: basic ios tutorial, Swift, SwiftUI
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • Text View trong 10 phút - SwiftUI Notes #25
    Text View trong 10 phút - SwiftUI Notes #25
  • Convenience Initializer trong 10 phút
    Convenience Initializer trong 10 phút
  • Task & Task Group trong 10 phút - Swift 5.5
    Task & Task Group trong 10 phút - Swift 5.5
  • Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
    Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
  • Guard Keyword trong 10 phút
    Guard Keyword trong 10 phút

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!