Objective-C基礎教程讀書筆記(6)

來源:互聯網
上載者:User

第6章 源檔案組織
到目前為止,我們討論過的所有項目都是把原始碼統統放入main.m檔案中。類的main()函數,@interface和@implementation部分都被塞入同一個檔案裡。這種結構對於小程式和簡便應用來說沒什麼問題,但是並不適用於較大的項目。隨著程式規模越來越大,檔案內容會越來越多,尋找資訊也會越來越困難。
回想一下你的學生時代。你不會把所有的期末論文都放在同一個檔案裡而會把每篇論文都單獨存檔,並起一個易懂的檔案名稱。
將程式拆分為多個小檔案有助於更快地找到重要的代碼,而且其他人在查看項目時也能有個大致的瞭解。另外將代碼放入多個檔案還可以更容易地將有趣的類代碼發給朋友:只需打包其中幾個檔案即可,不用打包整個項目。本章將討論把程式碼拆分到不同檔案中的方法。
拆分介面和實現
前面已提到,Objective-C類的原始碼分為兩部分。一部分是介面,用來展示類的構造。介面包含了使用該類所需的所有資訊。編譯器將@interface部分編譯後,你才能使用該類的對象,調用類方法,將對象複合到其他類中,以及建立子類。
原始碼的另一個組成部分是實現。@implementation部分告訴Objective-C編譯器如何讓該類工作。這部分代碼實現了介面所聲明的方法。
在類的定義中,代碼很自然地被拆分為介面和實現兩個部分,所以類的代碼通常分別放在兩個檔案中。一個檔案存放介面部分的代碼:類的@interface指令、公用struct定義、enum常量、#defines和extern全域變數等。由於Objective-C繼承了C的特點,所以上述代碼通常放在標頭檔中。標頭檔名稱與類名相同,只是用.h做尾碼。例如,Engine類的標頭檔會被命名為Engine.h,而Circle類的標頭檔名稱是Circle.h。
所有的實現內容,如類的@implementation指令、全域變數的定義、私人struct等都被放在了與類同名但以.m為尾碼的檔案中(有時叫做.m檔案)。上面兩個類的實現檔案將會被命名為Engine.m和Circle.m。
如果用.mm做副檔名,編譯器就會認為你是用Objective-C++編寫的代碼,這樣你就可以同時使用C++和Objective-C來編程了。
在Xcode中建立新檔案
建立新類時,Xcode會自動產生.h和.m檔案。在Xcode程式中選擇File->New->New File後,會出現視窗,它列出了Xcode能夠建立的檔案類型。
選中Objective-C Class,然後點擊Next按鈕,會彈出另一個視窗要求你填寫類的名稱。
你還可以選擇新建立的類的父類(預設是NSObject)。每個類都必須有一個父類(NSObject沒有父類,它是所有類的父類)。你可以在下拉式功能表中指定這個新類的父類,如果下拉式功能表中沒你想要的父類,也可以直接輸入類的名稱。
點擊Next按鈕之後,Xcode會詢問你將檔案儲存體在哪裡。最好選擇當前項目中其他檔案所在的目錄位置。
此外你還會看到蒼有趣的東西。比方說,你可以選擇將新檔案放入哪個組(Group)。目前我們不會詳細討論Target這個概念,只是順便提一下:複雜的項目可以擁有多個Target,它們源檔案的配置各不相同,構建規則也不同。
新類建立完畢後,Xcode會在項目中添加相應的檔案,並在項目視窗中展示出來。
Xcode中有一個與項目同名的群組,檔案都放在群組內的檔案夾中。(你可以在項目導航器中瀏覽專案檔的構造。)這些檔案夾(Xcode中稱作群組)能夠幫你組織項目中的源檔案。例如,你可以建立一個用來存放使用者介面類的群組,再建一個用來存放資料處理類的群組,這樣你的項目將更易於瀏覽。在設定群組時,Xcode並不會在硬碟上移動檔案或者建立目錄。群組關係僅僅是由Xcode負責管理的一項奇妙的功能。當然如果你願意的話,可以設定群組指向檔案系統中某個特定的目錄,Xcode會幫你將建立的檔案放入該目錄。
拆分Car程式
首先要建立兩個繼承自NSObject的類:Tire和Engine。之後把原來寫在main.m中有關於Tire和Engine類的聲明和實現分別複製到對應的.h和.m檔案中。這裡貼出Tire類的代碼,Engine類與之類似:
Tire.h
#import <Foundation/Foundation.h>

@interface Tire : NSObject

@end
Tire.m
#import "Tire.h"

@implementation Tire

- (NSString *)description{
    return (@"我是一個輪胎");
}//description

@end//Tire
這裡比較有趣的代碼就是Tire.m中的#import了,它不是匯入Foundation.h,而是匯入了Tire.h。其實這是標準的過程,在你以後所建立的項目裡基本都會這麼寫(.m檔案都要#import對應的.h檔案)。編譯器需要知道類裡的執行個體變數配置,這樣才能產生合適的代碼,但是它並不知道與.m檔案配套的標頭檔是誰。所以我們要匯入.h檔案。如果在程式編譯過程中碰到了諸如“Cannot find interface declaration for Tire”(無法找到Tire類的介面定義)之類的錯誤資訊,通常是因為你忘記用#import匯入類的標頭檔了。

說明:注意,匯入標頭檔有兩種方法:使用引號或者角括弧。例如,#import<Cocoa/Cocoa.h>和#import “Tire.h”。帶角括弧的語句用於匯入系統標頭檔,而帶引號的語句則說明匯入的是項目本地的標頭檔。如果你看到的標頭檔名是用角括弧括起來的,那麼這個標頭檔對你的項目來說是唯讀,因為它屬於系統。如果標頭檔名前後用的是引號,那麼你便可以編輯它。
現在,重複上面的步驟來建立Slant6類和AllWeatherRadial類。此時程式可能會報告錯誤,這可能是由於Slant6類和AllWeatherRadial類沒有匯入Engine和Tire類的標頭檔造成的,自己手工補一下,將engine.h和tire.h檔案#import到對應的子類標頭檔(.h檔案)中即可。下面列出各類檔案中匯入的標頭檔情況:
Tire.h:
#import <Foundation/Foundation.h>
Tire.m:
#import "Tire.h"
Engine.h
#import <Foundation/Foundation.h>
Engine.m
#import "Engine.h"
Slant6.h
#import "Engine.h"
Slant6.m
#import "Slant6.h"
AllWeatherRadial.h
#import "Tire.h"
AllWeatherRadial.m
#import "AllWeatherRadial.h"
Car.h
#import "Engine.h"
#import "Tire.h"
Car.m
#import "Car.h"
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"
使用跨檔案依賴關係
依賴(dependency)是兩個實體之間的一種關係。在編程和開發過程中,經常會出現關於依賴關係的問題。依賴關係可以存在於兩個類之間,例如,Slant6類困繼承關係而依賴於Engine類。如果Engine類發生了變化,例如添加了一個新的執行個體變數,那麼就需要重新編譯Slant6來適應這個變化。
依賴關係也可以存在於兩個或多個檔案之間。Car.h依賴於Tire.hEngine.h檔案。如果兩個檔案中的任何一個發生了變化,都需要重新編譯Car.m來適應這個變化。
匯入標頭檔使標頭檔和源檔案之間建立了一種緊密的依賴關係。如果標頭檔有任何變化,那麼所有依賴它的檔案都得重新編譯。即使有一堆超效能主機任你使用,也需要花費相當長的時間。
由於依賴關係是傳遞的,標頭檔之間也可以互相依賴,所以重新編譯的問題會更加嚴重。不過,儘管重新編譯需要花費很長的時間,但至少Xcode能幫你記錄所有的依賴關係。
重新編譯須知
Objective-C提供了一種方法,能夠減少由依賴關係引起的重新編譯帶來的負面影響。導致依賴關係問題的原因是Objective-C編譯器需要某些資訊才能夠工作。但其實編譯器有時只需要知道類名就可以了,不需要瞭解太多。
例如,在對象複合之後,複合通過指標指向對象。這之所以行得通,是因為所有Objective-C對象都使用動態分配的記憶體。編譯器只需要知道這是一個類就可以了,然後就會知道執行個體變數的大小,就是一個指標的大小,無需知道更多。
前面我們使用#import的方式很好的完成了Car.h檔案和Car.m檔案,但它產生的重編譯副作用也很明顯,所以現在我可以採用另一種方式:@Class,這種方法足以告知編譯器處理Car類的@interface部分所需要的全部資訊了。不過這種方式在Car.m中是不行的,因為Car.m中我們需要建立具體Engine和Tire執行個體,所以編譯器必須知道這兩個類的全部內容。在Car.m中我們只能使用匯入(#import)。
說明:@class建立了一個前向引用。這是在告訴編譯器:“相信我,以後你自然會知道這個類到底是什麼,但是現在,你知道這些足矣。”
如果有循環相依性關係,@class也很有用。即A類使用B類,B類也使用A類。如果試圖通過#import語句讓這兩個類互相引用,那麼就會出現編譯錯誤。但是如果在A.h檔案中使用@class B,在B.h中使用@class A,那麼這兩個類就可以互相引用了。
讓汽車跑一會兒

Car.m需要更多關於Tire和Engine的資訊,所以在Car.m中要匯入Tire.h和Engine.h。(默然說話:我在這裡出了個問題,因為在前面我並沒有將Car.m中的init方法代碼刪除,所以即使在Car.m中匯入了Tire.h和Engine.h,init方法仍然在報錯,不知道是何原因。後來只好把init刪除了。)修改後的Car.m的代碼如下:
//
//  Car.m
//  CarParts
//
//  Created by mouyong on 13-7-6.
//  Copyright (c) 2013年 mouyong. All rights reserved.
//

#import "Car.h"
#import "Engine.h"
#import "Tire.h"

@implementation Car

- (void) print{
    NSLog(@"%@",engine);
    NSLog(@"%@",tires[0]);
    NSLog(@"%@",tires[1]);
    NSLog(@"%@",tires[2]);
    NSLog(@"%@",tires[3]);
}//print
-(Engine *)engine{
    return engine;
}//engine
-(void) setEngine:(Engine *)newEngine{
    engine=newEngine;
}//setEngine
-(Tire *)tireAtIndex:(int)index{
    if (index <0 || index>3) {
        NSLog(@"錯啦 索引值%d不對!",index);
        exit(1);
    }
    return (tires[index]);
}//tireAtIndex
-(void)setTire:(Tire *)tire atIndex:(int)index{
    if (index <0 || index>3) {
        NSLog(@"錯啦 索引值%d不對!",index);
        exit(1);
    }
    tires[index]=tire;
}//setTire:atIndex

@end//Car
匯入和繼承
Slant6和AllWeatherRadial繼承自我們自己建立的類:Slant6繼承自Engine,AllWeatherRadial繼承自Tire。所以在標頭檔裡不能使用@class。只能在Slant6.h中#import “Engine.h”,在AllWeatherRadial.h中#import “Tire.h”。
為什麼呢?因為編譯器需要Crowdsourced Security Testing道所有關於父類的詳細資料才能成功地為子類編譯@interface部分。它需要瞭解父類中執行個體變數的配置資訊。
最後,main.h中只剩下了#import命令和一個孤零零的函數。如下所示:
//
//  main.m
//  CarParts
//  類與類的關係:複合
//  Created by mouyong on 13-6-23.
//  Copyright (c) 2013年 mouyong. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Car.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"

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

    Car *car;
    car=[Car new];
    Engine *engine=[Slant6 new];
    [car setEngine:engine];
    for (int i=0; i<4; i++) {
        Tire *tire=[AllWeatherRadial new];
        [car setTire:tire atIndex:i];
    }
    [car print];
    return 0;
}//main
小結
在本章中,我們學習了使用多個檔案來組織原始碼的基本技巧。通常,每個類都有兩個檔案:包含類@interface部分的標頭檔和包含@implementation的.m檔案。類的使用者可以通過#import命令匯入標頭檔來獲得該類的功能。在學習過程中,我們認識了檔案的依賴關係,在這種關係中,標頭檔或源檔案需要使用另一個標頭檔中的資訊。檔案匯入過於混亂會延長編譯時間,也會導致不必要的重複編譯,而巧妙地使用@class指令告訴編譯器“相信我,你最終肯定會瞭解這個名稱的類”,可以減少必須匯入的標頭檔的數量,從而縮短編譯時間。(默然說話:另外,由於匯入的延展性和Coaca的自動檢查,我們可以每個標頭檔都只匯入一遍,就算重複匯入了,也不用太擔心過份的佔用記憶體,因為Coaca會幫我們把重複的匯入進行合并,只是不知道這個機制靠不靠譜)
接下來我們將領略一些有趣的Xcode功能,下一章見。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.