iOS應用開發之Objective-C學習點滴
iOS應用開發之Objective-C學習點滴是本文要介紹的內容,Objective-C 是編寫 Mac 軟件的主要語言,如果你適應基本的面向對象和C語言,會給向你展示許多這些內容。如果你不知到C,你應當先閱讀 C 指南[英文]。
這個指南由Scott Stevenson撰寫并排版。
1. Calling Methods
為了盡快開始,讓我們先來看一些簡單的例子。調用某個對象的方法的一些基本語法如下:
- [object method];
- [object methodWithInput:input];
方法可以返回一個值:
- output = [object methodWithOutput];
- output = [object methodWithInputAndOutput:input];
同樣可以調用類的方法,用于創建對象。在下面的例子中,調用了類NSString的string方法,返回了一個新的NSString對象:
- id myObject = [NSString string];
類型id表示myObject變量可以是任何類型的對象的引用,所以在編譯的時候,它并不知道實際實現的類和方法。
在這個例子中,顯然對象類型是NSString,所以可以改變類型為:
- NSString* myString = [NSString string];
現在這是一個NSString變量,這樣編譯器會在常識調用NSString不支持的方法的時候進行警告。
留意在對象類型的右邊有一個星號。所有的Objective-C對象變量都是指針類型。id類型被預定義為指針類型,所以不需要添加星號。
嵌套消息
在許多語言中,嵌套的方法或函數調用像下面的形式:
- function1 ( function2() );
函數2的結果作為函數1的輸入。在Objective-C中,嵌套消息是如下的形式:
- [NSString stringWithFormat:[prefs format]];
避免在移行中嵌套超過兩次的調用,這樣代碼可以更加易懂。
多輸入方法
一些方法接受多輸入值。在Objective-C中,一個方法名可以被分為許多段。首先,多輸入方法是如下形式:
- -(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
你可以這樣調用這個方法:
- BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO];
這不僅僅是命名參數。在運行時的系統中,方法名實際上是writeToFile:atomically:。
2. 存取器
在Objective-C中,默認所有的實例變量都是私有的,所以在多數情況下,應當使用存取器來獲取或者設置值。有兩種語法。這是傳統的1.x語法:
- [photo setCaption:@"Day at the Beach"];
- output = [photo caption];
代碼的第二段不是直接訪問實例變量。它實際上調用了叫做caption的方法。在多數情況下,在Objective-C中,你不用向getter添加“get”前綴。
當你看到在方括號中的代碼,就表示向對象或類發送消息。
點語法
在Mac OS X 10.5帶的Objective-C 2.0中新增了點語法支持getter和setter:
- photo.caption = @"Day at the Beach";
- output = photo.caption;
可以在兩種語法中任選一種使用,但是在每個工程中只能使用一種。點語法只能用在setter和getter,不能用在其他目的的方法上。
3. 創建對象
主要有兩種方式創建對象。下面是***種:
- NSString* myString = [NSString string];
這是更加方便的自動創建的形式。在這個例子中,創建了一個自動釋放的對象,一會我們會了解更多的細節。在許多情況下,你需要用手工模式創建對象:
- NSString* myString = [[NSString alloc] init];
這是嵌套方法調用。首先NSString本身調用alloc方法。這是相對低等級的調用,用于分配內存并實例化對象。
第二個部分是調用新對象的init。init通常實現一些基本的設置,例如創建實例變量。對于使用類的用戶來說實現細節是不知道的。
在一些情況下,可以使用帶有輸入的不同的init版本:
- NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];
4. 基本內存管理
當編寫Mac OS X程序時,可以選擇開啟垃圾收集。通常,這意味著在遇到相當復雜的情況之前,無需關注內存管理。
然而,不可能總是工作在支持垃圾收集的環境。在這種情況下,需要了解一些關鍵點。
如果使用手工的alloc形式創建對象,需要在使用后釋放對象。不應當手動釋放任何自動釋放對象,如果這么做的話應用會異常退出。
這有兩個例子:
- // string1 將會被自動釋放
- NSString* string1 = [NSString string];
- // 使用完后必須釋放must release this when done
- NSString* string2 = [[NSString alloc] init];
- [string2 release];
在這個指南中,可以認為自動對象在當前函數結束后總是會被釋放。
關于內存管理還有許多需要學習,不過需要等到我們了解更多的一些其他要點之后。
#p#
5. 設計類接口
Objective-C創建類的語法非常簡單。通常有兩部分。
類接口通常保存在文件ClassName.h,用于定義實例變量和公共方法。
實現在文件ClassName.m中,包含其方法的實際代碼。通常也定義類的用戶不可用的私有方法。
下面展示了接口文件的樣子。類被稱作Photo,因此文件被命名為Photo.h:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- @end
首先,引入了Cocoa.h,用于引入Cocoa應用所需的所有基本類。#import指令自動處理在一個文件中多次引用的問題。
@interface表示這是類Photo的定義。冒號后定義了父類,這里是NSObject。
在花括號中,有兩個實例變量:caption和photographer。這里都是NSString,但其實變量可以是任何類型,包括id。
***的@end標記結束了類的定義。
添加方法
為實例變量添加getter和setter:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- - caption;
- - photographer;
- @end
記住,Objective-C方法通常不要“get”前綴。在方法名前的橫線表示這是一個實例方法。在方法名前的加號表示這是一個類方法。
默認,編譯器假設方法返回id對象,并且所有輸入值都為id。上面的代碼技術上是正確的,但是通常不用。給返回值指定類型:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- - (NSString*) caption;
- - (NSString*) photographer;
- @end
現在添加setter:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- - (NSString*) caption;
- - (NSString*) photographer;
- - (void) setCaption: (NSString*)input;
- - (void) setPhotographer: (NSString*)input;
- @end
Setter不需要返回值,所以設置為void。
6、類實現
從getter開始創建實現:
- #import "Photo.h"
- @implementation Photo
- - (NSString*) caption
- {
- return caption;
- }
- - (NSString*) photographer
- {
- return photographer;
- }
- @end
這部分的代碼由@implementation和類名開始,并且像接口一樣有@end。所有的方法必須放在這兩個聲明之間。
如果之前做過編碼,getter看起來應該非常熟悉,所以將精力放在setter上,這需要一些解釋:
- - (void) setCaption: (NSString*)input
- {
- ;
- caption = [input retain];
- }
- - (void) setPhotographer: (NSString*)input
- {
- [photographer autorelease];
- photographer = [input retain];
- }
每個setter有兩個變量。***個是已有對象的引用,第二個是新輸入的對象。在垃圾回收環境中,可以直接設置新值:
- - (void) setCaption: (NSString*)input
- {
- caption = input;
- }
但是如果不能使用垃圾回收,就需要釋放(release)舊對象,并保持(retain)新對象。
實際上有兩種方式釋放對象的引用:釋放(release)和自動釋放(autorelease)。標準的釋放將會立即移除引用。自動釋放方法將會在一小會后釋放,但可以明確的是它會保留到當前函數結束(除非添加自定義的代碼明示改變這個規則)。
自動釋放方法在setter中更加安全一些,因為變量的新舊值會指向相同的對象。肯定不想立刻釋放需要保持的對象。
現在似乎有一些混亂,但是將會按照進度有一個整體的介紹。現在無需弄清楚所有內容。
Init
可以創建一個init方法用于初始化實例變量:
- - (id) init
- {
- if ( self = [super init] )
- {
- [self setCaption:@"Default Caption"];
- [self setPhotographer:@"Default Photographer"];
- }
- return self;
- }
這段代碼自己已經解釋了很多問題,除了第二行看起來不同尋常以外。這里有一個等號,將[super init]的結果賦值給self。
這實質上是告訴父類進行其自己的初始化。那個if語句用于保證在嘗試設置變量默認值之前驗證初始化已經成功。
Dealloc
dealloc方法在對象被從內存中移除時調用。這通常是***的釋放所有子實例變量引用的時機:
- - (void) dealloc
- {
- [photographer release];
- [super dealloc];
- }
在前兩行,發送了release到每個實例變量。不需要在這里使用autorelease,標準的release會更快一些。
***一行非常重要。必須發送[super dealloc]消息告訴父類進行它自己的清理。如果不這樣做的話,對象不會被移除,從而導致內存泄露。
在執行垃圾收集激活的情況下,dealloc方法不會被調用。 需要實現finalize方法。
#p#
7、內存管理進階
Objective-C的內存管理系統叫做引用計數。需要做的全部事情就是跟蹤引用,在運行時進行真正的內存釋放。
在通常的情況下,alloc一個對象,可能在某個位置retain它,需要對每個alloc/retain消息發送對應的release。 所以,如果使用alloc一次,然后retain一次,需要release兩次。
這個算法叫做引用計數。但是在實踐中,只有兩種情況需要創建一個對象:
1. 將其保存為實例變量
2. 在函數中臨時使用
在多數情況下,實例變量的setter應當自動釋放(autorelease)舊的對象,并且保持(retain)新的。同時只要確保dealloc中正確釋放即可。
所以真正需要做的,只是管理函數中的本地引用。并且只有一個規則:如果通過alloc或copy創建了一個對象,在函數的***向其發送release或autorelease消息。如果通過其他方式創建的對象,什么也不要做。
這里有***種情況的例子,管理實例變量:
- - (void) setTotalAmount: (NSNumber*)input
- {
- [totalAmount autorelease];
- totalAmount = [input retain];
- }
- - (void) dealloc
- {
- [totalAmount release];
- [super dealloc];
- }
下面是另一種情況,本地引用。只需要釋放通過alloc創建的對象:
- NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
- NSNumber* value2 = [NSNumber numberWithFloat:14.78];
- // 只釋放value1,不操作value2
- [value1 release];
這里有一個集成:使用本地引用設置對象的實例變量:
- NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
- [self setTotal:value1];
- NSNumber* value2 = [NSNumber numberWithFloat:14.78];
- [self setTotal:value2];
- [value1 release];
注意這和管理本地引用的規則完全一致,不論是否將其設置為實例變量。也不用考慮setter是如何實現的。
如果明白了這個,就明白了90%需要知道的關于Objective-C內存管理的內容。
日志
在Objective-C中向控制臺記錄消息非常簡單。實際上,NSLog()函數相當接近C的printf()函數,除了額外增加的用于對象的%@標記。
NSLog ( @”The current date and time is: %@”, [NSDate date] );
可以將一個對象作為日志記錄到控制臺。NSLog函數調用對象的description方法,然后打印其返回的NNString。可以在類中重寫description方法返回自定義字符串。
9、屬性
之前編寫caption和author訪問器方法的時候,可能已經留意到那些代碼很直接,并且很普通。
屬性是Objective-C的特性之一,用于自動創建通用的訪問器,同時還有一些其他的功能。現在來修改Photo類使用屬性。
這是修改之前的樣子:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- - (NSString*) caption;
- - (NSString*) photographer;
- - (void) setCaption: (NSString*)input;
- - (void) setPhotographer: (NSString*)input;
- @end
這里是轉換為屬性寫法的樣子:
- #import <Cocoa/Cocoa.h>
- @interface Photo : NSObject
- {
- NSString* caption;
- NSString* photographer;
- }
- @property (retain) NSString* caption;
- @property (retain) NSString* photographer;
- @end
@property是Objective-C用于定義屬性的指令。圓括號中的“retain”表示setter應當保持(retain)輸入的值,剩下的部分只是指定屬性的類型和名稱。
現在看看類的實現:
- #import "Photo.h"
- @implementation Photo
- @synthesize caption;
- @synthesize photographer;
- - (void) dealloc
- {
- ;
- [photographer release];
- [super dealloc];
- }
- @end
@synthesize指令自動生成了setter和getter,所以這個類僅剩需要實現的是dealloc方法。
訪問器僅在其不存在時自動創建,所以對你的屬性隨意使用@synthesize,如果需要可以另外實現自定義的getter和setter。編譯器將自動填充不完整的方法。
屬性定義還有許多其他選項,不過那些已經超出了本指南的范圍。
10、在Nil上調用方法
在Objective-C中,nil對象等同于其他許多語言的NULL指針。不同之處在于你可以在nil上調用方法而不會引起崩潰或異常。
這個技術通過許多不同的方式使用在框架中,但是現在這主要意味著不需要在調用對象方法前檢查對象是否為nil。如果調用了一個nil對象的方法返回一個對象,返回值將會是nil。
可以用這個來改進dealloc方法:
- - (void) dealloc
- {
- self.caption = nil;
- self.photographer = nil;
- [super dealloc];
- }
由于將實例變量設置為nil,setter僅僅保存了nil(這個什么也不做)并且釋放了原有的值,所以這個能正常工作。這個寫法的dealloc通常更好,因為這避免了變量指向一個隨機的數據,而這個數據是變量之前存儲的位置。
注意這里使用了self.語法,這意味著使用了setter和內存管理。如果像這樣直接設置值,就會有內存泄露產生:
- // 錯誤,引起內存泄露。
- // 通過self.caption使用setter
- caption = nil;
分類
分類是Objective-C的又一常用功能。本質上說,分類允許在不繼承或不了解類的任何實現細節的情況下對已有的類添加方法。
因為可以向內建對象添加方法,所以通常這是很有用的。如果希望在應用中對所有的NSString實例添加一個方法,僅僅需要添加一個分類。不需要將所有工作都放到子類來完成。
例如,為NSString添加一個方法來判斷是否是合法的URL,可能類似這樣:
- #import <Cocoa/Cocoa.h>
- @interface NSString (Utilities)
- - (BOOL) isURL;
- @end
這非常接近類定義。不同之處在于沒有列出父類,同時在括號中有分類的名稱。名稱可以是期望的任何內容,雖然它應當同內部的方法進行通信。
這里是實現。需要留意這不是一個很好的URL檢測的實現。這僅僅是為了簡介一下分類:
- #import "NSString-Utilities.h"
- @implementation NSString (Utilities)
- - (BOOL) isURL
- {
- if ( [self hasPrefix:@"http://"] )
- return YES;
- else
- return NO;
- }
- @end
現在可以在任何NSString上使用這個方法。接下來的代碼將會在控制臺打印”string1 is a URL”:
- NSString* string1 = @"http://pixar.com/";
- NSString* string2 = @"Pixar";
- if ( [string1 isURL] )
- NSLog (@"string1 is a URL");
- if ( [string2 isURL] )
- NSLog (@"string2 is a URL");
不象子類那樣,分類不能添加實例變量。然而,可以使用分類重寫類中已有的方法,但是這么做需要相當小心。
記住,使用分類修改一個類時,應用中的所有這個類的實例都會受到影響。
小結:iOS應用開發之Objective-C學習點滴的內容介紹完了,這是Objective-C一個基本的概覽。如同已經看到的,這個語言相當簡單。沒有許多語法需要學習,相似的約定一遍又一遍的在Cocoa中被使用。***希望本文對你有所幫助!