C语言的Context,如何异步持有?

作者: 电一闪 | 来源:发表于2017-08-10 11:31 被阅读91次

-------本文业务场景,在引擎组件渲染的游戏页面中异步调用native获取用户头像和昵称

先上一段代码,这是第一版

void bkPlatformMQQAccountGetInfo(JSContextRef ctx,const char * openID,enum bkMQQAccountInfoType type)
{
    if (openID == NULL) {
        bkPlatformMQQAccountRetrunInfo(ctx, openID, type, NULL);
        return ;
    }

    if (type == bkMQQAccountNick) {
        const char * openIdCopy = strdup(openID);
        asyncGetNickName([NSString stringWithUTF8String:openIdCopy], ^(NSString *nick) {
            if (nick) {
                bkPlatformMQQAccountRetrunInfo(ctx, openIdCopy, type, (void *)[nick UTF8String]);
            } else {
                bkPlatformMQQAccountRetrunInfo(ctx, openIdCopy, type, NULL);
            }
            free((void *)openIdCopy);
        });
    } else {
        bkPlatformMQQAccountRetrunInfo(ctx, openID, type, NULL);
    }
}

这段代码截取自组件和native中间层的一部分。该函数传入JSContextRef,为游戏渲染界面当前上下文;openID用于兑换uin请求昵称;type则用来标识是获取昵称还是其他信息。引擎在必要时调用此C函数,native拿到昵称用bkPlatformMQQAccountRetrunInfo函数回调给引擎。
  这样的场景在平日的业务开发中很常见,发起请求,异步获取,通过代理模式或闭包回调结果;在可能野指针或循环引用的地方,ARC使用__weak做弱引用,MRC使用__block声明生命周期跟随block,很容易解决。
  那么在看这篇文章的各位可以思考,上方的中间层函数会不会出现野指针crash,如果存在这种可能,该怎么解决呢?


查看JSContextRef的定义,是

/*! @typedef JSContextRef A JavaScript execution context. Holds the global object and other execution state. */
typedef const struct OpaqueJSContext* JSContextRef;

很明显,JSContextRef是一个结构体指针。根据我对C语言的理解,直接赋值ctx变量是浅拷贝,只保存了ctx的地址,如果ctx的内存被释放,从浅拷贝的指针值中是无从得知的。而如何深拷贝,成为此问题的切入点。
  众所周知,C语言中值拷贝是深拷贝,那我能否新建一个JSContextRef,取出ctx指针对应的结构体的值,赋值后保存呢?修改后第二版的代码如下:

void bkPlatformMQQAccountGetInfo(JSContextRef ctx,const char * openID,enum bkMQQAccountInfoType type)
{
    if (openID == NULL) {
        bkPlatformMQQAccountRetrunInfo(ctx, openID, type, NULL);
        return ;
    }

    if (type == bkMQQAccountNick) {
        //用NSData对象把指针对应的内存空间内容保存起来
        NSData *stateData = [[NSData dataWithBytes:&ctx length:sizeof(ctx)] retain];
        const char * openIdCopy = strdup(openID);
        asyncGetNickName([NSString stringWithUTF8String:openIdCopy], ^(NSString *nick) {
            JSContextRef ctxRe;
            [stateData getBytes:&ctxRe length:stateData.length];
            [stateData release];
            
            if (nick) {
                bkPlatformMQQAccountRetrunInfo(ctxRe, openID, type, (void *)[nick UTF8String]);
            } else {
                bkPlatformMQQAccountRetrunInfo(ctxRe, openID, type, NULL);
            }
            free((void *)openIdCopy);
        });
    } else {
        bkPlatformMQQAccountRetrunInfo(ctx, openID, type, NULL);
    }
}

大家觉得,第二版代码可以达到深拷贝的效果吗?


还是出现了crash。仔细review代码,发现在获取ctx值拷贝时,写成了&ctx。ctx是const struct OpaqueJSContext*类型,本就是结构体指针的typedef形式,&ctx就成了指针的指针,深拷贝了一个整型指针值,起不到任何作用。修改后的第三版代码如下:

一直提示对不兼容的类型OpaqueJSContext应用sizeof函数

起初以为是代码哪里写错了,自己又写了个Demo验证,Demo代码如下。

typedef struct {
    int number; //学号
    int age; //年龄
    char *name; //名字
} Student;

typedef Student * StudentRef;

void testStructPointerDeepCopy() {
    //构造原始结构体变量
    StudentRef stu = calloc(1, sizeof(Student));
    stu->number = 2017;
    stu->age = 27;
    stu->name = "a goodman";
    
    //对stu变量的数据深拷贝,保存在NSData对象中
    NSData * data = [NSData dataWithBytes:stu length:sizeof(*stu)];
    StudentRef stuCopy = calloc(1, data.length);
    [data getBytes:stuCopy length:data.length];
    
    //打印深拷贝结果
    printf("copied result, number:%d, age:%d, name:%s\n\n", stuCopy->number, stuCopy->age, stuCopy->name);
    free(stu);
    stu = NULL;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //修改深拷贝的结果,跟原结构体变量一起打印做对比
        stuCopy->number = 2018;
        stuCopy->age = 28;
        stuCopy->name = "still a goodman";
        printf("copied result after modifying {number:%d, age:%d, name:%s}\n", stuCopy->number, stuCopy->age, stuCopy->name);
        
        //释放内存
        free(stuCopy);
    });
}

打印结果为:

copied result, number:2017, age:27, name:a goodman

copied result after modifying {number:2018, age:28, name:still a goodman}

Demo的打印结果表明,深拷贝内存区域的二进制数据达到对上下文的异步持有,这个方案一般是可行的。但是对于JSContextRef这个结构体指针的typedef类型来说,由于结构体定义细节未暴露出来,无法应用sizeof()函数计算一个结构体对象占用内存空间大小,进而也无法通过深拷贝内存二进制数据的方法持有上下文。
  经过和其他同事的讨论,最终采用了异步请求逻辑和页面“构造-销毁”周期同步的方式来管理上下文的生命周期。方法如下:

  • 因为游戏页面通过单例管理器创建,可以在全局任意处获取单例中的页面数据,所以在游戏页面构造时生成一个UUID绑定该页面,页面销毁时UUID清空,用UUID标识一个游戏页面。
  • 在发起异步获取头像时,在逻辑对象中持有一份当前页面的UUID,并在异步请求回来后,拿持有的UUID和游戏管理器单例的当前页面的UUID比较,如果管理器页面的UUID为空或比较结果不相等,则说明发起异步请求的页面已释放,不执行后续的回调逻辑。

相关文章

  • C语言的Context,如何异步持有?

    -------本文业务场景,在引擎组件渲染的游戏页面中异步调用native获取用户头像和昵称 先上一段代码,这是第...

  • swoole日记

    PHP的协程高性能网络通信引擎,使用C/C++语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络...

  • PHP实现基于Swoole简单的HTTP服务器

    引用Swoole官方定义: PHP语言的异步、并行、高性能网络通信框架,使用纯C语言编写,提供了PHP语言的异步多...

  • Android 切换系统语言

    切换系统语言分为下面两个步骤: 1. 创建不同语言资源;2. 替换当前页面 Context 所持有的资源; 一、创...

  • swoole

    什么是swoole swoole是PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步...

  • 《Lua in ConTeXt》03:两个世界

    ConTeXt 里的 Lua。这句话博大精深。 ConTeXt 世界里的大部分设施是用 TeX 语言构建的。在 C...

  • swoole

    简介 swoole是PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,...

  • GCD基本原理

    GCD(Grand Central Dispatch)简介 纯C语言,函数(异步dispatch_async、同步...

  • Context: 业务流程共享变量

    Context指的是标准库的context.Context,是一个接口对象,常用于异步IO控制以及上下文流程变量的...

  • Android内存泄漏及优化

    1 内存泄漏的场景 1.1 单例持有了Activity的context的引用 改为单例持有applicationC...

网友评论

    本文标题:C语言的Context,如何异步持有?

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