《Effective Objective-C 2.0》讀書/實戰筆記 二

##《Effective Objective-C 2.0》讀書/實戰筆記 二程序員

第3章:接口與API設計

🇪🇪 第15條:用前綴避免命名空間衝突

  • 選擇與你的公司,應用程序或二者皆有關聯之名稱做爲類名的前綴,並在全部代碼中均使用這一前綴
  • 若本身所開發的程序庫中用到了第三方庫,則應爲其中的名稱加上前綴

顧名思義就是說在本身開發的類須要加前綴,iOS程序員開發工程師廣泛使用雙字母的前綴,就像我在開發時習慣加前綴 XW,其實,這是不科學的,由於蘋果爸爸公司保留使用全部「兩字母前綴」的權利,因此本身的前綴應該是三個字母的,不只僅是類名,還有分類、全局變量...objective-c

🇦🇩 第16條:提供「全能初始化方法」

  • 在類中提供一個全能初始化方法,並於文檔裏指明。其餘初始化方法均應調用此方法。
  • 若全能初始化方法與超類不一樣,則需覆寫超類中對應的方法
  • 若是超類的初始化方法不適應於子類,那麼應該覆寫這個超類方法,並在其中拋出異常

舉一個生動形象的例子: Chinese 類編程

//.h
//  中國人
#import <Foundation/Foundation.h>
@interface Chinese : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, assign, readonly) NSUInteger age;
/// 全能初始化對象方法
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age;
/// 全能初始化類方法
+ (instancetype)chineseWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age;
/// 其餘初始化對象方法
+ (instancetype)chineseWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end

//.m
#import "Chinese.h"
@interface Chinese()
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Chinese
/// 全能初始化函數-只有全能初始化函數才能進行賦值操做
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age {
    if (self = [super init]) {
        self.firstName = firstName;
        self.lastName = lastName;
        self.age = age;
    }
    return self;
}
+ (instancetype)chineseWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age {
    Chinese *people = [[self alloc] initWithFirstName:firstName lastName:lastName age:age];
    return people;
}
- (instancetype)init {
    return [self initWithFirstName:@"龍的" lastName:@"傳人" age:1]; // 調用指定初始化函數賦予其默認值
}
+ (instancetype)chineseWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
    return [self chineseWithFirstName:firstName lastName:lastName age:1];
}
@end
複製代碼

Student 類繼承自 Chinese設計模式

//.h
//  中國學生
#import "Chinese.h"
@interface Student : Chinese
@property (nonatomic, strong, readonly) NSArray *homework;
/// 指定初始化函數-需直接調用父類初始化函數
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age homework:(NSArray *)homework;
/// 指定初始化類方法
+ (instancetype)studentWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age homework:(NSArray *)homework;
/// 其餘初始化方法
+ (instancetype)studentWithHomework:(NSArray *)homework;
@end

//.m
#import "Chinese.h"
@implementation Student {
    NSMutableArray *p_homework;
}
/// 子類重寫父類全能初始化函數-更改默認值!
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age {
    return [self initWithFirstName:firstName lastName:lastName age:age homework:@[]];
}
/// 指定初始化函數-需直接調用父類初始化函數
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age homework:(NSArray *)homework {
    if (self = [super initWithFirstName:firstName lastName:lastName age:age]) {
        p_homework = homework.mutableCopy;
    }
    return self;
}
/// 指定初始化類方法
+ (instancetype)studentWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSUInteger)age homework:(NSArray *)homework {
    return [[self alloc] initWithFirstName:firstName lastName:lastName age:age homework:homework];
}
/// 重寫系統初始化方法
- (instancetype)init {
    return [self initWithFirstName:@"祖國的" lastName:@"花朵" age:6 homework:@[]];
}
/// 其餘初始化方法
+ (instancetype)studentWithHomework:(NSArray *)homework {
    return [self studentWithHomework:homework];
}
@end
複製代碼

🇦🇴 第17條:實現 description 方法

  • 實現 description 方法返回一個有意義的字符串,用以描述該實例
  • 若想在調試時打印出更詳盡的對象描述信息。則應實現 debugDescription 方法

若直接打印自定義對象,控制檯僅僅是顯示該對象的地址,不會顯示對象的具體細節,在程序開發中對象指針的地址或許有用,但大多數狀況下,咱們須要得知對象內部的具體細節,因此OC提供了 description 方法能夠實現。數組

@interface Chinese()
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Chinese
- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ : %p, %@>",[self class],self,
            @{
              @"firstName":_firstName,
              @"lastName" :_lastName,
              @"age": @(_age)
              }];
}
@end
複製代碼

這種使用字典輸出各屬性或成員變量內存的方式比較好,若以後須要增刪屬性直接修改字典的鍵值對就能夠了。 另外 debugDescription 方法是在控制檯使用 po 命令打印對象信息所調用的方式,若已經實現 description 方法, 可不覆寫 debugDescription 方法,由於系統會默認調用 description 方法。緩存

🇦🇮 第18條:儘可能使用不可變對象

  • 儘可能建立不可變對象
  • 若某屬性儘可用於對象內部修改,則在 「class-continuation分類」 中將其由readonly屬性擴展爲readwrite屬性
  • 不要把可變對象的collection做爲屬性公開,而應提供相關方法,以此修改對象中的可變 collection

在開發自定義類時,在 .h 裏聲明的屬性儘可能設置爲不可變,只讀的屬性,外界只能經過特定的方法更改其內容,這對於一個功能的封裝性是相當重要的。例如咱們以前所聲明的 Student 類:安全

// .h
@interface Student : Chinese
@property (nonatomic, copy, readonly) NSString *school;
@property (nonatomic, strong, readonly) NSArray *homework;

- (void)addHomeworkMethod:(NSString *)homework;
- (void)removeHomeworkMethod:(NSString *)homework;
@end

// .m
@interface Student()
@property (nonatomic, copy) NSString *school;
@end
@implementation Student {
    NSMutableArray *p_homework;
}
- (void)addHomeworkMethod:(NSString *)homework {
    [p_homework addObject:homework];
}
- (void)removeHomeworkMethod:(NSString *)homework {
    [p_homework removeObject:homework];
}
- (instancetype)initWithSchool:(NSString *)school homework:(NSArray *)homework {
    if (self = [self init]) {
        self.school = school;
        p_homework = homework.mutableCopy;
    }
    return self;
}
@end
複製代碼

如此定義外界只能經過固定的方法對對象內的屬性進行更新,便於功能的封裝,減小 bug 出現的機率。 另外使用不可變對象也加強程序的執行效率。多線程

🇦🇬 第19條:使用清晰而協調的命名方式

  • 起名時應聽從標準的 Objective-C命名規範,這樣建立出來的接口更容易爲開發者所理解
  • 方法名要言簡意賅,從左至右讀起來要像個平常用語的句子纔好
  • 方法名裏不要使用縮略後的類型名稱
  • 給方法起名時的第一要務就是確保其風格與你本身的代碼或所要集成的框架相符

就是說在爲本身建立的屬性、成員變量、方法、協議等起名要見名知意。app

🇦🇹 第20條:爲私有方法名加前綴

  • 給私有方法的名稱加上前綴,這樣能夠很容易地將其同公共方法區分開
  • 不要單用一個下劃線作私有方法的前綴,由於這種作法是預留給蘋果公司用的

對於一個寫好的類而言,若爲公開方法更更名稱,則須要在外部調用此類的方法的地方一樣作修改,這樣比較麻煩,在類內部實現的私有方法不會有這個問題,因此爲私有方法加前綴可更好的區分二者。便於後期開發。用何種前綴取決於開發者的開發習慣,不建議使用下劃線開頭的前綴,由於這是Apple Dad 專屬的方式。做者的習慣是私有方法的前綴是 p_ ,例如:框架

/// 這是一個私有方法
- (id)p_playAirplaneMethod {
    id xx = @"**";
    return xx;
}
複製代碼

🇦🇽 第21條:理解 Objective-C 錯誤類型

  • 只有發生了可以使整個應用程序崩潰的嚴重錯誤時,才應使用異常
  • 在錯誤不那麼嚴重的狀況下,能夠指派 「委託方法」 來處理錯誤,也能夠把錯誤信息放在 NSError對象裏,經由「輸出參數」返回給調用者

在項目中能夠自定義一個錯誤類型模型:

//  .h
//  自定義錯誤類型
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, XWErrorCode) {
    XWErrorCodeUnknow       = -1, //未知錯誤
    XWErrorCodeTypeError    = 100,//類型錯誤
    XWErrorCodeNullString   = 101,//空字符串
    XWErrorCodeBadInput     = 500,//錯誤的輸入
};
extern NSString * const XWErrorDomain;
@interface XWError : NSError
+ (instancetype)errorCode:(XWErrorCode)errorCode userInfo:(NSDictionary *)userInfo;
@end

// .m
#import "XWError.h"
@implementation XWError
NSString * const XWErrorDomain = @"XWErrorDomain";
+ (instancetype)errorCode:(XWErrorCode)errorCode userInfo:(NSDictionary *)userInfo {
    XWError *error = [[XWError alloc] initWithDomain:XWErrorDomain code:errorCode userInfo:userInfo];
    return error;
}
@end
複製代碼

在調試程序合適的回調中可傳入自定義錯誤信息。

🇦🇺 第22條:理解 NSCopying 協議

  • 若想令本身所寫的對象具備拷貝功能,則需實現 NSCopying 協議
  • 若是自定義的對象分爲可變版本和不可變版本。那麼就要同時實現 NSCopying 協議 與 NSMutableCopying 協議
  • 賦值對象時需決定採用淺拷貝仍是深拷貝,通常狀況下應該儘可能執行淺拷貝
  • 若是你寫的對象須要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法

我想讓我建立的 Student 類具有拷貝屬性,那我須要實現 NSCopying 協議,實現它僅有的一個 - (id)copyWithZone:(nullable NSZone *)zone 方法。 以下:

@interface Student() <NSCopying>
@end
@implementation Student {
    NSMutableArray *p_homework;
}
#pragma mark - NSCopying
- (id)copyWithZone:(nullable NSZone *)zone {
    Student *stuCopy = [[Student allocWithZone:zone] initWithFirstName:self.firstName lastName:self.lastName age:self.age homework:p_homework.copy];
    return stuCopy;
}
複製代碼

如此在調用 Studentcopy 方法便會生成一個內容相同的不一樣 Student 對象

Student *stu = [Student studentWithFirstName:@"小極客" lastName:@"學偉" age:6 homework:@[@"小提琴",@"籃球"]];
Student *stu2 = [stu copy];
複製代碼

若但願自定義對象擁有 深拷貝 功能,那須要實現 NSMutableCopying 協議,並實現其惟一的方法 - (id)mutableCopyWithZone:(nullable NSZone *)zone 具體實現以下:

#pragma mark - NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
    Student *stuMtableCopy = [[Student allocWithZone:zone] initWithFirstName:self.firstName lastName:self.lastName.mutableCopy age:self.age homework:p_homework.copy];
    return stuMtableCopy;
}
複製代碼

補充一個 Array 和 Dictionary 分別指向淺複製和深複製以後的類型列表:

Array

首先聲明兩個數組:

NSArray *array = @[@1,@2];
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
複製代碼

對其進行淺拷貝和深拷貝,打印結果以下:

2018-08-01 11:46:32.255187+0800 XWInterviewDemos[80249:5837261] [array copy]:__NSArrayI
2018-08-01 11:46:32.255337+0800 XWInterviewDemos[80249:5837261] [array mutableCopy]:__NSArrayM
2018-08-01 11:46:32.255431+0800 XWInterviewDemos[80249:5837261] [mutableArray copy]:__NSArrayI
2018-08-01 11:46:32.255516+0800 XWInterviewDemos[80249:5837261] [mutableArray mutableCopy]:__NSArrayM
複製代碼

其中 __NSArrayI 爲不可變數組,__NSArrayM 爲可變數組,結論:

原類 操做 拷貝結果
NSArray 淺拷貝(copy) 不可變(__NSArrayI)
NSArray 深拷貝(mutableCopy) 可變(__NSArrayM)
NSMutableArray 深拷貝(copy) 不可變(__NSArrayI)
NSMutableArray 深拷貝(mutableCopy) 可變(__NSArrayM)
Dictionary

首先聲明兩個字典:

NSDictionary *dictionary = @{@"key":@"value"};
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary];
複製代碼

對其進行淺拷貝和深拷貝,打印結果以下:

2018-08-01 11:57:20.810019+0800 XWInterviewDemos[80385:5844478] [dictionary copy]:__NSSingleEntryDictionaryI
2018-08-01 11:57:20.810162+0800 XWInterviewDemos[80385:5844478] [dictionary mutableCopy]:__NSDictionaryM
2018-08-01 11:57:20.810277+0800 XWInterviewDemos[80385:5844478] [mutableDictionary copy]:__NSFrozenDictionaryM
2018-08-01 11:57:20.810374+0800 XWInterviewDemos[80385:5844478] [mutableDictionary mutableCopy]:__NSDictionaryM
複製代碼

其中 __NSSingleEntryDictionaryI__NSFrozenDictionaryM 爲不可變字典,__NSDictionaryM 爲可變字典,結論:

原類 操做 拷貝結果
NSDictionary 淺拷貝(copy) 不可變(__NSSingleEntryDictionaryI)
NSDictionary 深拷貝(mutableCopy) 可變(__NSDictionaryM)
NSMutableDictionary 深拷貝(copy) 不可變(__NSFrozenDictionaryM)
NSMutableDictionary 深拷貝(mutableCopy) 可變(__NSDictionaryM)

第4章:協議與分類

🇲🇴 第23條:經過委託與數據源協議進行對象間通訊

  • 委託模式爲對象提供了一套接口,使其可由此將相關事件告知其餘對象
  • 將委託對象應該支持的接口定義成協議,在協議中把可能須要處理的事件定義成方法
  • 當某對象須要從另外一個對象獲取數據時,可使用委託模式。這種情景下,該模式亦稱「數據源協議」
  • 如有必要,可實現有位移段的結構體,將委託對象是否能響應協議方法這一信息緩存至其中

委託與數據源協議咱們在使用 UITableView 時常常用到,咱們在開發時可仿照其設計模式,將須要的數據經過數據源獲取;將執行操做後的事件經過代理回調;並弱引用其代理對象。

@class Chinese;
@protocol ChineseDelegate <NSObject>
@optional
- (void)chinese:(Chinese *)chinese run:(double)kilometre;
- (void)chinese:(Chinese *)chinese didReceiveData:(NSData *)data;
- (void)chinese:(Chinese *)chinese didReceiveError:(NSError *)error;
@end

@interface Chinese : NSObject
// 委託對象-需弱引用
@property (nonatomic, weak) id<ChineseDelegate> delegate;
@end
複製代碼

在對象跑步時,經過代理方法回調給委託對象:

- (void)run {
    double runDistance = 0.0;
    if (self.delegate && [self respondsToSelector:@selector(chinese:run:)]) {
        [self.delegate chinese:self run:runDistance];
    }
}
複製代碼

假若此方法每分鐘都會調用 成百上千次,每次都執行 respondsToSelector 方法不免會對性能有必定影響,由於除第一次有效外其他都是重複判斷,因此咱們能夠將是否可以響應此方法進行緩存!如例所示:

#import "Chinese.h"
@interface Chinese() {
    /// 定義一個結構體擁有三個位段,分別存儲是否實現了三個對應的代理方法
    struct {
        unsigned int didReceiveData     : 1;    //是否實現 didReceiveData
        unsigned int didReceiveError    : 1;    //是否實現 didReceiveError
        unsigned int didRun             : 1;    //是否實現 run
    }_chineseDelegateFlags;
}
@end
@implementation Chinese
/// 重寫 Delegate 方法,爲 位段進行賦值
- (void)setDelegate:(id<ChineseDelegate>)delegate {
    _delegate = delegate;
    _chineseDelegateFlags.didRun = [delegate respondsToSelector:@selector(chinese:run:)];
    _chineseDelegateFlags.didReceiveData = [delegate respondsToSelector:@selector(chinese:didReceiveData:)];
    _chineseDelegateFlags.didReceiveError = [delegate respondsToSelector:@selector(chinese:didReceiveError:)];
}
/// 在調用delegate 的相關協議方法再也不進行方法查詢,直接取結構體位段存儲的內容進行調用
- (void)run {
    double runDistance = 0.0;
    if (_chineseDelegateFlags.didRun) {
        [self.delegate chinese:self run:runDistance];
    }
    ///if (self.delegate && [self respondsToSelector:@selector(chinese:run:)]) {
    ///    [self.delegate chinese:self run:runDistance];
    ///}
}
複製代碼

若代理方法可能回調屢次,那此項優化將大大提高程序運行效率!

🇧🇧 第24條:將類的實現代碼分散到便於管理的數個分類之中

  • 使用分類機制把類的實現代碼劃分紅易於管理的小塊
  • 將應該視爲「私有」的方法納入名叫 Private 的分類,可隱藏實現細節

在開發一個類通常將全部的代碼都放在一塊兒,即使都是高聚合低耦合的代碼,若程序愈來愈大,不免也會感受不優雅,優雅的方式是按照功能將實現抽離到不一樣的分類中實現,在主類中引入其分類,直接調用分類中實現的方法。這樣也便於管理。 根據分類的名稱,可快速定位代碼所屬功能區,便於擴展維護。另外可建立一個所開發類名對應的 Private 分類,存放一些私有方法。這些方法無需暴露給外界,開發者本身維護。

🇵🇬 第25條:老是爲第三方類的分類名稱加前綴

  • 向第三方類中添加分類時,總應給其名稱加上你專用的前綴
  • 向第三方類中添加分類時,總應給其中的方法名加上你專用的前綴

分類中所實現的方法最終會在編譯時加載到本類的方法列表中,若存在相同名稱的方法,後編譯的分類會覆蓋前編譯的,因此爲分類中的方法加前綴是頗有必要的。

🇧🇸 第26條:勿在分類中聲明屬性

  • 把封裝數據所用的所有屬性都定義在主接口裏
  • 在 「class-continuation 分類「 中,能夠定義存取方法,但儘可能不要定義屬性

本來分類中聲明屬性僅僅是自動生成該屬性 getter 方法和 setter 方法的聲明,不會生成成員變量和對應屬性的getter 方法和 setter 方法 雖然 可使用 runtime 的關聯對象的方式爲分類添加屬性 getter 方法和 setter 方法的實現,使得分類可以定義屬性。 可是分類的本質在於擴展類的功能,而非封裝數據。使用上述方式須要寫大量類似的代碼,而且在內存管理上容易出錯,改動屬性的類型須要改變關聯對象的相關類型,不利於維護,代碼不優雅!

🇵🇰 第27條:使用 「class-continuation 分類」 隱藏實現細節

  • 使用 「class-continuation 分類」 向類中新增實例變量
  • 若是某屬性在主接口中聲明爲 「只讀」,而類的內部又要用設置方法修改此屬性,那麼就在 「class-continuation 分類」 中將其擴展爲 「可讀寫」
  • 把私有方法的原型聲明在 「class-continuation 分類」 裏面
  • 若想使類所遵循的協議不爲人所知,則可於 「class-continuation 分類」 中聲明

例如:

// .h  對外聲明爲只讀,防止外界隨意修改
@interface Chinese : NSObject
@property (nonatomic, weak) id<ChineseDelegate> delegate;
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@end

// .m 對內聲明爲可讀寫。使用擴展聲明一些外界不得而知的私有成員變量
@interface Chinese() <NSCopying> {
    struct {
        unsigned int didReceiveData     : 1;    //是否實現 didReceiveData
        unsigned int didReceiveError    : 1;    //是否實現 didReceiveError
        unsigned int didRun             : 1;    //是否實現 run
    }_chineseDelegateFlags;
    
    NSString *p_girlFriend; 
}
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end
複製代碼

或者在 「class-continuation 分類」 裏面聲明 C++ 對象,這樣 .m 編譯爲 .mm 文件,對外界暴露依然是純正的 OC 接口,在 Foundation 框架中,常用此策略。

🇵🇾 第28條:經過協議提供匿名對象

  • 協議可在某種程度上提供匿名類型。具體的對象類型能夠淡化成聽從某協議的id類型,協議裏規定了對象所應實現的方法
  • 使用匿名對象來隱藏類型名稱(或類名)
  • 若是具體類型不重要,重要的是對象可以響應(定義在協議裏的)方法,那麼可以使用匿名對象來表示

第5章:內存管理

🇵🇸 第29條:理解引用計數

  • 引用計數機制經過能夠遞增遞減的計數器來管理內存。對象建立好以後,其保留技術至少爲1。若保留計數爲正,則對象繼續存活。當保留計數降爲0時,對象就被銷魂了。
  • 在對象生命期中,其他對象經過引用來保留或釋放此對象。保留與釋放操做分別會遞增及遞減保留計數

何爲引用計數,用一張圖表示即是:

Snip20180801_8
圖轉自 《Objective-C高級編程+iOS與OS+X多線程和內存管理》圖1.3

看完此圖差很少已經理解引用計數了,OK,本條完結。。。

另外,補充一個概念-自動釋放池 使用自動釋放池可以使對象的生命週期跨越 「方法調用邊界」後存活到 runloop 的下一次事件循環。

🇧🇭 第30條:以 ARC 簡化引用計數

  • 有ARC以後,程序員就無須擔憂內存管理問題了,使用ARC來編程,可省去類中的許多 「樣板代碼」
  • ARC 管理對象生命週期的辦法基本上就是:在合適的地方插入「保留」及「釋放」操做。在ARC環境下,變量的內存管理語義能夠經過修飾符指明,則原來則須要手工執行「保留」及「釋放」操做
  • 由方法返回的對象,其內存管理語義老是經過方法名來體現。ARC將此肯定爲開發者必須遵照的規則
  • ARC只負責管理 Objective-C 對象的內存。尤爲要注意:CoreFoundation 對象不歸ARC 管理,開發者必須適時調用 CFRetain/CFRelease

ARC會以一種安全的方式設置:先保留新值,再釋放舊值,最後設置實例變量,其中可使用如下修飾符改變局部變量和實例變量的語義:

__strong 默認語義,保留此值 __unsafe_unretained 不保留此值,這麼作可能不安全,由於等再次使用變量時,其對象可能已經回收了 __weak 不保留此值,可是變量可安全使用,由於若是系統把這個對象回收了,那麼變量也會自動清空 - 可避免循環引用 __autoreleasing 把對象「按引用傳遞」給方法時,使用這個特殊的修飾符。此值在方法返回時自動釋放

🇵🇦 第31條:在 dealloc 方法中只釋放引用並解除監聽

  • dealloc 方法裏,應該作的事情就是釋放指向其餘對象的引用,並取消原來訂閱的「簡直觀測」(KVO)或 NSNotificationCenter 等通知,不要作其餘事情
  • 若是對象持有文件描述符等系統資源,那麼應該專門編寫一個方法來解釋此種資源。這樣的類要和其使用者約定:用完資源後必須調用 close 方法
  • 執行異步任務的方法不該在 dealloc 裏調用;只能在正常狀態下執行的那些方法也不該在 dealloc 裏調用,由於此時對象已處於正在回收的狀態了

dealloc 方法是對象釋放所調用的方法,此時若使用對象的成員變量可能已經被釋放掉了,若使用異步回調時自身已經被釋放,若回調中包含 self 會致使程序崩潰。

- (void)dealloc {
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    // 釋放需手動釋放的資源
    //CFRelease(coreFoundationObject);
}
複製代碼

另外 即使對象釋放,在極個別狀況下並不會調用 dealloc 方法,程序終止時必定會調用的是在 application delegate 的 - (void)applicationWillTerminate:(UIApplication *)application 方法, 若必定要清理某些對象,可在此方法中處理。

🇧🇷 第32條:編寫「異常安全代碼」時留意內存管理問題

  • 捕獲異常時,必定要注意將 try 塊內所創立的對象清理乾淨
  • 在默認狀況下,ARC不生成安全處理異常所需的清理代碼,開啓編譯器標誌後,可生產這種代碼,不過會致使應用程序變大,並且會下降運行效率

🇧🇾 第33條:以弱引用避免保留環

  • 將某些引用設爲 weak,可避免出現 「保留環」
  • weak 引用能夠自動清空,也能夠不自動清空。自動清空是隨着ARC而引入的新特性,由運行期系統來實現。在具有自動清空功能的弱引用上,能夠隨意讀取其數據,由於這種引用不會指向已經回收過的對象

若兩個對象互相引用,會造成保留環(循環引用),如圖:

Snip20180802_9

保留環會引發內存泄露,對象間的互相持有致使保留環內的全部對象均沒法正常釋放。 避免保留環最佳方式是弱引用,經過「非擁有關係」的聲明將環打破。這種關係可用 weakunsafe_unretained 實現。二者的區別是 weak 修飾的對象在釋放以後自己會置 nil, 而 unsafe_unretained 不會,在對象釋放以後會依然指向被釋放的那塊內存。如圖:

Snip20180802_11

@property (nonatomic, weak) id<ChineseDelegate> delegate;

 __weak typeof(self) weakSelf = self;
[NSTimer xw_timerTimeInterval:1.0 block:^{
    [weakSelf timerMethod];
} repeats:YES];
複製代碼

🇧🇲 第34條:以「自動釋放池塊」下降內存峯值

  • 自動釋放池排布在棧中,對象收到 autorelease消息後,系統將其放入最頂端的池裏。
  • 合理利用自動池,可下降應用程序的內存峯值
  • @autoreleasepool 這種新式寫法能建立出更爲輕便的自動釋放池

主線程和GCD機制中的線程默認都會有自動釋放池,無需程序員手動建立,而且系統會自動在 runloop 的執行下次時間循環時將池內對象清空。 若是在一個大的循環體中須要建立n多個對象時,使用 「自動釋放池塊」 可下降內存峯值,如例所示:

未作優化的方式:

- (void)testFor1 {
    NSMutableArray *arrayM = [NSMutableArray array];
    for (int i = 0; i < 100000; i++) {
        NSString *str = [NSString stringWithFormat:@"%d",i];
        [arrayM addObject:str];
        NSLog(@"%@",str);
    }
}
複製代碼

此時內存使用狀況:

Snip20180802_13
CPU使用狀況:
Snip20180802_14

使用 「自動釋放池塊」 優化的方式:

- (void)testFor2 {
    NSMutableArray *arrayM = [NSMutableArray array];
    for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"%d",i];
            [arrayM addObject:str];
            NSLog(@"%@",str);
        }
    }
}
複製代碼

優化後內存使用狀況:

Snip20180802_15

優化後CPU使用狀況:

Snip20180802_16

顯而易見根據Xcode 顯示**:並無什麼卵用**,此條原理上是能夠下降內存佔用峯值,但實際狀況確實二者沒有太大區別,可否起到優化的做用還需往後繼續觀察...

🇧🇬 第35條:用「殭屍對象」調試內存管理問題

  • 系統在回收對象時,能夠不將其真的回收,而是把它轉化爲殭屍對象。經過環境變量 NSZombieEnabled可開啓此功能
  • 系統會修改對象的 isa 指針,令其指向特殊的殭屍類,從而使該對象變爲殭屍對象。殭屍類可以響應全部的選擇子,響應方式爲:打印一條包含消息內容及其接受者的消息,而後終止應用程序

在Xcode 中勾選 Zombie Objects 可啓用殭屍對象檢測,此時給殭屍對象發送消息將會在控制檯打印相關信息:

Snip20180802_1

🇲🇵 第36條:不要使用 retainCount

  • 對象的保留計數看似有用,實則否則,由於任何給定時間點上的「絕對保留計數」都沒法反映對象生命期的全貌
  • 引入 ARC 以後,retainCount 方法就正式廢止了,在ARC下調用該方法會致使編譯器報錯。

在 ARC 時代下自己就不會使用 retainCount, 書中所講述的幾種狀況僅出如今 MRC 編程環境下, 例如 retainCount 可能不爲 0 的時候對象就會被系統釋放,因此 retainCount 引用計數可能永遠不爲0,這是系統優化對象的釋放行爲所致使的。

前五章完結,後續幾天會陸續發表其他篇章的讀書/實戰筆記,筆者期待和衆大神一塊兒學習,共同進步。

未完待續...

相關文章
相關標籤/搜索