一.内存的五大区
- 栈区:编译器自动分配并释放,存放函数的参数值、局部变量、基本类型的变量或对象引用类型
- 堆区:由程序员分配和释放
- 全局区: 全局变量和静态变量是放在一块的
- 常量区: 常量、字符串
-
代码区
二.ARC的核心思想
- 自己生成的对象,自己持有.
- 非自己生成的对象,也可以持有.
- 自己持有的对象不再需要时,需要对其进行释放.
- 非自己持有的对象无法释放.
三. MRC的内存泄漏问题
- (void)setName:(NSString *)name
{
//如果_name==name,你先release再retain没有任何意义,还会导致崩溃
if(_name != name)
{
//因为已经重新设值新的name了,所以要把原来的_name引用计数-1
[_name release];
//同时要将新的name引用技术+1
_name = [name retain];
}
}
- 在dealloc()方法里面需要将_name置nil.
四.copy问题
- 浅拷贝是拷贝了指向对象的指针
深拷贝不但拷贝了对象的指针,还在系统中再分配一块内存,存放拷贝对象的内容 - 不管是NSString还是NSMutableString
1.copy都产生NSString类型,mutableCopy都产生NSMutableString类型.
2.不过NSSring的copy只是拷贝指针(浅拷贝)不可变对象进行copy操作都是浅拷贝
其他的都会产生新的对象.(深拷贝)不可变对象进行mutableCopy和可变对象进行copy或mutableCopy
五.内存优化问题
- apple使用taggedPointer技术来优化内存
taggedPointer:如果定义的常量内容较小,64bit的地址完全可以存下这个内容,那么会直接把内容存到64位的数据里面,也就是指针+内容
. - arm64后,isa指针的33位存储真正的地址,其他的位用来存储
引用计数
等信息.
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
extra_rc
:表示引用计数-1.如果19位不够存储,那么会使用has_sidetable_rc
.
has_sidetable_rc
:是否使用SideTable
表
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table; //弱引用表
}
SideTable
里面有个RefcountMap
散列表,key为对象value为引用计数.
为什么每个对象都有一个SideTable呢?分离锁:增加读写效率
六.dealloc方法
- 什么时候调用dealloc()?
当一个对象调用release()方法的时候会检查引用计数,如果引用计数=0则会调用dealloc()方法. - dealloc()都做了什么?
//释放对象
- (void)dealloc {
//---
_objc_rootDealloc(self);
}
判断是否是taggedPointer对象,判断是否有弱引用等,如果都没有则直接释放该对象.
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
/*
*是否TaggedPointer,它并不是真正的指针
*是否有弱引用
*是否有关联
*是否有析构器
*是否引用计数过大
*/
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
//快速释放
free(this);
}
else {
//---一般执行这里
object_dispose((id)this);
}
}
id object_dispose(id obj)
{
if (!obj) return nil;
//---释放实例变量、移除关联属性、弱引用指向nil
objc_destructInstance(obj);
//释放自己
free(obj);
return nil;
}
移除关联属性,将弱引用置为nil
这些都是在运行时做的,不是编译的时候就做的..毕竟类似关联对象都是运行时的东西.
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//---释放实例变量
if (cxx) object_cxxDestruct(obj);
//---移除关联属性
if (assoc) _object_remove_assocations(obj);
//---将弱引用置为nil
obj->clearDeallocating();//清空弱引用表中的指针
}
return obj;
}
- 所以说:arc是llvm和runtime协作的结果(llvm的编译器会给对象增加retain release, runtime会将弱引用置为nil)
七.autoreleasePool原理
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
- 源码
class AutoreleasePoolPage : private AutoreleasePoolPageData
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;
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)
{
}
};
每个pool的大小为4096字节
define I386_PGBYTES 4096
- 命令行objc_rewrite可得:
1.autoreleasepool会自动在大括号第一行加入autoreleasepoolobj = objc_autoreleasePoolPush()
,并且插入一个POOL_BOUNDARY
2.在最后一行加入objc_autoreleasePoolPop(autoreleasepoolobj)
,当调用pop时,autoreleasePage会从栈顶找到POOL_BOUNDARY
,将其中的每一个对象都执行一遍release
3.为什么需要POOL_BOUNDARY
呢,是为了解决pool大于4096时,如果本页都清空了还没找到POOL_BOUNDARY
,就代表需要去上一页继续release直到遇到POOL_BOUNDARY
跨页问题 - autoreleasepool是以栈为结点的双向链表结构.
- 调用了autorelase方法的对象,而且没有被@autorelasePool{}包裹,在什么时候被释放,例如这样的
NSObject *objc = [[[NSObject alloc] init] autorelease];
答:系统在runloop里面增加了一个observer,在当次runloop将要休眠或者退出loop的时候调用AutoreleasePoolPage::pop().
如果是被@autorelasePool{}包裹的对象,则是在大括号结束的时候就释放(调用release方法)。
@autoreleasepool { NSObject *objc = [[NSObject alloc] init]; }
- 在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool.
八.其他
九. 定时器的循环引用
9.1 为什么会有循环引用
不管是手动加入runloop的timer
//这种写法,必须自己将timer加入到runloop,否则不会生效的
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(action) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
[self.timer fire];
还是系统帮你开启runloop的timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(action) userInfo:nil repeats:YES];
他们手动调用invalidate
方法都可以销毁定时器
- (void)p_stopTimer {
[self.timer invalidate];
self.timer = nil;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self p_stopTimer];
}
但是如果不是手动调用invalidate
方法,而是在dealloc
里面停止定时器,则不起作用。
因为timer强引用了self,根本不会走dealloc
方法,所以无法销毁定时器
- (void)dealloc
{
[self p_stopTimer];
NSLog(@"%s",__func__);
}
那我给target传个弱引用怎么样?
不行的,虽然weakSelf是个若指针,不会让self的应用计数加1,但是当参数传给NSTimer时,weakSelf指向的对象作为参数,还是会被强引用的。
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(action) userInfo:nil repeats:YES];
9.2 使用block解决循环引用
//因为block内部实现(block捕获局部变量的时候,也会捕获它的修饰符)
//所以:self强引用了timer,timer有一个block,但block弱引用了self,
//所以不会造成循环引用,控制器最终打印了dealloc方法.
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf action];
}];
9.3 使用NSProxy或者@interface MindleProxy : NSObject解决循环引用
加一个中间件,中间件弱引用self,就能解决循环引用了
同时使用forwardingTargetForSelector
,将消息转发给self,完美解决
- NSProxy的效率更高、更专业一些,因为继承自NSObject会进行方法查找,找不到方法才会走动态方法解析和消息转发,而NSProxy则是直接进行消息转发(包含任何消息,只要是给NSProxy发消息,就会被他转发)
MindleProxy *proxy = [[MindleProxy alloc] init];
proxy.target = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(action) userInfo:nil repeats:YES];
//弱引用target
@interface MindleProxy : NSObject
@property (nonatomic, weak) id target;
@end
@implementation MindleProxy
//动态方法解析
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
- NSProxy则是直接进行消息转发(包含任何消息,只要是给NSProxy发消息,就会被他转发)
//使用NSProxy
RealProxy *realProxy = [RealProxy alloc];
realProxy.target = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:realProxy selector:@selector(action) userInfo:nil repeats:YES];
// isKindOfClass也被消息转发了
NSLog(@"%ld", [realProxy isKindOfClass:UIViewController.class]);//1
@implementation RealProxy
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}