標籤:objective-c objective-c文法 oc
本文的閱讀基本條件:
- 具備C/C++基礎知識,瞭解物件導向特徵
- 閱讀過《Objective-C 2.0 程式設計(第二版)》、《Objective-C 程式設計 第6版》或相關基礎OC書籍
參考資源:
1.《Effective Objective-C2.0》
2. 《Objective-C 2.0 程式設計(第二版)》/《Objective-C 程式設計 第6版》
3. http://www.cnblogs.com/pengyingh/articles/2354709.html
4. http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/1
5. http://www.cocoachina.com/ios/20141218/10679.html
6. http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html
知識要點匯總模式: 提出問題,給出詳解(Q&A)
PS:會以擴充方式延伸介紹一些知識點
問題列表:
Q1. Objective-C特點以及和C++的區別?
Q2. 屬性的特點,和執行個體變數的區別,使用注意事項?
Q3. 類的繼承,協議和分類的概念和使用,以及需要注意的問題?
內容詳情:Q1. Objective-C特點以及和C++的區別?
A: Objective-C(OC)是C的超集,基於C語言加入了物件導向特性和訊息轉寄機制的動態語言(繼承了smalltalk語言物件導向的經典思想)。除編譯器外還需要用運行時(runtime)系統來動態建立類和對象進行訊息發送和轉寄
,而runtime系統是一個用C語言編寫動態連結程式庫(libobjc.A.dylib), 該庫提供了OC語言所需的各種動態特性的支援。
OC採用訊息傳遞(Messaging)即向對象傳遞訊息的方式而非方法調用。如: [receiver message],編譯器並不馬上執行對象的message方法,而是向receiver對象發送一條message訊息,編譯器將其轉化為 obj_msgSend (runtime和物件模型詳細實現參見Foundation架構要點歸納)及其相關函數調用,在程式運行時會根據obj_msgSend 尋找具體方法實現。
簡單的說,即OC程式在編譯時間並未具體確定方法實現,而是在運行時藉助runtime系統實現方法的動態綁定,通過obj_msgSend實現尋找方法的類別,進而調用方法實現,通過runtime系統實現物件導向的多態特性(不同的類有相同的方法名)。
PS: C++ 也是一種對C實現物件導向特性的擴充,可以從物件導向的三個特性(封裝、繼承/派生、多態)角度簡單分析C++和OC的不同
封裝: OC和C++都有自己風格的類結構文法定義方式。OC成員變數預設為protected,屬性是OC的一個特性,用於封裝對象中的資料。C++預設為private。 OC可以通過分類對原始類進行擴充,在不破壞類封裝特性的基礎上對類進行方法添加,但不能增加成員變數(屬性可以)。
繼承: C++可以多繼承,即同時繼承多個父類。 OC只能單繼承,但通過協議有效增加了繼承的靈活性和多樣性,彌補了單繼承的缺點。
多態: 即相同類具有同名函數(方法),但可以通過類別來區分實現對應的函數(方法)。C++和OC不同的是其對象採用函數調用方式,且在編譯階段實作類別和函數的綁定(虛函數的動態綁定機制除外),這點和OC具有明顯的區別,OC是在運行時通過runtime系統來尋找具體類別方法並實現。
而且,C++可以實現函數重載功能,OC沒有該功能。
Q2. 屬性的特點,和執行個體變數的區別,使用要點?
A:屬性(property)是OC一個特性,用於封裝對象中的資料。OC通過定義執行個體變數來儲存物件所需的資料,並通過存取方法(access method,setter,getter)來訪問。上述概念的成型且經由“屬性”這一特性而成為OC 2.0 一部分。
使用“屬性”取代定義傳統的執行個體變數和存取方法讓編譯器自動合成存取方法有利於提高程式效能(可以通過@dynamic關鍵字告訴編譯器不要實現屬性所用的執行個體變數和存取方法)。而引入了點文法(dot syntax)更提高程式可讀性(”.”文法實際上編譯成訊息傳遞模型[receiver message])。
property有一些具有特殊用途的關鍵字(特質),一般分為三類:原子性,存取器控制,記憶體管理
(1) 原子性
atomic(預設):只允許一個線程訪問執行個體變數,安全執行緒效率低下
nonatomic: 可以被多線程訪問,效率高
(2) 讀寫權限
readwrite(預設):當屬性通過@synthesize實現時,編譯器自動產生setter和getter方法
readonly :只有屬性由@synthesize實現時,編譯器才會合成擷取方法,只有 getter沒有setter
(3)記憶體管理
顯示記憶體管理原則
assign(預設):用於實值型別,如 int,float,NSInteger 等表示單純的複製
retain: 在setter方法中,需要對傳入對象進行引用計數+1的操作,即對對象具有所有權,該對象不會被釋放
strong:和retain意思相同,併產生相同代碼,但語意上更能體現對象擁有
weak:setter方法中對傳入對象不進行引用計數+1的操作,即對傳入的對象沒有所有權,當對象引用計數為0時,對象被釋放,聲明執行個體變數指向nil
unsafe_unretained: 和assign相同,但是它適用於“物件類型”,非擁有關係,當目標對象被清除時,該屬性之不被清空(nil, 和weak有區別), 訪問會造成崩潰。
copy: 和strong類似,但區別在於對對象副本擁有所有權而非對象本身,常用於NS String * 類型,用於保護其封裝性。
property使用要點
1. 在implementation中如果不用@synthesize, 則使用屬性時可以通過 _name(編譯器隱藏了 @synthesize name = _name;) 或者self.name , [self name]訪問,點運算子和調用預設getter方法效果一樣。
2. 但如果自訂了getter或者setter方法,則在方法實現中不可以使用 self ,需要用_name對執行個體變數進行訪問,否則 會進入死迴圈, 下面的Demo代碼中有體現
3. 如果使用@synthesize,則可以直接使用變數 name
// ------AClass.h------#import <Foundation/Foundation.h>@interface AClass : NSObject// property@property (nonatomic, strong) NSString *aName;// print- (void)print;// self-defined getter & setter// 主要和 自訂getter方法作比較,通過擷取的限制條件- (NSString *)aName;@end// ------AClass.m------@implementation AClass//@synthesize aName = _aName; // 系統隱藏//@synthesize aName;// print- (void)print{ // 調用執行個體變數 NSLog(@"內部Print方法直接調用執行個體變數%@", _aName); // 調用自訂getter函數 NSLog(@"內部Print方法通過存取方法擷取屬性%@", self.aName); NSLog(@"內部Print方法通過存取方法擷取屬性%@", [self aName]);}// getter- (NSString *)aName{ if ([_aName isEqualToString:@"King"]) { // self.aName 會進入死迴圈 return _aName; } else { return @"Not King~"; }}@end// ------main.m------int main(int argc, char const *argv[]){ @autoreleasepool { AClass *a = [[AClass alloc] init]; a.aName = @"King"; NSLog(@"通過點運算子實現屬性訪問: %@", a.aName); // 調用存取方法的setter [a setAName:@"King2"]; NSLog(@"通過存取方法實現屬性訪問: %@", [a aName]); NSLog(@"----------------------------------------"); [a print]; return 0; }}
OutPut:通過點運算子實現屬性訪問: King通過存取方法實現屬性訪問: Not King~----------------------------------------內部Print方法直接調用執行個體變數King2內部Print方法通過存取方法擷取屬性Not King~內部Print方法通過存取方法擷取屬性Not King~`
Q3. 類的繼承,協議和分類的概念和使用,以及需要注意的問題?
零散知識點匯總(Maybe you need it or learn something important):
[繼承] OC是物件導向語言,繼承方式和C++有明顯區別即單繼承,而沒有C++的多繼承。但多協議彌補了不能多繼承的缺陷。可以通過子類繼承的方式
a.增加新的方法/或執行個體變數,
b.類的特別介面封裝,
c. 覆寫 一個或者多個方法改變類的預設行為
[多態,動態類型和動態綁定]
多態: 不同的類對象定義相同名稱
動態類型id類型: 直到運行時才確定所屬對象的類,通用物件類型,定義為:
typedef struct objc_object *id;
(id 變數不能使用點運算子)
動態綁定:運行時才確定實際要調用的對象方法
[分類 - Category]
提供一種簡單方式將類定義模組化到相關方法的組或分類中。一般,如果主類為B,則分類標頭檔和實現檔案名稱分別為:B+subClass.h, B+subClass.m
從調用角度,分類就是對原始類的一種擴充
從程式角度,分類可讀性更強
分類檔案格式
#import “SuperClassName.h”
@interface Fraction (MathOp)
@end
類擴充:
如果建立一種未命名的分類,則稱為類的擴充,在有命名的分類中時不允許的。和有命名分類不同,未命名分類主要在主實現區實現,而非分離實現地區。未命名分類擴充的方法和屬性或者執行個體變數只能由該類本身私人。
Category 使用情境:
a. 已經定義的類需要添加新的方法功能
b. 一個類中包括許多種不同類型方法,需要不同團隊實現,有利於任務分配
注意問題:
a. Category可以訪問原始類方法,但不能添加變數,添加變數可以通過建立子類(繼承)方式來實現
b. Category可以重載原始方法,會導致不能訪問原來的方法,建立子類實現 重載 覆寫
c. 和普通介面有所區別,分類實現檔案中可以不必實現所有聲明的方法
- [協議]
協議 即多個類共用一個方法列表,協議中列出的方法沒有相應的實現,通過文檔說明,使用協議中的方法可以通過文檔說明進行實現。
一系列不屬於任何類的方法列表,其中聲明的方法可以被任何類實現。這種模式稱為代理模式。在不同情境中實現不同模式。Apple採用了大量的代理模式來實現 MVC中 View和 Controller 的解耦。
最常用的是委託代理模式, Cocoa架構中大量採用這種模式實現 資料和UI的分離: UIView產生的所有事件 都是通過委託的方式 交給 Controller 完成
架構中尾碼為Delegate都是 Protocol
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
(可以放在單獨的.h標頭檔中定義,也可以放在相關類的h檔案中)
使用:
@interface AddressBook : NSObject
PS: 1. 沒必要在介面部分聲明協議的方法,但要在實現部分定義這些方法
2. 可以通過 如下代碼來檢查一個對象是否遵循某項協議
id currentObject;
if ([currentObject conformsToProtocol:@protocol(Drawing)] == YES) {
}
3. 也可以使用 respondsToSelector:@selector() 來檢查是否實現了可選的方法
代理(delegation)
-(BOOL)respondsToSelector:selector 被廣泛用於實現委託方法定義
4. id currentObject; 藉助編譯器來檢查變數一致性
5. 和類名一樣,協議名必須唯一
6. 協議是可以繼承的,具有繼承的屬性,如果協議B繼承了協議A,則實現協議B 需要實現A和B的所有方法
- [代理] delegation
協議是一種兩個類之間的介面定義,定義了協議的類可以看作是將協議定義的方法代理給實現他們的類。 (定義了協議的類稱為 代理)
//******測試例子: (涵蓋之前一些內容,算是整合一下思路)//******MTProtocol.h 檔案,為定義的協議@protocol Printing// 預設@required- (void)printProtocol;@optional- (void)printProtocolOptional;@end//******A.h 檔案,包含A類定義,採用Printing協議#import <Foundation/Foundation.h>#import "MTProtocol.h"// 介面處定義執行個體變數, 採用協議@interface A : NSObject <Printing>{ int x;}@property (nonatomic, assign) int y;- (void)initXY;- (void)printXY;@end//******A.m 檔案,包含A類實現,注意執行個體變數和屬性初始化的問題(這裡沒有重載init方法,所以不需要模版 self = [super init] 。。。)#import "A.h"@implementation A- (void)initXY{ x = 1; self.y = 2;}- (void)printXY{ NSLog(@"This is Class A : %i, %i", x, self.y);}- (void)printProtocol{ NSLog(@"I am printProtocol Method form Printing!");}- (void)printProtocolOptional{ NSLog(@"I am printProtocolOptional Method form Printing!");}@end//******A+Op.h A類的分類介面,用於擴充(感覺就是擴充,一方面通過子類,一方面在原類中進行分類擴充,或者 合成類 這個比較奇葩)#import "A.h"@interface A (Op)- (void)printCategory;@end//******A+Op.m A類的分類實現#import "A+Op.h"@implementation A (Op)- (void)printCategory{ NSLog(@"Op Category for Class A!");}@end//******B.h B類介面,A 類子類,在B類中我們嘗試 未命名分類,即私人方法擴充#import "A.h"@interface B : A- (void)initXY;- (void)printXY;@end//******B.m B類實現,注意未命名分類#import "B.h"@interface B ()- (void)printNanNameCategory;@end@implementation B- (void)initXY{ x = 10; // A類中介面處定義了執行個體變數,可以被繼承 super.y = 20; // 屬性或者實現部分聲明的變數為私人執行個體變數,通過合成取值方法擷取}- (void)printXY{ NSLog(@"This is Class B : %i, %i", x, self.y);}- (void)printNanNameCategory{ NSLog(@"I am Nan Name Category for B!");}@end//******main.m 實現,注意未命名分類#import "A+Op.h"#import "B.h"// ---------- main ----------int main(int argc, const char * argv[]) { @autoreleasepool { A *a = [[A alloc] init]; B *b = [[B alloc] init]; [a initXY]; [b initXY]; // 定義動態類型 id id tmp = a; [tmp printXY]; [tmp printCategory]; [tmp printProtocol]; [tmp printProtocolOptional]; tmp = b; [tmp printXY]; [tmp printProtocolOptional]; // 測試 tmp 是否是A類別 //[tmp isKindOfClass:[A class]] ? NSLog(@"Yes") : NSLog(@"No"); // 測試A 類是否包含printXY方法 //[A instancesRespondToSelector:@selector(printXY)] ? NSLog(@"Yes") : NSLog(@"No"); // 測試對象a 是否包含init方法 //[a respondsToSelector:@selector(init)] ? NSLog(@"Yes") : NSLog(@"No");}}
結果分析
This is Class A : 1, 2
Op Category for Class A!
I am printProtocol Method form Printing!
I am printProtocolOptional Method form Printing!
This is Class B : 10, 20
I am printProtocolOptional Method form Printing!
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Objective-C 2.0 基礎要點歸納