2011年7月22日 星期五

ios delegate你必須知道的事情

當你開始寫iOS程式不久,應該開始面對到很多的delegate,
不管是用別人的library或是自己寫library,可能都逃不了delegate。
為了怕有些人不知道什麼是delegate,在這邊簡單的介紹一下,
delegate中文叫做委託,通常會用在class內部把一些事件處理"委託"給別人去完成。
舉個例子,XML Parser可能他知道怎麼parse xml,但是parse到的東西要怎麼處理xml parser可能不知道。
所以NSXMLParser就提供了一個NSXMLParserDelegate給client去實作,
當parse到某個element的時候,就callback delegate所定義的message,
讓他client自己去決定怎麼去處理這個element。
好吧,我承認我解釋的很模糊,不過我這篇本來就不是要你搞懂什麼是delegate,
而是針對使用或是設計delegate的時候,可能會要注意的事情。

在我們的class中設計delegate的時候,我們通常會有幾個注意事項。
假設我的class叫做MyClass,那我們可能會有定義一個MyClassDelegate這個protocol當作我的delegate protocol。
而MyClass中我們可能是這樣寫。
@protocol MyClassDelegate <NSObject>
- (void) myClassOnSomeEvent:(MyClass*)myClass;
@end

@interface MyClass
{
    id<MyClassDelegate> _delegate;
}
@property (nonatomic, assign) delegate;
@end
上面的code我們注意到delegate此property是定義為@property (assign)。
為什麼我們不用retain而要用assign呢?
原因就是在於iOS的reference counting的環境中,我們必須解決circular count的問題。
讓我們來寫寫我們平常都怎麼用delegate的,下面的code我想大家應該不陌生
- (void)someAction
{
   myClass = [MyClass new];
   myClass.delegate = self;
   ....
}
這邊很快的就出現circular reference了
假設上面的code是寫在一個myViewController的物件當中,
之後一旦myViewController的reference count變成1的時候,
myViewController跟myClass這兩個兄弟兩只剩下互相retain,那就變成了孤島,也就因此造成了memory leak!!!


也因為這樣,iOS官方文件才會要建議我們所以的delegate都要用assign property。
也就是所謂"weak reference"的property,他的特色就是雖然會持有對方的reference,但是不會增加retain count。
如此下來,當myViewController的retain count變成0,則會dealloc。
同時在dealloc中,也一併把myClass release,則myClass也跟著被release。
- (void)dealloc
{
   [myClass release];
   [super dealloc];
}


事情就結束了嗎? 還沒有唷...
這邊還有一個大家常常忘記的重點,那就是上面的dealloc這樣寫會有潛在危險。
應該要改成這樣
- (void)dealloc
{
   myClass.delegate = nil;
   [myClass release];
   [super dealloc];
}
你可能會很納悶,myClass不是馬上就會被release了嗎? 幹嘛要先把他的delegate設成nil?
那是因為我們假設myClass會馬上會被dealloc,但是現實狀況這個是不一定的,
有可能裡面內部有建個NSURLConnection,或是正在做某件事情而讓其他物件也retain myClass。
如果myClass沒有馬上dealloc,那他的myClass.delegate不就正指向一個不合法的位置了嗎? (此種pointer稱作dangling pointer)


解決方法是在MyViewController的dealloc中,在release myClass之前,
要先把原本指向自己的delegate改設成nil,這樣才可以避免crash發生。
在我之前寫的project,很大一部份的crash都是這樣造成的,因為這個問題通常不是每次都發生,
但是發生的時候確很難在重新複製,所以不可不慎啊。


但是很興奮的是到了iOS5中的Automatic Reference Counting這個問題可以有所改善。
在ARC中提出了一個新的weak reference的概念來取代原本的assign,
weak reference指到的物件若是已經因retain count歸零而dealloc了,則此weak reference也自動設成nil。
而原本舊的這種assign的作法,在ARC中叫做__unsafe_unretained,這只是為了相容iOS4以下的版本。

回顧重點:
如果你是寫library給別人用的,記得把你的delegate設成assign property,這樣才不會造成circular reference
當你是要始用別人的library,記得在你自己dealloc的時候,把delegate設成nil,以避免crash的事情發生。

References
[1] Ownership of Delegates, Observers, and Targets

2011年7月12日 星期二

Flurry - Mobile Application Analytics


原本想說要一個禮拜寫一篇文章的
想不到才一開始就懶散了
趁今天晚上下雨沒有辦法踢足球的晚上
再來趕功課

今天聽到客戶說要分析我們的ipad app上使用者的行為
希望可以知道使用者點了哪些item 還有做了哪些事情
希望進一步份析使用者的使用狀況
相信有寫web的應該二話不說就直接想到Google Analytics
但是到了mobile領域 還真不知道使用什麼東西勒
經過學長的指點 說有一個service叫做Flurry好像就是在做這樣的服務
用了一下還真的挺簡單易用了
在此就跟大家報告一下

要分析使用者的行為我的需求如下
1. 可以自訂我想監控的行為
2. 很簡單的可以插入我的code
3. 最好可以在離線的時候也可以統計使用者行為
4. server端有類似Google Analytics的報表介面
5. 有API可以抓報表資料整合外部的系統

想不到Flurry是五個願望一次滿足,
而且我只用了一個小時的時間就知道怎麼用了
完全沒有花到太多的功夫。
以下是使用方法及功能介紹

1. 註冊帳號
首先連到它們的官方網站 http://www.flurry.com/
註冊一個帳號 並且新增加一個application 並選擇你的平台 (我當然是選iPad, iPhone, iPod..)
之後就可以根據你選擇的平台Download SDK,而它也會給你這個app的application key
我們之後就必須用這個key來跟Flurry互動

2. 整合進你的project
a. 解開sdk,裡面主要有兩個檔案需要加到我們的project
FlurryLib/FlurryAPI.h
FlurryLib/libFlurry.a

b. 在你的AppDelegate的進入點,塞進初始化function

#import "FlurryAPI.h"

- (void)applicationDidFinishLaunching:(UIApplication *)application {
[FlurryAPI startSession:@"YOUR_API_KEY"];
}



3. 追蹤使用者行為
如果你想監控使用者是否按下一個button,你可以在對應的action加上下面這段code
[FlurryAPI logEvent:@"EVENT_NAME"];
這個event name可以自己定義,每個event name會變成server端的一個獨立數據


如果想用類似網站的pageview概念
可以使用
+[FlurryAPI logAllPageViews:]
+[FlurryAPI logPageView]
前者可以帶一個參數是UINavigationController或是UITabBarController,在每次切換的動作發生時,自動增加一次page view。
後者是手動的增加一次page view

4. 上傳追蹤資料。
上傳的動作是完全自動的。
Flurry會在app開啓或是關閉的時候自動的把這些追蹤資料整包的丟給server,
我們完全不用寫任何一段code。
另外因為他的做法本來就是先把這些使用者的行為放在local,
所以即使離線也可以紀錄,
直到下次使用者打開或是關閉app的時後是有連上網的時候在一併上傳

5. 報表系統
server端有完整的圖形化介面來統計數據
有Dashboard的整體分析
還有Usage, Audience, Events, Technical的細項分析
基本上功能還算完善







6. 數據資料下載
如果你有自己的server,想要把這些統計資料整合到自己的系統的話
我們也可以用它們的REST API去抓取這些資料
如果有需要的話可以連到這邊去看詳細資料
http://wiki.flurry.com/index.php?title=API_Usage


另外提醒一點的是,
在我初次測試的時候,我會想說log的event應該要馬上出現在server
事實上他並沒有那麼快的反應,可能會要等個半個小時左右才會更新一次報表 (詳細間隔還需要確認)
所以如果你的log沒有出現可能要請你等一會唷..

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程式應該會是標準配備。

2011年5月31日 星期二

XCode tip: 觀看UI Hierarchy的方法

在xcode的console下,我們除了可以看log
其實因為它是gdb環境,所以我們還可以對目前的環境做操作
我比較常用的是
gdb> po self
來列印中斷點所在的物件

也可以列印所在物件的message所回傳的object
gdb> po [self view]

如果該物件是個UIView.. 則可以列印它的UI Hierarchy
gdb> po [[self view] recursiveDescription]

這個是大絕招
gdb> po [[[[UIApplication sharedApplication] windows] objectAtIndex:0] recursiveDescription]

這樣就可以隨時pause.. 隨時看UI Hierarchy
就不需要在該UIView或UIViewController設定中斷點
參考資源
http://goo.gl/alEjL

2011年3月19日 星期六

XCode4一些使用經驗分享

XCode4 3/10正式開放下載,小弟馬上抓下來玩玩看。雖然從XCode3轉到XCode4一定會有些不適應,不過很快的也被很多讓開發更加便利的小細節所吸引。這篇是跟大家分享一些小技巧,希望對大家有幫助。

Command-Click / Option-Click
現在Command按著移到Symbol上面點擊,就可以直接跳到定義的部份
還有Option按著移到Symbol上面點擊,就可以直接看這個Symbol的文件
(如果有開發iOS的,記得到Preference -> Documentation裡面下載iOS library文件)

Assistant Editor:
這是 XCode4才有的新功能,方便我們同時修改兩個檔案甚至多個檔案。這對diff兩個檔案,或是同時想修改.h/.m都很有幫助。使用方法很簡單,XCode4右上角的Editor那邊就可以切換了。

而Assistant Editor也有一些進階用法。
1. 在左邊瀏覽頁面上,按Option + Click檔案,可以開啓檔案到Assistant editor
2. 在Editor中,按Command + Option + Click一個Symbol,可以在Assistant editor開啓。可以跟上面講的Command+Click選擇使用。
3. Command + Enter則可以關掉Assistant editor

Tab bar
不知道這是不是Xcode4才有的功能? 但這個功能在大部份的Editor都有,也很實用。
為了方便使用,我修改了一些設定
1. View -> Show Tab bar 把他點出來,讓Tab bar可以總是出現
2. Preference裡面 General -> Double Click Navigation 改成Use Separate Tab
這樣好處就是我在Project Navigators裡面雙擊一個檔案就可以開在一個新的tab
同樣的按著command.. 雙擊某個Symbol也可以在新的tab開啓。

Display Navigation Chooser

如果需要更有彈性的開啓方法
可以使用Navigation Chooser
1. 在Project Navigator當中用Shift+Option點檔案
2. 在Editor中按著Command+Shift+Option去點檔案


整理上面的幾個熱鍵
在Editor中
Command + Click: 同個Editor開Symbol
Option + Click: 看Symbol的Document
Command + Option + Click: 開在Assistant Editor
Command + Shift + Option + Click: 用Navigation Chooser來開
Command + DoubleClick: 用新Tab開
若在project navigator中
Click: 同個Editor開Symbol
Option + Click: 開在Assistant Editor
Shift + Option + Click: 用Navigation Chooser來開
DoubleClick: 用新Tab開

另外我覺得這個縮排熱鍵也很實用
先框選要排版的範圍
Command+[ 集體反縮排
Command+] 集體縮排
Control + | 集體重新排版
切換.h/.m改成
Command+Control+Up
Command+Control+Down
回到前一個位置
Command+Control+Left
回到下一個位置
Command+Control+Right

其他還有蠻多新的功能也很不錯 如
Auto build: 這點跟Eclipse很像,XCode會隨時在背景build,讓我們在Edit的時候就可以馬上發現是否有錯誤產生

Debug: 可以在Debug的時候,滑鼠移到變數上可以直接看變數的內容,甚至可以直接dump description到console


Workspace Window的設計:算是把以前多個Window整合到一個window的設計,這個好壞見仁見智,我是覺得還不錯的。


IB整合到XCode裡面: 這個早就該這樣設計了

新的Scheme的概念: 把以前的Build Configuration, Target, Executable都包進Scheme裡面來,這個使用上還需要習慣。

2010年11月14日 星期日

又寫了一個新的jquery plugin - jquery imgdesc

最近一個project是需要有產品瀏覽..
而每個產品還有詳細描述
為了增加畫面的簡潔度跟流暢感
所以又寫了一個jquery的plugin imgdesc
呈現的結果是這樣

要使用請按照下面的步驟
1. 首先先確定你的html的DOCTYPE如下

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

這樣跑出來的結果位置才會對

2. include需要的javascript以及css

<script type="text/javascript" src="js/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="js/jquery-imgdesc.js"></script>
<link type="text/css" rel="stylesheet" href="css/jquery-imgdesc.css">


3. 資料的部分,請這樣定義



Title

Your description




Title

Your description




Title

Your description




4. 最後一步就是呼叫imgdesc()套上imgdesc的plugin

$(function()
{
$(".modelImage").imgdesc();
});