setNeedsDisplay(): bản chất của phương thức và cơ chế vẽ lại trong iOS

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

setNeedsDisplay() là một phương thức instance của UIView đánh dấu view là cần được vẽ lại. Không giống như gọi trực tiếp draw(_:), setNeedsDisplay() chỉ thiết lập cờ dirty cho view và trả lại quyền điều khiển ngay lập tức. Hệ thống xử lý yêu cầu này trong chu kỳ kết xuất gần nhất, cho phép kết hợp nhiều lệnh gọi thành một thao tác vẽ duy nhất. Theo Tài liệu Apple (2025), vẽ lại trì hoãn thông qua setNeedsDisplay() là cách chính xác duy nhất để cập nhật nội dung của UIView tùy chỉnh.

Những Điểm Chính

  • setNeedsDisplay() — một yêu cầu vẽ lại trì hoãn cho UIView, không gọi draw(_:) đồng bộ
  • cờ dirty — một cờ nội bộ của view được thiết lập bởi setNeedsDisplay() và được xóa sau draw(_:)
  • setNeedsDisplay(_:) — một overload với CGRect để vẽ lại một phần chỉ khu vực được chỉ định
  • displayIfNeeded() — vẽ lại đồng bộ bắt buộc, được sử dụng để cập nhật ngay lập tức
  • Chu kỳ kết xuất — hệ thống xử lý các view dirty ở cuối run loop, kết hợp nhiều yêu cầu thành một lượt duy nhất

setNeedsDisplay() là gì

setNeedsDisplay() là một phương thức UIView báo hiệu cho hệ thống rằng nội dung của view đã cũ và cần được kết xuất lại. Phương thức không nhận tham số và trả về void. Sau khi gọi, view được đánh dấu là dirty, và trong chu kỳ vẽ gần nhất, hệ thống gọi draw(_:) cho view này.

Đặc điểm chính là bất đồng bộ. Nhiều lệnh gọi setNeedsDisplay() liên tiếp cho cùng một view sẽ không dẫn đến nhiều lệnh gọi draw(_:) — hệ thống kết hợp chúng thành một thao tác duy nhất. Điều này cực kỳ quan trọng cho hiệu suất: nếu một thuộc tính view thay đổi nhiều lần trong một khung hình, draw(_:) chỉ được gọi một lần.

Cách hoạt động của vẽ lại trì hoãn

Cơ chế vẽ lại trì hoãn dựa trên chu kỳ kết xuất UIKit. Khi setNeedsDisplay() được gọi, hệ thống thiết lập cờ nội bộ layer.needsDisplay thành true. Ở cuối run loop hiện tại, hệ thống duyệt qua tất cả các lớp dirty và gọi draw(_:) cho mỗi lớp.

Run loop và các bước kết xuất

Run loop UIKit bao gồm một giai đoạn cập nhật trong đó các view dirty được xử lý. Trong giai đoạn này, layoutSubviews() được gọi nếu cần, sau đó draw(_:) cho tất cả các view được đánh dấu. Sau khi hoàn tất vẽ, kết quả được chuyển đến GPU thông qua Core Animation để tổng hợp.

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // Nền
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // Thanh tiến trình
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

Trong ví dụ này, progressView cập nhật progress và mỗi setter tự động gọi setNeedsDisplay(). Hệ thống chờ đến cuối run loop và gọi draw(_:) với giá trị progress thực tế. Nếu progress thay đổi ba lần trong một khung hình, draw(_:) chỉ thực thi một lần — với giá trị cuối cùng.

setNeedsDisplay() vs setNeedsDisplay(_:)

Phương thức setNeedsDisplay(_:) nhận một tham số rect kiểu CGRect, giới hạn khu vực vẽ lại. Điều này cho phép vẽ lại chỉ phần đã thay đổi của view, thay vì toàn bộ view. Sự khác biệt về hiệu suất trở nên rõ rệt khi làm việc với các view lớn nơi các thay đổi ảnh hưởng đến một khu vực nhỏ.

Đặc điểmsetNeedsDisplay()setNeedsDisplay(_:)
Tham sốKhông cóCGRect — khu vực vẽ lại
Khu vực vẽ lạiToàn bộ bounds của viewChỉ hình chữ nhật được chỉ định
Hiệu suấtThấp hơn với view lớnCao hơn với thay đổi một phần
Tham số rect trong draw(_:)Toàn bộ boundsHình chữ nhật được chỉ định
Khi nào sử dụngThay đổi toàn bộ nội dungThay đổi điểm (con trỏ, tô sáng)

Đối với đồ họa phức tạp, nên vẽ toàn bộ cảnh trong một bối cảnh ngoài màn hình trước, sau đó gọi setNeedsDisplay(_:) chỉ cho khu vực đã thay đổi. Đây là kỹ thuật tiêu chuẩn trong các ứng dụng vẽ và trình chỉnh sửa đồ họa trên iOS.

displayIfNeeded() so với setNeedsDisplay()

displayIfNeeded() là một phương thức đồng bộ kích hoạt ngay lập tức việc vẽ lại tất cả các view dirty trong hệ thống phân cấp, mà không chờ đến cuối run loop. Không giống như setNeedsDisplay() chỉ thiết lập một cờ, displayIfNeeded() buộc thực thi ngay lập tức draw(_:).

Apple khuyến nghị chỉ sử dụng displayIfNeeded() trong các trường hợp cực đoan khi kết quả vẽ cần ngay lập tức — ví dụ, trước khi chụp ảnh màn hình hoặc trong quá trình tạo hình ảnh đồng bộ cho UIGraphicsImageRenderer. Trong các tình huống thông thường, mô hình bất đồng bộ qua setNeedsDisplay() được ưa chuộng hơn vì nó không chặn luồng chính.

swift
    // Bất đồng bộ — được khuyên dùng
someView.setNeedsDisplay()

    // Đồng bộ — chỉ khi cần thiết
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) được gọi ngay lập tức

Gọi displayIfNeeded() mà không có setNeedsDisplay() trước đó sẽ không có tác dụng — phương thức chỉ xử lý các view đã được đánh dấu là dirty. Nếu không có view nào cần vẽ lại, displayIfNeeded() hoàn thành mà không gọi draw(_:).

Tối ưu hóa các lệnh gọi setNeedsDisplay()

Các lệnh gọi setNeedsDisplay() thường xuyên có thể làm giảm hiệu suất, đặc biệt là trong quá trình hoạt hình. Hãy xem xét các chiến lược tối ưu hóa chính.

Gộp các thay đổi thông qua một mô hình

Thay vì gọi setNeedsDisplay() mỗi khi một thuộc tính riêng lẻ thay đổi, hãy tích lũy các thay đổi và gọi phương thức một lần sau khi áp dụng tất cả các cập nhật. Ví dụ, khi thay đổi màu sắc, kích thước và vị trí của một phần tử — hãy gọi setNeedsDisplay() một lần sau khi thiết lập tất cả các thuộc tính.

Sử dụng CALayer cho các thuộc tính có thể hoạt hình

Nếu một thuộc tính có thể được hoạt hình thông qua CALayer (backgroundColor, opacity, position, transform), hãy sử dụng Core Animation thay vì draw(_:). Các thuộc tính CALayer được cập nhật trên GPU mà không gọi draw(_:), cung cấp 60 FPS mà không tải CPU.

Cập nhật hàng loạt qua CATransaction

Để gộp nhiều thay đổi thành một lượt kết xuất duy nhất, hãy sử dụng CATransaction. Điều này cho phép kết hợp nhiều lệnh gọi setNeedsDisplay() thành một thao tác draw(_:) duy nhất ở cuối giao dịch.

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

Đừng lạm dụng displayIfNeeded() — vẽ lại đồng bộ chặn luồng chính và có thể gây giảm khung hình. Chỉ sử dụng nó cho các thao tác mà kết quả vẽ là quan trọng trước khi run loop hiện tại kết thúc.

Các Câu Hỏi Thường Gặp

Tôi có thể gọi draw(_:) trực tiếp thay vì setNeedsDisplay() không?

Không, việc gọi trực tiếp draw(_:) bị cấm. setNeedsDisplay() đánh dấu view là dirty một cách chính xác và hệ thống gọi draw(_:) vào thời điểm thích hợp của chu kỳ kết xuất. Gọi trực tiếp draw(_:) bỏ qua cơ chế lưu đệm và có thể dẫn đến trạng thái không nhất quán.

Điều gì xảy ra với nhiều lệnh gọi setNeedsDisplay()?

Nhiều lệnh gọi setNeedsDisplay() cho cùng một view được hệ thống kết hợp thành một lệnh gọi draw(_:) duy nhất. Điều này xảy ra vì phương thức chỉ thiết lập cờ dirty, chỉ được xóa sau khi thực thi thực tế draw(_:) ở cuối run loop.

setNeedsDisplay() khác setNeedsLayout() như thế nào?

setNeedsDisplay() yêu cầu vẽ lại nội dung của view (gọi draw(_:)). setNeedsLayout() yêu cầu sắp xếp lại các subview (gọi layoutSubviews()). layoutSubviews() có thể dẫn đến thay đổi kích thước, từ đó có thể kích hoạt vẽ lại.

Khi nào nên sử dụng setNeedsDisplay(_:)?

setNeedsDisplay(_:) nên được sử dụng cho các thay đổi điểm: di chuyển con trỏ, thay đổi màu của vùng được chọn, cập nhật một phần đồ thị. Điều này giới hạn khu vực vẽ lại và cải thiện hiệu suất so với việc vẽ lại toàn bộ view.

Việc thay đổi frame có tự động gọi setNeedsDisplay() không?

Theo mặc định, thay đổi frame hoặc bounds không gọi setNeedsDisplay(). Hệ thống chỉ di chuyển view. Nếu cần vẽ lại khi kích thước thay đổi, hãy đặt thuộc tính contentMode thành .redraw — khi đó UIKit sẽ tự động gọi setNeedsDisplay() mỗi khi bounds thay đổi.

Tổng Kết

  • setNeedsDisplay() — một phương thức bất đồng bộ của UIView để yêu cầu vẽ lại trì hoãn bằng cách thiết lập cờ dirty
  • setNeedsDisplay(_:) — một overload với CGRect để vẽ lại một phần chỉ khu vực được chỉ định, tối ưu hiệu suất
  • displayIfNeeded() — một phương thức đồng bộ để vẽ lại ngay lập tức các view dirty, chỉ sử dụng khi thực sự cần thiết
  • Run loop — hệ thống xử lý các view dirty ở cuối chu kỳ hiện tại, kết hợp nhiều lệnh gọi thành một lượt draw(_:) duy nhất
  • CALayer — cho các thuộc tính có thể hoạt hình (position, opacity) hãy sử dụng Core Animation thay vì draw(_:) — hiệu quả hơn nhiều
  • CATransaction — gộp nhiều thay đổi vào một chu kỳ kết xuất, giảm số lượng lệnh gọi draw(_:)
  • contentMode = .redraw — kích hoạt tự động gọi setNeedsDisplay() khi bounds của view thay đổi

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