Classes - 類

來源于 Ry’s Objective-C Tutorial - RyPress

一個(gè)學(xué)習(xí)Objective-C基礎(chǔ)知識(shí)的網(wǎng)站.

個(gè)人覺得很棒,所以決定抽時(shí)間把章節(jié)翻譯一下.

本人的英語水平有限,有讓大家誤解或者迷惑的地方還請指正.

原文地址:http://rypress.com/tutorials/objective-c/classes.html

僅供學(xué)習(xí),如有轉(zhuǎn)摘,請注明出處.


正如其他許多面向?qū)ο蟮木幊陶Z言,OC類提供了一個(gè)創(chuàng)建對象的設(shè)計(jì)(圖).首先,你在類中定義一組(系列)可重用的屬性以及行為(方法).隨后,使用該類實(shí)例化(一個(gè))對象來與這些屬性和行為交互.

OC與C++在從類實(shí)現(xiàn)中抽象出它的接口(這個(gè)方面)很相似.一個(gè)接口聲明了類中的公共屬性和方法,并在對應(yīng)的實(shí)現(xiàn)文件中定義這些屬性與方法實(shí)際執(zhí)行的代碼.這跟我們之前所見的函數(shù)聲明實(shí)現(xiàn)分離關(guān)注點(diǎn)一樣.


A class’s interface and implementation
A class’s interface and implementation

在這個(gè)模塊,我們將探討類的基礎(chǔ)語法,涉及接口,實(shí)現(xiàn),屬性和方法,并探討實(shí)例化對象的規(guī)范方法.我們也將會(huì)介紹一些OC的內(nèi)省和反射的能力.

創(chuàng)建類 - 使用Xcode

接口

Car.h包含了一些模板代碼,但我們將要把它改成下面所示的.(Car.h)聲明了一個(gè)model的屬性以及drive方法.

// Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject {
    // Protected instance variables (not recommended)
}

@property (copy) NSString *model;

 - (void)drive;

@end

用@interface指令來創(chuàng)建一個(gè)接口,跟在(@interface)后面是這個(gè)類以及其超類的名稱,通過冒號(hào)分隔.受保護(hù)的變量可以定義在花括號(hào)內(nèi),但大多數(shù)開發(fā)人員把實(shí)例變量當(dāng)做實(shí)現(xiàn)的細(xì)節(jié)并傾向于把它們放在.m(實(shí)現(xiàn))文件中.

@property指令則聲明了一個(gè)公共屬性,并且(copy)決定該屬性(property)的內(nèi)存管理方式.在這種情況下,給模型分配的值會(huì)被作為副本存儲(chǔ),而不是被直接指向.屬性模塊(對這方面)論述更多細(xì)節(jié).緊跟(@property)其后的是該屬性的數(shù)據(jù)類型以及名稱,跟常規(guī)的變量聲明一樣.

-(void)drive 這一行聲明了一個(gè)無參的drive方法,(void)這部分規(guī)定了它的返回值類型.預(yù)置的減號(hào)(-)標(biāo)識(shí)該方法是一個(gè)實(shí)例方法(與類方法對應(yīng)).

實(shí)現(xiàn)

任何一個(gè)類的實(shí)現(xiàn)第一件事是導(dǎo)入相應(yīng)的接口.@implementation指令與@interface指令除了不用包含它的超類之外,其他都類似.私有變量可以存儲(chǔ)在跟在類名之后的花括號(hào)內(nèi).

// Car.m
#import "Car.h"

@implementation Car {
    // Private instance variables
    double _odometer;
}

@synthesize model = _model;    // Optional for Xcode 4.4+

- (void)drive {
    NSLog(@"Driving a %@. Vrooooom!", self.model);
}

@end

@synthesize是為屬性生成存取方法的便捷指令(在Xcode4.4之后會(huì)自動(dòng)生成存取(方法),不使用這個(gè)指令也行,但對于屬性起別名還是有用的,不過很少人這么做).默認(rèn)情況下,getter(獲取)方法就是屬性名稱,setter(設(shè)置)方法則是在屬性名加上set前綴并將屬性名第一個(gè)字母大寫(setModel).這比手動(dòng)創(chuàng)建屬性的存取(方法)容易得多.synthesize這個(gè)語句中的_model決定該屬性要使用的私有實(shí)例變量名.

在Xcode4.4中,通過@property聲明的屬性會(huì)自動(dòng)合成(存取器),所以如果你對默認(rèn)的實(shí)例變量的命名規(guī)約習(xí)慣的話,你可以忽略@synthesize這一行.

drive的實(shí)現(xiàn)與接口中的定義有著相同的簽名,但在其后跟著該方法調(diào)用是需要執(zhí)行的代碼.請注意我們是如何通過 self.model 來代替 _model 訪問實(shí)例變量的值(的方式).這是一個(gè)很好的實(shí)踐,因?yàn)樗?self.model)利用了屬性的存取方法.一般來說,唯一你要直接訪問實(shí)例變量的地方是在init方法以及dealloc方法.

self關(guān)鍵字引用著(指著)調(diào)用方法的實(shí)例(類似C++與Java中的this關(guān)鍵字).除了訪問屬性之外,self也可以用來調(diào)用在同一個(gè)類中定義的方法(比如,[self anotherMethod]).我們將在該教程中看到很多這樣的例子.

實(shí)例與使用

任何需要訪問一個(gè)類的文件都必須導(dǎo)入那個(gè)類的頭(接口)文件 - 決不能嘗試直接導(dǎo)入類的實(shí)現(xiàn)文件.那樣有悖于公有API與其底層實(shí)現(xiàn)分離的目標(biāo).為了查看Car類的行為,將main.m修改如下:

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *toyota = [[Car alloc] init];
        
        [toyota setModel:@"Toyota Corolla"];
        NSLog(@"Created a %@", [toyota model]);
        
        toyota.model = @"Toyota Camry";
        NSLog(@"Changed the car to a %@", toyota.model);
        
        [toyota drive];
        
    }
    return 0;
}

在使用#import指令導(dǎo)入接口之后,便可以使用上面寫的alloc/init方式實(shí)例化對象.正如你所見,實(shí)例(一個(gè)對象)分成兩步:首先,你必須通過調(diào)用alloc方法給該對象分配內(nèi)存空間,然后初始化該對象以便使用.你絕不能使用一個(gè)未初始化的對象.

必須將對象存儲(chǔ)為指針是值得重復(fù)(的話題).這也是為什么我們聲明變量使用Car *toyota而不是Car toyota.

想調(diào)用OC對象的方法,可以通過在方括號(hào)內(nèi)放置(該對象的)實(shí)例與方法,并且用空格隔開實(shí)現(xiàn).參數(shù)在跟在方法名之后,且前面放置冒號(hào).所以,如果你有從事C++,Java或者Python的背景,那么這種調(diào)用方式[toyota setModel:@"Toyota Corolla"]可以轉(zhuǎn)化(理解)為:

toyota.setModel("Toyota Corolla");

對剛轉(zhuǎn)到這門語言(OC)的人來說,這種方括號(hào)語法可能會(huì)不習(xí)慣,但請放心,在你閱讀完[Methods]模塊后,你會(huì)對OC方法的約定感到非常舒服(適應(yīng),不僅僅是舒服).

這個(gè)例子也向你展示了兩種使用對象屬性的方式.既可通過synthesize又可通過setModel存取方法,或者使用便捷的點(diǎn)語法,這對那些曾使用過Simula-style語言的開發(fā)人員來說,應(yīng)該更熟悉.

<h4 id="jump">類方法與變量</h4>
上面的代碼段定義了實(shí)例層次的屬性以及方法,但也能定義類層次的.在其他編程語言中被統(tǒng)稱為"static"方法/屬性(不要與static關(guān)鍵字混淆).

類方法的聲明跟實(shí)例方法的很像,除了它們的前綴用加號(hào)替換了減號(hào).比如,給Car.h如下增加一個(gè)類層次的方法:

// Car.h
+ (void)setDefaultModel:(NSString *)aModel;

同樣地,類方法的實(shí)現(xiàn)也在方法前有個(gè)加號(hào).盡管OC中的類層次變量在技術(shù)沒有這種(寫法),但你可以在定義實(shí)現(xiàn)之前聲明一個(gè)靜態(tài)變量來與之對應(yīng)(模擬):

// Car.m
#import "Car.h"

static NSString *_defaultModel;

@implementation Car {
...

+ (void)setDefaultModel:(NSString *)aModel {
    _defaultModel = [aModel copy];
}

@end

[aModel copy]這種調(diào)用方式創(chuàng)建了這個(gè)參數(shù)的副本,而不是直接分配(給_defaultModel).這就是我們在給model屬性使用copy特性時(shí)真正做的事情.

Class methods use the same square-bracket syntax as instance methods, but they must be called directly on the class, as shown below. They cannot be called on an instance of that class ([toyota setDefaultModel:@"Model T"] will throw an error).

// main.m
[Car setDefaultModel:@"Nissan Versa"];

"構(gòu)造"方法

(準(zhǔn)確來說)OC中并沒有構(gòu)造方法.相應(yīng)的,OC中,一個(gè)對象在申請(分配內(nèi)存)之后就立即調(diào)用init方法來完成初始化操作.這也是為什么實(shí)例化操作總是兩步過程:分配(內(nèi)存空間),然后初始化.稍后將討論一下類層次的初始化方法.

init是默認(rèn)的用來初始化的方法,但你仍可以定義你自己的可以接受配置參數(shù)的初始化版本(方法).即使自定義的初始化方法也沒有什么特寫-除了方法名總是用init開頭之外,它們也只是普通的實(shí)例方法.下面展示了一個(gè)"構(gòu)造"方法原型(模板):

// Car.h
- (id)initWithModel:(NSString *)aModel;

若要實(shí)現(xiàn)上述的方法,你應(yīng)該遵循initWithModel:所示的公認(rèn)的(權(quán)威的)初始化模式.其中super關(guān)鍵字引用著父類,再(強(qiáng)調(diào))一次,self關(guān)鍵字引用著調(diào)用該方法的實(shí)例.下一步,我們將下述的方法添加到Car.m中.

// Car.m
- (id)initWithModel:(NSString *)aModel {
    self = [super init];
    if (self) {
        // Any custom setup work goes here
        _model = [aModel copy];
        _odometer = 0;
    }
    return self;
}

- (id)init {
    // Forward to the "designated" initialization method
    return [self initWithModel:_defaultModel];
}

初始化方法應(yīng)永遠(yuǎn)都是返回一個(gè)指向?qū)ο蟊旧淼囊?并且如果未能完成初始化,應(yīng)該返回nil.這就是為什么我們在使用對象之前要核查self是否存在的原因.通常也只有初始化方法才需要做這件事,剩下的只需要調(diào)用(該類)指定的初始化器就行了.這樣的話,當(dāng)你有幾個(gè)自定義的init方法時(shí)便可忽略樣板代碼(更加通用)了.

請注意我們是怎樣在initWithModel:方法給_model和_odometer賦值的.記住,這是唯一的地方(初始化方法),你應(yīng)該這么做-而在其他的方法中,你應(yīng)該這么用-self.model,self.odometer.

類層次的初始化

initialize方法是類層次中與init等同的(初始化方法).它能讓你在使用類之前對類進(jìn)行設(shè)置.比如,我們可以(通過這方法)來給_defaultModel填充一個(gè)合法的值,就像下面這樣:

// Car.m
+ (void)initialize {
    if (self == [Car class]) {
        // Makes sure this isn't executed more than once
        _defaultModel = @"Nissan Versa";
    }
}

initialize類方法對于每個(gè)類來說,都是在使用之前僅調(diào)用一次.這種情況包括Car的所有子類,也就意味著假如Car的其中一個(gè)子類沒有重新實(shí)現(xiàn)(重寫)initialize方法,那么Car將會(huì)被調(diào)用兩次.所以,使用self == [Car class]條件來保證initialization的代碼只執(zhí)行一次是很好的做法.也需要注意,在類方法中,self關(guān)鍵字引用著類本身,而不是一個(gè)實(shí)例.

OC不強(qiáng)制要求標(biāo)識(shí)一個(gè)方法是被重寫的.雖然init和initialize都是在它們的超類NSObject中定義的,但你在Car.m中重定義它們,編譯器也不會(huì)抱怨.

下一個(gè)重復(fù)的main.m將展示我們自定義的初始化方法.在第一次使用類之前,[Car initialize]會(huì)被自動(dòng)調(diào)用,設(shè)置_defaultModel為@"Nissan Versa".可以在第一個(gè)NSLog()看到(結(jié)論).當(dāng)然,也可以第二個(gè)日志輸出中看到自定義方法的結(jié)果.

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // Instantiating objects
        Car *nissan = [[Car alloc] init];
        NSLog(@"Created a %@", [nissan model]);
        
        Car *chevy = [[Car alloc] initWithModel:@"Chevy Corvette"];
        NSLog(@"Created a %@, too.", chevy.model);
        
    }
    return 0;
}

動(dòng)態(tài)類型

類本身就代表著對象,這也就是的它們可以查看自己的屬性(內(nèi)省),甚至改變自己的行為(通過反射).這些時(shí)非常強(qiáng)大的動(dòng)態(tài)類型能力,因?yàn)檫@種能力可以讓你即使在不知道對象是什么類型的情況下調(diào)用對象的方法或者設(shè)置對象的屬性.

最容易得到一個(gè)類對象的方式時(shí)通過類層次的方法(對(經(jīng)常使用)這些冗余術(shù)語標(biāo)識(shí)歉意).例如,[Car class]返回一個(gè)代表Car本身的對象.你可以將該對象傳遞給類似isMemberOfClass:以及isKindOfClass這樣的方法來獲取其他實(shí)例的信息.下面給出了一個(gè)綜合的例子.

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *delorean = [[Car alloc] initWithModel:@"DeLorean"];
        
        // Get the class of an object
        NSLog(@"%@ is an instance of the %@ class",
              [delorean model], [delorean class]);
        
        // Check an object against a class and all subclasses
        if ([delorean isKindOfClass:[NSObject class]]) {
            NSLog(@"%@ is an instance of NSObject or one "
                  "of its subclasses",
                  [delorean model]);
        } else {
            NSLog(@"%@ is not an instance of NSObject or "
                  "one of its subclasses",
                  [delorean model]);
        }
        
        // Check an object against a class, but not its subclasses
        if ([delorean isMemberOfClass:[NSObject class]]) {
            NSLog(@"%@ is a instance of NSObject",
                  [delorean model]);
        } else {
            NSLog(@"%@ is not an instance of NSObject",
                  [delorean model]);
        }
        
        // Convert between strings and classes
        if (NSClassFromString(@"Car") == [Car class]) {
            NSLog(@"I can convert between strings and classes!");
        }
    }
    return 0;
}

NSClassFormString()函數(shù)時(shí)其中一種讓你觸摸(獲取)到類對象的方式.這種方式很靈活,因?yàn)樗茏屇阍谶\(yùn)行時(shí)動(dòng)態(tài)的請求類對象;然而,它也是相當(dāng)?shù)托?由于這個(gè)原因,你應(yīng)該盡可能地選擇類方法(來請求).

如果你對動(dòng)態(tài)類型感興趣,請務(wù)必查閱選擇器以及id類型.

總結(jié)

在這個(gè)模塊,我們學(xué)習(xí)了怎么創(chuàng)建類,實(shí)例化對象,定義初始化方法,以及操作類層次的方法以及變量.我們也簡單的看了動(dòng)態(tài)類型.

前一個(gè)模塊提到OC不支持命名空間,那也是Cocoa函數(shù)需要像NS,CA,AV等這樣的前綴去避免命名沖突.這(使用前綴)對類也有同樣要求.推薦的規(guī)約是使用三個(gè)字母前綴用于你應(yīng)用程序中的特定類(比如,XYZCar).

即使你對屬性和方法感覺還不是很舒服也請不要擔(dān)心,因?yàn)檫@些(上述的內(nèi)容)基本上是你在開始寫自己類時(shí)要知道的了,盡管我們略過了一些重要的細(xì)節(jié).在下一個(gè)模塊,我們將通過詳細(xì)的地查看@property指令以及影響其行為的所有特性來填補(bǔ)這些孔(略過的重要細(xì)節(jié)).


寫于15年09月05號(hào)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市潭苞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖响逢,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棕孙,居然都是意外死亡舔亭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蟀俊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钦铺,“玉大人,你說我怎么就攤上這事肢预∶矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵烫映,是天一觀的道長沼本。 經(jīng)常有香客問我,道長锭沟,這世上最難降的妖魔是什么抽兆? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮族淮,結(jié)果婚禮上辫红,老公的妹妹穿的比我還像新娘。我一直安慰自己祝辣,他們只是感情好贴妻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著较幌,像睡著了一般揍瑟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乍炉,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天绢片,我揣著相機(jī)與錄音,去河邊找鬼岛琼。 笑死底循,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的槐瑞。 我是一名探鬼主播熙涤,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祠挫?” 一聲冷哼從身側(cè)響起那槽,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎等舔,沒想到半個(gè)月后骚灸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慌植,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年甚牲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝶柿。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丈钙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出交汤,到底是詐尸還是另有隱情雏赦,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布蜻展,位于F島的核電站喉誊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纵顾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一栋盹、第九天 我趴在偏房一處隱蔽的房頂上張望施逾。 院中可真熱鬧,春花似錦例获、人聲如沸汉额。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蠕搜。三九已至,卻和暖如春收壕,著一層夾襖步出監(jiān)牢的瞬間妓灌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工蜜宪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虫埂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓圃验,卻偏偏與公主長得像掉伏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容