1、Objective-C 起源:
在 C 語言基礎上添加了面向對象特性,是 C 語言的超集。Objective-C 由 SmallTalk 語言演變過來,使用消息結構,運作環境由運作環境決定。
OC 對象所占記憶體總是配置設定在堆空間,絕不會在棧空間配置設定記憶體。不含 * 的變量,儲存的不是OC對象,可能使用棧空間。
2、在類的頭檔案中,應盡量少引用其他頭檔案:
在編譯一個使用其他類的頭檔案(.h檔案)時,如果不需要知道那個類的實作細節,可以使用 @class ***; 即可。在實作檔案中(.m),需要知道要引入類實作細節時,可使用 #import "***"。
* 除非确有必要,否則不要引入頭檔案。一般來說,在一個類檔案中使用向前聲明來提及别的類,并在實作檔案中引入那些類的頭檔案,這樣可以降低類之間的耦合;
* 有時無法使用向前聲明,比如聲明某個類 遵循某一協定。此時,盡量把“該類遵循某協定”這條聲明移至“class-continuation”分類中,如果不行的話,就把協定單獨放一個頭檔案中。
3、多用字面量文法,少用與之等價的方法:
例如:
NSString *str = @"Hello";
// NSString *str1 = [NSString ];
NSNumber *num = @2;
NSNumber *num1 = [NSNumber numberWithInt:1];
NSArray *arr = @[@"1", @"sds", @"sjo"];
NSArray *arr1 = [NSArray arrayWithObjects:@"1", @"sds", @"sjo", nil];
NSDictionary *dic = @{
@"key1":@"value1",
@"key2":@"value2",
@"key3":@"value3"
};
NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:@"key1", @"value1", @"key2", @"value2",@"key3", @"value3", nil];
推薦使用 str,num,arr,dic 方式。
* 應該使用字面量文法來建立字元串、數值、數組、字典,與正常方法相比更簡明扼要;
* 應該通過取下标操作來通路數組下标或字典中鍵對應的元素;
* 用字面量文法建立數組或字典時,務必保證值裡面不能包含 nil。
4、多用類型常量,少用 #define 預處理指令:
若常量在外部使用,命名方法:以類名作為字首
// .h 檔案中
// extern 聲明外部的常量,加上 static 表示不允許修改,不加上則可以修改。
extern NSString * const EOLoginManagerDidLoginNotification;
extern const NSTimeInterval EOBookReadDuring;//閱讀時長
// 檔案中.m
NSString * const EOLoginManagerDidLoginNotification = @"EOLoginManagerDidLoginNotification";
const NSTimeInterval EOBookReadDuring = 155;//機關(分鐘)
若常量限于某個編譯單元之内使用,即不打算公開這個常量,命名時加上字首 "k",隻需在實作檔案中(.m)中聲明即可!
static const NSTimeInterval kAnimationDuring = .3;//動畫時長 (static const)不允許修改這個常量
* 不要用預編譯處理指令定義常量。這樣定義出來的常量不含類型資訊。編譯器隻會在編譯前查找和替換,即使有人定義了常量值,編譯器也不會報警告,這将導緻程式中常量值不一緻。
* 在實作檔案中使用 static const 定義“隻在編譯單元内可用的常量”,由于此類常量不在全局符号表中,是以無需為此添加類名字首,加上 k 即可。
* 在頭檔案中使用 extern 聲明全局常量,并在實作檔案中定義其值,這種常量要出現在全局符号表中,是以其應該加上類名以區分。
5、使用枚舉表示狀态、選項、狀态碼:
typedef NS_ENUM(NSInteger, EOBookType) {
EOBookTypeFinished = 0,
EOBookTypeSerialize,
EOBookTypeVIP,
EOBookTypeFree,
};
switch (self.bookType) {
case EOBookTypeFinished:
//
break;
case EOBookTypeSerialize:
//
break;
case EOBookTypeVIP:
//
break;
case EOBookTypeFree:
//
break;
// 若用枚舉定義狀态,最好不要有 default 分支。
// default:
// break;
}
* 應該用枚舉來表示狀态機的狀态、傳遞給方法的選項及狀态碼等值,給這些值起個簡單易懂的名字。
* 如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那麼就将各選項值定為 2 的幂數,以便通過按位或操作将其組合起來。
* 用 NS_ENUM 與 NS_OPTIONS 宏來定義枚舉類型,并指明其底層資料類型。這樣做可以確定枚舉是用開發者所選的資料類型來實作出來的,而不是編譯器所選類型。
* 在處理枚舉類型的 switch 語句中,不要實作 default 分支,否則,在加入新的枚舉類型時,編譯器會提示開發者,switch 語句并未處理所有枚舉類型。
6、了解“屬性”這一概念:
屬性 = 執行個體變量+setter方法+getter 方法。編譯器會自動生成一套存取方法,用以通路給定類型中給定名稱的變量。
* 可以用 @property 文法來定義對象中所封裝的資料;
* 通過屬性來指定存儲資料所需的正确語義;
* 在設定屬性所對應的執行個體變量時,一定要遵從該屬性所聲明的語義;
* 開發 iOS 程式時應該使用 nonatomic 屬性,因為 atomic 屬性會嚴重影響性能。
7、在對象内部盡量直接通路執行個體變量:
直接通路執行個體變量的速度快,編譯器所生産的代碼會直接通路儲存對象執行個體變量的那塊記憶體;
直接通路執行個體變量時,不會調用其 setter 方法,這就繞過了相關屬性所定義的記憶體管理語義。如果在ARC下直接通路一個聲明為 copy 的屬性,那麼并不會拷貝改屬性,隻會保留新值并釋放舊值;
在寫入執行個體變量時,通過其 setter 方法來做,而在讀取執行個體變量時,則直接通路。此辦法既能提高讀取操作的速度,又能控制對屬性的寫入操作。
* 在對象内部讀取資料時,應該直接通過執行個體變量來讀,而寫入資料時,則通過屬性來寫;
* 在初始化方法及 dealloc 方法中,總是應該直接通過執行個體變量來讀寫資料;
* 有時會使用懶加載初始化技術配置某分資料,在這種情況下,需要通過屬性來讀取資料。
8、了解“對象等同性”這一概念:
比較兩個對象是否相等,則兩個對象的所有屬性值都相等,有兩種方式比較
- (BOOL)isEqual:(id)object {
return YES;
}
- (NSUInteger)hash {
return 1009;
}
首先判斷兩個指針是否相等,若相等,兩個對象必定相等。然後比較兩對象所屬的類。
若兩對象相等,則其哈希碼必然相等,反之,則未必相等
9、以“類族模式”隐藏實作細節:
例如:+ (instancetype)buttonWithType:(UIButtonType)buttonType;
該方法傳回的對象取決于傳入的 buttonType,然而不管傳回什麼類型的對象,他們都繼承自相同的基類。使 button 的使用者無需關系創造出來的按鈕屬于哪個子類,也不用關心按鈕的繪制方式及實作細節。
#import <Foundation/Foundation.h>
typedef NS_ENUM (NSInteger, EOEmployeeType) {
EOEmployeeTypeDeveloper,
EOEmployeeTypeDesigner,
EOEmployeeTypeFinance
};
NS_ASSUME_NONNULL_BEGIN
@interface EOEmployee : NSObject
+ (EOEmployee *)employeeWithType:(EOEmployeeType)employeeType;
- (void)workStart;
@end
@interface EOEmployeeDeveloper : EOEmployee
@end
@interface EOEmployeeDesigner : EOEmployee
@end
@interface EOEmployeeFinance : EOEmployee
@end
NS_ASSUME_NONNULL_END
@implementation EOEmployee
+ (EOEmployee *)employeeWithType:(EOEmployeeType)employeeType {
switch (employeeType) {
case EOEmployeeTypeFinance:
//
return [EOEmployeeFinance new];
break;
case EOEmployeeTypeDesigner:
//
return [EOEmployeeDesigner new];
break;
case EOEmployeeTypeDeveloper:
//
return [EOEmployeeDeveloper new];
break;
default:
break;
}
}
- (void)workStart {
}
@end
* 類族模式可以把實作細節隐藏在一套簡單的公共接口後面;
* 系統架構經常使用類族;
* 從類族的公共抽象基類中繼承子類時,若有開發文檔,請先閱讀。
10、在既有類中使用關聯對象存放自定義資料:
OBJC_EXPORT voidobjc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
OBJC_EXPORT id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_EXPORT void objc_removeAssociatedObjects(id _Nonnull object)
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"提示内容" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
// cancel
}
else if (buttonIndex == 1) {
// sure
}
};
objc_setAssociatedObject(alertView, EOAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alertView show];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOAlertViewKey);
block(buttonIndex);
}
* 可以使用關聯對象機制把兩個對象連接配接起來;
* 定義關聯對象時,可使用記憶體管理語義,用以模仿定義屬性時所采用的“擁有關系”和“非擁有關系”;
* 隻有在其他方法不可行時,才選用關聯對象,這種做法難以查找 Bug。
11、了解 objc_msgSend 作用:
消息傳遞。。。
C 語言使用“靜态綁定”,即在編譯時就确定了函數。
void printHello () {
printf("Hello!\n");
}
void printGoodBye () {
printf("GoodBye!\n");
}
void doSomething (int type) {// 靜态語言 在編譯時就能确定函數
if (type == 0) {
printHello();
}
else {
printGoodBye();
}
return;
}
void doAnything (int type) {// 動态語言 使用了動态綁定,要調用的函數在運作時才能确定
void (*fnc)(void);
if (type == 0) {
fnc = printHello;
}
else {
fnc = printGoodBye;
}
fnc();
return;
}
* 消息由接收者、選擇子及參數構成。給某對象“發送消息”,也就相當于該對象上“調用方法”。
* 發給某對象的全部消息都要由“動态消息派發系統”來處理,該系統會查出對應的方法,并執行其代碼。
12、了解消息轉發機制:
當對象接收到無法解讀的消息時,就會啟動“消息轉發”機制。
消息轉發分為兩大階段:第一階段先征詢接收者,所屬的類,看其能否動态添加方法,以處理目前這個“位置的選擇子”,這叫做“動态方法解析”;
第二階段涉及“完整的消息轉發機制”。如果運作期系統已經把第一階段執行完了,那麼接收者自己無法再以動态新增的方法來響應包含該選擇子的消息了。此時,運作期的系統會請求接收者以其他手段來處理與消息相關的方法調用。首先,請接收者看看有沒有 其他對象處理這條消息。若有,則運作期系統會把消息轉給那個對象,于是消息轉發過程結束,一切正常。若沒有“備援的接收者”則啟動完整的消息轉發機制,運作期系統會把與消息有關的全部細節都封裝到 NSInvocation對象中,再給接收者最後一次機會,令其設法解決目前還未處理的這條消息。
動态方法解析:對象在收到無法解讀的消息後,首先将調用其所屬類的下列類方法:+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
}
else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
首先将選擇子化為字元串,然後檢測其是否表示設定方法。若字首 set,則表示設定方法,否則為 get 方法。不管哪種情況,都會把處理該選擇子的方法加到類裡面,所添加的方法是純 C 函數實作的。
備援接收者:當接收者還有第二次機會處理未知的選擇子,在這一步中,運作期系統會問它:能不能把這條消息轉給其他接收者來處理。與該步驟對應的處理方法如下:- (id)forwardingTargetForSelector:(SEL)aSelector
消息轉發全流程:
#import "EOAutoDictionary.h"
#import <objc/runtime.h>
@interface EOAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation EOAutoDictionary
@dynamic string, number, date, opaqueObject;
- (instancetype)init {
if (self = [super init]) {
_backingStore = [NSMutableDictionary dictionaryWithCapacity:1];
}
return self;
}
id autoDictionaryGetter(id self, SEL _cmd) {
EOAutoDictionary *typeSelf = (EOAutoDictionary *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value) {
EOAutoDictionary *typeSelf = (EOAutoDictionary *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
}
else {
[backingStore removeObjectForKey:key];
}
}
+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
}
else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
@end
* 若對象無法響應某個選擇子,則進入消息轉發流程;
* 通過運作期的動态方法解析功能,我們可以在需要用到某個方法時再将其加入到類中;
* 對象可以把其無法解讀的某些選擇子轉交給其他對象來處理;
* 經過上述兩步後,如果還是沒有辦法處理選擇子,那就啟動完整的消息轉發機制;
13、使用 “方法調配技術”調試“黑盒方法”
#import "NSString+EOAddtions.h"
#import <objc/runtime.h>
@implementation NSString (EOAddtions)
+ (void)load {
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eo_lowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
}
- (NSString *)eo_lowercaseString
{
NSString *lowercase = [self eo_lowercaseString];
NSLog(@"%@=>%@", self, lowercase);
return lowercase;
}
@end
* 在運作時,可以向類中新增或替換選擇子所對應的方法實作;
* 使用另一份實作來替換原有的方法實作,叫 method_swizzing,開發者常用此技術向原有實作中添加新功能;
* 一般來說,隻有調試程式的時候才需要在運作時修改方法實作,此方法不宜濫用。
14、了解“類對象”的用意:
* 每個執行個體都有一個指向 Class 對象的指針,用以表明其類型,而這些 Class 對象構成了類的繼承體系;
* 如果對象類型無法在編譯期确定,那麼就應該使用類型資訊查詢方法來探知;
* 盡量使用類型資訊查詢方法來确定對象類型,而不要直接比較類對象,因為某些對象可能實作了消息轉發功能。
15、使用字首避免命名空間沖突:
* 選擇與你公司、應用程式或者二者皆有關聯之名稱作為類名的字首,并在所有代碼中均使用這一字首;
* 若自己開發的程式中用到了第三方庫,則應為其中的名稱加上字首。
16、提供“全能初始化方法”:
// .h
@interface EORectangle : NSObject <NSCoding>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
- (id)initWithWidth:(float)width
height:(float)height;
@end
//.m
#import "EORectangle.h"
@implementation EORectangle
- (id)initWithWidth:(float)width
height:(float)height
{
if (self = [super init]) {
_width = width;
_height = height;
}
return self;
}
- (instancetype)init {
return [self initWithWidth:5.0 height:10.0];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[NSNumber numberWithFloat:_width] forKey:@"width"];
[aCoder encodeObject:[NSNumber numberWithFloat:_height] forKey:@"height"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
_width = [aDecoder decodeFloatForKey:@"width"];
_height = [aDecoder decodeFloatForKey:@"height"];
}
return self;
}
@end
//.h
@interface EOSpuare : EORectangle
- (id)initWithDimension:(float)dimension;
@end
//.m
#import "EOSpuare.h"
@implementation EOSpuare
- (id)initWithDimension:(float)dimension {
return [super initWithWidth:dimension height:dimension];
}
- (id)initWithWidth:(float)width height:(float)height {
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
//
}
return self;
}
@end
* 在類中提供一個全能初始化方法,并于文檔中說明。其他初始化方法均應調用此方法;
* 若全能初始化方法與父類不同,則需重寫父類中對應的方法;
* 如果父類的初始化方法不适用于子類,那麼應該重寫這個父類的方法,并在其中抛出異常。
17、實作 descriptin 方法:
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, EOGender) {
EOGenderMale = 0,
EOGenderFemale
};
NS_ASSUME_NONNULL_BEGIN
@interface EOAuther : NSObject
@property (nonatomic, strong) NSString *name;//名稱
@property (nonatomic, strong) NSString *realName;//真實姓名
@property (nonatomic, assign) BOOL isSigned;//是否是簽約作家
@property (nonatomic, assign) EOGender gender;//性别
@end
NS_ASSUME_NONNULL_END
#import "EOAuther.h"
@implementation EOAuther
- (NSString *)description {
return [NSString stringWithFormat:@"<%@:%p,\"%@ %@\">", [self class], self, _name, _realName];
}
@end
* 實作 decription 方法傳回一個有意義的字元串,用以描述該執行個體;
* 若想在調試時列印出更詳盡的對象描述,則應實作 debugDescrition 方法
18、盡量使用不可變對象:
* 盡量建立不可變的對象;
* 若某屬性僅可用于内部修改,則在 "class-continuation分類"中将其由 readonly 改為 readwrite;
* 不要把可變的 collection 作為屬性公開,而應提供相關方法,用以修改對象中的可變 collection
19、使用清晰而協調的命名方式:
* 起名時應遵從标準的 OC 命名規範,這樣建立出來的接口更容易為開發者所了解;
* 方法名要言簡意赅;
* 方法名裡不要使用縮略後的類型名稱;
* 給方法起名時的第一要務就是要確定其代碼風格與自己的代碼或所要內建的架構相符。
20、為私有方法添加字首:
* 給私有方法的名稱加上字首,這樣可以容易将其和公共方法區分開;
* 不要單用一個下劃線做私有方法的字首,因為這種做法是預留給蘋果公司的
21、了解 OC 錯誤模型:
* 隻有發生了可使整個程式 Crash 的嚴重 Bug 時,才應使用異常;
* 在錯誤不那麼嚴重時,可以指派“委托方法”來處理錯誤,也可以把錯誤資訊放在 NSError 對象裡,經由“輸出參數”傳回給調用者。
22、了解 NSCopying 協定:
深拷貝:在拷貝對象自身時,将其底層資料一并複制過去。
淺拷貝:隻拷貝容器對象本身,并不複制其中資料,Foundation 架構中所有的 Collection 類在預設情況下都是淺拷貝。
#import "EOPerson.h"
@implementation EOPerson {
NSMutableSet *_friends;
}
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
_friends = [NSMutableSet copy];
}
return self;
}
- (void)addFriend:(EOPerson *)person {
[_friends addObject:person];
}
- (void)removeFriend:(EOPerson *)person {
[_friends removeObject:person];
}
- (id)copyWithZone:(NSZone *)zone {
EOPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName lastName:_lastName];
copy->_friends = [_friends mutableCopy];
return copy;
}
- (id)deepCopy {
EOPerson *copy = [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems: YES];
return copy;
}
@end
* 若想令自己所寫的對象具有拷貝功能,則需實作 NSCopying 協定;
* 如果自定義對象分為可變與不可變版本,那麼需要同時實作 NSCopying 與 NSMutableCopying 協定;
* 複制對象時需決定采用淺拷貝還是深拷貝,一般情況下盡量使用 淺拷貝;
* 如果寫的對象需要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法。
23、通過 delegate 與 dataSource 進行對象間通信:
#import "EONetworkFetcher.h"
@interface EONetworkFetcher () {
struct {
unsigned int didReceiveData : 1;
unsigned int didFailWithError : 1;
unsigned int didUpdateProgressTo : 1;
}_delegateFlags;
}
@end
@implementation EONetworkFetcher
- (void)setDelegate:(id<EONetworkFetcherDelegate>)delegate {
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didFailWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}
@end
* 将委托對象應該支援的接口定義成協定,在協定中把可能需要處理的事件定義成方法;
* 當某對象需要從另一個對象擷取資料時,可以使用委托模式,dataSource protocal
* 若有必要,可實作含有位段的結構體,将委托對象是否能響應相關協定方法這一資訊緩存到其中。
24、将類的實作代碼分散到便于管理的某個數的分類中:
* 通過分類機制(category),可以把類代碼分成很多個易于管理的小塊,以便單獨檢視;
* 将應該視為“私有”的方法歸入名叫 Private 的分類中,以隐藏實作細節。
25、 總是為第三方類的方法名稱加字首:
* 向第三方類中添加分類時,總應給其名稱加上你專用的字首;
* 向第三方類中添加分類時,總應給其中的方法名加上你專用的字首;
26、勿在分類(Category)中聲明屬性:
因為存取方法聲明為 dynamic,隻有在運作時才能提供,在編譯器期時是看不到的,編譯器會報警告。
* 把封裝資料所用的全部屬性都定義在主接口裡;
* 在“class-continuation分類”之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。
27、使用 "class-continuation分類"隐藏實作細節:
@interface EOPerson ()//特殊的分類
//自定義方法
@end
* 通過“class-continuation分類”向類中新增執行個體變量;
* 如果某屬性在主接口中聲明為 readOnly,而類的内部又要用設定方法修改此屬性,那麼就在"class-continuation分類"中将其擴充為 readWrite;
* 把私有方法的原型聲明在 "class-continuation分類"裡;
* 若想使類所遵循的協定不為人知,則可于"class-continuation分類"中聲明。
28、通過協定提供匿名對象:
* 協定可在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某協定的 id 類型,協定裡規定了對象所應實作的方法;
* 使用匿名對象來隐藏類型名稱;
* 如果具體類型不重要,重要的是對象能夠響應特定方法,那麼可使用匿名對象來表示。
29、了解應用計數:
30、以 ARC 簡化引用計數:
ARC 在調用 retain/release/autorelease/dealloc 這些方法時,并不通過普通的 OC消息派發機制,而是直接調用其底層 C 語言版本。這樣做性能更好 retain<=>objc_retain..
在編譯期,ARC 會把能夠互相抵消的 retain/release/autorelease 操作簡約,如果發現在用一個對象上執行了多次 retain/release ,那麼 ARC 有時可以成對的移除兩個操作。
__strong:預設語義,保留此值;
__unsafe_unretained:不保留此值,那麼做不安全,因為等到再次使用變量時,其對象可能已被回收;
__weak:不保留此值,但是變量可以安全使用,因為如果系統把這個對象回收了,那麼變量也會自動清空;
__autoreleasing:把對象“按應用傳遞”給方法時,使用這個特殊的修飾符,此值在方法傳回時自動釋放。
* ARC 隻負責管理 OC 對象的記憶體,CoreFoundation 對象不歸 ARC 管理,開發者必須适時調用 CFRetain/CFRelease。
31、在 dealloc 方法中隻釋放引用并解除監聽:
* 在 dealloc 方法裡,釋放指向其他對象的引用,并取消原來訂閱的 KVO 或 NSNotificationCenter,不要做其他事情;
* 如果對象持有檔案描述符等資源檔案,那麼應該專門編寫一個方法來釋放此種資源。這樣的類要和其他使用者約定:用完資源後必須調用 close 方法;
* 執行異步任務的方法不應在 dealloc 裡調用:隻能在正常狀态下執行的那些方法也不應在 dealloc 裡調用,應為對象已經正在回收的狀态了。
32、編譯“異常安全代碼”時留意記憶體管理問題:
如果 MRC,必須捕獲異常,那麼要設法保證代碼能把對象正确清理幹淨;
如果 ARC,必須捕獲異常,則需打開編譯器的 -fobjc-arc-exceptions 标志。
* 捕獲異常時,一定要注意将 try 塊内所設立的對象清理幹淨;
* 在預設情況下,ARC 不會生成安全處理異常所需的清理代碼。開啟編譯器标志後,可以生成這種代碼,不過會導緻程式變大,而且會降低運作效率。
33、以弱引用避免保留環(循環引用):
使用弱引用來表示“非擁有關系”,避免記憶體洩漏。
使用 weak 來聲明;
* 将某些引用設為 weak,可避免出現循環引用;
* weak 引用可以自動清空,也可以不自動清空。ARC的自動清空,由運作時系統來實作。在具備自動清空功能的弱引用上,可以随意讀取其資料,因為這種引用不會指向已經回收過的對象。
34、以“自動釋放池”降低記憶體峰值:
自動釋放池用于存放那些需要在稍後某個時刻釋放的對象。清空自動釋放池時,系統會向其中的對象發送 release 消息。
GCD線程,預設擁有自動釋放池,每次執行“事件循環”時,就會将其自動清空。是以不需要手動建立“自動釋放池”。
main 函數的 自動釋放池。。。
應用程式在執行循環時的記憶體峰值就會降低。記憶體峰值:是指應用程式在某個特定時間内的最大記憶體用量,自動釋放池可以減少這個峰值,因為系統會在塊的末尾把某些對象回收掉。
* 自動釋放池在棧區,對象收到 autorelease 消息後,系統将其放在最頂端的池裡;
* 合理運用自動釋放池,可降低應用程式的記憶體峰值;
* @autoreleasepool 這種新式寫法能建立出更為輕便的自動釋放池。
35、用“僵屍對象”調試記憶體管理問題:
scheme->run->Diagnostics->Enable Zombie Objects。
僵屍對象工作原理:代碼深植于 OC 的運作時程式庫,Foundation架構及CoreFoundation架構中。系統在即将回收對象時,如果發現通過環境變量啟用了僵屍對象功能,那麼還将執行一個附加步驟。這一步就是把對象轉化為僵屍對象,而不徹底回收。
* 系統在回收對象時,可以不将其真的回收,而是把它轉化為僵屍對象。通過環境變量 NSZombieEnabled 可開啟此功能;
* 系統會修改對象的 ISA 指針,令其指向特殊的僵屍類,進而使該對象為僵屍對象。僵屍類能夠響應所有的選擇子,響應方式:列印一條包含消息及其接受着的消息,然後終止應用程式。
36、不要使用 retainCount:
轉載于:https://www.cnblogs.com/ZachRobin/p/10316648.html