Skip to main content

Lazy var trong Swift


Xin chào, lại là mềnh đây. T_T hôm nay lại là một bài dịch mà mềnh học được trong lúc ngồi viết báo cáo thực tập. Mình nghĩ bài viết này sẽ khá hay cho các fresher như mình để hiểu sâu hơn về Swift và iOS. Rất mong các bạn ủng hộ.

Lazy var trong swift

Trong quá trình phát triển các sản phẩm iOS, chắc chắn rằng các developer luôn để ý tới việc số lượng bộ nhớ (amount of memory) mà app của chúng ta sử dụng. Nếu app của chúng ta quá phức tạp thì vấn đề về bộ nhớ là một trong những thách thức khó nhằn với các dev. Chính vì lẽ đó, các dev chúng ta phải rất cẩn thận với từng dòng code của mình và đương nhiên đặt ưu tiên hàng đâu cho việc tối ưu hoá cấp phát bộ nhớ. Chúng ta nên tránh việc khởi tạo hay làm gì đó vô tội vạ trừ khi điều đó rất là cần thiết, bởi đơn giản, những điều nó có thể gây khó khăn cho chúng ta về sau này trong việc fix bug hay thậm chí là gây ảnh hưởng tới performance của app.
Trong Swift có một kỹ thuật gọi là lazy giúp cho bạn trì hoãn việc khởi tạo đối tượng cho đến khi nó thật sự được gọi đến. Nếu không được gọi đến, nó sẽ không bao giờ chạy. Chính vì lẽ đó, nó sẽ giúp bạn khá nhiều trong việc tiết kiệm thời gian xử lý trong app.
Đây là định nghĩa của lazy được lấy từ doc của Apple:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
Bạn cũng nên để ý rằng, luôn dùng từ khoá lazy cho các đối tượng là biến(với từ khoá var), bởi vì giá trị ban đầu của nó không được lấy ra cho đến khi việc khởi tạo giá trị hoàn tất. Các biến hằng số (constants) thì luôn luôn phải có giá trị trước khi việc khởi tạo hoàn tất nên thành ra không được khởi tạo với lazy.
Chúng ta hãy đi vào ví dụ cho dễ hiểu nhé: Hãy tưởng tượng rằng bạn có 1 struct là InterviewCandidate. Nó có 1 biến optional bool để kiểm tra là thí sinh này (candidate) apply cho vị trí iOS hay Android. Biến string để mô tả hồ sơ cá nhân của thí sinh này được khai báo với từ khoá lazy. Nhìn vào dòng code dưới đây bạn sẽ hiểu, thí sinh apply ở vị trí iOS và biến giá trị lazy iOSResumeDescription sẽ được khởi tạo khi gọi print, còn biến androidResumeDescription sẽ bị nil.
import UIKit

struct InterviewCandidate {
    var isiOS:Bool?
    
    lazy var iOSResumeDescription: String = {
        return "I am an iOS developer"
    }()
    lazy var androidResumeDescription: String = {
        return "I am an android developer"
    }()
}

var candidateA = InterviewCandidate()
candidateA.isiOS = true

if candidateA.isiOS! {
    print(candidateA.iOSResumeDescription)
} else {
    print(candidateA.androidResumeDescription)
}
Đây là 1 ví dụ khá đơn giản. Nếu chúng ta có 1 class hay struct khá phức tạp chứ một đống biến computed (computed variable) và trả nó về giá trị từ 1 hàm đệ quy nào đó. Thử tưởng tượng bạn tạo khoảng 1000 objects như vậy thì chắc chắn 100% là vấn đề về performance và memory sẽ đập vào mặt bạn ngay.

Lazy Stored Property vs Stored Property

Có một vài lời ích mà 1 biến lazy nhỉnh sơn so với các biến thông thường:
  1. Closure đi kèm với các biến lazy chỉ được thực thi chỉ khi bạn sử dụng biến ấy. Vì thế, nếu như vì vài lí do nào đó nó không được sử dụng (Vd: có thể là do quyết định của user giống như androidResumeDescription ở đoạn code phần trên cũng vậy) bạn sẽ tránh được việc phân bổ và khởi tạo không cần thiết cho app.
  2. Bạn có thể gán giá trị 1 một biến đã được lưu trữ vào một biến lazy
  3. Bạn có thể dùng self bên trong closure của biến lazy. Nếu như dùng self bên trong closure của lazy, nên để ý và khai báo thêm [unowned self] để tránh việc làm tăng strong reference count lên 1.
import UIKit
import Foundation

class InterviewTest {
    var name: String
    lazy var greeting : String = { [unowned self] in
        return "Hello \(self.name)"
    }()
    
    init(name: String) {
        self.name = name
    }
}
let testObj = InterviewTest(name:"abhi")
testObj.greeting
Bạn có thể tham chiếu các biến cho dù bạn có dùng closure hay không. Ví dụ:
lazy var iOSResumeDescription = “I am an iOS developer”
Note: Các bạn phải nhớ rằng, mục đích chính của biến lazy là nó chỉ được compiler thực thi khi nó được gọi ra, sau đó thì giá trị của nó đã được lưu lại. Chính vì lẽ đó, nếu bạn gọi iOSResumeDescription lần nữa, thì bạn vẫn sẽ thấy giá trị cũ trước đó.

Các quy tắc của Lazy

  • Bạn không thể dùng lazy với let
  • Bạn chỉ có thể dùng lazy bên trong struct và class
  • Biến lazy không được khởi tạo lúc ban đầu nên nó sẽ gây không an toàn cho thread.
  • Bạn không thể dùng lazy chung với computed properties. Bởi vì các computed properties thường trả về giá trị mỗi lần chúng ta truy cập tới sau khi chúng thực thi các dòng code bên trong block.

Tài liệu tham khảo: https://medium.com/@abhimuralidharan/lazy-var-in-ios-swift-96c75cb8a13a

Comments

Popular posts from this blog

Swift GCD part 1: Thread safe singletons

Preview Singletons are entities, referenced to the same instance of a class from everywhere in your code. It doesn't matter if you like them or not, you will definitely meet them, so it's better to understand how they work. Constructing and handling a set of data doesn't seem to be a big challenge at first glance. The problems appear when you try to optimise the user experience with background work and your app starts acting weird. ??‍♂️ After decades of watching your display mostly with a blank face, you finally realize that your data isn't handled consistently by the manager because you're accessing it (running tasks on it) from multiple threads at the same time. So you really do have to deal with making your singletons thread safe. This article series is dedicated to thread handling using Swift. In the first part below you will get a comprehensive insight into som...

Thread safe singleton’s in Swift

What are singletons? — Singleton is design patterns which says that there should be only one instance of the class for the lifetime of the application. One the best example of Singleton is AppDelegate . How to write a singleton class ? class DefaultDict{ private var dict:[String:Any] = [:] public static let sharedManager = DefaultDict() private init(){ } public func set(value:Any,key:String){ dict[key] = value } public func object(key:String) -> Any?{ dict[key] } public func reset(){ dict.removeAll() } }   Testing singleton class under concurrent circumstances. We are going to write an example where we will set values in dict from various threads and even try to access some with different threads. When we do this we will encounter a crash. If you look closely it will be because of race condition and the crash will be on line set(value:Any,key:String) . class ViewController: UIViewController { ...

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...