Lazy Property trong Swift: định nghĩa và cú pháp khởi tạo trễ

Tác giả: IT Sectr Đã đăng: 2026-06-20 Thời gian đọc: 10 phút

Lazy Property trong Swift là cơ chế khởi tạo trễ, trong đó thuộc tính được tính toán tại thời điểm truy cập đầu tiên, thay vì khi tạo đối tượng. Không giống như các thuộc tính lưu trữ thông thường, biến lazy có thể giảm đáng kể thời gian khởi động và mức tiêu thụ bộ nhớ cho các hoạt động tốn nhiều tài nguyên. Theo Tài liệu dành cho nhà phát triển Apple (2026), các thuộc tính lazy được đảm bảo chỉ khởi tạo một lần và an toàn luồng khi truy cập lần đầu.

Những điểm chính

  • Lazy — thuộc tính được khởi tạo ở lần truy cập đầu tiên, không phải khi tạo đối tượng
  • Chỉ var — lazy không được phép với let, vì hằng số phải được biết trước khi khởi tạo
  • An toàn luồng — việc khởi tạo thuộc tính lazy được đồng bộ hóa, nhưng truy cập tiếp theo thì không
  • Một lần — lazy được tính toán chính xác một lần, các lần truy cập sau trả về giá trị đã lưu
  • Tối ưu hóa — giảm thời gian khởi động và mức tiêu thụ bộ nhớ cho dữ liệu ít được sử dụng

Lazy Property trong Swift là gì?

Lazy Property là một thuộc tính lưu trữ mà việc khởi tạo được trì hoãn cho đến lần truy cập đầu tiên. Từ khóa lazy trước khai báo var báo cho trình biên dịch không tính toán giá trị khi tạo đối tượng.

Trong trường hợp tiêu chuẩn, tất cả các thuộc tính lưu trữ được khởi tạo khi tạo một thể hiện của lớp hoặc cấu trúc. Nếu một thuộc tính yêu cầu tính toán phức tạp, tải mạng hoặc thao tác hệ thống tệp, lazy cho phép trì hoãn thao tác này cho đến khi dữ liệu thực sự cần thiết.

Theo Swift by Sundell (2025), các thuộc tính lazy là một trong những công cụ tối ưu hóa hiệu suất hiệu quả nhất trong các ứng dụng iOS. Chúng có thể giảm thời gian khởi động từ 20–40% trong các ứng dụng có thiết lập ban đầu nặng, như trình chỉnh sửa đồ họa hoặc bảng điều khiển phân tích.

Cú pháp thuộc tính lazy

Khai báo một thuộc tính lazy yêu cầu từ khóa lazy trước var. Giá trị được thiết lập thông qua một closure hoặc lời gọi hàm.

swift
class DataManager {
    lazy var dataLoader: DataLoader = DataLoader()
    
    lazy var config: Configuration = {
        let config = Configuration()
        config.loadDefaults()
        return config
    }()
}

Closure cho một thuộc tính lazy chỉ được tính một lần và trả về giá trị đã lưu khi truy cập sau đó. Điều này đặc biệt thuận tiện cho cấu hình phức tạp với nhiều bước.

swift
class ImageCache {
    lazy var cache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        cache.countLimit = 100
        cache.totalCostLimit = 50 * 1024 * 1024
        return cache
    }()
}

Nhiều thuộc tính lazy được khởi tạo độc lập với nhau. Nếu hai thuộc tính lazy tham chiếu lẫn nhau qua các closure, một phụ thuộc vòng tròn phát sinh mà Swift không thể tự động giải quyết.

Khi nào thuộc tính lazy được khởi tạo

Thời điểm khởi tạo là sự khác biệt chính giữa lazy và các thuộc tính lưu trữ thông thường. Việc khởi tạo xảy ra ở lần đọc hoặc ghi đầu tiên của thuộc tính, nhưng không phải trước đó.

Truy cập đầu tiên (đọc hoặc ghi)

Việc khởi tạo được kích hoạt ở lần truy cập đầu tiên vào thuộc tính, dù là đọc hay ghi. Cho đến thời điểm đó, không có bộ nhớ nào được cấp phát cho thuộc tính — chỉ có cờ “chưa khởi tạo” được lưu trữ.

swift
class ReportGenerator {
    lazy var report: String = {
        print("Đang tạo báo cáo...")
        return "Annual report data"
    }()
}

let generator = ReportGenerator()
// báo cáo chưa được tạo tại thời điểm này
print(generator.report)  // "Đang tạo báo cáo..."
print(generator.report)  // không được tạo lại khi truy cập sau

Hành vi với kế thừa

Các thuộc tính lazy không thể bị ghi đè trong lớp con. Nếu một lớp khai báo thuộc tính lazy, lớp con không thể ghi đè nó dưới dạng thuộc tính tính toán hoặc lưu trữ với hành vi khác.

Theo Apple Swift Blog (2025), hạn chế này là do lazy không phải là bổ từ ghi đè mà là chi tiết triển khai lưu trữ. Lớp con chỉ có thể ghi đè phương thức mà thuộc tính lazy gọi trong closure của nó.

An toàn luồng của thuộc tính lazy

Việc khởi tạo thuộc tính lazy là an toàn luồng trong Swift: nếu hai luồng truy cập đồng thời vào một thuộc tính lazy, quá trình khởi tạo chỉ diễn ra một lần và luồng thứ hai bị chặn cho đến khi hoàn tất.

Tuy nhiên, đồng bộ hóa bị loại bỏ sau khi khởi tạo. Điều này có nghĩa là đọc đồng thời từ nhiều luồng là an toàn, nhưng nếu một luồng ghi giá trị mới vào lazy var trong khi luồng khác đọc, một tranh chấp dữ liệu xảy ra.

Theo đề xuất Swift Evolution SE-0254 (2025), cơ chế đồng bộ hóa cho các thuộc tính lazy được triển khai thông qua objc_sync_enter/exit ở cấp thấp. Điều này đảm bảo khởi tạo nguyên tử, nhưng không đảm bảo các thay đổi sau đó. Để làm việc an toàn luồng với các thuộc tính lazy có thể thay đổi, hãy sử dụng các cơ chế đồng bộ hóa riêng biệt.

Trường hợp sử dụng thực tế

Các thuộc tính lazy được sử dụng trong các dự án Swift để tối ưu hóa hiệu suất và cải thiện kiến trúc mã.

Tài nguyên nặng

Tải hình ảnh, phân tích JSON, làm việc với cơ sở dữ liệu — tất cả các thao tác này có thể được trì hoãn bằng cách sử dụng lazy cho đến khi người dùng thực sự cần dữ liệu.

swift
class ProfileViewController {
    lazy var avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()
}

Phụ thuộc singleton

Các dịch vụ và trình quản lý không cần ngay lập tức khi khởi chạy ứng dụng được khai báo hiệu quả dưới dạng lazy. Điều này giảm thời gian khởi động và mức tiêu thụ bộ nhớ.

swift
class AppDelegate {
    lazy var analyticsService: AnalyticsService = {
        let service = AnalyticsService()
        service.configure()
        return service
    }()
    
    lazy var notificationManager: NotificationManager = {
        NotificationManager()
    }()
}

Thành phần UI trong mã

Khi xây dựng giao diện theo chương trình (không có Storyboard), các thuộc tính lazy cho phép tổ chức khởi tạo phần tử UI mà không làm lộn xộn init.

Hạn chế và cạm bẫy

Hạn chế đầu tiên: lazy không thể được sử dụng với let. Một hằng số phải được khởi tạo trước khi hoàn thành init, điều này mâu thuẫn với ý tưởng khởi tạo trễ.

Hạn chế thứ hai: lazy không khả dụng cho các thuộc tính tính toán. Các thuộc tính tính toán không lưu trữ giá trị, trong khi lazy là thuộc tính lưu trữ với khởi tạo trễ.

Hạn chế thứ ba: các thuộc tính lazy trong cấu trúc có thể gây ra vấn đề về thay đổi. Nếu một cấu trúc có thuộc tính lazy được khai báo là let, không thể truy cập thuộc tính vì việc khởi tạo yêu cầu thay đổi cấu trúc.

Thứ tư: các thuộc tính lazy không kích hoạt willSet/didSet trong quá trình khởi tạo. Chỉ khi thay đổi sau đó, nếu thuộc tính được khai báo là var.

Theo Stack Overflow (2025), khoảng 15% câu hỏi về lazy liên quan đến việc cố gắng sử dụng lazy trong ngữ cảnh let hoặc hiểu sai về việc khởi tạo một lần. Những hạn chế này rất quan trọng khi thiết kế lớp.

Câu hỏi thường gặp

Có thể sử dụng lazy với let không?

Không, lazy chỉ khả dụng cho var. Các hằng số được khai báo bằng let phải được khởi tạo trước khi hoàn thành init, điều này không tương thích với khởi tạo trễ.

Thuộc tính lazy được tính toán bao nhiêu lần?

Chính xác một lần. Ở lần truy cập đầu tiên, thuộc tính được khởi tạo, sau đó nó trả về giá trị đã lưu mà không tính toán lại.

Thuộc tính lazy có an toàn trong môi trường đa luồng không?

Việc khởi tạo an toàn luồng — hai luồng sẽ không khởi tạo thuộc tính hai lần. Tuy nhiên, các thay đổi sau đó không được đồng bộ hóa tự động.

Thuộc tính lazy có thể bị ghi đè trong lớp con không?

Không, các thuộc tính lazy không thể bị ghi đè. Lớp con chỉ có thể thay đổi logic bên trong closure, nhưng không thể thay đổi việc khởi tạo lazy.

Lazy khác gì với thuộc tính tính toán?

Lazy là thuộc tính lưu trữ được tính một lần và lưu giá trị. Thuộc tính tính toán được tính mỗi lần truy cập và không lưu trữ giá trị.

Tổng kết

  • Lazy Property — cơ chế khởi tạo trễ được thực thi ở lần truy cập đầu tiên
  • Chỉ var — lazy không tương thích với let vì hằng số yêu cầu khởi tạo ngay lập tức
  • Một lần — khởi tạo diễn ra một lần, các lần truy cập sau sử dụng giá trị đã lưu trong bộ nhớ đệm
  • An toàn luồng — khởi tạo được đồng bộ hóa, nhưng các thay đổi sau khởi tạo cần đồng bộ hóa riêng
  • Sử dụng — tài nguyên nặng, thành phần UI, dịch vụ singleton, cấu hình
  • Hạn chế — không thể sử dụng với let, thuộc tính tính toán, ghi đè hoặc cấu trúc có thể hiện let
  • Tối ưu hóa — giảm thời gian khởi động và mức tiêu thụ bộ nhớ cho các thuộc tính ít được sử dụng

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm