美文网首页
iOS底层day10 - 内存管理

iOS底层day10 - 内存管理

作者: 宁夏灼雪__ | 来源:发表于2019-01-15 09:40 被阅读0次

    Example1:NSTimer 、CADisplayLink 循环引用问题

    先看以下代码:

    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(TimerTest) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    

    self.timer为强引用,这里Timer与控制器形成了循环引用,如果要解决这个问题,一可以使用block代替即:

    [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            // todo  使用weakSelf
        }]
    

    当类没有block时,亦或者使用中间变量 : proxy,可以让 Timer强引用Proxy,让Proxy弱引用 Target
    请看以下代码:

    self.timer = [NSTimer timerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(TimerTest) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    @interface MJProxy : NSObject
    + (MJProxy *)proxyWithTarget:(id)target;
    @property (nonatomic, weak)id target;
    @end
    
    + (MJProxy *)proxyWithTarget:(id)target {
        MJProxy *proxy = [MJProxy new];
        proxy.target = target;
        return proxy;
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return self.target;
    }
    

    这里运用了runtime的消息转发,当Timer的Target为MJProxy对象时,找不到TimerTest方法,即进入动态方法解析,再进入消息转发,会调用forwardingTargetForSelector方法,返回target对象即传进来的Target,调用TargetTimerTest方法
    系统还有另外一个类NSProxy,我们集成它,他的原理和刚刚的MJProxy是一样的,只是在找不到方法的时候,他会直接进入:

    @interface MJProxy1 : NSProxy
    + (MJProxy1 *)proxyWithTarget:(id)target;
    @property (nonatomic, weak)id target;
    @end
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return [self.target methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    

    省去了objc_msgSend 前面的动态方法解析,以及forwardingTargetForSelector,性能更佳

    CADisplayLink也是一个定时器,跟NSTimer的解决方法是一样的

    Example2:GCD定时器

    NSTimer有可能会出现不准时的情况:因为NSTImer是基于RunLoop实现的,而RunLoop是循环执行的,有可能在繁忙的时候,循环执行出现延迟
    解决方案:使用GCD定时器
    使用方法:

       // 定时器的执行队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        // 创建定时器
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        
        // 设置开始时间与间隔
        uint64_t start = 2.0f;
        uint64_t interval = 1.0f;
        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(@"111222333");
        });
        // 启动定时器
        dispatch_resume(timer);
    

    Example3:内存布局

    内存布局

    如图所示,内存地址由低往高分别是 代码段数据段,栈的内存分配地址顺序是由高往低,堆的内存分配地址是由低往高

    Tagged Point

    NSNumber为栗子

        NSNumber *number = [NSNumber numberWithInt:10];
        NSNumber *number1 = [NSNumber numberWithInt:11];
        NSNumber *number2 = @(0xFFFFFFFFFFFFFFF);
        
        NSLog(@"%p ---%p---- %p ",number,number1,number2);
    

    我们看他的输出:

    0xb0000000000000a2 ---0xb0000000000000b2---- 0x604000035e60
    

    我们可以看到他们的内存有很大的区别,这就是Tagged Point的作用,在使用Tagged Point 之前,像NSNumberNSStringNSData这些数据都是以对象的形式存储于堆空间,这样无疑是浪费内存空间的,而使用了Tagged Point之后,数据会存储在指针内,以标记+数据的形式存在
    如:
    0xb0000000000000a2b2即为NSNumber的标记,而a则对应数字10的存储

    如何判断一个内存是否为Tagged Point ?
    mac 下最低有效位 &11
    iOS 下最高有效位 &1<<63 (64位) 为 1<<63

    Copy

    请看以下代码:

        NSString *str = @"123";
        NSString *str2 = [str copy];
        NSMutableString *str3 = [str mutableCopy];
        NSLog(@"%p --- %p --- %p",str,str2,str3);
        
        NSMutableString *str4 = [NSMutableString stringWithFormat:@"33333"];
        NSString *str5 = [str copy];
        NSMutableString *str6 = [str mutableCopy];
        NSLog(@"%p --- %p --- %p",str4,str5,str6);
    

    打印输出:

    0x10b3090a0 --- 0x10b3090a0 --- 0x6000004492d0
    0x604000442760 --- 0x10b3090a0 --- 0x604000442940
    

    我们可以看到内存地址的变化,当类进行copy时返回的都是不可变的类,而进行MutableCopy时,返回的都是可变的类
    当不可变类进行copy时,只是对指针进行复制,指向同一个对象(不可变对象copy不需要多一块内存地址,可以节省内存空间),而mutableCopy时则拷贝了一块新内存地址
    无论可变类进行copy/mutableCopy,都是拷贝一份新的内存地址

    copy 不可变内存图 可变内存图

    同理 数组字典也是一样的
    用一张图总结:

    image.png

    由于copyFoundation框架的方法,当对象要进行copy时,对象则需要遵守NSCopying协议,并实现copyWithZone方法

    内存总结

    image.png

    weak 、 unsafe_unretain

    weakunsafe_unretain都不会对对象进行强引用,但是weak是相对安全的,在对象释放后,指向对象的指针会自动置nil,而unsafe_unretain不会

    相关文章

      网友评论

          本文标题:iOS底层day10 - 内存管理

          本文链接:https://www.haomeiwen.com/subject/obwxrqtx.html