一:面试题:
1.1:CADisplayLink、NSTimer
1.1.1:CADisplayLink、NSTimer循环引用
1.1.2:NSProxy小问题
1.1.3:GCD定时器
1.2:iOS程序的内存布局
1.2.1:Tagged Pointer
1.3:OC对象的内存管理
1.3.1:copy:
1.3.2:引用计数的存储
1.3.3:weak指针的原理
1.3.4:autorelease
1.3.5:autorelease释放时机
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
这两个定时器底层是基于runloop(有事情做事情,没事情休眠)来实现的,所以有可能并不准时。
CADisplayLink:不用设置时间,保证调用频率和屏幕的刷帧频率一致,60FPS
- (void)viewDidLoad {
[super viewDidLoad];
// 保证调用频率和屏幕的刷帧频率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 这个self是自动加入到runloop中的
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
// 循环引用导致这里没有调用
NSLog(@"%s", __func__);
[self.link invalidate];
// [self.timer invalidate];
}
看这个 如果传入的是弱指针 也无用, 因为传入的 不论是弱指针还是强指针,都是一个内存地址,这个是一个形参,传入到NSTimer内部,它内部是肯定有一个强指针的属性或者变量 来保存这个形参,重点在于NSTimer内部的那个是强指针,所以这里传弱的还是强的都无所谓。
- (void)viewDidLoad {
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: weakSelf selector:@selector(timerTest) userInfo:nil repeats:YES];
}
但是如果block内部用的是弱指针,则对外部的是弱引用,这是它的特点,因为block对外的指针强弱的关系跟传入的强弱有关系,这里不懂的,请看前面block,里面讲解很透彻。
解决方案
方法1:使用block:NSTimer对block产生强引用,block对self产生弱引用,self强引用Timer,所以没有循环引用。
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
方法2:使用代理对象(NSProxy:NSObject)
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
// self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
// [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
// [self.timer invalidate];
}
#import
@interface MJProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy.h"
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
MJProxy *proxy = [[MJProxy alloc] init];
proxy.target = target;
return proxy;
}
// 消息转发:如果不实现,会爆消息找不到的错误,这样返回它自己,就找到方法了。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
NSTimer的那根线不开源,所以只能退而求其次。
方法三:特殊类:NSProxy
@interface NSProxy {
Class isa;
}
@interface NSObject {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
可以看到NSProxy跟NSObject一样,都是基类。
NSProxy 没有init方法。存在的意义就是用来解决代理行为、转发行为。只要去调用NSProxy的某一个方法的时候,马上就会直接调用它的另外一个方法:- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
而它是没有- (id)forwardingTargetForSelector:(SEL)aSelector这个方法的。
同样用这个也可以实现上面的循环引用
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
#import
@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy.h"
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
(MJProxy继承自NSProxy)会发现控制器会dealloc。因为定时器引用的 是MJProxy,而MJProxy里面引用着是target(弱引用),而且一旦调用MJProxy的某一个方法的时候,就会马上来到methodSignatureForSelector方法进行消息转发,方法签名用原来的方法签名,对上面来说,就是调用控制器对象的methodSignatureForSelector这个方法,返回控制器timerTest方法的签名,方法签名有了,会接着调用forwardInvocation方法,把方法签名包装成一个NSInvocation给你,再用这个target调用这个。
跟上面用NSObject的区别和优势是什么呢?
好处在于比NSObject的效率高,专门用来做消息转发。
看一下两个类 不实现消息转发,报错是什么。先看NSObject
2018-10-09 11:03:37.781345+0800 Interview03-定时器[2088:194218] -[MJProxy1 timerTest]: unrecognized selector sent to instance 0x60c00000b9f0
2018-10-09 11:03:37.787655+0800 Interview03-定时器[2088:194218] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MJProxy1 timerTest]: unrecognized selector sent to instance 0x60c00000b9f0'
*** First throw call stack:
(
0 CoreFoundation 0x0000000107b7b1e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x0000000107210031 objc_exception_throw + 48
2 CoreFoundation 0x0000000107bfc784 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x0000000107afd898 ___forwarding___ + 1432
4 CoreFoundation 0x0000000107afd278 _CF_forwarding_prep_0 + 120
5 Foundation 0x0000000106c7a4dd __NSFireTimer + 83
6 CoreFoundation 0x0000000107b0ae64 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
7 CoreFoundation 0x0000000107b0aa52 __CFRunLoopDoTimer + 1026
8 CoreFoundation 0x0000000107b0a60a __CFRunLoopDoTimers + 266
9 CoreFoundation 0x0000000107b01e4c __CFRunLoopRun + 2252
10 CoreFoundation 0x0000000107b0130b CFRunLoopRunSpecific + 635
11 GraphicsServices 0x000000010ccefa73 GSEventRunModal + 62
12 UIKit 0x0000000107ff8057 UIApplicationMain + 159
13 Interview03-定时器 0x000000010690e5ef main + 111
14 libdyld.dylib 0x000000010b5d8955 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
再看一下用NSProxy的
2018-10-09 11:05:16.053761+0800 Interview03-定时器[2142:200608] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSProxy methodSignatureForSelector:] called!'
*** First throw call stack:
(
0 CoreFoundation 0x000000010796a1e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x0000000106fff031 objc_exception_throw + 48
2 CoreFoundation 0x00000001079df975 +[NSException raise:format:] + 197
3 Foundation 0x0000000106b12b22 -[NSProxy methodSignatureForSelector:] + 43
4 CoreFoundation 0x00000001078ec45a ___forwarding___ + 346
5 CoreFoundation 0x00000001078ec278 _CF_forwarding_prep_0 + 120
6 Foundation 0x0000000106a694dd __NSFireTimer + 83
7 CoreFoundation 0x00000001078f9e64 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
8 CoreFoundation 0x00000001078f9a52 __CFRunLoopDoTimer + 1026
9 CoreFoundation 0x00000001078f960a __CFRunLoopDoTimers + 266
10 CoreFoundation 0x00000001078f0e4c __CFRunLoopRun + 2252
11 CoreFoundation 0x00000001078f030b CFRunLoopRunSpecific + 635
12 GraphicsServices 0x000000010c6aca73 GSEventRunModal + 62
13 UIKit 0x0000000107de7057 UIApplicationMain + 159
14 Interview03-定时器 0x00000001066fd61f main + 111
15 libdyld.dylib 0x000000010b3c7955 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
可以明显看到执行流程是不同的,
如果是NSObject,执行流程就是三大阶段:消息发送、动态解析、消息转发,这里需要进行消息发送,里面需要递归查找方法。如果是NSProxy,是一步到位,先看一下自己类中有没有这个方法,不会去父类中查找方法,直接进入消息转发,省略了去父类中找方法的过程和动态解析过程。
这个对NSTimer和CADisplayLink一样有用。
#import
@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy.h"
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
#import
@interface MJProxy1 : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
#import "MJProxy1.h"
@implementation MJProxy1
+ (instancetype)proxyWithTarget:(id)target
{
MJProxy1 *proxy = [[MJProxy1 alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
MJProxy *proxy = [MJProxy proxyWithTarget:self];
MJProxy1 *proxy1 = [MJProxy proxyWithTarget:self];
NSLog(@"%d %d",
[proxy isKindOfClass:[ViewController class]],
[proxy1 isKindOfClass:[ViewController class]]
);
}
2018-10-09 11:21:27.580295+0800 Interview03-定时器[2337:231792] 1 0
proxy1是继承自NSObject,所以按照NSObject的方式来判断,判断proxy1既不是ViewController,又不是它的子类,所以为0;
但是proxy是继承自NSProxy,这个在找不到方法的时候,就会进行消息转发,而这个MJProxy我们写的消息转发是转发到传入对象自己身上,就会变成ViewController在调用isKingOfClass,所以是自己这个类,所以打印为1。
源码:由于是Fontdation的,所以源码不开源,需要看gnustep,(讲fondatoin的很多类重写了一下,可以做参考。)里面对ismemberclass和iskindofclass都进行了消息转发。
CADisplayLink、NSTimer这两个定时器底层是基于runloop(有事情做事情,没事情休眠)来实现的,所以有可能并不准时。
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
(runloop:每跑一圈 看一下时间,而每跑一圈所花费的时间是不一定的,因为任务不一定,所以导致可能不准时)
而GCD的定时器会更加准时(跟系统内核挂钩,不依赖runloop)
拖拽模式下都不影响GCD的定时器。时间非常准确,可以在任意线程工作,可以在当时或者延后时间工作,很强大
- (void)test
{
// 队列
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_queue_create("timers", DISPATCH_QUEUE_SERIAL);
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
uint64_t start = 2.0; // 2秒后开始执行
uint64_t interval = 1.0; // 每隔1秒执行
// NSEC_PER_SEC:纳秒 这个是CGD的定时器的需要传入的单位
// dispatch_time:从第一个参数开始算时间 第二个参数也就是多长时间后执行
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
// 设置回调1
// dispatch_source_set_event_handler(timer, ^{
// NSLog(@"1111");
// });
// 设置回调2
dispatch_source_set_event_handler_f(timer, timerFire);
// 启动定时器
dispatch_resume(timer);
// 需要用强引用保留这个时间
self.timer = timer;
}
void timerFire(void *param)
{
NSLog(@"2222 - %@", [NSThread currentThread]);
}
2018-10-09 14:44:22.014101+0800 Interview02-GCD定时器[3835:374964] begin
2018-10-09 14:44:24.015619+0800 Interview02-GCD定时器[3835:375012] 2222 - {number = 3, name = (null)}
2018-10-09 14:44:25.015591+0800 Interview02-GCD定时器[3835:375012] 2222 - {number = 3, name = (null)}
2018-10-09 14:44:26.015557+0800 Interview02-GCD定时器[3835:375012] 2222 - {number = 3, name = (null)}
在arc环境下 GCD创建出来的对象 都不需要销毁。
封装
#import
@interface MJTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
@end
#import "MJTimer.h"
@implementation MJTimer
// 自己创建的唯一标识,一个定时器对应一个,这样防止乱掉
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
// 防止在多线程下执行 这个范围:无所谓的就不要加锁,防止影响效率
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
// 解锁
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!target || !selector) return nil;
// 这个target会不会对这个block造成循环引用呢?
// 因为这个block并不被外面的self所拥有,所以self不会对立面的block产生强引用,
// 而里面的block可能会对传进来的target产生强引用,也就是对self产生强引用,
// 所以这是单方面强引用,就好像写了一个GCD里面的block一样,
// 外部selftask强引用的是timer内部的字典内部的字符串,并不是timer,所以不会产生强引用。
return [self execTask:^{
if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push // xcode去掉警告 红色的字是xcode给的警告语
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
// 防止在多线程下执行
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin");
// 接口设计
self.task = [MJTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:NO];
// self.task = [MJTimer execTask:^{
// NSLog(@"111111 - %@", [NSThread currentThread]);
// } start:2.0 interval:-10 repeats:NO async:NO];
}
- (void)doTask
{
NSLog(@"doTask - %@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[MJTimer cancelTask:self.task];
}
注意上面target和block不会产生循环引用。上面已经解释清楚。
#import
#import "AppDelegate.h"
// 已初始化的全局变量
int a = 10;
// 未初始化的全局变量
int b;
int main(int argc, char * argv[]) {
@autoreleasepool {
// 已初始化的静态变量
static int c = 20;
// 未初始化静态变量
static int d;
// 栈 :地址会越来越小
int e; // 内存地址比f要大 因为是从高往低处放的
int f = 20;
// 字符串常量
NSString *str = @"123";
// 堆 地址会越来越大
NSObject *obj = [[NSObject alloc] init];
// 验证:
NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n",
&a, &b, &c, &d, &e, &f, str, obj);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
/*
数据区:
字符串常量 :内存地址最低
str =0x10dfa0068
已初始化的全局变量、静态变量
&a =0x10dfa0db8
&c =0x10dfa0dbc // c是12
未初始化的全局变量、静态变量 这个d和b没有按照写的顺序,这个是编译器行为。
&d =0x10dfa0e80
&b =0x10dfa0e84 // 从这可以跟堆这里对比可以看出,这个空间很大
堆:
obj=0x608000012210 // 看堆6和栈7 也差的很大 堆和栈中间的一段有可能是堆也有可能是栈,因为堆是越来越大,栈是越来越小。
栈:先分配给e再分配给f,地址是越来越小的
&f =0x7ffee1c60fe0
&e =0x7ffee1c60fe4
*/
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中(tag标记可以知道是number还是date还是string,标记在最后面面)
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
==》 比以前少耗性能,直接使用指针,优化了内存,并且在使用上,直接用objc_msgSend可以读取出来,还有使用方面的优化。
如何判断一个指针是否为Tagged Pointer?
iOS平台,最高有效位是1(第64bit) (是 1UL<<63,)
Mac平台,最低有效位是1
这个平台的源码判断是
#import
// 0b1110001
// &0b0000001
//-----------
// 0000001
BOOL isTaggedPointer(id pointer)
{
// 看最低有效位是不是1, 这里不是很严谨,也有可能是6,这个仅仅是一个验证
return (long)(__bridge void *)pointer & 1;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 下面三种写法 等价
// NSNumber *number = [NSNumber numberWithInt:10];
// NSNumber *number = @(10);
NSNumber *number1 = @4;
NSNumber *number2 = @5;
NSNumber *number3 = @(0xFFFFFFFFFFFFFFF);
// 这是调用objc_msgSend() 就是用的oc方法,通过isa找到类对象,
// 类对象去搜索intvalue这个方法,但是number1 是采取 targe pointer技术实现的,
// 这个没有堆空间,也没有isa,就会找不到这个方法,
//但是好处是objc_msgSend这个方法内部会判断这个number是不是一个target pointer,
//一旦发现时target pointer,会直接从指针中取出它的值
number1.intValue;
// 只要是堆空间的地址,最后一位一定是0.因为oc对象有内存对齐的概念,用16来对齐,都是16的倍数。所以为0
NSLog(@"%d %d %d", isTaggedPointer(number1), isTaggedPointer(number2), isTaggedPointer(number3));
NSLog(@"=====");
NSLog(@"%p %p %p", number1, number2, number3);
NSLog(@"=====%d",nu);
}
return 0;
}
2018-10-09 16:25:23.509030+0800 Interview04-TaggedPointer[4747:517412] 1 1 0
2018-10-09 16:25:23.509250+0800 Interview04-TaggedPointer[4747:517412] =====
2018-10-09 16:25:23.509259+0800 Interview04-TaggedPointer[4747:517412] 0x427 0x527 0x100404c40
2018-10-09 16:25:23.509267+0800 Interview04-TaggedPointer[4747:517412] =====4
讲个例子:看下面的代码会发生什么问题?
@interface ViewController ()
@property (strong, nonatomic) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
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"];
// 解锁
});
}
}
@end
以前的name会被释放两次,造成坏内存访问。
原因:set方法的本质是
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
而赋值之前,会先release掉旧值,因为上面是多线程,所以就会发生同时访问的问题,那本身已经release掉一次后,如果为0,再relsease一次,那就是坏内存访问了。
解决方式:
多线程加锁,atomic(这个不推荐使用,因为在get方法处或者其他地方都用到这个属性,我们只需要在这段代码中加锁,意义不大。)
因为运行在堆内存,释放,
那我们改一下,改成下面的形式,再看一下结果
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
这种情况下,就不会崩溃。为啥?
因为是target pointer,不会像oc对象一样 有set方法,不会有release这些,只是对指针进行的赋值。
NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"123abc"];
NSLog(@"%@ %@", [str1 class], [str2 class]);
NSLog(@"%p", str2);
2018-10-11 09:49:50.135962+0800 Interview05-TaggedPointer面试题[1475:71624] __NSCFString NSTaggedPointerString
2018-10-11 09:49:50.136063+0800 Interview05-TaggedPointer面试题[1475:71624] 0xa006362613332316
可以看到最低位 不是0。如果是0就是oc对象了。为16的倍数。
0xa006362613332316最低有效位不是1,但是最高位第64位是1,这是ios程序的特点,可以看一下
0x a006 3626 1333 2316:一共4组,这个里面每一个16进制位,代表4个2进制位,所以就是16 * 4 = 64位。所以a的位置是低64位,a -> 10 -> 8 + 2 ->1010, 所以a最高位就是1,所以是target poniter。
那普通的oc对象是内存对象管理,不是上面的target pointer(后来新改的)
在iOS中,使用引用计数来管理OC对象的内存
因为我们是runloop运行的程序,如果不释放的话,越堆越多,内存越来越少,导致内幕才能泄漏。
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
调用retain会让OC对象的引用计数+1 ,调用release会让OC对象的引用计数-1
copy也能让计数器+1,当调用者是不可变的,得到的就是+1的不可变对象
内存管理的经验总结
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
现在我们使用的都是arc,前身是mrc。
所以我们先来看一下mrc的使用和演变过程。
#import
#import "MJDog.h"
@interface MJPerson : NSObject
{
MJDog *_dog;
int _age;
}
- (void)setAge:(int)age;
- (int)age;
- (void)setDog:(MJDog *)dog;
- (MJDog *)dog;
@end
#import "MJPerson.h"
@implementation MJPerson
// 基本数据类型
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
// oc对象:需要拥有它,进行内存管理
- (void)setDog:(MJDog *)dog
{
if (_dog != dog) { // 为了防止赋重复的值,这里内存就出乱了。
[_dog release]; // 为了防止赋值不同的实例对象,旧的实例对象没有销毁。
_dog = [dog retain]; // 这一步是为了把dog的内存管理retain一份在person的管理中,拥有这个属性,因为第一次赋值_dog为nil,这个+1,后面再无论重复赋值多少次,都不用重复+1。
}
}
- (MJDog *)dog
{
return _dog;
}
- (void)dealloc
{
// [_dog release];
// _dog = nil;
self.dog = nil; // 这个代替上面两行,因为调用的是set方法
self.car = nil;
NSLog(@"%s", __func__);
// 父类的dealloc放到最后
[super dealloc];
}
@end
可以看出,对oc对象的set方法进行了内存管理,因为这样才好控制这个成员的声明周期,不至于在自己使用的时候就释放掉了,产生坏内存访问,所以需要retain。
对旧值进行release是因为,前面会赋值不同的实例对象,那旧的实例对象就会产生内存泄漏。
加上if判断,就可以阻止赋同样的实例对象,重复释放的问题,赋不同的值,同样可以达到销毁旧对象,对新对象的内存管理问题。
===》紧接着编译器进行发展,不再这么麻烦了,所以产生property的写法。
简化升级2:编译器帮我们做了操作。帮我们进行set和get的声明,
synthesize:自动生成成员变量和属性的setter、getter实现
但是需要自己写dealloc对对象进行释放和管理。
#import
#import "MJDog.h"
@interface MJPerson : NSObject
// 简化升级2:编译器帮我们做了操作。帮我们进行set和get的声明
@property (nonatomic, assign) int age;
@property (nonatomic, retain) MJDog *dog;
@end
#import "MJPerson.h"
@implementation MJPerson
// 升级附属2:自动生成成员变量和属性的setter、getter实现
@synthesize age = _age;
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
- (void)setDog:(MJDog *)dog
{
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
- (MJDog *)dog
{
return _dog;
}
- (void)dealloc
{
self.dog = nil;
NSLog(@"%s", __func__);
[super dealloc];
}
@end
===》再紧接着编译器进行发展,
升级 3:随着编译器发展,@synthesize不用写了,property的作用就变成了:自动生成set和get的声明,并且_的成员变量,并且set和get方法的实现。在arc下,还是需要在dealloc中自己释放。也就是现在的使用方法
所以我们对于@property的关键字 一定要用好,不能瞎用,传入什么,就会对应做操作和处理。
同样也需要自己处理dealloc。
@interface MJPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, retain) MJDog *dog;
@property (nonatomic, retain) MJCar *car;
+ (instancetype)person;
@end
关于copy和字典、字符串和数组比较全面、深入的讲解看这里
拷贝的目的:产生一个副本对象,跟源对象互不影响
修改了源对象,不会影响副本对象
修改了副本对象,不会影响源对象
iOS提供了2个拷贝方法
1.copy,不可变拷贝,产生不可变副本 (不论调用者是谁,产生结果均是不可变的, (如果调用者是不可变的,不产生新对象,因为指向同一个对象,都是不可变的,所以就不再开辟内存了。这个对NSArray、NSDictionary都是一样的,同时retaincout + 1 ))
2.mutableCopy,可变拷贝,产生可变副本
深拷贝和浅拷贝
1.深拷贝:内容拷贝,产生新的对象(mutableCopy)
2.浅拷贝:指针拷贝,没有产生新的对象(调用copy不一定都得到的是浅拷贝,比方说调用者是可变的,得到的就是深拷贝的不可变的对象)
void test2()
{
NSString *str1 = [[NSString alloc] initWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 浅拷贝,指针拷贝,没有产生新对象 这个cocy相当于是retain
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝,内容拷贝,有产生新对象
NSLog(@"%@ %@ %@", str1, str2, str3);
NSLog(@"%p %p %p", str1, str2, str3);
[str3 release];
[str2 release];
[str1 release];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 深拷贝
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝
// [str1 appendString:@"111"];
// [str3 appendString:@"333"];
//
// NSLog(@"%@ %@ %@", str1, str2, str3);
[str1 release];
[str2 release];
[str3 release];
test();
}
return 0;
}
属性copy:从上面得知的面试题
@interface MJPerson : NSObject
//@property (copy, nonatomic) NSMutableArray *data; // 产生的是不可变数组,往里面添加数据会报错,
@property (copy, nonatomic) NSArray *data;
@end
#import "MJPerson.h"
@implementation MJPerson
- (void)setData:(NSArray *)data
{
if (_data != data) {
[_data release];
_data = [data copy];
}
}
- (void)dealloc
{
self.data = nil;
[super dealloc];
}
@end
so:
字符串:一般用copy,
数组和字典用:strong。
OC对象的Copy:
#import
@interface MJPerson : NSObject
@property (assign, nonatomic) int age;
@property (assign, nonatomic) double weight;
@end
#import "MJPerson.h"
@implementation MJPerson
// copy底层实现的方法
- (id)copyWithZone:(NSZone *)zone
{
MJPerson *person = [[MJPerson allocWithZone:zone] init];
person.age = self.age;
// person.weight = self.weight;
return person;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"age = %d, weight = %f", self.age, self.weight];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *p1 = [[MJPerson alloc] init];
p1.age = 20;
p1.weight = 50;
MJPerson *p2 = [p1 copy];
// p2.age = 30;
NSLog(@"%@", p1);
NSLog(@"%@", p2);
[p2 release];
[p1 release];
// NSString *str;
// [str copy];
// [str mutableCopy];
// mutableCopy只给fondation自带的类的特性
// NSArray, NSMutableArray;
// NSDictionary, NSMutableDictionary;
// NSString, NSMutableString;
// NSData, NSMutableData;
// NSSet, NSMutableSet;
}
return 0;
}
rc:retaincout
详情里在runtime的详解里,有关于isa的引用计数的详解
extra_rc:里面存储的值是引用计数器减1 (一共是19位,如果这19位不够存储,下面的has_sidetable_rc就会变为1)
has_sidetable_rc:引用计数器是否过大无法存储在isa中 ,值就会为1,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
refcnts是一个存放着对象引用计数的散列表
weak+table:弱引用表
看一下内部源码结构
retainCout:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { // 看是否是非指针类型:是否是优化过的isa指针
uintptr_t rc = 1 + bits.extra_rc; // + 1
if (bits.has_sidetable_rc) { // 是否为1:引用计数不是存储在isa中,而是存储在sidetable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this]; // 根据key取出value。
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
release
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) { // 判断nonpointer
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
.......
}
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) { // 是否要进行dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
retain:
- (id)retain {
return ((id)self)->rootRetain();
}
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
........
}
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
将弱引用存到哈希表中,对象要销毁的时候,就会取出当前对象对应的弱引用表,把里面的弱引用都清除掉。
看源码 看dealloc,销毁的时候 详细请看这篇weak
- (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指针
!isa.weakly_referenced && // 是否有弱引用指向
!isa.has_assoc && // 是否有关联对象
!isa.has_cxx_dtor && // 是否有c++的析构函数/销毁函数
!isa.has_sidetable_rc)) // 是否有另外一个结构存储引用计数
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this); // 上面有
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return 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); // 移除关联对象
obj->clearDeallocating(); // 指向当前对象的弱引指针置为nil
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this]; // key value 还有弱引用表
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_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 传入弱引用表和当前对象
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
.....
// 找到之后从这个表中移除
weak_entry_remove(weak_table, entry);
}
// 这里可以发现弱引用表使用了哈希表
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// 哈希表的索引,根据对象的地址值
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
我们会发现,有一些oc对象创建出来,系统有些类不需要我们释放,比方说下面这样
self.data = [NSMutableArray array];
其实是因为系统内部对类方法这种形式已经做了处理,不需要我们再release。对于mrc下的oc对象,我们只需要注意一点,谁创建(alloc、new),谁释放.
@interface MJPerson : NSObject
+ (instancetype)person;
@end
#import "MJPerson.h"
@implementation MJPerson
+ (instancetype)person
{
return [[[self alloc] init] autorelease];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[super dealloc];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [MJPerson person];
}
return 0;
}
那autorelease什么时候释放的呢?
用终端转一下下面代码 转 源码 看发生了什么
@autoreleasepool {
MJPerson *person = [[[MJPerson alloc] init] autorelease];
}
{
__AtAutoreleasePool __autoreleasepool; // 这个一执行,就会调用这个结构体的构造函数objc_autoreleasePoolPush();
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
// c++的结构体跟类很像。 当我们创建出来一个结构体变量的时候,自动调用结构体的构造函数
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
本质上讲,就是在大括号开始的时候,调用一个构造函数,这个构造函数会执行一个C语言函数 叫objc_autoreleasePoolPush,在大括号即将结束的时候,就会调用这个结构体的析构函数 也就是objc_autoreleasePoolPop,而且这个参数也就是当初push的出来的值。
objc_autoreleasePoolPush:刚开始可能会创建AutoreleasePoolPage对象
objc_autoreleasePoolPop
我们去objc源码中找objc_autoreleasePoolPush和objc_autoreleasePoolPop,会发现一个结构体对象AutoreleasePoolPage
static inline void pop(void *token)
{
AutoreleasePoolPage *page; // 自动释放池页
id *stop;
........
}
page结构体关键部分如下
class AutoreleasePoolPage
{
magic_t const magic; //
id *next;
pthread_t const thread; // 这个page对象可能是专属于某一个线程的
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
}
自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
AutoreleasePoolPage的结构
Autorelease内部的创建的调用autorelease的对象的地址值就是存放在AutoreleasePoolPage的内存中
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象(就是调用了autorelease的对象)的地址
因为AutoreleasePoolPage内存是有限的,如果4069存满了,就会创建一个新的AutoreleasePoolPage,在程序运行过程中,可能有多个AutoreleasePoolPage对象的,而这些所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起。
调用begin会返回0x1038这个地址给你,返回的就是那里开始存放AutoreleasePoolPage对象的地址。
end:算出来AutoreleasePoolPage的结束地址在哪里:自己的地址(起始地址)+4096。
多个AutoreleasePoolPage的联系:child指针放着下一个AutoreleasePoolPage对象的地址值,而每一个AutoreleasePoolPage都有自己的对象,多出来的空间就用来存储:调用autorelease对象的地址值。
parent:指向上一个对象,如果是第一个AutoreleasePoolPage,则parent是空的,如果是最后一个AutoreleasePoolPage对象,则child也是空的。
objc_autoreleasePoolPush:
(创建完page)调用push方法会将一个POOL_BOUNDARY(这个值为0)入栈(放入begin的位置)(这个栈说的是数据结构的栈,先进后出,这个操作的内存区域还是在堆区),并且返回其存放的内存地址(这个值是要给pop函数传入的),,紧接着,一旦有一个对象调用autorelease,就会将这个对象的内存地址存放到下一个位置。按顺序一个一个往下存。(所以这个内存会存两个内容:一个是POOL_BOUNDARY,一个是对象指针)
objc_autoreleasePoolPop:
调用pop方法时传入一个POOL_BOUNDARY的内存地址(这个值就是上面的返回的地址值),会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY,结束。
next:
id *next指向了下一个能存放autorelease对象地址的区域
刚开始什么都没有的时候,指向0x1038,紧接着调用push,就会将POOL_BOUNDARY存到刚才的位置,所以next指向下一个位置。此时调用第一个autorelease对象,地址也放进去了,next就会又动一个,指向下一个位置。
好,紧接着讲一个更复杂的嵌套情况
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
@autoreleasepool { // r2 = push()
for (int i = 0; i < 600; i++) {
MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
}
@autoreleasepool { // r3 = push()
MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
上面的代码是下面图片的结构
上面的过程解释1:
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
// fondation内部的函数
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
@autoreleasepool { // r2 = push()
MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
// for (int i = 0; i < 600; i++) {
// MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
// }
@autoreleasepool { // r3 = push()
MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
// 直接调用,编译器回去寻找这个函数,并调用 c语言的特性
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
objc[8529]: ##############
objc[8529]: AUTORELEASE POOLS for thread 0x100390380
objc[8529]: 7 releases pending.
objc[8529]: [0x10100a000] ................ PAGE (hot) (cold) // 只有一个page对象。hot:当前page,正在使用的page
objc[8529]: [0x10100a038] ################ POOL 0x10100a038
objc[8529]: [0x10100a040] 0x10050f420 MJPerson
objc[8529]: [0x10100a048] 0x100506a30 MJPerson
objc[8529]: [0x10100a050] ################ POOL 0x10100a050
objc[8529]: [0x10100a058] 0x1005096b0 MJPerson
objc[8529]: [0x10100a060] ################ POOL 0x10100a060
objc[8529]: [0x10100a068] 0x1005096e0 MJPerson
objc[8529]: ##############
上面的过程解释2:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj); // 没有满的情况下添加
} else if (page) {
return autoreleaseFullPage(obj, page); // 满了就再创建一个
} else { // 如果没有page,就创建一个新的
return autoreleaseNoPage(obj);
}
}
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
autorelease
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this); // 看这里哪个对象调用autorelease,就把哪个对象内存地址放进去
}
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj); // 讲对象地址放进去
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
autoreleasePoolPush源码
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 刚开始没有poolpage的情况
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY); // 创建一个新的page并且放入POOL_BOUNDARY
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
autoreleasePoolPop
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
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) {// 判断节点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();
}
}
}
mrc:
1:如果代码中被autoreleasepool代码块包住的话,release释放时机是就是大括号结束。也就是AutoreleasePoolPage调用pop方法的时候。
@autoreleasepool {
}
2:如果是下面的情况呢?
- (void)viewDidLoad { // 在source0中处理的
[super viewDidLoad];
NSLog(@"11");
// 这个Person什么时候调用release,是由RunLoop来控制的
// 它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
// 打印的viewWillAppear和viewDidAppear 同时打印,说明是在同一次循环中
MJPerson *person = [[[MJPerson alloc] init] autorelease];
NSLog(@"222");
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
2018-10-15 10:15:05.169044+0800 Interview18-autorelease时机[1715:136709] -[ViewController viewWillAppear:]
2018-10-15 10:15:05.170537+0800 Interview18-autorelease时机[1715:136709] -[MJPerson dealloc]
2018-10-15 10:15:05.171813+0800 Interview18-autorelease时机[1715:136709] -[ViewController viewDidAppear:]
在上面打印runloop;会发现有observer,可以看一下
observers = (
"{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1086a5d92), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7feb71002048>\n)}}",
"{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x108c8b6b3), context = }",
"{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1086d4da1), context = }",
"{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10e1d34ce), context = }",
"{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1086d4e1c), context = }",
"{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1086a5d92), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7feb71002048>\n)}}"
),
而这个observer,跟这个autorelease有关系,请看
/*
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
};
*/
/*
kCFRunLoopEntry push :(activities = 0x1)
{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
kCFRunLoopBeforeWaiting | kCFRunLoopExit (activities = 0xa0 :a:10:160)
kCFRunLoopBeforeWaiting pop、push
kCFRunLoopExit pop
{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
*/
so:
iOS在主线程的Runloop中注册了2个Observer
a:第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
b:第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
过程:
1:刚进入runloop就来了一次objc_autoreleasePoolPush操作,往下走,
2:在休眠之前,调用objc_autoreleasePoolPop(跟一开始的push进行对应,他们之前有autorelease的操作,就会在此次休眠之前进行释放),紧接着调用了一次objc_autoreleasePoolPush操作。接着被唤醒,做各种操作,再尽心新的一次循环,当即将又进入休眠的时候再次进行第二部的操作,先pop,再push。
3:接着往下走,如果没有循环了,在退出runloop之前,pop。
在处理runloop循环中,一开始进行了push操作,进入循环,接着处理source0的时候,处理viewdidload,这个viewdidload中有个对象(person)调用了autorelease,那么就会交给AutoreleasePoolPage处理,紧接着调用viewwillappear(在同一次循环中),接着往下走,开始休眠,当即将进入休眠之前,会调用objc_autoreleasePoolPop,来对应上一次的push(上面的 person对象就release了)。紧接着会再次调用objc_autoreleasePoolPush。
mrc:如果对person对象用的是autorelease,不会马上释放,那么会在runloop的循环结束休眠前进行释放,经验证是在viewwillappear之后。
mrc:如果是在person对象的局部变量里直接用的release,那么就在局部变量里调用release之后就释放了。
arc:经验证,person释放在viewwillappear之前,所以我们猜测,在局部变量大括号执行之前,加入了release。也就是马上释放。
LLVM(编译器)+ Runtime :ARC是LLVM编译器和Runtime系统相互协作的一个结果
ARC利用LLVM编译器自动帮我们生成release、retain和autorelease的代码。
像弱引用这样的是需要Runtime的支持,是在程序运行过程中,检测到对象销毁的时候,就会把对象对应的弱引用都会销毁掉。