内存管理(一)

面试题

1.使用CADisplayLink、NStimer有什么注意点?
  - 注意循环引用
  - 会造成时间不准确的问题(NStimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时)
  - 要求比较准时的时候,还是需要用GCD来实现定时器
2.介绍下内存的几大区域?
地址从低到高:保留区/代码区/数据段/堆/栈/内核区
3.讲一下你对ios内存管理的理解?
    TaggedPointer、NONPOINTER_ISA(64位系统下)、散列表(引用计数表、weak表)
4.ARC帮我们做了什么?
    LLVM+Runtime
    LLVM(编译器在编译的时候,会自动帮我们在合适的地方添加retain,release等操作)+runtime(在运行时的比如弱指针会帮我们去管理清除)
    总的来说:是LLVM和Runtime共同作用的结果   
5.weak指针实现的原理?
    将弱引用存储到一个哈希表里,当对象要销毁时,就会取出当前对象的弱引用表,将该表存储的弱引用都给清除掉
【具体的实现原理可参考-- https://www.jianshu.com/p/9c52b6f9d01c】
6.autorelease对象在什么时机会被调用release?
    如果有被autoreleasepool包裹的,是出大括号就开始被释放(调用了pop的方法时候),如不是 :它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
7.方法里有局部对象,出了方法后会被立即释放?
    MRC环境下:不一定,是在当前runloop循环中,即将进入休眠时释放
  ARC环境下:马上释放,只要出了作用域就释放

在下面我们会逐步来说明

Demo代码可见MemoryManagement


分析下面的情况

-(void)test1{
    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"];
        });
    }
}

================
-(void)test2{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i=0;i<1000;i++){
        dispatch_async(queue, ^{
             self.name = [NSString stringWithFormat:@"abc"];
        });
    }
}
memoryManagerment_1.png

解决办法:加锁

@property (assign, nonatomic) os_unfair_lock lock;
@property (nonatomic,strong)NSString  *name;

-(void)test1{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i=0;i<1000;i++){
        dispatch_async(queue, ^{
             os_unfair_lock_lock(&_lock);
             self.name = [NSString stringWithFormat:@"abcdefghijk"];
             os_unfair_lock_unlock(&_lock);
        });
    }
}

第二种:没有崩溃(正常运行)

  • 分析:taggedPointer

  • abc直接存储在指针里面了

何为:taggedPointer 下面我们会具体的分析


CADisplayLink、NSTimer使用注意

CADisplayLink、NSTimer会对target产生强引用,如果target又对他们产生强引用,那么必然会造成循环强引用
如:
self.link = [CADisplayLink displayLinkWithTarget:self  selector:@selector(linkTest)];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector: @selector(timerTest) userInfo:nil repeats:YES]; 
  • 解决办法
    • 使用block
    • 使用代理对象(NSProxy)
timer解决:
方法一:     
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerTest];
}];

- (void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

方法二:
YDProxy.h
#import 
@interface YDProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

YDProxy.m
#import "YDProxy.h"

@implementation YDProxy
+ (instancetype)proxyWithTarget:(id)target{
    YDProxy *proxy = [[YDProxy alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return self.target;
}

-(void)dealloc{
    NSLog(@"%s", __func__);
}
@end

使用:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];

-(void)timerTest{
    NSLog(@"%s",__FUNCTION__);
}

-(void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

=====================
CADisplayLink解决:
//保证调用频率和屏幕的刷帧频率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

-(void)dealloc{
    [self.link invalidate];
}
memoryManagerment_2.png

NSProxy

  • 1.专门用来做消息转发的(和NSObject平级)
  • 2.和继承自NSobject比较,效率高(本类方法没有,不需要 从父类去找了,直接进入消息转发)
YDProxy2.h
#import 
@interface YDProxy2 : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

YDProxy2.m
#import "YDProxy2.h"

@implementation YDProxy2
+ (instancetype)proxyWithTarget:(id)target{
    // NSProxy对象不需要调用init,因为它本来就没有init方法
    YDProxy2 *proxy = [YDProxy2 alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.target methodSignatureForSelector:sel];
}

//重定向消息
- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.target];
}
@end

ViewController_3.m
#import "ViewController_3.h"
#import "YDProxy2.h"

@interface ViewController_3()
@property (strong, nonatomic) NSTimer *timer;
@end

@implementation ViewController_3
-(void)viewDidLoad{
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy2 proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
    
     //分析:
    //继承了NSProxy ,那么大部分就会进入消息转发阶段。
    //在调用isKindOfClass 的时候,进入了消息转发。进入到 methodSignatureForSelector 和 forwardInvocation 这两个方法。
    //调用顺序就改成了 target 。也就是说先拿到[YDProxy2 proxyWithtarget: vc] 中的 vc
    //然后变成了[vc isKindOfClass:[ViewController class]];
    NSLog(@"%d",[[YDProxy2 proxyWithTarget:self] isKindOfClass:[ViewController_3 class]]);
}

- (void)timerTest{
    NSLog(@"%s", __func__);
}

- (void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end

GCD定时器

memoryManagerment_3.png
#import "ViewController_4.h"
@interface ViewController_4()
@property (nonatomic,strong)dispatch_source_t timer;
@end

@implementation ViewController_4
-(void)viewDidLoad{
    [super viewDidLoad];
    
    [self test1];
}

-(void)test1{
    // 队列
    dispatch_queue_t queue = dispatch_queue_create("timer", 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秒执行
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),interval * NSEC_PER_SEC, 0);
    // 设置回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"1111");
    });
    
//    OR: dispatch_source_set_event_handler_f(timer, timerFire);
    
    // 启动定时器
    dispatch_resume(timer);
    self.timer = timer; //得使用强引用引用下
}

void timerFire(void *param){
    NSLog(@"2222 - %@", [NSThread currentThread]);
}

- (void)dealloc{
    NSLog(@"%s", __func__);
}
@end

接下来我们封装下:

YDTimer.h
#import 
@interface YDTimer : 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

YDTimer.m
#import "YDTimer.h"

@implementation YDTimer
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;
    
    return [self execTask:^{
        if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#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

调用:
#import "ViewController_5.h"
#import "YDTimer.h"
@interface ViewController_5()
@property (copy, nonatomic) NSString *task;
@end

@implementation ViewController_5
- (void)viewDidLoad {
    [super viewDidLoad];
    // 接口设计
    self.task = [YDTimer execTask:self
                         selector:@selector(doTask)
                            start:2.0
                         interval:1.0
                          repeats:YES
                            async:NO];
}

- (void)doTask{
    NSLog(@"doTask - %@", [NSThread currentThread]);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [YDTimer cancelTask:self.task];
}

- (void)dealloc{
    NSLog(@"%s", __func__);
    [YDTimer cancelTask:self.task];
}
@end

iOS程序的内存布局

memoryManagerment_4.png
static int A = 10 ;
int B;

@implementation ViewController_6
-(void)viewDidLoad{
    [super viewDidLoad];
    
    //数据段
    NSLog(@"%p",&A);
    NSLog(@"%p",&B);
    
    NSString *str = @"字符串常量";
    NSLog(@"%p",str);
    
    //堆
    NSObject *obj1 = [[NSObject alloc]init];
    NSObject *obj2 = [[NSObject alloc]init];
    NSLog(@"%p",obj1);
    NSLog(@"%p",obj2);
    
    //栈
    int age=10;
    float height = 180.0f;
    NSLog(@"%p",&age);
    NSLog(@"%p",&height);
    
}

打印:
MemoryManagement[10795:942733] 0x105488190
MemoryManagement[10795:942733] 0x105488210
MemoryManagement[10795:942733] 0x105485450
MemoryManagement[10795:942733] 0x600002aab7e0
MemoryManagement[10795:942733] 0x600002aaba60
MemoryManagement[10795:942733] 0x7ffeea77c3c4
MemoryManagement[10795:942733] 0x7ffeea77c3c0
memoryManagerment_5.png

Tagged Pointer

memoryManagerment_6.png
#import "ViewController_7.h"
#import 

@implementation ViewController_7
-(void)viewDidLoad{
    [super viewDidLoad];
    
    NSNumber *a = @(1);
    NSLog(@"%p",a);
    
    NSNumber *b  = @(2);
    NSLog(@"%p",b);
    
    NSNumber *c = @(3.1415926);
    NSLog(@"%p",c);
    
    NSLog(@"=======Start======");
    for(int i=0;i<10;i++){
        NSNumber *d  = @((int)(arc4random() % 520));
        NSLog(@"%p--a=%@",d,d);
    }
    NSLog(@"=======End======");
    
    NSString *str = @"abc"; // __NSCFConstantString(常量字符串)
    NSLog(@"%p -- %@",str,[str class]);
    
    NSString *str1 = [NSString stringWithFormat:@"%@",str]; // NSTaggedPointerString
    NSLog(@"%p -- %@",str1,[str1 class]);
    
    NSString *str2 = [str copy]; // __NSCFConstantString(常量字符串)
    NSLog(@"%p -- %@",str2,[str2 class]);

    NSMutableString *str3 = [str mutableCopy]; // __NSCFString(字符串)
    NSLog(@"%p -- %@",str3,[str3 class]);

}
@end

打印:
MemoryManagement[30107:1510547] 0x982ce8502b222809
MemoryManagement[30107:1510547] 0x982ce8502b222839
MemoryManagement[30107:1510547] 0x6000029efa00
MemoryManagement[30107:1510547] =======Start======
MemoryManagement[30107:1510547] 0x982ce8502b222d49--a=85
MemoryManagement[30107:1510547] 0x982ce8502b222009--a=129
MemoryManagement[30107:1510547] 0x982ce8502b222239--a=162
MemoryManagement[30107:1510547] 0x982ce8502b223959--a=276
MemoryManagement[30107:1510547] 0x982ce8502b222899--a=8
MemoryManagement[30107:1510547] 0x982ce8502b223f99--a=376
MemoryManagement[30107:1510547] 0x982ce8502b222ee9--a=111
MemoryManagement[30107:1510547] 0x982ce8502b223429--a=451
MemoryManagement[30107:1510547] 0x982ce8502b223969--a=279
MemoryManagement[30107:1510547] 0x982ce8502b222c29--a=67
MemoryManagement[30107:1510547] =======End======
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
MemoryManagement[30107:1510547] 0x882ce8502d140e08 -- NSTaggedPointerString
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
memoryManagerment_7.png

发现:变量a和变量b不是一个对象(伪对象)、而变量c是一个真正的oc对象

(如果:是一个比较长的数字,或者小数等(占位较大的),就是一个真正的oc对象)

memoryManagerment_8.png

发现:变量str1不是一个对象(伪对象,isa的指向0x0),而变量str,str2,str3是oc对象

(如果:是一个比较长的字符串,那么就是一个真正的OC对象)

我们在来看看上面打印的内存地址:

对于NSNumber:是taggerPointer的末尾 是一样的(现在的重新运行时9)(再次运行可能结果不一样)

并且:他们的前面的几位都有一样的

对于string的 是NSTaggedPointerString的,他的地址和真正的字符串地址是有很大不一样的

具体的:可以参考Objective-C 的 Tagged Pointer 实现、Objective-C对象的TaggedPointer特性

【这里只是简单的瞅瞅】


判断是否为Tagged Pointer

objc4-750里面
objc_internal.h

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if 
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr){
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

即:(需要讲16进制的地址转为2进制的)
ios平台(64bit):最高有效位为1
Mac平台:最低有效位是1

我们用上面的例子:

对于:NSNumber *a = @(1); a的地址是:0x982ce8502b222809 转为二进制

1001/1000/0010/1100/1110/1000/0101/0000/0010/1011/0010/0010/0010/1000/0000/1001

发现:最高位为1(是Tagged Pointer)

对于:NSNumber *c = @(3.1415926); c的地址是:0x6000029efa00转为二进制

0000/0000/0000/0000/0110/0000/0000/0000/0000/0010/1001/1110/1111/1010/0000/0000

发现:最高位为0(不是Tagged Pointer)

这里我们可以发现:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
   dispatch_async(queue, ^{
       self.name = [NSString stringWithFormat:@"abc"];
   });
}

没有崩溃:不会出现资源争夺的问题(它的值是直接存储在指针地址里面的,不是一个真正的oc对象)


OC对象的内存管理

memoryManagerment_9.png
MRC:
Person *person1 = [[[Person alloc] init] autorelease];
在自动释放池里面调用的autorelease会被释放掉

============
// 自动生成成员变量和属性的setter、getter实现
@synthesize age = _age;


@property (nonatomic, retain) Dog *dog;
@synthesize dog= _dog;

/*
- (void)setDog:(Dog *)dog{
    if (_dog != dog) {
        [_dog release];
        _dog = [dog retain];
    }
}

- (Dog *)dog{
    return _dog;
}
*/

- (void)dealloc{
    self.dog = nil;
    [super dealloc];
}

==========
@property (retain, nonatomic) NSMutableArray *data;

1.
self.data = [[[NSMutableArray alloc] init] autorelease];
    
2.
self.data = [[NSMutableArray alloc] init];
[self.data release];
    
3.
NSMutableArray *data = [[NSMutableArray alloc] init];
self.data = data;
[data release];
 
4.self.data = [NSMutableArray array];
- (void)dealloc {
     self.data = nil;    
     [super dealloc];
}

copy

拷贝的目的:产生一个副本对象,跟源对象互不影响
修改了源对象,不会影响副本对象
修改了副本对象,不会影响源对象

iOS提供了2个拷贝方法
1.copy,不可变拷贝,产生不可变副本
 
2.mutableCopy,可变拷贝,产生可变副本

深拷贝和浅拷贝
1.深拷贝:内容拷贝,产生新的对象
2.浅拷贝:指针拷贝,没有产生新的对象

NSString *str1 = [NSString stringWithFormat:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
    
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
    
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002a13480 -- __NSCFString
    
[str3 release];
[str2 release];
[str1 release];

打印:
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x6000016a03c0 -- __NSCFString

发现:str1和str2指向同一个地方,而str3不是

memoryManagerment_10.png
NSMutableString *str1 = [[NSMutableString alloc]initWithString:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0x600002d16dc0 -- __NSCFString
    
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xef964e52ba42afec -- NSTaggedPointerString

NSString *str3 = [str2 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002d16cd0 -- __NSCFString
    
[str3 release];
[str2 release];
[str1 release];

打印:
MemoryManagement[31096:1571416] 0x600002d16dc0 -- __NSCFString
MemoryManagement[31096:1571416] 0xef964e52ba42afec -- NSTaggedPointerString
MemoryManagement[31096:1571416] 0x600002d16cd0 -- __NSCFString
memoryManagerment_11.png

copy和mutableCopy

memoryManagerment_12.png

@property (copy, nonatomic) NSMutableArray *data; //有问题,讲一个可变的数组copy成了不可变的了


友情链接:

  • gitHub_Demo

  • 内存管理(二)

  • weak原理实现

你可能感兴趣的:(内存管理(一))