版權聲明

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

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;
}

1 則留言:

匿名 提到...

Not bad

Related Posts with Thumbnails