iOS,object-c和swift开发iOS开发iOS Developer

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

2017-08-10  本文已影响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()函数计算一个结构体对象占用内存空间大小,进而也无法通过深拷贝内存二进制数据的方法持有上下文。
  经过和其他同事的讨论,最终采用了异步请求逻辑和页面“构造-销毁”周期同步的方式来管理上下文的生命周期。方法如下:

上一篇 下一篇

猜你喜欢

热点阅读