1. 了解 Objective-C 語言的起源
Objective-C 語言使用”消息結構”而非”函數調用”.Objective-C 語言由 Smalltalk演化而來,后者是消息類型語言的鼻祖.編譯器甚至不關心接收消息對象的何種類型.接收消息的對象問題也要在運行時處理,其過程叫做”動態綁定”.
Objective-C為 C 語言添加了面向對象特性,是其超類. Objective-C 使用動態綁定的消息結構,也就是說,在運行時才會檢查對象類型.接收一條消息后,究竟應執行何種代碼,有運行期環境而非編譯器決定.理解 C 語言的核心有助于寫好 Objective-C 程序.尤其是掌握內存模型與指針.
2. 在類的頭文件中盡量少引用其他頭文件
Objective-C 語言編寫類的標準行為:以類名做文件名,分別闖將兩個文件,有文件后綴用. h,實現文件后綴用. m.
在開發中有時候我們會在. h 文件中引入很多用不到的內容,這當然會增加編譯時間.除非有必要,否則不要引入頭文件,一般來說,某個類的頭文件中使用向前聲明來體積別的類,并在實現文件中引入哪些類的頭文件,這樣做可以盡量降低類之間的耦合.有時無法使用前聲明,比如要聲明某個類遵循意向協議,這種情況下,盡量把 “該類遵循某協議”的這條聲明移至 class=continuation 分類中,如果不行的話,就把協議單獨存放在一個頭文件中,然后將其引入.
3. 多用字面量語法,少用與之等價的方法
NSArray *arr = [NSArray arrayWithObjects:@"num1",@"num2",@"num3", nil];
NSArray *arr = @[@"num1",@"num2",@"num3”];
字面量語法創建字符串,數組,數值,字典.與穿件此類對象的常規方法相比,這么做更加簡明扼要.
注意事項:
- 除了字符串以外,所創建的類必須屬于 Foundation 框架才行,如果自定義了這些類的子類,則無法用字面量語法創建其對象.
- 穿件的數組或字典時,若值有 nil, 則會拋出異常.因此,務必確保值中不含 nil.
4. 多用類型常量,少用# deine 預處理指令
不要用預處理指令定義常量,這樣定義出來的常量不含類型信息,編譯器只會在編譯前根據執行查找與替換操作,即使有人重新定義了常量值,編譯器也不會產生井道信息,這將導致應用程序常量值不一致.
static NSString *const PersonConstant = @"PersonConstantStr” ;
但是我個人認為其實,還是#define用的多, 開發避免不了使用 pch文件. 如果有強迫癥的同學,定義常量就想使用 staitc,extren,const這些關鍵字.那我建議新建一個專門存放這些常量的類,然后在 pch 中導入這個類.
- static 修飾符意味著該變量盡在定義此變量的單元中可見
- extern 全局變量
Person.h
#import <Foundation/Foundation.h>
static NSString *const PersonConstant;
@interface Person : NSObject
@end
Person.m
#import "Person.h"
static NSString *const PersonConstant = @"PersonConstantStr";
@implementation Person
@end
.pch
#define DefineStr @"DefineStr"
#import "Person.h"
#endif
5. 用枚舉表示狀態,選項,狀態碼
應該用枚舉來表示狀態機的狀態,傳遞給方法的選項以及狀態碼等值,給這些值起個易懂的名字
enum PersonEnum{
PersonEnumNum1,
PersonEnumNum2,
PersonEnumNum3,
};
typedef enum PersonEnum PersonState;
6. 理解屬性這一概念
屬性是 Objective-C 的一項特性,用于封存對象中的數據.
屬性特質:原子性 讀寫權限
內存管理語義
- assign 這是方法只會執行針對純量類型(CGFloat,NSInteger)的簡單賦值操作
- strong 此特質表明該屬性定義一種擁有關系,為這種屬性設置新值時,這只方法會先保存新值,并釋放舊值
- weak 此特質表明屬性定義了一種”非擁有關系”,為這種屬性設置新值是,設置方法既不保留新值,也不釋放舊值.此特質同 assign 類似,然而在屬性所指對象遭到摧毀時,屬性值會清空
- unsafe_unretainde 此特質與 assign 相同,它適用于對象類型,該特質表達一種"非擁有關系”,當目標對象遭到摧毀時,屬性不會自動清空,因為它是不安全的,這一點與 weak 的區別
- copy 此特質所表達的所屬關系與 strong 類似,然而設置方法并不保留新值,而是將其拷貝,多用于 NSString.
7. 在對象內部盡量直接訪問實例變量
直接訪問實例變量的速度比較快,因為不經過 Objective-C 方法派發,編譯器所生成的代碼會直接訪問保存催下實例量的那塊內存.
直接訪問實例變量時,不會調用設置方法,這就繞過了相關屬性所定義的內存管理語義.
8. 理解"對象等同性”這一概念
根據等同性來比較對象是一個非常有用的功能,不過,按照==操作符比較出來的結果未必是我們想要的,因為該操作比較的事兩個指針本身,而不是其所指的對象.應該使用 NSObject 協議中的聲明的”isEqual”方法來判斷兩個對象的等同性,一般來說兩個類型不同的對象總是不相等的.直接比較字符串的時候 isEqual 比 isEqualToString慢,因為前者還要執行額外步驟.
9. 以"類族模式"隱藏實現細節
“類族”是一種很用用的模式,可以隱藏抽象基類背后實現的細節. 這是一種”工廠模式”.比如iOS 用戶界面框架 UIKit 中就有一個名為 UIButton 的類.想創建按鈕,需要調用下面這個類方法.
+(UIButton*)buttonWithType:(UIButtonType)type;
我到是認為,作者想告訴我我們要好好封裝代碼,這個地方沒啥好說的.
10. 在既有類中使用關聯對象存放自定義數據
有時需要在對象中存放相關信息,這是我們通常會從對象所屬的類中繼承一個子類,然后改用這個子類對象.然而并非所有情況下都這么做,有時候類的實例可能是由某種機制所創建的,而開發者無法令這種機制創建出自己所寫的實例. Objective-C 中有意向強大的特性可以解決問題,這就是關聯對象.
11. 理解objc_msgSend的作用
用Objetive-C的術語來說,這叫做“消息傳遞”。這里說的是運行時。
12. 理解消息轉發機制
當對象接收到無法解讀的消息后,就會啟動消息轉發機制,程序員可經此過程告訴對象應該圖和處理未知消息。這里說的是運行時。
13. 用方法調配技術調試黑盒方法
運行期間,可以向類中新增或替換選擇子所對應的方法實現。
使用另一份實現來替換原有的方法實現,這道工序叫做方法調配,開發者常用此技術想原有實現中添加新功能。
一般來說,只有調試程序的時候才需要運行期修改方法實現,這種做法不易濫用。這里說的是運行時。
14. 理解類對象的用意
每個Objective-C對象實例都是指向某塊內存數據的指針,如果把對象所需的內存分配到棧上編譯器就會報錯
每個對象結構體的首個成員是Class類的變量,該變量定義了對象所屬的類,通常稱為isa指針。
typedef struct objc_class *class
struct objc_class{
Class isa;
Class super_class;
const char* name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list *ivars;
struct objc_cache *cache;
struct objc_protocol_list protocols;
}
此結構體存放類的元數據,例如類的實例實現了幾個方法,具備多少個實例變量等信息。次結構體的首個變量也是isa指針,這說明Class本身亦為Objctive-C對象。結構體里還有個變量叫做super_class,它定義本類的超類,類對象所屬的類型(isa指針所指向的類型)是另外一個類,叫做元類,用來標書類本身所具備的元數據。類方法就定義于此處,因為這些方法可以理解成類對象的實例方法,每個類僅有一個類對象,每個類對象僅有一個與之相關的元類。 (元數據,就是這個類的數據。)
isKindOfClass:能夠判斷對象是否為某類或其派生類的實例
isMemberOfClass: 能夠判斷出對象是否為某個特定類的實例
15. 用前綴避免命名空間沖突
這個沒啥可說的
16. 提供全能初始化方法
這個沒啥可說的
17. 實現description方法
調試程序時經常需要打印并查看對象信息。description 很實用。
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,assign)int age;
@property(nonatomic ,copy)NSString* name;
@end
Person.m
#import "Person.h"
@implementation Person
- (NSString *)description
{
return [NSString stringWithFormat:@"name %@ , age %d", self.name, self.age];
}
@end
description寫出想要的屬性, 在ViewController打印 Person 對象 Person的相關屬性就會出現在控制臺。
18. 盡量使用不可變對象
這個沒啥可說的
19. 使用清晰而協調的全名方式
這個沒啥可說的
20. 為私有方法名加前綴
這個沒啥可說的
21. 理解Objective-C錯誤模型
NSError的用法更加靈活,因此經由此對象,我們可以把導致錯誤的原因匯報給調用者。
- NSError domain(錯誤范圍,其類型為字符串)
錯誤發生的范圍,也就是產生錯誤的根源,通常用一個特有的全局變量來定義,比方說 “處理URL子系統”從URL的解析獲取數據時如果出錯了,那么就會使用NSURLErrorDomain來表示錯誤范圍 - Error code(錯誤碼,其類型為整數)
獨有的錯誤代碼,用以指明在某個范圍內具體發生了何種錯誤。某個特性范圍內可能會發生一系列相關錯誤,這些錯誤情況通常采用enum來定義。例如,當HTTP請求出錯時,可能會把HTTP狀態碼設為錯誤碼 - User info(用戶信息,其類型為字典)
有關錯誤的額外信息,其中或許包含一段“本地化的描述”或許還含有導致錯誤發生的另外一個錯誤,經由此種信息,可將相關錯誤串成一條“錯誤鏈”
@try {
NSString *str = @"wotaxiwa";
NSString *strErr = [str substringFromIndex:100];
NSLog(@"%@",str);
} @catch (NSException *exception) {
NSLog(@"ERROR: %@",exception);
} @finally {
NSLog(@"%s",__func__);
}
如果出現exception,異常后面的代碼將不會繼續執行
22. 理解NSCopying協議
copy方法實際上是調用 -(id)copyWithZone:(NSZone*)zone; 實現copy操作, 如果想對自己的類支持拷貝并且做額外操作,那就要實現NSCopying協議此的方法。
為何出現NSZone呢,以前開發程序時,會據此把內存分成不用的區,而對象會創建在某個區。 現在不用了,每個程序只有一個區:“默認區”,所以不用擔心zone參數。
copy方法由NSObject實現,該方法只是以默認區為參數調用。mutableCopy方法實際上是調用 -(id)mutableCopyWithZone:(NSZone*)zone; 實現mutableCopy操作
copy mutableCopy 我認為就是原型設計模式不明白的同學請看這個網頁
23. 通過委托與數據協議進行對象間通信
這一條說的就是delegate(代理設計模式)。但是并沒有說delegate的循環引用的問題,在使用代理聲明一個 @property的時候,記得用weak。
@protocol PersonDelegate <NSObject>
-(void)areYouPerson;
@end
@property (nonatomic,weak)id<PersonDelegate> pd;
24. 將類的實現代碼分散到便于管理的數個分類之中
這個沒啥可說的
25. 總是為第三方類的分類名稱加前綴
這個沒啥可說的
26. 勿在分類中聲明屬性
正常的分類是不可以聲明屬性的,但是從技術上說,分類里可以用runtime聲明屬性。
#import <objc.runtime.h>
static const char *kFriendsPropertyKey = “kFriendsPropertyKey”;
@implementation EOCPerson(Friendship)
-(NSArray*)friends{
return objc_getAssociatedObject(self,kFriendsPropertyKey);
}
-(void)setFriends:(NSArray*)friends{
objc_setAssociateObject(self.kFriendsPropertyKey,friends,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
這樣做可行,但是不太理想,要吧相似的代碼寫很多遍。而且容易出現Bug,可以使用class-continuation實現分類添加屬性。
27. 使用class-continuation分類隱藏實現細節
class-continuation分類和普通的分類不同,它必須在其所接續的那個類的實現文件里。其重要之處在于,這是唯一能生命實例變量的分類,而且此分類沒有特定的實現文件,其中的方法應該定義在類的主實現文件里。與其他分類不用,“class-continuation分類”沒有名字,比如,有個類叫做EOCPerson,其“class-continuation分類”寫法如下:
@interface EOCPerson()
@end
我們在創建一個類的時候系統已經自動幫我們在,m中實現了,以下代碼都應該很熟悉吧。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
沒錯它就是 class-continuation分類,在此代碼之間可以添加屬性,修改屬性。
@interface ViewController ()
@end
使用class-continuation分類的好處
- 可以向類中新增實例變量。
- 如果類中的主接口聲明為只讀,可以再類內部修改此屬性。
- 把私有方法的原型文件生命在”class-continuation分類”里面。
- 想使類遵循的協議不為人知,可以用“class-continuation分類”中聲明。
28. 通過協議提供匿名對象
這個沒啥可說的
說的就是這句話
@property (nonatomic,weak)id<PersonDelegate> pd;
29. 理解引用計數
理解引用計數,方便于了解iOS的內存管理。不過現在都是ARC的時代了。
引用計數機制通過可以遞增遞減的計數器來管理內存。對象創建好之后,其保留計數至少為1。若保留計數為正,則對象繼續存活。當保存計數降為0,對象就被銷毀了。
在對象生命期中,其余對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增及遞減保留計數。
30. 以ARC簡化引用計數
使用ARC要計數,引用計數實際上還是要執行的,只不過保留與釋放操作現在由ARC自動為你添加。由于ARC會自動執行retain、release、autorelease等操作,所以直接在ARC下調用這些內存管理方法是非法的。
ARC在調用這些方法時,并不用過普通的Objective-C消息派發機制,二十直接調用其底層C語言版本,這樣做性能更好,直接調用底層函數節省很多CPU周期。
雖然有了ARC之后無需擔心內存管理問題,但是CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRelease.
31. 在dealloc方法中只釋放引用并解除監聽
當一個對象銷毀的時候會調用dealloc方法,但是當開銷較大或系統內稀缺資源則不再此列,像是文件描述、套接字、大塊內存等都屬于這種資源,通常對于開銷較大的資源實現一個方法,當程序用完資源對象后,就調用此方法。這樣一來,資源對象的生命期就變得明確了。
32.編寫“異常安全代碼”時留意內存管理問題
圖不重要看字。
圖片中的情景是MRC。現在已經是ARC時代了。但是在使用@try 的時候也要注意,在捕獲到異常的時候@try{}中的語句執行到異常代碼的那一行后不在執行,然后把異常拋給@catch。當然@finally是一定要執行的。
如下 并沒有執行代碼中的NSLog(@"%@",array);
@try {
NSArray *array = @[@"a",@"b",@"c"];
NSString *str_arr = array[4];
NSLog(@"%@",array);
} @catch (NSException *exception) {
NSLog(@"%@",exception);
} @finally {
NSLog(@"%s",__func__);
}
33. 以弱引用避免保留環
- unsafe_unretained 語義同assign等價。然而assign通常用于int、float、結構體等。unsafe_unretained多用于對象類型。
- weak 與 unsafe_unretained 作用相同,然而只要系統把屬性回收,屬性值為nil。
推薦使用weak,畢竟是ARC時代的產物,而且用的人也很多。
34. 以“自動釋放池塊”降低內存峰值
@autoreleasepool {
<#statements#>
}
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSNumber *num = [NSNumber numberWithInt:i];
NSString *str = [NSString stringWithFormat:@"%d ", i];
[NSString stringWithFormat:@"%@%@", num, str];
if(lagerNum-1 == i)
{
NSLog(@"end");
}
}
}
由此可見我們合理運用自動釋放池,可降低應用程序的內存峰值。
35. 用“僵尸對象"調試內存管理問題
向已回收的對象發送消息是不安全的。
在左上角標題欄找到項目單擊后選擇 Edit scheme 勾選圖中檢測僵尸對象
36. 不要使用retainCount
這個沒啥可說的
MRC時代的產物,忽略
37. 理解”塊“這一概念
這里其實就是在說block,復習一下block的語法吧
返回值類型(block名稱)(參數)
需要注意的是 定義block時候,其所占內存區域是分配在棧中的,快只在定義它的那個范圍內有效。block所使用的整個內存區域,在編譯期已經完全確定,因此,全局block可以生命在全局內存里,而不需要在每次用到的時候于棧中創建,另外,全局block的拷貝是個空操作,因為全局block絕不可能為系統所回收,這種block實際上相當于單例。
38. 為常用的塊類型創建typedef
這個沒啥可說的
typedef <#returnType#>(^<#name#>)(<#arguments#>);
@property (nonatomic,copy)name nm_blk;
39. 用handler塊降低代碼分散程度
這條書上說的就是block的回調。只不過是把block放在方法中去使用。
// Person.h
#import <Foundation/Foundation.h>
typedef void(^Blk)(NSString *name,int age);
@interface Person : NSObject
-(void)handler:(Blk)blk;
@end
// Person.m
#import "Person.h
@implementation Person
-(void)handler:(Blk)blk{
if(blk){
blk(@"zhangsan" ,28);
}
}
@end
// 使用
Person *per =[Person new];
[per handler:^(NSString *name, int age) {
NSLog(@"%@ %d",name, age);
}];
這樣使用的好處是 在兩個對象通信的時候可以不使用delegate,方便了代碼的管理。其實這樣的用法很常見,多用于封裝網絡請求的基類。
40. 用塊引用其所屬對象時不要出現保留環
這個沒啥可說的
41. 多用派發隊列,少用同步鎖
派發隊列可用來表述同步語義,這種做法比使用@synchronize塊或NSLock對象更簡單
將同步與異步派發結合起來,可以實現與普通枷鎖機制一樣的同步行為,而這么做卻不會阻塞執行異步派發的線程
使用同步隊列及柵欄塊,可以令同步行為更加高效(不常用)
42. 多用GCD,少用performSelector系列方法
這個沒啥可說的
43. 掌握GCD及操作隊列的適用時機
這個沒啥可說的
解決多線程與任務管理問題時,派發隊列并非唯一方案
操作隊列提供了一套高層的Objective-C API 能實現純GCD所具備的絕大部分功能,而且還完成一些更為復雜的操作,那些操作弱改用GCD來實現,則需另外編寫代碼。
使用NSOperation對線程管理
44. 通過Dispatch Group機制,根據系統資源狀況來執行任務
這個沒啥可說的
一系列任務可歸入一個dispatch group之中。開發者可以在這組任務執行完畢時獲得通知
通過dispatch group,可以在并發式派發隊列里同時執行多項任務。此時GCD會根據系統西苑狀況來調度這些并發執行的任務。開發者若自己來實現此功能。則需要便攜大量代碼。
45. 使用dispatch_once來執行秩序運行一次的線程安全代碼
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
<#code to be executed once#>
});
46.不用使用dispatch_get_current_queue
這個沒啥可說的
iOS系統6.0版本起,已經正式啟用此函數了
__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_6,__MAC_10_9,__IPHONE_4_0,__IPHONE_6_0)
DISPATCH_EXPORT DISPATCH_PURE DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_t
dispatch_get_current_queue(void);
47.熟悉系統框架
打開Xcode command + shift + 0 選擇性的了解一些 Foundation、UIKit
也可以看看這篇博客 http://www.lxweimin.com/p/58bc11c800e4
48. 多用枚舉,少用for循環
因為枚舉遍歷的時候用的多線程(GCD并發執行),所以效率更快些。我覺得其實用什么都行。
NSArray *arr = @[@"b",@"c",@"s"];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
49. 對自定義其內存管理語義的collection使用無縫橋接
NSArray *anNSArray = @[@1,@3,@5,@8];
CFArrayRef acFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"%@",acFArray);
通過無縫橋接技術,可以在Foundation框架中Objective-C對象與CoreFoundation框架中的C語言數據結構之間來回轉換。
在CoreFoundation層面穿件collection時,可以指定許多回調函數,這些函數表示此collection應如何處理其元素,然后可運用無縫橋接技術,將其轉換成具備特殊內存管理語義的Objective-C collection。
50. 構建緩存時選用NSCache而非NSDictionary
NSCache勝過NSDictionary之處在于,當系統資源耗盡時,它能自動刪減緩存。
51. 精簡Initialize與load的實現代碼
類初始化的時候一定會調用兩個方法
+(void)load{}
+ (void)initialize
{
if (self == [<#ClassName#> class]) {
<#statements#>
}
}
load方法只會調用一次,不管該類的頭文件有沒有被使用,該類都會被系統自動調用,而且只調用一次。 當然了,如果不重寫這個方法的話,我們是不知道這個方法有沒有被調用的。
如果分類也重寫了load方法,先調用類里的,在調用分類。initialize 和load類似,不過在類被初始化的時候才會被調用(init之前)。需要注意的是,<#ClassName#>如果有子類繼承的時候要判斷類名。
52. 別忘了NSTimer會保留其目標對象
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,strong)NSTimer *timer;
-(void)start;
-(void)stop;
@end
// Person.m
#import "Person.h"
@implementation Person
-(void)start{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
-(void)run{
NSLog(@"%s",__func__);
}
-(void)dealloc{
NSLog(@"%s",__func__);
}
-(void)stop{
[self.timer invalidate];
}
@end
調用
@property (nonatomic,strong)Person *person;
self.person = [Person new];
[self.person start];
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person stop];
}
好,那么問題來了,是不是沒有調用dealloc方法,沒有調用dealloc方法就說明Person對象并沒有被銷毀,為什么沒有被銷毀
因為在控制器強引用了self.person,[self.person start]強引用了 self.timer; self.timer 的target指向了self(self.person)所以循環引用了。
怎么解決。 NSTimer銷毀的時候,把Person對象為nil即可
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person stop];
self.person = nil;
}