Classes - 類

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

一個學習Objective-C基礎知識的網站.

個人覺得很棒,所以決定抽時間把章節翻譯一下.

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

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

僅供學習,如有轉摘,請注明出處.


正如其他許多面向對象的編程語言,OC類提供了一個創建對象的設計(圖).首先,你在類中定義一組(系列)可重用的屬性以及行為(方法).隨后,使用該類實例化(一個)對象來與這些屬性和行為交互.

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


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

在這個模塊,我們將探討類的基礎語法,涉及接口,實現,屬性和方法,并探討實例化對象的規范方法.我們也將會介紹一些OC的內省和反射的能力.

創建類 - 使用Xcode

接口

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

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

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

@property (copy) NSString *model;

 - (void)drive;

@end

用@interface指令來創建一個接口,跟在(@interface)后面是這個類以及其超類的名稱,通過冒號分隔.受保護的變量可以定義在花括號內,但大多數開發人員把實例變量當做實現的細節并傾向于把它們放在.m(實現)文件中.

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

-(void)drive 這一行聲明了一個無參的drive方法,(void)這部分規定了它的返回值類型.預置的減號(-)標識該方法是一個實例方法(與類方法對應).

實現

任何一個類的實現第一件事是導入相應的接口.@implementation指令與@interface指令除了不用包含它的超類之外,其他都類似.私有變量可以存儲在跟在類名之后的花括號內.

// 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之后會自動生成存取(方法),不使用這個指令也行,但對于屬性起別名還是有用的,不過很少人這么做).默認情況下,getter(獲取)方法就是屬性名稱,setter(設置)方法則是在屬性名加上set前綴并將屬性名第一個字母大寫(setModel).這比手動創建屬性的存取(方法)容易得多.synthesize這個語句中的_model決定該屬性要使用的私有實例變量名.

在Xcode4.4中,通過@property聲明的屬性會自動合成(存取器),所以如果你對默認的實例變量的命名規約習慣的話,你可以忽略@synthesize這一行.

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

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

實例與使用

任何需要訪問一個類的文件都必須導入那個類的頭(接口)文件 - 決不能嘗試直接導入類的實現文件.那樣有悖于公有API與其底層實現分離的目標.為了查看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指令導入接口之后,便可以使用上面寫的alloc/init方式實例化對象.正如你所見,實例(一個對象)分成兩步:首先,你必須通過調用alloc方法給該對象分配內存空間,然后初始化該對象以便使用.你絕不能使用一個未初始化的對象.

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

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

toyota.setModel("Toyota Corolla");

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

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

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

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

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

同樣地,類方法的實現也在方法前有個加號.盡管OC中的類層次變量在技術沒有這種(寫法),但你可以在定義實現之前聲明一個靜態變量來與之對應(模擬):

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

static NSString *_defaultModel;

@implementation Car {
...

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

@end

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

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"];

"構造"方法

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

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

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

若要實現上述的方法,你應該遵循initWithModel:所示的公認的(權威的)初始化模式.其中super關鍵字引用著父類,再(強調)一次,self關鍵字引用著調用該方法的實例.下一步,我們將下述的方法添加到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];
}

初始化方法應永遠都是返回一個指向對象本身的引用,并且如果未能完成初始化,應該返回nil.這就是為什么我們在使用對象之前要核查self是否存在的原因.通常也只有初始化方法才需要做這件事,剩下的只需要調用(該類)指定的初始化器就行了.這樣的話,當你有幾個自定義的init方法時便可忽略樣板代碼(更加通用)了.

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

類層次的初始化

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

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

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

OC不強制要求標識一個方法是被重寫的.雖然init和initialize都是在它們的超類NSObject中定義的,但你在Car.m中重定義它們,編譯器也不會抱怨.

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

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

動態類型

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

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

// 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()函數時其中一種讓你觸摸(獲取)到類對象的方式.這種方式很靈活,因為它能讓你在運行時動態的請求類對象;然而,它也是相當低效.由于這個原因,你應該盡可能地選擇類方法(來請求).

如果你對動態類型感興趣,請務必查閱選擇器以及id類型.

總結

在這個模塊,我們學習了怎么創建類,實例化對象,定義初始化方法,以及操作類層次的方法以及變量.我們也簡單的看了動態類型.

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

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


寫于15年09月05號

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容