iOS - OC 面向对象语法
1、類
1)根類:因為類 NSObject 是層次結構的最頂層,因此稱為根類。
- 可以將類稱為子類(subclass)和父類(superclass),也可以將類稱為子類和超類。
2)分類/類別(category):允許以模塊的方式向現有類定義添加新的方法(默認不能添加實例變量)。擴展自己或他人以前實現的類,使它適合自己的需要。
分類的名稱括在類名之后的一對圓括號“( )”中。
@interface QCStudent (Print)@end@implementation QCStudent (Print)@end分類文件名使用符號“+”來分隔類和分類的名字(Xcode 會自動生成)。
QCStudent+Print.mQCStudent+Print.h分類用它可以將類的定義模塊化到相關方法的組或分類中。它還提供了擴展現有類定義的簡便方式,并且不必訪問類的源代碼,也無需創建子類。
分類可以覆寫該類中的另一個方法,但是通常認為這種做法是拙劣的設計習慣。一個類可以擁有多個分類。使用分類添加新方法來擴展類不僅會影響這個類,同時也會影響它的所有子類。分類為現有類添加新方法可能對你有用,但它們可能和該類的原始設計或意圖不一致。對象/分類命名對必須是唯一的。
iOS 開發中,分類默認不允許添加屬性。但是如果在自己開發的框架中,希望在分類中動態添加屬性,可以通過 OC 運行時的關聯對象功能添加,詳見 iOS - OC Category 分類。
3)類的擴展:有一種特殊的情況是創建一個未命名的分類,并且括號“( )”之間不指定名字。
這種特殊的語法定義稱為類的擴展。定義一個像這樣的未命名的分類時,可以通過定義額外的實例變量和屬性來擴展類,這在有命名的分類中是不允許的。
@interface QCStudent ()@end@implementation QCStudent@end未命名的分類中聲明的方法需要在主實現區域實現,而不是在分類的的實現區域。
未命名的分類的方法都是私有的。如果需要寫一個類,而且數據和方法僅供這個類本身使用,未命名分類比較合適。
4)抽象類:有時創建類只是為了更容易創建子類。因此,這些類名為抽象類,或等價的稱為抽象超類。在該類中定義方法和實例變量,但不希望任何人從這個類創建實例。
5)類與類之間的關系:
- 類的包含(復合):
- 一個類中有另一個類的 @class 聲明。一個類中包含有 #import 另一個類的 .h 頭文件。
- 一般在 .h 頭文件中用 @class 聲明一個類,在 .m 文件中使用到該類時再在 .m 文件中包含該類的 .h 頭文件。
- 類的包含(復合):
6)類的加載:
1>、類加載時自動調用方法:+ (void)load;
+ (void)load {NSLog(@"%@",@"Student ------------- load");}2>、類首次使用時自動調用方法:+ (void)initialize;
+ (void)initialize {NSLog(@"%@",@"Student ------------- initialize");}3>、使用 %@ 打印對象時會自動調用方法:- (NSString *)description;
// description 覆寫- (NSString *)description {return [NSString stringWithFormat:@"age : %d, name : %@", self.age, self.name];}
7)是在子類中使用的實例變量,必須先在接口部分聲明,而不是在實現部分聲明。在實現部分聲明和合成的實例變量是私有的,子類中不能直接訪問,需要明確定義或合成取值方法,才能訪問實例變量的值。
8)類前綴:
使用 Objective-C 開發 iOS 程序時,最好在每個類名前面加一個前綴,用來標識這個類。
- 目的是防止 N 個人開發了一樣的類,出現沖突。
- 比如 Jake Will、Kate Room 在同一個項目中都各自開發了個 Button 類,這樣的程序是不能運行起來的。
- 解決方案:Jake Will 的類名叫做 JWButton,Kate Room 的類名叫做 KRButton。
類前綴的設置
Xcode 6 之前:
在創建項目時設置。
Xcode 6 之后:
創建完項目后設置。
設置完后,再創建新的文件時會自動添加上設置的類前綴。
2、對象、方法
1)類的獨特存在就是一個實例(對象),對實例執行的操作稱為方法。
2)合成對象:可以定義一個類包含其它類的一個或多個對象,這個新類的對象就是所謂的合成對象,因為它是由其它對象組成的。
- 作為創建子類的代替方法,可以定義一個新類,它包含要擴展類的實例變量。然后,只需在新類中定義適合該類的方法。
3)實例初始化
1>、初始化方式:
alloc :方法保證對象的所有實例變量都變成初始狀態。 創建對象。init :方法用于初始化類的實例變量。 初始化對象。new :可以將 alloc 和 init 的結合起來。 創建并初始化對象。- 但用兩步來實現創建和初始化的方式通常更好,這樣可以在概念上理解正在發生兩個不同的事件:首先創建一個對象,然后對它初始化。
2>、構造方法:實例初始化常見的編程習慣是類中所有初始化方法都以 init 開頭。如果希望在類對象初始化時做一些事情,可以通過重載 init 方法達到這個目的。下面是重載 init 方法的一個標準模板。
不帶參數:- (instancetype)init {self = [super init];if (self) {// 初始化代碼}return self; }帶參數:- (instancetype)initWithAge:(int)age andNo:(int)no { self = [super init];if (self) {_age = age;_no = no; }return self;}類方法:+ (instancetype *)studentWithAge:(int)age {Student *stu = [[Student alloc] init];stu.age = age;return stu;}執行父類的初始化方法,使得繼承的實例變量能夠正常的初始化。如果父類初始化成功,返回的值將是非空的。self 用來指明對象是當前方法的接收者。必須將父類 init 方法的執行結果賦值給 self,因為初始化過程改變了對象在內存中的位置(意味著引用將要改變)。
特殊類型 instancetype 表明從 init 方法返回的類型與它的初始化類(也就是初始化消息的接收者)相同。 init 被定義為返回 instancetype 類型,這是編寫可能被繼承的類 init 方法的一般規則。當編譯器遇見 instancetype 作為返回類型,它就知道返回的類型是發送消息的對象。
4)消息:請求一個類或實例來執行某個操作時,就是在向它發送一條消息,消息的接受者稱為接收者。
- OC 采用特定的語法對類和實例應用方法:[類/實例 方法];
- 5)類方法(靜態方法):是對類本身執行某些操作的方法。
實例方法(動態方法):對類的實例執行一些操作。
創建方法名時,參數名實際上是可選的,參數名可以省略。如:- (int)set :(int)name :(int)age;
方法(函數)不返回任何值時,無需在方法的末尾執行一條 return 語句。或者也可以執行一條不帶任何指定值的 return 語句:return;。
6)重寫(覆蓋):在子類中新建一個與父類中的方法同名的方法。子類中的新方法必須具有相同的返回類型,并且參數的數目和覆寫的方法相同。
- 如果需要來擴展繼承的方法。子類中包含對 if (self = [super init]) 的判斷。
7)重載:在類中,相同名字不同參數的方法的寫法有一個專門的術語來描述,叫做重載。
8)懶加載
- 對象在用到時再去加載,而且只加載一次。加載的數據比較大時可以節省內存。
一般重寫 getter 方法實現對象的懶加載。
@property (strong, nonatomic) NSArray *shops;- (NSArray *)shops {// 加載數據if (_shops == nil) {NSString *filePath = [[NSBundle mainBundle] pathForResource:@"shops" ofType:@"plist"];_shops = [NSArray arrayWithContentsOfFile: filePath];}return _shops;}
3、數據封裝(實例變量)
1)數據封裝:將實例變量隱藏起來的這種做法實際上涉及一個關鍵概念 --“數據封裝”。
- 不能在類的外部編寫方法直接設置或獲取實例變量的值,而需要編寫設置方法和取值方法來設置和獲取實例變量的值,這便是數據封裝的原則。
- 必須通過使用一些方法來訪問這些通常對“外界”隱藏的數據,這種做法集中了訪問實例變量的方式,并且能夠阻止其它一些代碼直接改變實例變量的值。如果可以直接改變,會讓程序很難跟蹤、調試和修改。
2)實例變量的定義作用域
@public 全局都可以訪問,實例對象可以使用符號 “->” 直接訪問實例變量。 @protected 只能在類內部和子類中訪問 (訪問器方法 默認) 。@private 只能在類內部訪問 (合成取值方法 默認)。@package 常用于框架類的實例變量,同一包內能用,跨包就不能訪問。3)訪問器方法(accessor):取值方法和設值方法通常稱為訪問器方法。通常實例變量聲明時以下畫線( _ )字符開頭,此實例變量默認為保護(@protected)的。在類內部和子類中都可以訪問。
設值方法(setter):設置實例變量值的方法通常總稱為設值方法。定義時在實例變量名前加上 set。如:
// ARC- (void)setAge:(NSNumber *)age {_age = age;}// MRC- (void)setAge:(NSNumber *)age {if (_age) { [_age release]; } _age = [age retain];}取值方法(getter):用于檢索實例變量值的方法叫做取值方法。定義時直接使用實例變量名。如:
- (NSNumber *)age {return _age;}
4)合成取值方法:通常實例變量聲明時不以下畫線( _ )字符開頭,以字母開頭,并且此實例變量是私有(@private)的。只能在類內部訪問。
在接口部分中使用 @property 指令標識屬性,聲明實例變量的 setter 和 getter 方法。 - 如:@property int numerator, denominator;
在實現部分中使用 @synthesize 指令標識屬性,實現實例變量的 setter 和 getter 方法。 - 如:@synthesize numerator, denominator;
1>、如果使用了 @property 指令,就不需要在實現部分聲明相應的實例變量。當然也可以再聲明相應的實例變量,但是那不是必須要做的,編譯器會有一些提示。
- 可以不使用 @synthesize 指令,使用 @property 指令就足夠了,編譯器會自動為你生成 setter 和 getter 方法(聲明并實現),但是注意如果你不使用 @synthesize ,那么編譯器聲明的實例變量會以下劃線( _ )字符作為其名稱的第一個字符。
2>、@property 的修飾
在不寫任何修飾時,Xcode 會自動生成標準的 setter 和 getter 方法,寫修飾時 Xcode 會自動生成帶內存管理的 setter 方法,標準 getter 方法。
參數分類:
讀寫屬性:readwrite/readonlysetter 處理:assign/retain/copy原子性:atomic/nonatomic方法名:setter = method / getter = method引用型:strong/weak可選性:nonnull/nullable/null_unspecified/null_resettable // Xcode 7 新增特性readwrite:可讀寫,生成 setter 和 getter 方法。默認。readonly :只讀,只生成 getter 方法。assign :修飾普通類型,在 setter 方法中直接賦值。默認。簡單賦值,不更改引用計數。如:@property (nonatomic, assign)int age;retain :修飾 OC 對象,在 setter 方法中 release 舊值,retain 新值。釋放舊的對象,將舊對象的值賦予輸入對象,再提高輸入對象的引用計數為 1。如:@property (nonatomic, retain)Dog *dog;copy :修飾 NSString 類型,在 setter 方法中 release 舊值,copy 新值。建立了一個相同的對象,地址不同(retain:指針拷貝 copy:內容拷貝)。如:@property (nonatomic, copy)NSString *name;atomic :原子性,默認。是 OC 使用的一種線程保護技術,防止在寫入未完成的時候被另外一個線程讀取,造成數據錯誤。給 setter 和 getter 方法加鎖,保證多線程安全。nonatomic:非原子性,禁止多線程,變量保護,提高性能。不給 setter 和 getter 方法加鎖,執行相對快點。setter = method:指定 setter 方法的方法名。 如:@property (nonatomic, setter = setIsRich)BOOL rich; 將 rich 的 setter 方法重命名為 setIsRich 。getter = method:指定 getter 方法的方法名。 如:@property (nonatomic, getter = isRich)BOOL rich; 將 rich 的 getter 方法重命名為 isRich 。strong :強引用,在 OC 中對象默認都是 strong。(ARC 下的)和(MRC)retain 一樣 (默認)。viewController 對根視圖是強引用,view addSubviews 方法是向數組中添加子視圖,數組會對子視圖強引用。weak :弱引用,weak 的作用,一旦沒有強引用,會被立即釋放。(ARC 下的)和(MRC)assign 一樣。蘋果從 StoryBoard 拖線默認是 weak。weak 當指向的內存釋放掉后自動 nil 化,防止野指針。nonnull :不可為空nullable :可以為空null_unspecified:不確定是否可以為空(極少情況)null_resettable :set 方法可以為 nil,get 方法不可返回 nil,只能用在屬性的聲明中。
5)點運算符(點語法):訪問的是方法(setter/getter 方法),不是實例變量。
合成取值方法中可以使用點運算符訪問屬性,也可以對自定義的方法使用點運算符,如語句 myFraction.print ,并未考慮編碼風格是否良好。
點運算符通常用在屬性上,用于設置或取得實例變量的值。做其它工作的方法通常不是由點運算符執行的,而是使用傳統的方括號形式的消息表達式作為首選的語法。
6)尖運算符(->):當實例變量定義為 @public 類型時,實例對象可以使用符號 “->” 直接訪問實例變量。 如:car -> _speed = 80; int a = car -> _speed;
- 7)局部變量:是基本的 C 數據類型,并沒有默認的初始值,所以在使用前要先賦值。
局部對象變量:默認初始值為 nil 。
- 方法的參數名也是局部變量。執行方法時,通過方法傳遞的任何參數都被復制到局部變量中。因為方法使用參數的副本,所以不能改變通過方法傳遞的原值。
- 如果參數是對象,可以更改其中的實例變量值。當你傳遞一個對象作為參數時,實際上是傳遞了一個數據存儲位置的引用。
靜態變量:在局部變量聲明前加上關鍵字 static ,可以使局部變量保留多次調用一個方法所得的值。靜態變量的初始值為 0 。
- 很多情況下想要將變量定義為全局變量,但不是外部變量,可以在包含這個特定類型實現的文件中將這個變量定義為 static 。
外部變量:在方法外定義的變量不僅是全局變量,而且是外部變量。
使用外部變量時,必須遵循下面這條重要原則:變量必須定義在源文件中的某個位置。即在所有的方法和函數之外定義變量,并且前面不加關鍵字 extern 。在所有的函數之外聲明變量,在聲明前面加上關鍵字 extern 。
處理外部變量時,變量可以在許多地方聲明為 extern ,但是只能定義一次。
4、繼承、多態
1)繼承的概念作用于整個繼承鏈。
類的每個實例(對象)都擁有自己的實例變量,即使這些實例變量是繼承來的。
繼承通常用于擴展一個類。不能通過繼承刪除或減少方法。
為什么需要創建子類:(1)希望繼承一個類的函數,也需加入一些新的方法和/或實例變量。(2)希望創建一個類的特別版本。(3)希望通過覆寫一個或多個方法 來改變類的默認行為。
2)多態:使不同的類共享相同方法名稱的能力稱為多態。能夠使來自不同類的對象定義相同名稱的方法。
- 3)動態類型:id 類型的對象。在運行時而不是編譯時確定對象所屬的類,能使程序直到執行時才確定對象所屬的類。
- 靜態類型:將一個變量定義為特定類的對象時,使用的是靜態類型。靜態指的是對存儲在變量中的對象類型進行顯示的聲明。
動態綁定:在運行時而不是編譯時確定對象需要調用的方法,能使程序直到執行時才確定實際要調用的對象方法。
- OC 系統總是跟蹤對象所屬的類。
id 類型的對象先判定對象所屬的類(動態類型),然后在運行時確定需要動態調用的方法,而不是在編譯的時候(動態綁定)。
為什么還要關心靜態類型:(1)它能更好的在程序編譯階段而不是運行時指出錯誤。(2)它能提高程序的可讀性。
如果使用動態類型來調用一個方法,需要注意一下規則:如果在多個類中實現名稱相同的方法,那么每個方法都必須符合各個參數的類型和返回值類型,這樣編譯器才能為消息表達式生成正確的代碼。
處理動態類型的方法:
以下總結了 NSObject 類所支持的一些基本方法,其中,class-object 是一個類對象(通常是由 class 方法產生的),selector 是一個 SEL 類型的值(通常是由 @selector 指令產生的)。
- (BOOL)isKindOfClass:class-object // 對象是不是 class-object 或其子類的成員- (BOOL)isMemberOfClass:class-object // 對象是不是 class-object 的成員+ (BOOL)isSubclassOfClass:class-object // 某個類是否是指定類的子類- (BOOL)respondsToSelector:selector // 對象是否能夠響應 selector 所指定的方法+ (BOOL)instancesRespondToSelector:selector // 指定的類實例是否能響應 selector- (id)performSelector:selector // 應用 selector 指定的方法- (id)performSelector:selector withObject:object // 應用 selector 指定的方法,傳遞參數 object- (id)performSelector:selector withObject:object1 withObject:object2 // 應用 selector 指定的方法,傳遞參數 object1 和 object2[Square class] // 從名為 Square 的類中獲得類對象[mySquare class] // 知道對象 mySquare 所屬的類@selector(alloc) // 為名為 alloc 的方法生成一個 SEL 類型的值。
4)消除 performSelector: 方法警告
#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks"// performSelector: 方法#pragma clang diagnostic pop
5、協議、代理
1)協議:是多個類共享的一個方法列表。協議中列出的方法沒有相應的實現,計劃由其他人來實現。協議中列出的方法,有些是可以選擇實現,有些是必須實現。
1>、如果你定義了自己的協議,那么不必由自己實現它。但是,這就告訴其他程序員,如果要采用這項協議,則必須實現這些方法。這些方法可以從超類繼承。
協議不引用任何類,它是無類的。
分類也可以采用一項協議。
2>、定義一個協議很簡單:只要使用 @protocol 指令,后面跟上你給出的協議名稱。定義一項協議時,可以擴展現有協議的定義。
@protocol PlayerDelegate <NSObject>- (void)end;@end3>、協議的修飾
@optional:該指令之后列出的所有方法都是可選的。@required:該指令之后列出的所有方都是必須實現的,默認。由于 OC 是弱語法,雖然字面上是必須,但編譯器并沒有強求實現。4>、協議的聲明
@protocol protocol-name5>、協議的檢查
// 檢查一個對象是否遵守某項協議。- (BOOL)conformsToProtocol:(Protocol *)aProtocol;// 用于獲取一個協議名稱,并產生一個 Protocol 對象,conformsToProtocol: 方法期望這個對象作為它的參數。@protocol(Drawing)// 檢查對象是否能夠響應 selector 所指定的方法。- (BOOL)respondsToSelector:selector// 為名為 alloc 的方法生成一個 SEL 類型的值。@selector(alloc)
2)非正式協議:實際上是一個分類,列出了一組方法但并沒有實現它們。非正式協議通常是為根類定義的,有時,非正式協議也稱為抽象協議。
聲明非正式協議的類自己并不實現這些方法,并且選擇實現這些方法的子類需要在它的接口部分重新聲明這些方法,同時還要實現這些方法中的一個或多個。
指令 @optional 添加到 OC 2.0 語言中,用于取代非正式協議的使用。
3)代理:協議也是一種兩個類之間的接口定義。定義了協議的類可以看作是將協議定義的方法代理給了實現它們的類。
- 代理設計模式的作用:
- 1、A 對象監聽 B 對象的一些行為,A 成為 B 的代理
- 2、B 對象想告訴 A 對象一些事情,A 成為 B 的代理
- 代理設計模式的總結:
- 1、如果你想監聽別人的一些行為,那么你就要成為別人的代理
- 2、如果你想告訴別人一些事情,那么就讓別人成為你的代理
- 代理設計模式的開發步驟:
- 1、擬一份協議(協議名字的格式:控件名 + Delegate),在協議里面聲明一些代理方法(一般代理方法都是 @optional)
- 2、聲明一個代理屬性:@property (nonatomic, weak) id delegate;
- 3、在內部發生某些行為時,調用代理對應的代理方法,通知代理內部發生什么事
- 4、設置代理:xxx.delegate = yyy;
- 5、yyy 對象遵守協議,實現代理方法
- 代理設計模式的作用:
6、為什么 Objective-C 的方法調用要用方括
為什么 Objective-C 的方法調用要用方括號 [obj foo],而不是別的語言常常使用的點 obj.foo ?
首先要說的是,Objective-C 的歷史相當久遠,如果你查 wiki 的話,你會發現:Objective-C 和 C++ 這兩種語言的發行年份都是 1983 年。在設計之初,二者都是作為 C 語言的面向對象的接班人,希望成為事實上的標準。最后結果大家都知道了,C++ 最終勝利了,而 Objective-C 在之后的幾十年中,基本上變成了蘋果自己家玩的玩具。不過最終,由于 iPhone 的出現,Objective-C 迎來了第二春,在 TOBIE 語言排行榜上,從 20 名開外一路上升,排名曾經超越過 C++,達到了第三名(下圖),但是隨著 Swift 的出現,Objective-C 的排名則一路下滑。
Objective-C 在設計之初參考了不少 Smalltalk 的設計,而消息發送則是向 Smalltalk 學來的。Objective-C 當時采用了方括號的形式來表示發送消息,為什么沒有選擇用點呢?我個人覺得是,當時市面上并沒有別的面向對象語言的設計參考,而 Objective-C 「發明」了方括號的形式來給對象發消息,而 C++ 則「發明」了用點的方式來 “發消息”。有人可能會爭論說 C++ 的「點」并不是真正的發消息,但是其實二者都是表示「調用對象所屬的成員函數」。
另外,有讀者評論說使用方括號的形式是為了向下兼容 C 語言,我并不覺得中括號是唯一選擇,C++ 不也兼容了 C 語言么?Swift 不也可以調用 C 函數么?
最終,其實是 C++ 的「發明」顯得更舒服一些,所以后來的各種語言都借鑒了 C++ 的這種設計,也包括 Objective-C 在內。Objective-C 2.0 版本中,引入了 dot syntax,即:
a = obj.foo 等價于 a = [obj foo]obj.foo = 1 則等價于 [obj setFoo:1]Objective-C 其實在設計之中確實是比較特立獨行的,除了方括號的函數調用方式外,還包括比較長的,可讀性很強的函數命名風格。
我個人并不討厭 Objective-C 的這種設計,但是從 Swift 語言的設計來看,蘋果也開始放棄一些 Objective-C 的特點了,比如就去掉了方括號這種函數調用方式。
所以,回到我們的問題,我個人認為,答案就是:Objective-C 在 1983 年設計的時候,并沒有什么有效的效仿對象,于是就發明了一種有特點的函數調用方式,現在看起來,這種方式比點操作符還是略遜一籌。
大多數語言一旦被設計好,就很難被再次修改,應該說 Objective-C 發明在 30 年前,還是非常優秀的,它的面向對象化設計得非常純粹,比 C++ 要全面得多,也比 C++ 要簡單得多。
轉載于:https://www.cnblogs.com/QianChia/p/5780671.html
總結
以上是生活随笔為你收集整理的iOS - OC 面向对象语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一章 概率论的基本概念
- 下一篇: [hackinglab][CTF][基础