iOS实现await-async的js引擎下的语法

回调的本质

函数指针

  • 抛开所有的语言, 回调的本质是函数指针, 因为操作系统是基于C语言编写的自然的回调的原理就是基于C的函数指针, 这一点不多作解释
  • 在后来的编译器实现中, 出现了局部函数, lambda, 闭包...等它们的基本原理和函数指针一样, 绑定了函数地址, 基于这种原理用户在编写函数时异步的回调不用再分开到 新的函数中去书写
void func(int data){
    ++data;     // __code_user_2
}



typedef void(* Func)(int*);
void OS_API(Func fun){
    // 异步操作
    ..
    
    fun(200);
    
    ...
}

int main(int args, char** argv){
    OS_API(func);       // __code_user_1;
}

场景是调用系统的api, 系统会在内部通过传递的函数, 将有关数据回调(data)出来, 这样的方式是最原始的方式, 其中code_user_1 和 code_user_2这2个用户级别的代码在书写时迫于标准C的语法被分开


void OS_API(void(^cbk)(int data)){
    
    // ...
    
    cbk(3);
    
    // ...
    
}

int main(int args, char** argv){
    OS_API(^void(int data){
        // 回调的代码
    });
}

这里以iOS来举例, 调用的代码回调的代码是在一起书写, 可维护性比纯C的回调要好很多, 其它语言(JS, Java等下的lambda也是一样的, 但随着开发以及业务增大, 回调中的代码越来越多, 会变得更不可维护, 所以傻瓜式的JS出现了 async-await的书写, 使语法更简洁

const myFun2 = function(val, time) {
    return new Promise((resolve, reject) =>{
        setTimeout(()=>{
            resolve(val)
        }, time)
    })
}

const demo_3 = async function() {
    let a = await myFun2(3, 5000)
    console.log(a)
    let b = await myFun2(4, 10000)
    console.log(b)
    let c =  await myFun2(5, 15000)
    console.log(c)
    let d = a + b +c  
    console.log(d)
}

demo_3()

这里 js引擎内部提供了async, await, 内部实现原理其实很简单

  • demo_3被调用时, js引擎会开启新的线程, 利用锁机制实现 await的同步等待
  • 这使得js的同学们写代码不再关心回调嵌套的恶心代码

iOS下的网络请求

// HTTP是封装了AF
[HTTP GETSuccess:^(id  _Nullable success) {
        [MOSAdvanceLoadM register_key:@"MOSLocalLifeVC" data:success[@"data"]];
    } failure:^(HTTP_ERROR * _Nonnull failure) {

    } url:API->dg.ym.service_lst args:nil header:nil HUD:nil];

这种写法已经写噎死了, 本人不才, 实现了简单的await语法, 这个过程是基于C语言下的macro扩展来的


/** await 专用 */
__attribute__((objc_subclassing_restricted)) @interface HTTP_RES : NSObject

/** 200 成功, 其他失败 */
@property (nonatomic) NSInteger code;

/** 若code 为200,则 res = @{@"data":res, @"msg":success[@"message"]};
    若code 不为200, 则 res = HTTP_ERROR对象
 */
@property (nonatomic,strong) id _Nonnull res;

@property (nonatomic,strong,readonly) HTTP_ERROR* err;
@end







#define Async(__type, __var)                                    \
        "".length;                                              \
        dispatch_async(dispatch_get_global_queue(0, 0), ^{      \
        __unused __block __type __var = __var;                  \
        __unused __block __type __strong * __tmp_var = &__var;  \
        __block pthread_mutex_t __m_lock;                       \
        pthread_mutex_init(&__m_lock, NULL);                    \
        __block pthread_cond_t __cond;                          \
        pthread_cond_init(&__cond, NULL);                       \
        __unused id __place


#define Await                                                   \
        (0);                                                    \
        dispatch_async(dispatch_get_main_queue(), ^{            \



#define Signal()                                                \
        "".length;                                              \
        pthread_mutex_lock(&__m_lock);                          \
        pthread_mutex_unlock(&__m_lock);                        \
        pthread_cond_signal(&__cond);                           \


#define Catch                                                   \
        "".length;                                              \
        });                                                     \
        pthread_mutex_lock(&__m_lock);                          \
        pthread_cond_wait(&__cond, &__m_lock);                  \
        pthread_mutex_unlock(&__m_lock);                        \
        dispatch_async(dispatch_get_main_queue(), ^{



#define Over()                                                  \
        "".length;                                              \
        @Signal();                                              \
        });                                                     \
        pthread_mutex_lock(&__m_lock);                          \
        pthread_cond_wait(&__cond, &__m_lock);                  \
        pthread_mutex_unlock(&__m_lock);                        \
        pthread_mutex_destroy(&__m_lock);                       \
        pthread_cond_destroy(&__cond);                          \
        })



/**
 填充值 是HTTP_RES,
 hud外部自己处理
 */
extern void __http_req(NSString*            _Nonnull    url,
                       NSDictionary*        _Nullable   req_dic,
                       id __strong *        _Nonnull    result,
                       pthread_mutex_t*     _Nonnull    m_lock,
                       pthread_cond_t*      _Nonnull    cond,
                       NSInteger type);

#define http_req(_url,  _req_dic,   _type)                              \
    __http_req( (_url),                                                 \
                (_req_dic),                                             \
                (__tmp_var),                                            \
                (&__m_lock),                                            \
                (&__cond),                                              \
                (_type))                                                \
                ;



// 要求要有 HUD函数(当前工程必须导入 LB_DEV.h), __data是 HTTP_RES
#define HTTP_ERR_REMIND(__data)                                         \
    assign_code{                                                        \
        if([__data isKindOfClass:NSClassFromString(@"HTTP_RES")] &&     \
            ((HTTP_RES*)__data).code != 200){                           \
            __auto_type hud = HUD(((HTTP_ERROR*)(__data.res)).err);     \
            HUD_HIDE(hud, 1);                                           \
        }                                                               \
    }

对应函数的实现


@implementation HTTP_RES
- (HTTP_ERROR *)err{
    if(self.code == 200)
        return nil;

    return (HTTP_ERROR*)self.res;
}
@end




void __http_req(NSString*           _Nonnull    url,
                NSDictionary*       _Nullable   req_dic,
                id __strong *       _Nonnull    result,
                pthread_mutex_t*    _Nonnull    m_lock,
                pthread_cond_t*     _Nonnull    cond,
                NSInteger type){
    typedef void(^ Res_cbk)(NSURLSessionDataTask* _Nonnull, id _Nullable);
    typedef void(^ Err_cbk)(NSURLSessionDataTask* _Nullable task, NSError* _Nonnull);

    Res_cbk suc_cbk = ^void(NSURLSessionDataTask* task, id responseObject){
        
        id resultData = [HTTP objcWithData:responseObject];
        {BLOG(task.originalRequest.URL);}
        {BLOG([resultData mj_JSONString]);}
        if(res_code(resultData) == 200){
            if(result){
                HTTP_RES* tmp = [HTTP_RES new];
                tmp.code = 200;
                tmp.res = res_suc(resultData);
                *result = tmp;
            }
//          atomic_store((atomic_bool*)atomic_lock, true);

            pthread_mutex_lock(m_lock);
            pthread_mutex_unlock(m_lock);
            pthread_cond_signal(cond);
        }else{
            if(MOSUser.TOKEN.length && res_code(resultData) == 401){
                POST_NOTI(nil, PRO_NOTI_P->EXCEPT_LOGOUT);
//              atomic_store((atomic_bool*)atomic_lock, true);
                pthread_mutex_lock(m_lock);
                pthread_mutex_unlock(m_lock);
                pthread_cond_signal(cond);
                return;
            }

            if(result){
                __auto_type tmp = [HTTP_RES new];
                tmp.code = res_code(resultData);
                tmp.res = [HTTP_ERROR err:res_code(resultData)
                                         :resultData[@"message"]];
                *result = tmp;
            }
//          atomic_store((atomic_bool*)atomic_lock, true);
            pthread_mutex_lock(m_lock);
            pthread_mutex_unlock(m_lock);
            pthread_cond_signal(cond);
        }
    };


    Err_cbk err_cbk = ^void(NSURLSessionDataTask* task, NSError* err){
        {BLOG(task.originalRequest.URL);}
        {BLOG(err);}
        if(result){
            __auto_type tmp = [HTTP_RES new];
            tmp.code = -1;
            tmp.res = [HTTP_ERROR err:-1
                                     :err];
            *result = tmp;
        }

//      atomic_store((atomic_bool*)atomic_lock, true);
        pthread_mutex_lock(m_lock);
        pthread_mutex_unlock(m_lock);
        pthread_cond_signal(cond);
    };





    // get
    if(type == 0){
        [HTTP md5_dic:req_dic :&url];
        [HTTP.sessionMgr GET:url
                  parameters:nil
                     headers:nil
                    progress:nil
                     success:suc_cbk
                     failure:err_cbk];
    }

    if(type == 1){
        req_dic = [HTTP md5_dic:req_dic :nil];
        [HTTP.sessionMgr POST:url
                   parameters:req_dic
                      headers:nil
                     progress:nil
                      success:suc_cbk
                      failure:err_cbk];

    }
}

使用

// assign_code是一上空的宏
assign_code{
        @Async(id, result) = @Await http_req(API->dg.ym.service_lst, nil, 0) @Catch;


        NSLog(@"%@ %@", __tmp_var, result);

        HTTP_ERR_REMIND(result);

// 不要在这里面 使用 __tmp_var, 因为 *__tmp_var指向的是 nil, 直接使用 result
        @Over();
}

原理是用到系统的atomic锁(已废弃,使用的是mutex_lock)以及配合宏, 实现后的代码, 在单次网络请求的写法是基本和js是一样的, 这整个过程的实现也不作解释了, 有内核编程的思想在里面, 自己用, 这里记录一下
关于锁, 本来想使用 atomic, 但由于atomic在wait的时间段占用cpu资源, 本人想的解决方案时通过unix信号suspend来作pause的唤醒处理, 但在iOS环境中系统屏蔽了所有信号相关的函数, 所以改用了mutext_lock, 可能对于信号这方面本人目前在iOS系统层面上的知识体系还不够充分, 先放这里, 以后搞清楚了再改为atomic

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容