数据解析问题:
由于后台返回的数据是一串字符串,而不是JSON格式.所以需要我们自己处理.
数据格式是这样的:key=value&key=value&key=value....
- 首先我们要做的是创建一个数组,以&号为标志进行分割.
- 使用initWithCapacity初始化一个字典.
- 遍历数组,使用rangeOfString来搜索=的range信息.
- 使用substringToIndex来截取到的数据作为key值.
- 使用substringFromIndex来截取到的数据作为value值.
- 最后使用setObjcet:forKey赋值.
代码:
+ (NSMutableDictionary *)dataAnaly:(NSString *)string
{
//以&进行分割
NSArray * array = [string componentsSeparatedByString:@"&"];
//如果你知道大概要放多少东西,那么最好用initWithCapacity,这个会提高程序内存运用效率。
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:array.count];
for (NSString *str in array)
{
NSString *string = @"=";
//rangeOfString 前面的参数是要被搜索的字符串,后面的是要搜索的字符
//包含"="
NSRange range = [str rangeOfString:string];
//返回一个字符串,这个字符串截取接受对象字符穿范围是从索引0到给定的索引index
NSString *keyStr = [str substringToIndex:range.location];
//NSLog(@"%@",keyStr);
//返回一个字符串,这个字符串截取接受对象字符串范围是给定索引index到这个字符串的结尾
NSString *valueStr = [str substringFromIndex:range.location+1];
// (1)setObject:forkey:中value是不能够为nil的;setValue:forKey:中value能够为nil,但是当value为nil的时候,会自动调用removeObject:forKey方法
// (2)setValue:forKey:中key只能够是NSString类型,而setObject:forKey:的可以是任何类型
// (3)setObject:forkey:中value是不能够为nil的,不然会报错。
[result setObject:valueStr forKey:keyStr];
}
return result;
}
问题二:
因为是银行项目,涉及到金钱问题,所以只要涉及到输入密码的地方,都需要使用CFCA安全控件进行加密.
虽然CFCA使用很简单,但是第一次使用,也是需要踩坑的.
我们需要使用CFCA提供的CFCASIPInputField来创建这个控件.下面是它的几个属性设置.
/** 代理 */
_password_textField.sipInputFieldDelegate = self;
/** 键盘动作 */
_password_textField.bIsNeedKeyboardAnimation = NO;
/** 最小长度 */
_password_textField.nMinInputLength = 6;
/** 最大长度 */
_password_textField.nMaxInputLength = 16;
/** 音效 */
_password_textField.bHaveButtonClickSound = NO;
/** RSA加密 */
_password_textField.cipherType = SIP_KEYBOARD_CIPHER_TYPE_RSA;
/** 原文加密 */
_password_textField.emOutputValueType = OUTPUT_VALUE_TYPE_PLAIN_DATA;
/** 键盘类型 */
_password_textField.emSipKeyboardType = SIP_KEYBOARD_TYPE_COMPLETE;
/** 是否加密 */
_password_textField.bIsNeedEncrypt = YES;
/** 服务器随机数 */
_password_textField.strServerRandom = self.serverRandom_string;
看到服务器随机数没有,起初我认为是在创建控件之前,必须拿到的数据.然后傻傻的做了个控件创建延时.后来发现:
dispatch_async(dispatch_get_main_queue(), ^{ _password_textField.strServerRandom = self.serverRandom_string;
});
这么干也是可以的,真是哔了~
问题三:数据签名问题
签名机制如下:
对报文中出现签名域(signature)之外的所有数据元(数据元前后不能有空格)
采用key=value的形式按照key名称进行字典排序,然后以&作为连接符拼接成待签名串。其次,对待签名串使用SHA-1算法做摘要,再使用商户RSA私钥证书对摘要做签名操作(签名时算法选择SHA-1)。最后,对签名做Base64编码,将编码后的签名串放在签名(signature)表单域里和其他表单域一起通过HTTP Post的方式传输给支付平台。
这些算法网上都可以找到,一步步跟着来就好.最后程序只要加签就会崩溃,找了很多原因都没找到,所幸后台当时允许不加签测试.直到有一天我发现,原来是ras.pfx导入方式不正确.
在这里导入,否则Xcode是识别不到的.之前都是拖拽进去的.
问题四:正则表达式
由于是金融类软件,需要登记身份信息.所以就会产生很多对数据的合法性判断.我认为这部分判断是在前台执行的,但是后台告诉我,他们那边处理.想着,在前台处理,输入正确,就会发送请求.输入不正确就不发送请求.可以优化一下程序.然后就加入了一些正则判断.结果就是测试的人员不能任意的注册账号了.(-_-),必须输入正确的手机号格式.
#pragma 正则匹配手机号
+ (BOOL)checkTelNumber:(NSString *) telNumber
{
/**
* 手机号码
* 移动:134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
* 联通:130,131,132,152,155,156,185,186
* 电信:133,1349,153,180,189
*/
NSString * MOBILE = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
/**
10 * 中国移动:China Mobile
11 * 134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
12 */
NSString * CM = @"^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\\d)\\d{7}$";
/**
15 * 中国联通:China Unicom
16 * 130,131,132,152,155,156,183,185,186
17 */
NSString * CU = @"^1(3[0-2]|5[256]|8[356])\\d{8}$";
/**
20 * 中国电信:China Telecom
21 * 133,1349,153,180,189
22 */
NSString * CT = @"^1((33|53|8[09])[0-9]|349)\\d{7}$";
/**
25 * 大陆地区固话及小灵通
26 * 区号:010,020,021,022,023,024,025,027,028,029
27 * 号码:七位或八位
28 */
// NSString * PHS = @"^0(10|2[0-5789]|\\d{3})\\d{7,8}$";
NSPredicate *regextestmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", MOBILE];
NSPredicate *regextestcm = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM];
NSPredicate *regextestcu = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU];
NSPredicate *regextestct = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT];
if (([regextestmobile evaluateWithObject:telNumber] == YES)
|| ([regextestcm evaluateWithObject:telNumber] == YES)
|| ([regextestct evaluateWithObject:telNumber] == YES)
|| ([regextestcu evaluateWithObject:telNumber] == YES))
{
return YES;
}
else
{
return NO;
}
}
#pragma 正则匹配用户姓名,20位的中文或英文
+ (BOOL)checkUserName : (NSString *) userName
{
// NSString *pattern = @"^[A-Za-z0-9]{6,20}+$";
//NSString *pattern = @"^([\u4e00-\u9fa5]+|([a-zA-Z]+\s?)+)$";
NSString *pattern = @"^([\u4e00-\u9fa5]+|([a-zA-Z]+s?)+)$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
BOOL isMatch = [pred evaluateWithObject:userName];
return isMatch;
}
#pragma 正则匹配用户身份证号15或18位
+ (BOOL)checkUserIdCard: (NSString *) idCard
{
BOOL flag;
if (idCard.length <= 0) {
flag = NO;
return flag;
}
NSString *regex2 = @"^(\\d{14}|\\d{17})(\\d|[xX])$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex2];
BOOL isMatch = [pred evaluateWithObject:idCard];
return isMatch;
}
#pragma 正则匹配银行卡号是否正确
+ (BOOL) checkBankNumber:(NSString *) bankNumber{
NSString *bankNum=@"^([0-9]{16}|[0-9]{19})$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",bankNum];
BOOL isMatch = [pred evaluateWithObject:bankNumber];
return isMatch;
}
#pragma 正则只能输入数字和字母
+ (BOOL) checkTeshuZifuNumber:(NSString *) CheJiaNumber{
NSString *bankNum=@"^[A-Za-z0-9]+$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",bankNum];
BOOL isMatch = [pred evaluateWithObject:CheJiaNumber];
return isMatch;
}
还有一个关于金额输入的正则:
+ (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (string.length == 0) {
return YES;
}
//第一个参数,被替换字符串的range,第二个参数,即将键入或者粘贴的string,返回的是改变过后的新str,即textfield的新的文本内容
NSString *checkStr = [textField.text stringByReplacingCharactersInRange:range withString:string];
//正则表达式
NSString *regex = @"^\\-?([1-9]\\d*|0)(\\.\\d{0,2})?$";
return [self isValid:checkStr withRegex:regex];
}
//检测改变过的文本是否匹配正则表达式,如果匹配表示可以键入,否则不能键入
+ (BOOL) isValid:(NSString*)checkStr withRegex:(NSString*)regex
{
NSPredicate *predicte = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
return [predicte evaluateWithObject:checkStr];
}
问题五:关于登录界面逻辑
我们做的项目,必须登录后才能使用功能.刚开始我使用导航栏来做登录界面跳转逻辑,在登陆界面隐藏导航栏,跳转后显示.会出现这样一个问题:退出登录后,登录界面有时候会显示导航栏.后来换了一种思路:通过改变rootViewController来实现登录逻辑.这样就不会产生任何问题.
登录界面:
self.window.rootViewController = loginVc;
登录成功:
UIApplication.sharedApplication.delegate.window.rootViewController =tab;
退出登陆:
UIApplication.sharedApplication.delegate.window.rootViewController =loginVc;
问题六:红包
项目后期提出发红包需求,发红包首先需要添加好友.经理要求我们自己用socket实现.我们跟后台讨论了好久,基本上每一个接口都要调试好多次,只要调通,就立马开始下一个接口的调试,直到打通一条线.然后回过头再去修补逻辑.
我使用的是GCDAnsySocket这个框架,使用的时候,需要绑定Ip与port.这里会有一个问题:wifi状态下和流量状态下,手机的IP地址是变化的,这里就需要用到RealReachability这个工具,来检测网络变化:
- (void)networkChanged:(NSNotification *)notification
{
RealReachability *reachability = (RealReachability *)notification.object;
ReachabilityStatus status = [reachability currentReachabilityStatus];
ReachabilityStatus previousStatus = [reachability previousReachabilityStatus];
NSLog(@"networkChanged, currentStatus:%@, previousStatus:%@", @(status), @(previousStatus));
if (status == RealStatusNotReachable)
{
}
if (status == RealStatusViaWiFi)
{
Singleton *single = [Singleton shareData];
[self.udpSocket sendData:[single.operatorId dataUsingEncoding:NSUTF8StringEncoding]toHost:udpIP port:udpPort withTimeout:-1 tag:0];
}
if (status == RealStatusViaWWAN)
{
Singleton *single = [Singleton shareData];
[self.udpSocket sendData:[single.operatorId dataUsingEncoding:NSUTF8StringEncoding]toHost:udpIP port:udpPort withTimeout:-1 tag:0];
NSLog(@"这是手机Ip地址:%@",[GetWaanIp getIPAddress:YES]);
}
}
当手机切换网络的时候,重新和服务器绑定.
消息列表:用来显示加好友信息和红包消息,暂未加入聊天.
消息列表使用plist进行存储,作为列表,存储的都是小批量数据,plist能够满足.
我们在存储消息的时候,先判断plist是否存在,如果没有,就创建.如果有,就先将plist中的数据解析成数组,将新消息(可能是之前的未读消息,如果未读,后台会一直推送)与plist中的消息,进行拼接,通过for循环剔除重复的消息内容,然后进行展示.
通过后台一个标识来判断这个消息是未读消息,还是已读消息,如果是未读,则会提示用户.如果是已读,需要我们将plist中这条消息改为已读状态(同时后台也不再推送这条消息,因为领取红包的时候,会向后台发送一个请求,改变信息的状态).
聊天界面则使用数据库进行存储,我使用的是WHC_SQLite.我们在消息列表中点击某一条消息的时候,才会往数据库插入这条数据.然后从数据库重新查询对应数据,进行展示.
发送红包的时候,在红包界面点击发送红包,使用代理在聊天界面调用一次sendMessage的方法,用来展示红包,同时将这条数据存入数据库.
红包的领取界面及发送界面,我们是仿照的QQ的样式.当点击红包时,这里有一个判断,判断是本人点击的红包还是对方点击的红包,如果是对方,那么会向后台发送一条红包状态请求和领取红包请求,如果是本人,只发送红包状态请求.领取红包成功,红包下方展示一条领取记录.同时,发红包的人,哪里也会显示这条领取记录.