美文网首页
iOS Block异步接口转同步接口的方法

iOS Block异步接口转同步接口的方法

作者: 斯瑞德 | 来源:发表于2019-09-29 18:43 被阅读0次

    异步Block接口转为同步接口

    1.异步Block接口

    通常在遇到本地IO操作,网络请求等情况,我们习惯于使用Block作为回调,获取操作的结果,比如:

    doSomething({
        // success
    },{
        // failuer
    })
    

    但是当业务复杂,需要处理的异步任务比较多时,就会出现回调地狱的问题:

    doSomething({
        doSomething({
            doSomething({
                doSomething({
                    doSomething({
                        // =======================
                    })
                })
            })
        })
    })
    

    基于这个问题,出现了一些解决方案,使用比较多的,就是Promise,将这种"粽子"调用改成"宽面"的调用:

    doSomething()
    .then({
        // =======================
    })
    .then({
        // =======================
    })
    .thern({
        // =======================
    })
    .thern({
        // =======================
    })
    .thern({
        // =======================
    })
    

    但是这种代码还是不够优雅,于是又有了async/await的方案:

    await doSomething();
    await doSomething();
    await doSomething();
    await doSomething();
    await doSomething();
    

    这个就是我们理想中的代码。

    PS:以上代码为伪码

    2.iOS 将 block 转为同步

    理想很丰满,现实很骨感。Objective-C 没有提供 async/await 方案。如果自己撸一套,由于没有编译器支持,自己添加语法糖的话,会把代码搞的更丑,因此我尝试直接将 Block 转为同步。

    原理很简单,就是在 block 回调时,做一次线程同步:

    改造前:

    - (void)sendMessage:(NSString *)message callback:(MissionCallback)callback {
        float random = 1 + (arc4random()%100)/30.0;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(random * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString *tag = [NSString stringWithFormat:@"%@ - random:%f", message, random];
            callback(tag);
        });
    }
    

    改造后

    - (NSString *)sendMessageSemaphore:(NSString *)message {
        __block NSString *resultMessage;
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [self sendMessage:message callback:^(NSString * _Nonnull result) {
            resultMessage = result;
            dispatch_semaphore_signal(semaphore);
        }];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        return resultMessage;
    }
    

    代码很简单,最终的效果也差强人意。实测修改后,该接口同时支持63方并发访问,再多就会卡死。

    至于为什么是63这个数字,是因为gcd最大线程数是63。当使用gcd创建了64个线程并发时,再申请线程资源会被阻塞,直到有其他线程资源被释放。而本例中由于进行线程同步需要占用当前线程,因此当有64个任务并发时,就无法再创建callback的线程,导致所有任务卡死。

    附 xnu 地址:https://github.com/apple/darwin-xnu

    3.Block 转同步的其他姿势

    试验中,分别用信号量、条件锁、互斥锁、自旋锁写了demo,最终的实测效果和上面一样。性能上,dispatch_semaphore 和 pthread_mutex 性能比较好;OSSpinLock 不再安全已经废弃;NSCondition/NSConditionLock 更加灵活一些,Objective-C的接口对一些不熟悉c的程序员更友好。

    以下是几种不同的方法的代码实现:

    // Block
    - (void)sendMessage:(NSString *)message callback:(MissionCallback)callback {
        float random = 1 + (arc4random()%100)/30.0;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(random * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString *tag = [NSString stringWithFormat:@"%@ - random:%f", message, random];
            callback(tag);
        });
    }
    
    // 信号量
    - (NSString *)sendMessageSemaphore:(NSString *)message {
        __block NSString *resultMessage;
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [self sendMessage:message callback:^(NSString * _Nonnull result) {
            resultMessage = result;
            dispatch_semaphore_signal(semaphore);
        }];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        return resultMessage;
    }
    
    // 条件锁
    - (NSString *)sendMessageCondition:(NSString *)message {
        __block NSString *resultMessage;
        NSCondition *condition = [[NSCondition alloc] init];
        [condition lock];
        [self sendMessage:message callback:^(NSString *result) {
            resultMessage = result;
            [condition signal];
        }];
        [condition wait];
        [condition unlock];
        return resultMessage;
    }
    
    - (NSString *)sendMessageConditionLock:(NSString *)message {
        __block NSString *resultMessage;
        NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
        [self sendMessage:message callback:^(NSString *result) {
            resultMessage = result;
            [lock lock];
            [lock unlockWithCondition:0];
        }];
        [lock lockWhenCondition:0];
        [lock unlock];
        return resultMessage;
    }
    
    // 互斥锁
    - (NSString *)sendMessageMutex:(NSString *)message {
        __block NSString *resultMessage;
        pthread_mutex_t pMutex = PTHREAD_MUTEX_INITIALIZER;
        __block pthread_cond_t pCond = PTHREAD_COND_INITIALIZER;
        
        pthread_mutex_lock(&pMutex);
        [self sendMessage:message callback:^(NSString *result) {
            resultMessage = result;
            pthread_cond_signal(&pCond);
        }];
        pthread_cond_wait(&pCond, &pMutex);
        pthread_mutex_destroy(&pMutex);
        return resultMessage;
    }
    
    // 自旋锁
    - (NSString *)sendMessageSpin:(NSString *)message {
        __block NSString *resultMessage;
        __block OSSpinLock oslock = OS_SPINLOCK_INIT;
        
        OSSpinLockLock(&oslock);
        [self sendMessage:message callback:^(NSString *result) {
            resultMessage = result;
            OSSpinLockUnlock(&oslock);
        }];
        OSSpinLockLock(&oslock);
        return resultMessage;
    }
    

    本文Demo代码

    相关文章

      网友评论

          本文标题:iOS Block异步接口转同步接口的方法

          本文链接:https://www.haomeiwen.com/subject/aqunectx.html