一、Objective-C 使用引用計數來管理內存。
每個對象都有一個計數器,來表示引用該對象的個數;每次引用就加1,用完就減1;當計數為0時表示不再使用該對象,於是就銷毀該對象。
多個對象之間的引用形成閉環會導致循環引用,從而不能夠相互釋放,造成內存泄漏。
二、ARC 自動引用計數
ARC 自動引用計數,把內存管理事宜交由編譯器來處理。
使用 ARC 時,實際上引用計數還是在執行的,只不過保留操作和釋放操作由 ARC 在編譯時自動為你添加。
ARC 環境下特殊情況處理。
- (void)dealloc {
// 移除通知中心的監聽者
// 移除 KVO 監聽者
// 關閉 NSTimer,並將定時器置空(nil)
// 釋放非 Objective-C 對象的內存,如 CFRelease(...), free(...)
}
三、自動釋放池
@autoreleasepool {} 自動釋放池。
當向一個對象發送autorelease消息時,系統會將該對象放入到最新的自動釋放池。
在自動釋放池的作用域內,池子內的對象依然可以正常使用。
當自動釋放池的作用域結束時,再釋放池子內的對象———每個對象收到幾個autorelease就進行幾次release操作。
常見用途:循環內部創建大量臨時對象時,沒有及時釋放導致內存使用急劇增加,可以使用自動釋放池在每次循環結束時釋放對象。
for (NSInteger i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@"- %ld", i];
str = [str uppercaseString];
}
}
四、@property 屬性修飾符
原子性(多線程管理):atomic、nonatomic
讀寫屬性:readwrite、readonly
setter 語意:assign、retain、copy
強弱引用:strong、weak
1、NSNumber、NSString、Block 使用 copy。
2、IBOutlet 屬性、引用“引用對象”的屬性(控制器引用view的子視圖)、代理屬性使用 weak。
3、Class、id 類型的屬性使用 retain。
4、NSString 屬性使用 retain 時,可能賦值一個 NSMutableString,導致值為 NSMutableString 類型。
5、NSMutableString 屬性使用 copy 時,導致屬性值為 NSString 類型,有可能出現未識別方法調用的異常。
atomic 只是保證了讀寫的線程安全,但是在多線程環境訪問對象屬性時,訪問結果不一定符合我們的預期。
- (void)setAtomicObj:(NSObject *)atomicObj{
@synchronized(self) {
if (_atomicObj != atomicObj) {
[_atomicObj release];
_atomicObj = [atomicObj retain];
}
}
}
- (NSObject *)atomicObj{
@synchronized(self) {
return _atomicObj;
}
}
ARC下同時重寫getter、setter方法時,需使用關鍵字@synthesize 聲明變量和屬性的關系。
@synthesize name = _name;
注意:@property = ivar + getter + setter;
五、定時器使用時的內存管理
通常控制器會擁有一個定時器,如果把控制器設置為定時器的 target,定時器會引用控制器,導致定時器不關閉銷毀時不能釋放控制器。因此,想辦法不要定時器直接或者間接引用控制器即可。
定時器解決循環引用思路:
1、iOS10 以后系統提供了block 方法執行定時器操作。
[NSTimer timerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
}];
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
}];
2、給 NSTimer 提供一個分類,實現自定義的 block 方法。
@interface NSTimer (BBBlocksSupport)
+ (NSTimer *)bb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (BBBlocksSupport)
+ (NSTimer *)bb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(bb_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)bb_blockInvoke:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
3、中間件模式。
設計思路:給中間對象動態添加定時器需要執行的方法。
#import <Foundation/Foundation.h>
@interface BBTimerManager : NSObject
@property (weak, nonatomic) NSTimer *bb_timer;
+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
#import "BBTimerManager.h"
#import <objc/runtime.h>
@implementation BBTimerManager
+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {
Method targetMethod = class_getInstanceMethod([aTarget class], aSelector);
if (!class_addMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod))) {
class_replaceMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
}
BBTimerManager *manager = [BBTimerManager new];
manager.bb_timer = [NSTimer scheduledTimerWithTimeInterval:interval target:manager selector:aSelector userInfo:userInfo repeats:repeats];
return manager;
}
@end
- (void)dealloc {
[_timerManager.bb_timer invalidate];
_timerManager.bb_timer = nil;
}
六、優雅使用 KVO
在需要處理屬性變化事件的對象中添加觀察者,並在該對象銷毀時移除觀察者。
@interface RootViewController ()
@property (retain, nonatomic) Focus *focus;
@end
@implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.focus = [[Focus alloc] init];
[self.focus addObserver:self
forKeyPath:@"number"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
self.focus.number = @2;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"number"]) {
NSLog(@"%@", change);
}
}
- (void)dealloc {
[self.focus removeObserver:self forKeyPath:@"number"];
}
七、copy 操作
對於不可變對象 NSString、NSArray、NSDictionary 表示引用加1。
對於可變對象 NSMutableString、NSMutableArray、NSMutableDictionary 表示復制對象,返回不可變對象。
mutableCopy 操作:
對於不可變對象、可變對象都是表示復制對象,返回可變對象。
自定義對象使用 copy 和 mutableCopy 需要遵守 NSCopying 和 NSMutableCopying 協議。
- (id)copyWithZone:(nullable NSZone *)zone;
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
八、weak 引用原理
runtime 維護了一個 weak 表,weak 表其實是一個 hash(哈希)表,其中 Key 是所指對象的地址,Value 是 weak 指針的地址數組。
weak 的實現原理可以概括一下三步:
1、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。
2、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。
3、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然后遍歷這個數組把其中的數據設為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。