使用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
内存布局

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对象深拷贝与浅拷贝

引用计数的存储
在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
网友评论