开发墨迹天气App中遇到的问题总结(二)

1数字溢出

背景

写iOS代码经常需要在某处计算时间戳。通常时间戳是格林威治时间(即零时区时间)1970年1月1日0点至当前的毫秒数。例如北京时间(东八区)2015-7-4 10:35:6,的时间戳是1435977306000。就目前来说它是个13位数字。

现象

某种时间戳转为string的方式如下。

1
2
long st = [[NSDate date] timeIntervalSince1970] * 1000;
NSString *strSt = [@(st) stringValue];

用5s及以上的模拟器跑都能得到正确的结果。而用5s以下的模拟器跑都输出错误的值。

解释

产生该问题的原因如下:对于iOS的64位应用,long是8byte整数,涵盖-9223372036854775808 到 +9223372036854775807。而32位的iOS应用,long是4byte整数,涵盖-2147483648 到 +2147483647。32位下long最多只能表征10位正数,而我们的时间戳是13位的,显然数字溢出了。而对于64位的iOS应用long能表示19位正数,这是毫无压力的。详见iOS上应用如何兼容32位系统和64位系统

解法

我们观察到不管是IPL32规范还是IPL64规范,long long都是以8byte存储,能表征时间戳。所以此问题的正确姿势是用long long来存储时间戳。正确代码如下:

1
2
long long st = [[NSDate date] timeIntervalSince1970] * 1000;
NSString *strSt = [@(st) stringValue];

2NSDictionary使用不当

背景

iOS客户端跟服务器进行通信通常采用json格式。客户端从服务器上获取数据后,使用NSJSONSerialization把json串变为NSDictionary。早期,在不使用Mantle等第三方库做NSDictionary到Model转换的时候,我们需要自己去把Dic模型化。NSDictionary中的key是一个符合NSCopying协议的objc对象,通常我们用NSString作为Dic的key。NSDictionary中的value需要是一个objc对象。

现象

某次Dic模型化转换如下。其中,AppData是需要转换的模型。webType是一个BOOL量。发现该BOOL一直为YES。不受wap_type的控制。

1
2
3
4
5
6
7
AppData *appData = [[AppData alloc] initWithAppId:[dic[@"app_id"] integerValue]
iconURL:dic[@"app_icon"]
appName:dic[@"app_name"]
linkUrl:urlStr
webType:(BOOL)dic[@"wap_type"]
appTip:dic[@"app_desc"]
andAppMarks:appMarks];

解释

(BOOL)dic[@”wap_type”]

这种写法 ,是取出dic中@”wap_type”这个key的value,该value是一个objc对象,把该对象强制转换为BOOL类型。因此,只要dic中含有 @”wap_type”这个key对应的value的时候会是YES,否则是NO。它不会考虑这个value具体是YES还是NO。

解法

[dic[@”wap_type”] integerValue]

取出dic中@”wap_type”这个key的value,获取该value的integerValue。

3NSArray NSMutableArray相关问题

背景

NSArray和NSMutableArray是常用的objc容器,该容器常用于存储objc对象。

现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//例1

NSArray *testAry = [[NSArray alloc] init] ;
id firstObj = [testAry objectAtIndex:0];
id lastObj = [testAry objectAtIndex:testAry.cout - 1];

//例2

@interface TestClass : NSObject

- (id)createObj;

@end

@implementation TestClass

- (id)createObj {
id obj = [[NSObject alloc] init];
return obj;
}

@end

// {外部调用代码
TestClass * __weak weakTestObj = someStrongTestObj;
[self foo:^{
NSMutableArray *ary = [[NSMutableArray alloc] init];
[ary addObject:[weakTestObj createObj]];
}];
// 外部调用代码}

例1

对一个空NSArray(注意空NSArray!!!不是nil NSArray,两者不是一样的东西),访问第一个和最后一个对象,都会抛出 NSRangeException。如果不抓异常,程序会崩溃。

例2

[ary addObject:[weakTestObj createObj]]这函数调用有时候会抛出NSInvalidArgumentException。如果不抓异常,程序会崩溃。

解释

对于例1,NSArray通过index访问对象,如果index超限那么会抛出异常。这种函数的函数名一般都有一个AtIndex。这一点是要记住的!!!

对于例2,weakTestObj是一个weak对象,在其指向的对象被销毁后,weakTestObj可能是nil。那么,[weakTestObj createObj]函数会返回nil,在此种情况下[ary addObject:[weakTestObj createObj]]可能会抛出异常。

解法

这里给出一些常用的解决方案:

1)index访问对象通过条件语句判断index是否在合理范围内。NSArray的第一个对象访问推荐用firstObject属性,最后一个对象访问推荐用lastObject属性。

2)给NSMutableArray添加对象要做nil判断;

3)用try catch包一下,设置异常处理逻辑。(终极杀招)

4NSString NSMutableString相关问题

背景

在项目中经常需要比较两个string是否相等,取某个string的sub string,对一个mutable string添加string。

现象

1
2
3
4
5
6
7
8
9
10
11
12
//例1
NSString *test = [@"123" mutableCopy] ;
NSLog(@"%@", test == @"123" ? @"相等" : @"不等");
NSLog(@"%@", [test isEqualToString:@"123"] ? @"相等" : @"不等");

//例2
NSMutableString *str = [@"123" mutableCopy];
[str appendString:nil];

//例3
NSString *str = [@"123" mutableCopy];
NSString *subStr = [str substringFromIndex:str.length + 1];

例1

输出:不等、相等

例2

抛出非法参数异常

例3

抛出范围异常

解释

例1,两个string比较其内容是否相同要用- (BOOL)isEqualToString:(NSString *)aString方法。不要直接用”==”操作符比较。”==”操作符比较的是操作符两边的oc对象是否是同一个oc对象,这个问题可以从c语言指针的角度来思考。而- (BOOL)isEqualToString:(NSString *)aString是类似于c语言的库函数int strcmp(const char str1, const char str2)

例2和例3,其实跟上述NSArray NSMutableArray相关问题类似。访问之前判断范围,添加内容判断nil。

解法

1)NSString判等用- (BOOL)isEqualToString:(NSString *)aString

2)NSString和NSMutableString操作的时候要注意异常处理,一般含有Index的这种要注意范围异常,append这种要注意传参异常;当然终极杀招依旧是try catch

5表里不一,看起来是个NSString对象,实际上是其他对象

背景

由于项目原因,针对id号这种参数,早期服务器传参可能使用的是string。而服务器端代码重构后,是使用整数传参。

现象

1
2
3
4
5
6
7
8
9
NSString *topicId = dic[@"tId"]; //dic是NSJSONSerialization解析json串得到的字典

//判断topicId是否为空
if (![topicId length]) {
//对于一个NSString对象调用length判空可以判断两种情况,一种是该对象为nil,另一种是该对象是空串@""

} else {

}

调用上述代码后,程序会崩溃。

解释

由于服务端重构,把json中的string变成了int,所以NSString *topicId = dic[@”tId”]此处实际得到的是一个NSNumber对象,对NSNumber调用length方法会出现未知选择子崩溃。

解法

1)树立信念,不要相信服务器会100%传好数据给你,即使正确率有99.9%那剩下的0.1%就是异常;

2)从服务器上得到的数据总应该进行类型判断,具体做法是用- (BOOL)isKindOfClass:(Class)aClass- (BOOL)isMemberOfClass:(Class)aClass这两种方法;

3)用try catch包,做异常处理(终极解法)。