iOS 内存管理:Weak、Autorelease、Copy、Tagged Pointer、Timer问题

一:面试题:

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释放时机

 

 

 

 

一:面试题

 

1.1:CADisplayLink、NSTimer

1.1.1:CADisplayLink、NSTimer循环引用

  • 使用CADisplayLink、NSTimer有什么注意点?

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 <Foundation/Foundation.h>

@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 <NSObject> {
    Class    isa;
}
@interface NSObject <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 <Foundation/Foundation.h>

@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一样有用。

 

1.1.2:NSProxy小问题

#import <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>

@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都进行了消息转发。

 

1.1.3:GCD定时器

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 - <NSThread: 0x608000461b40>{number = 3, name = (null)}
2018-10-09 14:44:25.015591+0800 Interview02-GCD定时器[3835:375012] 2222 - <NSThread: 0x608000461b40>{number = 3, name = (null)}
2018-10-09 14:44:26.015557+0800 Interview02-GCD定时器[3835:375012] 2222 - <NSThread: 0x608000461b40>{number = 3, name = (null)}

在arc环境下 GCD创建出来的对象 都不需要销毁。

封装


#import <Foundation/Foundation.h>

@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<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [MJTimer cancelTask:self.task];
}

注意上面target和block不会产生循环引用。上面已经解释清楚。

 

1.2:iOS程序的内存布局

 

  •  
  • 代码段:编译之后的代码
  •  
  • 数据段:
  • 字符串常量:比如NSString *str = @“123"  @“123”会在常量区
  • 已初始化数据:已初始化的全局变量、静态变量等 int a = 10;
  • 未初始化数据:未初始化的全局变量、静态变量等 int a;
  •  
  • :函数调用开销,比如局部变量。分配的内存空间地址越来越小 。  栈内存的地址比较大
  •  
  • :通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大

 

 

 

#import <UIKit/UIKit.h>
#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
 */

 

1.2.1:Tagged Pointer

从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 <Foundation/Foundation.h>

//  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。

 

1.3:OC对象的内存管理

那普通的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 <Foundation/Foundation.h>
#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 <Foundation/Foundation.h>
#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

 

1.3.1:copy

关于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。:mutable的都用strong。

 

OC对象的Copy:

#import <Foundation/Foundation.h>

@interface MJPerson : NSObject <NSCopying>
@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;
}

 

1.3.2:引用计数的存储

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;
}

 

1.3.3:weak指针的原理

将弱引用存到哈希表中,对象要销毁的时候,就会取出当前对象对应的弱引用表,把里面的弱引用都清除掉。

看源码 看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];
}

 

1.3.4:autorelease

我们会发现,有一些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();
            }
        }
    }

 

1.3.5:autorelease释放时机

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 = (
    "<CFRunLoopObserver 0x60c000139a00 [0x1084f7c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1086a5d92), context = <CFArray 0x60c000045c70 [0x1084f7c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7feb71002048>\n)}}",
    "<CFRunLoopObserver 0x60c0001396e0 [0x1084f7c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x108c8b6b3), context = <CFRunLoopObserver context 0x60c0000d5310>}",
    "<CFRunLoopObserver 0x60c000139780 [0x1084f7c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1086d4da1), context = <CFRunLoopObserver context 0x7feb70d01de0>}",
    "<CFRunLoopObserver 0x600000139f00 [0x1084f7c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10e1d34ce), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x60c0001398c0 [0x1084f7c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1086d4e1c), context = <CFRunLoopObserver context 0x7feb70d01de0>}",
    "<CFRunLoopObserver 0x60c000139aa0 [0x1084f7c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1086a5d92), context = <CFArray 0x60c000045c70 [0x1084f7c80]>{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)


 <CFRunLoopObserver 0x60000013f220 [0x1031c8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
 
 
 kCFRunLoopBeforeWaiting | kCFRunLoopExit    (activities = 0xa0 :a:10:160)
 kCFRunLoopBeforeWaiting pop、push
 kCFRunLoopExit pop



 <CFRunLoopObserver 0x60000013f0e0 [0x1031c8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{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。

看一个完整的

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool { //  r1 = push() :POOL_BOUNDARY1每个都不同是地址
            
            Person *p1 = [[[Person alloc] init] autorelease];  // 插入对象地址
            Person *p2 = [[[Person alloc] init] autorelease];  // 插入对象地址
            
            @autoreleasepool { // r2 = push() POOL_BOUNDARY2
                //                   Person *p3 = [[[Person alloc] init] autorelease]; // 插入对象地址
                
                for (int i = 0; i < 5; i++) {
                    Person *p3 = [[[Person alloc] init] autorelease]; // 插入对象地址5次
                }
                
                @autoreleasepool { // r3 = push() POOL_BOUNDARY3
                    Person *p4 = [[[Person alloc] init] autorelease];  // 插入对象地址
                    // 直接调用,编译器回去寻找这个函数,并调用 c语言的特性
                    //                       _objc_autoreleasePoolPrint();
                    
                } // pop(r3) POOL_BOUNDARY3 把p4的内存地址释放掉清空(不再存储在page里面)
                
            } // pop(r2) POOL_BOUNDARY2 把五次创建的p3的地址都释放掉清空(不再存储在page里面)
            
            @autoreleasepool { // r4 = push() POOL_BOUNDARY4
                for (int i = 0; i < 5; i++) {
                    Person *p5 = [[[Person alloc] init] autorelease];// 插入对象地址5次
                }
            } // pop(r4) POOL_BOUNDARY4 把五次创建的p5的地址都释放掉清空(不再存储在page里面)
        } // pop(r1) POOL_BOUNDARY1 把存入的p1和p2地址清空(不再存储在page里面)
        
    } // 这里结束了 ,基本main函数也走到头了
    
    return 0;
}

假设断点打在pop(r3)的地方

(lldb) po _objc_autoreleasePoolPrint();
objc[10640]: ##############
objc[10640]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[10640]: 12 releases pending.
objc[10640]: [0x10100f000]  ................  PAGE  (hot) (cold)
objc[10640]: [0x10100f038]  ################  POOL 0x10100f038
objc[10640]: [0x10100f040]  ################  POOL 0x10100f040
objc[10640]: [0x10100f048]       0x10385a350  Person
objc[10640]: [0x10100f050]       0x10385a630  Person
objc[10640]: [0x10100f058]  ################  POOL 0x10100f058
objc[10640]: [0x10100f060]       0x103859690  Person
objc[10640]: [0x10100f068]       0x103858ae0  Person
objc[10640]: [0x10100f070]       0x103858bf0  Person
objc[10640]: [0x10100f078]       0x103857cd0  Person
objc[10640]: [0x10100f080]       0x103857580  Person
objc[10640]: [0x10100f088]  ################  POOL 0x10100f088
objc[10640]: [0x10100f090]       0x103856b00  Person
objc[10640]: ##############
0x190478a3fa420011

假设断点打在pop(r2)的地方

(lldb) po _objc_autoreleasePoolPrint();
objc[10647]: ##############
objc[10647]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[10647]: 10 releases pending.
objc[10647]: [0x10480b000]  ................  PAGE  (hot) (cold)
objc[10647]: [0x10480b038]  ################  POOL 0x10480b038
objc[10647]: [0x10480b040]  ################  POOL 0x10480b040
objc[10647]: [0x10480b048]       0x100609eb0  Person
objc[10647]: [0x10480b050]       0x100609f00  Person
objc[10647]: [0x10480b058]  ################  POOL 0x10480b058
objc[10647]: [0x10480b060]       0x100609f10  Person
objc[10647]: [0x10480b068]       0x100609f20  Person
objc[10647]: [0x10480b070]       0x100609f30  Person
objc[10647]: [0x10480b078]       0x100609f40  Person
objc[10647]: [0x10480b080]       0x100609f50  Person
objc[10647]: ##############
0x01cfb5f4a1ef002c

假设断点打在pop(r4)的地方

(lldb) po _objc_autoreleasePoolPrint();
objc[10647]: ##############
objc[10647]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[10647]: 10 releases pending.
objc[10647]: [0x10480b000]  ................  PAGE  (hot) (cold)
objc[10647]: [0x10480b038]  ################  POOL 0x10480b038
objc[10647]: [0x10480b040]  ################  POOL 0x10480b040
objc[10647]: [0x10480b048]       0x100609eb0  Person
objc[10647]: [0x10480b050]       0x100609f00  Person
objc[10647]: [0x10480b058]  ################  POOL 0x10480b058
objc[10647]: [0x10480b060]       0x100704cb0  Person
objc[10647]: [0x10480b068]       0x100705180  Person
objc[10647]: [0x10480b070]       0x100705d70  Person
objc[10647]: [0x10480b078]       0x100705d80  Person
objc[10647]: [0x10480b080]       0x100705d90  Person
objc[10647]: ##############
0x01cfb5f4a1ef002c

假设断点打在pop(r1)的地方

(lldb) po _objc_autoreleasePoolPrint();
objc[10647]: ##############
objc[10647]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[10647]: 4 releases pending.
objc[10647]: [0x10480b000]  ................  PAGE  (hot) (cold)
objc[10647]: [0x10480b038]  ################  POOL 0x10480b038
objc[10647]: [0x10480b040]  ################  POOL 0x10480b040
objc[10647]: [0x10480b048]       0x100609eb0  Person
objc[10647]: [0x10480b050]       0x100609f00  Person
objc[10647]: ##############
0x01cfb5f4a1ef002c

假设断点打在这里结束了的地方

(lldb) po _objc_autoreleasePoolPrint();
objc[10647]: ##############
objc[10647]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[10647]: 1 releases pending.
objc[10647]: [0x10480b000]  ................  PAGE  (hot) (cold)
objc[10647]: [0x10480b038]  ################  POOL 0x10480b038
objc[10647]: ##############
0x01cfb5f4a1ef002c
  • autorelease对象在什么时机会被调用release?

在处理runloop循环中,一开始进行了push操作,进入循环,接着处理source0的时候,处理viewdidload,这个viewdidload中有个对象(person)调用了autorelease,那么就会交给AutoreleasePoolPage处理,紧接着调用viewwillappear(在同一次循环中),接着往下走,开始休眠,当即将进入休眠之前,会调用objc_autoreleasePoolPop,来对应上一次的push(上面的 person对象就release了)。紧接着会再次调用objc_autoreleasePoolPush。

 

  • 介绍下内存的几大区域

 

  • 讲一下你对 iOS 内存管理的理解

 

 

  • LLVM + Runtime

 

  • weak指针的实现原理

 

  • 方法里有局部对象, 出了方法后会立即释放吗

mrc:如果对person对象用的是autorelease,不会马上释放,那么会在runloop的循环结束休眠前进行释放,经验证是在viewwillappear之后。

mrc:如果是在person对象的局部变量里直接用的release,那么就在局部变量里调用release之后就释放了。

arc:经验证,person释放在viewwillappear之前,所以我们猜测,在局部变量大括号执行之前,加入了release。也就是马上释放。

 

  • ARC 都帮我们做了什么?

LLVM(编译器)+ Runtime :ARC是LLVM编译器和Runtime系统相互协作的一个结果

ARC利用LLVM编译器自动帮我们生成release、retain和autorelease的代码。

像弱引用这样的是需要Runtime的支持,是在程序运行过程中,检测到对象销毁的时候,就会把对象对应的弱引用都会销毁掉。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值