2011年6月25日 星期六

初探ARC - Automatic Reference Counting

WWDC 2011的影片開始下載
今天颱風天
那就抓個大家之前有討論的ARC來研究一下
下面是我的小小心得 :D

我想寫過iOS的人
絕對最痛很的就是令人髮指的Memory Management
就連TBBT的leonard也為此煩惱

source:
http://digdog.tumblr.com/post/3628378498/actually-sheldon-has-better-design-if-youre

程式中充斥著retain, release, autorelease
相信即使是老手,也難免會恍神而不小心少release而產生memory leakage
或是多release而產生bad access直接當掉
在這個大家都用garbage collection的時代
這種memory management的方式實在讓人寫得很痛苦

當然目前objective-c的做法也不能說完全沒有好處
畢竟gc的動作是無法預測什麼時候會去執行的
也許剛好在你正在忙的時候給你gc一下就卡了
這也是為什麼iOS跑起來可以比別家順的其中一個原因。
而在iOS5首次提出了ARC(Automatic Reference Counting)這個解決方案
試圖讓開發者減少寫Reference Counting的時候產生的錯誤。

ARC有哪些重點?
1. 它是Compile-time的技術,並不是runtime有一個thread去幫你處理reference count..
2. 開發者不能再直接使用retain, release, autorelease,改由compiler幫你塞這些code
3. 所有的property不再使用retain, assign而改用strong, weak取代
4. NSAutoreleasePool也要改用特有的 @autoreleasepool{ } 來取代
沒了!!

好啦,其實當然不只這樣,但是先從簡單的來
先看下面的code

-(NSString*)helloString
{
return [[NSString alloc] initWithFormat:@"Hello %@", _name];
}

以現在的MRC(Manual Reference Counting)環境
這種寫法有潛在的memory leak的風險
因為根據命名規則,非new, copy, mutableCopy開頭的message回傳的物件不能是retained object
而是要retained and autoreleased object
所以可能要改成這樣的寫法

-(NSString*)helloString
{
return [NSString stringWithFormat:@"Hello %@", _name];
}

但是如果使用了ARC之後
前後兩個方法都可以
主要是因為compiler會分析你的程式碼中呼叫的message是否有含alloc, new, copy, mutableCopy這些開頭
如果有的話就知道回傳的object的ownership是你的
接著根據你所在的message是否有alloc, new,... 來決定你回傳的物件是哪種類型。
以上面message為例子,回傳的應該是一個retained and autoreleased object
以往MRC中,我們就要跟leonard一樣,好好的計算一下才不會錯
但是在ARC這些複雜的加減法都交給compiler來幫我們搞定,完全不需要知道實作細節。

另外memory mangagement還有一個重點是在member variable
以前我們定義在class中的member variable並沒有留下是否要retain物件的資訊
除非我們透過@property (retain)或是@property (assign)的方法才可以知道個別的ownership語意。
但是在ARC中,因為所有retain/release都是要自動的
因此所有的Member variable就定義為預設都是strong type,這有點類似@property (retain)的味道
除非我們在member variable前加上__weak這個描述才會是weak type,這就有點類似@property (assign)
所以在程式碼中,若物件被指到strong pointer,就會自動retain
weak pointer則不會retain..

另外weak reference還有一個非常吸引我的功能
就是所謂的zeroing weak reference.
這個不得不歡呼一下
以前我們在用delegate pattern的時候
文件都教我們使用@property (assign)來hold delegate object
目的就是為了避免circular reference所造成的memory leakage
但是常常會遇到delegate所指到的object已經因為retain count歸零而被清掉了
若我們還去使用它 系統馬上就當給你看
所以以前我們都必須要記得把delegate設成nil
在ARC當中,如果weak pointer所指到的object的retain count已經是0的時候
則這個weak pointer也會被設成0
這就是所謂的"zeroing weak reference"
所以在weak pointer中又比以前的@property (assign)更為安全

從我最新抓的xcode4.2 with iOS5的環境來看
預設已經是把ARC變成預設了
而舊的project可以透過refactor的方式Convert to Objective-C ARC
究竟麻不麻煩,這我還不敢發表評論 XD
但是我相信長痛不如短痛
ARC絕對是對objective-c的記憶體管理有非常大的幫助。
讓我們準備擁抱ios5的ARC時代吧

References
[1] http://clang.llvm.org/docs/AutomaticReferenceCounting.html

2011年6月23日 星期四

在ios使用sqlite的救贖 - FMDB

最近有個專案需要小型資料庫,當然首選一定是iOS內建的sqlite,但是因為他是C的lib,使用上是出了名的困難。之前就有聽說用sqlite可以使用FMDB這個objective-c wrapper library。果不其然的簡單易用,我馬上把我使用經驗跟大家分享,如果你想馬上體驗,也可以去抓我的Sample project - MySqlite

安裝FMDB
方法很簡單,就跟所有的open source library一樣的三步驟。
1. 首先你要先把 libsqlite3.dylib 加進library
2. 在需要用到FMDB的檔案中#import "FMDatabase.h"
3. 把FMDB的.h跟.m加進你的project中
接著就可以開心的使用FMDB

產生Database
基本上就是透過+[FMDatabase databaseWithPath]取產生Database instance。

NSURL *appUrl = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSString *dbPath = [[appUrl path] stringByAppendingPathComponent:@"MyDatabase.db"];
FMDatabase* db = [FMDatabase databaseWithPath:dbPath];

if (![db open]) {
NSLog(@"Could not open db");
}


產生所需table
其實寫過sql的應該對接下來的動作都不陌生,都一樣就是sql語法囉,詳細的請參考sqlite文件
下面的動作是產生一個user的table,並且有uid, name, descritpion三個column。我們使用IF NOT EXISTS來保證table不存在才會產生table。

if(![db executeUpdate:@"CREATE TABLE IF NOT EXISTS user (uid integer primary key asc autoincrement, name text, description text)"])
{
NSLog(@"Could not create table: %@", [db lastErrorMessage]);
}


新增record
在FMDB中,它有提供很貼心類似-[NSString stringWithFormat:...]的功能,但是比較不一樣的是如果你是字串,它會幫你加上括號,使用上更為便利。

if(![db executeUpdate:@"INSERT INTO user (name, description) VALUES (?,?)", name, description])
{
NSLog(@"Could not insert data: %@", [db lastErrorMessage]);
}


刪除record
一樣是下sql語法,這邊比較需要注意的是如果你要傳進去的是int, double這種基本形態,記得要轉換成物件,也就是用+[NSNumber numberWithInt]去轉換

if(![_db executeUpdate:@"DELETE FROM user WHERE uid = ?", [NSNumber numberWithInt:uid]])
{
NSLog(@"Could not delete data: %@", [db lastErrorMessage]);
}


查詢資料
在FMDB中有提供另外一個class FMResultSet來存放查詢出來的records,透過-[FMResultSet next]來iterate出所有的record

NSMutableArray* items = [NSMutableArray arrayWithCapacity:0];
FMResultSet *rs = [db executeQuery:@"SELECT uid, name, description from user"];

while ([rs next]) {
int uid = [rs intForColumn:@"uid"];
NSString *name = [rs stringForColumn:@"name"];
NSString *description = [rs stringForColumn:@"description"];

[items addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:uid], @"uid",
name, @"name",
description, @"description",
nil]];
}

[rs close];


基本上在其他oo平台中有用過sql的,對於FMDB的設計應該並不陌生,可以說完全不會有太多的困難。而相較於用傳統的plist或是archive的方法,我相信不管是效能上或是功能上,Sqlite+FMDB的組合絕對會更有優勢。對於中型以上的iOS程式應該會是標準配備。