了解Objective-C語言的起源
Objective-C是C語言的超集,是面向?qū)ο蟮恼Z言左权。Objetive-C是動態(tài)綁定的消息結(jié)構(gòu)蜕企,在運行時才會檢查對象類型洪灯。接受消息之后劈猿,執(zhí)行的代碼由運行期環(huán)境決定仇矾。
Objective-C是一種面向?qū)ο蟮恼Z言入热,與C++和Java之類的語言類似拍棕。不同的是,Objective-C是Smalltalk演化而來的勺良,使用的是消息結(jié)構(gòu)(messaging structure)而不是函數(shù)調(diào)用(function calling)绰播。
- 消息結(jié)構(gòu)(Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];
- 函數(shù)調(diào)用(C++)
Object *obj = new Object;
obj->perform(parameter1,parameter2);
使用函數(shù)調(diào)用的語言
以C++為例,運行時執(zhí)行的代碼由編譯器決定尚困。如果類中函數(shù)是多態(tài)的蠢箩,編譯器就會為該類生成一個虛函數(shù)表,然后自動將虛表指針添加為成員變量,根據(jù)虛表來執(zhí)行函數(shù)谬泌。
使用消息結(jié)構(gòu)的語言
以O(shè)bjective-C為例滔韵,運行時所執(zhí)行的代碼不是編譯器決定的,而是由runtime決定的掌实,不管消息是否為多態(tài)陪蜻,總是在運行時才會去查找執(zhí)行的方法,編譯器甚至不關(guān)心消息對象是何種類型贱鼻。
Objective-C的重要工作都由"runtime component"來完成,所有的面向?qū)ο蟮奶卣骱退璧娜繑?shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)都在里面宴卖。runtime component本質(zhì)上是一種與開發(fā)者所編寫的代碼相鏈接的動態(tài)庫,將代碼和程序粘合起來邻悬。這樣的話症昏,只要更新運行期組件就可以提升程序的性能。而在"編譯器"完成的語言拘悦,要想獲得類似的性能改變齿兔,就要重新編譯代碼。
聲明一個對象時础米,保存的是一個存在棧(stack)的一個指向堆(heap)中對象的指針诉儒。
NSString *someString = @"The string";
NSString *anotherString = someString;
可以看出,只創(chuàng)建了一個NString對象和兩個指向該對象的指針挑社,說明當(dāng)前棧幀(stack frame)里分配了兩塊內(nèi)存赵讯,每塊能容納下一個指針(32位機上是4個字節(jié),64位上是8個字節(jié))蘑斧,每塊中存儲的都是NSString的真實地址靖秩。
分配在棧中用于保存對象的內(nèi)存會在棧幀被彈出時自動清理,而分配在堆中的內(nèi)存必須直接管理竖瘾。
Objective-C將堆的內(nèi)存管理抽象出來了沟突,不需要malloc和free來分配或者釋放對象所占的內(nèi),而是使用引用計數(shù)來管理捕传。
在Objective-C中惠拭,有時會噴到定義不含*的變量,這些是直接使用"椨孤郏空間"來保存的變量职辅,不是對象,比如CoreGraphies框架中的CGRect
CGRect frame;
frame.size.width = 100.0f;
CGRect是結(jié)構(gòu)體聂示,只保存基本類型的變量域携,那么如果使用Objective-C對象來做的話,每次還需要分配和釋放堆內(nèi)存鱼喉,會造成額外的開銷秀鞭,因此如果只保存int,float等"非對象類型",使用結(jié)構(gòu)體就
struct CGRect{
CGPoint origin;
CGSize size;
};
在類的頭文件中盡量不要引入其他頭文件
除非確有必要趋观,不要引入頭文件。盡量使用向前聲明來提及別的類气筋,然后在實現(xiàn)文件中引入那些類的頭文件拆内。這樣做可以減低類之間的耦合。
協(xié)議單獨成類宠默,并盡量將協(xié)議寫在類擴展中麸恍,而不是公共頭文件中。
在一個類A中搀矫,如果要使用類B抹沪,在類A的頭文件中應(yīng)該盡量使用向前聲明來告知編譯器需要引用這類。因為編譯這個文件的時候瓤球,并不需要知道引入的文件的所有細(xì)節(jié)融欧。在類A的實現(xiàn)文件中,在來引入類B卦羡。
也就是說噪馏,如果要在類A中使用類B,那么在頭文件A.h中只需要
@class B;
在類A的實現(xiàn)文件A.m中绿饵,若要使用B欠肾,必須知道B的所有借口細(xì)節(jié),所以
#import B.h
將引入頭文件的時機盡量延后拟赊,只有在確有需要的時候引入刺桃,這樣可以
- 減少類使用者引入的頭文件的數(shù)量,減少編譯的時間吸祟。
- 解決兩個類互相引用的問題
假設(shè)有個Employer類和Employee類瑟慈,它們互為各自的成員變量,那么要編譯Employer則必須知道Employee屋匕,反之亦然葛碧。如果在各自頭文件中引入對方的文件,則會導(dǎo)致"循環(huán)引用"过吻。當(dāng)解析其中一個頭文件的時候进泼,會引入另一個頭文件,而另一個頭文件又要解析之前的頭文件疮装。使用#import而不是#include雖然不會導(dǎo)致死循環(huán),但是卻會導(dǎo)致其中一個類無法被正常編譯粘都。
但是有時候也必須在頭文件中引入其他頭文件
- 如果你的類繼承自某個超類廓推,則必須引入超類頭文件
- 如果要聲明你寫的類遵從某個協(xié)議(protocol),則該協(xié)議也必須引入
繼承自Shape并實現(xiàn)Drawable協(xié)議的Rectangle類的頭文件
//Ractangle.h
#import "Shape.h"
#import "Drawable.h"
@interface Rectangle:Shape<Drawable>
...
@end
需要注意的是,協(xié)議最好單獨成類翩隧,不要和其他類混在一起樊展,例如在類C的文件中,聲明了協(xié)議a,當(dāng)類D要使用協(xié)議a時专缠,就要引入類C,則又編譯時間增加雷酪,且有可能造成相互引用,所以協(xié)議最好單獨成類涝婉。
還有一種協(xié)議哥力,也就是"委托協(xié)議(delegate protocol)",就可以不用單獨寫一個頭文件墩弯,因為委托協(xié)議只有與被委托的類在一起才有意義吩跋,可以將兩者寫在一起。委托協(xié)議可以寫在類擴展(class-continuation)中渔工,不需要在公共頭文件中導(dǎo)入锌钮。
具體來說,每次引入其他文件時引矩,可以如下操作
- 如果可以用向前聲明梁丘,就使用向前聲明
- 協(xié)議單獨成類
- 協(xié)議最好寫在類擴展中
多用字面量語法,少用與之等價的方法
應(yīng)該使用字面量語法來創(chuàng)建字符串旺韭,數(shù)值氛谜,數(shù)組,字典茂翔。與創(chuàng)建此類的常規(guī)方法相比混蔼,這么做更加簡明扼要
應(yīng)該通過取下標(biāo)操作來訪問數(shù)組下標(biāo)或者是字典中的鍵所對應(yīng)的元素
用字面量語法來創(chuàng)建數(shù)組或者字典的時候,若值中有nil珊燎,或拋出異常惭嚣,因此要確保值中不含nil
字面數(shù)值
有時候需要將整數(shù),浮點數(shù)悔政,布爾值等基本類型封裝成類的時候晚吞,可以使用NSNumber。
- 常規(guī)方式
NSNumber *someNumber = [NSNumber numberWithInt:1];
- 字面量語法
NSNumber *someNumber = @1;
不僅語法更加精簡谋国,而且能以NSNumber實例表示的所有數(shù)據(jù)類型都可以使用該語法
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.141592;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
字面量數(shù)組
- 常規(guī):數(shù)組結(jié)尾處要加上nil
NSArray *animals = [NSArray arrayWithObject:@"cat",@"dog",@"mouse",@"badger",nil];
- 字面量:數(shù)組結(jié)尾處不需要加上nil
NSArray *animal = @[@"cat",@"dog",@"mouse",@"badger"];
不僅更加簡單槽地,而且還可以使用下標(biāo)來操作
字面量語法,實際上是一種語法糖
字典字面量
- 常規(guī)
NSDictionary *personData = [NSDictionaryWithObjectsAndKeys:
@"Matt",@"fistName",
@"Galloway",@"lastName",
[NSNumber numberWithInt:28],@"age",
nil];
- 字面量寫法
NSDictionary *personData =
@{@"fisrtName":@"Matt",
@"lastName":@"Galloway"
@"age":@28};
這樣寫更簡潔芦瘾,而且鍵出現(xiàn)在對象之前捌蚊,更易讀。
可變數(shù)組與字典
通過取下標(biāo)操作可以直接訪問某個下標(biāo)或者字典的對象近弟,如果容器是可變的缅糟,還可以直接修改
- 常規(guī)做法
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
- 字面量
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"]= @"Galloway";
多用類型常量,少用#define預(yù)處理命令
不要用預(yù)處理指令定義常量
在實現(xiàn)文件中使用"static const"來定義只在編譯單元內(nèi)可見的常量
在頭文件中使用extern來聲明全局常量
假如你現(xiàn)在需要設(shè)置某個動畫時間祷愉,也許會這樣做
#define ANIMATION_DURATION 0.3
這樣定義出的來的常量
- 沒有類型信息窗宦,看不出具體是指什么
- 在所有引入了這個頭文件的類中赦颇,ANIMATION_DURATION都將被替換成0.3
解決方法:可以是使用類型常量
static const NSTimeInterval kAnimationDuration = 0.3;
該方式定義包括了常量類型,能清晰描述常量的意義
這里kAnimationDuration是一個類變量赴涵,這個定義可以寫在類的實現(xiàn)文件的#import和實現(xiàn)函數(shù)之間
//AnimatedView.m
#import "Animated.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implement AnimatedView
...
@end
- 使用const,表示常量媒怯,如果在實現(xiàn)文件中企圖修改,會不成功
- 使用static意味著該變量僅在定義此變量編譯單元可見髓窜。如果不加static扇苞,編譯器會為它創(chuàng)建一個"外部符號",如果其他編譯單元中出現(xiàn)了同名變量,則拋出錯誤信息纱烘。
事實上杨拐,同時聲明為static和const之后,效果相當(dāng)于在該編譯單元進行了宏替換擂啥。
有時候哄陶,需要公開某個常量,比方說哺壶,你可能使用NSNotificationCenter來通知別人屋吨。這個變量可以聲明為外界可見的常值變量。
此類常量放需放在"全局符號表"中山宾,以便定義該常量編譯單元之外的單元使用至扰,定義方式如下
//在頭文件中
extern NSString *const StringConstant;
//在實現(xiàn)文件中
NSString *const StringConstant = @"value";
這個常量需要在頭文件中聲明,在實現(xiàn)文件中定義资锰。定義在頭文件中是為了告訴編譯器敢课,這邊有一個全局變量,請注冊到全局變量表中绷杜,而編譯器并不關(guān)心變量的定義直秆。
此變量必須定義,并且只能定義一次
用枚舉來表示狀態(tài)鞭盟、選項圾结、狀態(tài)碼
應(yīng)該用枚舉來表示狀態(tài)機的狀態(tài)
如果狀態(tài)是可以多選的,可以使用2的冪來表示枚舉齿诉,以便使用邏輯操作
使用NS_ENUM和NS_OPTIONS來代替enum
枚舉是一種常量命名的方式筝野,以套接字連接狀態(tài)為例
enum ConnectionState
{
DisConnected,
Connecting,
Connected,};
typedof enum ConnetionState ConnectionState; //這樣下次用枚舉的時候就不用加enum
枚舉默認(rèn)是使用NSInteger從0開始標(biāo)識,也可以自行改變標(biāo)識方式
enum ConnectionState
{
DisConnected=1, //從1開始
Connecting, //2
Connected, //3};
typedof enum ConnetionState ConnectionState; //這樣下次用枚舉的時候就不用加enum
當(dāng)定義選項的時候粤剧,如果可以彼此組合歇竟,只要枚舉得當(dāng),就可以通過"按位或操作符"來組合
如果iOS UI框架中有如下枚舉
enum UIViewAutoresizing{
UIVIewAutosizingNone = 0,
UIViewAutosizingFlexibleLeftMargin = 1 <<0,
UIViewAutosizingFlexibleWidth = 1 <<1,
UIViewAutosizingFlexibleRightMargin = 1 <<2,
UIViewAutosizingFlexibleTopMargin = 1 <<3,
UIViewAutosizingFlexibleHeight = 1 <<4,
UIViewAutosizingFlexibleBottomMargin = 1<<5,
}
每個選項都可以啟用或禁用
Foundation框架定義了一些輔助宏抵恋,日常中要避免使用C風(fēng)格的enum盡量使用NS_ENUM和NS_OPTION
用法如下
typedef NS_ENUM(NSUInteger,ConnectionState)
{
Disconnected,
Connecting,
Connected,};
typedef NS_OPTION(NSUInteger,PermiitedDirection)
{
Up =1<<0,
Down =1<<1,
Left =1<<2,
Right =1<<3,};