Linh Tạ

Lazy cùng swift

Lazy property là những biến mà giá trị khởi tạo của chúng sẽ chỉ được tính toán trong lần sử dụng đầu tiên. Nói một cách dễ hiểu, lazy property trì hoãn việc khởi tạo tới lúc bạn thực sự cần đến chúng. Đặc tính này của chúng mang lại rất nhiều lợi ích trong trường hợp việc khởi tạo tốn nhiều thời gian và bộ nhớ. Không chỉ vậy, chúng còn có rất nhiều tác dụng bất ngờ khác nếu được sử dụng đúng cách.

Lazy property và hiệu năng

Các biến lazy chỉ được khởi tạo một lần duy nhất và sẽ tồn tại cho đến hết vòng đời của object. Lưu ý rằng lazy property luôn ở dạng var chứ không phải let bởi các constant let bắt buộc phải có giá trị khởi tạo ban đầu.


struct Person {
    lazy var name = "Linh" //name thuộc dạng String

    lazy var age: Int = {  //age thuộc dạng Int
        return 24
    }()
}

Sức mạnh của các biến lazy thực sự được bộc lộ khi bạn phải xử lý các tác vụ nặng và ngốn tài nguyên. Giả sử bạn có một object với nhiệm vụ mã hoá các tin nhắn văn bản, việc khởi tạo object này có thể mất đến 1s.


class EncryptDataManager {
    //có nhiệm vụ mã hoá data
    //tốn 1s để khởi tạo
}

//Sử dụng store property
class ViewController: UIViewController {
    let encrypter = EncryptDataManager() //khởi tạo ngay lập tức
}

//Sử dụng computed property
class ViewController: UIViewController {
    var encrypter: EncryptDataManager {
        return EncryptDataManager() //khởi tạo mới với mỗi lần sử dụng
    }
}

Hai loại biến thông dụng nhất là store property và computed property đều bộc lộ nhiều nhược điểm. Nếu sử dụng store property để chứa EncryptDataManager thì nó sẽ được khởi tạo ngay lập tức. Việc này là thừa thãi và làm ảnh hưởng hiệu năng app nếu bạn không phải mã hoá tin nhắn ngay hoặc chỉ dụng chức năng mã hoá trong một vài trường hợp cụ thể. Còn với computed property thì một EncryptDataManager mới sẽ được tạo ra mỗi lần bạn sử dụng nó. Điều này có nghĩa bạn sẽ mất tới 10s nếu sử dụng biến này 10 lần…


//Lazy property
//Chỉ thực hiện việc khởi tạo khi được sử dụng và khởi tạo một lần duy nhất
class ViewController: UIViewController {
    lazy var encrypter: EncryptDataManager = {
        let encrypter = EncryptDataManager()
        //Gán các thuộc tính của encrypter
        //...
        return encrypter
    }()
}

Ở đây, việc sử dụng lazy property là cách làm hiệu quả và hợp lý nhất bởi bạn vừa có thể trì hoãn khởi tạo encrypter cho đến khi bạn thực sự cần nó, vừa có thể lưu lại giá trị của encypter cho những lần sử dụng tiếp theo. Điều này giúp giảm những tính toán không cần thiết và khiến của app mượt hơn đáng kể. Trong thực tế không phải lúc nào bạn cũng sẽ gặp những object ngốn tài nguyên như thế này, tuy nhiên việc để ý đến memory và tối ưu các tác vụ nặng là vô cùng quan trọng vì nó ảnh hưởng trực tiếp tới trải nghiệm người dùng và chất lượng của sản phẩm bạn tạo ra.

Dùng lazy property tạo subview

Ngoài giúp tăng hiệu năng, lazy property còn có thể được sử dụng để tạo các subview bằng code. Với các biến store property thông thường, bạn sẽ không thể làm gì nhiều ngoài việc khởi tạo subview từ các function init mặc định. Để có thể set các giá trị khác của subview như thêm gesture recognizer, thay backgroundColor, hay gán delegate, bạn bắt buộc phải thực hiện trong viewDidLoad hoặc một function nào đó.


//Cách làm thông thường
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.backgroundColor = .red
    }
}

Tuy nhiên, code của bạn sẽ dễ dàng trở nên rối rắm nếu bạn phải khởi tạo và setup nhiều subview. Việc set màu nền, đặt placeholder, tăng giảm font chữ, hay cung cấp giá trị mặc định cho các thuộc tính của subview không thuộc về viewDidLoad hay các function phụ mà nên được thực hiện ngay sau khi subview ấy được khởi tạo.

Bằng cách sử dụng các biến lazy, bạn có thể nhóm các bước trong việc init và cung cấp giá trị mặc định của subview vào một block có tổ chức và liên quan đến nhau thay vì ở trong các setup function thừa thãi.


class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.backgroundColor = .red

        return tableView
    }()

    lazy var sendButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Send", for: .normal)
        button.addTarget(self, action: #selector(handleSendMessage), for: .touchUpInside)

        return button
    }()
}

Nếu bạn để ý, mình đã sử dụng self trong thân closure khởi tạo của tableViewsendButton. Đây tác dụng đặc biệt của việc trì hoãn khởi tạo. Về bản chất, việc trì hoãn này như một lời hứa với compiler rằng đến lúc cần truy cập các biến lazy này thì self - ở đây là ViewController đã tồn tại và do vậy mình có thể dùng các thuộc tính cũng như function của ViewController một cách bình thường.

Những điểm cần lưu ý

Mình hy vọng rằng bài viết này đã giúp các bạn hiểu rõ hơn về bản chất và các cách sử dụng cơ bản của lazy property. Nếu được dùng đúng cách, lazy property sẽ giúp tăng hiệu năng của app và khiến code của bạn dễ đọc và gọn gàng hơn rất nhiều.