本节主要理解:
1.定时器的种类与注意事项(NSTimer循环引用/)
2.内存布局
3.Tagged Pointer
4.引用计数的原理
5.weak引用的原理
6.深拷贝与浅拷贝
7.自动释放池
一.读写安全
1.atomic与noatomic
2.pthead_rwlock
3.dispatch_barrier_async 栅栏函数
多读单写
dispatch_queue_t dispatchQueue = dispatch_queue_create("hyq.queue.next", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block1_for_reading)
dispatch_async(queue, block2_for_reading)
dispatch_async(queue, block_for_writing)
dispatch_async(queue, block3_for_reading)
dispatch_async(queue, block4_for_reading)
以上可能导致数据混乱
使用栅栏函数
dispatch_async(queue, block1_for_reading)
dispatch_async(queue, block2_for_reading)
dispatch_barrier_async(queue, block_for_writing)
dispatch_async(queue, block3_for_reading)
dispatch_async(queue, block4_for_reading)
dispatch_barrier_async会把队列的运行周期分为这三个过程:
- 首先等目前追加到并行队列中所有任务都执行完成
- 开始执行dispatch_barrier_async中的任务这时候即便向并行队列提交任务,也不会执行
- dispatch_barrier_async中任务执行完成后,并行队列恢复正常。
总的来说,dispatch_barrier_async起到了承上启下的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到一个栅栏或者分水岭的作用。
使用并行队列和diapatch_barrier_async方法,就可以高效的进行数据和文件读写了。
二.Tagged Pointer
■从64bit开始, iOS引入了Tagged Pointer技术,用于优化NSNumber. NSDate. NSString等小对象的存储
■在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等, NSNumber指针存储的是堆中NSNumber对象的地址值
■ 使用Tagged Pointer之后, NSNumber指针里面存储的数据变成了: Tag + Data ,也就是将数据直接存储在了指针中
■当对象指针的最低有效位是1 ,则该指针为Tagged Pointer,当对象指针的最低有效位是0时,为普通OC对象
■当指针不够存储数据时 ,才会使用动态分配内存的方式来存储数据
■objc_ msgSend能识别Tagged Pointer ,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
特点
1.我们也可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:
Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
3.在内存读取上有着3倍的效率,创建时比以前快106倍。
由此可见,苹果引入Tagged Pointer
,不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
三.copy和mutableCopy
1.iOS提供了2个拷贝方法
copy,不可变拷贝,产生不可变副本
mutableCopy,可变拷贝,产生可变副本
深拷贝和浅拷贝
深拷贝---内容拷贝,有产生新对象
浅拷贝---指针拷贝,未产生新对象
总结
不可变对象NSString ,NSArray,NSDictionary,copy是浅拷贝,mutableCopy是深拷贝
可变对象NSMutableString,NSMutableArray,NSMutableDictionary,不管copy还是mutableCopy都是深拷贝
为什么NSString使用copy修饰,NSMutableString使用strong修饰?
1.当原字符串是NSString时,由于是不可变字符串,所以,不管使用strong还是copy修饰,都是指向原来的对象,copy操作只是做了一次浅拷贝。
2.当源字符串是NSMutableString时,strong只是将源字符串的引用计数加1,而copy则是对原字符串做了次深拷贝,从而生成了一个新的对象,并且copy的对象指向这个新对象。
所以,如果源字符串是NSMutableString的时候,使用strong只会增加引用计数。但是copy会执行一次深拷贝,会造成不必要的内存浪费。而如果原字符串是NSString时,strong和copy效果一样,就不会有这个问题。
但是,我们一般声明NSString时,也不希望它改变,所以一般情况下,建议使用copy,这样可以避免NSMutableString带来的错误。
四.引用计数的存储
在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_ t weak_table;
};
refcnts是一个存放着对象引用计数的散列表
五.autoreleasepool 自动释放池
■自动释放池的主要底层数据结构是 :_ AtAutoreleasePool. AutoreleasePoolPage
■调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
源码分析
0 clang重写@autoreleasepool
0 objc4源码 : NSObject.mm
class AutoreleasePoolPage
{
magic_ t const magic;
id*next;
pthread_ _t const thread ;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_ ,t const depth;
uint32_ .t hiwat;
}
面试题
1.使用CADisplayLink、 NSTimer(基于runloop)有什么注意点 ?
循环引用
CADisplayLink、 NSTimer 会对taget产生强引用,如果target又对他们强引用,就会造成循环引用
此种方式会造成循环引用
self. timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target : self selector :@selector(timerTest) userInfo:nil repeats:YES];
(1)最简单的解决办法如下
_ weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *_ Nonnull timer) [
[weakSelf timerTest];
});
(2)或者使用NSProxy,(专门用来做转发,是一个基类,与NSObject并列,方法调用时会省去第一阶段-消息发送阶段,直接进入第三阶段-消息转发阶段)自行了解
不准时
因为依赖于runloop,如果runloop任务过于繁重(如线程中有滚动视图正在滚动,NSTimer会暂停),可能会导致NSTimer不准时,可以使用GCD定时器更加准时
//创建-个定时器
dispatch_ source_ t timer = dispatch_ source_ create (DISPATCH_ SOURCE_ TYPE_ TIMER, 0, 0, queue);
//没置吋囘(start是几秒后幵始抗行, interval是吋囘囘隔)
dispatch_ source_ ,set_ timer(timer,
dispatch_ time (DISPATCH_ TIME_ NOW, (int64_ t)(start * NSEC_ PER_ SEC)),
(uint64_ t)(interval *NSEC_ PER_ SEC),
0);
//设置回调
dispatch_ source_ ,set_ event_ handler(timer, ^{
));
//启动定时器
dispatch_ resume (timer);
2.介绍下内存的几大区域
地址从低到高布局
■代码段: 编译之后的代码
■数据段
0字符串常量:比如NSString *str = @"123"
0已初始化数据:已初始化的全局变量、静态变量等
0未初始化数据:未初始化的全局变量、静态变量等
■堆: 通过alloc、malloc. calloc等动态分配的空间,分配的内存空间地址越来越大
■栈: 函数调用开销,比如局部变量。分配的内存空间地址越来越小
3.讲一下你对iOS内存管理的理解
4.autorelease在什么时机会被释放
① 如果有@autoreleasepool{},所以autoreleasepool里面调用了autorelease方法的对象会在{}结束之后释放。
② 如果没写@autoreleasepool{},由于整个程序没有退出,autoreleasepool里面调用了autorelease方法的对象会在RunLoop休眠之前被释放。
5.方法里有局部对象 ,出了方法后会立即释放吗
会立即释放,因为就相当于在方法的最后加一行release代码。
6.ARC 都帮我们做了什么?
实际上"引用计数式内存管理"的本质在ARC中并没有改变,ARC只是自动的帮助我们处理"引用计数"的相关部分
7.weak指针的实现原理
- 在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; refcnts是一个存放着对象引用计数的散列表
weak_table_t weak_table; 弱引用表
};
当对象引用计数为0时,会自动调用dealloc方法。
苹果将弱引用都存在一个哈希表中,当对象调用dealloc方法时,系统会取出对象的地址,作为key到哈希表中找到对应的弱引用,并将其销毁掉。