تخزين البيانات مؤقتًا في Kotlin: كيف تسرّع تطبيقك 10 أضعاف دون سحر
تخيل: تفتح تطبيقك المفضل، ويستغرق التحميل 5 ثوانٍ، مع عرض "شاشة انتظار" ومؤشر تحميل لا نهائي. هل هذا مألوف؟ والآن تخيل أن نفس البرنامج يُفتح فورًا، حتى بدون اتصال بالإنترنت. السر يكمن في التخزين المؤقت الذكي للبيانات. واليوم سنشرح كيفية تنفيذه في Kotlin بحيث يكون كودك ليس سريعًا فحسب، بل أنيقًا أيضًا.
Kotlin هي لغة محبوبة بسبب إيجازها وأمانها. ولكن حتى باستخدام أفضل لغة، يمكنك كتابة تطبيق بطيء إذا لم تفكر في التخزين المؤقت. في هذه المقالة، سننتقل من ذاكرة التخزين المؤقت البسيطة في الذاكرة (in-memory cache) إلى الحلول المتقدمة مع Redis و DiskLruCache. ستتعلم كيفية تخزين البيانات مؤقتًا في Kotlin بحيث ينسى المستخدمون أوقات التحميل، وينسى الخادم الطلبات الزائدة.
لماذا يجب تخزين البيانات مؤقتًا في Kotlin؟
التخزين المؤقت مثل العادة الجيدة: صعب في البداية، لا غنى عنه بعد ذلك. إليك ثلاثة أسباب تجعل تطبيقك على Kotlin بحاجة إلى ذاكرة تخزين مؤقت:
- السرعة: تُقرأ البيانات من الذاكرة في ميكروثانية، وليس في ثوانٍ كما هو الحال من الخادم أو القرص.
- توفير البيانات: طلبات أقل لواجهة API تعني أموالاً أقل على الخوادم والإنترنت المحمول.
- وضع عدم الاتصال: يحب المستخدمون عندما يعمل التطبيق حتى في مترو الأنفاق دون اتصال.
لكن هناك فارق بسيط: التخزين المؤقت غير الصحيح قد يؤدي إلى تسرب الذاكرة (OutOfMemoryError - تحية من الماضي) أو عرض بيانات قديمة. لذلك دعونا نفهم كيف نفعل ذلك بشكل صحيح.
ذاكرة التخزين المؤقت في الذاكرة (In-memory cache): أبسط طريقة لتسريع تطبيق Kotlin
لنبدأ بالأساسيات - التخزين المؤقت في ذاكرة الوصول العشوائي (RAM). هذا مثل وضع الأدوات التي تحتاجها على مكتب العمل، بدلاً من الركض إلى المرآب في كل مرة.
أبسط خيار هو استخدام HashMap أو ConcurrentHashMap (إذا كنت تعمل مع تعدد المهام). لكن في Kotlin يمكننا فعل ذلك بشكل أجمل:
class SimpleCache<K, V>( private val maxSize: Int = 100) { private val cache = LinkedHashMap<K, V>(0, 0.75f, true) // access-order
fun get(key: K): V? { return cache[key] }
fun put(key: K, value: V) { if (cache.size >= maxSize) { val oldest = cache.keys.firstOrNull() oldest?.let { cache.remove(it) } } cache[key] = value }
fun clear() { cache.clear() }}تقوم ذاكرة التخزين المؤقت هذه تلقائيًا بحذف أقدم عنصر عند الوصول إلى الحد الأقصى. لكن هناك مشكلة - إنها لا تأخذ في الاعتبار وقت حياة البيانات. لنفترض أنك خزّنت قائمة أسعار العملات مؤقتًا، وبعد ساعة تغير السعر، لكن المستخدم يرى البيانات القديمة. هذا ليس جيدًا.
إضافة TTL (Time To Live)
data class CacheEntry<T>( val data: T, val expirationTime: Long)
class TTLCache<K, V>( private val ttlMillis: Long = 60_000, // دقيقة واحدة private val maxSize: Int = 100) { private val cache = LinkedHashMap<K, CacheEntry<V>>(0, 0.75f, true)
fun get(key: K): V? { val entry = cache[key] ?: return null if (System.currentTimeMillis() > entry.expirationTime) { cache.remove(key) return null } return entry.data }
fun put(key: K, value: V) { if (cache.size >= maxSize) { val oldest = cache.keys.firstOrNull() oldest?.let { cache.remove(it) } } cache[key] = CacheEntry(value, System.currentTimeMillis() + ttlMillis) }}الآن البيانات تعيش بالضبط للمدة المطلوبة. مثالي لتخزين استجابات API مؤقتًا، حيث لا تتغير البيانات كثيرًا (مثل قائمة فئات المنتجات).
التخزين المؤقت في Android: DiskLruCache و Room
على الأجهزة المحمولة، الذاكرة مورد محدود. إذا كان