别再使用stringByAddingPercentEscapesUsingEncoding

当遇到发送网络请求的参数中有汉字的情况,很多人一股脑地使用stringByAddingPercentEscapesUsingEncoding:进行转义,这样带有汉字的urlString就会将每个汉字转成相应的unicode编码对应的3个%形式,这叫urlEncode(每个能写后端的语言都有的方法),但是苹果的stringByAddingPercentEscapesUsingEncoding:却不是urlEncode。实际上我们使用的参数值可能会包含一些特殊的字符,如&?这样的字符,而Percent转义已经不能满足需求了,如下面的例子:

NSString *queryWord = @"汉字&ss";
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", queryWord];
NSString *escapedString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@", escapedString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97&ss

这是一个非常常见的情景,(之前公司项目的搜索中,也遇到过这种情况),这种被转义之后的URL,服务端接收到的参数会使这样的

["ie":"UTF-8", "wd":"汉字", "ss":nil]

即使你做如下的改进:(在请求之前将每个参数都转义,再使用&拼接参数也无济于事)

NSString *queryWord = @"汉字&ss";
NSString *escapedQueryWord = [queryWord stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", escapedQueryWord];
NSLog(@"%@", urlString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97&ss

产生这种情况的原因是:百分号转义不等于URLEncode
该编码不同于URL编码,由于不会对&字符编码,因此不会改变URL参数的分隔。URL编码会编码&?与其他标点符号。如果查询字符串包含了这些字符,那么需要实现一种更加彻底的编码方法。

不过还好iOS7.0推出了stringByAddingPercentEncodingWithAllowedCharacters:方法,这个方法会对字符串进行更彻底的转义,但是需要传递一个参数:这个参数是一个字符集,表示:在进行转义过程中,不会对这个字符集中包含的字符进行转义,而保持原样保留下来。
这样就可以使用它改造上面的代码了:

NSString *queryWord = @"汉字&ss";
NSString *escapedQueryWord = [queryWord stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet letterCharacterSet]];
NSLog(@"%@", escapedQueryWord); // %E6%B1%89%E5%AD%97%26ss
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", escapedQueryWord];
NSLog(@"%@", urlString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97%26ss

在上面的例子中传递参数[NSCharacterSet letterCharacterSet]来保证字母不被转义。所以被转义之后的参数值是:%E6%B1%89%E5%AD%97%26ss,这样问题就解决了,但是有时候会遇到queryString中的表单域也需要转义的情况,比如是一个表单数组如:

https://www.baidu.com/s?person[contact]=13801001234&person[address]=北京&habit[]=游泳&habit[]=骑行

这样可以使用将key转义,不过key中的[]字符是不需要转义的:可以自定义一个CharacterSet实现需求:

NSMutableCharacterSet *mutableCharSet = [[NSMutableCharacterSet alloc] init];
[mutableCharSet addCharactersInString:@"[]"]; // 允许'['和']'不被转义
NSCharacterSet *charSet = mutableCharSet.copy;

NSMutableString *mutableString = [NSMutableString string];
for (unit in queryString) {
    NSString *escapedField = [unit.field stringByAddingPercentEncodingWithAllowedCharacters:charSet];
    NSString *escapedValue = [unit.value stringByAddingPercentEncodingWithAllowedCharacters:charSet];
    [mutableString addFormat:@"%@=%@", escapedField, escapedValue];
}

这样问题已经圆满解决了,美中不足的是:当queryString非常多的时候你如何保证从queryString正确地提取出来每个unit呢,这个牵扯到复杂的字符串解析的问题。先不做讨论。实际上有一个好的方案是使用AFN将每个参数的URL和queryString在构建的时候分离,使用URL和parameter(字典)分别传入的方法,也就是说在使用AFN的时候避免使用:

GET:@"https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97%26ss"
parameters:nil
success:nil
failure:nil

而是尽量使用

GET:@"https://www.baidu.com/s"
parameters:@{@"ie":@"UTF-8",@"wd":@"汉字&ss"};
success:nil
failure:nil

为什么要这样,翻看AFN的源码会发现,AFN对queryString的组装是这样进行的:

AFN会将parameters的传递的字典通过将每个表单元素的field和value进行urlcode之后拼接,然后再直接附加在传递的URLString后面(当然,如果是POST方式就不是附加了,而是将拼好的串放到HTTP body中)。

那么如果要使用第一种方式,必须要确保自己在传入的URLString是经过完美转义的,因为AFN不会对你传入的URLString进行检测有没有进行了转义或者正确与否,但是AFN对上面方法中parameter参数的解析时非常彻底的,因此强烈建议使用第二种方式调用AFN的方法。那么AFN是如何完美解析parameter参数的呢,这刚好是一个可以将字典转为queryString的模块呀!!!,下面就来看一下:

对AFN urlEncode的研究

AFN将网络访问分割为三个过程模块
1.请求前:构建request的header和queryString、uploadContent和配置(如超时等),这部分的功能在AFURLRequestSerialization中
2.请求中:分别有基于NSConnection的访问(3.0移除)和基于NSURLSession的访问模块
3.请求后:1错误处理2.成功处理:数据格式转换和解析,主要在AFURLResponseSerialization中

requestSerialization就像过滤器一样,每一个用于构建网络请求的URLRequest对象都会经过requestSerialization配置,再返回一个NSMutableURLRequest对象(参见2.x版本的dataTaskWithHTTPMethod: URLString: parameters: success: failure方法,3.0版本dataTaskWithHTTPMethod URLString: parameters: uploadProgress: downloadProgress: success:方法),NSURLSession对象会使用这个NSMutableURLRequest对象创建task。而我们要讨论的将parameter转为queryString的功能全部在AFURLRequestSerialization中,它实际上使用了

NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

就完成了所有的请求前的配置功能,可以查看一下内部的实现,有一句关键性的代码

mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
// 这里的self是AFURLResponseSerialization对象

这句代码用于对request对象设置requestHeader和转义queryString,我们仅仅看一下对queryString进行转义的
其内部按照这样的思路实现:

1.如果传递过来的parameters不为空,就会判断self.queryStringSerialization是否为空(self.queryStringSerialization属性是一个 AFQueryStringSerializationBlock类型的block,它是用来实现转义的核心代码块)

2.如果self.queryStringSerialization不为空,使用self.queryStringSerialization(request, parameters, &serializationError);进行转义和组装:

3.如果self.queryStringSerialization为空,使用一个内部函数来执行:AFQueryStringFromParameters(parameters),实际上每一个AFURLResponseSerialization对象在创建的时候queryStringSerialization属性都是空的,因此外部不传递block类型的值给queryStringSerialization属性时都会走这条路线,也就是使用AFQueryStringFromParameters(parameters)来解析参数。

AFQueryStringFromParameters的实现是这样的:

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}

而其中使用到的AFQueryStringPairsFromDictionary函数是这样实现的:

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value); // 这个方法太长,只放置了原型

思路为:

1.利用AFQueryStringPairsFromKeyAndValue函数将parameters字典中的每个key-value对取出,将每个key-value对构建为AFQueryStringPair对象,放到一个数组中。

2.在AFQueryStringFromParameters方法内部遍历这个数组(每个元素为AFQueryStringPair对象),使用AFQueryStringPair类的转义方法URLEncodedStringValue将AFQueryStringPair转为字符串,将这些字符串存入新的数组中。这样新数组中的每个元素就是转义之后的field=value字符串,最后用&将数组元素连接即可。

函数AFQueryStringPairsFromKeyAndValue是一个非常完美的算法,基本上考虑到了所有类型的表单域:包括表单数组的处理和对一个表单域赋值多个value的情况的处理,表单数组在html页面经常用到的:

<form method="GET" action="http://127.0.0.1/test.php">
<input name="habit[]" value="游泳" />
<input name="habit[]" value="骑行" />
<input type="submit" value="提交">
</form>

浏览器自动转义: habit%5B%5D=%E6%B8%B8%E6%B3%B3&habit%5B%5D=%E9%AA%91%E8%A1%8C
AFN传递parameter = @{@"habit":@[@"游泳", @"骑行"]}:
2.x版本:habit[]=%E6%B8%B8%E6%B3%B3&habit[]=%E9%AA%91%E8%A1%8C
3.0版本:habit%5B%5D=%E6%B8%B8%E6%B3%B3&habit%5B%5D=%E9%AA%91%E8%A1%8C (与浏览器相同)
php会将$_GET解析为:

array(1) {
    ["habit"]=> array(2) {
        [0]=> string(6) "游泳"
        [1]=> string(6) "骑行"
    }
}

如果是这种写法:

<form method="GET" action="http://127.0.0.1/test.php">
<input name="person[contact]" value="13801001234" />
<input name="person[address]" value="北京" />
<input type="submit" value="提交">
</form>

浏览器自动转义:person%5Bcontact%5D=13801001234&person%5Baddress%5D=%E5%8C%97%E4%BA%AC
AFN传递parameter = @{@"person":@{@"contact":@"13801001234", @"address":@"北京"}}:
2.x版本: person[address]=%E5%8C%97%E4%BA%AC&person[contact]=13801001234 (没有将[]转义)
3.0版本:person%5Baddress%5D=%E5%8C%97%E4%BA%AC&person%5Bcontact%5D=13801001234 (与浏览器相同,但对field进行了排序)
结果为:

array(1) {
    ["person"]=> array(2) {
        ["contact"]=> string(11) "13801001234"
        ["address"]=> string(6) "北京"
    }
}

如果是对于一个field多参数的情况

<form method="GET" action="http://127.0.0.1/test.php">
<input type="checkbox" name="habit" value="游泳">游泳
<input type="checkbox" name="habit" value="骑行">骑行
<input type="submit" value="提交">
</form>

浏览器自动转义:habit=%E6%B8%B8%E6%B3%B3&habit=%E9%AA%91%E8%A1%8C
AFN传递parameter = @{@"habit":set} 其中set为:NSSet *set = [NSSet setWithObjects:@"游泳", @"骑行", nil];:
2.x版本: habit=%E6%B8%B8%E6%B3%B3&habit=%E9%AA%91%E8%A1%8C (与浏览器相同)
3.0版本:habit=%E6%B8%B8%E6%B3%B3&habit=%E9%AA%91%E8%A1%8C (与浏览器相同)

以上各种类型的html表单域的处理,函数AFQueryStringPairsFromKeyAndValue都已经很好地处理,不管如此,还对每个field按照NSString默认排序规则(字母表顺序)进行了排序。

在接下来我会针对2.x版本的AFN剖析一下AFQueryStringPair的转义方法- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding的实现,中间会说到一些和3.0的差别:
我们看一下- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding的代码

- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { // AFN3.0的区别是换了个方法名,而且不用传递stringEncoding
    if (!self.value || [self.value isEqual:[NSNull null]]) { // 如果value为空值,只转义field
        return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding);
    } else { // 将field和value转义后拼接
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)];
    }
}

而对于AFPercentEscapedQueryStringKeyFromStringWithEncodingAFPercentEscapedQueryStringValueFromStringWithEncoding方法,它的实现是这样的:

static NSString * const kAFCharactersToBeEscapedInQueryString = @":/?&=;+!@#$()',*"; // 在queryString进行URLEncode时需要进行转义的字符

static NSString * AFPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
    static NSString * const kAFCharactersToLeaveUnescapedInQueryStringPairKey = @"[]."; // 在urlencode时不需要转义

    return (__bridge_transfer  NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}

static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
    return (__bridge_transfer  NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}

这里是使用了一个CoreFoundation中定义的函数:CFURLCreateStringByAddingPercentEscapes这函数的参数解释如下:

index 参数名 解释
1 allocator 为新的CFString对象分配内存的分配器,传递NULL或者kCFAllocatorDefault使用当前默认的分配器
2 originalString 要copy的CFString对象
3 charactersToLeaveUnescaped 在百分号转义过程中要完好地留下的字符集,传递NULL指明所有非法的字符会被转义
4 legalURLCharactersToBeEscaped 需要转义的合法的字符集。传递NULL指明没有合法的字符集要被替换(所有字符都不转义)
5 encoding 转化过程使用的编码 如果你不关心正确的编码,你应该使用UTF-8 (kCFStringEncodingUTF8), 这是一个由RFC 3986设计的在URL使用中很合适的编码

看了参数的说明应该很容易理解为什么要那样传递参数,不过需要注意的是传递的字符串都是CFStringRef类型:因此要和NSString做一下桥接:

// NSString 转为CFStringRef
(__bridge CFStringRef)string

// CFStringRef 转为NSString
(__bridge_transfer  NSString *)string

而对于3.0的AFN无论是对field的转义还是对value的转义都使用了相同的函数NSString * AFPercentEscapedStringFromString(NSString *string)其在内部的实现就是使用了在本文第一部分提到提到的系统方法- (nullable NSString *)stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet *)allowedCharacters,而apple的文档中指出这个方法内部会按照UTF-8编码进行转义,因此这里刚好解释了为什么之前2.x版本需要传递编码参数,而3.0就不用了。
这里是NSString * AFPercentEscapedStringFromString(NSString *string)函数的一点核心代码:

NSString * AFPercentEscapedStringFromString(NSString *string) {
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; 

    // ......
    // .....
    return eacapedString;
}

可以看到允许转义的字符集一开始是URLQueryAllowedCharacterSet,然后去掉了kAFCharactersGeneralDelimitersToEncode(@":#[]@")和kAFCharactersSubDelimitersToEncode(@"!$&'()*+,;=")包含的字符,也就是说这些字符最终都需要转义,相比较2.x版本确实是多转义了[,].。这也是刚才看到说的使用AFN2.x版本参数值和3.0版本转义后参数值不同而3.0与浏览器中相同的原因。

费了那么大的劲,终于把这部分梳理清晰了,然后来做一件有意义的事:(将字典转为queryString的功能抽取)

将AFN字典转queryString模块抽取

这里我只是写了一个NSDictionary的分类

@interface NSDictionary (ConvertToQueryString)

- (NSString *)convertToQueryString;

@end
#import "NSDictionary+ConvertToQueryString.h"
#import "AFNetworking.h"

@implementation NSDictionary (ConvertToQueryString)

- (NSString *)convertToQueryString {
    if (!self || [self isEqual:[NSNull null]]) {
        return @"";
    }
#if AFN Version < 3.0
    return AFQueryStringFromParametersWithEncoding(self, NSUTF8StringEncoding);
#else
    return AFQueryStringFromParameters(self);
#endif
}

@end

一切就是那么简单,但是很有用处。接下来就是一点点扩展了,我们知道AFN已经封装了字典转为queryString的功能,那么有时候会有将queryString转为字典的需求,虽然这种需求并不常见,但偶尔也会碰到。那么具体怎么做呢。我推荐一个比较优秀的框架:Facebook的facebook-ios-sdk这是一个用于构建iOS应用的基础框架:包含了facebook登录和分享、处理应用间跳转的功能、一些绘图API,应用的数据统计模块等功能。这个框架并不是多么庞大,源码文件也比较少,但是其中一个网络工具还是挺好用的:FBSDKUtility。
在这个类的头文件中只是声明了这样4个方法:

@interface FBSDKUtility : NSObject

+ (NSDictionary *)dictionaryWithQueryString:(NSString *)queryString;

+ (NSString *)queryStringWithDictionary:(NSDictionary *)dictionary error:(NSError *__autoreleasing *)errorRef;

+ (NSString *)URLDecode:(NSString *)value;

+ (NSString *)URLEncode:(NSString *)value;

@end

在m文件中的实现并不复杂,单就URLEncode来说,它虽然对了一些对参数值的验空操作,但是没有想AFN那样将各种情况都充分考虑,因此若要完成queryStringWithDictionary:的功能还是建议使用AFN的功能,将AFN的方法加入到FBSDKUtility中,这种代码的普适性降低了但是增加几分可靠性。
至于dictionaryWithQueryString:方法,我相信我们都能写出这样的方法,但是说到底数据被转换后是要拿给后台使用的,排除各种后台语言和框架的差异,我们应该做到尽量使得传递的数据与后台经过解析之后获取的数据一致。

+ (NSDictionary *)dictionaryWithQueryString:(NSString *)queryString
{
  NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
  NSArray *parts = [queryString componentsSeparatedByString:@"&"];

  for (NSString *part in parts) {
    if ([part length] == 0) {
      continue;
    }

    NSRange index = [part rangeOfString:@"="];
    NSString *key;
    NSString *value;

    if (index.location == NSNotFound) {
      key = part;
      value = @"";
    } else {
      key = [part substringToIndex:index.location];
      value = [part substringFromIndex:index.location + index.length];
    }

    key = [self URLDecode:key];
    value = [self URLDecode:value];
    if (key && value) {
      result[key] = value;
    }
  }
  return result;
}

iOS. PercentEscape是错用的URLEncode,看看AFN和Facebook吧的更多相关文章

  1. ios上架报错90080,90087,90209,90125 解决办法

    ERROR ITMS-90087: "Unsupported Architectures. The executable for yht.temp_caseinsensitive_renam ...

  2. [ios] 定位报错Error Domain=kCLErrorDomain Code=0 &quot;The operation couldn’t be completed. (kCLErrorDomain error 0.)&quot;

    Error Domain=kCLErrorDomain Code=0 "The operation couldn’t be completed. (kCLErrorDomain error ...

  3. iOS CFNetwork报错

    2016-11-16 10:05:35.082 天天送[46197:11758717] 46197: CFNetwork internal error (0xc01a:/BuildRoot/Libra ...

  4. iOS 程序报错:reason: [NSArrayI addObject:]: unrecognized selector sent to instance

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI ad ...

  5. iOS 编译报错

    Undefined symbols for architecture i386: “_OBJC_CLASS_$_XXX”, referenced from: objc-class-ref in XXX ...

  6. 通读AFN①--从创建manager到数据解析完毕

    流程梳理 今天开始会写几篇关于AFN源码解读的一些Blog,首先要梳理一下AFN的整体结构(主要是讨论2.x版本的Session访问模块): 我们先看看我们最常用的一段代码: AFHTTPSessio ...

  7. 谁偷了我的热更新?Mono,JIT,iOS

    前言 由于匹夫本人是做游戏开发工作的,所以平时也会加一些玩家的群.而一些困扰玩家的问题,同样也困扰着我们这些手机游戏开发者.这不最近匹夫看自己加的一些群,常常会有人问为啥这个游戏一更新就要重新下载,而 ...

  8. iOS开发中遇到的错误整理 - 集成第三方框架时,编译后XXX头文件找不到

    iOS编译报错-XXX头文件找不到 错误出现的情况: 自己在继承第三方的SDK的时候,明明导入了头文件,但是系统报错,提示头文件找不到 解决方法 既然系统找不到,给他个具体路径,继续找去! 路径就填写 ...

  9. 【转】从开发者的角度看待各移动平台 ios/android/wp7/win8ost title

    T_T 这伪技术博客都快给写成Tron的读书笔记专栏了,这样可不行欸~ 如今正是移动平台的战国时期,厌烦了去讨论移动平台的未来,也无意于在HTML5和Native App之间纠结.本文只从开发者纯技术 ...

随机推荐

  1. javascript 心得

    1.&&和||等逻辑判断运算标记可以当成条件运算来使用例如: var a =  b = c = "12"; (a=="13"&& ...

  2. Eclipse导出插件工程

    一.Feature Projecties工程设置 1. 新建一个Feature Projecties 2. 选择我们的插件工程,finish 3. 在目录下新建一个Category definitio ...

  3. html,body的关系

    先上一张关系图 最底下的一个是画布,往上一层是html结构,再往上一层是body结构 默认情况下html,body的height都是0,你给这两个元素分别加个边框就能看出来,要特别注意的是加背景颜色是 ...

  4. linux chmod 755

    chmod是Linux下设置文件权限的命令,后面的数字表示不同用户或用户组的权限. 一般是三个数字: 第一个数字表示文件所有者的权限 第二个数字表示与文件所有者同属一个用户组的其他用户的权限 第三个数 ...

  5. Java生成CSV文件实例详解

    本文实例主要讲述了Java生成CSV文件的方法,具体实现步骤如下: 1.新建CSVUtils.java文件: package com.saicfc.pmpf.internal.manage.utils ...

  6. ora-01445:无法从不带保留关键字的表的连接视图中选择ROWID或采样

    系统要创建一个物化试图,用到很多张表,执行的时候报错:   ora-01445:无法从不带保留关键字的表的连接视图中选择ROWID或采样   网上搜了下,有多种原因和解决方法,最终我选择先尝试一下修改 ...

  7. Android代码优化——使用Android lint工具

    作为移动应用开发者,我们总希望发布的apk文件越小越好,不希望资源文件没有用到的图片资源也被打包进apk,不希望应用中使用了高于minSdk的api,也不希望AndroidManifest文件存在异常 ...

  8. cocos基础教程(5)数据结构介绍之cocos2d::Value

    1.概述 cocos2d::Valie 是一个包含了很多原生类型(int,float,double,bool,unsigned char,char* 和 std::string)外加 std::vec ...

  9. javascript的队列,优先队列,循环队列

    按书上的来弄的.慢慢理解了. function Queue() { var items = []; this.enqueue = function(element){ items.push(eleme ...

  10. SQL 存储和触发器

    存储过程:就像函数一样的会保存在数据库中-->可编程性 --> 存储过程 创建存储过程:create proc JiaFa--需要的参数@a int,@b intas --存储过程的内容 ...