美文网首页
iOS下的内存管理

iOS下的内存管理

作者: buding_ | 来源:发表于2024-07-03 09:46 被阅读0次

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

  • 循环引用问题,CADisplayLink、NSTimer会对target进行强引用,如果target又对它们产生强引用,那么就会引发循环问题
  • 不准时的问题,NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时(RunLoop每循环一遍的时间不是不固定的)
循环引用的解决方案:

1、使用block
2、引入proxy,target --strong-> CADisplayLink/NSTimer --strong-> proxy --weak-> target , proxy通过消息转发机制将方法转发给target

@interface TimerProxy: NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (nonatomic, weak) id target;

@end

@implementation TimerProxy

+ (instancetype)proxyWithTarget:(id)target {
    TimerProxy *proxy = [TimerProxy alloc];
    proxy.target = target;
    return proxy;
}

//- (id)forwardingTargetForSelector:(SEL)aSelector {
//    return self.target;
//}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

- (void)dealloc {
    NSLog(@"---- TimerHelper dealloc");
}
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    //调用频率和屏幕的刷帧频率一致 一般60FPS(若主线程做得事情过多有可能没有60FPS)
    self.disLink = [CADisplayLink displayLinkWithTarget:[TimerProxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.disLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[TimerProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];

}

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

GCD定时器
  • GCD不依赖RunLoop, 它是依赖内核函数,故它的准时性比较高
  • GCD对象在ARC下无需手动销毁
//创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);// 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.0秒后开始执行
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(@"---- timerTest %@",[NSThread currentThread]);

});
//    dispatch_source_set_event_handler_f(timer, timerGCDTest);
dispatch_resume(timer);
self.timer_gcd = timer;
  • 封装一个GCD定时器
@interface BDTimer : 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

@implementation BDTimer

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


内存布局
iOS程序内存分配.png
Tagged Pointer
  • 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber NSDate NSString 等小对象的存储
  • 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
  • 在使用Tagged Pointer之后,NSNumber指针里面存储的数据变成: Tag+Data 也就是将数据直接存储在了指针中
  • 当对象指针的最低有效位是1,则该指针为Tagged Pointer
  • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
  • objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
OC对象深拷贝与浅拷贝
深拷贝与浅拷贝.png
引用计数的存储

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

struct SlideTable {
  spinlock_t slock;
  RefcountMap refcnts;
  weak_table_t weak_table;
}
refcnts 是一个存放着对象应用计数的散列表 

ARC都帮我们做了什么
LLVM + Runtime 相互协助的结果
LLVM编译器会自动生成内存管理相关的代码(release retain等)
Runtime运行时,在程序运行过程中处理如弱引用等操作
weak指针的实现原理
赋值weak指针时,并不是对引用计数+1,而是将其存储到SlideTable的weak_table中;
当销毁对象时 取出weak_table中的指针置为nil

相关文章

网友评论

      本文标题:iOS下的内存管理

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