回调的本质
函数指针
- 抛开所有的语言, 回调的
本质是函数指针
, 因为操作系统是基于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