SQLite veritabanına göre daha karmaşık bir veri saklama yapısı olan Core Data, Mac OS X platformundan sonra iOS platformuna taşınarak iPhone uygulamalarında da kullanılabilir hale geldi. Tablolar arasında ilişki kurma, karmaşık veritabanı sorguları yazma, bellek üzerinde saklama (cache) gibi özellikler sunan Core Data, uygulamanızda gelişmiş bir veritabanı sistemine ihtiyacınız olduğu durumlarda birçok ihtiyacınızı karşılayacaktır.
UYARI: Core Data bir veritabanı sistemi değildir, arka planda veriler SQLite üzerine kayıt edilir. Core Data SQLite kullanımına göre bir takım avantajlar sunuyor olsa da çoğu zaman uygulamanızda sadece SQLite kullanmak yeterli olacaktır.
Bu bölümde sizlere Core Data’nın temel fonksiyonları ile ilgili bir giriş yapacağız. Projemize Core Data özelliği katmak için yeni proje yaratırken Use Core Data özelliğini işaretlememiz gerekir.
Bu seçeneği seçtiğimizde projeyle birlikte bir xcdatamodel adında bir dosya yaratılacaktır. Bu dosya veritabanında tutulacak objelerin tanımlarını ve bunların ilişkilerini içerir. Burada yapacağınız tanımlar arka planda yer alan veritabanında otomatik oluşturulacaktır.
Daha önceki veritabanı bölümünde anlattığımız ülkeler tablosunu hatırlarsanız, ülkelerin adı ve kodlarını içeren bir SQLite veritabanı oluşturulmuştu. Bu bölümde aynı işlemi Core Data ile gerçekleştireceğiz.
Add Entity düğmesini kullanarak yeni bir ülke sınıfı yaratalım ve buna Country adını verelim. Ardından Add Attribute ile ülke içerisinde yer alan isim ve kod özelliklerini nesne (obje) içerisinde tanımlayalım.
Burada değişkenlerin tanımları yapılırken iki değişken de metin değeri taşıyacağından String tipi seçilmiştir. Siz ihtiyacınıza göre başka tipleri seçebilirsiniz. Bu işlemi bitirdikten sonra kod içerisinde Country adında bir nesne yaratmamız gerekir.
Yeni bir dosya yaratmak için File > New > File yolunu takip ederek yukarıdaki sihirbazı açalım. Burada Core Data altında yer alan NSManagedObject subclass seçeneğini işaretleyelim. NSManagedObject Core Data tarafından yönetilen nesneleri tanımlamaktadır.
Bir sonraki ekranda xcdatamodel içerisinde tanımladığımız tabloları görürüz. Burada Country nesnesini seçerek bununla ilgili sınıfın oluşturulmasını sağlıyoruz.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Country : NSManagedObject
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSString * code;
@end
Bu aşamadan sonra artık Core Data ile çalışmaya başlayabiliriz. İlk yapacağımız, listeye üç adet ülke eklemek olacak. Bu işlemi uygulama ilk defa açıldığında bir kereliğine gerçekleştireceğiz. Şimdi AppDelegate içerisinde aşağıdaki kodu didFinishLaunchingWithOptions içerisine ekleyelim.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(![[defaults objectForKey:@"firstTime"] isEqualToString:@"no"])
{
NSManagedObjectContext *context = [self managedObjectContext];
Country *germany = [NSEntityDescription
insertNewObjectForEntityForName:@"Country"
inManagedObjectContext:context];
germany.title = @"Almanya";
germany.code = @"49";
Country *turkey = [NSEntityDescription
insertNewObjectForEntityForName:@"Country"
inManagedObjectContext:context];
turkey.title = @"Türkiye";
turkey.code = @"90";
Country *france = [NSEntityDescription
insertNewObjectForEntityForName:@"Country"
inManagedObjectContext:context];
france.title = @"Fransa";
france.code = @"49";
NSError *error;
if(![context save: &error])
{
NSLog(@"Hata : %@", [error localizedDescription]);
}
[defaults setObject:@"no" forKey:@"firstTime"];
[defaults synchronize];
}
Uygulamanın ilk defa açılıp açılmadığını NSUserDefaults üzerinden test edebiliriz. NSUserDefaults bize anahtar - değer ilişkisi ile bir takım verileri saklamamıza yardım eder ve genellikle uygulama içindeki basit saklama ihtiyaçları için kullanılır. Biz de burada firstTime adında bir değerin varlığını sorgulayacağız. Eğer uygulama ilk defa açılmışsa bu sorgu bize nil değer döneceğinden uygulamanın ilk defa açıldığından emin olabiliriz. if yapısı işini tamamladığında firstTime için no değerini vererek daha sonraki açılışlarda bu if yapısının çalışmamasını sağlarız.
Core Data üzerinde yapılacak işlemler NSManagedObjectContext üzerinden yürütülür. xcdatamodel içerisinde tanımlanan her türlü nesnenin güncelleme, sorgu ve silme işlemleri Core Data tarafından gerçekleştirilir. En alt katmanda yer alan veritabanı ile bellekte yer alan nesneler arasındaki operasyonların düzgün yönetilmesi için NSManagedObjectContext üzerinden işlem yapılması gerekir.
Yukarıda üç adet ülke ekleyeceğimizden ekleme amacıyla bir Core Data objesi oluşturup bu objenin değerlerini NSEntityDescription insertNewObjectForEntityForName ile belirtiyoruz. Burada oluşturulan objelerin Country ile ilişkili oldukları bildiriliyor. Üç adet ülke tanımlandıktan sonra context save metodu ile NSManagedObjectContext üzerinde yapılan değişiklikler kayıt ediliyor. Eğer bir sorun gerçekleşmediyse error değeri boş gelecektir.
Şimdi SQLite bölümüne benzer şekilde ülkeleri listelemeyi göstereceğiz. Bunun için UITableView sınıfında bir tablo ekleyerek temsilci metotları aşağıdaki gibi dolduralım.
#pragma mark UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"CountryCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [(Country *)[countries objectAtIndex:indexPath.row] title];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [countries count];
}
#pragma mark UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Ülkeler"
message:[(Country *)[countries objectAtIndex:indexPath.row] code]
delegate:self
cancelButtonTitle:@"Tamam"
otherButtonTitles:nil];
[message show];
}
Buradaki tek fark artık countries dizisi içerisinde yer alan objelerin NSDictionary yerine Country sınıfından oluşturulması. Yine eski örnekte olduğu gibi satırların üzerine tıklandığında ülke kodu görüntülenecek.
Bu aşamada yapmamız gereken ülkeleri countries dizisine NSManagedObjectContext ile doldurmak olacaktır. Şimdi reloadTable adında bir metot oluşturarak içerisini aşağıdaki gibi dolduralım;
- (void) reloadTable
{
[countries removeAllObjects];
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Country"
inManagedObjectContext:context];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:nil];
[countries addObjectsFromArray:fetchedObjects];
[myTableView reloadData];
}
Burada fetchRequest Core Data üzerinde gerçekleşecek sorguyu belirtmektedir. Biz herhangi bir ayrım yapmadan bütün Country objelerinin gelmesini talep ediyoruz. NSSortDescriptor ise bu objelerin title değerlerine göre alfabetik sıralanmaları gerektiğini belirtiyor. executeFetchRequest ise verilen parametrelerle sorguyu gerçekleştiriyor ve sonuçları bir dizi halinde getiriyor. Biz de gelen sonuçları countries dizisine ekleyerek tabloyu reloadData metoduyla yeniliyoruz.
Tablodaki bir veriyi silmek ve bu değişikliği Core Data üzerinde göstermek içinse aşağıdaki temsilci metodu kullanırız:
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(editingStyle == UITableViewCellEditingStyleDelete)
{
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
NSManagedObject *eventToDelete = [countries objectAtIndex:indexPath.row];
[context deleteObject:eventToDelete];
[countries removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
NSError *error = nil;
if (![context save:&error])
{
// Handle the error.
}
}
}
Bu metot ile kullanıcı bir satırı sağa doğru çektiğinde silme düğmesi görüntülenir ve düğmeye basıldığında yukarıdaki metod uyarılır. Biz burada countries dizisinde o satıra karşılık gelen objeyi alarak context içerisinde silinme uyarısı veriyoruz. İşlem başarıyla gerçekleşirse nesne tablodan silinecektir.
Son olarak da Core Data içerisinde basit bir arama yapmayı göstereceğiz. NSPredicate sorguları ile Core Data içerisindeki objelerde rahatlıkla arama yapabilirsiniz. Bu amaçla aşağıdaki arama metodunu oluşturuyoruz:
- (NSArray *) search:(NSString *) searchString
{
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
[NSString stringWithFormat:@"title contains[cd] '%@'",searchString]];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Country"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:nil];
return fetchedObjects;
}
Arama için yine bir NSFetchRequest oluşturarak Country tablosu içerisinde arama yapmak istediğimizi belirtiyoruz. Burada önemli olan NSPredicate içerisinde yazacağımız sorgu olacaktır. Biz title sütununun büyük küçük harf gözetmeksizin yapacağımız sorgu kelimesini içerdiğini kontrol edeceğimizden “contains[cd]” cümlesini kullanıyoruz. Daha sonra veritabanından sorgumuza uyan Country nesneleri getirilecektir.
NOT : NSPredicate NSArray içerisinde arama yapmak için de kullanılabilir.