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

前言

若想深刻地理解一門學(xué)科,或者學(xué)習(xí)一門手藝,必須經(jīng)過嚴謹?shù)模上到y(tǒng),成理論地學(xué)習(xí)。無疑,一本一本地去讀關(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"在編譯時其實是拷貝動作,把導(dǎo)入的StudentModel.h文件也拷貝進了該類進行編譯,如果在.h文件中泛濫導(dǎo)入其他文件,無疑會增加編譯時間。
其實在該YWViewController.h文件中僅僅需要聲明StudentModel是個類就行了,所以在此我們應(yīng)該使用@class關(guān)鍵詞聲明它是一個類,而在YWViewController.m文件中實現(xiàn)定義的方法時我們需要StudentModel類的細節(jié),所以這時得用#import "StudentModel.h"導(dǎo)入頭文件。

規(guī)范的寫法應(yīng)該像下面這樣:

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文件中才正式導(dǎo)入文件,因為需要知道該類細節(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.lxweimin.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.lxweimin.com/users/8b79c6535a4b/latest_articles";
    static const NSString *kMyURL = @"http://www.lxweimin.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"};

顯然這種寫法更簡潔,而且語義更符合人的思維習(xí)慣,更簡單易懂(代碼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ǔ)上,兩個對象對應(yīng)的屬性得完全相等。

NSObject協(xié)議中有兩個用于判斷對象等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
** 通過isEqual:方法判斷的兩對象若相等,則它們的hash值肯定也是相等的;但兩個對象的hash相等,并不能說明他們相等。
對于我們自定義的對象判斷等同性,我們可以實現(xiàn)上面兩個方法,實現(xiàn)isEqual:方法來定義我們自定義對象比較的邏輯。實現(xiàn)hash方法來定義自定義對象哈希值的算法。編寫hash方法時,應(yīng)該使用計算速度快而且哈希嗎碰撞幾率低的算法。(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

** 注意:** 判斷倆對象相等,并不一定都要判斷它們對應(yīng)的每個屬性是否都相等,要根據(jù)具體情況而定,若在僅通過“主鍵”等就可以判斷對象是否相等的情況下,完全沒必要一個一個去判斷屬性。

容器中可變類的等同性:

關(guān)于copy

** 拷貝的目的:改變原對象不影響副本,改變副本不影響原對象。**
并不是說只要拷貝了就一定會生成一個新對象,而要根據(jù)上面拷貝的目的來分析。比如,若一個不可變的對象進行了copy操作是不會生成新對象的,因為原始對象就是不可變的,拷貝后的對象也是不可變的,兩者均不可變,互不干擾,互不影響,并不需要生成一個新的對象,只需要進行指針拷貝就行了。
同理,若對一個對象進行了mutableCopy操作,不管原始對象是可變不可變,均會生成新對象。
** 容器類對象無論進行什么拷貝,其元素對象均是指針拷貝。但可以通過歸檔再解檔的方式實現(xiàn)元素對象的神拷貝。
** ||----修飾NSString型的屬性時為什么要用copy關(guān)鍵字?----||

這篇文章解釋得很好:
什么時候用copy,什么時候用strong

** ||----修飾block屬性時為什么要用copy關(guān)鍵字?----||**

首先這涉及到MRC時代。因為MRC時期,為了防止block內(nèi)用到的變量提前釋放導(dǎo)致程序崩潰,使用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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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