月度归档:2018年11月

使用GCD封装一个简单的定时器

1、GCD定时器实现的基础代码

1
2
3
4
5
6
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, <#intervalInSeconds#> * NSEC_PER_SEC, <#leewayInSeconds#> * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    <#code to be executed when timer fires#>
});
dispatch_resume(timer);

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
#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@interface SATimer : NSObject
 
/**
 创建并开启一个定时任务
 
 @param interval 重复执行的间隔时间
 @param count 重复次数
 @param block 重复执行代码
 @return 返回定时器的唯一标识
 */
+ (NSString *)timerWithInterval:(NSTimeInterval)interval repeatCount:(NSUInteger)count block:(void (^)(NSUInteger remainCount))block;
 
/**
 根据定时器的唯一标识,停止定时器
 
 @param timerId 定时器唯一标识
 */
+ (void)invalidateWithId:(NSString *)timerId;
@end
 
NS_ASSUME_NONNULL_END
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
#import "SATimer.h"
static NSMutableDictionary  *timerDictionary_;
static NSMutableDictionary  *countDictionary_;
static dispatch_semaphore_t semaphore_;
@implementation SATimer
+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        semaphore_ = dispatch_semaphore_create(1);
        timerDictionary_ = [NSMutableDictionary dictionary];
        countDictionary_ = [NSMutableDictionary dictionary];
    });
}
+ (NSString *)timerWithInterval:(NSTimeInterval)interval repeatCount:(NSUInteger)count block:(void (^)(NSUInteger remainCount))block;
{
    if ((block == nil) || (count <= 0) || (interval <= 0)) return nil;
    // gcd定时器基本实现
    dispatch_queue_t queue = dispatch_queue_create("com.yusian.timer-queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    NSString *timerId = [NSString stringWithFormat:@"%p", timer];
 
    // 字典写入时的线程安全
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    [timerDictionary_ setObject:timer forKey:timerId];
    [countDictionary_ setObject:@(count) forKey:timerId];
    dispatch_semaphore_signal(semaphore_);
    // 执行定时任务
    dispatch_source_set_event_handler(timer, ^{
        NSNumber *count = [countDictionary_ objectForKey:timerId];
        NSUInteger remainCount = count.unsignedIntegerValue;
        block(--remainCount);
        // 字典写入时的线程安全
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        [countDictionary_ setObject:@(remainCount) forKey:timerId];
        dispatch_semaphore_signal(semaphore_);
        // 重复次数剩余量为0时自动退出
        if (remainCount == 0) [self invalidateWithId:timerId];
    });
    dispatch_resume(timer);
    return timerId;
}
 
+ (void)invalidateWithId:(NSString *)timerId
{
    dispatch_source_t timer = [timerDictionary_ objectForKey:timerId];
    if (timer == nil) return;
    dispatch_source_cancel(timer);
    [timerDictionary_ removeObjectForKey:timerId];
    [countDictionary_ removeObjectForKey:timerId];
    NSLog(@"SATimer invalidate - %@", timerId);
}
 
@end

定时器NSTimer、CADisplayLink及代理NSProxy的基本使用

1、NSTimer常用的两种方式:scheduled与使用Runloop addTimer

1
2
3
4
5
6
7
8
9
10
11
12
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"code...");
}];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
 
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"code...");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
 
NSTimer *timer = [NSTimer timerWithTimeInterval:1.9 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

2、CADisplayLink常用的方式是addToRunloop

1
2
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[SAProxy proxyWithTarget:self] selector:@selector(linkMethod)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

3、这两个定时器的本质都是加入到runloop中循环调用,因此对象本身会被RunLoop引用,需要在业务上主动将其置为失效可释放,这样一来就容易使定时器自身与调用者形成循环引用的关系;

4、现在的问题是定时器在使用的时候会被外部引用,而定时器又可能直接引用了业务控制器,业务控制器需要在释放时将定时器销毁,可定时器引用了业务控制器,业务控制器不会被销毁;解决思路是避免定时器直接引用控制器,而是引用一个代理类,让代理类弱引用控制器,这样就可以解决循环引用的问题;

5、所谓的代理可以是任何对象,因为只需要完成所有的方法调用转发给业务器就行,参考:Objective-C消息发送机制原理objc_msgSend( ),也可以使用NSProxy,专业代理类;

6、NSProxy的作用也是消息转发,相比常规的类实现NSProxy省去了父类方法递归查找的过程,更高效,基本原理是相同的,实现methodSignatureForSelector:与forwardInvocation:方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface SAProxy : NSProxy
@property (nonatomic, weak) id  target;
@end
 
@implementation SAProxy
+ (instancetype)proxyWithTarget:(id)target
{
    SAProxy *proxy = [SAProxy alloc];
    proxy.target = target;
    return proxy;
}
 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

7、业务控制器参考代码:

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
@interface ViewController ()
{
    NSTimer *_timer;
}
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                              target:[SAProxy proxyWithTarget:self]
                                            selector:@selector(timerMethod)
                                            userInfo:nil
                                             repeats:YES];
}
- (void)timerMethod
{
    NSLog(@"%s", __func__);
}
- (void)dealloc
{
    [_timer invalidate];
    NSLog(@"%s", __func__);
}
@end

文件读写操作中锁的使用

1、简述
1.1、多线程操作中必然会涉及到锁的概念,使用锁来确保线程安全;
1.2、事实上某些操作,比如数据读取是可以允许多线程同时操作的;
1.3、但写操作不行,数据写入动作必须是同步的,否则会出现数据错误;
1.4、如果读、写操作动作同时发生,并且多线程并发操作时该如何处理?
1.5、通过普通加锁实现线程同步,这样线程是安全了,但影响了读取的效率;
1.6、基本思路是
1.6.1、允许读取操作多线程并发进行;
1.6.2、写入操作只能单个线程同步进行;
1.7、常用的解决方案有两个
1.7.1、pthread_rwlock
1.7.2、dispatch_barrier_async

2、代码示例
2.1、pthread_rwlock 继续阅读