Published on

Core Data munosabatlari (relationships), predikatlar va oʻchirish qoidalari

Authors

Core Data bilan ishlashda koʻp hollarda maʼlumotlarni oddiy saqlash va oʻqib olish (CRUD) kifoya qiladi. Biroq, murakkab ilovalar yaratishda entitilar (jadvallar) oʻrtasida munosabatlar (relationships) oʻrnatish va ularni sinxron saqlash talab etiladi.


1. Munosabatlar Arxitekturasi: Real Hayotiy Ssenariy

Biznes mantiqini tushunish uchun uchta obʼektni olamiz: Biznes (Business), Boʻlim (Department) va Xodim (Employee).

Xcode Data Model sozlamalari:

  1. BusinessEntity & DepartmentEntity (Many-to-Many):
    • Bir korxonada (masalan, Apple) bir nechta boʻlimlar (Marketing, Engineering) boʻlishi mumkin.
    • Shu bilan birga, bitta boʻlim (masalan, Engineering) bir vaqtning oʻzida ham Appleʼda, ham Microsoftʼda mavjud boʻlishi mumkin.
    • Xcodeʼda har ikki tomon munosabati ham To-Many qilib sozlanadi.
  2. EmployeeEntity (Many-to-One):
    • Xodim (masalan, Chris) faqat bitta korxonaga va faqat bitta boʻlimga tegishli boʻla oladi.
    • Xcodeʼda xodim tomondan munosabat To-One, korxona va boʻlim tomondan esa To-Many qilinadi.

2. Core Data menejeri (Singleton Pattern)

Ilovada bitta umumiy context bilan ishlash va xotirani toʻgʻri boshqarish uchun Core Data sozlamalarini alohida CoreDataManager klassiga ajratamiz.

import CoreData

class CoreDataManager {
    static let instance = CoreDataManager()
    let container: NSPersistentContainer
    let context: NSManagedObjectContext

    init() {
        container = NSPersistentContainer(name: "CoreDataContainer")
        container.loadPersistentStores { _, error in
            if let error = error { print("Yuklanishda xatolik: \(error)") }
        }
        context = container.viewContext
    }

    func save() {
        do {
            try context.save()
        } catch {
            print("Saqlashda xatolik: \(error.localizedDescription)")
        }
    }
}


3. Avtomatik Sinxronizatsiya — Core Dataʼning eng katta afzalligi

Siz kod yozayotganda munosabatning faqat bir tomonini oʻzgartirsangiz, Core Data Inverse (teskari munosabat) sozlamasi tufayli ikkinchi tomonni avtomatik ravishda yangilaydi.

Amaliy misollar bilan tushuntirish:

  • Teskari munosabat kuchi: Agar siz yangi xodim yaratib, uning korxona xususiyatiga newEmployee.business = apple deb biriktirsangiz, siz avtomatik ravishda apple.employees massiviga ham ushbu xodimni qoʻshgan boʻlasiz.
  • To-Many munosabatga element qoʻshish: Xcode entitilar asosida avtomatik ravishda addToDepartments(_:) yoki addToEmployees(_:) kabi funksiyalarni generatsiya qiladi. Masalan, Microsoftʼga Engineering boʻlimini qoʻshish:
existingBusiness.addToDepartments(engineeringDepartment)

  • Maʼlumotlar Tipi (NSSet): To-Many munosabatlar Swiftʼdagi Array (Massiv) emas, balki NSSet tipida generatsiya qilinadi. NSSet ichidagi elementlar takrorlanmaydi (unikal) va tartibsiz joylashadi. Ularni SwiftUI roʻyxatida koʻrsatish uchun Swift massiviga oʻgʻirish shart:
let array = entity.departments?.allObjects as? [DepartmentEntity]


4. NSSortDescriptor va NSPredicate (Saralash va Filtrlash)

Maʼlumotlar omboridan maʼlumotlarni yuklab olishda ularni tartiblash va keraklilarini ajratib olish uchun quyidagi mexanizmlar qoʻllaniladi.

A. Saralash (NSSortDescriptor)

Korxonalarni alifbo tartibida (A-Z) chiqarish:

let request = NSFetchRequest<BusinessEntity>(entityName: "BusinessEntity")
let sort = NSSortDescriptor(keyPath: \BusinessEntity.name, ascending: true)
request.sortDescriptors = [sort]

B. Predikatlar yordamida filtrlash (NSPredicate)

Predikat — bu SQL soʻrovlaridagi WHERE shartiga oʻxshash filtrdir.

  1. Matn boʻyicha aniq qidiruv: Faqat nomi "Apple" boʻlgan korxonani olish:
request.predicate = NSPredicate(format: "name == %@", "Apple")

  1. Obʼekt (Munosabat) boʻyicha qidiruv: Muayyan korxonaga tegishli barcha xodimlarni filtrlab olish:
// To-One munosabatlarda to'g'ridan-to'g'ri ob'ektni solishtirish mumkin
request.predicate = NSPredicate(format: "business == %@", businessObject)

⚠️ Muhim Cheklov: business == %@ kabi toʻgʻridan-toʻg'ri predikat filtrlari faqat To-One munosabatlar uchun ishlaydi. Agar munosabat To-Many boʻlsa (masalan, "Ushbu boʻlimga ega korxonalarni top"), bunday oddiy shart operatori xatolik beradi, uning uchun SUBQUERY yozish talab etiladi.


5. Oʻchirish qoidalari (Delete Rules) — Eng kritik qism

Biror obʼekt oʻchirilganda u bilan bogʻlangan boshqa maʼlumotlar taqdirini Delete Rule belgilaydi. Agar bu qoidalar toʻgʻri sozlanmasa, ilovada maʼlumotlar buzilishi (data corruption) yoki crashʼlar yuz beradi.

Qoida NomiIshlash mantiqiReal hayotiy misol
Nullify (Standart)Obʼekt oʻchganda, unga bogʻliq boʻlgan ikkinchi obʼekt xotirada qoladi. Faqat uning munosabat koʻrsatkichi nilga aylanadi.Finance boʻlimi oʻchirilsa, xodim Emily tizimda qoladi. Faqat uning kartasidagi boʻlim nomi oʻchib, "Yoʻq" boʻlib qoladi.
CascadeZanjirli oʻchirish. Asosiy obʻekt oʻchsa, unga bogʻliq barcha obʼektlar ham birga oʻchib ketadi.Apple korxonasi oʻchirilsa, unga tegishli barcha xodimlar ham tizimdan avtomatik ravishda butunlay oʻchiriladi.
DenyTaqiqlash. Agar obʼektga bogʻlangan birorta ham chet barcha element boʻlsa, tizim uni oʻchirishga mutloq ruxsat bermaydi.Ichida xodimlari bor Engineering boʻlimini oʻchirmoqchi boʻlsangiz, Core Data xatolik (Error saving) qaytaradi va oʻchirishni rad etadi.
No ActionHech qanday amal bajarilmaydi. Birinchi obʼekt oʻchadi, lekin ikkinchi obʼekt hali ham oʻchgan obʼektga koʻrsatkich (pointer) ushlab turaveradi.Tizimda xatoga sabab boʻlgani uchun deyarli ishlatilmaydi.

6. Loyihadagi toʻliq SwiftUI oqimi (Data Flow)

Ilovamizda foydalanuvchi interfeysi maʼlumotlar bilan qanday ishlashini koʻrsatuvchi toʻliq tuzilma:

import SwiftUI

// MARK: - VIEW MODEL
class CoreDataRelationshipViewModel: ObservableObject {
    let manager = CoreDataManager.instance
    @Published var businesses: [BusinessEntity] = []
    @Published var departments: [DepartmentEntity] = []
    @Published var employees: [EmployeeEntity] = []

    init() { refreshData() }

    func refreshData() {
        let bReq = NSFetchRequest<BusinessEntity>(entityName: "BusinessEntity")
        bReq.sortDescriptors = [NSSortDescriptor(keyPath: \BusinessEntity.name, ascending: true)]

        let dReq = NSFetchRequest<DepartmentEntity>(entityName: "DepartmentEntity")
        let eReq = NSFetchRequest<EmployeeEntity>(entityName: "EmployeeEntity")

        do {
            businesses = try manager.context.fetch(bReq)
            departments = try manager.context.fetch(dReq)
            employees = try manager.context.fetch(eReq)
        } catch { print("Yuklashda xatolik") }
    }

    func setupMockData() {
        // 1. Biznes yaratish
        let apple = BusinessEntity(context: manager.context)
        apple.name = "Apple"

        // 2. Bo'lim yaratish va munosabat o'rnatish
        let marketing = DepartmentEntity(context: manager.context)
        marketing.name = "Marketing"
        marketing.addToBusinesses(apple) // Many-to-Many aloqa

        // 3. Xodim yaratish va munosabat o'rnatish
        let chris = EmployeeEntity(context: manager.context)
        chris.name = "Chris"
        chris.age = 25
        chris.business = apple       // One-to-Many aloqa (To-One qismi)
        chris.department = marketing // One-to-Many aloqa (To-One qismi)

        manager.save()
        refreshData()
    }

    func deleteLastDepartment() {
        if let lastDept = departments.last {
            manager.context.delete(lastDept)
            manager.save()
            refreshData()
        }
    }
}

// MARK: - MAIN VIEW
struct CoreDataRelationshipBootCamp: View {
    @StateObject var vm = CoreDataRelationshipViewModel()

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    HStack {
                        Button("Mock Ma'lumot Qo'shish") { vm.setupMockData() }
                        Spacer()
                        Button("Bo'limni O'chirish") { vm.deleteLastDepartment() }
                    }
                    .buttonStyle(.borderedProminent)

                    // Korxonalar va ularning ichki munosabatlarini ekranga chiqarish
                    ForEach(vm.businesses) { business in
                        VStack(alignment: .leading, spacing: 5) {
                            Text("Kompaniya: \(business.name ?? "")").font(.headline)

                            if let depts = business.departments?.allObjects as? [DepartmentEntity] {
                                Text("A'zo bo'limlar: " + depts.map { $0.name ?? "" }.joined(separator: ", ")).font(.subheadline)
                            }
                            if let emps = business.employees?.allObjects as? [EmployeeEntity] {
                                Text("Ishchilar: " + emps.map { $0.name ?? "" }.joined(separator: ", ")).font(.caption).foregroundColor(.gray)
                            }
                        }
                        .padding()
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(10)
                    }
                }
                .padding()
            }
            .navigationTitle("Core Data BootCamp")
        }
    }
}

Xulosa

Core Data relationships tizimi ilovangizdagi jadvallarni bir-biri bilan xavfsiz bogʻlab turadi. Asosiy eʼtiborni Inverse xususiyatini oʻchirmaslikka hamda maʼlumot oʻchib ketganda ilova xato bermasligi uchun vaziyatga qarab Nullify yoki Cascade qoidalarini toʻgʻri tanlashga qaratish lozim.

Buy mea coffee