一、了解 Objective-C 語(yǔ)言的起源
Objective-C 與 C++、Java 等類似,是一種面向?qū)ο蟮恼Z(yǔ)言苇瓣。該語(yǔ)言使用“消息結(jié)構(gòu)”而非“函數(shù)調(diào)用”。Objective-C 語(yǔ)言由 Smalltalk 演化而來偿乖,后者是消息型語(yǔ)言的鼻祖击罪。
Objctive-C 為 C 語(yǔ)言添加了面向?qū)ο蟮奶匦裕瞧涑谙搿bjective-C 使用動(dòng)態(tài)綁定的消息結(jié)構(gòu)外邓,也就是說撤蚊,在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型古掏。接受一條消息之后,究竟應(yīng)該執(zhí)行何種代碼侦啸,由運(yùn)行期環(huán)境而非編譯器來決定槽唾。
二丧枪、在類的頭文件中盡量少引用其他頭文件
在類的頭文件 .h 中,如果需要引用其它類,盡可能少用 #import
改而使用 @class
,在 實(shí)現(xiàn)文件 .m 中若要使用該類再使用 #import
庞萍。這樣做是將引入頭文件的時(shí)機(jī)盡量延后拧烦,只在確有需要時(shí)才引入,這樣就可以減少類的使用者所需引入的頭文件數(shù)量钝计,從而減少編譯時(shí)間恋博。
#import、#include私恬、@class區(qū)別:
#include:
1债沮、在C語(yǔ)言中,我們使用 #include 來引入頭文件本鸣。
2疫衩、#include 會(huì)造成重復(fù)引用頭文件。
3荣德、為了防止重復(fù)引用可采用:
#ifndef ViewController_h #define ViewController_h #endif
#import:
import 是 include 的升級(jí)版闷煤,可以防止重復(fù)引入頭文件這種現(xiàn)象的發(fā)生。
import會(huì)包含這個(gè)類的所有信息涮瞻,包括實(shí)體變量和方法
使用 #import 頭文件會(huì)自動(dòng)只導(dǎo)入一次鲤拿,不會(huì)重復(fù)導(dǎo)入,相當(dāng)于#include和#pragma once
@class
@class 用來告訴編譯器署咽,有這樣一個(gè)類皆愉,使書寫代碼時(shí),不報(bào)錯(cuò)艇抠。 但是 @class 只是使導(dǎo)入的類名在引用時(shí)不受影響幕庐,不能創(chuàng)建該類的對(duì)象,因?yàn)閯?chuàng)建對(duì)象時(shí)也需要訪問其內(nèi)部方法家淤。
在編譯效率方面考慮异剥,如果你有100個(gè)頭文件都 #import 了同一個(gè)頭文件,或者這些文件是依次引用的絮重,如 A–>B, B–>C, C–>D 這樣的引用關(guān)系冤寿。當(dāng)最開始的那個(gè)頭文件有變化的話,后面所有引用它的類都需要重新編譯青伤,如果你的類有很多的話督怜,這將耗費(fèi)大量的時(shí)間。而是用 @class 則不會(huì)狠角。
但是也有一些情況号杠,是不可避免要在 .h 里引用的。比如:繼承某個(gè)類,必須在 .h 里 import 父類的 .h姨蟋;類實(shí)現(xiàn)某個(gè)接口屉凯,必須在 .h 里引用接口的 .h 等等
三、多用字面量語(yǔ)法眼溶,少用與之等價(jià)的方法
字面量字符串:
NSString *someStr = @"someStr";
字面數(shù)值:
NSNumber *intNum = @1;
NSNumber *boolNum = @YES;
NSNumber *charNum = @'a';
字面量數(shù)組:
NSArray *animale = @[@"cat",@"dog",@"mouse"];
使用字面量數(shù)組的好處是悠砚,當(dāng)數(shù)組元素對(duì)象中有 nil,則會(huì)拋出異常堂飞。因?yàn)樽置媪空Z(yǔ)法實(shí)際上是一種“語(yǔ)法糖”灌旧,其效果等于先是創(chuàng)建了一個(gè)數(shù)組,然后把方括號(hào)內(nèi)的所有對(duì)象都加到這個(gè)數(shù)組中绰筛。
下面這段代碼分別以兩種語(yǔ)法創(chuàng)建數(shù)組:
NSArray *arrayA = [NSArray arrayWithObjects:obj1,obj2,obj3, nil];
NSArray *arrayB = @[obj1,obj2,obj3];
假如 obj1 與 obj3 都指向了有效的 Objective-C 對(duì)象节榜,而 obj2 為 nil,則字面量語(yǔ)法創(chuàng)建的數(shù)組 arrayB 會(huì)拋出異常别智。而 arrayA 雖然能夠創(chuàng)建出來宗苍,但是其中卻只含有 obj1 一個(gè)對(duì)象。原因在于arrayWithObjects
方法會(huì)依次處理各個(gè)參數(shù)薄榛,之道發(fā)現(xiàn) nil 為止讳窟,由于 obj2 是 nil,所以給方法會(huì)提前結(jié)束敞恋。
所以使用字面量語(yǔ)法更為安全丽啡,向數(shù)組中插入 nil 通常說明程序有錯(cuò),而通過異秤裁ǎ可以更快的發(fā)現(xiàn)這個(gè)錯(cuò)誤补箍。
字面量字典:
NSDictionary *personData = @{
@"name":@"matt",
@"age":@28
};
使用字面量語(yǔ)法創(chuàng)建出來的字符串、數(shù)組啸蜜、字典對(duì)象都是不可變的坑雅,若想變成可變的版本的對(duì)象,則需復(fù)制一份:
NSMutableArray *mutable = @[@1,@2,@3].mutableCopy;
四衬横、多用類型常量裹粤,少用 #define (宏)預(yù)處理指令
我們定義常量一般會(huì)使用宏 #define:
#define ANIMATION_DURATION 0.3
這樣定義出來的常量不含類型信息,編譯器只是會(huì)在編譯前據(jù)此查找與替換操作蜂林。即使有人重新定義了常量值遥诉,編譯器也不會(huì)產(chǎn)生警告信息,這將導(dǎo)致應(yīng)用程序中的常量值不一致噪叙。
在實(shí)現(xiàn)文件 .m 中我們應(yīng)該使用類型常量來定義常量:
static const NSTimeInterval kAnimationDuration = 0.3;
在實(shí)現(xiàn)文件 .m 中矮锈,定義常量名稱前面一般加字母 k。若在 .h 中,即常量在類之外可見睁蕾,通常以類名為前綴:
//.h 中聲明:
extern const NSTimeInterval EOCAnimationDuration;
//.m 中實(shí)現(xiàn):
const NSTimeInterval EOCAnimationDuration = 1.0;
在頭文件中使用 extern 來聲明全局常量苞笨,并在相關(guān)實(shí)現(xiàn)文件中定義其值。這種常量要出現(xiàn)在全局符號(hào)表中,所以其名稱應(yīng)該加以區(qū)隔猫缭,通常用與之相關(guān)的類名做前綴葱弟。
#define壹店、const猜丹、static、extern區(qū)別
1硅卢、define 宏:
- 編譯時(shí)刻:宏是預(yù)編譯(編譯之前處理)射窒,const是編譯階段。
- 編譯檢查:宏不做檢查将塑,不會(huì)報(bào)編譯錯(cuò)誤脉顿,只是替換,const會(huì)編譯檢查点寥,會(huì)報(bào)編譯錯(cuò)誤艾疟。
- 宏的好處:宏能定義一些函數(shù),方法敢辩。 const不能蔽莱。
- 宏的壞處:使用大量宏,容易造成編譯時(shí)間久戚长,每次都需要重新替換盗冷。
2、const:
中文“常量”意思同廉。
- const用來修飾右邊的基本變量或指針變量仪糖。
- 被修飾的變量只讀,不能被修改迫肖。
看下面的例子锅劝,相信你就完全理解 const 的用法:
int const *p // *p只讀,p變量
int * const p // *p變量蟆湖,p只讀
const int * const p //p和*p都只讀
int const * const p //p和*p都只讀
3鸠天、static
中文“靜態(tài)”意思。
(1)修飾局部變量
保證局部變量永遠(yuǎn)只初始化一次帐姻,在程序的運(yùn)行過程中永遠(yuǎn)只有一份內(nèi)存稠集, 生命周期類似全局變量了,但是作用域不變饥瓷。例如:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static int i =0;
i++;
NSLog(@"i = %d",i);
}
得到的結(jié)果是變量 i 每次都會(huì)自增剥纷。如果不使用 static 修飾,則 i 每次結(jié)果只為 1呢铆。這就是關(guān)鍵字 static 修飾的局部變量的作用晦鞋,讓局部變量永遠(yuǎn)只初始化一次,一份內(nèi)存,生命周期已經(jīng)跟全局變量類似了悠垛,只是作用域不變线定。
(2)修飾全局變量
使全局變量的作用域僅限于當(dāng)前文件內(nèi)部,即當(dāng)前文件內(nèi)部才能訪問該全局變量确买。
iOS 中在一個(gè)文件聲明的全局變量斤讥,工程的其他文件也是能訪問的,但是我又不想讓其他文件訪問湾趾,這時(shí)就可以用 static 修飾它了芭商。
(3)修飾函數(shù)
static 修飾函數(shù)時(shí),被修飾的函數(shù)被稱為靜態(tài)函數(shù)搀缠,使得外部文件無法訪問這個(gè)函數(shù)铛楣,僅本文件可以訪問。這個(gè)在 oc 語(yǔ)言開發(fā)中幾乎很少用艺普,c 語(yǔ)言倒是能看到一些影子簸州,所以不詳細(xì)探討。
4歧譬、extern
中文“外部的”意思岸浑。它的作用是聲明外部全局變量。這里需要特別注意extern只能聲明缴罗,不能用于實(shí)現(xiàn)助琐。
在開發(fā)中,我們通常會(huì)單獨(dú)抽一個(gè)類來管理一些全局的變量或常量面氓,下面來看看逼格比較高的一種做法:
我們可以在.h文件中 extern 聲明一些全局的常量:
extern NSString * const name;
extern NSInteger const count;
.m 中實(shí)現(xiàn)
NSString * const name = @"王五";
NSInteger const count = 3;
這樣兵钮,只要導(dǎo)入頭文件,就可以全局的使用定義的變量或常量舌界。
五掘譬、用枚舉表示狀態(tài)、選項(xiàng)呻拌、狀態(tài)碼
枚舉是一種常量命名方式葱轩。
枚舉表示狀態(tài)(狀態(tài)碼同理)
//第一種寫法,先定義枚舉類型藐握,再定義枚舉變量
enum EOCConnetionState{
EOCConnetionStateDisconnected,
EOCConnetionStateConnecting,
EOCConnetionStateConnected
};
enum EOCConnetionState state;
//第二種寫法靴拱,定義枚舉類型的同時(shí)定義枚舉變量
enum EOCConnetionState{
EOCConnetionStateDisconnected,
EOCConnetionStateConnecting,
EOCConnetionStateConnected
}state;
由于每種狀態(tài)都用一個(gè)便于理解的值來表示,所以這樣寫出來的代碼更易讀懂猾普。
枚舉表示選項(xiàng)
一個(gè)“選項(xiàng)變量”的類型要能夠同時(shí)表示一個(gè)或多個(gè)組合的選擇袜炕,如下例子:
enum TTGDirection {
TTGDirectionNone = 0,
TTGDirectionTop = 1 << 0, //00000001
TTGDirectionLeft = 1 << 1, //00000010
TTGDirectionRight = 1 << 2, //00000100
TTGDirectionBottom = 1 << 3 //00001000
};
這里的選項(xiàng)是用位運(yùn)算的方式定義的,這樣的好處就是初家,我們的選項(xiàng)變量可以如下表示:
//用“或”運(yùn)算同時(shí)賦值多個(gè)選項(xiàng)
enum TTGDirection direction = TTGDirectionTop | TTGDirectionLeft | TTGDirectionBottom;
//用“與”運(yùn)算取出對(duì)應(yīng)位
if (direction & TTGDirectionTop) {
NSLog(@"top");
}
if (direction & TTGDirectionLeft) {
NSLog(@"left");
}
if (direction & TTGDirectionRight) {
NSLog(@"right");
}
if (direction & TTGDirectionBottom) {
NSLog(@"bottom");
}
direction變量的實(shí)際內(nèi)存如下:
0 0 0 0 1 0 1 1
控制臺(tái)輸出:
top
left
bottom
這樣偎窘,用位運(yùn)算乌助,就可以同時(shí)支持多個(gè)值。
enum在 Objective-C 中的“升級(jí)版”
一般來說陌知,我們不能指定枚舉變量的實(shí)際類型是什么他托,就是說,我們不知道枚舉最后是 int 型仆葡,還是其他的什么類型赏参。但是從 C++ 11開始,我們可以為枚舉指定其實(shí)際的存儲(chǔ)類型浙芙,如下語(yǔ)法:
enum TTGState : NSInteger {/*...*/};
但是登刺,我們?cè)诙x枚舉的時(shí)候如何保證兼容性呢籽腕?Foundation 框架已經(jīng)為我們提供了更加“統(tǒng)一嗡呼、便捷”的枚舉定義方法,如下:
//NS_ENUM皇耗,定義狀態(tài)等普通枚舉
typedef NS_ENUM(NSUInteger, TTGState) {
TTGStateOK = 0,
TTGStateError,
TTGStateUnknow
};
//NS_OPTIONS南窗,定義選項(xiàng)
typedef NS_OPTIONS(NSUInteger, TTGDirection) {
TTGDirectionNone = 0,
TTGDirectionTop = 1 << 0,
TTGDirectionLeft = 1 << 1,
TTGDirectionRight = 1 << 2,
TTGDirectionBottom = 1 << 3
};
所以,在 iOS 開發(fā)中郎楼,枚舉最好使用NS_ENUM
和NS_OPTIONS
定義万伤,并指明其底層數(shù)據(jù)類型,保證統(tǒng)一呜袁。
處理枚舉類型 switch 語(yǔ)句中不要實(shí)現(xiàn) default 分支
這樣的話敌买,加入新枚舉之后,編譯器就會(huì)提示開發(fā)者 switch 語(yǔ)句并未處理所有枚舉阶界。如果寫上了 default 分支虹钮,那么它就會(huì)處理這個(gè)新狀態(tài),從而導(dǎo)致編譯器不發(fā)警告信息膘融。
參考資料
1芙粱、Effective Objective-C 2.0
2、https://juejin.im/post/5aaf6943518825556e5de48e