引言
在iOS开发中,多线程技术是提升应用性能与响应能力的关键。它能让应用在同一时间处理多个任务,避免主线程阻塞,为用户带来流畅的体验。本文将深入探讨Objective - C底层的多线程技术,涵盖原子锁atomic
、GCD Timer、NSTimer
、CADisplayLink
等知识点,并结合示例代码进行详细说明。同时,会着重分析NSTimer
和CADisplayLink
可能造成的循环引用问题以及相应的解决办法。
一、原子锁 atomic
1.1 原理
在Objective - C中,atomic
是属性声明时的一个修饰符。当一个属性被声明为 atomic
时,编译器会自动生成一些额外的代码来保证该属性的 setter
和 getter
方法是原子操作。也就是说,在多线程环境下,同一时间只会有一个线程对该属性进行读写操作,从而避免数据竞争。
1.2 使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #import <Foundation/Foundation.h>
@interface AtomicExample : NSObject @property (atomic, assign) NSInteger counter; @end
@implementation AtomicExample
@end
int main(int argc, const char * argv[]) { @autoreleasepool { AtomicExample *example = [[AtomicExample alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{ for (int i = 0; i < 10000; i++) { example.counter++; } });
dispatch_async(queue, ^{ for (int i = 0; i < 10000; i++) { example.counter++; } });
sleep(2); NSLog(@"Counter value: %ld", (long)example.counter); } return 0; }
|
1.3 优缺点
- 优点:使用方便,只需要在属性声明时添加
atomic
修饰符,编译器会自动处理线程安全问题。
- 缺点:
atomic
只能保证属性的 setter
和 getter
方法是原子操作,但不能保证整个操作的原子性。例如,对于 self.counter++
操作,它包含了读取、加一和写入三个步骤,atomic
无法保证这三个步骤的原子性。而且,atomic
会带来一定的性能开销,因为它需要进行额外的锁操作。
二、GCD Timer
2.1 原理
GCD Timer 是基于 Grand Central Dispatch(GCD)实现的定时器。GCD 是苹果提供的一套多线程编程解决方案,它将任务封装成块(block)并放入队列中执行。GCD Timer 利用 GCD 的特性,在指定的时间间隔内重复执行任务。
2.2 使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #import <Foundation/Foundation.h>
@interface GCDTimerExample : NSObject @property (nonatomic, strong) dispatch_source_t timer; @end
@implementation GCDTimerExample
- (void)startTimer { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置定时器的开始时间、间隔和精度 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); dispatch_source_set_timer(self.timer, start, interval, 0);
// 设置定时器的回调函数 __weak typeof(self) weakSelf = self; dispatch_source_set_event_handler(self.timer, ^{ [weakSelf timerFired]; });
// 启动定时器 dispatch_resume(self.timer); }
- (void)timerFired { NSLog(@"GCD Timer fired"); }
- (void)stopTimer { if (self.timer) { dispatch_source_cancel(self.timer); self.timer = nil; } }
@end
int main(int argc, const char * argv[]) { @autoreleasepool { GCDTimerExample *example = [[GCDTimerExample alloc] init]; [example startTimer];
sleep(5); [example stopTimer]; } return 0; }
|
2.3 优缺点
- 优点:精度高,不会受到 RunLoop 的影响;可以在后台线程执行任务,不影响主线程的响应;可以通过
dispatch_source_set_timer
方法灵活设置定时器的开始时间、间隔和精度。
- 缺点:使用相对复杂,需要手动管理定时器的创建、启动和停止。
三、NSTimer
3.1 原理
NSTimer
是基于 RunLoop 实现的定时器。RunLoop 是一个事件循环机制,负责处理各种事件,如触摸事件、定时器事件等。当创建一个 NSTimer
并将其添加到 RunLoop 中时,RunLoop 会在指定的时间间隔内触发定时器事件。
3.2 使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #import <Foundation/Foundation.h>
@interface NSTimerExample : NSObject @property (nonatomic, strong) NSTimer *timer; @end
@implementation NSTimerExample
- (void)startTimer { self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; }
- (void)timerFired { NSLog(@"NSTimer fired"); }
- (void)stopTimer { if (self.timer) { [self.timer invalidate]; self.timer = nil; } }
@end
int main(int argc, const char * argv[]) { @autoreleasepool { NSTimerExample *example = [[NSTimerExample alloc] init]; [example startTimer];
sleep(5); [example stopTimer]; } return 0; }
|
3.3 循环引用问题
在上述示例中,NSTimer
的 target
是 self
,这会导致循环引用问题。NSTimer
会强引用 target
,而 target
又持有 NSTimer
的引用,当 target
所在的对象需要被释放时,由于 NSTimer
对其的强引用,对象无法被释放,从而造成内存泄漏。
3.4 解决办法
3.4.1 使用中间对象(NSProxy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #import <Foundation/Foundation.h>
@interface WeakProxy : NSProxy @property (nonatomic, weak, readonly) id target; - (instancetype)initWithTarget:(id)target; + (instancetype)proxyWithTarget:(id)target; @end
@implementation WeakProxy - (instancetype)initWithTarget:(id)target { _target = target; return self; }
+ (instancetype)proxyWithTarget:(id)target { return [[WeakProxy alloc] initWithTarget:target]; }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; }
- (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end
@interface NSTimerProxyExample : NSObject @property (nonatomic, strong) NSTimer *timer; @end
@implementation NSTimerProxyExample
- (void)startTimer { WeakProxy *proxy = [WeakProxy proxyWithTarget:self]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; }
- (void)timerFired { NSLog(@"NSTimer fired"); }
- (void)stopTimer { if (self.timer) { [self.timer invalidate]; self.timer = nil; } }
@end
|
使用 NSProxy
作为中间对象,NSTimer
强引用 NSProxy
,而 NSProxy
弱引用 target
,避免了循环引用。
3.4.2 在合适的时机手动停止定时器
在 target
即将被释放时,手动调用 invalidate
方法停止定时器,打破循环引用。
1 2 3 4
| - (void)dealloc { [self.timer invalidate]; self.timer = nil; }
|
3.5 优缺点
- 优点:使用简单,只需要创建一个
NSTimer
并将其添加到 RunLoop 中即可;可以方便地设置定时器的时间间隔、是否重复等属性。
- 缺点:精度相对较低,因为它依赖于 RunLoop 的运行状态。如果 RunLoop 被阻塞,定时器可能会延迟触发;只能在主线程或指定的 RunLoop 中运行;容易造成循环引用问题。
四、CADisplayLink
4.1 原理
CADisplayLink
是一个与屏幕刷新率同步的定时器,它会在每次屏幕刷新时触发一次回调。CADisplayLink
通常用于实现动画效果,因为它可以保证动画的流畅性。
4.2 使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #import <Foundation/Foundation.h> #import <QuartzCore/QuartzCore.h>
@interface CADisplayLinkExample : NSObject @property (nonatomic, strong) CADisplayLink *displayLink; @end
@implementation CADisplayLinkExample
- (void)startDisplayLink { self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFired)]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; }
- (void)displayLinkFired { NSLog(@"CADisplayLink fired"); }
- (void)stopDisplayLink { if (self.displayLink) { [self.displayLink invalidate]; self.displayLink = nil; } }
@end
int main(int argc, const char * argv[]) { @autoreleasepool { CADisplayLinkExample *example = [[CADisplayLinkExample alloc] init]; [example startDisplayLink];
sleep(5); [example stopDisplayLink]; } return 0; }
|
4.3 循环引用问题
与 NSTimer
类似,CADisplayLink
的 target
强引用 self
,而 self
又持有 CADisplayLink
的引用,会造成循环引用,导致 self
无法被释放,造成内存泄漏。
4.4 解决办法
4.4.1 使用中间对象(NSProxy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #import <Foundation/Foundation.h> #import <QuartzCore/QuartzCore.h>
@interface WeakProxy : NSProxy @property (nonatomic, weak, readonly) id target; - (instancetype)initWithTarget:(id)target; + (instancetype)proxyWithTarget:(id)target; @end
@implementation WeakProxy - (instancetype)initWithTarget:(id)target { _target = target; return self; }
+ (instancetype)proxyWithTarget:(id)target { return [[WeakProxy alloc] initWithTarget:target]; }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; }
- (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end
@interface CADisplayLinkProxyExample : NSObject @property (nonatomic, strong) CADisplayLink *displayLink; @end
@implementation CADisplayLinkProxyExample
- (void)startDisplayLink { WeakProxy *proxy = [WeakProxy proxyWithTarget:self]; self.displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(displayLinkFired)]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; }
- (void)displayLinkFired { NSLog(@"CADisplayLink fired"); }
- (void)stopDisplayLink { if (self.displayLink) { [self.displayLink invalidate]; self.displayLink = nil; } }
@end
|
利用 NSProxy
作为中间层,避免了 CADisplayLink
与 target
的循环引用。
4.4.2 在合适的时机手动停止定时器
在 target
即将被释放时,手动调用 invalidate
方法停止 CADisplayLink
,打破循环引用。
1 2 3 4
| - (void)dealloc { [self.displayLink invalidate]; self.displayLink = nil; }
|
4.5 优缺点
- 优点:与屏幕刷新率同步,能够实现流畅的动画效果;可以精确控制动画的帧率。
- 缺点:只能用于实现与屏幕刷新相关的任务,如动画;如果处理任务的时间过长,可能会导致屏幕卡顿;容易造成循环引用问题。
五、不同多线程技术的比较与选择
5.1 性能比较
- GCD Timer:性能较高,因为它基于 GCD 实现,不依赖于 RunLoop,能够在后台线程高效执行任务。
- NSTimer:性能相对较低,因为它依赖于 RunLoop,当 RunLoop 被阻塞时,定时器的精度会受到影响。
- CADisplayLink:性能与屏幕刷新率相关,如果处理任务的时间过长,会影响屏幕的流畅性。
5.2 适用场景
- **原子锁
atomic
**:适用于简单的属性读写操作,需要保证属性的线程安全。
- GCD Timer:适用于需要高精度定时任务的场景,如后台定时数据更新、定时任务执行等。
- NSTimer:适用于对精度要求不高的定时任务,如简单的倒计时、定时提醒等。
- CADisplayLink:适用于实现动画效果,如 UI 动画、游戏动画等。
六、总结
在Objective - C开发中,掌握多线程技术是提升应用性能和用户体验的关键。原子锁 atomic
、GCD Timer、NSTimer
和 CADisplayLink
各有优缺点,适用于不同的场景。同时,NSTimer
和 CADisplayLink
容易造成循环引用问题,开发者需要采取相应的解决办法。希望本文能帮助你深入理解Objective - C底层的多线程技术,在实际开发中灵活运用。
参考资料: