版權聲明

所有的部落格文章都可以在右邊[blog文章原始檔案]下載最原始的文字檔案,並依你高興使用 docutil 工具轉換成任何對應的格式方便離線閱覽,除了集結成書販賣歡迎任意取用,引用
顯示具有 cocoa 標籤的文章。 顯示所有文章
顯示具有 cocoa 標籤的文章。 顯示所有文章

多工筆記 (GCD)

多工筆記 (GCD)

在多核心 CPU 或是雲端運算應用越來越普及的現在,平行多工處理 變成十分重要和熱門的項目,近幾年 FP (Functional Programming) 越來越流行,相關程式語言越來越多可以看出這種趨向, Apple 在 snow leopard 和 iPhone OS 4.0 針對平行處理,多工等議題 加入了一個全新的功能 Grand Central Dispatch (GCD),請參考 官方的說明頁面:

http://www.apple.com/tw/macosx/technology/

Notification Note

Notification Note

Notification 做的事情和 delegate 差不多,把他想像成大家都可以收到的 delegate 就差不多了,一樣是一種 event 處理的機制。

What is Notification

Cocoa/Cocoa touch 中處理 event 最常用的機制就是 delegate,所謂 delegate 就是物件有一個屬性 delegate 當有 event 發生的時候會傳送對應的 message 給 delegate 指定的物件,如 UIApplication 會送 applicationDidFinishLaunching: 給 application.delegate 物件,這物件必須實作 UIApplicationDelegate。

TDD in XCode (Objective-C)

TDD in XCode (Objective-C)

What is TDD?

TDD (Test Driven Develop) 近期因為敏捷式開發火紅也受到相當的重視, TDD 用三條規則就可以說明其實踐和理念。

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

大意就是,寫最基本的測試->通過測試->重構->進行下一個測試,其實我還滿認同 TDD 的理念的,先求穩在求好。 一開始我也覺得如此開發阿很浪費時間,其實自己實際在一些案子上面實踐後,發現其實並不會尤其開發功能越多, 越複雜的反而比較節省時間。

Pyobjc project in Xcode 3.2+

Pyobjc project in Xcode 3.2+

XCode 3.2 拿掉了所有 python 和 ruby 的project樣本,也就是說不能直接開啟python 的專案了,原因是因為這些專案都沒有跟上xcode update的腳步,事實上除了新功能pyobjc一樣 可以運作無誤的。

不過想要開啟pyobjc的專案必須手動安裝 project template

下面是原文連結: http://ioanna.me/2009/09/installing-pyobjc-xcode-templates-in-snow-leopard/

簡介

Apple 官方 iPhone 手冊的 The Core Application 章節閱讀筆記。

原文連結

Core Application Architecture

iPhone 程式會有一個唯一的 UIApplication 來接收系統 event。 就像其他的 GUI 程式一樣 UIApplication 產生一個迴圈不斷擷取系統 event 並交送到程式架構中的 UIView 衍生物件處理。

The Application Life cycle

完整的生命週期就是按下程式圖示進入程式開始一直到離開程式為止。

The Main Function

iPhone 的 main() 幾乎不需要特別的更改,main()會負責啟動一個 autorelease pool, 這東西就是 Objective-C 記憶體管理中送出 autorelease 訊息的物件會暫時存放的地方, 然後呼叫 UIApplicationMain(..) 來建立 UIApplication 物件。

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

The Application Delegate

每個 iPhone 程式都必須具備一個實現(comform) UIApplicationDelegate protocal 的物件 ,透過 UIApplicationDelegate protocal 定義的 delegates 來控制一些十分重要的 application event,像 applicationDidFinishLaunching: 代表系統告知已經完成 程式啟動步驟。

The Main Nib File

如果程式的 Info.plist 檔案內容有 key 值 NSMainNibFile , 程式在啟動的時候會依據 NSMainNibFile 指示的檔案去讀取對應的 nib (xib [1])。

http://s3.amazonaws.com/ember/HaxYLTsQKKe2nlBYCpTShbMwWENUD3fe_m.png

一般基本的 main nib 應該有一個 UIApplicationDelegate 物件, 一個 window 物件, 每個 nib 檔案都會有 File's Owner 和 First Responder , 請參考下圖途中另外包含 一個基本 View control。

http://s3.amazonaws.com/ember/bhnbBikUK2XZAE3JRxeL7Le7Y0loYZg1_m.png
[1]xib 和 nib 是一樣的檔案,只是xib檔案用xml呈現內容。

The Event-Handling Cycle

看看下圖就很清楚事件的發生和流程。

手指觸碰 iPhone -> 作業系統收到 -> 作業系統將資訊包裝完畢後丟入 queue -> Application 從 queue 取出 event -> 將取出的 event 放入 application object -> application object 將 event 丟入 responder chain [2] 流程由上至下非常清楚。

http://s3.amazonaws.com/ember/rFJiKZSPT9b0EMYybcnRMJK7q0QMSBMn_m.png
[2]Responder chain - 將所有會對 touch or motion event 有興趣的 responder [3] 串起來 依序接收 event 直到有 responder 接手處理。
[3]Responder : UIResponder 或後代類別建立的物件簡稱。

The Application Runtime Environment

iPhone 應用程式設計非常強調速度,因為手持裝置的特性,太過漫長的啟動時間和過久的 關閉程序都會讓使用者有不好的體驗。

Fast Launch, Short Use

iPhoen 要求程式月快啟動越好,並且嚴格限制程式關閉必須在5秒以內,如果5秒作不完 關閉程式的流程,作業系統會強制中斷。

基於快速進入離開的特性,設計應用程式的時候應該特別注意狀態的回覆,也就是說當程式 離開時最好能保有目前狀態,下一次進入程式可以馬上銜接上上次離開狀態。

The Application Sandbox

基於安全的理由,iPhone 的應用程式資料都被限制在一定的位置,其他應用程式沒有權限 進入存取,在文件中都稱呼這個限制的區域為 sandbox,當應用程式載入以後作業系統會 計算出一個唯一的識別符號,並用這個唯一的識別符號當作應用程式的home目錄,

The Virtual Memory System

iPhone 應用程式受限於實體記憶體大小,寫程式的時候請注意這點。

The Automatic Sleep Timer

為了省電目的 iPhone 在一段時間沒有收到 touch event 會將螢幕的背燈關掉,如果 不希望 auto sleep 這功能啟動,請使用下面程式片段:

UIApplication *app = [UIApplication sharedApplication];
app.idleTimerDisabled = YES;

Warning

關掉 idle timer 會快速消耗電力,請特別注意使用時機。

The Application Bundle

寫好應用程式後編譯時 xcode 會將應用程式包裝成一個 bundle [4] ,這編列一般 設定好的檔案和其意義:

App 執行檔,副檔名為.app
Setting.bundle Application preference
Icon.png 57X57 png 圖檔,顯示在springboard [5] 上得應用程式圖示。
Icon-setting.png 29X29 png 圖檔,顯示在設定裡 TableView 顯示用。
MainWindow.nib 預設 main nib file可在info.plist中修改。
Default.png 480X320 png 圖檔,當應用程式讀取時的背景圖案。
iTunesArtwork 512X512 和 Icon.png 一樣,不過是給 App Store用的。
info.plist 應用程式設定
en.lproj 國際化資源檔
fr.lproj...  

存取 Bundle 的程式範例

NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"sun"
                                                    ofType:@"png"];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:imagePath];
[4]Bundle 就是經過設計有意義的目錄。
[5]iPhone 的 Finder,將所有應用程式排列並根據使用者觸碰執行對應應用程式。

The Information Property List

Runtime Configuration Guidelines 有列舉所有支援的 key 和代表的意義,有閒可以參考。

要得到 inof.plist 的資料可以使用 NSBundle 的兩個 method objectForInfoDictionaryKey:infoDictionary , 建議使用 objectForInfoDictionaryKey: 即可。

從 info.plist 取資料的程式範例:

                   // value = icon file name
NSString *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIncoFile"];

Handling Critical Application Termination(event)

章節解釋重要的系統事件 (System Event)。

Initialization and Termination

iPhone 應用程式十分要求使用者的操作體驗,因此在 initialization 和 termination 程序盡量快速小巧,非必要的處理都不要放在 initialization 和 termination 事件裡 。

事件:(在UIApplicationDelegate)
  • applicationDidFinishLaunching: : 當 os 將應用程式載入完畢會觸發
  • applicationWillTerminate: : OS 關閉應用程式前觸發

applicationDidFinishLaunching: 不是用來完成全部的初始化動作,只要把ui必備的 資料準備好,其餘大量的初始data動作不要全部丟到 applicationDidFinishLaunching event 中。

applicationWillTerminate: 在這個事件裡面儲存應用程式的狀態,以利下次啟動的 時候恢復到離開時的狀態。

Responding to Interruptions

iPhone 應用程式執行的時候有很多機會要中斷目前的操作,比如有電話近來,收到簡訊 ,使用者按下sleep按鍵或是日曆上面的事件觸發。

這類事件由兩個event掌控

  • applicationWillResignActive: 當中斷事件進來,作業系統顯示 alert 前
  • applicationDidBecomeActive: 如果 ignore alert 回答 yes 作業系統觸發這個 event 並將控制權還給應用程式。

applicationWillResignActive: 當有中斷進入的時候由系統觸發,應用程式應該在這做 必要處理,比如 Game 應該暫停畫面更新和timer之類。

applicationDidBecomeActive: 在這個event中回復 applicationWillResignActive: 中的 處理。

Cocoa Fundamental 筆記 (iPhone) part2

Cocoa Fundamental 筆記 (iPhone) part2

原文連結

Part1

閱讀文件後的整理筆記,方便日後參考用。

這不是翻譯文件,也沒有完整的對照原文件的結構,很多我覺得沒那麼重要的部份 都直接跳過,畢竟這只是筆記,所以建議有時間有精神還是閱讀原文比較完整。

Introspection

動態型別(Dynamically type)和靜態型別(Statically Type)的優劣爭論持續了好一陣子, 動態型別程式語言帶來的靈活性,靜態型別的嚴謹各有千秋,不過近年來 程式語言佔有率 的統計動態 型別表現傑出,幾個重要的程式語言如 python , ruby 都有相當亮眼的表現,Objective-C 也是一種 動態型別的程式語言,通常動態型別程式語言會提供比較多的自省(introspection)能力,如 runtime 檢查type,檢查method等等。

Evaluating Inheritance Relationships

NSObject protocol 定義了一些判斷Class的method,如 classsuperclass

// ...

while ( id anObject = [objectEnumerator nextObject] ) {

  if ( [self class] == [anObject superclass] ) {

     // do something appropriate...

  }
}

兩個常用來判斷物件和Class關係的 methods

//判斷 receiver 是否是 aClass 或是其後代。
- (BOOL)isKindOfClass:(Class)aClass;

//判斷 receiver 是否為 aClass 的 instance。
- (BOOL)isMemberOfClass:(Class)aClass;

Method Implementation and Protocol Conformance

有兩個十分有用的 introspection method

  1. 檢查物件是否有實做(Implement)指定的 selector
- (BOOL)respondsToSelector:(SEL)aSelector
// Example 檢查 selector 後在執行
- (void)doCommandBySelector:(SEL)aSelector
{
  if([self respondsToSelector:aSelector])
  {
    [self performSelector:aSelector withObject:nil];
  }
  else
  {
    NSLog(@"Not implement selector %@",aSelector);
  }
}
  1. 檢查物件是否 conform 指定的 protocol
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
// 檢查 myobject 是否 conform NSObject protocol
[myobject comformsToProtocol:@protocol(NSObject)];

Hidden Argument (_cmd)

Method 有一個隱藏的變數 _cmd,指向目前的 method。

- (void)myMethod
{
  NSLog(@"current method name is %s",sel_getName(_cmd));
}
// 執行結果
// current method name is myMethod

- (void) myMethod2:(unsigned)arg1
          withArg2:(unsigned)arg2
{
  NSLog(@"current method name is %s",sel_getName(_cmd));
}
// 執行結果
// current method name is myMethod2:withArg2:

Object Comparison

關於物件的比較有兩個較重要的method

  1. hash
// hash 傳回整數值,將物件放入 hash table 時會呼叫並以傳回值當作 key。
// 兩個一樣的物件 (isEqual) hash 傳回的整數應該一樣。
- (NSUInteger)hash
  1. isEqual:
- (BOOL)isEqual:(id)anObject

還有一群 isEqualTo 開頭的 methods 和 isEqual: 一樣是比較 receiver 和傳入 的物件是否一樣,不過這類 methods 會先作型別檢查,詭異的是我測試下面程式並沒有作 型別檢查...也沒原文件說得會發生 exception

NSString aStr = @"123";
[aStr isEqualToString:nil];

Object Mutability

cocoa 的物件分為可變(Mutable)和不可變(Immutable)兩種,一般來說 cocoa 提供的物件 幾乎都是 mutable ,這個章節主要就是說明為什麼要有 immutable 變體,和什麼時機該用 immutable object。

Why Mutable and Immutable Object Variants?

cocoa 中物件預設都是 mutable 的,幾乎所有的 objects 都允許使用者透過 setter method 來存取內部資料,基於幾個理由 cocoa 針對一些 object 提供了 immutable 版本。

  • NSMutableArray
  • NSMutableDictionary
  • NSMutableSet
  • NSMutableIndexSet
  • NSMutableCharacterSet
  • NSMutableData
  • NSMutableString
  • NSMutableAttributedString
  • NSMutableURLRequest

為什麼需要 immutable?

  1. 基於程式邏輯需要,需要不會被其他物件或是thread變更的內容。 比如 NSDictonary 的 key 值就不應該可以被變更,因為變更key直的內容附帶改變了 hash code,改變後的hash code 將無法保證在NSDictionary中是唯一。
  2. 效率。 不需要額外處理記憶體操作。

Programming With Mutable Objects

mutable object 使用上得小提醒

  • 經常需變內容或是建立後會增減大小的物件。
  • 如果內容變更屬於大範圍變更,應該使用 immutable 並用置換的方式變更內容。
  • 判斷物件是否為 mutable 請參考傳回值不要使用 isKindOfClass: 之類的 introspection method。
  • 不確定要使用 immutable or mutable 那就用 immutable 吧。

Creating and Converting Mutable Objects

建立 mutable object 最簡單的方式就是標準的 alloc-init :

NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] init];

通常 mutable object 的 class 會提供可以設定容量的 factory method 比如:

NSMutableArray *mutArray = [[NSMutableArray arrayWithCapacity:[timeZone count]];

Warning

Factory Method 在 Part1 的內容中有提到物件建立後都有呼叫 autorelease,所以使用 Factory Method 請呼叫 retain 確保物件不會被 autorelease pool 釋放。

將 immutable object 轉換成 mutable object -- 使用 mutableCopy:

NSMutableSet *mutSet = [aSet mutableCopy];

將 mutable object 轉換成 immutable object -- 使用 copy:

NSSet *aSet = [mutSet copy];

除了 mutableCopycopy 外,如果要轉換 mutable <-> immutable 可以參考物件的 說明文件,許多 cocoa objects 都有提供互換的method,比如:

  • typeWithType : --- arrayWithArray
  • setType: --- setString (mutable classes only)
  • initWithType:copyItems: -- initWithDectionary:copyItems:

Storing and Returing Mutable Instance Variable

Instance variable 尤其是 collection type (array, set or dictionary) 該用 mutable or immutable 有幾個重點必須要考慮:

  1. 如果是 mutable 傳回給其他物件使用的時候,任意變更內容是否會造成其他物件錯誤? 比如有一個 array 要傳給 UITableView 當作 data source 使用,如果是 mutable 而任意修改內容是否會造成 UITableView 的錯誤?

  2. 基於理由一,如果真的頻繁修改所以必須使用 mutable ,可以在 getter 傳回是改變成 immutable ex:

    @interface MyClass:NSObject
    {
      //...
      NSMutableSet *widgets;
    }
    //...
    @end
    - (NSSet *)widgets
    {
      return (NSSet *)[[widgets copy] autorelease];
    }
    @implementation MyClass
    

Class Clusters

Part1 有稍微提到 Class Cluster , 基本上就是 abstract factory pattern 的實做。

Class cluster 的 public abstract class 的責任很單純:

  1. 為每一個 concrete subclass 建立對應的 initializer
// NSNumber 為他的 concrete subclass 建立的 initializer
NSNumber *aChar = [NSNumber numberWithChar:'a'];
NSNumber *anInt = [NSNumber numberWithInt:100];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0];
// 除了上面的 factroy method 外當然也有對應的initWith....
NSNumber *aChar = [[NSNumber alloc]  initWithChar:'a'];
NSNumber *anInt = [[NSNumber alloc]  initWithInt:100];
NSNumber *aFloat =[[NSNumber alloc]  initWithFloat:1.0];
  1. 建立所有 concrete subclass 必備的 method 宣告,這些method有專有名詞 primitive methods,比如要subclass NSArray 必須 override primitive mothods -- countobjectAtIndex:

A True Subclass

其實 class cluster 要擴充的機會沒有很多,但是你真的不得不擴充的情形下有兩個選擇:

  1. subclass
  2. Composite Object

當你要建立一個新的subclass並丟入Class Cluster必須作三件事情:

  1. subclass of cluster's abstract superclass
  2. Declare its own storage. 因為 abstract superclass 只有宣告 method 不會宣告任何 instance variable,所以要自行處理。
  3. Override the superclass's primitive methods.

除了 primitive methods 外其他 abstract superclass 定義的 method 基本上都是藉由 primitive methods 實踐,比如 NSArray 的 lastObject:

- (id)lastObject
{
  return [self objectAtIndex[self count]-1];
}

所以除非有特殊需求,這些衍生的method (Derived Method) 是不必要 override 的。

先前提過 Class cluster's abstract superclass 會幫 subclass 宣告許多 initializer ,當我們建立自己的 subclass 可以透過 category 將 initializer 加到 abstract superclass 中,下面是一個實際的例子:

@interface MonthArray : NSArray
{

}

+ monthArray;
- (unsigned) count;
- (id) objectAtIndex:(unsigned)index;

@end

@implementation MonthArray

static MonthArray *sharedMonthArray = nil;
static NSString *months[] = {
@"January",@"February",@"March",
@"April",@"May",@"June",
@"July",@"August",@"September",
@"October",@"November",@"December"};

+ monthArray
{
  if (!sharedMonthArray)
  {
    sharedMonthArray = [[MonthArray alloc]init];
  }
  return sharedMonthArray;
}

- (unsigned)count
{
  return 12;
}

- (id)objectAtIndex:(unsigned)index
{
  if (index >= [self count])
  {
    [NSException raise:NSRangeException
                format:@"***%s: index (%d) deyound bounds (%d)",
                        sel_getName(_cmd),index,[self count]-1];
  }
  else
    return months[index];
}

@end

@interface NSArray (MonthArray)

+ arrayOfMonthArray;

@end

@implementation NSArray (MonthArray)

+ arrayOfMonthArray
{
  return [MonthArray monthArray];
}

@end

A Composite Object

除了 subclass 可以在 class cluster 裡面擴充自己的需求外,另一種常用的手法是 composite,就下圖所呈現的 compoite object 對物件進行包裝,並針對自己感興趣 的部份進行處理即可。

http://s3.amazonaws.com/ember/Pcl44uQczcNfMUtXZwxEbYHAYmIIyhy0_m.png

例子是擴充 NSMutableArray 能夠對加入的物件進行自訂檢查程序:

@interface ValidatingArray : NSMutableArray

{
  NSMutableArray *embeddedArray;
}

+ validatingArray;
- init;
- (unsigned)count;
- objectAtIndex:(unsigned)index;
- (void)addObject:object;
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
- (void)removeLastObject;
- (void)insertObject:object atIndex:(unsigned)index;
- (void)removeObjectAtIndex:(unsigned)index;
@end


@implementation ValidatingArray

- init
{
    self = [super init];
    if (self) {
        embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init];
    }
    return self;
}

+ validatingArray
{
    return [[[self alloc] init] autorelease];
}

- (unsigned)count
{
    return [embeddedArray count];
}

- objectAtIndex:(unsigned)index
{
    return [embeddedArray objectAtIndex:index];
}

- (void)addObject:object
{
    if (/* modification is valid */) {
        [embeddedArray addObject:object];
    }
}

- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
{
    if (/* modification is valid */) {
        [embeddedArray replaceObjectAtIndex:index withObject:object];
    }
}

- (void)removeLastObject;
{
    if (/* modification is valid */) {
        [embeddedArray removeLastObject];
    }
}

- (void)insertObject:object atIndex:(unsigned)index;
{
    if (/* modification is valid */) {
        [embeddedArray insertObject:object atIndex:index];
    }
}

- (void)removeObjectAtIndex:(unsigned)index;
{
    if (/* modification is valid */) {
        [embeddedArray removeObjectAtIndex:index];
    }
}

Creating a Singleton Instance

這章節教大家如何在cocoa中實踐 singleton pattern。

步驟:

  1. 宣告 static instance 並出使化為 nil
  2. 在製作 factory method 的命名方式使用 sharedXXXXX,比如 sharedScreen, 並僅僅在步驟1 宣告的 static instance = nil 的情形下建立物件,否則直接傳回 static instance。
  3. Override allocWithZone: 確保不會有人不使用 factory method 而直接使用 init 建立物件。
  4. copyWithZone: , release , retain , retainCount and autorelease 都要 override 來確保 singleton 狀態不會發生變化。

Example:

@implementation MyGizmoClass
static MyGizmoClass *sharedGizmoManager = nil;

// 命名由 shared開頭
+ (MyGizmoClass*)sharedManager
{
  if (sharedGizmoManager == nil)
  {
    // 將產生的指標 assign to sharedGizmoManager 在 allocWithZone:
    [[self alloc] init];
  }
  return sharedGizmoManager;
}

+ (id)allocWithZone:(NSZone *)zone
{
  if (sharedGizmoManager == nil)
  {
    sharedGizmoManager = [super allocWithZone];
    return sharedGizmoManager;
  }
  // 不作接下來的 init 程序
  return nil;
}

- (id)copyWithZone:(NSZone *)zone
{
  return self;
}

// 不處理 retain counter
- (id)retain
{
  return self;
}

- (unsigned)retainCount
{
  // 確認物件絕對不會被釋放
  return UINT_MAX;
}

- (void)release
{
  // 不做事
}

- (id)autorelease
{
  return self;
}

Cocoa Fundamental 筆記 (iPhone) part1

Cocoa Fundamental 筆記 (iPhone) part1

原文連結

閱讀文件後的整理筆記,方便日後參考用。

原先計畫是一次po完,不過好像內容比預計的龐大只好分段, part2 待整理完在po.

這不是翻譯文件,也沒有完整的對照原文件的結構,很多我覺得沒那麼重要的部份 都直接跳過,畢竟這只是筆記,所以建議有時間有精神還是閱讀原文比較完整。

Cocoa architecture of iPhone

http://s3.amazonaws.com/ember/LWCl3UFB13gezYpC3uob1kD8YrjgjWp5_m.png

iPhone 開發流程

  1. Configure remote device.
  2. 在 xCode 建立 iPhone project.
  3. write code.
  4. Define the target ande exectutable environment.
  5. Build
  6. Test and debug.
  7. 測試應用程式執行效率 (XCode 有提供一些工具可用 ex: Instruments)

iPhone Application 必要的 cocoa framework

  • Foundation
  • UIKit

Foundation

Foundation 提供除了 User Interface 外設計程式所需要的基本 class 和功能,如

  • Memory management, Object mutability, Notifications.
  • Bundle 相關功能.
  • Unicode string support.
  • Object persistence, Object distribution.
  • Basic data type, file system function, thread...

Fundation Framework 中特別的名詞或是觀念

Mutable class variants:
Cocoa 提供的一些基礎 class 如 NSString, NSArray 在效率的考量上都是不可變的 (Immutable),如果需要執行期間可以改變,必須使用 Mutable variant (可變的變體) 如 NSMutableArray。
Class clusters:
Class cluster 由一個 super abstract class 和一群 concrete subclass 組成, super abstract class 藉由不同的 creat method 產生對應的 subclass instance。 class cluster 由 abstract factory pattern 衍生而來。
Notifications:
衍生至 Observer pattern,是一種可以通知多個objects狀態變更的方式。 Apple 在文件 中也直接使用 observers 稱呼訂閱notification的物件。

Fundation Classes

Fundation 一不同功能提供相當多 class,如果有興趣請參閱原始文件。 這章節筆記重要功能 讓我們知道有哪些功能就好。

幾乎都沒有翻譯,因為我這是筆記不是翻譯文件,而且我覺得這些 keyword 都一再出現在文件裡面, 保留原文還是比較看懂。

  • Value object:
    • NSData - 包裝 stream of bytes.
    • NSNumber - 包裝 scalar value (數值).
    • NSDate, NSCalendarDate, NSTimeZone, NSCalendar, NSLocale..和時間日期相關物件.
  • String
    • NSString - 包裝字串和編碼資料以及基本的字串處理能力,NSScanner 可以解析 NSString ,如有需要 可以配合 NSCharacterSet 使用。
  • Collection
    • NSArray - list, array.
    • NSDictionary - key:value 格式的資料.
    • NSSet - 包裝數學概念 Set.
    • NSEnumerator - 提供 iterator 能力 (foreach).
  • Operation-system service
    • File system and URL
      • NSBubdle
      • NSFileManager
      • NSURL
    • Networking
      • NSNetService
      • NSNetServiceBrowser
  • Notifications

  • Objective-C language service
    • NSException
    • NSAssertionHandler
  • XML
    • NSXML

UIKit

http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/Art/uikit_classes.jpg

原文有 Application Kit (OSX) 我就省略了,直接接 UIKit。

UIKit Framework 提供 iPhone 上面使用的 User Interface 物件,聽說可以深入閱讀 iPhone Application Programming Guide。

class hierachy 圖也請參閱原文件。

UIResponder 定義物件處理和接收 touch / motion event 的介面, cocoa 維護一個 list包含所有繼承至 UIResponder 的物件,當 touch / motion event 發生時, framework 會將 event 反應至 list 的第一個物件 (first responder),如果物件不 處理event就將event送至下一個物件直到有物件接收處理。

Application Coordination

iPhone 程式只會有唯一一個 UIApplication 物件, UIApplication 物件建立 main event loop , 和接收系統等級的 notification.

Events

UIKit 使用 UIEvent 包裝touch / montion event, UIEvent 包含一到多個 UITouch 物件,描述多點觸碰的每一點資訊。

Text and Image

UIKit 中輸入文字的介面定義在 UITextInputTraits protocol, UITextView 和 UITextField 都有 conform to [1] protocol UITextInputTraits。

假如想直接畫 string 到 view ,請參考 Category UIStringDrawing 相關 methods.

圖形相關資訊請閱讀 UIImage 相關文件。

[1]Conforms to protocol 表示實做protocol中非 option 的 method, 文件裡面都是使用 conform 因此不作任何翻譯

Core Data

大名鼎鼎好用到讓人想問微軟在幹麼的 Core Data 終於在 iPhone SDK 3.0 現身在 iPhone 上面 了,OK 就連官方的文件都說 Core Data 不是一項入門的技術,建議讀者必須先了解 Cocoa Fundamental (也就是本篇),Data modeling 和 model-view-controller design pattern (本篇原文的 Cocoa Design Patterna 章節),最後還要具備 key-value coding 相關背景知識 (收尋 Key-Value Coding Programming Guide 或是不保證正確連結 key-value )。

簡單來說, Core Data 就是一種包裝過的關聯是資料庫技術,可以很直覺簡單(不是說這不是入門技術, 還列一大堆必備知識嗎..) 設定好資料模型,然後很簡單的和介面掛勾,很簡單的變成應用程式....

有興趣的可以search "Core Data Programming Guide" or "Core Data Tutorial for iPhone OS"

不保證正確連結在此:

Core Data Programming Guide

Core Data Tutorial for iPhone OS

Cocoa Objects

關於 Objective-C 的相關敘述跳過,Blog 有相關資料了。

The Root Class

先定義一下 Root Class : 沒有 super class 的 class。

Cocoa Framework 有兩個 root class NSObject 和 NSProxy,不過 NSProxy 非常少使用到 因此一般提到 cocoa 物件,幾乎都是繼承至 NSObject。

NSObject 同樣名稱有一個 protocol, NSObject Protocol 定義了 cocoa class 必須具備的 基本行為和介面,

簡單介紹 NSObject<NSObjct> (NSObject comforms to NSObject protocol ) 相關行為和介面:

Allocation, initialization and duplicaiton:

分配記憶空間,初始化物件和複製物件相關 methods

  • alloc and allocWithZone : 分配記憶體並傳回指標。
  • init : 類似 C++ 的 default constructor,通常物件真正有用的init method 是 initWith 開頭的系列 method。
  • new : 結合 alloc 和 init,方便你少打幾個字用的。
  • copy and copyWithZone : 複製物件,由 NSObject protocol 定義, 交給繼承的 calss 實踐。 (相關參考 mutableCopy and mutableCopyWithZone NSMutableCopying protocol 定義)

Object retention and disposal:

記憶體配置管理相關 methods

  • retain : 增加物件的 retain 計數, retain=0 表示沒有物件參考可以釋放。
  • release: 減少物件的 retain 計數。
  • autorelease: 一段時間後減少物件的 retain 計數。
  • retainCount: 傳回 retain count 的值。
  • dealloc: 物件的 destructor。

Introspection and comparison:

由於 cocoa 屬於動態語言,也就是編譯的時候不會對物件的型態有什麼特別的意見,因此 run time 的型別判斷就顯得重要許多,introspection 就是提供這方面資訊。

  • superclass and class: 傳回 Class 。
  • isMemberOfClass:: 是否為 class 的 instance。
  • + isSubclassOfClass:: 是否為 class 的 subclass。
  • respondsToSelector:: 判斷 receiver 是否有實做 selector。
  • + instancesRespondToSelector:: 判斷 class 的 instance 是否有實做 selector。
  • conformsToProtocol: 判斷 reveiver 是否有實做 protocol。
  • isEqual:: object 比較
  • description: 傳回描述字串。
// respondsToSelector: 不要這樣使用,super 事實上會檢查整個 instance不單單只有 super
// class 的範圍。
SEL selector = @selector(tableView:cellForRowAtIndexPath:);
[super respondsToSelector:selector];

// 真的要檢查 super class 有沒有實做某個 selector 使用下面片段
[[self superclass] instancesRespondToSelector:selector];

// conformsToPrototcol: sample
[self conformsToProtocol:@protocol(NSObject)];

Object encoding and decoding:

  • encodeWithCoder: and initWithCoder:: 使用指定的 coder init objecte 或是存入 object。

深入閱讀 Archives and Serializations Programming Guide for Cocoa

Message forwarding:

  • forwardInvocation: 物件間的 message 傳遞,這邊要注意 message 在 cocoa 文件 的定義可能和你使用過的其他系統不同, message 在 cocoa 就是 method。
http://s3.amazonaws.com/ember/06etr0JKtKd9vr4w9KtSd1dSiApJpt0Y_m.png

Message dispatch:

  • performSelector: 執行 method,送出 selector 給 receiver。
// performSelector: example (single argument)
[self performSelector:@selector(conformsToProtocol:)
           withObject:@protocol(NSObject)];

Instance and Class Methods

Objective-C 定義 Method 分成兩種,Instance method 和 Class method。

舉個例子 alloc 就是 Class method, init 是 Instance method, Class method 定義時候用加號(+)開頭,instance method 用減號(-)。

注意一點,任何 Class 都可以將 root class 定義的 instance method 當作 class method 來呼叫。

SEL method = @selector(riskAll:);

// respandsToSelector: 是 NSObject (root class) 定義的 instance
// method,所以 MyClass 可以直接當作 class method 來使用。
if([MyClass respandsToSelector:method])
   [MyClass performSelector:method withObject:self];

Warning

如果class override root class 定義的 instance method後,則不在視為 root class 的 instance method 所以不能在當作 class method 使用.

Object Retention and Disposal

在 Mac OSX 10.5 (Leopard) 之後引入了 Garbage Collection (GC) 的技術, 可以幫助工程師減輕記憶體管理的負擔,可惜 iPhone 基於行動裝置有限的記憶體限制並 不支援GC這個好用的功能,所以不討論這個議題,如果有興趣請參考原文。

手動記憶體管理最基本的原則就是,每次 alloc - init , retain 或是 copy 物件都要記得呼叫 release 或 autorelease 來減少 retain counter 達到記憶體正 確的釋放。

說明記憶體管理之前有必要了解一下 cocoa 物件產生的過程,了解了物件的建立就知道如何 正確的釋放它,整個過程就是物件的記憶體管理了。

不論呼叫 allocallocWithZone: 還是其他的 factory method [2] cocoa 都會作兩件非常重要的事情:

  • 設定物件的 isa 指標到正確的 class,透過 isa cocoa 可以在 runtime的 時候確定物件的資訊,比如 class , superClass , comformed protocol 等等

    http://s3.amazonaws.com/ember/cvCkSAroCG5MoK4CNAErIMwQUJzEIKA4_m.png
  • 物件的 retain counter 加一 (retain counter ++),retain counter 紀錄 目前有多少物件參考到 self ,當 retain counter 等於零的時候,代表沒有其他物件 參考(或是擁有) self 系統可以放心呼叫 dealloc 進行記憶體回收的動作。

http://s3.amazonaws.com/ember/KhLmbNDo48V6RvTxE02TRfmKJzRVA1lC_m.png

注意!對已經釋放的物件送出 message 會造成程式的當機。

除了 alloc/init 會將 retain counter ++ 避免物件被釋放外,還可以透過 retain 作 同樣的事情。

- (void) myProcess: (id) anObject
{
  // 確保 anObject 不會被系統釋放
  id arg = [anObject retain];
  // ... some process
  // 現在不需要 anObject
  [arg release];
}
http://s3.amazonaws.com/ember/aGOLM51350ifp4piHtNS56VVc7oPlCKu_l.png

上圖清楚說明了 retain 的使用時機和方式。 使用 retain 就有義務呼叫 release 避免 memory leak發生。

除了 retain 還可以使用 copy 避免物件被釋放造成程式的錯誤,copy 最複製一份物件的內容, 並將新複製出來的物件 retain counter 設定為1,和 retain 一樣 copy 也有責任呼叫 release 來釋放物件。

通常處理可變的資料如 NSMutableString 並會針對內容進行改變又不想影響原本的內容時才 會使用copy,除此之外 copy 和 retain 並沒有什麼不同。

http://s3.amazonaws.com/ember/ttUJwc0WLbh6aUd0ItNWBqd7t24bnpsx_m.png

有一種情況,當我們建立了物件然後丟給其他物件使用,有時後會不知道什麼時候丟出去 的物件已經沒人要用可以釋放了,最簡單的例子就是 method 傳回一個 method 內部 建立的物件給呼叫 method 的物件使用:

/// 根據傳入的 UTF-8 編碼字串建立 NSString 物件並傳回
-(NSString *) stringWithUTF8String:(const char *)bytes
{
  // 建立string 物件
  NSString *retString = [[NSString alloc]
                        initWithBytes:bytes
                               length:strlen(bytes)
                               encode:@"UTF8"];

  // 物件傳回去了,無法被正確 release..
  return retString;
}

傳統 C 語言這問題一直無法有效避免,不過 cocoa 提供了一個 auto release pool 來幫助處理這類問題。

Autorelease pool 是一塊記憶區塊當程式對物件送出 autorelease message 時, 物件就會交給 autorelease pool 管理,不用在明確的傳送 release message給物件 來釋放物件,一切交給 autorelease pool 管理。所以上面的程式可以改寫:

/// 根據傳入的 UTF-8 編碼字串建立 NSString 物件並傳回
-(NSString *) stringWithUTF8String:(const char *)bytes
{
  // 建立string 物件
  NSString *retString = [[NSString alloc]
                        initWithBytes:bytes
                               length:strlen(bytes)
                               encode:@"UTF8"];

  // 物件傳回去前交給 autorelease pool 管理
  // 不用擔心 memory leak 的問題了
  [retString autorelease];
  return retString;
}

下面整理了一些規則,在處理記憶體管理的時候參考:

  • 如果你使用 alloc/init 建立物件,你就有責任傳送 release message 來釋放物件 佔用的記憶體。
  • 如果你 copy 物件,你有責任送出 release message 給複製的物件來釋放它,被複製 的物件釋放責任和你無關。
  • 如果使用 retain 保留物件,當你不在需要使用該物件時請送出 release 給物件,確保 最後物件的 retain counter 可以歸零並正確的被釋放。

相反地

  • 如果你從其他物件接收了某一個物件,請不要多事傳送 release message,除非你確定 這個物件並不會在其他地方被收到 release message.
  • 從 initWith.... 之類的 Factroy Method 收到的物件要記得 release。
  • 但是從 xxxxWith.... 之類的 Factroy Method (如 arrayWithArray:) 收到的物 件不用傳送 releae message,因為這些物件名稱開頭的Factroy Method都有用 autorelease message 交給 autorelease pool 管理了,有一個例外就是你想要長時間 使用收到的物件必須對收到的物件送出 retain 來確保物件不會因為 autorelease pool 滿了會記憶體不夠的情形下釋放,此時由於傳送了retain 自然要傳送 release 來正確的釋放。
  • 最後,千萬不要在 subclass 中 retain superclass,這樣會造成 cycle references 使的物件永遠不會被正確的釋放。
[2]Factory Method 如 NSString 的 + stringWithFormat: 是一種建立 物件的 Designed Pattern,有興趣可以 google Factroy Method 有一堆 資料。

Object Creation

在 C++ 的世界裡物件的建立只需要一個 new 就可以完成,new 分配了記憶體後就會自動呼叫 對應的 constructor 完成 member 的初值設定,在 Objective-C 物件的建立分成兩步驟 ,傳送 alloc message 完成記憶體的配置,傳送 init 完成其他初始化動作。

alloc 之前章節有提過了,現在把焦點放在 init 身上,init 是 NSObject 定義的 instance method,如果 initial object 時不需要任何其他資訊, subclass 直接 override init 就好了,在裡面完成所有的 initial 動作如成員變數的初值設定 ,準備外部資源如圖片音效等等,如果 initial object 必須傳入必要的參數,建議 init method 命名的方式採用 cocoa 的使用慣例 initWithxxxxx,如

- (id)initWithArray:(NSArray *)array;
- (id)initWithTimeInterval:(NSTimeInterval)secsToBeAdded
                 sinceDate:(NSDate *)anotherDate;

cocoa 的慣例大概就長這樣,傳回 id type 用 initWith 開頭,後面接參數。

init 不一定會成功,有許多理由可能造成 init 失敗,通常 cocoa 的設計是 init 失敗會傳回 nil , 所以較為優良的寫法應該如下例:

id myObj = [[MyClass alloc] init];
if ( myObj )
{
  [myObj doSomeThing];
}
else
{
  // 錯誤處理
}

Implementing an Initializer

原文文件列出實做 init 必須注意和遵守的規則:

下面的 initializer 泛指所有以 init 開頭的初始化 method。

  • 不論如何,第一件事情就是呼叫 superclass (super) 的 initializer.
  • 檢查 superclass initializer 的傳回值如果是 nil , 應該停止程式並傳回 nil.
  • 當 initializer 中要初始化 instance variables 時,如果 variable 參考外部 傳入的物件,應該用 retain 或是 copy 確定物件不會議外被釋放。
  • Initializer 完成後應該傳回 self,除非初始化過程錯誤或是有其他理由必須傳回替代 的物件。

Example:

- (id)initWithAccountID:(NSString *)identifier
{
  // 第一件事情呼叫 superclass initializer 並檢查是否成功
  if ( self = [super init] )
  {
    // 下面程式片段示範如何用替代的物件取代 self 的過程,例子是檢查一個
    // global accountDictionary 物件是否已經存在想要建立的物件。
    Account *ac = [accountDictionary objectForKey:identifier];
    if (ac)
    {
      [self release];
      return [ac retain];
    }

    // 一般的init程序
    if (identifier)
    {
      // accountID is instance variable
      accountID = [identifier copy];
      [accountDictionary setObject:self forKey:identifier];
      return self;
    }
    else
    {
      [self release];
      return nil;
    }
  }
  else // if ( self = [super init])
  {
    return nil;
  }
}

如果 instance variable 只是要設成零值 (0,nil,NO...)不需要特別在 initializer 中明確指定,因為執行 alloc 的時候就已經對所有 instance variable 進行歸零的動作了。

Multiple Initializers and the Designated Initializer

Objective-C 允許工程師定義多個 initializer,就像 C++ C# 可以定義許多 constructor 一樣,比如 NSSet :

- (id)initWithArray:(NSArray *)array;
- (id)initWithObjects:(id *)objects count:(unsigned)count;
- (id)initWithObjects:(id)firstObj, ...;

還記得前面章節 "Fundation Framework 中特別的名詞或是觀念" 有提到 cocoa 提供的 data object 會有兩種可變和不可變的,這些不可變的物件內容只能在 init 的時候設定好 ,所以下面的例子其實試行不通的:

NSSet *set = [[NSSet alloc] init];
// 不可變的物件往往沒有提供設定內容相關 method
[set setContext: array];

也因此多個 initializer 不僅僅是為了方便,有時候更是為了維持物件的不變性下必須 提供給物件設定內容的方法。

不論是為了方便,可讀性還是不得已提供多個 initializer 有一些簡單的設計規則可以 參考。

  • instance variable 只在一個地方設定。
  • 並只在同一個地方呼叫 superclass init。

第一項規則淺顯易懂,將相同性質的程式碼擺在一起不但易於管理,還降低程式間耦合性, 這不是我亂說的 Refactoring 相關書籍 裡面都有提到這點,第二點規則是基於第 一點規則,既然整個初始化過程都集中在一起了,呼叫 superclass init 這個一定要 做的事情擺一起,讓整個初始化流程完整統一也是十分合理。

在 initializer 中通常參數帶最多的 initializer 負責初始化 instance variable 和呼叫 superclass init,這個initializer 有個專有名詞 designated initializer, 和GCC擴充功能designated initializer名字 一樣請不要搞混了。

在多個 initializer 的情況下,除了 designated initializer 外通常只是呼叫 其他的 initializer 請看下面例子

// Designated Initializer
- (id)initWithName:(NSString *)name age:(unsigned)age
{
  if(self == [super init])
  {
     [name copy];
     [age copy];
     instanceName = name;
     instanceAge = age;
  }
  else
  {
     return nil;
  }
}

- (id)initWithName:(NSString *)name
{
  // 呼叫 initWithName: age: 進行初始化動作,並傳入預設值
  return [self initWithName:name age:0];
}

- (id)init
{
  // 呼叫 initWithName: 進行初始化動作,並傳入預設值 (nil)
  return [self initWithName:nil];
}

觀察上例可以發現所有的 initializer 串在一起,由 init 開始呼叫參數較少的 initWithName: 並傳入預設值,在由 initWithName: 依循相同模式呼叫參數 最多的 initWithName:age: ,而 initWithName:age: 正是 designated initializer 負責真正的初始化動作和 superclass init。

依照這個規則 initializer 可以正確的串連在一起並正確初始化整個繼承體系。

http://s3.amazonaws.com/ember/hLNuKf4NuZzKV8bXeH4vRHhhfCtnnpeb_m.png

切記一點,如果物件使用多個 initializer 可是你卻不遵守這個規定很有可能讓整個 initial chain 斷掉,造成初始化不完全的情形。

Related Posts with Thumbnails