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