کالبدشکافی باگ•۹ دقیقه مطالعه•۲۴ اسفند ۱۴۰۴

راز کرش‌های ناگهانی در سافاری موبایل: کالبدشکافی رم بیت‌مپ، لیمیت‌های GPU و افکت‌های بلور در iOS

چرا وب‌سایتی که روی کروم دسکتاپ با ۶۰ فریم اجرا می‌شود و حجم فایل‌های AVIF آن کم است، روی آیفون ناگهان کرش می‌کند؟ بررسی عمیق مکانیزم Jetsam، سقف بافت ۱۶K در متال و خطایابی لایه‌های WebKit.

Ghazaleh Niazi

غزاله نیازی ثابت

توسعه‌دهنده ارشد فرانت‌اند

راز کرش‌های ناگهانی در سافاری موبایل: کالبدشکافی رم بیت‌مپ، لیمیت‌های GPU و افکت‌های بلور در iOS

معمای کرش در سافاری: وقتی دسکتاپ بی‌نقص است اما آیفون تسلیم می‌شود

شاید برای شما هم پیش آمده باشد: وب‌سایتی توسعه داده‌اید که در گوگل کروم و سافاری دسکتاپ مثل ساعت با سرعت ۶۰ فریم بر ثانیه اجرا می‌شود. تصاویر را با فرمت مدرن AVIF فشرده کرده‌اید، نمره عملکرد در Lighthouse بالای ۹۵ است و هیچ خطای جاوااسکریپتی در کنسول وجود ندارد.

اما به محض اینکه سایت را روی آیفون در سافاری موبایل باز می‌کنید، پس از چند ثانیه اسکرول، تب ناگهان سفید می‌شود، صفحه خودبه‌خود ریفرش شده و در نهایت پیام خطای بدنام «This webpage was reloaded because a problem occurred» ظاهر می‌شود.

عجیب‌تر این است که ابزارهای مانیتورینگ مثل Sentry هیچ خطایی ثبت نمی‌کنند! چرا؟ چون این یک خطای جاوااسکریپتی نیست، بلکه یک کرش سخت در سطح سیستم‌عامل و موتور WebKit است.

چرا Sentry کرش‌های سافاری موبایل را نمی‌بیند؟

در معماری iOS، هر تب مرورگر سافاری در یک پروسه ایزوله به نام WebContent اجرا می‌شود. وقتی این پروسه از سقف مجاز حافظه سیستم‌عامل فراتر می‌رود، مکانیزم محافظتی Jetsam در کرنل iOS سیگنال مرگبار SIGKILL 9 را مستقیماً به پروسه می‌فرستد. جاوااسکریپت فرصت اجرای هیچ بلاک catch یا رویداد onError را پیدا نمی‌کند و تب در یک میلی‌ثانیه منهدم می‌شود.

تله‌ی بزرگ دیکود بیت‌مپ: تفاوت حجم فایل روی دیسک با حجم در رم

بسیاری از توسعه‌دهندگان تصور می‌کنند فرمت‌های فشرده مدرن مثل AVIF یا WebP حافظه رم مرورگر را هم نجات می‌دهند. این یک سوءتفاهم بزرگ مهندسی است!

فرمت AVIF فقط و فقط حجم فشرده فایل روی شبکه و دیسک را کاهش می‌دهد. اما کارت گرافیک و موتور رندرینگ مرورگر نمی‌توانند فایل فشرده را مستقیماً روی نمایشگر رسم کنند؛ مرورگر باید تصویر را Decode کرده و تمام پیکسل‌های آن را به صورت یک ماتریس خام بیت‌مپ (RGBA) در حافظه RAM بارگذاری کند.

فرمول اشغال رم توسط هر تصویر باز شده در مرورگر به این صورت است:

سقف مرگبار حافظه در iOS Jetsam

در سیستم‌عامل iOS، سقف مصرف رم مجاز برای هر تب سافاری در مدل‌های استاندارد آیفون حدود ۲۵۰ تا ۳۵۰ مگابایت است. لود همزمان تنها چند اسکرین‌شات بلند در یک صفحه، فوراً تب را از سقف فراتر برده و منجر به اجرای حکم اعدام پروسه توسط Jetsam می‌شود!

memory-formula.ts
typescript
// فرمول محاسبه اشغال حافظه رم توسط هر عکس در تمام مرورگرها:
// Memory (Bytes) = Pixel Width × Pixel Height × 4 Bytes (Red, Green, Blue, Alpha)

// مثال ۱: اسکرین‌شات دسکتاپ با ارتفاع ۱۰,۶۲۶ پیکسل
const width = 3024;
const height = 10626;
const rawRamBytes = width * height * 4; // 128,527,104 Bytes ≈ 128.5 MB RAM!

// مثال ۲: اسکرین‌شات فول‌پیج با ارتفاع ۲۸,۸۰۰ پیکسل
const mobileShotHeight = 28800;
const mobileShotWidth = 704;
const mobileRamBytes = mobileShotWidth * mobileShotHeight * 4; // ≈ 81 MB RAM!

// اگر صفحه اصلی شما ۴ کاور از این تصاویر داشته باشد:
// مجموع رم مصرفی = ۱۲۸ + ۱۰۸ + ۱۱۰ + ۱۰۵ = ۴۵۱ مگابایت رم خام!

برخورد با دیوار سخت‌افزاری پردازنده‌های گرافیکی اپل (سقف ۱۶K پیکسل در Metal)

علاوه بر حافظه رم خام، یک محدودیت سخت‌افزاری بسیار حیاتی در چیپست‌های گرافیکی اپل وجود دارد: سقف ابعاد بافت دو‌بعدی (MAXTEXTURESIZE).

در تمام پردازنده‌های گرافیکی آیفون (فریم‌ورک Metal) و وب‌کیت، ابعاد یک لایه بافت گرافیکی نمی‌تواند از ۱۶,۳۸۴ پیکسل فراتر برود. اگر تصویری با ارتفاع ۲۱,۴۳۱ پیکسل یا ۲۸,۸۰۰ پیکسل داشته باشید، کارت گرافیک امکان ایجاد تکسچر واحد برای آن را ندارد.

هنگامی که این تصویر وارد یک کامپوننت متحرک یا انیمیشن اسکرول شود، پردازنده گرافیکی دچار کرش داخلی (GPU Process Panic) شده و کل صفحه از بین می‌رود.

سناریوی واقعی پروداکشن
تجربه میدانی

گزارش حادثه پروداکشن: کرش شات‌های اسکرولی ۲۸,۰۰۰ پیکسلی

بستر و محیط:

صفحه اختصاصی نمونه‌کار با کامپوننت‌های تعاملی AutoScroll که طرح‌های ریسپانسیو و قاب گوشی آیفون را نمایش می‌دادند.

حادثه و اثر روی کاربر:

کاربران با اسکرول کردن به بخش لندینگ‌ها روی آیفون، با کرش لحظه‌ای سافاری و پیام خطای وب‌پیج ریفرش شد مواجه شدند.

تحلیل ریشه‌ای و دیاگنوز:

بررسی ابعاد با ابزار sips نشان داد فایل resposive-mobile-example.avif دارای ارتفاع ۲۸,۸۰۰ پیکسل و case-study-desktop دارای ارتفاع ۲۵,۸۱۰ پیکسل بوده‌اند که بسیار فراتر از سقف ۱۶,۳۸۴ پیکسلی پردازنده گرافیکی اپل است.

راهکار مهندسی اعمال‌شده:

ری‌سایز کردن ایمن ابعاد تصاویر بلند به حداکثر ارتفاع ۷,۵۰۰ پیکسل با حفظ کامل وضوح رتینا و ایمن‌سازی لایه‌ها.

درس کلیدی: هیچ فایلی در وب، تحت هیچ شرایطی نباید عرض یا ارتفاعی فراتر از ۱۶,۳۸۴ پیکسل داشته باشد.

شتاب‌دهنده‌های گرافیکی بی‌موقع: بهینه‌سازی will-change در صفحات لمسی

ویژگی CSS شتاب‌دهنده گرافیکی یعنی will-change: transform یک شمشیر دولبه است. این خاصیت به مرورگر دستور می‌دهد المان را از جریان اصلی صفحه جدا کرده و یک لایه مستقل اختصاصی در حافظه کارت گرافیک (VRAM) برای آن رزرو کند.

وقتی روی دسکتاپ هستید، این کار اسکرول را نرم می‌کند؛ اما در آیفون که حافظه VRAM مشترک با رم اصلی دستگاه است، ایجاد چند لایه شتاب‌یافته برای تصاویری با ارتفاع چندهزار پیکسل باعث بلعیده شدن صدها مگابایت حافظه اختصاصی می‌شود.

AutoScrollImageDisplay.tsx
tsx
// ❌ الگوی آسیب‌پذیر: ایجاد لایه گرافیکی دائمی روی تمام دیوایس‌ها
<div
  style={{
    transform: `translate3d(0, -${scrollPos}px, 0)`,
    willChange: "transform", // در آیفون باعث رزرو دائم رم و کرش بافر می‌شود
  }}
/>

// ✅ الگوی پایدار: غیرفعال‌سازی هوشمند لایه دائمی در صفحات لمسی
<div
  style={{
    transform: `translate3d(0, -${scrollPos}px, 0)`,
    willChange: isTouchDevice ? "auto" : "transform", // مرورگر تنها در زمان نیاز لایه می‌سازد
  }}
/>

بمباران رم با انباشت افکت‌های بلور روی لایه‌های ثابت (CSS Blur Stacking)

فیلترهای CSS مانند blur() و backdrop-filter یکی از زیباترین ترندهای طراحی مدرن هستند، اما از نظر محاسبات گرافیکی بی‌نهایت سنگین‌اند.

مرورگر برای اعمال blur-[150px] روی المان‌های شناور و همزمان رندر کردن نوبار fixed با backdrop-blur-xl، مجبور است در هر فریم اسکرول کاربر، بافرهای موقتی رندر خارج از صفحه (Offscreen Render Buffers) تولید کند.

در کنار چندصد مگابایت رم اشغال‌شده توسط تصاویر، این بافرهای موقتی ضربه نهایی را به آیفون وارد کرده و باعث ریفرش اجباری صفحه می‌شوند.

راهکار طلایی برای استایل‌های بلور در موبایل

در Tailwind CSS همیشه برای افکت‌های بلور سنگین از کلاس‌های واکنش‌گرا استفاده کنید: blur-3xl sm:blur-[140px]. همچنین لایه‌های نوری کمکی و شناور در پس‌زمینه را در صفحات موبایل با hidden sm:block مخفی کنید تا سخت‌افزار موبایل انرژی خود را صرف محتوای اصلی کند.

چک‌لیست طلایی مهندسی فرانت‌اند برای پایداری در سافاری موبایل

برای اینکه مطمئن شوید وب‌اپلیکیشن یا پورتفولیوی شما هرگز روی آیفون دچار کرش حافظه نمی‌شود، این ۴ قانون طلایی را رعایت کنید:

۱. کاورها را جدا کنید: هرگز اسکرین‌شات بلند چندصفحه‌ای را مستقیماً به عنوان کاور کارت صفحه اصلی قرار ندهید. همیشه یک نسخه تامبنیل اختصاصی سبک (مثلاً ۱۲۰۰x۷۵۰) بسازید.

۲. قانون ۱۶K را نقض نکنید: مطمئن شوید ابعاد هیچ تصویری (عرض یا ارتفاع) به سقف ۱۶,۳۸۴ پیکسل سخت‌افزاری پردازنده‌های گرافیکی نزدیک هم نمی‌شود.

۳. از will-change هوشمندانه استفاده کنید: روی دیوایس‌های لمسی لایه‌های شتاب‌یافته گرافیکی را به صورت دائمی در حافظه قفل نکنید.

۴. بلورهای پس‌زمینه را سبک کنید: لایه‌های بلور با شعاع بالای ۱۰۰ پیکسل را در صفحات کوچک مهار کنید.

برچسب‌ها:#Safari Mobile#WebKit#iOS#GPU#Performance#Memory Leaks
آزمون سنجش یادگیری

آزمون درک مفاهیم: پایداری WebKit و مدیریت رم

دانش خود را درباره دلایل کرش حافظه در سافاری موبایل و تفاوت‌های رندرینگ GPU بسنجید.

برای سنجش یادگیری خود روی گزینه بالا کلیک کنید تا ۵ سوال تخصصی مرتبط با این مقاله برای شما نمایش داده شود.