iOS 有效編寫高質量Objective-C方法(一)

ppsheep發表於2016-11-03

歡迎大家關注我的公眾號,我會定期分享一些我在專案中遇到問題的解決辦法和一些iOS實用的技巧,現階段主要是整理出一些基礎的知識記錄下來

iOS 有效編寫高質量Objective-C方法(一)

文章也會同步更新到我的部落格:
ppsheep.com

之前有讀過Effective Objective-C 2.0 覺得受益匪淺,決定再讀一遍,應該會有不一樣的感受,這裡就將閱讀的過程記錄下來,供自己查閱,也供大家參考。

在類的標頭檔案中儘量少引用其他標頭檔案

與C和C++一樣,OC也是使用標頭檔案(.h)和實現檔案(.m)來分隔程式碼。
程式碼看上去是這樣:

//PPSTeacher.h
#import <Foundation/Foundation.h>

@interface PPSTeacher : NSObject
@property (nonatomic, copy) NSString *name;
@end


//PPSTeacher.m
#import "PPSTeacher.h"

@implementation PPSTeacher
//具體實現
@end複製程式碼

在OC中 每個類都需要引入Foundation.h。如果在該類本身不引用,那麼就需要引用與其超類所對應的基本標頭檔案,比如我們常見的UIViewController 通常繼承UIViewControlelr的子類都需要引入UIKit.h 因為在每個用到的控制元件都會用到UIKit中的大部分內容,在需要用到的那些控制元件中都已經引入了Foundation.h 所以實際上 還是引入了Foundatiuon框架

好我們現在需要再建立一個學生類 PPSStudent ,每位老師有一位學生,那麼我們的程式碼可能就需要這麼寫了

#import <Foundation/Foundation.h>
#import "PPSStudent.h"

@interface PPSTeacher : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) PPSStudent *student;

@end複製程式碼

這樣寫當然不會有什麼問題,但是我們想想,在Teacher中,我只需要知道有學生這麼一個類就好,我不想知道他到底能幹什麼,我也不關心。OC提供了這樣一種方法,叫做“向前宣告”:

@class PPSStudent;

#import <Foundation/Foundation.h>

@class PPSStudent;
@interface PPSTeacher : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) PPSStudent *student;

@end複製程式碼

在實現檔案中我們需要引入 #import "PPSStudent.h",因為在實現過程中,我們需要知道PPSStudent能做哪些事情

#import "PPSTeacher.h"
#import "PPSStudent.h"

@implementation PPSTeacher
//實現
@end複製程式碼

這樣做的好處有兩個:

  • 能夠縮短編譯器的編譯時間
  • 還能夠避免迴圈引用

避免迴圈引用:

之前是每個老師擁有一個學生,我們再加上邏輯每個學生需要一個老師

如果按照之前直接#import的引入方式

PPSTeacher.h

#import <Foundation/Foundation.h>
#import "PPSStudent.h"

@interface PPSTeacher : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) PPSStudent *student;

@end複製程式碼

PPSStudent.h

#import <Foundation/Foundation.h>
#import "PPSTeacher.h"

@interface PPSStudent : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *name;

- (void)addTeacher:(PPSTeacher *)teacher;

@end複製程式碼

如果我們是按照這種方式去引用了 那麼就會造成迴圈引用,編譯是會報錯的
在編譯PPSStudent時 import了PPSTeacher,然後編譯器編譯PPSTeacher,編譯PPSTeacher時,又發現了PPSStudent, 這樣就造成了引用迴圈。

多用字面量,少用與之等價的方法

使用字面量,能夠使程式碼簡潔易讀

字面量數值

常規的方法我們需要

NSNumber *number = [NSNumber numberWithInt:1];複製程式碼

使用字面量,我們只需要

NSNumber *number = @1;複製程式碼

並且NSNumber例項表示的所有資料型別都可以使用字面量:

NSNumber *number = @1;
NSNumber *number = @2.5f;
NSNumber *number = @23.312121;
NSNumber *number = @YES;
NSNumber *number = @'a';複製程式碼

字面量陣列

一般的建立陣列方法:

NSArray *arr = [NSArray arrayWithObjects:@"1",@"2", nil];複製程式碼

使用字面量

NSArray *arr = @[@"1",@"2"];複製程式碼

字面量字典

一般建立方法:

NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"key1",@"object1",@"key2",@"object2", nil];複製程式碼

這樣會給我們帶來困惑,key和value完全不能一樣區分開來

使用字面量:

NSDictionary *dic = @{
                          @"key1"   :   @"value1",
                          @"key2"   :   @"value2",
                      };複製程式碼

使用字面量要點

  • 對於字串、數值、陣列、字典,應儘量使用字面量建立
  • 訪問陣列或字典,應儘量使用下標發來訪問 例如:arr[1] dic[@"key1"]
  • 建立字面量時,需要保證值中沒有nil物件,否則會報異常

多用型別常量,少用#define預處理指令

首先討論不需要對外開放使用的常量

編寫程式碼時,我們經常會定義一些常量。例如,要寫一個UIView檢視類,此檢視顯示出來後開始播放動畫,播放完成後消失。那麼我們可能就會想把這個動畫的播放時間,提取為一個常量,一般來說,我們會寫成這樣:

當然是在.m中定義,因為不需要對外開放

#define ANIMATION_DURATION 0.3複製程式碼

這樣定義有一個壞處,就是我們並不知道這個常量 是一個什麼型別的常量,也不知道他究竟是幹什麼的,有一個辦法比這種預處理指令更好

static const NSTimeInterval kAnimationDuration = 0.3;複製程式碼

按照此方法定義的常量表明瞭他的型別為NSTimeInterval,有助於其他團隊成員理解程式碼,並且有助於編寫開發文件,如果有更多的常量定義,那麼這種方法就更能展現他的優勢

對於常量的命名,一般用法是:

如果常量只是作用於當前的編譯單元(就是當前的.m實現類),那麼應該在常量的名稱前加上k

如果常量還要作用於外部,需要以當前的類名為字首

常量一定要用static const兩個一起定義,因為我們本來就是希望它是一個常量,不能夠被更改

還有一個原因,因為我們常量只作用於當前的.m類,如果不加上static,那麼編譯器在編譯我們當前的類時,會給它加上一個外部符號(external symbol),如果其他類也定義了一個相同的同名變數,那麼編譯器就會報錯

需要對外開放的常量

這種情況,一般我們比較常見的是,在當前類中需要完成一項操作,需要傳送一個全域性通知(NSNotificationCenter),用以通知他人,在派發通知時,我們需要用到當前的一個常量字串,在外部,接收者也需要知道這樣一個字串

我們通常這樣定義

在標頭檔案中:(假定當前的類名是PPSView)

extern NSString *const PPSViewNotofication;複製程式碼

在實現檔案中

NSString *const PPSViewNotofication = @"PPSViewNotofication";複製程式碼

這樣定義的話,在引入該標頭檔案的檔案中,當編譯器知道extern關鍵字時,就能明白,在全域性符號表中需要一個PPSViewNotofication的符號,編譯器無需知道這個符號的定義,當連結成二進位制檔案後,就能找到這個常量。

在物件內部儘量直接訪問例項變數

在物件之外,我們知道總是通過屬性(property)來對例項變數進行操作,那麼在例項內部應該怎麼做呢? 強烈建議在除了在懶載入中,其他情況下,都應該是:

在讀取變數時,都應該採用直接訪問的形式(_變數名),在設定例項變數時通過屬性來設定

我們來看個例子:

當前有個PPSPerson類

//PPSPerson.h
@interface PPSPerson : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

- (NSString *)fullName;

- (void)setFullName:(NSString *)funllName;

@end

//PPSPerson.m
@implementation PPSPerson

-(NSString *)fullName{
    return [NSString stringWithFormat:@"%@ %@",self.firstName,self.lastName];
}

-(void)setFullName:(NSString *)funllName{
    NSArray *components = [funllName componentsSeparatedByString:@" "];//通過空格 將firstName 和lastName分割開來
    self.firstName = components[0];
    self.lastName = components[1];
}

@end複製程式碼

在上面的程式碼中,我們都通過了點語法,來存取相關的例項變數,現在假設我們不經過存取方法,而是直接訪問例項變數:

@implementation PPSPerson

-(NSString *)fullName{
    return [NSString stringWithFormat:@"%@ %@",_firstName,_lastName];
}

-(void)setFullName:(NSString *)funllName{
    NSArray *components = [funllName componentsSeparatedByString:@" "];//通過空格 將firstName 和lastName分割開來
    _firstName = components[0];
    _lastName = components[1];
}

@end複製程式碼

這兩種寫法有以下幾個區別:

  • 由於不經過Objective-C的“方法派發”(後面會講到),所以直接訪問例項變數的速度當然比較快。編譯器所生成的程式碼會直接訪問儲存物件例項變數的那塊記憶體
  • 直接訪問例項變數時,不會呼叫其“設定方法”,這就繞過了為相關屬性所定義的“記憶體管理語義”。比如,如果在ARC下直接訪問一個宣告為copy的屬性,那麼並不會拷貝該屬性,只會白柳新值並釋放舊值
  • 如果直接訪問,不會觸發KVO,當然這具體還是需要看需求
  • 通過屬性訪問有助於排查與之相關的錯誤,可以再set和get方法中新增斷點除錯

之前講的懶載入方法,就必須使用屬性來訪問了,否則例項變數永遠不會初始化

要點

  • 在物件內部讀取資料時,應該直接通過例項變數來讀,而寫入資料時,則應該通過屬性來寫
  • 在初始化及dealloc方法中,總是應該通過例項變數來讀寫資料
  • 在懶載入中應該通過屬性來讀取資料

相關文章