先上代码,我们平时用的定时器,cadisplaylink、nstimer,CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
{
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(test) userInfo:nil repeats:YES];
}
- (void)test{
}
解决方案就是,nstimer的时候使用block执行任务,cadisplaylink只能使用代理对象来解决循环引用,使用NSProxy这个类或者nsblock,前者效果更好,或者我们使用gcd的定时器,这是跟内核打交道的,更加精准。
{
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf test];
}];
}
@interface FGTimer : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation FGTimer
+ (instancetype)proxyWithTarget:(id)target{
FGTimer *timer = [self alloc];
timer.target = self;
return timer;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
如果是nsobject,他找不到方法的时候会调用消息发送,动态方法解析、消息转发,而nsproxy直接是消息转发,提高笑了,代理对象就是专门做这个的。
iOS程序的内存布局
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)
这是源码里面判断是否是tagged pointer指针,下面是一道关于tagged pointer有关的面试题,问分别执行代码1和代码2,会发生生么?
// 代码1
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijkl"];
});
}
// 代码2
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {
dispatch_async(queue1, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
// set方法底层实现
- (void)setDog:(Dog *)dog
{
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
这两段代码区别就是字符串的长度了,先说会发生生么吧,代码一会crash,坏内存访问: *** -[CFString release]: message sent to deallocated instance 0x600001967020,代码2,正常执行,原因呢,就是我们学的tagged pointer,代码1里面的字符串就不再是常量区了,而是变成一个oc对象,而set方法底部实行是先release前对象,再retain一次,因为线程异步执行,导致release的对象被释放了,又进行一次release,所以发生这个现象,而代码2,保存在常量区,是直接赋值,所以不会发生。
OC对象的内存管理
- 在iOS中,使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 可以通过以下私有函数来查看自动释放池的情况 extern void _objc_autoreleasePoolPrint(void);
copy
面试中,我们经常遇到问,深拷贝,浅拷贝,深拷贝就是内容拷贝,产生了新对象,浅拷贝就是指针拷贝,都指向同一块内存地址
|
copy |
mutableCopy |
NSString |
NSString 浅拷贝 |
NSMutableString 深拷贝 |
NSMutableString |
NSString 深拷贝 |
NSMutableString 深拷贝 |
NSArray |
NSArray 浅拷贝 |
NSMutableArray 深拷贝 |
NSMutableArray |
NSArray 深拷贝 |
NSMutableArray 深拷贝 |
NSDictionary |
NSDictionary 浅拷贝 |
NSMutableDictionary 深拷贝 |
NSMutableDictionary |
NSDictionary 深拷贝 |
NSMutableDictionary 深拷贝 |
dealloc
objc4 源码
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && // 被优化过的指针
!isa.weakly_referenced && // 是否弱指针
!isa.has_assoc && // 是否有关联对象
!isa.has_cxx_dtor && // 是否含有c++的析构函数
!isa.has_sidetable_rc)) // 是否内存指针保存不够,存在sidetable中
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
//
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
在释放对象前,他会先判断,具体写在代码里,然后如果有weak指针,会调用objc_object::clearDeallocating_slow(),会在散列表里面将对应的value设置为nil,这也就是我们weak指针的实现原理,weak修饰的对象会存放在散列表中,地址作为key,value为对象,当对象销毁时,会以地址为key搜索到对应的对象,释放掉,这样就避免僵尸对象。
自动释放池
自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage;
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的,下面是源码里面的结构
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址,所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
具体使用为
- 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
static inline void *push()
{
id *dest;
if (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.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
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 (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
最后的结构就是
atautoreleasepoolobj = objc_autoreleasePoolPush();
FGPerson *person = [[[FGPerson alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
那么问问题来了,什么时候去调用呢,也就是说什么时候释放对象呢?这里牵扯到nsrunloop了,我们打印NSLog(@"%@",[NSRunLoop currentRunLoop]);
// 监听0x1 为kCFRunLoopEntry
"<CFRunLoopObserver 0x600001f14140 [0x7fff805ed4e0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4715bddc), context = <CFArray 0x600002058c30 [0x7fff805ed4e0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ff238802038>\n)}}",
// 0xa0 kCFRunLoopExit + kCFRunLoopBeforeWaiting
"<CFRunLoopObserver 0x600001f141e0 [0x7fff805ed4e0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4715bddc), context = <CFArray 0x600002058c30 [0x7fff805ed4e0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ff238802038>\n)}}"
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), //64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。