《52個有效方法》筆記1——熟悉Objective-C

前言

若想深刻地理解一門學科,或者學習一門手藝秫筏,必須經(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次方·····)胁后。
看下面這張圖有助于理解:

屏幕快照 2015-12-12 21.47.42.png

“對象等同性”判斷

有時我們有比較兩對象是否相等的需求硫眯。首先想到的是通過等號“==”判斷,但它** 僅僅比較的是兩個指針本身择同,而不是其所指向的對象两入。 **即使兩個不同的指針,也有可能都指向同一個對象敲才。
然后我們想想兩個對象到底怎樣就會相等呢裹纳?...
** 首先 **择葡,若他倆是同一個指針的話,那不用再費勁去比較了剃氧,兩個對象毫無疑問是相等的敏储,一個對象可以被多個指針指向,但一個指針不可能指向多個對象吧朋鞍;
** 然后 **已添,倆對象若要相等,他倆肯定同屬于一種類型吧滥酥,若它們的類型不同更舞,則兩個對象肯定不相等。
** 最后 **坎吻,在同屬于一種類型的基礎(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)鍵字

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虚汛,更是在濱河造成了極大的恐慌当宴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泽疆,死亡現(xiàn)場離奇詭異户矢,居然都是意外死亡,警方通過查閱死者的電腦和手機殉疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門梯浪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓢娜,你說我怎么就攤上這事挂洛。” “怎么了眠砾?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵虏劲,是天一觀的道長。 經(jīng)常有香客問我褒颈,道長柒巫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任谷丸,我火速辦了婚禮堡掏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刨疼。我一直安慰自己泉唁,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布揩慕。 她就那樣靜靜地躺著亭畜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迎卤。 梳的紋絲不亂的頭發(fā)上拴鸵,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音止吐,去河邊找鬼宝踪。 笑死,一個胖子當著我的面吹牛碍扔,可吹牛的內(nèi)容都是我干的瘩燥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼不同,長吁一口氣:“原來是場噩夢啊……” “哼厉膀!你這毒婦竟也來了溶耘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤服鹅,失蹤者是張志新(化名)和其女友劉穎凳兵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體企软,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡庐扫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仗哨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片形庭。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厌漂,靈堂內(nèi)的尸體忽然破棺而出萨醒,到底是詐尸還是另有隱情,我是刑警寧澤苇倡,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布富纸,位于F島的核電站,受9級特大地震影響旨椒,放射性物質(zhì)發(fā)生泄漏晓褪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一钩乍、第九天 我趴在偏房一處隱蔽的房頂上張望辞州。 院中可真熱鬧怔锌,春花似錦寥粹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岛杀,卻和暖如春阔拳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背类嗤。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工糊肠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遗锣。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓货裹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親精偿。 傳聞我的和親對象是個殘疾皇子弧圆,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容