chapter 17 - macfeteria.commacfeteria.com/wp-content/uploads/2013/11/chapter_17.pdf ·...

21
Chapter 17 Database & Core Data การจัดการข้อมูลด้วย Cocoa สามารถทำได้หลายวิธีด้วยกัน ในบทที่ผ่านมาก็ได้ใช้ทั้ง plist หรือเขียนไฟล์จัดการ ด้วยตัวเองกันไปแล้ว แต่เมื่อต้องจัดการกับข้อมูลที่มีปริมาณมากขึ้นหรือมีความซับซ้อน การจัดการข้อมูลด้วย plist อาจไม่ใช่ ทางเลือกที่เหมาะสมมากนัก วิธีการที่ดีกว่านั้นคือการใช้ระบบฐานข้อมูล ซึ่งช่วยให้จัดการข้อมูลต่างๆได้อย่างมีประสิทธิภาพ และง่ายกว่าเดิม ในบทนี้จะได้เรียนรู้การจัดการฐานด้วย SQLite ซึ่งเป็นระบบฐานข้อมูลเบื้องต้น นอกจากนี้ยังจะได้ศึกษา และทำความเข้าใจการจัดการข้อมูลในระดับสูงด้วยการใช้ Core Data Framework SQLite ระบบฐานข้อมูลเชิงสัมพันธ์หรือที่เรียกว่า Relational Database เป็นระบบการจัดเก็บข้อมูลที่อยู่ในรูปแบบของตาราง โดย แต่ละตารางจะแบ่งออกเป็นแถว ในแต่ละแถวก็ยังแบ่งย่อยออกเป็นคอลัมน์ตามแต่ผู้ใช้กำหนด การจัดการข้อมูลแบบตารางนีมีความนิยมแพร่หลายมากที่สุดเพราะง่ายต่อการทำความเข้าใจ เช่น ระบบฐานข้อมูล MySQL , Oracle , Microsoft SQL เป็นต้น และ SQLite ก็เป็นหนึ่งในระบบฐานข้อมูลแบบ relational database เช่นกัน การใช้ระบบฐานข้อมูล SQLite มีข้อดี หลายอย่าง เช่นทำงานเร็ว ใช้หน่วยความจำน้อย และข้อดีที่เห็นได้ชัดอีกอย่างหนึ่งคือ SQLite เป็นฐานข้อมูลเบื้องต้นที่มา พร้อมกับ iOS และ Mac OS X กล่าวคือมีไลบรารีและชุดคำสั่งภาษา C ให้เรียกใช้งาน โดยไม่ต้องติดตั้งไลบรารีจากภายนอก เพิ่มเติมแต่อย่างใด Expense Project โปรแกรมที่จะได้เขียนกันต่อไปนี้เป็นโปรแกรมบันทึกรายจ่ายประจำวันด้วย SQLite เนื่องจากหนังสือเล่มนี้เน้นการใช้ Framework และ Library เป็นหลัก จึงจะไม่อธิบายการทำงานของคำสั่งทาง SQL โดยละเอียด ผู้อ่านควรมีความรู้เกี่ยวกับ ระบบการจัดการฐานข้อมูลเบื้องต้นมาก่อน Create Database ก่อนที่จะเริ่มลงมือเขียนโปรแกรม เราจะสร้างไฟล์ฐานข้อมูลกันเสียก่อน และเพื่อให้ง่ายต่อการทำความเข้าใจ ฐานข้อมูลที่ใช้ ในโปรแกรมจึงได้ออกแบบไม่ซับซ้อน โดยประกอบไปด้วยตาราง EXPENSE เพียงตารางเดียว ที่เก็บข้อมูลหมายเลขของ รายการ (index) , กลุ่มของรายการ (group), จำนวนเงิน (amount), และสุดท้ายคือเวลา(date) ดังที่แสดงต่อไปนีCopyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

Upload: others

Post on 12-Jul-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

  • Chapter 17

    Database & Core Data!! การจัดการข้อมูลด้วย Cocoa สามารถทำได้หลายวิธีด้วยกัน ในบทที่ผ่านมาก็ได้ใช้ทั้ง plist หรือเขียนไฟล์จัดการด้วยตัวเองกันไปแล้ว แต่เมื่อต้องจัดการกับข้อมูลที่มีปริมาณมากขึ้นหรือมีความซับซ้อน การจัดการข้อมูลด้วย plist อาจไม่ใช่ทางเลือกที่เหมาะสมมากนัก วิธีการที่ดีกว่านั้นคือการใช้ระบบฐานข้อมูล ซึ่งช่วยให้จัดการข้อมูลต่างๆได้อย่างมีประสิทธิภาพและง่ายกว่าเดิม ในบทนี้จะได้เรียนรู้การจัดการฐานด้วย SQLite ซึ่งเป็นระบบฐานข้อมูลเบื้องต้น นอกจากนี้ยังจะได้ศึกษาและทำความเข้าใจการจัดการข้อมูลในระดับสูงด้วยการใช้ Core Data Framework

    SQLite

    ระบบฐานข้อมูลเชิงสัมพันธ์หรือที่เรียกว่า Relational Database เป็นระบบการจัดเก็บข้อมูลที่อยู่ในรูปแบบของตาราง โดยแต่ละตารางจะแบ่งออกเป็นแถว ในแต่ละแถวก็ยังแบ่งย่อยออกเป็นคอลัมน์ตามแต่ผู้ใช้กำหนด การจัดการข้อมูลแบบตารางนี้มีความนิยมแพร่หลายมากที่สุดเพราะง่ายต่อการทำความเข้าใจ เช่น ระบบฐานข้อมูล MySQL , Oracle , Microsoft SQL เป็นต้น และ SQLite ก็เป็นหนึ่งในระบบฐานข้อมูลแบบ relational database เช่นกัน การใช้ระบบฐานข้อมูล SQLite มีข้อดีหลายอย่าง เช่นทำงานเร็ว ใช้หน่วยความจำน้อย และข้อดีที่เห็นได้ชัดอีกอย่างหนึ่งคือ SQLite เป็นฐานข้อมูลเบื้องต้นที่มาพร้อมกับ iOS และ Mac OS X กล่าวคือมีไลบรารีและชุดคำสั่งภาษา C ให้เรียกใช้งาน โดยไม่ต้องติดตั้งไลบรารีจากภายนอกเพ่ิมเติมแต่อย่างใด

    Expense Project

    โปรแกรมที่จะได้เขียนกันต่อไปนี้เป็นโปรแกรมบันทึกรายจ่ายประจำวันด้วย SQLite เนื่องจากหนังสือเล่มนี้เน้นการใช้ Framework และ Library เป็นหลัก จึงจะไม่อธิบายการทำงานของคำสั่งทาง SQL โดยละเอียด ผู้อ่านควรมีความรู้เก่ียวกับระบบการจัดการฐานข้อมูลเบื้องต้นมาก่อน

    Create Databaseก่อนที่จะเริ่มลงมือเขียนโปรแกรม เราจะสร้างไฟล์ฐานข้อมูลกันเสียก่อน และเพ่ือให้ง่ายต่อการทำความเข้าใจ ฐานข้อมูลที่ใช้ในโปรแกรมจึงได้ออกแบบไม่ซับซ้อน โดยประกอบไปด้วยตาราง EXPENSE เพียงตารางเดียว ที่เก็บข้อมูลหมายเลขของรายการ (index) , กลุ่มของรายการ (group), จำนวนเงิน (amount), และสุดท้ายคือเวลา(date) ดังที่แสดงต่อไปนี้

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • EXPENSEEXPENSE

    Column Name Type

    index INTEGER ( auto increment )

    group CHAR

    amount DOUBLE

    date DATETIME ( CURRENT_TIMESTAMP )

    จากตารางได้กำหนดให้ หมายเลขของรายการ (index) เพ่ิมค่าขึ้นโดยอัตโนมัติ, และบันทึกค่าของเวลาอัตโนมัติ เช่นเดียวกัน เมื่อเขียนออกมาเป็นคำสั่ง SQL ก็จะได้ดังนี้

    CREATE TABLE "EXPENSE" ("no" INTEGER PRIMARY KEY NOT NULL ,"group" CHAR DEFAULT (null) ,"amount" DOUBLE,"date" DATETIME DEFAULT (CURRENT_TIMESTAMP) )

    การสร้างไฟล์ฐานข้อมูล SQLite รวมไปถึงการใช้ sql statement ต่างๆใน Mac สามารถทำผ่าน Terminal โดยที่ไม่ต้องลงโปรแกรมเพ่ิมแต่อย่างใด วิธีการคือให้เปิดโปรแกรม Terminal และใช้คำสั่ง sqlite3 ตามด้วยชื่อของ database ที่จะสร้างเช่น sqlite3 expense.sqlite หลังจากนั้นจะเห็น sqlite command prompt ดังภาพ

    เมื่อเข้าสู่ sqlite command prompt แล้วก็สามารถสร้างตารางจาก sql statement ที่ได้ให้ไปได้ทันที เสร็จแล้วก็สั่ง .exit เพ่ือออกจาก command prompt เพียงเท่านี้ก็จะได้ไฟล์ database ที่พร้อมสำหรับการใช้งาน อย่างไรก็ตามการสร้างไฟล์ด้วยวิธีแบบนี้อาจจะไม่สะดวกมากนักและเกิดความผิดพลาดได้ง่ายเพราะต้องพิมพ์ sql statement เองทั้งหมด จึงแนะนำให้ใช้โปรแกรมจัดการ SQLite ที่เป็นแบบ GUI ซึ่งหาได้ใน App Store หรือจะเป็นโปรแกรมแจกฟรี เช่น SQLite Manager ดังที่แสดงในรูป (add on ของ Firefox) ก็สามารถใช้งานได้เช่นเดียวกัน

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • Adding SQLite Libraryหลังจากสร้างไฟล์ฐานข้อมูลเรียบร้อยแล้ว ต่อไปก็คือขั้นตอนการเพ่ิมไลบรารี่ SQLite เพ่ือใช้งานในโปรเจค เพราะเมื่อสร้างโปรเจคขึ้นมาใหม่จะยังไม่สามารถใช้ SQLite C API ได้ต้องเพ่ิม libsqlite3.dylib เข้ามายังโปรเจคเสียก่อน การเพ่ิม library นี้สามารถทำได้ที่ Build Phases > Link Binary With Libraryies เมื่อเพ่ิมเรียบร้อยแล้วก็จะเห็นดังรูป

    หลังจากที่ได้เพ่ิมไลบรารีในโปเจคแล้ว ต่อไปจะสร้างคลาส DBController ขึ้นมาเพ่ือใช้อำนวยความสะดวกในการจัดการฐานข้อมูล เช่น การอ่านไฟล์ เป็นต้น

    DBController.h1 #import 2 #import 3

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 4 @interface DBController : NSObject5 {6 sqlite3* _database;7 }8 -(id) initWithFile:(NSString*)fileName;9 -(void) closeDB;10 -(BOOL) insertExpenseToGroup:(NSString*)group amount:(float)cost;11 -(NSArray*) expenseFromGroup:(NSString*)group;12 @end

    ฟังชั่นต่างๆของ SQLite ประกาศใน sqlite3.h ดังนั้นก่อนจะใช้คำสั่งต่างของ SQLite ได้ต้องเพ่ิม sqlite3.h เข้ามาเสียก่อน จากนั้นคลาส DBController ก็ได้ประกาศ _database ที่เป็นพ้อยเตอร์ของ sqlite3 ซึ่งในเอกสารการ SQL API ได้บอกว่าตัวแปรชนิดนี้เป็น database connection ที่จะทำหน้าที่ติดต่อกับไฟล์ฐานข้อมูล ในส่วนของเมธอดที่ได้ประกาศประกอบไปด้วยเมธอดที่ใช้ เปิดปิดฐานข้อมูล , บันทึกรายจ่าย และขอดูรายการค่าใช้จ่ายต่างๆ

    DBController.m1 #import "DBController.h"23 @implementation DBController4 -(id) initWithFile:(NSString*)fileName5 {6 self = [super init];7 if( self != nil)8 {9 if (sqlite3_open([fileName UTF8String], &_database) != SQLITE_OK)10 {11 NSLog(@"Failed to open database!");12 return nil;13 }14 }15 return self;16 }1718 -(void) closeDB19 {20 sqlite3_close(_database);21 }

    เมธอด initWithFile: มีหน้าที่หลักคือเรียกฟังชั่น sqlite3_open ซึ่งเป็นฟังชั่นที่ใช้ในการเปิดไฟล์ SQLite โดยพารามิเตอร์แรกคือที่ไฟล์ SQLite ที่ต้องการจะใช้งาน และต้องส่งเป็นสตริงของภาษา C ดังนั้นตัวแปร fileName จึงใช้เมธอด UTF8String ซึ่งจะได้สตริงของภาษา C เมื่อฟังชั่น sqlite3_open ทำงานสำเร็จก็จะส่งค่า YES กลับคืนมาพร้อมกับ database connection ที่จะส่งกลับออกมาผ่านทางพารามิเตอร์ที่สอง แต่ถ้าไม่สำเร็จจะได้ NO และไม่ได้รับ database connection ดังนั้นตัวแปร _database ก็จะเป็น nil ส่วนเมธอดที่สอง closeDB ทำหน้าที่ปิด database connection หลังจากการใช้งานเสร็จสิ้น

    22 -(BOOL) insertExpenseToGroup:(NSString*)group amount:(float)amount23 {24 BOOL success = YES;25 sqlite3_stmt *stmt;26 27 NSString *update = @"INSERT INTO \"EXPENSE\" (\"group\",\"amount\") VALUES (?,?);";28 29 if (sqlite3_prepare_v2(_database, [update UTF8String], -1, &stmt, nil) == SQLITE_OK)30 {31 sqlite3_bind_text(stmt, 1, [group UTF8String], -1, NULL);32 sqlite3_bind_double(stmt, 2, amount);33 if (sqlite3_step(stmt) != SQLITE_DONE)34 NSLog(@"Error inserting table: %s", sqlite3_errmsg(_database));35 sqlite3_finalize(stmt);36 }37 else

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 38 {39 NSLog(@"Error: %s", sqlite3_errmsg(_database));40 success = NO;41 }42 return success;4344 }

    เมธอด insertExpenseToGroup:amount: เป็นเมธอดเพ่ือใช้บันทึกรายจ่าย เริ่มต้นด้วยการประกาศ stmt เป็นตัวแปรเก็บ sqlite statement จากนั้นประกาศตัวแปร update เพ่ือใช้เป็น query ของ statement นี้ ซึ่ง query ที่ได้เขียนไปใช้สำหรับการเพ่ิมข้อมูลลงในฐานข้อมูล เมื่อพิจารณา query จะเห็นว่าจำนวนของค่าที่ query ต้องการคือ 2 อย่างด้วยกันคือ group และ amout ถ้าหากย้อนกลับไปดูโครงสร้างของตารางจะเห็นว่ามีค่าที่ต้องเก็บทั้งหมดด้วยกัน 4 ค่า แต่ที่ส่งไปเพียง 2 ค่า นั่นก็เพราะว่า index ได้ประกาศให้เพ่ิมค่าโดยอัตโนมัติ และ date ก็กำหนดให้บันทึกเวลาอัตโนมัติเช่นกัน ดังนั้น query จึงไม่จำเป็นต้องใช้ค่าเหล่านี้ในลำดับต่อมาเรียกใช้ฟังชั่น sqlite3_prepare_v2 เพ่ือเตรียม sqlite statement ที่จะใช้ทำงาน ในฟังชั่นนี้ต้องการพารามิเตอร์ด้วยกันทั้งหมด 5 ค่า โดยค่าแรกเป็น database connection ที่ได้อธิบายไปแล้ว พารามิเตอร์ที่สองคือ query ที่ต้องส่งเป็นสตริงของภาษา C ดังนั้นตัวแปร update จึงใช้เมธอด UTF8String เพ่ือให้ได้ C string ที่ฟังชั่นนำไปใช้งานได้ พารามิเตอร์ที่สามเป็นการกำหนดว่าสตริงที่เป็น query นั้นมีขนาดเท่าใด ในกรณีที่ส่งค่าเป็น -1 ฟังชั่นจะอ่านสตริงไปจนกว่าจะเจอ zero terminator หรือจุดสิ้นสุดของสตริง พารามิเตอร์ที่สี่คือ sqlite statement ที่ต้องการเตรียมใช้งาน และสุดท้ายจะเป็น pointer ไว้ชี้ตำแหน่งของ byte แรกหลังจากการอ่าน query เสร็จสิ้น ในกรณีนี้ไม่ได้ใช้จึงส่ง NULL เมื่อฟังชั่นทำงานเสร็จสิ้นก็จะได้ sql statement ที่พร้อมสำหรับการใช้งาน และในขั้นตอนต่อไปก็คือการกำหนดค่าที่จะใช้ใน query ของ sql statement ซึ่งการกำหนดค่าให้กับ query นั้นมีฟังชั่นให้ใช้ตามชนิดของข้อมูล เช่น sqlite3_bind_text ใช้เพ่ือกำหนดค่าตัวแปรที่เป็นชนิดสตริง โดยพารามิเตอร์แรกเป็น sql statement ส่วนพารามิเตอร์ตัวที่สองคือลำดับของตัวแปรที่ได้กำหนดใน query ส่วนพารามิเตอร์ที่สามคือค่าของสตริงที่ต้องการ ส่วนฟังชั่น sqlite3_bine_double ก็มีการทำงานคล้ายกันคือต้องบอกลำดับและค่าที่ต้องการ เมื่อกำหนดตัวแปรให้กับ query เรียบร้อยแล้ว ขั้นตอนต่อมาคือ คือ sqlite3_step เพ่ือสั่งให้ sqlite statement ที่ได้เตรียมไว้ให้ทำงาน เมื่อทำงานจบ ก็ต้องคืนหน่วยความจำด้วยการเรียก sqlite3_finalize และในกรณีที่เกิดข้อผิดพลาด เราสามารถใช้ฟังชั่น sqlite3_errmsg เพ่ือขอข้อมูลความผิดพลาดจากการทำงานได้ 45 -(NSArray*) expenseFromGroup:(NSString*)group46 {47 sqlite3_stmt *stmt = nil;48 NSString *query = @"SELECT * FROM \"EXPENSE\" where \"group\" = ?;";49 if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, NULL) == SQLITE_OK)50 {51 sqlite3_bind_text(stmt, 1, [group UTF8String], -1, NULL);52 NSMutableArray* result = [NSMutableArray array];53 while (sqlite3_step(stmt) == SQLITE_ROW)54 {55 NSMutableDictionary *row = [NSMutableDictionary dictionary];56 57 int no = sqlite3_column_int(stmt, 0);58 const unsigned char *group = sqlite3_column_text(stmt, 1);59 double amount = sqlite3_column_double(stmt, 2);60 const unsigned char *date = sqlite3_column_text(stmt,3);61 62 [row setObject:[NSNumber numberWithInt:no] forKey:@"INDEX"];63 [row setObject:[NSString stringWithUTF8String:group] forKey:@"GROUP"];64 [row setObject:[NSNumber numberWithDouble:amount] forKey:@"AMOUNT"];65 [row setObject:[NSString stringWithUTF8String:date] forKey:@"DATE"];66 67 [result addObject:row];68 }69 return result;

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 70 }71 else72 {73 NSLog(@"Error updating table: %s", sqlite3_errmsg(_database));74 return [NSArray array];75 }76 }

    เมธอดสุดท้ายคือ expenseFromGroup เมธอดนี้ใช้สำหรับขอดูรายการค่าใช้จ่ายตามกลุ่มที่กำหนด การทำงานในช่วงแรกของเมธอดก็ต้องเตรียม sql statement เช่นเดียวกันกับเมธอดที่ผ่านมา ส่วนที่สำคัญของเมธอดนี้คือ while loop เนื่องจากในการทำงานของเมธอด sqlite3_step จะได้ผลลัพธ์ทีละ 1 แถว ดังนั้นจึงใช้ลูป while สั่งให้ทำงานไปเรื่อยๆจนกว่าจะถึงผลลัพธ์แถวสุดท้าย และใน while loop นี้ก็จะดึงเอาค่าต่างๆตามลำดับของคอลัมน์ โดยเริ่มที่ 0 เป็นคอลัมน์แรกสุด ไปจนถึงคอลัมน์สุดท้าย ก็จะได้ค่าครบตามที่ต้องการ

    main.m1 #import 2 #import "DBController.h"34 int main(int argc, const char * argv[])5 {67 @autoreleasepool8 {9 DBController *db = [[DBController alloc] initWithFile:@"expense.sqlite"];10 [db insertExpenseToGroup:@"Food" amount:400];11 [db insertExpenseToGroup:@"Shopping" amount:300];12 [db insertExpenseToGroup:@"Movie" amount:500];13 [db insertExpenseToGroup:@"Food" amount:400];14 15 16 NSArray* result = [db expenseFromGroup:@"Food"];17 for( NSDictionary* item in result)18 {19 NSLog(@"%@ %@ %@ %@",item[@"INDEX"],item[@"GROUP"],20 item[@"AMOUNT"],item[@"DATE"] );21 }22 [db closeDB];23 24 }25 return 0;26 }

    โปรแกรมหลักได้เพ่ิมรายจ่าย 4 รายการด้วยกัน หลังจากนั้นก็เรียกดูข้อมูลเฉพาะในกลุ่มของ Food ดังนั้นผลลัพธ์จึงเป็นดังนี้

    Program 17.1 Output

    1 Food 400 2013-11-02 23:37:424 Food 900 2013-11-02 23:37:42

    เห็นได้ชัดว่าการใช้ระบบฐานข้อมูลมีความสะดวกมากกว่า plist หรือการจัดเก็บข้อมูลแบบไบนารี่ด้วยตัวเอง แต่อย่างไรก็ตาม plist ก็ยังมีเหมาะกับการใช้เก็บข้อมูลที่ไม่มากนัก เพราะไม่ต้องออกแบบฐานข้อมูลให้ยุ่งยาก ในหัวข้อต่อไปเราจะได้ศึกษาระบบการจัดเก็บข้อมูลของ Cocoa ที่เรียกว่า Core Data กันต่อไป

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • Core DataCore Data คือเฟรมเวิร์คที่ได้ออกแบบมาเพ่ือช่วยในการจัดเก็บข้อมูล ( object persistence framework) Core Data มีความสามารถหลายอย่างมากเป็นต้นว่า ผู้ใช้งานสามารถกำหนดได้ว่าข้อมูลที่จะจัดเก็บนั้นจะใช้ XML , SQLite หรือเก็บใน iCloud แต่แนวคิดการจัดเก็บข้อมูลของ Core Data นั้นจะต่างจาก SQL พอสมควร เพราะไม่ได้มองข้อมูลในรูปแบบตาราง แต่จะมองว่าข้อมูลในรูปแบบของอ็อบเจ็ก (Managed Object) ถึงแม้ว่าตัว Core Data เองนั้นจะเป็นเพียง Framework เพ่ือใช้จัดเก็บข้อมูล ซึ่งไม่ได้รวมไปถึงการออกแบบ managed object ที่จะใช้งาน แต่อย่างไรก็ตามการออกแบบ managed object นั้น ทำได้ง่ายมากใน XCode เพราะมีเครื่องมือช่วย

    Core Data Stack

    ก่อนที่จะไปใช้งาน API และเครื่องมือต่างๆนั้น ต้องทำความเข้าใจเก่ียวกับโครงสร้างของ Core Data ก่อนว่าประกอบด้วยอะไรบ้าง และแต่ละส่วนมีความสัมพันธ์กันอย่างไร อย่างที่เห็นในรูปด้านล่าง Core Data ประกอบไปด้วย 4 ส่วนด้วยกันคือ Managed Object Model , Manage Object Context , Persistant Coordinator และ Data Store ส่วนประกอบทั้งหมดนี้จะเรียกว่า Core Data Stack

    Manage Object Context

    Manage Object Model

    Persistent Coordinator

    Data Store

    Data Storeเป็นชั้นล่างสุดของ framework มีหน้าที่หลักคือจัดการไฟล์ที่ใช้ในการเก็บข้อมูล เช่นการอ่านหรือเขียนไฟล์ โดยทั่วๆไปแล้วแค่ระบุว่าโปรแกรมจะจัดเก็บข้อมูลแบบใดเช่น SQLite หรือ Binary จากนั้นแทบจะไม่ต้องจัดการอะไรเพิ่มเติมในส่วนนี้เลย

    Persistent Coordinator เรียกสั้นๆว่า coordinator เป็นตัวกลางระหว่าง store และ Mange Object Context เช่นเมื่อ Manage Object Context ต้องการข้อมูล ก็จะร้องขอผ่าน coordinator ซึ่งหน้าที่ของ coordinator ก็คือจัดการว่าข้อมูลที่ถูกร้องขอมานั้นอยู่ใน store ใด จากนั้นก็จะไปอ่านข้อมูลใน store นั้น และส่งต่อข้อมูลที่ได้กลับไปให้ Mange Object Context นั่นเอง

    Manage Object Model หรือเรียกง่ายๆว่า model เป็นส่วนที่ใช้ในการกำหนดรูปแบบของอ็อบเจ็ก (Manged Object) ที่จะใช้ในการเก็บข้อมูล รวมไปถึงความสัมพันธ์ระหว่างข้อมูลต่างๆ ถ้าดูตามรูปจะเห็นว่าโมเดลนี้ทำงานเก่ียวข้องกับ coordinator และอย่างที่ได้อธิบายไปว่า

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • coordinator ทำหน้าที่เป็นตัวกลาง คือเมื่อ Manage Object Context ต้องการข้อมูล ก็ต้องติดต่อกับ coordinator แต่หลังจากที่ coordinator ได้ข้อมูลมาจาก store ก็ไม่อาจจะรู้ได้ว่าข้อมูลนี้คืออะไร ดังนั้นการจะสร้าง Managed Object จากข้อมูลที่ได้มาก็ต้องอาศัย Model นี้นั่นเอง

    Manage Object Context หน้าที่ของ context คือใช้จัดการ manged object ทั้งหมดในโปรแกรม ไม่ว่าจะเป็นการสร้าง การลบ หรือการเปลี่ยนแปลง และเมื่อถึงจุดที่ต้องการจะจัดเก็บ ก็สามารถสั่งให้ context นี้จัดเก็บข้อมูลลงฮาร์ดดิสหรือ iCloud ได้ โดยเบื้องหลังการทำงานของ context นี้ก็จะทำงานผ่าน coordinator อีกทอดหนึ่งนั่นเอง

    School Project

    หลังจากทีทำความเข้าใจกับ core data stack ไปเรียบร้อยแล้ว ก็ถึงเวลาที่จะลองใช้ Core Data และโปรแกรมที่จะได้เขียนกันต่อไปนี้ เป็นโปรแกรมรายชื่อนักเรียนอย่างง่าย ประกอบไปด้วยข้อมูล 2 อย่างหลักๆด้วยกันคือ Classroom ไว้เก็บรายชื่อของนักเรียนในห้องทั้งหมด ส่วน Student คือข้อมูลของของนักเรียน ถ้าหากออกแบบฐานข้อมูลแบบ relational database ก็อาจจะได้ตารางดังที่แสดงต่อไปนี้

    ClassroomClassroom

    Data Name Type

    index Integer

    name String

    teacher String

    StudentStudent

    Data Name Type

    room index Integer

    name String

    gender String

    score Double

    เมื่อพิจารณาจากตารางจะเห็นว่า สองตารางนี้มีความเชื่อมโยงกัน กล่าวคือห้องเรียนหรือ Classroom มีนักเรียนได้หลายคน เราจะเรียกความสัมพันธ์ลักษณะนี้ว่าเป็นแบบ One To Many แต่ในทางตรงกันข้ามนักเรียนแต่ละคนไม่สามารถมีรายชื่ออยู่ในห้องเรียนได้มากกว่าหนึ่งห้อง ความสัมพันธ์แบบนี้เรียกว่า One To One ถ้าหากเขียนโปรแกรมใช้การเก็บข้อมูลด้วย SQL ก็จำเป็นต้องมี Database Schema เพ่ืออธิบายความสัมพันธ์ อีกทั้งยังต้องเขียน SQL Query ในการดึงข้อมูลที่ต้องการ แต่การเขียนโปรแกรมด้วย Core Data นั้นจะต่างออกไป เพราะเราจะมองข้อมูลเป็นอ็อบเจ็กไม่ใช่ตาราง และที่ง่ายไปกว่านั้นคือใน XCode มีเครื่องมือช่วยในการออกแบบ Model และความสัมพันธ์ของข้อมูล

    เริ่มต้นด้วยการสร้างโปรเจคขึ้นมาใหม่ และก่อนจะใช้ Core Data ได้ก็ต้องเพ่ิมเฟรมเวิร์คเช่นเดียวกันกับโปรเจคที่ผ่านมา สำหรับ framework ที่ต้องเพ่ิมเข้ามาก็คือ CoreData Framework เมื่อเพ่ิมเข้ามาเรียบร้อยแล้ว จะต่อด้วยการออกแบบ Model ซึ่งการออกแบบ Model นั้นทำได้ด้วยการเพ่ิมไฟล์ Data Model เข้ามาในโปรเจคดังรูป

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • หลังจากตั้งชื่อไฟล์เรียบร้อยแล้ว ก็จะพบกับหน้าต่างที่ใช้ในการออกแบบ Model ที่เรียกว่า Model Editor ให้กดปุ่ม Add Entity ที่อยู่ด้านล่าง ซึ่งเอนทิตีนี้ (Entity) ก็คือชื่อที่ใช้อ้างอิงถึงสิ่งต่างๆ เช่น นักเรียน ห้องเรียน เป็นต้น หลังจากกดเพ่ิมเอนทิตีก็จะมีหน้าตาลักษณะดังนี้

    บริเวณตรงกลางของ Model Editor คือส่วนที่ใช้กำหนด Attributes , Relationships และ Fetched Properties ให้กับ Entity ที่ได้สร้างขึ้น ส่วนทางด้านขวามือคือ Data Model Inpector เป็นส่วนไว้กำหนดคุณสมบัติต่างๆ

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • Entities & Attributeหลังจากเพ่ิมเอนทิตรีแล้วสิ่งที่จะทำในลำดับแรกสุดคือแก้ไขชื่อของเอนทิตีให้เป็น Classroom เสียก่อน โดยสามารถแก้ไขได้ที่ Inspector ด้านขวามือ จากนั้นกดเพ่ิม Attributes ซึ่งแอททริบิ้วนี้คือข้อมูลที่แสดงลักษณะของเอนทิตี้ เช่น เอนทิตี้นักเรียน ก็อาจจะมีชื่อ เพศ และคะแนน เป็นต้น ซึ่งแอททริบิ้วที่จะเพ่ิมเข้าไปในเอนทิตี้นี้จะคล้ายกับตาราง Classroom ที่ได้แสดงไปก่อนหน้านี้ ให้กดเพ่ิมแอททริบิ้วใหม่จำนวน 2 แอททริบิ้วด้วยกัน และกำหนดชื่อของแอททริบิ้วแรกคือ name และกำหนดชนิดของแอททริบิ้วเป็น String ส่วนแอททริบิ้วที่สองกำหนดชื่อให้เป็น teacher และเป็น String เช่นเดียวกัน ภายหลังจากการประกาศเอนทิตีและกำหนดแอททริบิ้วต่างๆเรียบร้อยแล้ว ก็จะสามารถเปลี่ยนโหมดการทำงานของ Model Editor ให้เป็นโหมดรูปภาพ (Graph) ได้ด้วยการกดปุ่ม Editor Style ด้านล่างขวามือ จากนั้นจะเห็นหน้าต่างดังนี้

    การออกแบบ Model ของเรายังไม่เสร็จ เพราะยังเหลืออีกหนึ่ง Entity นั่นก็คือ Student ให้ทำการเพ่ิมเอนทริตรี Student ซึ่งมีแอททริบิ้ว name , gender เป็น string และ score เป็น double หลังจากที่ได้เพิ่มเอนทิตรี Student , Classroom และกำหนดแอททริบิ้วเรียบร้อยแล้ว ก็จะไปสู่ขั้นตอนต่อไป

    Relationshipหากเอนทิตีที่สร้างขึ้นมีเพียงแค่แอททริบิ้ว ก็ไม่ต่างอะไรกับการเก็บข้อมูลด้วยตารางเช่น SQLite ในหัวข้อที่ผ่านมา สิ่งที่จะทำให้ model สมบูรณ์เพ่ิมมากขึ้นก็คือการกำหนด relationship ให้กับ manged object ความสัมพันธ์ของคลาสทั้งสองเมื่อเขียนแผนภาพออกมาก็จะได้ดังนี้

    Classroom Student1 *studentList

    room

    จากรูปมี relationship ด้วยกัน 2 relastionship อันดับแรกนั่นก็คือ studentList ซึ่งเป็นความสัมพันธ์จาก classroom ไปยัง student ความสัมพันธ์นี้เป็นแบบ One To Many ส่วนความสัมพันธ์ที่สอง room ก็จะเป็นความสัมพันธ์ที่ในทางตรงข้ามกันคือ One To One จาก student ไปยัง classroom นั่นเอง

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • ในการประกาศความสัมพันธ์ของอ็อบเจ็กจะต้องกำหนดคุณสมบัติทั้งหมด 5 อย่างด้วยกันคือ

    1. Relationship name ชื่อของความสัมพันธ์2. Destination entity เป้าหมายของความสัมพันธ์ เช่น ความสัมพันธ์ studentList เป็นความสัมพันธ์ที่ Classroom ได้สร้างขึ้นมาโดยมี destination คือ Student 3. Type คือชนิดของความสัมพันธ์ เช่น One To One (เรียกสั้นๆว่า To One) , One To Many ( To Many)4. Inverse relationship ความสัมพันธ์ทางตรงกันข้าม โดยทั่วไปความสัมพันธ์ระหว่างอ็อบเจ็กจะมี inverse relationship เสมอ เช่น studentList ก็จะมี homeRoom เป็นความสัมพันธ์ทางตรงกันข้าม 5. Delete rule ข้อกำหนดในการลบอ็อบเจ็ก ซึ่งจะแบ่งย่อยออกเป็น 4 อย่างด้วยกันคือ

    • nullify เป็นข้อกำหนดที่ง่ายที่สุด คือถ้าลบอ็อบเจ็ก classroom ออกไปอ็อบเจ็ก student ที่เก่ียวเนื่องจะไม่โดนลบไปด้วย ส่วนค่า classroom ที่ใช้ใน student ก็จะถูกปรับเป็น null คิดง่ายๆคือถ้ายุบห้องเรียน นักเรียนจะไม่โดนไล่ออก แล้วก็จะทำการประกาศให้นักเรียนรู้ตัวว่าไม่มีห้องเรียนแล้ว• no action คือไม่จัดการอะไรให้เลย นั่นหมายความว่าถ้าหากยุบห้องเรียน นักเรียนก็จะไม่โดนไล่ออก แต่นักเรียนจะยังเข้าใจว่าตัวเองมีห้องเรียน• cascade คือหากยุบห้องเรียน นักเรียนทั้งหมดก็จะถูกไล่ออกไปด้วย • deny หมายถึงว่า ก่อนจะยุบห้องเรียนได้ ต้องไล่นักเรียนออกให้หมดเสียก่อน

    เมื่อทำความเข้าใจกับคุณสมบัติต่างๆแล้ว ก็ได้เวลาสร้าง relationship แรกกันนั่นก็คือ studentList ในการประกาศ relationship ให้เลือก entity ที่ต้องการจะประกาศเสียก่อน จากนั้นกดปุ่ม Add Relationship ด้านล่างขวา หรือจะกด + ในตาราง relationship ก็ได้เช่นเดียวกัน เมื่อตั้งชื่อ relationship เรียบร้อยแล้วให้กำหนด destination เป็น Student และกำหนดให้ delete rule เป็น cascade นั่นหมายความว่าถ้าลบห้องเรียน นักเรียนในห้องก็จะโดนลบไปด้วย ส่วน Type เป็น To Many ซึ่งการกำหนด delete rule และปรับคุณสมบัติต่างๆทำได้ที่ model inspector ดังรูป

    หลังจากประกาศเสร็จเรียบร้อยแล้ว ถ้าหากเปลี่ยน Model Editor เป็นโหมดรูปภาพก็จะเห็นเส้นเชื่อมระหว่าง Classroom และ Student ลักษณะดังนี้

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • เมื่อดูจากหัวลูกศรก็จะสัญลักษณ์ >> หมายความว่าเป็นแบบ To Many พร้อมกับชี้ไปยัง Student ซึ่งหมายถึง relationship นี้เป็นความสัมพันธ์จาก Classroom ไปยัง Student นั่นเอง ถ้าทุกอย่างถูกต้องแล้วก็ลงมือสร้างความสัมพันธ์ room กันต่อได้เลย ใน relationship ที่สองนี้จะกำหนด destination ให้เป็น Classroom ส่วน Type เป็น To One พร้อมกับกำหนดให้ความสัมพันธ์นี้เป็น invert relationship ของ studentList ด้วย และสุดท้ายกำหนด delete rule ให้เป็น nulltify หมายถึงว่าถ้าลบ Student ออกจากระบบ Classroom ก็จะรู้ว่านักเรียกคนนี้ได้ไม่ได้อยู่ในห้องเรียนแล้ว

    หลังจากกำหนดความสัมพันธ์ครบแล้ว เมื่อดูจากรูปก็จะเห็นว่าเส้นความสัมพันธ์มีลูกศรทั้งสองด้านที่แตกต่างกัน และมาถึงขั้นตอนสุดท้ายนั้นก็คือสร้างคลาสที่จะใช้งานจริงจากโมเดลที่ได้เราได้ออกแบบไว้ ในขั้นตอนนี้ให้เลือกที่เมนู Edit > Create NSMangedObjectSubclass.. จากนั้นจะมีหน้าต่างขึ้นมาให้เลือกว่าว่า ต้องการจะสร้าง subclass จาก Entity ใดบ้าง

    หลังจากกด Next เรียบร้อย จะขึ้นหน้าต่างให้เลือกโฟเดอร์ที่ต้องการจะ Save พร้อมกับมีช่องให้เลือก Use Scalar Property for Primitive Type ซึ่งถ้าหากเลือกตรงนี้ ข้อมูลที่เป็นตัวเลขเช่น Integer จะใช้ตัวแปรแบบ int แทน NSNumber หลังจากเสร็จขั้นตอนนี้จะได้คลาสใหม่ขึ้นมาสองคลาสคือ Classroom และ Student ซึ่งจะมีโค้ดประมาณนี้

    Student.h1 #import 2 #import 34 @class Classroom;56 @interface Student : NSManagedObject78 @property (nonatomic, retain) NSString * gender;9 @property (nonatomic, retain) NSNumber * score;10 @property (nonatomic, retain) NSString * name;

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 11 @property (nonatomic, retain) Classroom *room;1213 @end

    เมื่อพิจารณาคลาส Student ที่โปรแกรมได้สร้างให้จะเห็นว่าคลาสที่ได้มาใหม่นี้เป็น subclass ของ NSMagedObject ส่วน relationship และ attributes ที่ได้กำหนดใน Model นั้น จะถูกสร้างให้เป็นเป็น property ของคลาส

    Classroom.h1 #import 2 #import 34 @class Student;56 @interface Classroom : NSManagedObject78 @property (nonatomic, retain) NSString * name;9 @property (nonatomic, retain) NSString * teacher;10 @property (nonatomic, retain) NSSet *studentList;11 @end1213 @interface Classroom (CoreDataGeneratedAccessors)1415 - (void)addStudentListObject:(Student *)value;16 - (void)removeStudentListObject:(Student *)value;17 - (void)addStudentList:(NSSet *)values;18 - (void)removeStudentList:(NSSet *)values;1920 @end

    ส่วนคลาส Classroom ก็มีพร๊อพเพอร์ตี้ที่เกิดขึ้นจาก relationship และ attributes เช่นกัน แต่ที่พิเศษกว่านั้นคือมีเมธอดที่ใช้เพ่ิมและลบ Student ออกจาก Classroom ให้เสร็จด้วย นั่นเป็นเพราะความสัมพันธ์ studentList เป็นแบบ To Many นั่นเอง

    Put them togatherหลังจากที่ได้ออกแบบโมเดลเสร็จเรียบร้อยแล้ว ต่อไปก็คือการนำคลาสมาใช้ โดยในขั้นตอนแรกต้องสร้าง Core Data Stack ขึ้นมาเสียก่อน และหากกลับไปดูรูปของ Core Data Stack จะเห็นส่วนประกอบด้วยกันทั้งหมด 4 อย่างด้วยกัน data store , coordinator , context และสุดท้าย model ที่เพ่ิงได้สร้างไป ซึ่งเราจะเขียนส่วนประกอบเหล่านี้ในการทำงานหลักของโปรแกรมดังนี้

    main.m1 #import 2 #import 3 #import "Student.h"4 #import "Classroom.h"56 int main(int argc, const char * argv[])7 {89 @autoreleasepool {10 11 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model"12 withExtension:@"momd"];13 14 if( modelURL == nil)15 NSLog(@"Fail url");16 17 // Model18 NSManagedObjectModel *model = nil;19 model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];20 21 // Coordinator

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 22 NSPersistentStoreCoordinator *psc = nil;23 psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

    การสร้าง coordinator จำเป็นต้องมีโมเดลเพ่ือให้สร้างและจัดการกับ managed object ได้อย่างถูกต้อง ดังนั้นจึงต้องอ่านโมเดลจากไฟล์ที่กำหนด แม้ว่าตัวโมเดลจะได้ออกแบบในไฟล์ .xcadatamodel แต่เราไม่อาจจะอ่านไฟล์นี้ได้โดยตรง แต่จะใช้ไฟล์ .momd ซึ่งเป็นไฟล์โมเดลที่ผ่านการคอมไพล์มาแล้ว

    26 NSURL *storeURL = [NSURL fileURLWithPath:@"student.sqlite"];27 NSError *error = nil;28 NSPersistentStore *store = nil;29 30 // Store31 store = [psc addPersistentStoreWithType:NSSQLiteStoreType32 configuration:nil33 URL:storeURL34 options:nil35 error:&error];3637 NSManagedObjectContext* context;38 NSManagedObjectContextConcurrencyType ccType = NSMainQueueConcurrencyType;3940 // Context41 context = [[NSManagedObjectContext alloc] initWithConcurrencyType:ccType];42 [context setPersistentStoreCoordinator:psc];

    ต่อมาคือการระบุ store ที่ต้องทำงานร่วมกับ cooridnator เมื่อดูจากพารามิเตอร์ที่ใช้ จะเห็นว่าได้กำหนดให้ store เก็บข้อมูลด้วย SQLite ซึ่งในขั้นตอนนี้ไม่ต้องสร้างไฟล์ sqlite และออกแบบฐานข้อมูล เพียงแค่กำหนดที่อยู่ของไฟล์ที่ต้องการจัดเก็บเท่านั้น ขั้นตอนสุดท้ายคือการสร้าง context เพ่ือใช้งาน หลังจากจบขึ้นตอนนี้ โปรแกรมก็มีส่วนประกอบของ Core Data Stack ครบเรียบร้อยพร้อมใช้งาน

    Create Managed Objectสิ่งที่จะต้องทำต่อไปคือสร้าง managed object ขึ้นมาทดสอบการใช้งาน วิธีการเขียนนั้นจะใช้คลาส NSEntityDescription เพ่ือระบุเอนทิตีที่จะใช้ในการสร้างอ็อบเจ็ก เช่นตัวอย่างโค้ดต่อไปนี้

    NSEntityDescription *studentEntity = [[model entitiesByName] objectForKey:@"Student"];

    Student* mai = (Student*)[[NSManagedObject alloc] initWithEntity:studentEntity insertIntoManagedObjectContext:context];

    ตัวแปร studentEntity เป็นตัวกำหนดว่าจะใช้เอนทิตีชื่อ Student จากโมเดลที่ได้ออกแบบไว้ หลังจากนั้นจะส่งต่อให้กับคลาส NSManageObject เพ่ือสร้างอ็อบเจ็กจากเอนทิตีที่กำหนด พร้อมกับกำหนด context ที่ใช้สำหรับจัดการอ็อบเจ็กนี้

    44 // Create managed objects45 NSEntityDescription *bioEntry = [[model entitiesByName] objectForKey:@"Classroom"];46 Classroom* bio = (Classroom*)[[NSManagedObject alloc]47 initWithEntity:bioEntry48 insertIntoManagedObjectContext:context];49 50 Student* mario = [NSEntityDescription insertNewObjectForEntityForName:@"Student"51 inManagedObjectContext:context];5253 Student* mai = [NSEntityDescription insertNewObjectForEntityForName:@"Student"54 inManagedObjectContext:context];55 56 mario.name = @"Mario Maurer";57 mario.gender = @"M";58 mario.score = @67;

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 59 60 mai.name = @"Mai Davika";61 mai.gender = @"F";62 mai.score = @82;63 64 65 bio.name = @"Biology Class";66 [bio addStudentListObject:mai];67 [bio addStudentListObject:mario];68 69 // Store objects70 [context save:&error];

    จากโค้ดได้สร้างอ็อบเจ็ก bio ด้วยวิธีการที่ได้อธิบายไป ในขณะที่อ็อบเจ็ก mario และ mai ใช้คลาสเมธอดที่ชื่อ insertNewObjectForEnityForName: inManagedObjectContext: ไม่ว่าจะวิธีใดก็ทำงานได้เช่นกัน ในช่วงท้ายของโค้ดจะเห็นว่าสามารถใช้เมธอด addStudentListObject เพ่ือเพ่ิม Student เข้าไปใน Classroom ได้ทันที โดยที่ไม่ต้องเขียนโค้ดใดๆเลย เมื่อมีอ็อบเจ็กต่างๆแล้ว การกำหนดค่าให้กับอ็อบเจ็ก รวมไปถึงการลบหรือเพ่ิมอ็อบเจ็กใหม่จะอยู่ในหน่วยความจำเท่านั้น เพราะ context ยังไม่ได้จัดเก็บลงฐานข้อมูลจริงๆ และเมื่อต้องการจะเก็บข้อมูลลงในดิสก็ให้เรียกเมธอด save

    Fetch Managed Objectหลังจาก context เรียกเมธอด save ข้อมูลทุกอย่างจะถูกจัดเก็บลงฐานข้อมูล ต่อไปก็จะทดสอบเรียกข้อมูลที่ได้จัดเก็บไป ซึ่งจะใช้คลาส NSFetchRequest เพ่ือกำหนดข้อมูลที่ต้องการ

    72 // Fetch objects73 NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Classroom"];74 NSArray *array = [context executeFetchRequest:request error:&error];75 76 77 for (Classroom* room in array)78 {79 NSArray* studentList = [room.studentList allObjects];80 for (Student* student in studentList)81 {82 NSLog(@"%@ (%@) %@" ,student.name ,student.gender,student.score);83 }84 }

    จากโค้ดได้กำหนดให้ Classroom เป็นเอนทิตีที่ต้องการจะขอข้อมูล จากนั้นสั่งให้ context เรียกข้อมูลตามที่กำหนด ดังนั้นผลลัพธ์ของโปรแกรมก็จะได้ดังนี้

    Program 17.2 Output

    Mario Maurer (M) 67Mai Davika (F) 82

    กลับไปพิจารณาโค้ดอีกครั้ง จะเห็นว่าถึงแม้ว่าจะกำหนดให้ขอข้อมูลจากเอนทิตรี Classroom แต่โปรแกรมสามารถแสดงข้อมูลนักเรียนทุกคนที่อยู่ใน bio ได้ทั้งหมด โดยอาศัยพร๊อพเพอร์ตี้ studentList ซึ่งเป็น relationship ของ Classroom และในทางกลับกัน Student ก็สามารถที่จะเข้าถึง Classroom ผ่านทาง room ได้เช่นเดียวกัน

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • School Project V.2

    โปรแกรมที่ได้เขียนไปก็ทำงานได้ถูกต้อง แต่ถ้าหากเพ่ิมเมธอดเข้าไปในคลาส Managed Object ที่ XCode สร้างให้เช่น เพ่ิมเมธอด countStudent เข้าไปในคลาส Classroom เพ่ือนับจำนวนนักเรียน แต่หลังจากที่เขียนโปรแกรมไปสักพัก ก็พบว่าต้องการจะแก้ไข model เช่น เพ่ิมชื่อของอาจารย์ประจำชั้น ผลที่ตามมาจากการเปลี่ยนโมเดล คือคลาสที่เคยใช้ก็จะใช้ไม่ได้เพราะไม่เข้ากับโมเดลใหม่ที่สร้างขึ้น ดังนั้นจึงต้องให้ XCode สร้างคลาสให้ใหม่ การให้ XCode สร้างคลาสให้ใหม่นั้นไม่ใช่ปัญหา แต่สิ่งที่เป็นปัญหาคือเมธอดที่ได้เขียนเพ่ิมเข้าไปก็จะหายไปด้วย เนื่องจากเราได้เขียนเมธอดการทำงานต่างๆไว้ในคลาสตัวเก่า เมื่อสร้างคลาสใหม่ XCode จะสร้างไฟล์ใหม่ทับของเดิม โค้ดที่ได้เขียนไปจึงหายไปด้วย การแก้ไขปัญหานี้อาจจะทำได้ด้วยการคัดลอกโค้ดไว้ก่อน แล้วนำไปวางในคลาสใหม่ แต่ถ้าหากเกิดแก้ไขพร้อมกันสัก 5 คลาส การใช้วิธี copy & paste ย่อมไม่ใช่หนทางที่ดีแน่นอน การแก้ปัญหาที่ดีกว่านั้นก็คือใช้แคทิกกอรี่ ดังเช่นตัวอย่างต่อไปนี้

    Student+DataManagement.h1 #import "Student.h"23 @interface Student (DataManagement)4 +(Student*) studentName:(NSString*)name5 score:(NSNumber*)score6 inContext:(NSManagedObjectContext*) context;78 -(NSString*) grade;9 @end

    Student+DataManagement.m1 #import "Student+DataManagement.h"23 @implementation Student (DataManagement)4 +(Student*) studentName:(NSString*)name5 gender:(NSString*)gen6 score:(NSNumber*)score7 inContext:(NSManagedObjectContext*) context;89 {10 Student* student = nil;11 student = [NSEntityDescription insertNewObjectForEntityForName:@"Student"12 inManagedObjectContext:context];13 student.name = name;14 student.score = score;15 student.gender = gen;16 17 return student;18 }1920 -(NSString*) grade21 {22 int score = [self.score intValue];23 24 if( score >= 80) return @"A";25 else if(score >= 70 && score < 80) return @"B";26 else if(score >= 60 && score < 70) return @"C";27 else if(score >= 50 && score < 60) return @"D";28 else return @"F";29 }3031 @end

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • จากโค้ดของแคทิกกอรี่ DataMagement ของ Student มีเมธอดทั้งหมด 2 เมธอดด้วยกัน โดยเมธอดแรกนี้เป็นเมธอดที่ใช้คำนวนเกรดของนักเรียน ส่วนเมธอดที่สองเป็นคลาสเมธอด เพ่ือช่วยให้สร้างอ็อบเจ็กได้ง่ายขึ้น

    Classroom+DataManagement.h1 @interface Classroom (DataManagement)2 +(Classroom*) classRoomName:(NSString*)name3 inContext:(NSManagedObjectContext*) context;4 @end

    Classroom+DataManagement.m1 #import "Classroom+DataManagement.h"23 @implementation Classroom (DataManagement)4 +(Classroom*) classRoomName:(NSString*)name5 inContext:(NSManagedObjectContext*) context6 {7 Classroom* room = nil;8 room = [NSEntityDescription insertNewObjectForEntityForName:@"Classroom"9 inManagedObjectContext:context];10 room.name = name;11 return room;12 }1314 @end

    สำหรับคลาส Classroom ก็เพ่ิมแคทิกอรี่เพ่ือใช้สร้างอ็อบเจ็กเช่นเดียวกันกับคลาส Student และนอกจากการเพ่ิมแคทิกกอรี่แล้ว เราจะเพ่ิมคลาส DataManagement เพ่ืออำนวยความสะดวกในการสร้าง Core Data Stack มากขึ้น

    DataManagement.h

    1 #import 2 #import 3 #import "Classroom+DataManagement.h"4 #import "Student+DataManagement.h"56 @interface DataManagement : NSObject78 @property (strong) NSManagedObjectContext* context;910 -(id) initWithSQLFile:(NSString*) file;11 -(void) saveChange;1213 @end

    DataManagement.m

    1 #import "DataManagement.h"23 @implementation DataManagement4 -(id) initWithSQLFile:(NSString*) file5 {6 self = [super init];7 if( self != nil)8 {9 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model"10 withExtension:@"momd"];11 12 NSManagedObjectModel *model = nil;13 model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];14 15 NSPersistentStoreCoordinator *psc = nil;16 psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];17

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 18 NSURL *storeURL = [NSURL fileURLWithPath:file];19 NSError *error = nil;20 21 [psc addPersistentStoreWithType:NSSQLiteStoreType22 configuration:nil23 URL:storeURL24 options:nil25 error:&error];26 27 NSManagedObjectContextConcurrencyType ccType = NSMainQueueConcurrencyType;28 29 // Context30 _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:ccType];31 [_context setPersistentStoreCoordinator:psc];3233 }34 return self;35 }36 -(void) saveChange37 {38 NSError* error;39 [self.context save:&error];40 if( error != nil)41 NSLog(@"%@",error);42 }4344 @end

    เราได้ย้ายโค้ดการสร้าง store , coordinator , context และอ่านโมเดล มาไว้ในคลาส DataManagement เพ่ือให้จัดการเตรียมทุกอย่างที่จำเป็นต่อการใช้ Core Data และในส่วนของโปรแกรมหลักมีโค้ดดังนี้

    main.m

    1 #import 2 #import "DataManagement.h"34 int main(int argc, const char * argv[])5 {67 @autoreleasepool {8 9 DataManagement* db = [[DataManagement alloc] initWithSQLFile:@"student.sqlite"];10 Student* mario = [Student studentName:@"Mario Maurer" gender:@"M"11 score:@67 inContext:db.context];12 Student* mai = [Student studentName:@"Mai Davika" gender:@"F"13 score:@75 inContext:db.context];14 Student* nadech = [Student studentName:@"Nadech Kugimiya" gender:@"M"15 score:@69 inContext:db.context];16 Student* yaya = [Student studentName:@"Yaya Urassaya" gender:@"F"17 score:@87 inContext:db.context];18 19 Classroom* bio = [Classroom classRoomName:@"Biology" inContext:db.context];20 Classroom* phy = [Classroom classRoomName:@"Physic" inContext:db.context];21 22 [bio addStudentListObject:mario];23 [bio addStudentListObject:mai];24 25 [phy addStudentListObject:nadech];26 [phy addStudentListObject:yaya];27 28 [db saveChange];29 30 31 NSError* error;32 NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Classroom"];33 34 NSArray *array = [db.context executeFetchRequest:request error:&error];35

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • 36 for (Classroom* room in array)37 {38 NSArray* studentList = [room.studentList allObjects];39 for (Student* student in studentList)40 {41 NSLog(@"%@ - %@ : %@" ,room.name , student.name, [student grade]);42 }43 }4445 }46 return 0;47 }

    โปรแกรมที่ได้เขียนไปมีนักเรียนทั้งหมด 4 คน และมีห้องเรียนจำนวนสองห้อง เมื่อให้โปรแกรมทำงานก็จะได้ผลลัพธ์ดังนี้

    Program 17.3 Output

    Physic - Yaya Urassaya : APhysic - Nadech Kugimiya : CBiology - Mai Davika : BBiology - Mario Maurer : C

    เมื่อเปรียบเทียบโปรแกรมที่ได้ปรับปรุงไป ก็จะเห็นได้ว่าโปรแกรมเข้าใจได้ง่ายกว่า และถ้้าหากได้แก้ไข Model ก็จะไม่กระทบกับโค้ดที่ได้เขียนเพ่ิมเข้าไป

    Predicateในการใช้งานฐานข้อมูลเช่น SQLite หากต้องการแสดงข้อมูลตามเงื่อนที่ต้องการ เช่น กำหนดว่าแสดงข้อมูลเฉพาะนักเรียนที่ชื่อ Mario ก็จะอาจจะเขียน query แบบนี้

    SELECT * FROM STUDENT WHERE name = “Mario”

    แม้ว่า Core Data สามารถที่จะกำหนดให้จัดเก็บข้อมูลแบบ SQLite ได้ แต่ก็ไม่อาจจะใช้ SQL Query เพ่ือกำหนดเงื่อนไขข้อมูลได้ อย่างไรก็ตามการใช้งาน Core Data ก็มีการกำหนดเงื่อนไขเพ่ือแสดงข้อมูลได้แบบเดียวกันซึ่งเรียกว่า predicate การใช้ predicate นี้ไม่จำกัดเพียงแค่ Core Data เท่านั้น แต่ยังสามารถที่จะใช้กับ NSArray , NSSet เพ่ือใช้เป็นตัวกรองข้อขูลที่ต้องการได้เช่นกัน การกำหนด predicate จะใช้คลาส NSPredicate พร้อมกับกำหนดเงื่อนไขที่ต้องการ เช่น ถ้าต้องการกำหนดเงื่อนไขแบบเดียวกันกับ SQL Query ที่ได้แสดงไปก็จะเขียนได้ดังนี้

    NSPredicate *marioPredicate = [NSPredicate predicateWithFormat:@"name = 'Mario'"];

    หนังสือเล่มนี้ไม่อาจจะครอบคลุมการใช้งาน predicate ได้ทั้งหมด จึงขอยกตัวอย่างการใช้งานบางส่วนเพ่ือให้ผู้อ่านได้เข้าใจหลักการเบื้องต้นของการใช้งาน และสามารถไปศึกษาต่อเพ่ิมเติมได้ด้วยตัวเอง

    Basic Operation เงื่อนไขเปรียบเทียบ มากกว่า น้อยกว่า เท่ากัน สามารถใช้ opertator ปกติได้ เช่น > , < , != รวมไปถึงการใช้ && , || หรืออาจจะเขียน AND , OR , NOT ได้เช่นเดียวกัน นอกจากนี้จะมีเงื่อนไข BETWEEN เพ่ือใช้กำหนดขอบเขตของค่าที่ต้องการ เช่น BETWEEN { 5 , 20} เป็นต้น

    NSPredicate *goodStudent = [NSPredicate predicateWithFormat:@"score >= 80"];

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • String Comparisonการเปรียบเทียบสตริงใน predicate มีเงื่อนไขดังนี้ BEGINEWITH , CONTAINS , ENDSWITH , LIKE และ MATCHES เช่น

    NSPredicate *maPredicate = [NSPredicate predicateWithFormat:@"name contains 'Ma'"];

    และยังสามารถกำหนด case insensitive โดยการใช้ [c] ได้ เช่น

    NSPredicate *maPredicate = [NSPredicate predicateWithFormat:@"name contains[c] 'ma'"];

    Aggregate Operationการกำหนดจำนวนมี operator ให้ใช้งานดังนี้ ANY, SOME, ALL , NONE , IN เช่น

    NSPredicate *goodStudent = [NSPredicate predicateWithFormat:@"ALL score >= 80"];

    Array Operationหากต้องการกำหนดเงื่อนไขของอาร์เรย์ สามารถใช้ array[index] โดยระบุตำแหน่งของอาร์เรย์ หรือ array[FIRST] เพ่ือกำหนดเฉพาะตัวแรก ส่วนการกำหนดสมาชิกตัวสุดท้ายใช้ array[LAST] และสุดท้ายกำหนดขนาดของอาร์เรย์ได้ด้วย array[SIZE]

    เพ่ือความเข้าใจการใช้งาน predicate มากขึ้น จะปรับโปรแกรมที่ได้เขียนไปสักเล็กน้อย โดยเพ่ิมเงื่อนไขใน NSFetchRequest ให้เลือกข้อมูลเฉพาะนักเรียนที่คะแนนมากกว่าหรือเท่ากับ 70 ขึ้นมาแสดง

    NSPredicate *scorePre = [NSPredicate predicateWithFormat:@"score >= 70"];NSError* error;NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];[request setPredicate:scorePre]; NSArray *studentList = [db.context executeFetchRequest:request error:&error];for (Student* student in studentList){ NSLog(@"%@ : %@ (%@)" , student.name, student.score, [student grade]);}

    Program Output

    Yaya Urassaya : 87 (A)Mai Davika : 75 (B)

    ลองเขียน predicate ที่มีความซับซ้อนมากกว่านี้ เช่น เลือกนักเรียนเฉพาะห้องเรียน Biology ที่ชื่อมีตัวอักษร k หรือ K ก็จะเขียน predicate ได้ดังนี้

    NSPredicate *scorePre = [NSPredicate predicateWithFormat:@"name CONTAINS[c] %@ " "AND homeRoom.name LIKE %@ ",@"k",@"Biology"];NSError* error;NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];[request setPredicate:scorePre];

    NSArray *studentList = [db.context executeFetchRequest:request error:&error];for (Student* student in studentList){ NSLog(@"%@ : %@ (%@)" , student.name, student.score, [student grade]);}

    พิจารณาจาก predicate จะเห็นว่า ในขั้นแรกได้กำหนดข้อบังคับให้กับพร๊อพเพอร์ตี้ name ที่ต้องประกอบด้วย k จากนั้นก็เพ่ิมข้อบังคับให้กับ homeRoom ซึ่งเป็น relationship ให้มีค่าเท่ากับ Biology ดังนั้นผลลัพธ์ที่ได้ก็จะเป็นดังนี้

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/

  • Program Output

    Yaya Urassaya : 87 (A)Mai Davika : 75 (B)

    Summary

    บทนี้ได้แนะนำการใช้ฐานข้อมูลด้วย C API ของ SQite ที่สามารถใช้ได้ทั้ง iOS และ Mac OS X แต่อย่างไรก็ตามในปัจจุบันมีไลบราลี่จากภายนอกที่อำนวยความสะดวกให้ใช้มากมาย แนะนำผู้อ่านควรศึกษาเพ่ิมเติม เข่น FMDB นอกเหนือจาก SQLite แล้วในบทนี้ยังได้เรียนรู้การใช้งาน Core Data ซึ่งเป็นเฟรมเวิร์คในการจัดการข้อมูลที่มีประสิทธิภาพสูงมาก การเลือกใช้ Core Data และ SQLite นั้นมีข้อดีและข้อเสียต่างกันไป เช่น หากต้องการจะเขียนโปรแกรมให้รองรับแพลตฟอร์มอื่นๆเช่น Android การใช้ SQLite นั้นดูจะเหมาะสมกว่า เพราะสามารถใช้ไฟล์ฐานข้อมูลเดียวกัน รวมถึงใช้ Query ร่วมกันได้ แต่ถ้าโปรแกรมทำงานเฉพาะ iOS อย่างเดียว การใช้ Core Data อาจจะเหมาะสมกว่าเพราะไม่ต้องไปเขียน SQL Query อีกทั้งมีเครื่องมือในการออกแบบข้อมูลที่ง่ายมาก และยังสามารถจัดเก็บข้อมูลในแบบอ็อบเจ็กได้เลยทันที ในขณะที่ SQLite ไม่สามารถทำได้ ข้อดีอีกอย่างของการใช้ Core Data คือรองรับ iCloud บริการการเก็บข้อมูลบนกลุ่มเมฆ ดัังนั้นการเลือกใช้วิธีการเก็บข้อมูลต้องพิจารณาตามความเหมาะสมของ Application ดังนั้นการเลือกใช้ก็ต้องพิจารณาตามความเหมาะสม

    Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 17 Version 2

    http://www.macfeteria.com/http://www.macfeteria.com/