GCD的一点小补充

本文深入探讨了GCD在主线程中的应用,通过监听RunLoop的活动周期揭示了在主线程使用异步方法的实际延迟时间,有助于理解iOS应用中UI更新的最佳实践。

GCD大家经常在开发中使用到,这个已经很熟悉了,而且异步的意义大家也都知道,今天要说的就是和异步相关的一个小知识,不讲那些老生常谈的东西。可能大家经常这样写,或许也能简单的说出一些门道,但是能完全搞清楚其中的意义吗?PS:真实经历,思考这个问题的时候,问了一个开发5年的前辈,他也不能清楚的说出其中的意义。最终还是自己探索清楚的,每当彻底搞清楚这样的小知识的时候,就是最开心最踏实的时候。得意

直接上代码

    dispatch_async(dispatch_get_main_queue(), ^{
        //主线程的一些操作
    });

这段代码大家想必很熟悉,今天要说的就是这个问题。或许大家经常用到这个方法,最熟悉的场景莫过于在一个异步线程的block里面,耗时的操作完成后,下面紧接着写上这一段,也就是下面的这种情况

    /**
     *  异步线程(再开一条线程(子线程)执行下面的耗时操作)
     */
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //耗时操作
        //************
        
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程,刷新UI
        });
    });

但是很多时候经常在项目中看到在主线程就直接撸上了第一段的代码,突然有一天,仔细一想,我X,这尼玛不对啊,首先主线程是个串行队列,在一个串行队列里面来个异步操作有什么意义呢?不还是一个一个的串着执行?模糊的解释就是说这个block里面的操作会延迟执行,”过一会“再执行。请教一通前辈之后,得到的也是这个模糊的答案惊讶。在我的另一篇文章里面有关于这个执行顺序的详解,这里不再赘述,这里要说的是这个“一会”是多久?

其实根据经验也可以知道,这个时机肯定和runloop的跑圈息息相关,因为这些代码的执行就是在跑圈的过程中得以执行的。自然想到,监听runloop的跑圈,直接上代码

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self addObserver];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self testGCD];
    });
}

- (void)testGCD
{
    NSLog(@"test1输出");
    /**
     *  异步线程(再开一条线程(子线程)执行下面的耗时操作)
     */
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"test2输出");
    });
    
    NSLog(@"test3输出");
}

-(void)addObserver
{
    //1.创建监听者
    /*
     第一个参数:怎么分配存储空间
     第二个参数:要监听的状态 kCFRunLoopAllActivities 所有的状态
     第三个参数:时候持续监听
     第四个参数:优先级 总是传0
     第五个参数:当状态改变时候的回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        /*
         kCFRunLoopEntry = (1UL << 0),        即将进入runloop
         kCFRunLoopBeforeTimers = (1UL << 1), 即将处理timer事件
         kCFRunLoopBeforeSources = (1UL << 2),即将处理source事件
         kCFRunLoopBeforeWaiting = (1UL << 5),即将进入睡眠
         kCFRunLoopAfterWaiting = (1UL << 6), 被唤醒
         kCFRunLoopExit = (1UL << 7),         runloop退出
         kCFRunLoopAllActivities = 0x0FFFFFFFU
         */
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"runloop退出");
                break;
                
            default:
                break;
        }
    });
    
    /*
     第一个参数:要监听哪个runloop
     第二个参数:观察者
     第三个参数:运行模式
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer, kCFRunLoopDefaultMode);
    
    //NSDefaultRunLoopMode == kCFRunLoopDefaultMode
    //NSRunLoopCommonModes == kCFRunLoopCommonModes
}

@end


从打印输出可以很清晰的看到,显然这个执行的间隔是在两趟跑圈里面,也就是说间隔了一个runloop循环的时间。(1/60秒)。

终于彻底搞清楚了,以后使用起来也更加的自信和清晰了。其实这个延迟很重要的,有些时候你在当前的这个循环里面没有拿到一些设置的属性(比如delegate,这也是项目实战的解Bug的症结所在,项目中的一个图片请求的控件的代理设置时间问题出现的Bug),可以在下一个循环时再做一些东西,自然而然的可以考虑这样做了。

同时附上另外一种与runloop循环相关的延迟操作的方案,如下图:



明显可以看出,也是间隔了一个循环的时间,这个小知识也可以在合适的场景下运用起来!

这些检查错误是否正确? 该教程整体结构清晰、内容详实,准确地介绍了欧几里得算法及其扩展形式的数学原理、实现方式和实际应用。然而,在严谨性和细节处理上仍存在**若干可改进或严谨之处**,具体分析如下: --- ### ✅ **正确且严谨的部分** - 算法核心思想($\gcd(a,b) = \gcd(b, a \bmod b)$)表述准确。 - 迭代与递归实现代码基本正确,并考虑了负数取绝对值的问题。 - 扩展欧几里得算法的推导逻辑清晰,贝祖等式解释恰当。 - 示例计算过程无误,复杂度分析引用拉梅定理合理。 --- ### ⚠️ **存在的问题与严谨之处** #### 1. **输入条件假设完整** 在 **2.2 算法步骤** 中提到:“输入两个整数 $a$ 和 $b$,且 $a \geq b \geq 0$”,这是**必要的限制**。 > 实际上,欧几里得算法要求 $a \geq b$。若 $a < b$,第一步执行后会自动交换二者角色(因为 $a \bmod b = a$,然后进入 $\gcd(b, a)$)。因此该前提条件可以去掉或改为更宽松的形式。 ✅ 正确说法应为:输入任意两个整数 $a, b$,同时为零。 --- #### 2. **未处理边界情况:当两数都为0时** 函数 `gcd(a, b)` 在 $a = b = 0$ 时返回 0,这在数学上是有争议的($\gcd(0,0)$ 通常认为**无定义**或按上下文定义为 0),但应在文档中明确说明。 > 建议添加注释:若两数均为0,则约定返回0;否则返回正的最大公约数。 --- #### 3. **扩展欧几里得算法中的变量命名易引起误解** 在 `extended_gcd` 函数中: ```python g, x, y = g, y1, x1 - (a // b) * y1 ``` 这一行虽然语法合法,但属于冗余赋值(`g = g`),容易让初学者困惑。此外,它掩盖了一个重要事实:**我们是在回溯过程中更新 $x, y$**。 ✅ 更清晰写法是直接返回元组而中间赋值: ```python return (g, y1, x1 - (a // b) * y1) ``` 另外,函数未对负数做绝对值处理,而基础版本却做了 —— **一致性缺失**。 👉 应统一说明是否支持负数,或在扩展算法中也加入 `abs()` 处理并调整符号规则。 > 注意:即使输入为负数,扩展欧几里得仍能工作,但需注意贝祖系数的符号意义。最好显式处理或加注释。 --- #### 4. **贝祖等式的解唯一,但未提及通解形式** 教程只给出一组特解 $(x, y)$,但未指出其通解形式: $$ x' = x + k \cdot \frac{b}{\gcd(a,b)},\quad y' = y - k \cdot \frac{a}{\gcd(a,b)} $$ 这对于求解线性同余方程至关重要。 ❌ 缺少这一点可能导致读者误以为解是唯一的。 --- #### 5. **模逆元的应用描述过于简略** 在 **6.3 密码学应用** 中提到“用于计算模逆元”,但没有进一步说明如何通过扩展欧几里得算法求解 $a^{-1} \mod m$。 ✅ 应补充:当 $\gcd(a,m)=1$ 时,解 $ax + my = 1$ 中的 $x \bmod m$ 即为 $a$ 的模逆元。 --- #### 6. **练习题答案未提供,影响学习闭环** 提供了三道练习题,但没有附带解答或验证方法,利于自学者核对结果。 建议至少提供第一题的答案作为示例。 --- #### 7. **关于时间复杂度的说法略显模糊** 提到“时间复杂度为 $O(\log(\min(a, b)))$”是对的,但应强调这是**位操作复杂度**下的结果,且基于除法代价恒定的前提。 ⚠️ 若考虑大整数运算(如密码学中上千位的数),每次除法再是 $O(1)$,总复杂度需重新评估。 --- ### ✅ 改进建议总结 | 问题 | 建议修改 | |------|----------| | 输入要求 $a \geq b$ | 删除此限制,改为任意整数(全为零) | | 未处理 $\gcd(0,0)$ | 明确定义或抛出警告 | | 扩展算法未处理负数 | 统一使用 `abs()` 或说明符号处理机制 | | 变量冗余赋值 | 简化返回语句,提升可读性 | | 贝祖等式解唯一性误导 | 补充通解公式 | | 模逆元应用缺失细节 | 增加求逆元的具体步骤 | | 无练习答案 | 提供参考解答 | --- ### 示例修正:扩展欧几里得算法(增强版) ```python def extended_gcd(a, b): if b == 0: return (a, 1, 0) else: g, x1, y1 = extended_gcd(b, a % b) return (g, y1, x1 - (a // b) * y1) ``` > 注:此版本已足够正确;若要支持负数并保持正GCD,可在外部包装: ```python def extended_gcd_safe(a, b): if a == 0 and b == 0: raise ValueError("gcd(0,0) is undefined") a, b = abs(a), abs(b) # 注意:此处绝对值会影响x,y的符号,需根据需求调整 return extended_gcd(a, b) ``` --- 该教程整体结构清晰、内容严谨,但存在以下可优化或补充的小问题: 1. **输入条件未严格限定**:在基础算法步骤中提到“输入两个整数 \(a\) 和 \(b\),且 \(a \geq b \geq 0\)”,但实际实现代码中通过 `abs(a)` 和 `abs(b)` 处理了负数情况,未在步骤中明确说明算法对负数的处理逻辑。 2. **递归实现边界条件冗余**:递归实现中 `a, b = abs(a), abs(b)` 放在函数开头,但基础步骤要求 \(a \geq b \geq 0\),未在代码中强制保证 \(a \geq b\)(虽然实际运行中 `%` 操作会隐式处理,但逻辑上够直观)。 3. **扩展算法示例回溯步骤重复计算**:在扩展欧几里得算法的示例解析中,回溯计算系数时多次重复计算 \(a // b\),例如 \(\text{extended_gcd}(5, 2)\) 和 \(\text{extended_gcd}(7, 5)\) 中均计算了 \(a // b\),可优化为提前存储。 4. **拉梅定理表述严谨**:拉梅定理的“十进制位数”应明确为“较小数的十进制位数”,且未说明是“最多超过”还是“平均超过”,可能误导读者对复杂度的理解。
10-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值