版權聲明

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

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