كيفية إضافة قاعدة بيانات Realm إلى تطبيق CRUD على iOS باستخدام Swift

دقائق القراءة: 8

مقدمة عملية إلى استخدام Realm في تطبيقات iOS

إذا كنت تطوّر تطبيقات iPhone أو iPad باستخدام Swift، فستحتاج غالباً إلى وسيلة مرنة لحفظ البيانات محلياً وتنفيذ العمليات الأساسية عليها، مثل الإضافة والقراءة والتعديل والحذف. في هذا المقال سنشرح بطريقة عملية كيفية دمج قاعدة بيانات Realm داخل تطبيق iOS بسيط من نوع ToDo، بحيث تتعرف بوضوح على آلية تنفيذ عمليات CRUD خطوة بخطوة.

سنركّز على بناء فهم حقيقي للمفاهيم، وليس مجرد نسخ ولصق للأكواد. لذلك ستتعلم كيف تضيف المكتبة إلى المشروع، وكيف تنشئ نموذج البيانات، ثم تربط ذلك بواجهة استخدام بسيطة تعتمد على UITableView.

إضافة قاعدة بيانات Realm إلى تطبيق iOS باستخدام Swift لبناء تطبيق CRUD للمهام

ما هي قاعدة بيانات Realm؟

Realm هي قاعدة بيانات محلية مفتوحة المصدر موجّهة لتطبيقات الأجهزة المحمولة، وتمتاز بسهولة الدمج وسلاسة التعامل مقارنة ببعض الخيارات التقليدية. ويعتمد عليها كثير من المطورين كبديل عملي لـ Core Data في تطبيقات iOS.

من أبرز مزايا Realm أنها متعددة المنصات، ما يعني إمكانية استخدامها في تطبيقات Android وiOS الأصلية، وكذلك في التطبيقات العابرة للمنصات مثل التطبيقات المبنية باستخدام React Native.

كما أنها تدعم عدة لغات برمجة، من بينها:

  • Objective-C
  • Swift
  • Java
  • Kotlin
  • C#
  • JavaScript

نظرة تعريفية على قاعدة بيانات Realm المخصصة لتطبيقات الهواتف المحمولة

لماذا تختار Realm في تطبيقك؟

الاعتماد على Realm مناسب في كثير من الحالات، خاصة عندما ترغب في:

  • تخزين البيانات محلياً بسرعة.
  • تقليل التعقيد المرتبط بإدارة قواعد البيانات داخل تطبيق الهاتف.
  • البدء سريعاً في مشروع صغير أو متوسط.
  • إنشاء طبقة بيانات واضحة تدعم عمليات CRUD بسهولة.

وبالنسبة لتطبيق المهام الذي سنبنيه هنا، فإن Realm تُعد خياراً ممتازاً لأنها تسمح بتخزين كل مهمة مع خصائصها، ثم استرجاعها وتحديثها أو حذفها بسطرية عمل واضحة.

إعداد Realm داخل مشروع iOS

يمكنك إضافة Realm إلى مشروعك باستخدام أكثر من أداة لإدارة الاعتماديات، مثل:

  • Swift Package Manager
  • CocoaPods
  • Carthage

في هذا الشرح سنستخدم CocoaPods لأنه شائع وسهل في المشاريع التعليمية والعملية.

إنشاء مشروع جديد في Xcode

ابدأ بإنشاء مشروع iOS App جديد باستخدام UIKit وSwift، واحرص على عدم تفعيل خيار Core Data. بعد ذلك أغلق Xcode وافتح تطبيق الطرفية Terminal.

إنشاء ملف Podfile

انتقل إلى مجلد المشروع من خلال Terminal ثم نفّذ الأمر التالي:

pod init

بعد تنفيذ الأمر، ستجد ملفاً جديداً باسم Podfile. افتح هذا الملف باستخدام أي محرر نصوص، ثم أضف اعتماد Realm إليه بالشكل المناسب.

تعديل ملف Podfile لإضافة اعتماد Realm إلى مشروع iOS

تثبيت الاعتماديات

بعد حفظ ملف Podfile، ثبّت المكتبات المطلوبة عبر الأمر التالي:

pod install

تثبيت مكتبة Realm باستخدام CocoaPods داخل مشروع iOS

بذلك تكون قد أضفت Realm DB بنجاح إلى مشروعك. بعد الانتهاء، افتح المشروع باستخدام ملف .xcworkspace وليس .xcodeproj، عبر الأمر التالي:

open YOUR_APP_NAME.xcworkspace

ملاحظة مهمة: بعد فتح المشروع في Xcode، نفّذ عملية بناء للمشروع بالضغط على Command+B للتأكد من أن كل شيء يعمل بصورة صحيحة.

تصميم واجهة التطبيق بطريقة بسيطة وواضحة

لأغراض الشرح، سنعتمد واجهة استخدام بسيطة جداً. افتح ملف Main.storyboard وأضف جدولاً من نوع Table View مع خلية أولية Prototype Cell. بعد ذلك اربط الشاشة داخل Navigation Controller حتى تتمكن من إضافة زر في الشريط العلوي.

ثم أنشئ مخرج ربط IBOutlet للجدول داخل ملف ViewController.swift.

تصميم واجهة تطبيق مهام بسيط في Xcode باستخدام Table View وNavigation Controller

إنشاء نموذج البيانات في Realm

في تطبيق المهام لدينا، كل عنصر يجب أن يحتوي على اسم المهمة ومعرّف خاص بها. لذلك سننشئ نموذج بيانات باسم ToDoTask.

أنشئ ملف Swift جديداً داخل المشروع، ثم أضف الكود التالي:

import Foundation
import RealmSwift

class ToDoTask: Object {
    @objc dynamic var tasknote: String?
    @objc dynamic var taskid: String?
}

هذا الكلاس يرث من Object، وهي الفئة الأساسية التي توفرها Realm للتعامل مع الكيانات المخزنة داخل قاعدة البيانات. وبمجرد وراثة هذا النوع، يصبح الكائن قابلاً للحفظ والاسترجاع والتعديل داخل قاعدة البيانات المحلية.

أما الخصائص الموجودة في هذا النموذج فهي:

  • tasknote: تمثل نص المهمة المطلوب تنفيذها.
  • taskid: تمثل المعرف الفريد للمهمة.

واستخدام @objc يعني جعل الخاصية مرئية لنظام Objective-C runtime، بينما تشير الكلمة dynamic إلى أن الوصول إلى الخاصية سيتم بأسلوب ديناميكي، وهو أمر تعتمد عليه Realm داخلياً في تتبع التغييرات.

ما العمليات التي سينفذها التطبيق؟

قبل كتابة الكود، من المفيد تحديد ما الذي نريد من التطبيق أن يفعله. التطبيق هنا سيدعم العمليات التالية:

  1. استقبال إدخال من المستخدم عبر UIAlertController.
  2. إضافة المهمة إلى قاعدة البيانات وعرضها في الجدول.
  3. تعديل المهمة عند الضغط على الصف.
  4. حذف المهمة عبر السحب Swipe to Delete.
  5. قراءة جميع المهام المحفوظة وإظهارها عند فتح التطبيق.

إضافة زر لإنشاء مهمة جديدة

ابدأ بتهيئة الواجهة داخل الدالة viewDidLoad() في ملف ViewController.swift كما يلي:

navigationItem.rightBarButtonItem = UIBarButtonItem(
    image: .add,
    style: .done,
    target: self,
    action: #selector(addTask)
)

navigationController?.navigationBar.prefersLargeTitles = true
title = "RealmDB"

الكود السابق يضيف زر Add في الجهة اليمنى من شريط التنقل، وعند الضغط عليه سيتم استدعاء الدالة addTask().

إنشاء نافذة إدخال باستخدام UIAlertController

الآن أنشئ الدالة التالية:

@objc func addTask() {
    let ac = UIAlertController(title: "Add Note", message: nil, preferredStyle: .alert)
    ac.addTextField(configurationHandler: .none)
    ac.addAction(UIAlertAction(title: "Add", style: .default, handler: { (UIAlertAction) in
        if let text = ac.textFields?.first?.text {
            print(text)
        }
    }))
    ac.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
    present(ac, animated: true, completion: nil)
}

هذه الدالة تعرض نافذة منبثقة تحتوي على حقل نصي، ومن خلالها يستطيع المستخدم إدخال اسم المهمة. حالياً نحن فقط نطبع النص في وحدة التحكم، لكننا بعد قليل سنربط ذلك مباشرة بقاعدة البيانات.

تهيئة الاتصال بقاعدة بيانات Realm

حتى تتمكن من حفظ البيانات أو قراءتها، يجب أولاً إنشاء كائن من النوع Realm. أضف الخاصية التالية داخل ViewController:

var realmDB: Realm!

ثم قم بتهيئتها داخل viewDidLoad():

override func viewDidLoad() {
    super.viewDidLoad()

    navigationItem.rightBarButtonItem = UIBarButtonItem(
        image: .add,
        style: .done,
        target: self,
        action: #selector(addTask)
    )

    navigationController?.navigationBar.prefersLargeTitles = true
    title = "RealmDB"
    realmDB = try! Realm()
}

من خلال try! Realm() يتم إنشاء اتصال مباشر بملف قاعدة البيانات المحلية الخاص بالتطبيق.

إنشاء مصفوفة لتغذية الجدول بالبيانات

رغم أن البيانات محفوظة في Realm، إلا أننا سنستخدم أيضاً مصفوفة محلية لعرض العناصر بسهولة داخل Table View:

var tasks = [ToDoTask]()

هذه المصفوفة ستحتوي على المهام التي سنعرضها في الواجهة، كما ستُحدَّث عند الإضافة أو القراءة أو الحذف.

حفظ المهمة الجديدة داخل قاعدة البيانات والواجهة

الآن عدّل الجزء الخاص بزر الإضافة داخل الدالة addTask() بحيث يتم:

  • قراءة النص المدخل من المستخدم.
  • إنشاء كائن جديد من النوع ToDoTask.
  • توليد معرّف فريد باستخدام UUID().
  • إضافة المهمة إلى المصفوفة.
  • حفظها داخل قاعدة البيانات.
  • تحديث الجدول مباشرة.

استخدم الكود التالي:

if let text = ac.textFields?.first?.text {
    let t = ToDoTask()
    t.taskid = UUID().uuidString
    t.tasknote = text
    self.tasks.append(t)

    try! self.realmDB.write {
        self.realmDB.add(t)
    }

    self.tasktv.reloadData()
}

بهذه الخطوة أصبحت عملية Create جاهزة. فعند إدخال مهمة جديدة، سيتم تخزينها محلياً وعرضها فوراً للمستخدم.

عرض المهام داخل UITableView

بعد حفظ البيانات، لن تظهر العناصر تلقائياً في الجدول ما لم تطبق بروتوكولي UITableViewDelegate وUITableViewDataSource.

أولاً اجعل ViewController يلتزم بهذين البروتوكولين، ثم أضف الدوال الأساسية.

تحديد عدد الصفوف

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tasks.count
}

هذه الدالة تعيد عدد العناصر في المصفوفة tasks، وهو نفسه عدد الصفوف المطلوب عرضها.

تعبئة كل خلية بالبيانات المناسبة

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "cell") {
        cell.textLabel?.text = tasks[indexPath.row].tasknote
        return cell
    }
    return UITableViewCell()
}

في هذه الدالة نعيد استخدام خلية تحمل المعرّف cell، ثم نضع داخلها نص المهمة المستخرج من الخاصية tasknote.

تعديل المهمة عند الضغط على الصف

لكي نسمح للمستخدم بتحديث المهمة، يمكننا الاعتماد على الدالة didSelectRowAt. عند الضغط على أي صف، سنعرض نافذة إدخال جديدة، وبعد إدخال النص الجديد سنحدّث القيمة داخل Realm ثم نعيد تحميل الجدول.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let tasktomodify = tasks[indexPath.row]
    let ac = UIAlertController(title: "Update task", message: nil, preferredStyle: .alert)
    ac.addTextField(configurationHandler: .none)
    ac.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (UIAlertAction) in
        if let text = ac.textFields?.first?.text {
            if (!text.isEmpty) {
                try! self.realmDB.write({
                    tasktomodify.tasknote = text
                })
                self.tasktv.reloadData()
            }
        }
    }))
    present(ac, animated: true, completion: nil)
}

هذه الخطوة تمثل عملية Update. من الناحية العملية، ما يحدث هو تعديل الكائن نفسه داخل معاملة كتابة باستخدام write، ثم تحديث واجهة المستخدم لعرض النص الجديد.

تفعيل السحب للحذف وربطه بقاعدة البيانات

من الميزات الشائعة في تطبيقات القوائم على iOS إمكانية حذف الصف بالسحب. لتنفيذ ذلك، أضف الدالة التالية:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let tasktoDelete = tasks[indexPath.row]
        try! realmDB.write({
            realmDB.delete(tasktoDelete)
            self.tasks.remove(at: indexPath.row)
            self.tasktv.deleteRows(at: [indexPath], with: .fade)
        })
    }
}

هنا تتم عملية الحذف على ثلاثة مستويات في الوقت نفسه:

  • حذف السجل من Realm.
  • إزالة العنصر من المصفوفة tasks.
  • تحديث الجدول بصرياً عبر حذف الصف من الواجهة.

وهكذا تكون قد أنجزت عملية Delete بصورة متكاملة.

قراءة جميع البيانات عند تشغيل التطبيق

لكي لا تختفي المهام بعد إغلاق التطبيق وإعادة فتحه، يجب قراءة البيانات المخزنة وعرضها من جديد. أنشئ دالة باسم getTodos() كما يلي:

func getTodos() {
    let notes = realmDB.objects(ToDoTask.self)

    self.tasks.removeAll()

    if (!notes.isEmpty) {
        for n in notes {
            self.tasks.append(n)
        }
        self.tasktv.reloadData()
    }
}

هذه الدالة تسترجع جميع الكائنات من النوع ToDoTask باستخدام objects()، ثم تنظف المصفوفة المحلية لتجنب التكرار، وبعد ذلك تعيد تعبئتها وتحدّث الجدول.

لضمان تنفيذ عملية Read عند بدء التطبيق، استدعِ الدالة getTodos() داخل viewDidLoad() بعد تهيئة realmDB.

نصيحة إضافية: كيف تتأكد من أن البيانات محفوظة فعلاً؟

قد يعمل التطبيق بشكل صحيح أمامك، لكن من المفيد أحياناً التحقق بصرياً من أن السجلات محفوظة فعلاً في ملف قاعدة البيانات. يمكنك القيام بذلك باستخدام أداة MongoDB Realm Studio عند اختبار التطبيق على iOS Simulator.

أضف السطر التالي داخل viewDidLoad():

print(realmDB.configuration.fileURL!)

سيقوم هذا السطر بطباعة المسار الفعلي لملف قاعدة البيانات في وحدة التحكم.

طباعة مسار ملف قاعدة بيانات Realm داخل Xcode للتحقق من مكان التخزين

بعد نسخ المسار، افتح Terminal ونفّذ الأمر التالي:

open REALM_FILE_PATH_HERE

إذا كانت أداة MongoDB Realm Studio مثبتة على جهازك، فسيتم فتح ملف قاعدة البيانات مباشرة، وستشاهد البيانات المخزنة داخل جداول واضحة. كما ستلاحظ أن أي تعديل أو حذف تقوم به داخل التطبيق ينعكس في قاعدة البيانات أيضاً.

عرض بيانات تطبيق iOS المخزنة في Realm باستخدام MongoDB Realm Studio

أفضل ممارسات مهمة عند بناء تطبيق CRUD باستخدام Realm

رغم أن المثال هنا تعليمي وبسيط، فإن هناك بعض التحسينات التي يُستحسن التفكير فيها عند تطوير تطبيق فعلي:

  • التحقق من أن النص المدخل ليس فارغاً قبل الحفظ.
  • استخدام معالجة أخطاء أفضل بدلاً من try! في التطبيقات الإنتاجية.
  • تعريف مفتاح أساسي primary key إذا كنت تحتاج إلى إدارة دقيقة للسجلات.
  • فصل طبقة البيانات عن ViewController في المشاريع المتوسطة والكبيرة.
  • تقليل إعادة تحميل الجدول كاملاً إذا أمكن، واستبدالها بتحديثات جزئية لتحسين الأداء.

الخلاصة التقنية

يُعد دمج Realm في تطبيقات iOS خياراً عملياً جداً عندما تحتاج إلى قاعدة بيانات محلية سريعة وسهلة الاستخدام. في هذا المثال أنشأنا تطبيق مهام بسيطاً يدعم عمليات Create وRead وUpdate وDelete باستخدام Swift وUIKit. تقنياً، ما يميز Realm هنا هو وضوح نموذج البيانات وسهولة تنفيذ معاملات الكتابة والقراءة دون تعقيد كبير. وإذا كنت تبحث عن بداية قوية لبناء تطبيقات تعتمد على التخزين المحلي، فإن هذا النهج يمنحك أساساً ممتازاً يمكن التوسع عليه لاحقاً.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *