Object-C 類


Classes 類

像其它的面向對象的語言一樣,Object-C也提供創建對象的藍本。即類。

首先我們在類中定義一些能夠反復使用的屬性和方法。

然后,我們實例化類,即對象,之后就能夠使用屬性和訪問。

Object-C和C++一樣。從類的實現中抽象出了類的接口。接口中定義了類的公開的方法和屬性。相應的實現代碼,定義方法和屬性的詳細的實現。

這和我們看到的函數,分離關注點是一樣的。

A class’s interface and implementation

這張我們將學習關於類接口。實現、屬性、方法已經實例化的基本的語法。我們也會介紹Object-C的內省和反射的功能。

Creating Classes 創建對象

我們將定義一個叫Car的類。他的接口在Car.h文件里,他的實如今Car.m 文件里。

這些是標准的Object-C的文件擴展名。當其它的類想使用這個類的功能時,須要使用這個類的頭文件,而類的實現文件是給編譯器使用的。

xcode 提供方便的創建類的模板。我們能夠從 File > New > File …或者Cmd + N 快捷鍵創建類。在模板中從iOS > Cocoa Touch中選擇Object-C類模板。之后。提示您配置一些信息:

創建類

在Class 域中填寫Car。在subclass of 中選擇NSobject。NSObject 類是其它全部Object-C類的父類。點擊下一步,提示選擇文件存儲的位置。選擇存儲在project根文件夾下。在對話框的以下,注意選擇您的project為目標project。這樣是保證文件會被編譯。

點擊next后。您會在Xcode的project導航中看到Car.h文件和Car.m文件。假設您選擇project名稱,在編譯資源模塊中。您會發如今Car.m文件在構建路徑中。不論什么您想讓編譯器編譯的文件都必須在這個類表中。

Interfaces 接口

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指令創建接口,后面接着類的名字和父類的名字,中間用冒號隔開。

保護類型的變量能夠定時在花括號內。

大多數開發人員喜歡講實例變量放在.m文件里,而不是頭文件里。

@property 聲明一個public 類型的變量,並且通過copy定義內存管理行為。這種情況下,賦值給model變量的值。是一個副本,而不是直接指向值。在屬性章節我們將會討論很多其它的細節。接下來是屬性的數據類型和名字,就像普通的變量聲明一樣。

以- (void)開頭的行。定義了一個叫drive的函數,沒有參數, (void) 部分定義函數的返回值類型。在方法前面的減號,表示一個實例方法,與之相相應的是類方法。

Implementations 實現

不論什么類的實現。第一件事是引入相相應的頭文件。

@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 幫助我們為屬性產生存取方法(getter和setter方法)。默認,getter方式是屬性名(model),setter方法是set前綴加屬性名首字符大寫,這樣比手動寫每個存儲器的特征方便。_model 部分定義了屬性的私有實例變量名。

xcode 4.4 之后,用@property聲明的屬性變量,自己主動產生存取方法。假設您覺得默認的實例變量命名規則能夠,您能夠省略@synthesize行。

drive方法的實現和接口中有相同的方法簽名。可是在簽名之后。有方法調用時運行的代碼。我們是通過self.model 獲取model的值,而不是通過_model。這是最佳實踐,利用了屬性的存儲方法。

僅僅有在init方法和dealloc方法中您才須要直接訪問實例變量。

self 關鍵是指向方法調用的實例。

除了獲取屬性值。還能夠調用方法。在教程中您將看到非常多這種樣例。

Instantiation and Usage 實例化和使用方法

不論什么想訪問類的文件,都要引入類的頭文件,我們絕對不能直接訪問類的實現。

這樣將違反了,分離實現和接口的目的。

所以,為了讓我們類生效,例如以下改動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。

為了調用Object-C對象的方法。我們將實例名和方法名,放在方括號內。用空格分開。

在方法名之后是參數。參數之前是冒號。所以您有C++、java、Python編程背景,[toyota setModel:@”Toyota Corolla”]能夠轉化成toyota.setModel(“Toyota Corolla”)。

toyota.setModel("Toyota Corolla");

方括號的語法可能是您不太習慣。可是我確信,在您閱讀網方法章節后,您會非常習慣這種寫法。

這個樣例為您展示了兩種獲取屬性的方法。

您能夠使用存取方法,或者點語法。

Class Methods and Variables 類方法和變量

上面的樣例定義的實例方法和屬性。我們也能夠定義類的方法和屬性。在其它的語言中稱為static方法或者屬性。

類方法的聲明和實例方法是一樣的,除了在方法之前是加號而不是減號。加入例如以下的class方法在Car.h文件里:

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

相同,在方法的實現之前也加入加號,可是沒有類變量,您能夠在實現文件里定義static變量模擬。

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

static NSString *_defaultModel;

@implementation Car {
...

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

@end

[aModel copy] 創建了一個參數的副本,而不是直接指向參數。這就是為什么我們在聲明model變量時。使用(copy)屬性。

類方法使用和實例方法一樣的方括號語法。可是他必須直接從類調動。不能在實例上調用。

[toyota setDefaultModel:@”Model T”]將會報錯。

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

“Constructor” Methods 構造方法

在Object-C中沒有構造方法。然而,對象的初始化是通過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.這也就是為什么我們在使用之前。總是檢測他是否存在。

可是總是僅僅有一個初始化方法做這件事。其它的初始化方法,調用這個指定的初始化方法。這樣當你定義多個初始化方法的時候,就省去了一些模板代碼。

注意,我們在initWithModel方法中直接給_model和_odometer賦值,也僅僅有這一個地方能夠這樣。其它的地方。須要使用self.model和self.odometer.

Class-Level Initialization 類-初始化方法

類級別的初始化方法和init方法一樣。我們能夠在這里初始化類,在其它地方使用它之前。比如我們賦值_defaultModel。

比如:

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

類的初始化方法,在類使用之前,僅僅能被調用一次。這就包括他全部的子類。最佳實踐是我們使用self == [Car class]確認初始化方法已經運行了。注意,在類方法中,self關鍵字,指向類自己,而不是實例。

Object-C不強制您標示重寫方法。

接下來我們看看自己定義初始化方法的運行情況。在類第一次被使用的時候,會運行[Car initialize]方法。即將_defaultModel賦值@”Nissan Versa”。這個能夠在第一個NSLog中看到。

第二個自己定義方法initWithModel輸出結果能夠在第二個輸出中看到。

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

Dynamic Typing 動態類型

類能夠被一個對象表示,即類對象,這樣我們就能夠方法他們的屬性。

並且能夠在運行時改變類的行為,添加或者改動方法。

這是非常厲害的動態類型能力。這樣您能夠在不知道當前對象是什么類型,也能夠調用方法和設置屬性。

最簡單的獲取類方法的方式是調用類方法。比如,[Car class]返回一個表示類的對象。您能夠將此對象傳遞給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;
}

NSClassFromString() 是還有一種獲取類對象的方法。

這樣能夠使您在運行時獲取類對象。然而不是高效的。所以最好使用類方法獲取類對象。

假設您對動態類型感興趣。您能夠查看Slectors和id類型的相關內容

Summary

這個模塊我們學習了創建類、初始化對象、定義初始化方法、調動類方法和變量。

了解了一點關於動態類型的知識。

Object不支持命名空間。所以Cocoa中的函數有NS、CA、AV這種前綴。避免沖突。

這個對類也是一樣的。我me你建議對於特定項目的類,命名使用三個字母的前綴。比如。XYZCar。

當然我們忽略了一些重要的細節。不用操心,假設您對屬性和方法還不是非常清楚。下一章我我們將近不這些漏洞,我們近距離觀察 @property 指令和影響他行為的屬性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM