iOS程序的内存布局
代码段
:编译之后的代码
数据段
:
- 字符串常量:比如
NSString *str = @"123"
- 已初始化数据:已初始化的全局变量、静态变量等
- 未初始化数据:未初始化的全局变量、静态变量等
栈
:函数调用开销,比如局部变量。分配的内存空间地址越来越小
堆
:通过 alloc
、malloc
、calloc
等动态分配的空间,分配的内存空间地址越来越大
OC对象的内存管理
在 iOS 中,使用 引用计数
来管理 OC 对象的内存。
一个新创建的 OC 对象引用计数默认是 1
,当引用计数减为 0
,OC 对象就会销毁,释放其占用的内存空间。
调用 retain
会让 OC 对象的引用计数 +1
,调用 release
会让OC对象的引用计数 -1
。
可以通过以下私有函数来查看自动释放池的情况,可以在 objc4
源码中查看函数实现:
extern void _objc_autoreleasePoolPrint(void);
内存管理的经验总结
当调用 alloc
、new
、copy
、mutableCopy
方法返回了一个对象,在不需要这个对象时,要调用 release
或者 autorelease
来释放它。
想拥有某个对象,就让它的引用计数 +1
;不想再拥有某个对象,就让它的引用计数 -1
。
MRC手动内存管理
Car
类:
@interface Car : NSObject
- (void)drive;
@end
@implementation Car
- (void)drive
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[super dealloc];
}
@end
Person
类:
@interface Person : NSObject {
Car *_car;
}
- (void)setCar:(Car *)car;
- (Car *)car;
@end
@implementation Person
- (void)setCar:(Car *)car
{
if (car != _car) {
if (_car) {
[_car release];
}
_car = [car retain];
}
}
- (Car *)car
{
return _car;
}
- (void)dealloc
{
// 相当于调用 [self setCar:nil];,在 setter 方法内 release 并置空。
self.car = nil;
NSLog(@"%s", __func__);
[super dealloc];
}
@end
手动内存管理使用 Person
和 Car
:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *car1 = [[Car alloc] init];
Car *car2 = [[Car alloc] init];
Person *percon = [[Person alloc] init];
[percon setCar:car1];
[percon setCar:car2];
[[percon car] drive];
[car1 release];
[car2 release];
[percon release];
}
return 0;
}
拷贝
拷贝的目的是为了产生一个副本对象,跟源对象互不影响。
copy
不可变拷贝,产生不可变副本。
mutableCopy
可变拷贝,产生可变副本。
引用计数的存储
在 64bit
中,引用计数可以直接存储在优化过的 isa
指针中,也可能存储在 SideTable
中。
在 union isa_t
中,有类似下面代码,不同平台可能存储的位数不同:
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
如果 has_sidetable_rc
为 1
则引用计数存储在 extra_rc
,否则存储在SideTable
中。
SideTable
结构
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 是一个存放着对象引用计数的散列表
weak_table_t weak_table; // 弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
weak指针的实现原理
一个对象的所有弱引用都存储在一个 hash
表里,对象销毁时会取出 hash
表里所有弱引用赋值 nil
。
ARC做了什么
利用 LLVM
编译器自动添加 MRC
需要手动添加的内存管理代码(retain/release/autorelase等),利用 Runtime
监控对象销毁时,清空实例变量和弱引用指针置nil。
自动释放池
自动释放池的主要底层数据结构是:__AtAutoreleasePool
、AutoreleasePoolPage
。
调用了 autorelease
的对象最终都是通过 AutoreleasePoolPage
对象来管理的。
每个 AutoreleasePoolPage
对象占用 4096
字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease
对象的地址
所有的 AutoreleasePoolPage
对象通过双向链表的形式连接在一起。
示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[[NSObject alloc] init] autorelease];
}
return 0;
}
转换 C++
代码:
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
magic_t const magic;
__unsafe_unretained id *next; // 指向了下一个能存放autorelease对象地址的区域
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
};
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
struct __AtAutoreleasePool {
// 构造函数
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
// 析构函数
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
{
__AtAutoreleasePool __autoreleasepool;
NSObject *obj = objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
autorelease对象什么时候调用release
iOS 在主线程的 Runloop
中注册了2
个 Observer
。
第1
个 Observer
:
- 监听了
kCFRunLoopEntry
事件,会调用objc_autoreleasePoolPush()
。
第2
个 Observer
:
- 监听了
kCFRunLoopBeforeWaiting
事件,会调用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
。 - 监听了
kCFRunLoopBeforeExit
事件,会调用objc_autoreleasePoolPop()
。
如果 autorelease
对象包含在 @autoreleasepool {}
自动释放池内,则在最近的自动释放池结束时调用。
Tagged Pointer
从 64bit
开始,iOS 引入了 Tagged Pointer
技术,用于优化 NSNumber
、NSDate
、NSString
等小对象的存储。
在没有使用 Tagged Pointer
之前,NSNumber
等对象需要动态分配内存
、维护引用计数
等,NSNumber
指针存储的是堆中 NSNumber
对象的地址值。
使用 Tagged Pointer
之后,NSNumber
指针里面存储的数据变成了:Tag + Data
,也就是将数据直接存储在了指针中。
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。
objc_msgSend
能识别 Tagged Pointer
,比如 NSNumber
的 intValue
方法,直接从指针提取数据,节省了以前的调用开销。
如何判断一个指针是否为 Tagged Pointer
?
- iOS 平台,最高有效位是
1
(第64bit) - Mac 平台,最低有效位是
1
#if OBJC_SPLIT_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#elif OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @(0xFFFFFFFFFF);
NSLog(@"%d %d %d", _objc_isTaggedPointer(&number1), _objc_isTaggedPointer(&number2), _objc_isTaggedPointer(&number3));
}
@end
可能多个线程同时执行了 [_name release]
,出现坏内存访问。用 atomic
或者加锁可以防止崩溃。
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
下面代码不会崩溃,因为这个 NSString
是一个 Tagged Pointer
。也就意味着它不会像 OC 对象那样处理引用计数,而是直接赋值数据:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
可以通过调用 class
来证实:
NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"abc"];
NSLog(@"%@ %@", [str1 class], [str2 class]); // __NSCFString NSTaggedPointerString
CADisplayLink、NSTimer使用注意
CADisplayLink
、NSTimer
会对 target
产生强引用,如果 target
又对它们产生强引用,那么就会引发循环引用。
也就是
CADisplayLink
、NSTimer
可能会有循环引用和计时不准的问题。
使用 block
解决循环引用:
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%@", weakSelf);
}];
}
- (void)dealloc
{
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
@end
使用 NSProxy
代理对象解决循环引用:
@interface DisplayLinkProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation DisplayLinkProxy
+ (instancetype)proxyWithTarget:(id)target
{
DisplayLinkProxy *proxy = [DisplayLinkProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// self对link强引用,proxy对self弱引用,link对proxy强引用
DisplayLinkProxy *proxy = [DisplayLinkProxy proxyWithTarget:self];
self.link = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(test)];
[self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)test
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
if (self.link) {
[self.link invalidate];
self.link = nil;
}
}
@end
GCD定时器解决计时不准
NSTimer
依赖于 RunLoop
,如果 RunLoop
的任务过于繁重,可能会导致 NSTimer
不准时。而 GCD
的定时器会更加准时。
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置延迟开始时间和间隔时间
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), (uint64_t)(1.0f * NSEC_PER_SEC), 0);
// 设置定时器回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"%@", [NSThread currentThread]);
});
// 启动定时器
dispatch_resume(self.timer);
}
@end