Contents
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.
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.
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ệuwrapperdValue
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
haylet
để 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ùngT
- Hàm khởi tạo
init
cũng sẽ dùngT
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!
Related Posts:
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
Fan page
Tags
Recent Posts
- Prompt Engineering trong 10 phút
- Một số ví dụ sử dụng Prompt cơ bản khi làm việc với AI
- Prompt trong 10 phút
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
You may also like:
Archives
- December 2024 (3)
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- 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)