Mobil Programlama

iOS

SQLite Kullanımı

Lisans: Creative Commons 11.12.2020 tarihinde güncellendi
Bakabileceğiniz Etiketler: Eğitmen: Geleceği Yazanlar Ekibi

iOS platformu da diğer mobil platformlar gibi veritabanı olarak SQLite kullanımını tercih etmektedir. Hem SQL komutlarını çalıştırabilmesi hem de mobil cihazlar gibi düşük kapasiteli ortamlarda kolayca çalışabilmesi SQLite’ı Android ve iOS platformlarında bir numaralı tercih haline getirmiştir.

http://www.sqlite.com adresinden indirebileceğiniz SQLite’ı komut satırından çalıştırabilirsiniz. Terminal penceresinde aşağıdaki komutu girdiğinizde yeni bir veritabanı yaratılacak ve bellekte aynı isimle bir dosya oluşturulacaktır.

Last login: Thu Apr  4 15:28:55 on ttys001
Ozans-MacBook-Pro:~ ozanuysal$ sqlite3 countries

Bu komutla beraber bellekte countries adında bir dosya oluşur ve biz veritabanı üzerinde SQL komutlarını kullanmaya başlayabiliriz. SQL komutları MySQL gibi karmaşık veritabanlarına göre daha basit olsa da SQLite, mobil uygulamalarda karşılaşabileceğiniz her türlü ihtiyacı giderecek kapasiteye sahiptir.

Şimdi aşağıdaki komutu girerek yeni bir tablo oluşturalım:

SQLite version 3.7.12 2012-04-03 19:43:07
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table country (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, code TEXT);

Yukarıdaki komutla country adında bir tablo oluşturmuş olduk. Burada id değeri her satıra karşılık gelen anahtar değeridir ve ana anahtar olarak tanımlanmıştır (PRIMARY KEY). AUTOINCREMENT her eklenen satırda otomatik olarak arttırılacağını gösterir. title ve code eklenen ülkenin adı ve ülke kodu için ayrılmıştır ve TEXT veritipindedir.

NOT : SQLite veritipleri ve detaylı SQL komutları iOS konusu dışında olduğundan bu modülde anlatılmayacaktır. Ayrıntılı bilgi için http://www.sqlite.com ya da Türkçe bir kaynak için: http://samertek.com/wp-content/uploads/2012/10/sqlite_veritaban%C4%B1_programlama_GUI.pdf adreslerini ziyaret edebilirsiniz.

Bu şekilde yeni bir tablo oluşturmuş olduk. Aşağıdaki komutla yeni bir satır ekliyoruz:

sqlite> INSERT INTO country VALUES (1,'Almanya','49');

Yukarıdaki INSERT cümlesi ile country tablosuna Almanya adında 49 koduna sahip bir ülke girmiş olduk. Bundan sonra diğer ülkeleri de girerek tablomuzu dolduralım.

sqlite> INSERT INTO country VALUES (1,'Almanya','49');
sqlite> INSERT INTO country VALUES (2,'Turkiye','90');
sqlite> INSERT INTO country VALUES (3,'Ingiltere','44');
sqlite> INSERT INTO country VALUES (4,'Amerika','49');

Bu şekilde tablomuza dört adet ülke girmiş olduk. Son adımda aşağıdaki komutu girerek veritabanından çıkıyoruz.

sqlite> .quit
Ozans-MacBook-Pro:~ ozanuysal$

Xcode içerisinde yeni bir proje oluşturalım ve UITableView ile tablo doldurduğumuz kodun bir benzerini oluşturalım. Hatırlarsanız o bölümde bir dizi içerisinde yer alan ülkeler ekranda gösterilmişti. Bu bölümde ise tablodaki ülke isimlerini az önce oluşturduğumuz veritabanından çekeceğiz. Interface Builder yardımıyla ekrana bir tablo atalım ve gerekli datasource ve delegate tanımlamalarını gerçekleştirelim:

datasource ve delegate tanımlamaları

 

ViewController.h dosyamız ise aşağıdaki şekilde tanımlanacaktır.

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) NSMutableArray *countryList;

- (void) fillCountries;
@end

 

countryList dizisi veritabanından gelecek ülke listesini tutacak dizi olacaktır. fillCountries metodu veritabanından tablo verilerini çekip countryList dizisi içerisine doldurmakla görevlidir.

fillCountries metoduna geçmeden önce projemizde sqlite kullanmak için gerekli bazı adımları gerçekleştirmemiz gerekir. Öncelikle sqlite.lib dosyasını projemize ekleyelim.

Proje ekleme

 

İlk aşamada oluşturduğumuz veritabanını da proje içerisinde kullanmak için countries dosyasını projeye eklememiz gereklidir. Bu amaçla dosya ekle seçeneği ile veritabanı dosyasını projeye ekliyoruz.

Projeye veritabanı dosyası ekleme

 

Bu noktadan sonra countries dosyası projenin içerisinde yer alıyor. Bildiğiniz gibi iOS uygulamaları diskte dosya yazma yapabilmek için kendilerine ayrılmış özel alanları kullanıyordu. Eğer veritabanına yeni bir satır eklemek istersek bu bir yazma işlemi olacağından veritabanı dosyasını da bu özel alana taşımamız gerekir. İşte bu yüzden uygulama açılırken AppDelegate dosyası içerisinde ufak bir dosya kopyalama işlemi gerçekleştiriyoruz. Bu sayede veritabanı okuma ve yazma işlemlerine açık hale geliyor.

 (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if(![[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/Documents/countries.sqlite",NSHomeDirectory()]])
    {
        NSString *oldDatabasePath = [[NSBundle mainBundle] pathForResource:@"countries" ofType:@""];
        [[NSFileManager defaultManager] copyItemAtPath:oldDatabasePath toPath:[NSString stringWithFormat:@"%@/Documents/countries.sqlite",NSHomeDirectory()] error:nil];
        NSLog(@"Dosya kopyalama");
    }
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

Burada yapılan işlemi kısaca anlatırsak NSFileManager ile countries.sqlite adında bir dosyanın Documents altında yer alıp almadığı kontrol ediliyor. Eğer yoksa uygulama ilk kez açılıyor demektir ve dosyayı Documents altına kopyalamamız gerekir. copyItemAtPath metodu da bu kopyalama işlemini gerçekleştirir. Bu işlemin ardından veritabanı okuma ve yazma işlemine uygun bir alandadır.

Şimdi ViewController.m içerisinde fillCountries metodunun uyarlamasını yapalım:

(void) fillCountries
{
    NSString *databasePath = [NSString stringWithFormat:@"%@/Documents/countries.sqlite",NSHomeDirectory()];
    NSLog(@"%@",databasePath);
    countryList = [[NSMutableArray alloc] init];
    sqlite3 *database;
    if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
    {
        NSLog(@"Veritabanı açık");
        const char *sqlStatement = [@"SELECT * FROM country" UTF8String];
        NSLog(@"Query : %s",sqlStatement);
        sqlite3_stmt *compiledStatement;
        if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
        {
            while(sqlite3_step(compiledStatement) == SQLITE_ROW)
            {
                NSNumber *countryId = [NSNumber numberWithInt:(int)sqlite3_column_int(compiledStatement, 0)];
                NSString *title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
                NSString *code = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
                NSDictionary *country = [NSDictionary dictionaryWithObjects:@[countryId,title,code] forKeys:@[@"id",@"title",@"code"]];
                [countryList addObject:country];
            }
        }
    }
    sqlite3_close(database);
}

Öncelikle countryList değişkenini alloc ve init metodlarıyla bir NSMutableArray olarak tanımlıyoruz. sqlite_open metodu bize databasePath ile belirtilen veritabanına erişim sağlıyor. Eğer metot SQLITE_OK dönerse veritabanına sorunsuz bir şekilde bağlandık demektir. Bu aşamadan sonra sqlStatement değişkeni veritabanına bir SELECT sorgusu gönderiyor ve tablodaki bütün veriyi çekmemizi sağlıyor. Burada sqlite3_step metodu bir imleç (cursor) aracılığıyla satırları dolaşarak o satıra ait bilgileri okuyor ve biz bunları sqlite3_column metotları aracılığıyla ilgili değişkenlere atıyoruz. id değeri bize INTEGER tipinde geldiğinden sqlite3_column_int metodunu, title ve code değerleri TEXT geldiğinden sqlite3_column_text metodunu kullanıyoruz. Son olarak da ülkeleri NSDictionary tipinde anahtar ve değer mantığıyla saklayarak countryList dizisine gönderiyoruz.

Son aşamada yapmamız gereken ise UITableDataSource ve UITableViewDelegate temsilci metotlarının countryList kullanarak tabloyu doldurmasını sağlamak olacaktır.

#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 = [[countryList objectAtIndex:[indexPath row]] objectForKey:@"title"];
    return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [countryList count];
}

Bu metotlar daha önceki bölümde detaylı bir şekilde anlatıldığından üzerinde durmayacağız. Özetlersek tablodaki eleman sayısı countryList count metodu ile belirtiliyor. Tablodaki satırlar ise o satıra denk gelen NSDictionary objesi içerisinden title özelliği ile dolduruluyor. Bu işlemden sonra karşımıza aşağıdaki tablo gelecektir:

Ekran görüntüsü

 

Satırlara dokunduğumuzda oluşturulacak eylem ise aşağıdaki gibi belirleniyor.

(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Ülkeler"
        message:[NSString stringWithFormat:@"%@ %@",[[countryList objectAtIndex:indexPath.row] objectForKey:@"title"],[[countryList objectAtIndex:indexPath.row] objectForKey:@"code"]]
        delegate:self
        cancelButtonTitle:@"Tamam"
        otherButtonTitles:nil];
    [message show];
}

 

Kullanıcı bir satıra dokunduğunda ekranda ülke adı ve ülke kodu görüntülenecektir. Bu değerler veritabanından alınan verilerle doldurulan countryList tablosundan gelir:

 

ViewController.m dosyasının tam hali aşağıdaki gibidir:

//
//  ViewController.m
//  SQLiteExample
//
//  Created by Ozan Uysal on 4/2/13.
//  Copyright (c) 2013 Turkcell. All rights reserved.
//

#import "ViewController.h"
#import <sqlite3.h>

@interface ViewController ()
@end
@implementation ViewController
@synthesize tableView;
@synthesize countryList;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self fillCountries];
    
    [tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
#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 = [[countryList objectAtIndex:[indexPath row]] objectForKey:@"title"];
    return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [countryList count];
}

#pragma mark UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Ülkeler"
        message:[NSString stringWithFormat:@"%@ %@",[[countryList objectAtIndex:indexPath.row] objectForKey:@"title"],[[countryList objectAtIndex:indexPath.row] objectForKey:@"code"]]
        delegate:self
        cancelButtonTitle:@"Tamam"
        otherButtonTitles:nil];
    [message show];
}
- (void) fillCountries
{
    NSString *databasePath = [NSString stringWithFormat:@"%@/Documents/countries.sqlite",NSHomeDirectory()];
    NSLog(@"%@",databasePath);
    
    countryList = [[NSMutableArray alloc] init];
    
    sqlite3 *database;
    if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
    {
        NSLog(@"Veritabanı açık");
        const char *sqlStatement = [@"SELECT * FROM country" UTF8String];
        NSLog(@"Query : %s",sqlStatement);
        sqlite3_stmt *compiledStatement;
        if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
        {
            while(sqlite3_step(compiledStatement) == SQLITE_ROW)
            {
                NSNumber *countryId = [NSNumber numberWithInt:(int)sqlite3_column_int(compiledStatement, 0)];
                NSString *title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
                NSString *code = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
                NSDictionary *country = [NSDictionary dictionaryWithObjects:@[countryId,title,code] forKeys:@[@"id",@"title",@"code"]];
                [countryList addObject:country];
            }
        }
    }
    sqlite3_close(database);
}
@end