Objective-C基础-内存管理

1、CADisplayLink、NSTimer使用

CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
解决方案

  • 使用Block
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer *timer) {
    [weakSelf test];
}];

- (void)test {
    
}
  • 使用代理对象(NSProxy)
@interface SRProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

@implementation SRProxy

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

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

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                              target:[SRProxy proxyWithTarget:self]
                                            selector:@selector(test)
                                            userInfo:nil
                                             repeats:YES];
                                             
- (void)test {
    
}

2、GCD定时器

  • NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
  • 而GCD的定时器会更加准时
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);

//创建定时器
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, (int64_t)(start * NSEC_PER_SEC)),
                          interval * NSEC_PER_SEC,
                          0 * NSEC_PER_SEC);
    
//设置回调
dispatch_source_set_event_handler(timer, ^{
    
});
    
//启动定时器
dispatch_resume(timer);

3、iOS程序的内存布局

Objective-C基础-内存管理_第1张图片
内存布局.jpg
  • 代码段:编译之后的代码
  • 数据段
  • 字符串常量:比如NSString *str = @"123"
  • 已初始化数据:已初始化的全局变量、静态变量等
  • 未初始化数据:未初始化的全局变量、静态变量等
  • 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
  • 堆:通过allocmalloccalloc等动态分配的空间,分配的内存空间地址越来越大

4、Tagged Pointer

  • 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumberNSDateNSString等小对象的存储
  • 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
  • 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
  • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
  • objc_msgSend能识别Tagged Pointer,比如NSNumberintValue方法,直接从指针提取数据,节省了以前的调用开销
  • 如何判断一个指针是否为Tagged Pointer
  • iOS平台,最高有效位是1(第64bit)
  • Mac平台,最低有效位是1
//objc-internal.h
#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
//objc-internal.h
static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

例子
多线程访问设置NSString崩溃

@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

将字符串改为短点的abc不崩溃,因为abc直接存储到name的指针中。

@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:@"abc"];
        });
    }
}
@end

5、OC对象的内存管理

  • 在iOS中,使用引用计数来管理OC对象的内存
  • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  • 内存管理的经验总结
  • 当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
  • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
  • 可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);

6、copy和mutableCopy

Left Aligned copy mutableCopy
NSString NSString(浅拷贝) NSMutableString(深拷贝)
NSMutableString NSString(深拷贝) NSMutableString(深拷贝)
NSArray NSArray(浅拷贝) NSMutableArray(深拷贝)
NSMutableArray NSArray(深拷贝) NSMutableArray(深拷贝)
NSDictionary NSDictionary(浅拷贝) NSMutableDictionary(深拷贝)
NSMutableDictionary NSDictionary(深拷贝) NSMutableDictionary(深拷贝)

7、引用计数的存储

在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
};

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

refcnts是一个存放着对象引用计数的散列表

8、dealloc

当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是

dealloc
_objc_rootDealloc
rootDealloc
object_dispose
objc_destructInstance、free
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;
}

9、自动释放池

  • 自动释放池的主要底层数据结构是:__AtAutoreleasePoolAutoreleasePoolPage
  • 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
  • 源码分析
  • clang重写@autoreleasepool
  • objc4源码:NSObject.mm
class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  • 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
  • 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
  • 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
  • id *next指向了下一个能存放autorelease对象地址的区域
Objective-C基础-内存管理_第2张图片
AutoreleasePoolPage的结构.jpg

10、Runloop和Autorelease关系

iOS在主线程的Runloop中注册了2个Observer

  • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer
  • 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()
  • 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

你可能感兴趣的:(Objective-C基础-内存管理)