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沒有出現可能要請你等一會唷..