کش تصاویر با قابلیت استفاده مجدد در سوئیفت — از صفر تا صد



تعداد بازدید ها:
0

تقریباً همه اپلیکیشن‌ها شامل نوعی گرافیک هستند. به همین جهت است که دانلود کردن و نمایش تصاویر در اپلیکیشن موبایل یکی از رایج‌ترین وظایف برای توسعه‌دهندگان اپلیکیشن محسوب می‌شود. اما در صورتی که قرار باشد اپلیکیشن برخی تصاویر را به طور مکرر دانلود و نمایش دهد، این فرایند موجب کارهای غیرضروری می‌شود. در این مقاله روش بهینه‌سازی این وضعیت را با ایجاد یک Image Cache و یکپارچه‌سازی آن با Image Loader با استفاده از فریمورک Combine توضیح می‌دهیم. بدین ترتیب به قابلیت کش تصاویر با قابلیت استفاده مجدد در سوئیفت دست می‌یابیم.

استفاده از NSCache به عنوان محل ذخیره‌سازی

زمانی که می‌خواهیم یک سازوکار کش در یک پروژه iOS بسازیم، در اغلب موارد باید از کلاس NSCache استفاده کنیم. این کلاس برخی مزایا مانند thread-safe بودن و حذف آیتم‌ها از کش در زمان استفاده دیگر اپلیکیشن‌ها از حافظه دارد، اما از برخی عیب‌ها نیز مانند داشتن فرایند خروجی ناروشن برخوردار است.

در هر حال کلاس NSCache گزینه بهتری برای کش کردن در مقایسه با کلاس‌های collection از کتابخانه استاندارد سوئیفت یا فریمورک Foundation محسوب می‌شود. در این مقاله، ما از NSCache به عنوان یک ذخیره‌گاه کش تصویر درونی استفاده می‌کنیم. البته می‌توانید آن را با راه‌حل دیگری نیز جایگزین کنید.

Pipeline رندرینگ تصویر

اگر اپلیکیشن شما تصاویر را از وب دانلود می‌کند، یکی از چالش‌های رایج واکنش‌گرا بودن و عملکرد اپلیکیشن است. برای نمونه ممکن است در زمان اسکرول کردن یک «نمای جدولی» (Table View) شامل تصاویر با کندی مواجه شوید. مشکل این است که رندرینگ تصویر به یکباره هنگام انتساب تصویر به یک «نمای تصویر» (Image View) رخ نمی‌دهد. pipeline رندرینگ تصویر شامل چندین مرحله است:

  • بارگذاری – تصویر فشرده‌شده در حافظه بارگذاری می‌شود.
  • دیکدینگ – داده‌های تصویر کد شده به اطلاعات تصویر مبتنی بر پیکسل تبدیل می‌شود.
  • رندرینگ – داده‌های تصویر از بافر تصویر درون بافر فریم کپی و مقیاس‌بندی می‌شود.

این فرایند می‌تواند حجم کار بالایی به نخ اصلی اضافه کند، به طوری که اپلیکیشن شما دیگر پاسخگو نباشد. البته می‌توان برخی بهینه‌سازی‌ها مانند دیکدینگ و رندرینگ تصویر قبل از انتساب به UIImageView انجام داد.

این تابع یک UIImage معمولی استفاده می‌کند و یک نسخه نافشرده و رندر شده بازگشت می‌دهد. داشتن یک کش از تصاویر نافشرده ایده مناسبی به نظر می‌رسد. بدین ترتیب عملکرد رسم بهبود می‌یابد، اما هزینه جانبی به صورت استفاده بیشتر از فضای ذخیره‌سازی دارد.


اگر دوست دارید در این مورد اطلاعات بیشتری کسب کنید، پیشنهاد می‌کنیم این سخنرانی‌های WWDC مربوط به بررسی عمیق حافظه iOS (+) و رویه‌های مناسب برای تصاویر و گرافیک‌ها (+) را ببینید.

کش تصاویر درون حافظه

آغاز کار با تعریف کردن الزامات کش تصاویر، می‌تواند یک شروع خوب باشد. کش باید تابع‌های CRUD (یعنی ایجاد، خواندن، به‌روزرسانی و حذف) را تعریف بکند. کش باید یک «زیرنویس» (subscript) داشته باشد تا کد خوانایی بیشتری پیدا کند. در اغلب موارد تصویری که از شبکه بارگذاری شده را کش می‌کنیم و از این رو بهتر است از URL به عنوان کلید آن استفاده کنیم. در نهایت می‌توانیم ImageCacheType را به صورت زیر اعلان کنیم:


پیاده‌سازی کش تصویر

با در نظر گرفتن همه نکات فوق می‌توانیم کلاس ImageCache را اعلان کنیم. این کلاس به صورت داخلی دو فیلد NSCache دارد تا تصاویر فشرده و تصاویر نافشرده را ذخیره سازد. اندازه کش را برحسب بیشینه تعداد اشیا و هزینه کلی مثلاً بنا بر اندازه تصاویر به بایت محدود می‌کنیم. وهله NSLock برای ارائه دسترسی منحصراً متقابل و thread-safe ساختن کش استفاده شده است.


اکنون باید چند تابع را برای تأمین الزامات ImagecacheType که قبلاً تعریف کردیم پیاده‌سازی کنیم. بدین ترتیب می‌توانیم تصاویر را در کش درج و حذف کنیم:


ممکن است متوجه شده باشید که ما هزینه‌ای برای تصویر کدگشایی‌شده تعیین می‌کنیم. decodedImageCache با پیکربندی شده است. این متد باید برخی عناصر را در زمانی که هزینه کلی از بیشینه مجاز تجاوز کرد، حذف کند. برای دریافت یک تصویر از کش ابتدا باید تصاویر دیکد شده را به عنوان سناریوی بهترین حالت بررسی کنیم. سپس به دنبال تصویر در imageCache بگردیم و یا مقدار nil را به عنوان بازخورد بازگشت دهیم.


ما می‌توانیم از تابع فوق برای تعریف زیرنویس برای ImageCache استفاده کنیم:


بدین ترتیب کش تصاویر را ایجاد کردیم که می‌تواند درون پروژه‌ها مورد استفاده مجدد قرار گیرد و آن‌ها را سریع‌تر و پاسخگوتر سازد.

یکپارچه‌سازی با بخش بارگذاری تصویر

اینک به بررسی شیوه یکپارچه‌سازی ImageCache در پروژه‌های مختلف می‌پردازیم. فرض کنید یک Image Loader دارید که از قبل تعریف شده است. اگر چنین نیست می‌توانید آن را به صورت زیر با استفاده از فریمورک Combine تعریف کنید:


  1. dataTaskPublisher یک ناشر ایجاد می‌کند که نتایج اجرای وظایف داده‌ای نشست URL را تحویل می‌دهد. این ناشر یک pipeline از چندتایی (data: Data, response: URLResponse) بازگشت می‌دهد.
  2. عملگر map برای ایجاد یک شیء UIImage اختیاری مورد استفاده قرار می‌گیرد.
  3. ما از عملگر catch برای مدیریت خطا استفاده می‌کنیم. این عملگر ناشر بالادستی را با ناشر Just(nil) عوض می‌کند.
  4. کارهایی را روی صف پس‌زمینه اجرا می‌کند.
  5. برای دریافت تصویر از صف اصلی سوئیچ می‌کند.
  6. eraseToAnyPublisher عمل پاک کردن نوع را روی زنجیره‌ای از عملگرها اجرا می‌کند تا تابع loadImage(from:) یک شیء با نوع AnyPublisher<UIImage?, Never> بازگشت دهد.

سپس باید تغییراتی در ImageLoader ایجاد کنیم تا در صورتی که تصویری در کش داشته باشیم، بی‌درنگ بازگشت یابد و زمانی که بارگذاری تصویر پایان یافت، آن را کش کند. در نهایت ImageLoader به صورت زیر درمی‌آید:


ناشر Just را با تصویر کش شده (در صورت وجود) بازگشت می‌دهد.

داده‌ها به receiveOutput ارسال می‌شوند تا به عنوان ناشر آن‌ها را عرضه کند. در این بخش تصویر را به محض انجام یافتن بارگذاری داده اجرا می‌کنیم و dataTaskPublisher یک مقدار جدید صادر می‌کند.

سخن پایانی

با استفاده از ImageCache می‌توان بارگذاری تصویر را درون اپلیکیشن بهینه‌سازی کرد و تجربه کاربری را بهبود بخشید. در نهایت بارگذاری تصاویر از کش باید سریع‌تر از دریافت آن‌ها از شبکه باشد.

کش تصاویر با قابلیت استفاده مجدد در سوئیفت

لازم به ذکر است که می‌توانید برخی بهینه‌سازی‌ها در راه‌حل فوق ایجاد کنید:

  • از کش LRU به جای NSCache استفاده کنید.
  • ذخیره‌سازی دائمی (Persistence) را اضافه کنید.
  • از قفل خواندن-نوشتن برای عملکرد بهتر استفاده کنید.

همه کدهای معرفی‌شده در این مقاله را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید.

اگر این نوشته برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

telegram
twitter