美文网首页
iOS内存管理

iOS内存管理

作者: CoderKK | 来源:发表于2020-03-27 15:57 被阅读0次
    iOS程序的内存布局(由低到高)
    保留段
    代码段(_TEXT)
    数据段(_DATA)
    堆(heap)
    栈(stack)
    内核区

    代码段:编译之后的代码
    数据段:按如下由低到高分配

    • 字符串常量,如NSString *str = @"abc" (常量区)
    • 已初始化数据:已初始化的全局变量和静态变量等 (全局区)
    • 未初始化数据未初始化的全局变量和静态变量等 (全局区)

    栈:函数调用开销,比如局部变量。内存分配的内存空间越来越小
    堆:通过alloc、malloc等动态分配的空间,分配的空间地址越来越大

    Tagged Pointer
    • 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
    • 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
    • 使用Tagged Pointer之后,NSNumber指针里面存储数据编程了:Tag + Data,也就是将数据直接存储在了指针中
    • 当指针不够存储数据时,才会使用动态分配内存的方式创建对象来存储数据
    • objc_msgSend能识别Tagged Pointer,如果是Tagged Pointer,直接从指针提取数据,节省了以前的调用开销
    • 判断是否Tagged Pointer
      iOS平台,最高有效位是1
      Mac平台,最低有效位是1

    判断Tagged Pointer相关源码

    #if TARGET_OS_OSX && __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 OBJC_MSB_TAGGED_POINTERS
    #   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;
    }
    
    相关面试题

    思考一下以下两段代码会发生什么事?有什么区别?

    @property (strong, nonatomic) NSString *name;
    
    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"];
        });
    }
    
    @property (strong, nonatomic) NSString *name;
    
    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"];
        });
    }
    

    第一段会报坏内存访问(EXC_BAD_ACCESS)异常,第二段正常运行
    原因:第一段self.name的本质是调用[self setName:]方法,方法实质实现如下

    - (void)setName:(NSString *)name{
        if (_name != name) {
            [_name release];
            _name = [name retain];
        }
    }
    

    当多个线程同时调用时,就会出现由于_name已经被释放但还继续调用[_name release]方法,造成坏内存访问,可以使用加锁方式解决该异常
    第二段由于[NSString stringWithFormat:@"abc"]是个Tagged Ponter,采用的是直接赋值的方式,所以不会造成问题。

    OC对象的内存管理
    • 在iOS中,使用引用计数来管理OC对象的内存
    • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象会销毁,释放其占用的内存空间
    • 调用retain会会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
    • 内存管理经验总结:
      当调用alloc、new、copy、mutableCopy方法返回一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
    copy和mutableCopy
    NSString NSMutableString NSArray NSMutableArray NSDictionary NSMutableDictionary
    copy 浅拷贝 深拷贝 浅拷贝 深拷贝 浅拷贝 深拷贝
    mutableCopy 深拷贝 深拷贝 深拷贝 深拷贝 深拷贝 深拷贝

    深拷贝和浅拷贝的判断依据是有没有生成新的副本对象
    总结:不可变对象的copy是浅拷贝,其他的均为深拷贝
    自定义对象使用copy,要遵循NSCopying协议,实现copyWithZone:方法

    ARC
    • ARC的本质也是MRC, 只不过我们不用手动去管理引用计数,管理引用计数的相关代码由编译器(lvvm)帮我们生成了。ARC是编译器(lvvm)和Runtime机制(weak指针自动置nil)共同协调完成的。
    • 在ARC环境下,我们需注意循环引用问题,一般使用weak或__unsafe_unretained解决循环引用。
    • ARC 只能帮我们管理 Foudation 对象,非 Foundation 框架下的还是需要我们手动管理的,比如我们常用到 CoreFoundation,使用 CoreFoundation 时就需要注意是否分配了内存,如果分配了内存需要手动调用 free进行释放。以及 Foundation 对象转 CoreFoundation下的内存管理权的移交问题。

    相关文章

      网友评论

          本文标题:iOS内存管理

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