setNeedsDisplay(): ماهیت روش و مکانیسم ترسیم مجدد در iOS

نویسنده: IT Sectr منتشر شده: 2026-07-20 زمان مطالعه: 6 دقیقه

setNeedsDisplay() — این یک روش نمونه UIView است که نما را به عنوان نیازمند ترسیم مجدد علامت‌گذاری می‌کند. بر خلاف فراخوانی مستقیم draw(_:)، setNeedsDisplay() فقط پرچم dirty را برای نما تنظیم می‌کند و کنترل را بلافاصله برمی‌گرداند. سیستم این درخواست را در نزدیک‌ترین چرخه رندر پردازش می‌کند که امکان ترکیب چندین فراخوانی در یک عملیات ترسیم را فراهم می‌کند. طبق Apple Documentation (2025)، ترسیم مجدد با تأخیر از طریق setNeedsDisplay() تنها روش صحیح به‌روزرسانی محتوای UIView سفارشی است.

نکات اصلی

  • setNeedsDisplay() — درخواست ترسیم مجدد با تأخیر UIView، draw(_:) را همزمان فراخوانی نمی‌کند
  • dirty flag — پرچم داخلی نما که توسط setNeedsDisplay() تنظیم و پس از draw(_:) بازنشانی می‌شود
  • setNeedsDisplay(_:) — بارگذاری با CGRect برای ترسیم مجدد جزئی فقط ناحیه مشخص شده
  • displayIfNeeded() — ترسیم مجدد همزمان اجباری، برای به‌روزرسانی فوری استفاده می‌شود
  • چرخه رندر — سیستم نماهای dirty را در پایان run loop پردازش می‌کند و چندین درخواست را در یک مرحله ترکیب می‌کند

setNeedsDisplay() چیست

setNeedsDisplay() — یک روش UIView است که به سیستم سیگنال می‌دهد محتوای نما قدیمی شده و نیاز به رندر مجدد دارد. این روش پارامتری نمی‌پذیرد و void برمی‌گرداند. پس از فراخوانی، نما به عنوان dirty علامت‌گذاری می‌شود و در نزدیک‌ترین چرخه ترسیم، سیستم draw(_:) را برای این نما فراخوانی می‌کند.

ویژگی کلیدی ناهمزمانی است. چندین فراخوانی متوالی setNeedsDisplay() برای یک نما منجر به فراخوانی‌های متعدد draw(_:) نمی‌شود — سیستم آنها را در یک عملیات ترکیب می‌کند. این برای عملکرد حیاتی است: اگر ویژگی نما چندین بار در یک فریم تغییر کند، draw(_:) فقط یک بار فراخوانی می‌شود.

ترسیم مجدد با تأخیر چگونه کار می‌کند

مکانیسم ترسیم مجدد با تأخیر بر اساس چرخه رندر UIKit است. هنگامی که setNeedsDisplay() فراخوانی می‌شود، سیستم پرچم داخلی layer.needsDisplay را به true تنظیم می‌کند. در پایان run loop جاری، سیستم از همه لایه‌های dirty عبور کرده و برای هر کدام draw(_:) فراخوانی می‌کند.

Run loop و مراحل رندر

UIKit run loop شامل فاز update است که در آن نماهای dirty پردازش می‌شوند. در این فاز، در صورت لزوم layoutSubviews() فراخوانی می‌شود، سپس draw(_:) برای همه نماهای علامت‌گذاری شده اجرا می‌شود. پس از اتمام ترسیم، نتیجه برای ترکیب از طریق Core Animation به GPU منتقل می‌شود.

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

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

        // پس‌زمینه
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // نوار پیشرفت
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

در این مثال، progressView progress را به‌روزرسانی می‌کند و هر setter به طور خودکار setNeedsDisplay() را فراخوانی می‌کند. سیستم منتظر پایان run loop می‌ماند و draw(_:) را با مقدار جاری progress فراخوانی می‌کند. اگر progress سه بار در یک فریم تغییر کند، draw(_:) فقط یک بار — با آخرین مقدار — اجرا می‌شود.

setNeedsDisplay() در مقابل setNeedsDisplay(_:)

روش setNeedsDisplay(_:) پارامتر rect از نوع CGRect را می‌پذیرد که ناحیه ترسیم مجدد را محدود می‌کند. این امکان ترسیم مجدد فقط بخش تغییر یافته نما را فراهم می‌کند، نه کل آن. تفاوت عملکرد هنگام کار با نماهای بزرگ که تغییرات ناحیه کوچکی را تحت تأثیر قرار می‌دهند قابل توجه می‌شود.

ویژگیsetNeedsDisplay()setNeedsDisplay(_:)
پارامترنداردCGRect — ناحیه ترسیم مجدد
ناحیه ترسیم مجددکل bounds نمافقط مستطیل مشخص شده
عملکردپایین‌تر در نماهای بزرگبالاتر در تغییرات جزئی
پارامتر rect در draw(_:)کل boundsمستطیل مشخص شده
زمان استفادهتغییر کل محتواتغییرات نقطه‌ای (مکان‌نما، highlight)

برای گرافیک پیچیده، توصیه می‌شود ابتدا کل صحنه را در زمینه offscreen رسم کنید، سپس setNeedsDisplay(_:) را فقط برای ناحیه تغییر یافته فراخوانی کنید. این یک تکنیک استاندارد در برنامه‌های طراحی و ویرایشگرهای گرافیکی در iOS است.

displayIfNeeded() در مقابل setNeedsDisplay()

displayIfNeeded() — یک روش همزمان است که بلافاصله ترسیم مجدد همه نماهای dirty در سلسله‌مراتب را بدون منتظر ماندن برای پایان run loop شروع می‌کند. بر خلاف setNeedsDisplay() که فقط پرچم را تنظیم می‌کند، displayIfNeeded() اجرای فوری draw(_:) را مجبور می‌کند.

Apple توصیه می‌کند از displayIfNeeded() فقط در موارد ضروری استفاده کنید — مثلاً قبل از ایجاد اسکرین‌شات یا هنگام تولید همزمان تصویر برای UIGraphicsImageRenderer. در سناریوهای معمولی، مدل ناهمزمان از طریق setNeedsDisplay() ترجیح داده می‌شود زیرا thread اصلی را مسدود نمی‌کند.

swift
    // ناهمزمان — توصیه شده
someView.setNeedsDisplay()

    // همزمان — فقط در صورت لزوم
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) بلافاصله فراخوانی شد

فراخوانی displayIfNeeded() بدون setNeedsDisplay() قبلی اثری نخواهد داشت — این روش فقط نماهایی را پردازش می‌کند که قبلاً به عنوان dirty علامت‌گذاری شده‌اند. اگر هیچ نمایی نیاز به ترسیم مجدد نداشته باشد، displayIfNeeded() بدون فراخوانی draw(_:) پایان می‌یابد.

بهینه‌سازی فراخوانی‌های setNeedsDisplay()

فراخوانی‌های مکرر setNeedsDisplay() می‌توانند عملکرد را کاهش دهند، به ویژه در هنگام انیمیشن. استراتژی‌های اصلی بهینه‌سازی را بررسی می‌کنیم.

ترکیب تغییرات از طریق مدل

به جای فراخوانی setNeedsDisplay() در هر تغییر یک ویژگی جداگانه، تغییرات را جمع کنید و پس از اعمال همه به‌روزرسانی‌ها یک بار روش را فراخوانی کنید. مثلاً هنگام تغییر رنگ، اندازه و موقعیت عنصر — پس از تنظیم همه ویژگی‌ها یک بار setNeedsDisplay() را فراخوانی کنید.

استفاده از CALayer برای ویژگی‌های قابل انیمیشن

اگر ویژگی می‌تواند از طریق CALayer انیمیشن داده شود (backgroundColor, opacity, position, transform)، به جای draw(_:) از Core Animation استفاده کنید. ویژگی‌های CALayer بدون فراخوانی draw(_:) روی GPU به‌روزرسانی می‌شوند که 60 FPS بدون بار CPU فراهم می‌کند.

به‌روزرسانی دسته‌ای از طریق CATransaction

برای گروه‌بندی چندین تغییر در یک مرحله رندر از CATransaction استفاده کنید. این امکان ترکیب چندین setNeedsDisplay() را در یک عملیات draw(_:) در پایان تراکنش فراهم می‌کند.

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

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

CATransaction.commit()

از displayIfNeeded() سوءاستفاده نکنید — ترسیم مجدد همزمان thread اصلی را مسدود کرده و می‌تواند باعث افت فریم شود. فقط برای عملیاتی استفاده کنید که نتیجه ترسیم قبل از پایان run loop جاری حیاتی است.

سؤالات متداول

آیا می‌توان به جای setNeedsDisplay() مستقیماً draw(_:) را فراخوانی کرد؟

خیر، فراخوانی مستقیم draw(_:) ممنوع است. setNeedsDisplay() به درستی نما را به عنوان dirty علامت‌گذاری می‌کند و سیستم draw(_:) را در لحظه مناسب چرخه رندر فراخوانی می‌کند. فراخوانی مستقیم draw(_:) مکانیسم کش را نادیده می‌گیرد و می‌تواند به حالت ناهماهنگ منجر شود.

در صورت فراخوانی‌های متعدد setNeedsDisplay() چه اتفاقی می‌افتد؟

چندین فراخوانی setNeedsDisplay() برای یک نما توسط سیستم در یک فراخوانی draw(_:) ترکیب می‌شوند. این به این دلیل رخ می‌دهد که روش فقط dirty flag را تنظیم می‌کند که تنها پس از اجرای واقعی draw(_:) در پایان run loop بازنشانی می‌شود.

setNeedsDisplay() چه تفاوتی با setNeedsLayout() دارد؟

setNeedsDisplay() ترسیم مجدد محتوای نما را درخواست می‌کند (فراخوانی draw(_:)). setNeedsLayout() چیدمان مجدد subviewها را درخواست می‌کند (فراخوانی layoutSubviews()). layoutSubviews() می‌تواند منجر به تغییر اندازه شود که به نوبه خود ممکن است باعث ترسیم مجدد شود.

چه زمانی از setNeedsDisplay(_:) استفاده کنیم؟

setNeedsDisplay(_:) باید در تغییرات نقطه‌ای استفاده شود: حرکت مکان‌نما، تغییر رنگ ناحیه انتخاب شده، به‌روزرسانی بخشی از نمودار. این کار ناحیه ترسیم مجدد را محدود کرده و عملکرد را در مقایسه با ترسیم مجدد کامل کل نما افزایش می‌دهد.

آیا تغییر frame به طور خودکار setNeedsDisplay() را فراخوانی می‌کند؟

به طور پیش‌فرض، تغییر frame یا bounds setNeedsDisplay() را فراخوانی نمی‌کند. سیستم فقط نما را جابجا می‌کند. اگر در هنگام تغییر اندازه نیاز به ترسیم مجدد است، ویژگی contentMode را به .redraw تنظیم کنید — در این صورت UIKit در هر تغییر bounds به طور خودکار setNeedsDisplay() را فراخوانی می‌کند.

خلاصه

  • setNeedsDisplay() — روش ناهمزمان UIView برای درخواست ترسیم مجدد با تأخیر از طریق تنظیم dirty flag
  • setNeedsDisplay(_:) — بارگذاری با CGRect برای ترسیم مجدد جزئی فقط ناحیه مشخص شده، عملکرد را بهینه می‌کند
  • displayIfNeeded() — روش همزمان برای ترسیم مجدد فوری نماهای dirty، فقط در موارد ضروری استفاده شود
  • Run loop — سیستم نماهای dirty را در پایان چرخه جاری پردازش می‌کند و فراخوانی‌های متعدد را در یک مرحله draw(_:) ترکیب می‌کند
  • CALayer — برای ویژگی‌های قابل انیمیشن (position, opacity) به جای draw(_:) از Core Animation استفاده کنید — این به مراتب کارآمدتر است
  • CATransaction — چندین تغییر را در یک چرخه رندر گروه‌بندی کرده و تعداد فراخوانی‌های draw(_:) را کاهش می‌دهد
  • contentMode = .redraw — فراخوانی خودکار setNeedsDisplay() را هنگام تغییر bounds نما فعال می‌کند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید