1.前言
上一篇已經介紹了App Delegate、View Controller的基本概念,除此之外,分別利用storyboard和純代碼創建了第一個Xcode的工程,並對不同方式搭建項目進行了比較。這一篇文章,我准備為大家介紹一下奇葩的Objective-C語言的語法和使用。這篇文章主要講Objective-C的理論概念。
2.學習目標
2.1 Objective-C語言面向對象的特性
與C++類比學習,聲明定義一個MyObject類,並創建這個類的實例。
2.2 Objective-C語言常見語法概念概覽
介紹類,方法,分類,擴展,協議,消息機制,動態特性selector,block,ARC等概念
本文關於Objective-C整理總結的內容比較多,不需要完全掌握,混個眼熟就成。
3.奇葩的Objective-C語言
1)對於已經有開發經驗的開發者而言,初次接觸Objective-C的時候覺得根本不能接受,甚至有種畏懼,oc奇葩的語法簡直顛覆了自己的世界觀,因為出現了莫名其妙的符號,如, + - [ ]。但是,不要怕,我們慢慢來了解這到底是毛線。從名字即可看出,Objective-C是一個面向對象的編程語言,也就是它具有繼承、封裝、多態的語言特性。
2)那對於沒有任何編程語言的童鞋,如何理解面向對象的編程呢?
對於沒有任何編程經驗的童鞋,我想這樣解釋,要弄清楚兩個問題:i)編程到底在做什么?ii)什么是面向對象的編程?
i)通俗的語言講,編程就是在和計算機說話,用計算機明白的語言跟他交流,讓他幫你做事。(就好像你跟外國人交流一樣,你想跟人家交朋友,總得用人家懂得語言吧~)
ii)“面向對象編程”,首先只是一個名詞,實質是一種抽象的概念,主要是將一類待處理的數據及數據對應的操作集合化,寫(封裝)在一起。比如,一個人的屬性有,身高、體重、年齡、性別,人可以做的事情有吃飯、睡覺、打豆豆~所有具有這些特征的都可以稱之為人這一類。這便是面向對象的編程。(如果看到這里還不明白,那還是要多寫程序,慢慢接受了新的概念有了固化的思維模式,就能理解並應用了)
學習的時候可以多種語言比較學習。我選擇用C++和Objective-C進行類比,來聲明定義一個MyObject類,比較異同點,了解各自面向對象編程的方法。(為何選擇C++?因為,1.C++有指針的概念;2.C++是面向對象的語言。另,指針就是內存的地址。)
3.1 先看一下C++中聲明定義類是如何編寫的
3.1.1 C++程序源碼
#include <iostream> using namespace std; class MyObject { public: MyObject(); ~MyObject(); void display(); void setValue(int x); int getIndex(); int getKey(); static void classMethod(); private: int index; int key; }; MyObject::MyObject() { cout<<"construct"<<endl; } MyObject::~MyObject() { cout<<"destruct"<<endl; } void MyObject::setValue(int x) { this->index = x; this->key = 1<<(x+2); } void MyObject::display() { cout<<"index == "<<this->index<<endl; cout<<"key == "<<this->key<<endl; } int MyObject::getKey() { return this->key; } int MyObject::getIndex() { return this->index; } void MyObject::classMethod() { cout<<"this is static (or class) method"<<endl; } int main(int argc, const char * argv[]) { // insert code here... std::cout << "Hello, World!\n"; MyObject *object = new MyObject(); MyObject::classMethod(); object->setValue(3); object->display(); int c = object->getIndex() + object->getKey(); object->MyObject::~MyObject(); printf("c = %d\n", c); MyObject object2; object2.setValue(2); object2.display(); return 0; }
3.1.2 控制台打印信息
Hello, World!
construct
this is static (or class) method
index == 3
key == 32
destruct
c = 35
construct
index == 2
key == 16
destruct
Program ended with exit code: 0
3.1.3 代碼解釋:
以上代碼利用C++聲明定義了MyObject類
1)定義了公有方法方法:
- 構造函數MyObject()
- 析構函數~MyObject()
- 打印index和key的方法display()
- 設置私有變量方法setValue(int x)
- 獲得index值方法getIndex()
- 獲得key方法getKey()
- 類方法classMethod()
2)定義了私有成員變量:
- index和key
3.1.4 特別注意
- 構造函數是創建實例時調用的,在這個方法中可以為實例進行初始化操作;
- 析構函數是銷毀實例對象是被系統調用的,這里可以將對象中的指針對象都delete掉;
- 不管是否使用new關鍵字創建對象,系統都會自動調用構造函數和析構函數,構造函數實質是為對象在內存分配一個空間,存儲這個對象的數據,而析構函數則是將這個對象占用的內存釋放掉。
- 使用new關鍵字創建類的實例時,系統在堆內存為該實例動態分配了一塊內存區域(實際上等同於調用的C語言的malloc方法為變量動態分配內存),調用對象的方法(或成員變量)使用“->”符號;
- 不使用new關鍵字創建類的實例時,系統在棧內存為該實例分配一塊內存區域,調用對象的方法和變量須使用“.”符號。
- 類方法是靜態方法,是在類裝載的時候裝載的。(但是要特別注意,類的靜態變量是該類的對象所共有的,即是所有對象共享變量。所以建議盡量少用靜態變量。盡量在靜態方法中使用內部變量。)
3.2 再看一下Objective-C是如何定義一個MyObject類的
3.2.1 創建新項目
選擇OS X中的Command Line Tool,創建好工程后,按住command+N鍵創建Cocoa Touch Class,創建一個名為MyObject的NSObject的子類。
這樣就創建了OS X的命令行程序,以用於簡單演示如何聲明定義Objective-C的類
3.2.2 程序源碼
1)main.m文件:
#import <Foundation/Foundation.h> #import "MyObject.h" int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); MyObject *object = [[MyObject alloc] initWithIndex:3]; [object display]; // object = nil; } return 0; }
2) MyObject.h
#import <Foundation/Foundation.h> @interface MyObject : NSObject - (instancetype)initWithIndex:(NSInteger)index; - (void)display; + (void)classMethod; @end
3) .m文件
#import "MyObject.h" @interface MyObject() @property (assign, nonatomic) NSInteger index; @property (assign, nonatomic) NSInteger key; @end @implementation MyObject @synthesize index = _index; @synthesize key = _key; - (instancetype)initWithIndex:(NSInteger)index { self = [super init]; if (self) { NSLog(@"init"); self.index = index; } return self; } - (void)setIndex:(NSInteger)index { _index = index; self.key = 1<<(index+2); } - (NSInteger)index { return _index; } - (NSInteger)key { return _key; } - (void)display { NSLog(@"index == %ld \n key == %ld", (long)_index, (long)_key); } + (void)classMethod { NSLog(@"this is static (or class) method"); } - (void)dealloc { NSLog(@"dealloc"); } @end
3.2.3 控制台打印結果
2016-01-10 17:55:50.401 CompileOC[20792:1052939] Hello, World!
2016-01-10 17:55:50.402 CompileOC[20792:1052939] init
2016-01-10 17:55:50.402 CompileOC[20792:1052939] index == 3
key == 32
2016-01-10 17:55:50.402 CompileOC[20792:1052939] dealloc
Program ended with exit code: 0
3.2.4 代碼解釋
1) 在main.m文件中創建了MyObject的實例並調用display方法。
2)在頭文件中聲明了三個公有方法,
- (instancetype)initWithIndex:(NSInteger)index;
- (void)display;
+ (void)classMethod;
3).m文件中
- 創建了一個MyObject類的擴展,在擴展中聲明了兩個NSInteger的屬性,屬性的特性為assign和nonatomic
- 重寫了父類中的實例(減號)init方法,並為index屬性賦值
- 實現了index屬性的setter方法
- 實現了index和key屬性的getter方法
- 創建了打印index和key的方法
- (void)display
- 創建了類方法
+ (void)classMethod;
- 重寫了父類的dealloc方法
4)與C++代碼做對比
- 在C++中可以創建沒有父類的類,但是在Objective-C中創建的類都繼承自NSObject或其子類。不選擇父類的話是不能創建Objective-C的類的。
- 在C++中調用方法或變量用“->”或“.”符號,而在Objective-C中調用方法是[],而且順序不是從左至右,而是從內到外。
- 在C++中只有當new來創建對象時,才會動態分配內存;
- 而在Objective-C中創建的所有類的實例對象都是動態分配的,從創建Objective-C實例的方式
類名 *實例名 = [[類名 alloc] init];
也可以看出,Objective-C創建實例都是動態分配內存,實際上也是等同於調用的C語言的malloc方法為變量動態分配內存。 - 即在Objective-C中所有的對象都是指針。
- 在C++中聲明公有方法用public關鍵字表明, 私有方法用private表明。而在Objective-C中聲明公有方法只要在頭文件的
@interface
@end
之間就可以了,而私有方法不需要在頭文件中聲明。 - 補充,在Objective-C中可以使用@private @public @protected @package 編譯器指令來設置實例變量的訪問限制。
實例變量的聲明方式為,@implementation {
@private
int index;
int key;
}
@end
-
注意實例變量和類的屬性並不是一回事,屬性和實例變量的本質區別是,屬性無法直接訪問對象的內部狀態,但是可以利用setter和getter方法進行訪問,而在setter和getter方法中還可以包含其他邏輯,比如,本文章中
- (void)setIndex:(NSInteger)index
方法,不單為實例變量_index賦值,還用setter的簡潔方式self.key = 1<<(index+2);
為實例變量_key賦值。 -
那_index和_key實例變量是誰聲明的呢?這是Xcode編譯器自己生成的:
@synthesize index = _index;
@synthesize key = _key;
上面的代碼使用了 @synthesize 編譯指令,讓編譯器分別為屬性index和key自動創建實例變量_index和_key。即,自動為屬性創建對應實例變量的方法是
@synthesize 屬性名 = 實例變量名;
事實上,當沒有實現setter方法時,@synthesize指令不寫,編譯器也可以為屬性自動創建實例變量,編譯器自動為屬性創建的變量名默認為_屬性名,在調試的時候可以設置變量觀察。 - 注意本程序是在Xcode7中創建運行的,Xcode5之后創建工程時就默認ARC模式了,現在基本項目都是自動引用計數的模式編寫的。老項目中的使用手動管理的需要做如下的設置:
在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc
即可 - 在項目開發中,我認為屬性遠比實例變量好用,大可以只聲明屬性,而不聲明實例變量。
4.Objective-C語言常見語法概念
4.1 基本語法概念
4.1.1類元素
1)實例變量
聲明方法:
@implementation {
//聲明實例變量
}
@end
實例變量是是類封裝的數據
2)屬性
@property (特性) 類型 屬性名;
屬性是Objective-C提供的訪問對象共享狀態的機制,編譯器可以自動生成訪問實例變量的方法。Xcode自動創建的實例變量默認變量名為“_屬性名”
3)類的聲明(接口)
@interface MyObject : NSObject
@end
通用的如下:
@interface 類名 : 父類名
@end
說明:
- @interface 和 @end 指令之間的區域只能聲明屬性或方法,不能有具體的實現過程。
- 如果在頭文件中的接口聲明的屬性或方法,其實就是公有的屬性和方法,可以直接訪問。
4)類的實現
@implementation {
//實例變量的聲明
}
//屬性和方法的定義
@end
說明:
- 方法的實現過程必須寫在 @implementation 和 @end 之間
5)類的方法:分為類方法和實例方法
- 聲明:
[+或-](返回類型)方法名:(參數類型)參數1 參數名:(參數類型)參數2 ... 參數名:(參數類型)參數n;
- 實現:
[+或-](返回類型)方法名:(參數類型)參數1 參數名2:(參數類型)參數2 ... 參數名n:(參數類型)參數n {
//code here
return 返回值;
}
- 調用:
[消息接收者(即類的實例對象) 方法名:1 參數名2:2 ... 參數名n:n];
說明:
- 方法的聲明其實就是方法部分實現除了{}以外的代碼Objective-C的方法定義方式很奇葩,剛開始不好接受,這其實是Objective-C消息機制一種體現。
- 在其他語言中,方法的書寫方式是函數式的,類似於數學中的函數f(x,y,z)。
- 在Objective-C中調用方法時向消息的接收者(類的實例對象)發送一條消息,書寫形式為[消息接收者 消息方法名:參數1 參數名2:參數2],所有調用方法都是動態的,根據發出消息的方法名來動態查找對應的指針。感興趣的話可以參考羅朝輝大神的博客 http://blog.csdn.net/kesalin/article/details/6689226
“不同的類可以擁有相同的 selector,這個沒有問題,因為不同類的實例對象performSelector相同的 selector 時,會在各自的消息選標(selector)/實現地址(address) 方法鏈表中根據 selector 去查找具體的方法實現IMP, 然后用這個方法實現去執行具體的實現代碼。這是一個動態綁定的過程,在編譯的時候,我們不知道最終會執行哪一些代碼,只有在執行的時候,通過selector去查詢,我們才能確定具體的執行代碼。”
-
多參數的聲明、實現和調用。例如,我在MyObject中定義一個多參數的請求方法:
- (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion { //code here NSError *responseError = nil;//assume it is responsed from server NSDictionary *responseData = @{@"message": @"OK"}; if (completion != nil) { if (responseError != nil) { completion(responseError, nil); } else { completion(nil, responseData); } } }
說明:
- - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion直接可以復制到@interface 和 @end 之間作為方法的聲明
- 注意方法中第三個參數是block變量,這個后面會稍作介紹,先了解概念混個眼熟,以后慢慢細琢磨。
- block是一塊代碼塊,實質是閉包,程序運行時,block{}內的不會立即執行,即非順序執行的,是異步執行的,所以最適合選為做網絡異步請求的回調函數,功能類似於ajax。
6)類的擴展
其實就是寫在類.m文件中的接口,只不過與類.h中接口相比,在.m文件中聲明的實例變量、屬性、方法外部不能訪問,即實現了方法、屬性的私有化。例如MyObject.m文件中
@interface MyObject()
@property (assign, nonatomic) NSInteger index;
@property (assign, nonatomic) NSInteger key;
@end
這段代碼就是擴展,並且聲明的index和key屬性外部不能訪問,只能在類的內部訪問。
7)分類
分類的目的主要是為了擴展類的方法,比如MyObject類我添加一個NY的分類,創建方法如下:
- command+N 選擇iOS Source中的Objective-C File,Next
- 填寫File(分類名),選擇category,class 填寫MyObject,然后點Next,再點Create創建
- 頭文件:
#import "MyObject.h" @interface MyObject (NY) @end
- 實現文件
#import "MyObject+NY.h" @implementation MyObject (NY) @end
說明:
- 分類中只可以聲明和定義方法,不可以聲明類接口屬性或實例變量。因為已經引用了原來類的頭文件會報重復聲明類的語法錯誤。
/Users/.../CompileOC/CompileOC/MyObject+NY.m:10:1: Duplicate interface definition for class 'MyObject'
8)協議
- 協議的聲明定義
@protocol NYProtocol <NSObject>
@optional
- (void)nyOptionalProtocalMethod;
@required
- (void)nyRequiredProtocalMethod;
@end
通用:
@protocol 協議名 <NSObject>
@optional
//可選擇實現的方法
@required
//必須實現的方法
@end
- 類遵從協議的方法
@interface MyObject() <NYProtocol>
@property (assign, nonatomic) NSInteger index;
@property (assign, nonatomic) NSInteger key;
@end
說明:
- 協議的定義可以寫寫在類的頭文件中也可以寫在類的實現文件中,或者也可以創建一個單獨的協議頭文件,但是必須要寫在遵從該協議的類接口之前,否則會報語法錯誤
- @optional指令標明的方法,遵從該協議的類不須要實現,但@required指令標明的必須實現
- 只有類的接口后面可以跟<協議> @interface MyObject() <NYProtocol>
- 協議方法類似於C++中的虛函數,不需要具體實現,只需聲明方法名,在子類中實現具體方法。協議方法實際是為了實現C++多繼承的目的,子類可以根據需要實現方法,比如,tableview的delegate協議和datasource協議,根據具體的UI需要來實現對應的協議方法。
4.2 ARC(Automatic Reference Counting)自動引用計數
ARC是Objective-C提供的內存管理方式,系統根據對象被引用的次數計次,當引用計數為零時,自動釋放該對象所占有的內存。而非系統自動管理內存的方式,稱為MRR(Manual Retain Release),老的項目代碼中仍會有手動管理內存的代碼。你會看到 retain 、 release、 autorelease 、dealloc這樣的消息(方法)代碼,以用來管理內存。但是在ARC模式下並不能手動編寫這些代碼,當引用一個對象時,即這個對象引用計數加一,當這個對象中某個指針,例如,本文中的 object = nil 時,引用計數減一,引用計數為零時,系統自動釋放對象所占用的內存。
但在Xcode5之后,創建項目時無法限定非ARC模式,而且「基本現在開發的項目都使用ARC」,所以,對於初學者來說,手動內存管理就暫時不需要更深入的了解了。但如果,希望能實現ARC與MRR的混編譯,那么就需要在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc
即可實現ARC和MRR混編。
5.小結
本篇文章主要介紹了 Objective-C 的基本語法,希望初學者讀完之后能對 Objective-C 編程語言有一點基本的了解,打消一部分 Objective-C 這種陌生的編程語言的畏懼感,從而能繼續往下學。
關於學習的方法,我還想說幾句。我認為,人接觸新鮮事物總會有種恐懼感,適當的恐懼感反而會更有意思,恐懼感太強烈會阻礙接受新的概念。接受新觀念肯定是需要一定時間,因為原有的觀念已經給思維產生一個定式,新觀念可能和舊觀念有沖突的地方,但肯定兩者之間也存在一定聯系。找出兩者的共同點,能快速理解新概念。所以,我覺得學習一種新事物,首先要接受,慢慢的就適應了,不急~咱還年輕~
6.博文推薦
http://blog.csdn.net/kesalin/article/details/8155245
7.結語
十分感謝萌萌噠樂樂(https://medium.com/@HiSuneBear)給我提的排版建議及文章評價,讓我有更強烈的動力把文章寫好(這篇文章寫的太長,截至2016-01-11 17:41:55 我寫了快三天。所以,最后有點累,不想寫了,但是樂樂童鞋讓我的小宇宙又重新燃了😂 決心要把這篇文章拉回正軌~~)。另外,也十分感謝 @朱不鳴 @D 兩位小童鞋對文章的批評建議~我會努力把文章寫好,希望能給更多的人幫助,謝謝!
最后,預告一下接下來的文章內容:
- 通過介紹蘋果官方文檔來介紹Cocoa Touch的框架
- 通過介紹如何搭建項目來介紹Xcode的基本配置
- 通過搭建一個簡單應用來熟悉UIKit中各種視圖控制器和視圖的使用
敬請期待吧😉 ~