前言
若想深刻地理解一門學科,或者學習一門手藝秫筏,必須經(jīng)過嚴謹?shù)挠站希上到y(tǒng),成理論地學習这敬。無疑航夺,一本一本地去讀關(guān)于它們的著作是最好的方式,因為書籍中的文字流露出的思維邏輯是最細膩最連貫的崔涂。在浩瀚的文字中你不僅能知其然阳掐,且能知其所以然。而碎片化閱讀仿佛閱讀一片片的小紙片冷蚂,是散亂的缭保。你得到的信息是不成系統(tǒng)的,不能融會貫通的蝙茶,感覺就像盲人摸象似是而非艺骂,而深度閱讀后仿佛站在了上帝視角,以前所有的疑問和謎團也撥云見日隆夯,逐漸清晰了钳恕。
這幾天在閱讀《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》,發(fā)現(xiàn)書中有很多干貨蹄衷,而且有很多重要的細節(jié)是以前不曾認真思考的忧额。所以想記錄下來。
在類的頭文件中盡量少引入其他頭文件
如下我們在.h文件中定義類的屬性或者定義方法時愧口,需要引入其有關(guān)類的頭文件(StudentModel.h)睦番。
#import <UIKit/UIKit.h>
#import "StudentModel.h"
@interface YWViewController : UIViewController
@property (nonatomic, strong)StudentModel *studentModel;
- (void)studentStudy:(StudentModel *)studentModel;
@end
但這樣不太完美,我們知道#import "StudentModel.h"
在編譯時其實是拷貝動作耍属,把導入的StudentModel.h文件也拷貝進了該類進行編譯托嚣,如果在.h文件中泛濫導入其他文件,無疑會增加編譯時間厚骗。
其實在該YWViewController.h文件中僅僅需要聲明StudentModel是個類就行了注益,所以在此我們應該使用@class
關(guān)鍵詞聲明它是一個類,而在YWViewController.m文件中實現(xiàn)定義的方法時我們需要StudentModel
類的細節(jié)溯捆,所以這時得用#import "StudentModel.h"
導入頭文件丑搔。
規(guī)范的寫法應該像下面這樣:
YWViewController.h
#import <UIKit/UIKit.h>
@class StudentModel; // 在.h文件中僅聲明其確實是一個類
@interface YWViewController : UIViewController
@property (nonatomic, strong)StudentModel *studentModel;
- (void)studentStudy:(StudentModel *)studentModel;
@end
YWViewController.m
#import "YWViewController.h"
#import "StudentModel.h" // 在.m文件中才正式導入文件,因為需要知道該類細節(jié)
@interface YWViewController ()
@end
@implementation YWViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)studentStudy:(StudentModel *)studentModel
{
NSLog(@"----%@----%@----",studentModel.studentId,studentModel.name);
}
@end
OC中類里的常量和全局常量
- 在OC中定義一個只對類內(nèi)部有效的常量:
即在.m文件中用static和const關(guān)鍵詞同時來修飾提揍,static表示只在該“編譯單元”內(nèi)有效啤月,const表示其為常量,不可修改劳跃,若類里對該常量值進行了修改谎仲,編譯時會報錯。而且一般命名時以k開頭表示只對類內(nèi)部有效的常量刨仑。
// YWViewController.m文件
#import "YWViewController.h"
static NSString *const kMyURL = @"http://www.reibang.com/users/8b79c6535a4b/latest_articles";
@interface YWViewController ()
@end
@implementation YWViewController
- (void)viewDidLoad{
[super viewDidLoad];
}
@end
** 千萬需要注意的是:const
和*
的位置前后關(guān)系有著非常重要的意義郑诺,const
修飾的是它右邊的部分夹姥。**在上面的例子中const
右邊的部分是kMyURL
,因此不可變的是kMyURL
辙诞。而若const
處在下面這樣的位置辙售,則代表就代表另外的意思了!
static NSString const *kMyURL = @"http://www.reibang.com/users/8b79c6535a4b/latest_articles";
static const NSString *kMyURL = @"http://www.reibang.com/users/8b79c6535a4b/latest_articles";
** 因為const
修飾的是它后面的部分飞涂,而此時它后面的是(* kMyURL )
旦部。"*"
是指針指向符號 ,也就是說此時kMyURL
指向的內(nèi)存地址不可變较店,而內(nèi)存塊中保存的內(nèi)容是可變的士八。 **
用下面代碼來驗證:
static NSString const *kUserName = @"wang66";
NSLog(@"%@----%x",kUserName,&kUserName);
kUserName = @"wang77";
NSLog(@"%@----%x",kUserName,&kUserName);
// 2016-04-09 17:57:06.199 OCDemo[1860:994583] wang66----87f6190
// 2016-04-09 17:57:06.199 OCDemo[1860:994583] wang77----87f6190
給kUserName
賦值時編譯器沒報錯,賦值成功梁呈,打印的內(nèi)存地址一樣婚度。由此驗證上面的結(jié)論是正確的。
- 定義一個全局常量:
** 先在.h文件中通過extern關(guān)鍵詞聲明此全局常量官卡,然后在.m文件中定義該常量陕见。一般命名全局常量時會加上該類的前綴已提高可讀性。**
比如在登錄成功后我們需要發(fā)送一個已登錄的通知味抖,這個通知的名字一般得定義成全局常量评甜,其他地方需要判斷是否已登錄時需要通過該通知名來觀察該通知,即需要使用該全局常量仔涩。
// YWViewController.h文件
#import <UIKit/UIKit.h>
extern NSString *const YWHasLoginedNotifition; // 先在.h文件中聲明此全局常量
@interface YWViewController : UIViewController
@end
// YWViewController.m文件
#import "YWViewController.h"
NSString *const YWHasLoginedNotifition = @"YWHasLoginedNotifition"; // 然后在.m文件中定義該常量
@interface YWViewController ()
@end
@implementation YWViewController
- (void)viewDidLoad{
[super viewDidLoad];
}
@end
關(guān)于數(shù)組和字典的初始化
數(shù)組和字典初始化系統(tǒng)提供了下面幾個方法:
方式1:
NSArray *arr1 = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
NSArray *arr2 = [[NSArray alloc] initWithObjects:@"a", @"b", @"c", nil];
NSDictionary *dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
@"valueA", @"keyA",
@"valueB", @"keyB",
@"valueC", @"keyC", nil];
NSDictionary *dict2 = [[NSDictionary alloc] initWithObjectsAndKeys:
@"valueA", @"keyA",
@"valueB", @"keyB",
@"valueC", @"keyC", nil];
這幾種初始化方法是最基本的忍坷,但是有更簡潔的初始化方式:
方式2:
NSArray *arr3 = @[@"a", @"b", @"c"];
NSDictionary *dict3 = @{@"keyA":@"valueA",
@"keyB":@"valueB",
@"keyC":@"valueC"};
顯然這種寫法更簡潔,而且語義更符合人的思維習慣熔脂,更簡單易懂(代碼1里字典初始化時value在key前佩研,這和人們平時的思維是相反的)。
這兩種初始化方法并不僅僅是寫法不同而已霞揉,需要注意的是通過方式2初始化時旬薯,元素不能為空,否則會crash适秩。初始化時放入集合的元素對象必須保證是非空的绊序。
而通過方式1初始化時若有元素是nil,會怎樣呢秽荞?
NSArray *arr1 = [NSArray arrayWithObjects:@"a", nil, @"c", nil];
for(int i=0; i<arr1.count; i++)
{
NSLog(@"arr1_____%@",arr1[i]);
}
2015-12-12 20:40:36.341 WangDemo[8254:3133600] arr1_____a
可以從上面代碼中看到:通過方式1初始化時骤公,若遇到某元素是nil,則就在該處終止扬跋,后面的元素不再放入集合中阶捆。上面的代碼中arr1在初始化時第二個元素是nil,則在此終止初始化,所以最終arr1里只有一個元素@“a”洒试。
另外倍奢。
通過方式2創(chuàng)建的數(shù)組和字典都是不可變的,若想變成可變的垒棋,那你可以拷貝一份使其成為可變數(shù)組:
NSMutableArray *mutArr = [@[@"a", @"b", @"c"] mutableCopy];
不過拷貝新份對象和一開始就這樣初始化并無多大差異卒煞。總之捕犬,具體場景具體選擇吧跷坝!
NSMutableArray *mutArr = [NSMutableArray arrayWithObjects:@"a", @"b", @"c", nil];
善用枚舉表示類型酵镜,狀態(tài)碉碉、選項,組合等
枚舉的寫法:
最常見的枚舉定義形式是:
typedef enum{
MainList_Ads=0, //輪播廣告
MainList_Topic, //專題
MainList_Classify, //課程分類
MainList_Teacher, //推薦老師
MainList_Celebrity, //名師推薦
MainList_Agency, //推薦機構(gòu)
}MainListType;
不過OC中對這種定義形式稍稍進行了封裝淮韭。使其可以指明該枚舉底層的數(shù)據(jù)類型垢粮,其實默認都是整形的。既然枚舉類型是整形的靠粪,那在定義屬性時就得是這樣的:
@property (nonatomic, assign) MainListType mainListType;
指定第一項為0蜡吧,則后面項的值會遞增。其實通過枚舉判斷類型和定義一個整形變量判斷沒有區(qū)別占键。枚舉好就好在它的語義一看就明白代表什么意思昔善。
typedef NS_ENUM(NSUInteger, MainListType)
{
MainList_Ads=0, //輪播廣告
MainList_Topic, //專題
MainList_Classify, //課程分類
MainList_Teacher, //推薦老師
MainList_Celebrity, //名師推薦
MainList_Agency, //推薦機構(gòu)
};
枚舉的組合:
用枚舉表示選項時,選項是可以組合多選的畔乙。比如自動布局的條件是枚舉君仆,而且需要多選組合使用。
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
既然枚舉的每項元素要組合使用以表示不同的情況牲距,那我們就得保證元素組合后的唯一性返咱,不能A和B和C和D組合后表示一個值。
那怎么確定枚舉每項組合后值的唯一性呢牍鞠?
普通的枚舉每項值是遞增的咖摹,此時&&或者||的結(jié)果是不唯一的。為了保證組合后的唯一性难述,人類就給每項元素賦值時做了一點改動:枚舉每項的值執(zhí)行按位或操作(第一項是1的0次方萤晴,第二項是1的1次方,第三項是1的2次方·····)胁后。
看下面這張圖有助于理解:
“對象等同性”判斷
有時我們有比較兩對象是否相等的需求硫眯。首先想到的是通過等號“==”判斷,但它** 僅僅比較的是兩個指針本身择同,而不是其所指向的對象两入。 **即使兩個不同的指針,也有可能都指向同一個對象敲才。
然后我們想想兩個對象到底怎樣就會相等呢裹纳?...
** 首先 **择葡,若他倆是同一個指針的話,那不用再費勁去比較了剃氧,兩個對象毫無疑問是相等的敏储,一個對象可以被多個指針指向,但一個指針不可能指向多個對象吧朋鞍;
** 然后 **已添,倆對象若要相等,他倆肯定同屬于一種類型吧滥酥,若它們的類型不同更舞,則兩個對象肯定不相等。
** 最后 **坎吻,在同屬于一種類型的基礎(chǔ)上缆蝉,兩個對象對應的屬性得完全相等。
NSObject協(xié)議中有兩個用于判斷對象等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
** 通過isEqual:方法判斷的兩對象若相等瘦真,則它們的hash值肯定也是相等的刊头;但兩個對象的hash相等,并不能說明他們相等诸尽。
對于我們自定義的對象判斷等同性原杂,我們可以實現(xiàn)上面兩個方法,實現(xiàn)isEqual:方法來定義我們自定義對象比較的邏輯您机。實現(xiàn)hash方法來定義自定義對象哈希值的算法穿肄。編寫hash方法時,應該使用計算速度快而且哈希嗎碰撞幾率低的算法往产。(hash方法的編寫這個日后補充) **
若StudentModel
類的有比較對象等同性的需求被碗,則實現(xiàn)isEqual:方法。
// StudentModel.m
#import "StudentModel.h"
@implementation StudentModel
- (BOOL)isEqual:(id)object
{
if(self == object) return YES; // 若倆指針相同仿村,則肯定是同一個對象锐朴。
if([self class] != [object class]) return NO; // 若倆對象不屬于同一類型,則肯定不相等蔼囊。
StudentModel *otherStuModel = (StudentModel *)object;
if(![_studentId isEqualToString:otherStuModel.studentId])
return NO;
if(![_name isEqualToString:otherStuModel.name]) // 若有一個屬性不相等焚志,則倆對象不相等
return NO;
return YES;
}
@end
像NSString,NSArray,NSDictionary等系統(tǒng)對象,還提供了諸如isEqualToString:
畏鼓、isEqualToArray:
等針對這些類特定的比較方法酱酬,這樣就不用先判斷兩個對象是否是同一類型了,這樣速度更快云矫。我們的自定義對象也完全可以借鑒膳沽。
// StudentModel.m
#import "StudentModel.h"
@implementation StudentModel
- (BOOL)isEqual:(id)object
{
if(self == object) return YES;
if([self class] == [object class]){
return [self isEqualToStudentModel:(StudentModel *)object]; // 此時只需要調(diào)用isEqualToStudentModel:方法即可
}else{
return [super isEqual:object];
}
}
// 已知倆對象同為StudentModel類型時
- (BOOL)isEqualToStudentModel:(StudentModel *)stuModel
{
if(![_studentId isEqualToString:stuModel.studentId])
return NO;
if(![_name isEqualToString:stuModel.name])
return NO;
return YES;
}
@end
** 注意:** 判斷倆對象相等,并不一定都要判斷它們對應的每個屬性是否都相等,要根據(jù)具體情況而定挑社,若在僅通過“主鍵”等就可以判斷對象是否相等的情況下陨界,完全沒必要一個一個去判斷屬性。
容器中可變類的等同性:
關(guān)于copy
** 拷貝的目的:改變原對象不影響副本痛阻,改變副本不影響原對象菌瘪。**
并不是說只要拷貝了就一定會生成一個新對象,而要根據(jù)上面拷貝的目的
來分析阱当。比如俏扩,若一個不可變的對象進行了copy
操作是不會生成新對象的,因為原始對象就是不可變的弊添,拷貝后的對象也是不可變的录淡,兩者均不可變,互不干擾表箭,互不影響赁咙,并不需要生成一個新的對象钮莲,只需要進行指針拷貝
就行了免钻。
同理,若對一個對象進行了mutableCopy
操作崔拥,不管原始對象是可變不可變极舔,均會生成新對象。
** 容器類對象無論進行什么拷貝链瓦,其元素對象均是指針拷貝拆魏。但可以通過歸檔再解檔的方式實現(xiàn)元素對象的神拷貝。
** ||----修飾NSString
型的屬性時為什么要用copy
關(guān)鍵字慈俯?----||
這篇文章解釋得很好:
什么時候用copy渤刃,什么時候用strong
** ||----修飾block
屬性時為什么要用copy
關(guān)鍵字?----||**
首先這涉及到MRC時代贴膘。因為MRC時期卖子,為了防止block內(nèi)用到的變量提前釋放導致程序崩潰,使用copy將block存放到堆中刑峡,此時block會對內(nèi)部變量進行一次retain操作洋闽,從而防止意外清空。同時block放入堆中也會帶來一個新的問題突梦,self持有block的引用诫舅,如果在block中使用self就會產(chǎn)生循環(huán)引用,所以不論MRC還是ARC宫患,我們都分別用blcok和weak來修飾self刊懈。
這篇文章解釋得很全面:
認識copy關(guān)鍵字