釋放掉不用的記憶體,保證還可能被使用的記憶體不會被回收。這是記憶體管理要做的的事情,OC是通過引用計數來管理的,MRC和ARC的區分只是:引用計數是由程式設計師還是編譯器和語言來負責管理。
為啥要使用引用計數
在c中堆中的物件是由程式設計師負責的:
// malloc必須和free成對出現
char *str = (char*)malloc(sizeof(char)*10);
// do something
// 如果忘了free就洩漏了10個位元組的記憶體,如果多次free,那將導致崩潰
free(str);
// 如果此時又使用了str,這塊記憶體可能已經分配做其他用途了,所以行為是不確定的
// do not use str
複製程式碼
看起來好像很簡單,但是在do somethind
的過程中可能經歷裡很多的,所以說保證這塊記憶體的正確使用完全依靠程式設計師,有很大的風險,而且除錯指標異常也是很麻煩的。
OC中的引用計數
// NSObject 提供的引用計數的功能
- (instancetype)retain OBJC_ARC_UNAVAILABLE; // rc++
- (oneway void)release OBJC_ARC_UNAVAILABLE; // rc--,若rc==0,則釋放該例項物件
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE; // 延遲釋放
複製程式碼
蘋果記憶體管理文件給出了使用引用計數的原則:
- 你擁有你建立的任何物件,呼叫命名上為“alloc”, “new”, “copy”, or “mutableCopy”的函式,將獲得一個有所有權的例項物件(You own any object you create)
// 這個NSObject的例項物件屬於obj,也就是obj擁有NSObject的例項物件
NSObject *obj = [[NSObject alloc] init];
複製程式碼
- 可以使用retain獲取物件的所有權
// 這個NSObject的例項物件屬於obj,也就是obj擁有NSObject的例項物件
NSObject *obj = [[NSObject alloc] init];
// 通過retain方法,這個物件也屬於obj1了
NSObject *obj1 = [obj retain];
複製程式碼
- 當不再需要它時,必須放棄你擁有的物件的所有權
// 這個NSObject的例項物件屬於obj,也就是obj擁有NSObject的例項物件
NSObject *obj = [[NSObject alloc] init];
// 通過retain方法,這個物件也屬於obj1了
NSObject *obj1 = [obj retain];
// 通過release方法,放棄objc1對這個例項物件的所有權
[objc1 release];
複製程式碼
- 不能放棄沒有擁有權物件的所有權
// 根據1中的命名規則,str沒有這個NSString例項物件的所有權
NSString *str = [NSString stringWithFormat:@"hello NSString"];
// 所以如下,釋放一個沒有所有權例項物件的所有權是錯誤的
[str release];
複製程式碼
關於autorelease(延時釋放)
//
- (id)myMethod {
// obj 擁有 MyObject例項物件的所有權,引用計數為1
MyObject *obj = [[MyObject alloc] init];
// 如果按照上述蘋果的記憶體管理原則3,你需要在return前release obj,但是如果release了就被回收了
// 這就用到了autorelease 將物件延時釋放
return [obj autorelease];
}
複製程式碼
autorelease的實現是要配合autoreleasepool來完成的,AutoreleasePoolPage是由一個雙向連結串列實現的棧,每個節點一個虛頁的大小。autorelease訊息實際是將obj push到棧中,並在pop時進行release。
MRC與ARC
MRC:手動引用計數,需要我們手動控制擁有權,也就是負責傳送retain release autorelease訊息。 ARC:通過分析編譯生成的語法樹,編譯器幫我們自動插入引用計數相關的程式碼。
ARC存在的問題和解決方法
考慮如下程式碼:
// ARC環境下
@interface Person : NSObject <NSMutableCopying, NSCopying>
@property (nonatomic, copy)NSString *name;
@property (nonatomic)Person *partner;
+(instancetype) personWithName: (NSString*)name;
@end
Person *xiaohong = [Person personWithName: @"xiaohong"];
Person *xiaoming = [Person personWithName: @"xiaoming"];
xiaohong.partner = xiaoming;
xiaoming.partner = xiaohong;
複製程式碼
在上述程式碼中,xiaohong
和xiaoming
如果沒有其他的引用,那麼最終他們的引用計數都為1,所以他們都不能被釋放,又因為不能被釋放的原因是xiaohong
引用xiaoming
導致xiaoming
不能被釋放,xiaoming
引用xiaohong
導致xiaohong
不能被釋放,如下圖①,所以稱為迴圈引用
如何解決迴圈引用呢,首先選擇一方放棄對另一方的引用,例如xiaohong.partner = nil;
,如上圖②所示,此時沒有物件引用xiaoming
,所以釋放,在xiaoming
呼叫dealloc
時將會放棄對xiaohong
的引用,所以xiaohong
也能得以正確釋放。
使用weak來解決迴圈引用
在使用weak關鍵字:
// ARC環境下
@interface Person : NSObject <NSMutableCopying, NSCopying>
@property (nonatomic, copy)NSString *name;
@property (nonatomic, weak)Person *partner;
+(instancetype) personWithName: (NSString*)name;
@end
Person *xiaohong = [Person personWithName: @"xiaohong"];
Person *xiaoming = [Person personWithName: @"xiaoming"];
xiaohong.partner = xiaoming;
xiaoming.partner = xiaohong;
複製程式碼
此時的情況如上圖③所示,weak有並不會增加物件的引用計數,所以在xiaohong
和xiaoming
可以順利的被釋放。
其實解決迴圈引用只需要要打破這個引用環就可以,即如圖②所示, 圖③所示只是一種充分非必要條件。
Core Foundation和ARC
ARC是針對於NSObject的一套方法,所以在Core Framework是不適用的,OC中的一些物件與Core Frameword又是toll-bridge(就是可以進行簡單的相互轉換)的,雖然結構可以相互轉換,但是還需要考慮所有權的問題,即誰來負責release的工作。
// 即使是兩種結構是toll-bridge的,我們也不能簡單的類似於(NSString*)的指標強轉,因為還有所有權問題(引用計數)
// cf與NSObject結構相互轉化的關鍵字
__bridge // NSObject <-> cf 不改變所有權
__bridge_retained // NSObject -> cf 將所有權從ARC交給CF的引用計數
__bridge_transfer // cf -> NSObject 將所有權從CF的引用計數交給ARC
複製程式碼
在實際使用的過程中,無非兩種情況,記憶體管理最終交由誰處理呢?
1. 最終由ARC負責記憶體管理
// case 1 這應該是最常用的方式
NSString *str = [[NSString alloc]initWithFormat:@"test"];
CFStringRef cfStr = (__bridge CFStringRef)str; // 不改變所有權,類似於(CFStringRef)str
// case 2
CFStringRef cfName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *name = (__bridge_transfer NSString*)cfName; // 將所有權從CF的引用計數交給ARC
// case 3 下面實際是發生了所有權變化,但經過步驟1,2最終又將所有權交給了ARC
NSString *str = [[NSString alloc]initWithFormat:@"test"];
CFStringRef cfStr = (__bridge_retained CFStringRef)str; // 1. 將所有權從ARC交給CF的引用計數
str = (__bridge_transfer NSString*)cfstr; // 2. 將所有權從CF的引用計數交給ARC
複製程式碼
2. 最終由CF的引用計數負責
NSString *str = [[NSString alloc]initWithFormat:@"test"];
CFStringRef cfStr = (__bridge_retained CFStringRef)str; // 將所有權從ARC交給CF的引用計數
CFRelease(cfStr); // 由CF的記憶體管理負責回收
複製程式碼
由objc associated object 引發的迴圈引用
在category增加property時我們很有可能會使用associated object
// 獲取與self associal 的object
objc_getAssociatedObject(self, &key);
// 設定與self associal 的object
objc_setAssociatedObject(self, &key, retainAssocialValueInCategory, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// associal的object與self的四種關係
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
複製程式碼
如果有如下程式碼:
@interface Person: NSObject
@property NSString *name;
@property Person *partener;
@end
@implementation Person
static char partenerAssocialKey;
- (void)setPartener: (Person*)partener {
objc_setAssociatedObject(self, &partenerAssocialKey, partener, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (Person*)partener {
return objc_getAssociatedObject(self, &partenerAssocialKey);
}
@end
Person *xiaoming = [[Person alloc] init];
Person *xiaohong = [[Person alloc] init];
xiaoming.name = @"xiaoming";
xiaohong.name = @"xiaohong";
// 引用迴圈,由associal object 引起
xiaoming.partener = xiaohong;
xiaohong.partener = xiaoming;
複製程式碼
解決方案也比較簡單
// 加一層包裝
@interface CD_weakAssociatedObject : NSObject
@property (weak) id value;
@end
@implementation CD_weakAssociatedObject
@end
// 實現非原子的set associate方法
void cd_setWeakAssociatedObject(id _Nonnull object, id _Nullable value, const void* _Nonnull key) {
CD_weakAssociatedObject *obj = objc_getAssociatedObject(object, key);
if (!obj) {
obj = [[CD_weakAssociatedObject alloc] init];
objc_setAssociatedObject(object, key, obj, OBJC_ASSOCIATION_RETAIN);
}
obj.value = value;
}
// get
id _Nonnull cd_getWeakAssociatedObject(id _Nonnull object, const void* _Nonnull key) {
CD_weakAssociatedObject *obj = objc_getAssociatedObject(object, key);
if (obj) {
return obj.value;
}
return nil;
}
複製程式碼
通過新增對associated object的一層包裝,實現指向associated object的弱引用