2017年1月5日 星期四

Ch09 - 介紹 Objective-C 繼承

Objective-C 程式設計學習筆記 Ch09

  • Ch09-介紹 Objective-C 的繼承。

目錄:

  • (1) 什麼是繼承 (inheriance)
  • (2) override 覆蓋
  • (3) super 關鍵字
  • (4) 抽象類別 (Abstract classes)

(1) 什麼是繼承 (inheriance)

什麼是繼承 (inheriance), 我大概分以下幾個方向去描述:
  • 什麼是繼承?
  • 繼承可以得到什麼?
恩還記得前幾個章節我們所寫的幾乎每一個類別,都是繼承自 NSObject 嗎? 複習一下 interface 界面檔的宣告方式:
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass
    //方法(Method)的宣告
@end
假如有個 Demo 類別的 superclass 是 NSObject,那我們就可以說 Demo 的父類別是 NSObject,而 NSObject 也有一個子類別是 Demo:
@interface Demo : NSObject{
    //....
}
@end
你可能為覺得好奇的是,為什麼總是繼承 NSObject? 這是因為在 Objective-C 中的根類別 (root class) 就是 NSObject 類別,除了它以外,其他所有的類別都有父類別 (可以說 NSObject 沒有父類別)。
因此 Demo 類別已經繼承 NSObject 了,再回到之前我們是如何建立個實體的,例如 :
Demo *test = [[Demo alloc] init];
假設我們並沒有在 Demo 類別宣告 alloc 以及 init 的方法,為何 Demo 可以使用這兩個方法?這原因就是因為 Demo 類別繼承 NSObject,繼承有個特性,子類別可以繼承父類別的所有實體方法跟變數

以下這張圖用來解釋繼承的關係:

當 Demo 繼承自 NSObject,也表示繼承了 NSObject 的實體方法跟實體變數,因此當我們要使用 Demo 建立一個物件時,直接使用的 alloc 或 init 都是繼承自 NSObject。(此圖只是舉例,實際上 NSObject 當然不可能只有這些實體變數跟方法)。
而若有新的類別繼承字 Demo 類別,那麼他一樣能夠使用 Demo 類別的實體變數跟方法 (ex: getResult)。 ch09-001
繼承就是建立一個新類別來 (1)擴充 或 (2)修訂原類別(父類別) 或 (3)直接使用父類的實體變數與方法 的功能,修訂其實就是我們接下來會講的複寫 override。
objective-C 沒有多重繼承,一個類別只能有一個父類別。

一個練習:

現在我們來寫兩個 class,其中第二個 class 必須繼承第一個 Class,第一個又必須繼承 NSObject,並且試著呼叫父類的方法以及實體變數。

//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
@end

@implementation Demo2
-(void) printDemo2{
    NSLog(@"Hi i am Demo2");
}
@end
程式部分:
Demo1 *first = [[Demo1 alloc] init];
Demo2 *second = [[Demo2 alloc] init];

[first printDemo1];
[second printDemo1]; //second 可以呼叫父類別 Demo1 的實體方法
[second printDemo2]; 
執行結果:
2014-01-18 11:53:53.470 ch07[7366:303] Hi i am Demo1
2014-01-18 11:53:53.472 ch07[7366:303] Hi i am Demo1
2014-01-18 11:53:53.472 ch07[7366:303] Hi i am Demo2
  • 甚至你在輸入 second 的方法字頭 prin 時,Xcode 的提示字元也會出現父類是 prin 開頭的方法: 

(2) override 覆蓋

覆蓋其實就是寫一個跟父類同名方法,子類別的方法會取代父類別的方法,這就是 override。被覆蓋的方法就不會再繼承父類的方法了,但其他沒有被覆蓋的方法一樣是具有繼承的特性。
我們無法透過繼承刪除/修改父類的實體方法,因此想要改變繼承而來的方法其內部實作如何,就使用 override 吧。
覆蓋沒有什麼特別的關鍵字,只要該類別的:
  1. 實體方法命名與想要覆蓋的父類方法同名即可。
  2. 該方法的回傳形態跟參數形態要一樣。

我們來改一下上一段 Demo1 與 Demo2 類別的範例,讓 Demo2 新增一個方法來 override Demo1 類別的 printDemo1 的方法:

//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
-(void) printDemo1;
@end

@implementation Demo2
-(void) printDemo2{
    NSLog(@"Hi i am Demo2");
}
//覆寫 override Demo1 類別的 printDemo1 方法
-(void) printDemo1{
    NSLog(@"Haha! i override you ;)");
}
@end
程式部分:
Demo1 *first = [[Demo1 alloc] init];
Demo2 *second = [[Demo2 alloc] init];

[first printDemo1];
[second printDemo1];
[second printDemo2];
執行結果:
2014-01-19 11:48:01.631 ch07[9839:303] Hi i am Demo1
2014-01-19 11:48:01.632 ch07[9839:303] Haha! i override you ;)
2014-01-19 11:48:01.633 ch07[9839:303] Hi i am Demo2

(3) super 關鍵字

還記得我們上一章 ch08 有學過一個 self 關鍵字,self 用在一個方法裡面要呼叫另外一個方法。現在要介紹的這個 super 關鍵字,用在一個方法裡面要呼叫父類別的方法
沒錯,我們現在又要來改 Demo1 以及 Demo2 類別 :P,改成當使用 printDemo2 的方法時也會呼叫 Demo2 父類別(也就是 Demo1) 的 printDemo1 方法 [super printDemo1]
//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
@end

@implementation Demo2
-(void) printDemo2{
    //在 printDemo2 內部呼叫父類別(Demo1) 的方法 printDemo1
    [super printDemo1];
    NSLog(@"Hi i am Demo2");
}
@end
程式部分:
Demo2 *second = [[Demo2 alloc] init];
[second printDemo2];
執行結果:
2014-01-19 12:21:32.551 ch07[10030:303] Hi i am Demo1
2014-01-19 12:21:32.553 ch07[10030:303] Hi i am Demo2

(4) 抽象類別 (Abstract classes)

抽象類別 (Abstract classes) 又稱為抽象超類別 (Abstract superclasses),至於為什麼叫做 Abstract superclasses,是因為 abstract classes 只是用來更方便地產生一個子類別而存在的類別,實際的實作功能是必須要由 subclass(子類別) 來完成的,因此 abstract classes 也被稱為 abstract superclasses,NSObject 就是一個例子。

Ch08 - 介紹 Objective-C 類別(2)

Objective-C 程式設計學習筆記 Ch08

  • Ch08-介紹 Objective-C 的類別相關的應用。
這章節只是更深入介紹一些關於類別的功能,像是...上一章節,我們已經學會將介面檔 (interface)、實作檔(implementation) 分開,並且學會實作 getter 以及 setter 方法,這章節我們會學到使用 @property 和 @synthesize 來實做 setter 和 getter 兩個方法。
另外我們會深入地使用一些方法,像是實作傳遞多個參數到一個方法,以及介紹兩個關鍵字的使用方式 static 以及 self 關鍵字。

目錄:

  • (1) @property 以及 @synthesize
  • (2) static 關鍵字
  • (3) self 關鍵字

(1) @property 以及 @synthesize

上一章節 Mobile 的類別,我們是自己寫 getCharge 以及 setCharge 方法這一組 getter 與 setter,分別是取得 chargeValue 以及設定 chargeValue。
現在只要使用 Objective-C 的 @property 以及 @synthesize 就可以幫我們達成這件事情,@property 寫在 interface 介面檔(ex: mobile.h),定義需要產生 getter 與 setter 的方法(注意,@property並不是寫在 @interface 宣告區塊裡,而是獨立的一個區塊),而 @synthesize 是寫在 implementation實作檔裡(ex: mobile.m),為指定的實體變數產生一組 gettet 與 setter 方法。

@property 的宣告方式:

使用 @property 時,就不需要在 interface 區段宣告一樣的實體變數了。比方說 interface 已經有宣告 int charge,而 @property int charge; 也已經宣告過一次了,因此可以不用重複宣告 charge (雖然有宣告也不會有什麼編譯上的錯誤)。
@property (型別) 實體變數或屬性
我們來改寫一下 mobile.h,你就會知道 @property 在 interface 介面檔如何使用: 這是目前的 Mobile.h:
因為註解有點多,我們把註解拿掉,以下是目前比較完整以及乾淨的 Mobile.h:
#import <Foundation/Foundation.h>

@interface Mobile : NSObject

@property int charge;
@property bool mobileStatus;

-(void) setShutdown;
-(void) print;

@end
  • 使用 @property 指令識別屬性 charge 以及 mobileStatus。
  • 而實體方法有 setShutdown 以及 Print。

@synthesize 的宣告方式:

@synthesize 其實也沒什麼特別的宣告方式,跟 @property 很像,只是 @synthesize 是寫在實作檔。
還有不要忘記為什麼要用 @synthesize,是因為 @synthesize 可以自動幫我們生成 getter 與 setter 方法,而且需要搭配 interface 介面檔中的 @property。但是在 Xcode 4.5 之後已經可以不用 @synthesize 了,只要使用 @property 就可以幫你產生 getter 與 setter,即便如此,我們的範例還是會使用 @property 搭配 @synthesize 來示範。
@synthesize 後面只需要接實體變數或是屬性就可以了:
@synthesize  屬性或實體變數名稱;
當你有多個實體變數或是屬性需要 @synthesize 時,也可以寫在同一行,用『,』隔開:
@synthesize 屬性或實體變數名稱, 屬性或實體變數名稱;
回到我們的 mobile.m 檔,接著我們要讓 setCharge, getCharge 方法以及讓 mobileStatus 使用 @synthesize 的方法產生 getter 與 setter,我們更動了一下 mobile.m 檔,已經被註解的地方表示已經不需要自己宣告的部分:
現在畫面有點亂,下面是已經整理過後的 mobile.m 檔:
#import "Mobile.h"

@implementation Mobile

@synthesize charge;
@synthesize mobileStatus;

-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", charge);

    if(mobileStatus==1){
        NSLog(@"Now your mobile shutdown");
    }else{
        NSLog(@"Now your mobile is on standby");
    }
}

@end

大功告成

方便的事情是,現在我們已經利用 @property 以及 @synthesize 幫我們完成自動產生 getter 以及 setter。可是,怎麼用呢? 當我們用 @synthesize 製作 charge 的 setter 與 getter 時:
  • getter 方法使用方式就是直接輸入方法名稱,如 [win_mobile charge]。
  • setter 方法使用方式就是 set+ charge,也就是 setCharge,如[win_mobile setCharge:85]。
getCharge 跟 setCharge 就是上一章我們自己寫的 getter/setter,透過 @synthesize,我們可以省去自己寫這段 code。
執行你的 main.m
你會發現當我們改寫 mobile.h 以及 mobile.m 檔,改為使用 @synthesize 的方式,完全不需要動到 main.m,若你現在再次執行 main.m,一樣可以執行。
#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}
你可以自己玩玩看有哪些變化跟差別: 在下面這個範例中,先是用 setCharge 改變 charge 的值為 40,然後 NSLog 使用 getter 取得 charge 的值。
#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:40];
        NSLog(@"charge value is %i", [win_mobile charge]);
    }
    return 0;
}

(2) static 關鍵字

若要講 static 關鍵字的用法,必須要回溯到之前講到的區域變數的概念,還記得區域變數只有在定義他們的方法中可以使用嗎?那麼如果你希望有一個區域變數也可以被其他的方法呼叫且改變其值,那該怎麼辦? 使用 static 關鍵字來宣告那個變數,就可以讓該變數仍然保有他的值。
如果你將某個變數宣告前面的修飾詞加上 static,那該變數就會成為靜態變數,例如宣告一個靜態變數 dountsNumber,初始值為 0 (使用 static 修飾詞的變數有沒有設定初始值都沒差,因為預設都會為 0)。
(其實我不太習慣稱 static 為關鍵字,這是很多翻譯書的問題,其實其他程式語言的 class 也會有 static,只是有些書翻 static 為靜態成員或是靜態變數都有)

我們來看一個簡單的例子,寫一個簡單的 Test Class :

這個類別只有一個方法 count,count 內部實作有一個 static 靜態成員 number,預設值為 0,只要呼叫(傳遞) count 這個方法給實體,就會將 number 的累加 1。
@interface Test : NSObject
-(int) count;
@end


@implementation Test
-(int) count{
    static int number;
    number++;
    return number;
}
@end

程式部分:

程式的部分非常單純,建立一個 firstTime 實體,NSLog 的值為呼叫 count 方法,取得 count 方法的回傳值:
Test *firstTime = [[Test alloc] init];
NSLog(@"now number is %i", [firstTime count]);

Test *secondTime = [[Test alloc] init];
NSLog(@"now number is %i", [secondTime count]);

執行結果:

2014-01-16 23:14:35.039 ch07[1481:303] now number is 1
2014-01-16 23:14:35.040 ch07[1481:303] now number is 2
你會發現 firstTime 以及 secondTime 一樣都是呼叫 count 的值,但 secondTime 所回傳的 number 的值卻是累加後的結果,這就是 static 關鍵字的用處,讓該區域變數被呼叫時能然保有其值 。

[燈泡]或許你把 static 關鍵字拿掉,就會知道結果的不同了,試試看!:

我們把 static int number 改成 int number=0,讓 number 變成非靜態成員;
@interface Test : NSObject
-(int) count;
@end


@implementation Test
-(int) count{
    int number=0;
    number++;
    return number;
}
@end
程式的部分如下:
Test *firstTime = [[Test alloc] init];
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);

Test *secondTime = [[Test alloc] init];
NSLog(@"now number is %i", [secondTime count]);
執行結果: 執行結果會發現,count 方法每次都是從初始值 0 開始累加 1,可是沒有保持其值,因為對於每次呼叫 count 方法以及 number++ 時,初始值都是 0,即便呼叫兩次 [firstTime count] 也是一樣,沒有使用 static 關鍵字就沒有保持其值。
2014-01-16 23:26:36.459 ch07[1552:303] now number is 1
2014-01-16 23:26:36.461 ch07[1552:303] now number is 1
2014-01-16 23:26:36.462 ch07[1552:303] now number is 1

改變 static 靜態變數的存取區域

承最原始的範例,既然我們是把 static 靜態區域變數宣告在 @implementation 區域的 count 方法裡面,因此只有利用實體的 count 方法才能存取 static 靜態變數,靜態成員 number 在這個範例中只是區域變數,如果你想要讓 static 靜態成員不夠透過實體方法也能改變其值,就必須將 static 靜態變數拉出方法的 @implementation 定義之外。

範例 (注意 static int number=0 的程式位置):

  • 類別宣告部分:
@interface Test : NSObject
-(int) count;
@end

static int number=0;
@implementation Test
-(int) count{
    number++;
    return number;
}
@end
  • 程式部分:
Test *firstTime = [[Test alloc] init];

//傳遞方法給實體
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);

//直接取得 number 的值
NSLog(@"result Number is %i", number);

//也能直接改變 number 的值
number = 10;
NSLog(@"result Number is %i", number);
  • 執行結果:
2014-01-16 23:44:05.051 ch07[1661:303] now number is 1
2014-01-16 23:44:05.053 ch07[1661:303] now number is 2
2014-01-16 23:44:05.053 ch07[1661:303] now number is 3
2014-01-16 23:44:05.054 ch07[1661:303] now number is 4
2014-01-16 23:44:05.054 ch07[1661:303] result Number is 4
2014-01-16 23:44:05.054 ch07[1661:303] result Number is 10

(3) self 關鍵字

既然講了 static 那就一起講個 self 關鍵字好了。其實大部分具有物件導向特質的程式語言,都有自己呼叫類別的方法或是呼叫父類別的方法。
而 self 關鍵字的 self 代表自己,嚴格來說應該是代表著當前方法的調用/呼叫者。self 用在一個方法裡面要呼叫另外一個方法。

舉例: 這是一個 Demo 的 class

setX: andY: 方法用來相加所帶入的兩個參數 (x,y),接著 setX: andY: 還會再自己呼叫 getResult 的方法([self getResult]),並且 return 相加的結果。
@interface Demo : NSObject
-(int) setX:(int) x andY:(int) y;
-(int) getResult;
@end


@implementation Demo
{
    int xvalue,yvalue;
}
-(int) getResult{
    return xvalue + yvalue;
}

-(int) setX:(int) x andY:(int) y{
    xvalue = x;
    yvalue = y;
    return [self getResult];
}

@end
程式部分:
Demo *testAdd = [[Demo alloc] init];
NSLog(@"testAdd 10 + 18 = %i", [testAdd setX:10 andY:18]);
執行結果:
2014-01-17 21:30:44.296 ch07[6254:303] testAdd 10 + 18 = 28
另一個關鍵字為 super,代表呼叫父類別的方法,我們會在下一章節『繼承』介紹。

下一章節,將會介紹 Objective-C 繼承 。

Ch07 - 介紹 Objective-C 類別(1)

Objective-C 程式設計學習筆記 Ch07

  • Ch07-介紹 Objective-C 的類別。
其實這章不只講類別,還會提到物件導向的觀念,實體以及方法,以及會回到 ch01 所提及的 Objective-C 承襲 Smalltalk 的訊息傳遞模型 (message passing),如何使用方法應用於類別與物件。下一章節是類別(2)。
物件其實是有點抽象複雜的概念,我的程度大概就只能表達到這樣的水平了,我一直不曉得到底該怎麼表達會比較好,希望大家多給些意見。

目錄:

  • (1) 什麼是類別
  • (2) 實體 (Instance) 與方法 (Method)
  • (3) 類別(class)的定義與實作
  • (4) 存取實體變數與資料封裝

(1) 什麼是類別

類別 (class) 是由實體變數跟可存取變數的方法 (method) 所組成,可以透過定義該類別,建立該類別的實體(Instance) 或物件 (Object)。
假如有個類別叫做 Human,我們可以說 win 是 Human 的實體 (Instance),同理 Irene 以及 Eric 也是(此例人名僅是舉例),每一個 Human 的實體 (Instance) 又稱為物件。 001.png

(2) 實體 (Instance) 與方法 (Method)

類別或實體可以進行的行為,稱為方法 (Method),方法有實體方法 (instance method) 也有類別 (class method) 方法。
方法名稱說明
類別方法由類別所呼叫的方法,其方法的宣告前面都會加上 + 號。
實體方法由類別的實體所觸發的方法。
比方說所有的 Human 的實體,都會走路 (walk), 喝水 (drink), 吃東西 (eat),這些方法都稱為實體方法。
每個人被生下來的過程其實就是產生一個 Human 類別的實體,而該實體的性別會是女生 (becomeGirl) 還是男生 (becomeBoy),這是類別方法。

如何呼叫方法

假設有個 Class 為 Mobile,Mobile 有幾個方法,比方說打電話 (call), 充電 (charge), 取得現在的電量 (getCharge) 都是。 002
如何呼叫方法,你可以之前學過其他語言呼叫方法的方式會是這樣子: obj->method(argument);。但在 Objective-C 中,做法不太一樣,假如有一個類別或實體要呼叫一個方法 (或者說傳遞訊息給類別或實體),其語法如下:
[ClassOrInstance method];
左邊是類別或是實體的名稱(ClassOrInstance),右邊為方法(method)的名稱。 另外,因為有些方法是可以傳遞參數的,因此語法也可以是這樣:
[ClassOrInstance method: argument];
要使用這些方法之前,必須要有一個物件作為對象,比方說我們先取得一隻手機 : mobile 是 class(類別名稱), 而 new 是類別方法,表示取得一支新的手機。
myMobile = [mobile new];
接下來我要讓 myMobile 做充電的動作,因此需要呼叫 charge 這個實體方法:
[myMobile charge]
由於 Objective-C 承襲 Smalltalk 的訊息傳遞模型 (message passing),比較好一點的說法應該要說,發出一個 charge 的訊息給 myMobile 物件,charge 就是訊息 (message),而 myMobile 就是訊息的接收者。myMobile 收到 charge 這個訊息之後,就看 charge 的這方法的內部如何定義實作。
接著,假如 charge 這個方法可以接受參數,該參數代表充電到幾 % 就結束充電,假設充電到 70% 就停止充電,其語法表示為:
[myMobile charge : 70];
假設我的朋友 Eric 也有一隻 Mobile,我們可以說 ericMobile 也是 Mobile 的實體 (instance)。
ericMobile = [Mobile new];
因此同樣都是 Mobile 的實體, Eric 的手機也具有相同的方法,像是打電話,充電等等。
[ericMobile charge];

想想 Mobile 這個類別還有哪些方法?以及舉例使用方式

方法名稱說明
[myMobile call : eric]myMobile 這的實體打電話給 Eric
[myMobile charge : 100]myMobile 充電到 100%
[myMobile shutdown]myMobile 關機
......

實體/物件的狀態(state)

實體所呼叫的方法,可以改變物件的狀態,比方說目前手機的剩餘的電量就是一種狀態,如果使用 charge 方法,並且還有設定參數,比方說參數設為 100 ,[myMobile charge : 100],就表示要將手機充電到 100%,當方法執行完成時,剩餘的電量狀態,也會被更變為 100。
另外手機目前是開機的,這也是一種狀態,當你傳遞(呼叫)關機的訊息(方法)時 [myMobile shutdown],手機的狀態就會變成關機。

(3) 類別(class)的定義與實作

  • Objective-C 的類別必須定義介面(interface)與實作(implementation)兩個部分。
  • 類別定義檔案的副檔名沿襲 C 語言的設計,介面檔 (interdace) 以 .h 為副檔名,實作檔案(implementation)以 .m 為副檔名。(在剛開始介紹時還不會介紹如何拆開介面檔與實作檔,會暫時先寫在同一支程式做介紹)。
  • 以下範例,我們會來如何定義一個 Mobile 的類別 (Class),分為以下幾個方面介紹:
    • interface 區塊
    • implementation 區塊
    • 使用/建立 Mobile 的實體(物件)
    • 利用 Xcode 建立 Mobile 的 Class

在開始之前,請先使用 Xcode 建立一個新的 Project,我的 Project Name 取名為 ch07。 (如何建立可參考 Ch02 的做法,在此不再多做描述)。
建立好新的 Project 之後,點擊 main.m 檔案(接下來我們會覆蓋預設產生的 code): ch07-003.png

interface 區塊

編輯 main.m,首先 interface 介面檔的定義,格式如下:
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass{
    //屬性(Property)或實體變數
}
//方法(Method)的宣告
@end
如果你沒有屬性/實體變數要宣告,可以省略大括號 { } :
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass
    //方法(Method)的宣告
@end
interface
當你鍵入 @inter 時,xcode 會幫你產生提示字元,只需要按 tab 鍵即可,會幫你補足順序第一位的提示文字(或是你有選擇其他的),再接續按下一個 tab,則會跳到下一個提示字元(ex: @interface->(tab) class name -->(tab) superclass)
以下是目前的程式碼:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int) getCharge;
    -(void) setShutdown;
    -(void) print;
@end
我們定義了一個名為 Mobile 的類別 (Class),@interface Mobile: NSObject,其 : 後面所接的 superclass,代表 Mobile 類別繼承哪一個 Class (繼承的詳細內容會在下章提到)。目前 Mobile 繼承 NSObject,我們可以說 Mobile 的父類別是 NSObject,而 NSObject 有一個子類別是 Mobile,繼承的部分在此先知道這樣就可以了。
至於你可能會疑惑為什麼我們可以在這裡用到 NSObject,這是因為我們已經 import Foundation 的檔案了,Foundation.h 已經有幫我們載入 NSObject 的介面檔了(NSObject.h),你可以檢視 Foundation 的資料夾,就可以看到 Foundation 包含哪些介面檔。
foundation
接著我們在 @interface 區塊定義了我們這個類別所需要的實體方法。
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
  • 各個方法的功能:
    • setCharge 決定充電量要到多少
    • getCharge 取得目前的充電量
    • setShutdown 讓手機關機
    • print 用來印出一些手機狀態的 NSLog
通常在方法的宣告前面,如果是『-』減號,代表該方法為實體方法(instance method)。而『+』加號開頭的,代表該方法為類別方法(class Method)。因為我們的方法都是針對實體的行為,像是充電, 關機,透過實體方法改變實體的一些屬性等等,因此將這些方法定義為實體方法。類別方法是針對類別本身的行為,像是建立一個新的類別,暫時我們的範例還遇不到需要定義類別方法。
(void) 代表剛方法執行後的回傳值的形態,形態有很多種,void 只是其一,而 void 所指的就是『沒有回傳值』。
在 Objective-C 中,回傳一個值會用 return 回傳值;,如果沒有回傳值,你可以只寫 return; 即可。
假如今天我們宣告一個實體方法是需要回傳 int 型別的值,那麼你的宣告可能會是這樣: -(int) getCharge; ,也就是 getCharge 這個方法必須回傳一個 int 的數字。
接下來,我們要為我們所宣告的實體方法做一些改變,目前的實體方法,可以有一些改變,比方說充電,我們可以決定只要充電到多少 % 就可以停止,因此我們可以為我們所定義的方法,增加參數,而充電要充到多少 % ,這個數值就是該方法的參數。

帶有參數的方法可以這樣定義:

-(void) setCharge: (int) n;
此圖比對定義實體方法有無參數的差別:
ch07-007
現在我們已經將 setCharge 這個實體方法定義為,沒有回傳值(-(void)),而且可以接受一組 int 型別的參數(:(int) n),該參數名稱定義為 n (n是我們自己定義的,你也可以取其他的名字),而且 n 這個參數可以被 setCharge 這個方法所使用。

你可能會問,只能有一個參數嗎?

其實不然,參數可以不只有一個,假設今天有個日期的類別,其中有個方法要你設定一段日期的期間,那個就需要兩個參數,一個是起始日期,另一個是結束日期,你的實體方法可能會是這樣:
-(void) dateFrom: (int) n dateTo: (int) m;
口語上你可以說這是一個 dateFrom:dateTo: 方法。
interface 的介紹就先到這個部分,以下是目前 main.m 的完整版本,寫完介面檔之後,接下來就是要來定義這些方法如何實作,也就是進到下一個階段,implementation 實作檔階段。
main.m 檔:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end

implementation 區塊

撰寫 implementation 實作檔,必須要實作或定義你在介面檔所宣告的方法,在實作檔中,也會定義這個類別有哪些變數或是資料成員,這些變數就是實體變數(instance variable)。講直接一點就是,在 interface 介面檔你會定義你需要哪些方法,至於這些方法怎麼做事情,是寫在實作檔。
// @implementation 實作檔定義
@implementation className
{
    //資料成員定義 (Member Declarations)
}

//實作定義在 interface 區塊的方法 (methods)

@end
實作檔的定義一樣要給類別名稱 (className)(ex:@implementation Mobile),這裡的類別名稱要跟 interface 介面檔的一樣。
順帶一提,如果你的 implementation 實作檔如果少寫了 interface 所定義的方法,是會被警告的,你會得到 Method definition for 實體方法名稱 not found 的訊息。 
什麼是資料成員?其實就是實體變數,比方說充電量的值,會隨著 call 方法是否被呼叫而改變,這也算是一種資料成員,與該實體有關,能夠被存取,改變狀態的資料。
定義資料成員會被 { } 大括號所包覆,在這個 { } 裡所定義的變數就是實體變數,當你建立一個新的實體時,該實體的實體變數也會被建立。假設我們有個實體變數稱為 chargeValue,表示現在的充電量,如果有個新的實體稱為 win1mobile,有另一個實體稱為 win2modile,那麼 win1mobile 與 win2mobile 都會有各自的 chargeValue。
在 implementation 實作檔中方法(method)的定義,跟 interface 宣告實體變數跟方法的方式很像,一樣是先用『+』,『-』加號或減號宣告是類別方法還是實體方法,接下來使用 ( ) 小括弧宣告方法的回傳值的型別,然後接上方法名稱,若該方法有帶參數的話,一樣是需要使用 :(參數型別) 參數名稱,接著宣告的程式內容,{ } 左右括號內的程式就是實作該方法的程式。

以下是我們為 Mobile 這個類別 (class) 所寫的 implementation,不要衝動,看完再說 :

// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end
Mobile 這個類別的實作檔有兩個實體變數,分別是 chargeValue 以及 mobileStatus,chargeValue 用來儲存充電量的值,而 mobileStatus 型別是布林值(Bool),這個值用來儲存 Mobile 開/關機的狀態,因為開關這種狀態只有正反兩面的值,不是有就是沒有,因此宣告為布林值得形態。
接著是好幾個方法的宣告,方法的前面使用 『-』 減號,因為這些方法屬於實體方法。
  • setCharge 方法可以帶一個 int 型別的參數,此方法的內部實作就是將 chargeValue 的值改變為你所輸入的參數值。
  • getCharge 方法可以用來取得 chargeValue 的值(return chargeValue)。
  • setShutdown 方法可以改變 mobileStatus 的值,1 則為是/開機/YES。
  • print 方法用來 NSLog 出 chargeValue 以及 mobileStatus。
萬事俱備了,下一段教你怎麼用 Mobile 類別,如何產生一個 Mobile 的實體(物件),並且使用實體的方法。 以下是目前完整的 main.m 檔:
#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end


// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end

使用/建立 Mobile 的實體(物件)

我們繼續編輯 main.m,接下來我們會在 main 這個 function 寫我們的程式:
int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile print];
    }
    return 0;
}

(1)建立記憶體空間 (alloc) 與初始化 (init)

我們在 main 函式定義一個 win_mobile 的變數,而 win_mobile 就是 Mobile 的實體。
Mobile *win_mobile 是建立一個名為 win_mobile 的 Mobile 物件(實體)。* 表示該物件變數為一個指標 (pointer)。指標只儲存該物件的記憶體位置而不是物件本身。
宣告一個物件為指標只是指向一個記憶體位置,但不會自動初始化這個物件(實體),你會發現 Mobile *win_mobile 中間沒有任何等號 (=),他跟一般變數的指派運算子不一樣,用*來表示右邊的物件是建立的來源,如果你沒有指定 * 後面的物件,指標會不知道要指向給誰。
[Mobile alloc] 代表傳遞 alloc 這個訊息給 Mobile 這個類別,產生一個 Mobile 的實體並且分配記憶體空間給 win_mobile,alloc 這個方法重點在於分配記憶體空間。
除了要使用 alloc 分配記憶體空間之外,還要初始化 (init) 這個實體,所以必須要使用 init 這個方法,這樣你才有辦法正常的使用這個 win_mobile 實體。
alloc 是 allocate 的縮寫,英文為分配的意思。
Mobile *win_mobile =[[Mobile alloc] init];
其實上面這段程式也相當於這三段程式: (或許這樣看會比較好理解)
//將 win_mobile 變數的位置指定給 Mobile
win_mobile *Mobile;
//分配記憶體空間
win_mobile = [Mobile alloc];
//初始化 win_mobile 的實體
win_mobile = [Mobile init];
我們沒有宣告 alloc 以及 init 方法,為什麼有這兩個方法可以使用?
因為 Mobile 的 superClass(父類別) 是 NSObject,因此 alloc 以及 init 這兩個方法是繼承字 NSObject,我們會在後面繼承的章節再次遇到。
* 星號是一種有特殊意義的符號,在 Objective-C 中稱為指標 (pointer),指標是用來參考(reference) 其他物件或資料形態的值。
指標(Pointer)與地址(Address)的觀念: 我們可以宣告一個變數,該變數存的值是一個記憶體的位置,用 * 號表示,宣告指標變數時會這樣表示 : TypeNname *pointer; ,像是 win_mobile *Mobile 就是 win_mobile 這個變數儲存的值是 Mobile 類別的記憶體位置,我們也可以說 win_mobile 是一個指標變數。指標會在後面章節會再遇到,在此先知道這樣就可以了。
還有一種運算子叫做取址運算子(&),用來取得一個變數的記憶體位置。

(2)還記得 Objective-C 是如何呼叫方法的嗎?

[receiver message]
左邊是訊息的接收者,右邊是傳遞給接收者的訊息。
因此我們想要傳遞 setCharge 這個訊息 (方法) 給 win_mobile 這個實體,就會這樣表示: 另外,之所以可以傳遞參數 85 這個數值,也是因為我們在 interface 以及 implementation 有宣告此方法可以帶一個 int 型別的參數。
[win_mobile setCharge:85];
同樣的,如果要傳遞其他方法也是一樣 :
[win_mobile print];
我們 build 一下現在的程式,看看會有什麼結果,點按 xcode 左上角的 build 按鈕:
以下是執行結果的 Log: 我們可以看到第一行 Now yoy charge value is 85,這句 NSLog 是經由 print 方法所實踐的,而因為我們在 print 方法之前有先呼叫 [win_mobile setCharge:85]; ,因此我們已經改變了 chargeValue 的值。
第二行 Log : Now your mobile is on standby,顯示現在手機的開關機狀態,因為我沒有呼叫 setShutdown 方法,因此還是待機狀態。
我們來呼叫一下 setShutdown 方法,如此一來 Log 的結果就會不一樣了 :
Mobile *win_mobile =[[Mobile alloc] init];
[win_mobile setCharge:85];
[win_mobile setShutdown];
[win_mobile print];
Log 結果:

以下是目前 main.m 的完整程式碼,通常我們不會把一個類別的 interface 以及 implementation 以及使用它的程式寫在同一隻檔案,下一段教你如何建立 class 類別檔,拆分為 .h 檔以及 .m 檔。
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end


// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}

利用 Xcode 建立 Mobile 的 Class

這個階段,我們要把寫在 main.m 檔的 Mobile 類別製作成類別檔。
  • 第一步,選擇 『File』->『New』->『File...』 
  • 第二步,選擇 Objective-C class,然後點選『Next』。 
  • 第三步,輸入你要建立的類別(class)名稱,輸入 Mobile,接著點按 『Next』。
  • 第四步,選擇檔案儲存的路徑,通常我是按照預設的位置(project的資料夾底下)。
  • 完成後,你會看到你的目錄已經出現 mobile.h 以及 mobile.m 檔。 

編輯 mobile.h

把剛剛寫在main.m 的 interface 區段移到 mobile.h。 以下是 mobile.h:
//
//  Mobile.h
//  ch07
//
//  Created by win on 2014/1/12.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Mobile : NSObject
-(void) setCharge :(int) n;
-(int)  getCharge;
-(void) setShutdown;
-(void) print;
@end

編輯 mobile.m

把剛剛寫在 main.m 的 implement 區段移到 mobile.m。 以下是 mobile.m:
//
//  Mobile.m
//  ch07
//
//  Created by win on 2014/1/12.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import "Mobile.h"

@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
        NSLog(@"Now your mobile shutdown");
    }else{
        NSLog(@"Now your mobile is on standby");
    }
}

@end

編輯 main.m

既然剛剛把 mobile 的 interface 以及 implenemt 都搬到 mobile.h 以及 mobile.m 了,現在 main.m :
不過現在 main.m 出了一點問題,因為他不知道 win_mobile 要初始化的類別 Mobile 是誰,因為我們少了 import Mobile 這個類別(class),因此我們要在 #import 下面多 #import ”mobile” 的 interface (介面檔)。
修改後的 main.m:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}
為什麼引入 Foundation 是這樣寫 #import ,而引入 Mobile.h 卻是要用雙引號刮起來(#import "Mobile.h")?會有這樣的差別是因為 Foundation 是系統檔案,而 Mobile 類別是你自己客製化產生的類別 (class)。

(4) 存取實體變數與資料封裝

每個實體/物件都可以存取自己的實體變數,但是類別方法不能夠『直接』存取實體變數,因為類別方法沒有辦法處理自己產生的實體/物件,類別方法只能用在類別本身。
基本上每個實體內的資料都是私有的,你可以當作在 implementation 宣告的變數都是私有變數:
@implementation ClassName {
  int memberVar3; //私有實體變數
}
也正因為類別方法不能直接存取實體變數,因此會有一些實體方法提供取得實體變數的方法出來,比方說 getCharge 方法就是一種 getter,setCharge 就是一種 setter,你會常常在實體方法的宣告中看到用 set 開頭或是 get 開頭的方法,不論是 setter 或是 getter 這些都是 Accessor method (有些書會翻譯為『存取器』或『存取方法』) 。然而 setter 方法通常是用來設定或改變某些實體變數的方法,因此 setter 通常不會需要回傳值,因為他的角色是用來改變某些值; 而 getter 用來取值,因此 getter 是需要有回傳值的。

為什麼需要製作這些 getter 跟 setter 方法?

這是因為你無法從類別以外取得/改變實體變數的值,因此可以透過 setter 與 getter 讓外界取得/改變某些實體變數的值,這就是資料封裝的概念。(有一些文章有寫道其實 getter 與 setter 就是提供獲得實體對象屬性值的API。)
資料封裝的英文為 Data Encapsulation,這個字源自於英文的『capsule』的膠囊,你只需要知道某顆膠囊的功能跟服用方式,可是你不用知道膠囊是如何製作的。因此封裝的概念其實就是資訊隱藏,將資料 (屬性) 與方法 (method) 封裝在物件/實體中,而提供訊息傳遞的方法就是 getter 與 setter。

好物 @property 以及 @synthesize

如果你覺得總是要自己寫存取某些實體變數的 getter 與 setter 很麻煩,那麼下章節會介紹到 Objective-C 提供的合成存取方法,利用 @synthesize 自動產生 getter 與 setter。

下一章節,將會介紹 Objective-C 類別(2) 。

類別(2) 會介紹如何傳遞參數到方法, static 與 self 關鍵字, 合成存取方法(@synthesize)等等。