学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

GCD队列的服务质量与优先级

  • NSQualityOfServiceUserInteractive
    与用户交互的任务,这些任务通常跟 UI 级别的刷新相关,比如动画,这些任务需要在一瞬间完成;

  • NSQualityOfServiceUserInitiated
    由用户发起的并且需要立即得到结果的任务,比如滑动 scroll view 时去加载数据用于后续 cell 的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成;

  • NSQualityOfServiceUtility
    一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间;

  • NSQualityOfServiceBackground
    这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时;

  • NSQualityOfServiceDefault
    优先级介于 user-initiatedutility,当没有 QoS 信息时默认使用,开发者不应该使用这个值来设置自己的任务。

服务质量枚举类型

1
2
3
4
5
QOS_CLASS_USER_INTERACTIVE
QOS_CLASS_USER_INITIATED
QOS_CLASS_DEFAULT
QOS_CLASS_UTILITY
QOS_CLASS_BACKGROUND

队列的优先级与服务质量的对应关系:

给队列设置QoS

dispatch_queue_attr_make_with_qos_class

dispatch_set_target_queue

GCD队列任务间依赖关系

dispatch_set_target_queue

除了能用来设置队列的 QoS 之外,还能够创建队列的层次体系。当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的 target 指向新创建的队列即可,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"执行任务1,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
sleep(1);
});
dispatch_async(queue2, ^{
NSLog(@"执行任务2,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
sleep(1);
});
dispatch_async(queue2, ^{
NSLog(@"执行任务3,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
sleep(1);
});
/*
2020-02-01 19:05:18.142729+0800 多线程[6669:1213619] 执行任务1,queue1
2020-02-01 19:05:19.143926+0800 多线程[6669:1213619] 执行任务2,queue2
2020-02-01 19:05:20.147740+0800 多线程[6669:1213619] 执行任务3,queue2
*/

注意点: 避免相互依赖,如将队列 A 的目标队列设置为队列 B,并将队列 B 的目标队列设置为队列 A。

Dispatch Group队列组

GCD 队列组,又称“调度组”,实现所有任务执行完成后有一个统一的回调。

例如:异步下载歌曲,等所有歌曲都下载完毕以后,转到主线程提示用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"%@,下载歌曲1",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"%@,下载歌曲2",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"%@,下载歌曲3",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"%@,下载完毕",[NSThread currentThread]);
});
/*
<NSThread: 0x6000028c8c00>{number = 6, name = (null)},下载歌曲2
<NSThread: 0x60000283f400>{number = 5, name = (null)},下载歌曲1
<NSThread: 0x600002803440>{number = 4, name = (null)},下载歌曲3
<NSThread: 0x60000285c5c0>{number = 1, name = main},下载完毕
*/

队列组的原理

真正实现统一回调的操作:

1
2
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
1
2
3
4
5
6
7
dispatch_group_async(group, queue, ^{ 
});
//等价于
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_group_leave(group);
});
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
    // 1.创建队列组
dispatch_group_t group = dispatch_group_create();
// 2.获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//ARC中不用写也不能写
// dispatch_retain(group);
// 3.进入队列组,执行此函数后,再添加的异步执行的block任务都会被group监听
dispatch_group_enter(group);
// 4.添加任务
dispatch_async(queue, ^{
NSLog(@"%@,执行任务1",[NSThread currentThread]);
// 5.离开队列组
dispatch_group_leave(group);
//ARC中不用写也不能写
// dispatch_release(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%@,执行任务2",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 6.获得队列组的通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"%@,执行任务3",[NSThread currentThread]);
});
// 7.等待队列组,监听的队列中的所有任务都执行完毕,才会执行后续代码,会阻塞线程(很少使用)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"%@,执行任务4",[NSThread currentThread]);
/*
<NSThread: 0x600003d98d00>{number = 3, name = (null)},执行任务1
<NSThread: 0x600003d4de80>{number = 9, name = (null)},执行任务2
<NSThread: 0x600003dcd2c0>{number = 1, name = main},执行任务4
<NSThread: 0x600003dcd2c0>{number = 1, name = main},执行任务3
*/

NSTimer 的创建

NSTimer的创建通常有两种方式,一种是以 scheduledTimerWithTimeInterval 为开头的类方法 。这些方法在创建了 NSTimer 之后会将这个 NSTimerNSDefaultRunLoopMode 模式放入当前线程的 RunLoop

1
2
+ ( NSTimer *) scheduledTimerWithTimeInterval:invocation:repeats: 
+ ( NSTimer *) scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

另一种是以 timerWithTimeInterval 为开头的类方法。这些方法创建的 NSTimer 并不能马上使用,还需要调用 RunLoop 的 addTimer:forMode: 方法将 NSTimer 放入 RunLoop,这样 NSTimer 才能正常工作。

1
2
+ ( NSTimer *) timerWithTimeInterval:invocation:repeats:
+ ( NSTimer *) timerWithTimeInterval:target:selector:userInfo:repeats:

Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.

NSTimer 的官方文档可以得知,RunLoop 对加入其中的 NSTimer 会添加一个强引用。

阅读全文 »

事件循环机制

分析UIApplicationMain如何启动主线程的RunLoop

打断点,通过lldb指令bt查看函数调用栈如下:

在UIApplicationMain函数中调用了Core Foundation框架下的CFRunLoopRunSpecific函数

阅读全文 »

objc_supre与objc_msgSendSuper

objc_super 和 objc_super2

它们的区别在于第二个成员:

objc_super:super_class //receiverClass的父类
objc_super2: current_class//receiverClass(消息接收者的class对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// message.h(objc4)
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class; // receiverClass 的父类
#endif
/* super_class is the first class to search */
};

// objc_runtime_new.h(objc4)
struct objc_super2 {
id receiver; // 消息接收者
Class current_class; // receiverClass(消息接收者的class对象)
};
阅读全文 »

Association 关联对象

默认情况下,由于分类底层结构的限制,不能直接给 Category 添加成员变量,但是可以通过关联对象间接实现 Category 有成员变量的效果。

阅读全文 »

属性关键字和所有权修饰符

atomic 修饰的属性是怎么样保存线程安全的?

atomic 原子性(默认),编译器会自动生成互斥锁,对setter和getter方法进行加锁,可以保证属性的赋值和取值的原子性是线程安全的,但不包括操作和访问

比如说atomic修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的,但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic的负责范围之内的,所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。

阅读全文 »