至於未來會怎樣,要走下去才知道反正路還很長,天總會亮。 1. 面向對象 1.1 什麼是面向對象(OOP) 面向對象 Object Oriented Programming。在軟體開發中,我們雖然用的是面向對象的語言,但我相信絕大多數入門或者工作經驗不長的同學敲出來的代碼依然是大段的面向過程的思想,我 ...
至於未來會怎樣,要走下去才知道反正路還很長,天總會亮。
1. 面向對象
1.1 什麼是面向對象(OOP)
面向對象 Object Oriented Programming。在軟體開發中,我們雖然用的是面向對象的語言,但我相信絕大多數入門或者工作經驗不長的同學敲出來的代碼依然是大段的面向過程的思想,我們只是把面向對象來當做 OC 語言的一個特性而已,具體是什麼估計自己也說不明白到底是什麼。那麼到底該怎麼去理解面向對象編程呢?
面向對象是一種程式設計的範型,同時也是一種程式開發的方法。面向對象是將現實世界中的事物抽象成對象,現實世界中的關係抽象成類、繼承,幫助人們實現對現實世界的抽象與數字建模,用更利於人的理解方式,對複雜的系統進行分析、設計與編程。
現代的程式開發幾乎都是以面向對象為基礎。而在面向對象廣泛流行之前,軟體行業中使用最廣泛的設計模式是面向過程方式。面向過程的操作是以程式的基本功能實現為主,開發過程中只針對問題本身的實現,並沒有很好的模塊化設計,所以在代碼維護的時候較為麻煩。而面向對象,採用的更多的是進行子模塊化的設計,每一個模塊都需要單獨的存在,並且可以被重覆利用。所以面向對象開發更像是一個具備標準模式的編程開發,每一個單獨設計的模塊都可以單獨存在,需要時候只要通過簡單的組裝就可以使用。但是,面向對象的底層還是面向過程,這兩種程式設計思想是可以互相依存的,也是貫穿我們整個程式開發周期的思想指導,我們在設計程式、開發程式中應該牢記“面向對象”、“面向過程”。面向對象中類和對象是最基本、最重要的組成單元。下麵會講到該怎麼理解類和對象。
1.2 面向對象的三大特征
繼承:繼承是子類繼承父類非私有數據結構和方法的機制,是類之間的一種關係。它是面向對象語言特有的特征,面向過程的語言不具有繼承特性,而 OC 是單繼承。繼承提供了類的規範等級結構,使公共的特性能夠共用,提高的軟體的重用性。類的繼承性使所建的軟體具有開發性、可擴充性,簡化了對象、類的創建工作量,提高了代碼的重用性。
封裝:在面向對象的語言中,對象、類、方法都是一種封裝,對象是封裝的最基本單位。
類的封裝體現在每個類都有 .h 和 .m 兩個文件,將定義與實現分開,.h 聲明(用戶可見的外部介面),.m實現(用戶不可見的內部實現)。
方法的封裝,是最常見的,每個方法中封裝了一個小的功能,這是單一職責的很好體現,第三方框架和代碼也是一種封裝。
封裝使程式的結構更加清晰,將實現的信息封裝隱藏,用的時候直接調用封裝好的方法或類,提高效率。此外,可以減少程式間的相互依賴。
多態:不同對象以自己的方式響應相同的消息的能力叫做多態。假設有一個類包含一個方法,由這個類派生出兩個子類或者兩個對象,其各自實現各自的方法,也就是不同的對象以自己的方式響應了相同的消息。多態增強了軟體的靈活性和重用性。
1.3 對象的理解
什麼是對象?世界萬物皆對象,所有看到的看不到的都是對象,把對象引入到編程中,那就是面向對象編程(Object Oriented Programming,OOP,面向對象程式設計),在百度百科中有關於 OOP 詳細的介紹。簡單來說就是它將對象作為控制項的基本元素, 利用對象和對象之間的相互作用來設計程式,說白了,一款軟體的運行就是控制項之間的相互作用。說了那麼多,那到底啥是對象,對象是你,是我,是萬物,我認為在程式中能 alloc 出來的都是對象。就拿人來說,你是人,我也是人,為啥咱倆不一樣,要是放到程式里來說,是因為 alloc 分配的記憶體地址不一樣。人有頭髮、眼睛、手鼻等,這些在 OC 裡面稱之為屬性,人能跑、跳、投,等動作這些在 OC 裡面稱之為方法。其實,你在做項目的時候已經在用對象這個概念了,只不過你不知道罷了。舉個簡單的例子:點擊 tableview 的 cell 讓 cell 裡面的控制項變換顏色,我相信大家都能實現這個效果,是怎麼做的呢?肯定是定位點擊的哪個 cell,拿到當前 cell,那這個 cell 不就是對象,找到 cell 中的控制項,控制項不就是 cell 的屬性嗎?顏色也是控制項的屬性,控制項即是對象也是屬性,控制項間的相互作用完成了這個功能,說到底也就是操作的對象。
1.4 類的理解
面向對象編程中,具體的事物是對象,將具有相同或相似性質的對象的屬性或方法抽象出來便是類,類是對象的抽象化,對象便是類的具體實現。估計這不好理解,再拿網路請求數據來講,數據都有相同或相似的數據,將這段數據的相同點抽離出來便是我們所建的 model 類,比如 person 類,屬性有:name、height、weight 等,他們具有相同的屬性將屬性他們通過一個方法將數據進行轉化,那這個方法不就是封裝嗎?一般都是在 .h 文件中聲明方法,在 .m 中去實現,方法一般都是隱藏內部實現,預留一個穩定外部介面。面向對象程式設計中的方法可分為兩種,一為上述的實體(對象)方法,二為類方法,主要的差異在於實體方法需要有一對象去引發,而類別方法可以由類別名稱調用。
2. Objective-C 類
2.1 類概念
如同所有其他的面向對象語言,類是 Objective-C 用來封裝數據,以及操作數據的行為的基礎結構。對象就是類的運行期間實例,它包含了類聲明的實例變數自己的記憶體拷貝,以及類成員的指針。Objective-C 的類規格說明包含了兩個部分:定義(interface)與實現(implementation)。定義(interface)部分包含了類聲明和實例(成員)變數的定義,以及類相關的方法。實現(implementation)部分包含了方法的實現,以及定義私有(private)變數及方法。類的定義文件遵循 C 語言之慣例以 .h 為尾碼,實現文件以 .m 為尾碼。
2.2 類定義(Interface)
2.2.1 定義
定義文件遵循 C 語言之慣例以 .h 為尾碼。定義部分,清楚定義了類的名稱、數據成員和方法。 以關鍵字 @interface 作為開始,@end 作為結束。
下麵定義一個叫做 XBCar 的類的語法,這個類繼承自 NSObject 基礎類。類名之後的(用冒號分隔的)是父類的名字。類的實例(成員)變數聲明在被大括弧包含的代碼塊中。實例變數塊後面就是類聲明的方法的列表。每個實例變數和方法聲明都以分號結尾。
@interface XBCar : NSObject {
// 成員變數
float maxVelocity;
double money;
// 實例變數
NSString *name;
}
+ (void)class_method; // 類方法
- (void)instance_method1; // 實例方法
- (void)instance_method2:(int)p1;
@end
類定義具體內容包括:
- 類的聲明,類聲明總是由 @interface 編譯選項開始,由 @end 編譯選項結束;
- 實例(成員)變數的定義(用大括弧包含的);
- 類相關方法的定義:類方法和實例方法;
2.2.2 實例變數和成員變數的理解
實例(Instance)是針對類(class)而言的。實例是指類的聲明; 由此推理,實例變數(Instance Variable) 是指由類聲明的對象。成員變數就是基本類型聲明的變數。
嚴格來說 @interface{}
里定義的實例(成員)變數,它是這個類內部真正的全局變數。然而這個 instance variable 是不對外公開的,因此我們還需要一個對外公開的東西來調用,就是屬性,關鍵字 @property。它其實是告訴大家,我這個類里,有一個變數的 seter/geter 方法。比如,@property NSString* string;就是說,本類里有一個 string/setString 供你們調用。屬性具體的使用後面會講到,這裡就不展開了。
2.3 類實現(Implementation)
實現文件以 .m 為尾碼。實現區塊則包含了公開方法的實現,以及定義私有(private)變數及方法。 以關鍵字 @implementation 作為區塊起頭,@end結尾。
@implementation XBCar {
int private; // 私有成員變數
}
+ (void)class_method {
}
- (void)instance_method1 {
}
- (void)instance_method2:(int)p1{
}
值得一提的是不只 Interface 區塊可定義實例(成員)變數,Implementation 區塊也可以定義實例(成員)變數,兩者的差別在於訪問許可權的不同,Interface 區塊內的實體變數預設許可權為 protected,implementation 區塊的實例(成員)變數則預設為 private,故在 implementation 區塊定義私有成員更匹配面向對象之封裝原則,因為如此類別之私有信息就不需曝露於公開 interface(.h文件)中。在程式開發中我們會發現在實現文件(.m)中,有如下這樣的代碼:
@interface XBCar () // Class Extension
@end
那麼問題來了,為什麼 .h 文件和 .m 文件里各有1個 @interface 它們分別有什麼用呢?
定義文件(.h 文件)裡面的 @interface
,不用說,是典型的頭文件,用來定義(聲明)類的。
實現文件(.m 文件)裡面的 @interface
,在 OC 里叫作 Class Extension,是 .h文件中 @interface
聲明類的補充擴展。但是 .m 文件里的 @interface
,對外是不開放的,只在 .m 文件里可見。
2.4 創建對象
Objective-C 創建對象需通過 alloc 以及 init 兩個消息。alloc 的作用是分配記憶體,init 則是初始化對象。 init 與 alloc 都是定義在 NSObject 里的方法,父對象收到這兩個消息並做出正確回應後,新對象才創建完畢。
XBCar *car = [[XBCar alloc] init];
Objective-C 還可以通過 new 關鍵字來創建對象。
XBCar *blueCar = [XBCar new];
那麼 alloc/init 和 new 有什麼區別呢?首先功能上他兩幾乎是一致的,都是分配記憶體並完成初始化。差別在於,採用 new 的方式創建對象只能採用預設的 init 方法完成初始化,而通過 alloc 的方式可以採用其他定製的初始化方法完成初始化。另外 alloc 分配記憶體的時候使用了 zone。它是給對象分配記憶體的時候,把關聯的對象分配到一個相鄰的記憶體區域內,以便於調用時消耗很少的代價,提升了程式處理速度。
2.5 方法聲明
當你想調用一個方法,你傳遞消息到對應的對象。這裡消息就是方法標識符,以及傳遞給方法的參數信息。發送給對象的所有消息都會動態分發,這樣有利於實現 Objective-C 類的多態行為。也就是說,如果子類定義了跟父類的具有相同標識符的方法,那麼子類首先收到消息,然後可以有選擇的把消息轉發(也可以不轉發)給他的父類。
消息被中括弧( [ 和 ] )包括。中括弧中間,接收消息的對象在左邊,消息(包括消息需要的任何參數)在右邊。例如,給 myArray 變數傳遞消息insertObject:atIndex: 消息,你需要使用如下的語法:
[myArray insertObject:anObj atIndex:0];
為了避免聲明過多的本地變數保存臨時結果,Objective-C 允許你使用嵌套消息。每個嵌套消息的返回值可以作為其他消息的參數或者目標。例如,你可以用任何獲取這種值的消息來代替前面例子裡面的任何變數。所以,如果你有另外一個對象叫做 myAppObject 擁有方法,可以訪問數組對象,以及插入對象到一個數組,你可以把前面的例子寫成如下的樣子:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
雖然前面的例子都是傳遞消息給某個類的實例,但是你也可以傳遞消息給類本身。當給類發消息,你指定的方法必須被定義為類方法,而不是實例方法。你可以認為類方法跟 C++ 類裡面的靜態成員有點像(但是不是完全相同的)。
類方法的典型用途是用做創建新的類實例的工廠方法,或者是訪問類相關的共用信息的途徑。類方法聲明的語法跟實例方法的幾乎完全一樣,只有一點小差別。與實例方法使用減號作為方法類型標識符不同,類方法使用加號( + )。
下麵的例子演示了一個類方法如何作為類的工廠方法。在這裡,arrayWithCapacity 是 NSMutableArray 類的類方法,為類的新實例分配內容並初始化,然後返回給你。
NSMutableArray* myArray = nil; // nil 基本上等同於 NULL
// 創建一個新的數組,並把它賦值給 myArray 變數
myArray = [NSMutableArray arrayWithCapacity:0];
2.6 屬性
屬性(property)用於封裝對象中的數據,iOS 開發中最常用最方便的變數聲明方式,允許我們用點語法來訪問對象的實例變數。
2.6.1 屬性的實質是什麼
@property 是聲明屬性的語法。可以快速為實例變數創建存取器。允許我們通過點語法使用存取器。
屬性(@property) = 實例變數定義 + setter方法 + getter方法。
當我們聲明一個屬性屬性 name
的時候,在編譯階段,編譯器會自動給對象添加一個實例變數 name
和它的存取方法 - (void)setName:(NSString *)name
和 - (NSString *)name
。這個過程由於是在編譯階段自動合成的,所以我們在編輯階段是看不到的。添加實例變數是有一個前提的,就是對象還沒有同名的成員變數,就是如果已經有 _name 了,就不再添加了。我們可以用運行時驗證一下:
- (void)propertyTest {
unsigned int count = 0;
Ivar *varList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *varName = ivar_getName(varList[i]);
printf("成員變數----%s\n", varName);
}
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
SEL methodName = method_getName(methodList[i]);
NSLog(@"方法----%@",NSStringFromSelector(methodName));
}
}
列印的日誌:
成員變數----_name
2021-07-20 16:55:59.492166+0800 001 - Class[772:380998] 方法----propertyTest
2021-07-20 16:55:59.492321+0800 001 - Class[772:380998] 方法----name
2021-07-20 16:55:59.492551+0800 001 - Class[772:380998] 方法----setName:
2021-07-20 16:55:59.492664+0800 001 - Class[772:380998] 方法----viewDidLoad
2.6.2 @synthesize
@synthesize 屬於編譯器指令,用來告訴編譯器要做什麼。
@property (strong,nonatomic) NSString *name;
@synthesize name = myname;
@dynamic name;
@synthesize
關鍵字主要有兩個作用,在 ARC 下已經很少用了。
- 在 MRC 下,
@synthesize name
,用在實現文件中告訴編譯器自動實現實例變數name
的存取(訪問器 getter/setter)方法。不過在 ARC 下就不必了,無論你是否@synthesize name
,編譯器都會自動合成 name 的存取方法。 - 如果你聲明的屬性是 name,系統自動給你添加的成員變數是
_name
,如果你對這個變數名字不滿,可以這樣@synthesize name = myname
;自己給個名字。這樣系統給添加的成員變數就是 myname,而不是_name,但是變數的存取方法沒有變化。不過我建議最好不要這麼辦,因為都按照約定成俗的方式來命名變數,代碼的可讀性較高,大家都理解,所以我建議大家最好不要用這個關鍵字。
2.6.3 @dynamic
@dynamic 關鍵字主要是告訴編譯器不用為我們自動合成變數的存取方法, 我們會自己實現。即使我們沒有實現,編譯器也不會警告,因為它相信在運行階段會實現。如果我們沒有實現還調用了,就會報這個錯誤 '-[ViewController setName:]: unrecognized selector sent to instance 0x10040af10'
。
2.6.4 屬性的特性(關鍵字)
1)屬性特性概述
原子性:
- atomic(預設):atomic 意為操作是原子的,意味著只有一個線程訪問實例變數(生成的 setter 和 getter 方法是一個原子操作)。atomic 是線程安全的,至少在當前的存取器上是安全的。它是一個預設的特性,但是很少使用,因為比較影響效率;
- nonatomic:意為操作是非原子的,可以被多個線程訪問。它的效率比atomic 快。但不能保證在多線程環境下的安全性,開發中常用,所以我們在寫代碼的時候要儘量避非免線程安全的代碼出現;
讀寫許可權(存取器控制):
- readwrite(預設):readwrite 是預設值,表示該屬性同時擁有 getter 和 setter;
- readonly: readonly 表示只有 getter 沒有 setter;
- 有時候為了語意更明確可能需要自定義訪問器的名字;
記憶體管理語義:
-
retain(MRC)/strong(ARC)
記憶體管理語義:強引用
系統預設:ARC 情況下是預設
作用:只能修飾對象。對象會更改引用計數,那麼每次被引用,引用計數都會+1,釋放後都會-1;即使對象本身被釋放了,只要還有對象在引用,就會持有不會造成什麼問題;只有當引用計數為0時,就被dealloc析構函數回收記憶體了。對應變數許可權修飾符為__strong。
-
weak
記憶體管理語義:弱引用
系統預設:否
作用:只能修飾對象。不改變對象的引用計數,當其指向對象被銷毀時,它會自動置為nil;變數許可權修飾符為__weak,常用於容易造成迴圈引用的地方不改變對象的引用計數。
-
assign
記憶體管理語義:賦值
系統預設:MRC 情況下是
作用:主要用於修飾值類型,如 int、float、double 和 CGPoint、CGFloat 等表示單純的複製。還包括不存在所有權關係的對象,比如常見的 delegate。值類型變數的記憶體由編譯器自動管理;修飾對象屬性時,其指向一個對象後,不改變該對象的引用計數。即只引用已創建的對象,而不持有對象;assign 修飾的屬性不持有對象,當其指向對象在別處釋放後,該指針變為懸掛指針也叫野指針。對應的變數許可權修飾符為
__unsafe_unretained
。 -
unsafe_unretained
用來修飾屬性的時候,和 assing 修飾對象的時候是一模一樣的。為屬性設置新值的時候。唯一的區別就是當屬性所指的對象釋放的時候,屬性不會被置為 nil,這就會產生野指針,所以是不安全的。對應的變數許可權修飾符為
__unsafe_unretained
。 -
copy
記憶體管理語義:複製/拷貝
系統預設:否
作用:只能修飾對象。一般情況下屬性會持有該對象的一份拷貝,建立一個索引計數為1的新對象,然後釋放舊對象。copy 一般用在修飾有可變對應類型的不可變對象上,如 NSString, NSArray, NSDictionary。
2)strong 和 copy 修飾屬性的區別
不可變字元串:
@property (nonatomic,strong)NSString* strongedString;
@property (nonatomic,copy)NSString* copyedString;
// 當tempString為不可變字元串時候
NSString *tempString = @"Text";
self.strongedString = tempString;
self.copyedString = tempString;
NSLog(@"tempString : %@ %p %p",tempString, tempString, &tempString);
NSLog(@"strongedString : %@ %p %p",_strongedString, _strongedString, &_strongedString);
NSLog(@"copyedString : %@ %p %p",_copyedString, _copyedString, &_copyedString);
tempString = @"Change OK";
NSLog(@"tempString : %@ %p %p",tempString, tempString, &tempString);
NSLog(@"strongedString : %@ %p %p",_strongedString, _strongedString, &_strongedString);
NSLog(@"copyedString : %@ %p %p",_copyedString, _copyedString, &_copyedString);
// 列印結果:
BaseGrammar[40394:185339] tempString : Text 0x10d858e68 0x7ffee23adbf8
BaseGrammar[40394:185339] strongedString : Text 0x10d858e68 0x7ff634530440
BaseGrammar[40394:185339] copyedString : Text 0x10d858e68 0x7ff634530448
// 改變後:
BaseGrammar[40394:185339] tempString : Change OK 0x10d858ee8 0x7ffee23adbf8
BaseGrammar[40394:185339] strongedString : Text 0x10d858e68 0x7ff634530440
BaseGrammar[40394:185339] copyedString : Text 0x10d858e68 0x7ff634530448
結論:當 tempString 為不可變字元串時
- 不管是 strong 還是 copy 屬性的對象,其指向的地址都是同一個,即為tempString 指向的地址。
- 如果我們換作 MRC 環境,列印 tempString 的引用計數的話,會看到其引用計數值是3,即 strong 操作和 copy 操作都使原字元串對象的引用計數值加了1。
- 當 tempString 的值發生改變時,兩個對象的值也保持原來的值。
可變字元串:
// 可變字元串
NSMutableString *tempString = [NSMutableString stringWithFormat:@"Text"];
self.strongedString = tempString;
self.copyedString = tempString;
NSLog(@"tempString : %@ %p %p",tempString, tempString, &tempString);
NSLog(@"strongedString : %@ %p %p",_strongedString, _strongedString, &_strongedString);
NSLog(@"copyedString : %@ %p %p",_copyedString, _copyedString, &_copyedString);
// 改變tempString的值
[tempString appendString:@"Change OK"];
NSLog(@"tempString : %@ %p %p",tempString, tempString, &tempString);
NSLog(@"strongedString : %@ %p %p",_strongedString, _strongedString, &_strongedString);
NSLog(@"copyedString : %@ %p %p",_copyedString, _copyedString, &_copyedString);
// 列印結果:
BaseGrammar[42008:192582] tempString : Text 0x60000279c960 0x7ffee5983bf8
BaseGrammar[42008:192582] strongedString : Text 0x60000279c960 0x7fe20fe14380
BaseGrammar[42008:192582] copyedString : Text 0xb96aa7441ad7466e 0x7fe20fe14388
// 改變後:
BaseGrammar[42008:192582] tempString : TextChange OK 0x60000279c960 0x7ffee5983bf8
BaseGrammar[42008:192582] strongedString : TextChange OK 0x60000279c960 0x7fe20fe14380
BaseGrammar[42008:192582] copyedString : Text 0xb96aa7441ad7466e 0x7fe20fe14388
結論:當 tempString 為可變字元串時
-
此時 copy 屬性字元串已不再指向 tempString 字元串對象,而是深拷貝了 tempString 字元串,並讓 copyedString 對象指向這個字元串。
-
strongString 與 tempString 是指向同一對象,所以 strongString 的值也會跟隨著改變(需要註意的是,此時 strongString 的類型實際上是NSMutableString,而不是 NSString);而 copyedString 是指向另一個對象的,所以並不會改變。
-
在 MRC 環境下,列印兩者的引用計數,可以看到 tempString 對象的引用計數是2,而 copyedString 對象的引用計數是1。
由上面分析我們能得到一個結論:copy 對於不可變對象是淺拷貝,對於可變對象是深拷貝。在聲明 NSString 屬性時,到底是選擇 strong 還是 copy,可以根據實際情況來定。不過,一般我們將對象聲明為 NSString 時,都不希望它改變,所以大多數情況下,我們建議用 copy,以免因可變字元串的修改導致的一些非預期問題。即對於可變字元串以得到新的記憶體分配,而不只是原來的引用。
3)assign 和 weak 的區別
assign 的特點:
- 修飾基本數據類型和原子類類型。
- 修飾對象類型時,不改變其引用計數。
- 會產生懸掛指針(野指針),用 assign 修飾的對象被釋放之後,assign指針仍指向原對象的地址記憶體,繼續使用 assign 指針訪問原對象,會產生懸掛指針導致記憶體泄漏。
weak 的特點:
- 只能修飾對象。
- 不改變修飾對象的引用計數。
- 所指對象在被釋放之後會自動置為 nil。
4)深淺拷貝
淺拷貝:就是對記憶體地址的複製,讓目標對象指針和原對象指針指向同一片記憶體空間。淺拷貝增加了被拷貝對象的引用計數,並沒有產生新的記憶體分配空間。
深拷貝:就是讓對象指針和原對象指針指向兩片內容相同的記憶體空間。深拷貝沒有增加被拷貝對象的引用計數,產生新的記憶體分配控制項。
總結:
copy:對於可變對象為深拷貝,對於不可變對象為淺拷貝。拷貝出來的是不可變對象。
mutableCopy:始終是深拷貝。拷貝出來的是可變對象。
總的來說在 Objective-C 裡面只有一種情況是淺拷貝,那就是不可變對象的copy,其它的都是深拷貝(包括不可變對象mutableCopy、可變對象的的 copy 和mutableCopy)。
3. 引用關鍵字
關鍵字說明:
-
#import
是 Objective-C 導入頭文件的關鍵字。確保一個頭文件只能被導入一次,這使你在遞歸包含中不會出現問題,所以 #import 比起#include 的好處是不會引起交叉編譯。-
import<>
代表導入系統自帶的框架; -
import""
代表導入我們自己創建的頭文件;
-
-
#include
是C/C++導入頭文件的關鍵字。 -
@class
一般用於聲明某個字元串作為類名使用,它只是聲明瞭一個類名,沒有導入 .h 文件中的內容,不會引起交叉編譯問題。
import 和 @class 的區別總結:
- Import 會包含這個類的所有信息,包括實體變數和方法(.h 文件中),而 @class 只是告訴編譯器,其後邊的聲明的名稱是類的名稱,至於類是如何定義的,後邊再說,這邊不關心。或者說 @class 創建了一個前向引用,就是在告訴編譯器,“相信我,以後你會知道這個類到底是什麼,但是現在,你只需要知道這些就好”,如果有迴圈依賴關係;
- 在頭文件中,一般只需要知道被引用的類的名稱就可以了,不需要知道其內部的實體變數和方法,所以在頭文件中一般使用 @class 來聲明這個名稱是類的名稱,而在實現類裡邊,因為會用到這個引用類的內部的實體變數和方法,所以需要使用 #import 來包含這個被引用類的頭文件;
使用總結:
-
如果不是 C/C++,儘量使用 #import;
-
能在實現文件中 #import,就不在頭文件中 #import;
-
能在頭文件中 @class 實現文件中 #import,就不再頭文件中 #import;