##《Effective Objective-C 2.0》讀書/實戰筆記 二程序員
顧名思義就是說在本身開發的類須要加前綴,iOS程序員開發工程師廣泛使用雙字母的前綴,就像我在開發時習慣加前綴 XW
,其實,這是不科學的,由於蘋果爸爸公司保留使用全部「兩字母前綴」的權利,因此本身的前綴應該是三個字母的,不只僅是類名,還有分類、全局變量...objective-c
舉一個生動形象的例子: 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
複製代碼
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
方法。緩存
在開發自定義類時,在 .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 出現的機率。 另外使用不可變對象也加強程序的執行效率。多線程
就是說在爲本身建立的屬性、成員變量、方法、協議等起名要見名知意。app
對於一個寫好的類而言,若爲公開方法更更名稱,則須要在外部調用此類的方法的地方一樣作修改,這樣比較麻煩,在類內部實現的私有方法不會有這個問題,因此爲私有方法加前綴可更好的區分二者。便於後期開發。用何種前綴取決於開發者的開發習慣,不建議使用下劃線開頭的前綴,由於這是Apple Dad 專屬的方式。做者的習慣是私有方法的前綴是 p_
,例如:框架
/// 這是一個私有方法
- (id)p_playAirplaneMethod {
id xx = @"**";
return xx;
}
複製代碼
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
複製代碼
在調試程序合適的回調中可傳入自定義錯誤信息。
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;
}
複製代碼
如此在調用 Student
的 copy
方法便會生成一個內容相同的不一樣 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 分別指向淺複製和深複製以後的類型列表:
首先聲明兩個數組:
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) |
首先聲明兩個字典:
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) |
委託與數據源協議咱們在使用 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];
///}
}
複製代碼
若代理方法可能回調屢次,那此項優化將大大提高程序運行效率!
Private
的分類,可隱藏實現細節在開發一個類通常將全部的代碼都放在一塊兒,即使都是高聚合低耦合的代碼,若程序愈來愈大,不免也會感受不優雅,優雅的方式是按照功能將實現抽離到不一樣的分類中實現,在主類中引入其分類,直接調用分類中實現的方法。這樣也便於管理。 根據分類的名稱,可快速定位代碼所屬功能區,便於擴展維護。另外可建立一個所開發類名對應的 Private 分類,存放一些私有方法。這些方法無需暴露給外界,開發者本身維護。
分類中所實現的方法最終會在編譯時加載到本類的方法列表中,若存在相同名稱的方法,後編譯的分類會覆蓋前編譯的,因此爲分類中的方法加前綴是頗有必要的。
本來分類中聲明屬性僅僅是自動生成該屬性 getter
方法和 setter
方法的聲明,不會生成成員變量和對應屬性的getter
方法和 setter
方法 雖然 可使用 runtime
的關聯對象的方式爲分類添加屬性 getter
方法和 setter
方法的實現,使得分類可以定義屬性。 可是分類的本質在於擴展類的功能,而非封裝數據。使用上述方式須要寫大量類似的代碼,而且在內存管理上容易出錯,改動屬性的類型須要改變關聯對象的相關類型,不利於維護,代碼不優雅!
例如:
// .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 框架中,常用此策略。
何爲引用計數,用一張圖表示即是:
看完此圖差很少已經理解引用計數了,OK,本條完結。。。
另外,補充一個概念-自動釋放池 使用自動釋放池可以使對象的生命週期跨越 「方法調用邊界」後存活到 runloop 的下一次事件循環。
ARC會以一種安全的方式設置:先保留新值,再釋放舊值,最後設置實例變量,其中可使用如下修飾符改變局部變量和實例變量的語義:
__strong
默認語義,保留此值 __unsafe_unretained
不保留此值,這麼作可能不安全,由於等再次使用變量時,其對象可能已經回收了 __weak
不保留此值,可是變量可安全使用,由於若是系統把這個對象回收了,那麼變量也會自動清空 - 可避免循環引用 __autoreleasing
把對象「按引用傳遞」給方法時,使用這個特殊的修飾符。此值在方法返回時自動釋放
dealloc
方法中只釋放引用並解除監聽dealloc
方法裏,應該作的事情就是釋放指向其餘對象的引用,並取消原來訂閱的「簡直觀測」(KVO)或 NSNotificationCenter
等通知,不要作其餘事情close
方法dealloc
裏調用;只能在正常狀態下執行的那些方法也不該在 dealloc
裏調用,由於此時對象已處於正在回收的狀態了dealloc
方法是對象釋放所調用的方法,此時若使用對象的成員變量可能已經被釋放掉了,若使用異步回調時自身已經被釋放,若回調中包含 self
會致使程序崩潰。
- (void)dealloc {
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 釋放需手動釋放的資源
//CFRelease(coreFoundationObject);
}
複製代碼
另外 即使對象釋放,在極個別狀況下並不會調用 dealloc
方法,程序終止時必定會調用的是在 application delegate 的 - (void)applicationWillTerminate:(UIApplication *)application
方法, 若必定要清理某些對象,可在此方法中處理。
weak
,可避免出現 「保留環」weak
引用能夠自動清空,也能夠不自動清空。自動清空是隨着ARC而引入的新特性,由運行期系統來實現。在具有自動清空功能的弱引用上,能夠隨意讀取其數據,由於這種引用不會指向已經回收過的對象若兩個對象互相引用,會造成保留環(循環引用),如圖:
保留環會引發內存泄露,對象間的互相持有致使保留環內的全部對象均沒法正常釋放。 避免保留環最佳方式是弱引用,經過「非擁有關係」的聲明將環打破。這種關係可用 weak
和 unsafe_unretained
實現。二者的區別是 weak
修飾的對象在釋放以後自己會置 nil, 而 unsafe_unretained
不會,在對象釋放以後會依然指向被釋放的那塊內存。如圖:
@property (nonatomic, weak) id<ChineseDelegate> delegate;
__weak typeof(self) weakSelf = self;
[NSTimer xw_timerTimeInterval:1.0 block:^{
[weakSelf timerMethod];
} repeats:YES];
複製代碼
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);
}
}
複製代碼
此時內存使用狀況:
使用 「自動釋放池塊」 優化的方式:
- (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);
}
}
}
複製代碼
優化後內存使用狀況:
優化後CPU使用狀況:
顯而易見根據Xcode 顯示**:並無什麼卵用**,此條原理上是能夠下降內存佔用峯值,但實際狀況確實二者沒有太大區別,可否起到優化的做用還需往後繼續觀察...
NSZombieEnabled
可開啓此功能isa
指針,令其指向特殊的殭屍類,從而使該對象變爲殭屍對象。殭屍類可以響應全部的選擇子,響應方式爲:打印一條包含消息內容及其接受者的消息,而後終止應用程序在Xcode 中勾選 Zombie Objects 可啓用殭屍對象檢測,此時給殭屍對象發送消息將會在控制檯打印相關信息:
retainCount
retainCount
方法就正式廢止了,在ARC下調用該方法會致使編譯器報錯。在 ARC 時代下自己就不會使用 retainCount
, 書中所講述的幾種狀況僅出如今 MRC 編程環境下, 例如 retainCount
可能不爲 0 的時候對象就會被系統釋放,因此 retainCount
引用計數可能永遠不爲0,這是系統優化對象的釋放行爲所致使的。
前五章完結,後續幾天會陸續發表其他篇章的讀書/實戰筆記,筆者期待和衆大神一塊兒學習,共同進步。
未完待續...