數(shù)據(jù)解析問(wèn)題:
由于后臺(tái)返回的數(shù)據(jù)是一串字符串,而不是JSON格式.所以需要我們自己處理.
數(shù)據(jù)格式是這樣的:key=value&key=value&key=value....
- 首先我們要做的是創(chuàng)建一個(gè)數(shù)組,以&號(hào)為標(biāo)志進(jìn)行分割.
- 使用initWithCapacity初始化一個(gè)字典.
- 遍歷數(shù)組,使用rangeOfString來(lái)搜索=的range信息.
- 使用substringToIndex來(lái)截取到的數(shù)據(jù)作為key值.
- 使用substringFromIndex來(lái)截取到的數(shù)據(jù)作為value值.
- 最后使用setObjcet:forKey賦值.
代碼:
+ (NSMutableDictionary *)dataAnaly:(NSString *)string
{
//以&進(jìn)行分割
NSArray * array = [string componentsSeparatedByString:@"&"];
//如果你知道大概要放多少東西,那么最好用initWithCapacity,這個(gè)會(huì)提高程序內(nèi)存運(yùn)用效率钧嘶。
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:array.count];
for (NSString *str in array)
{
NSString *string = @"=";
//rangeOfString 前面的參數(shù)是要被搜索的字符串,后面的是要搜索的字符
//包含"="
NSRange range = [str rangeOfString:string];
//返回一個(gè)字符串,這個(gè)字符串截取接受對(duì)象字符穿范圍是從索引0到給定的索引index
NSString *keyStr = [str substringToIndex:range.location];
//NSLog(@"%@",keyStr);
//返回一個(gè)字符串,這個(gè)字符串截取接受對(duì)象字符串范圍是給定索引index到這個(gè)字符串的結(jié)尾
NSString *valueStr = [str substringFromIndex:range.location+1];
// (1)setObject:forkey:中value是不能夠?yàn)閚il的;setValue:forKey:中value能夠?yàn)閚il侍芝,但是當(dāng)value為nil的時(shí)候,會(huì)自動(dòng)調(diào)用removeObject:forKey方法
// (2)setValue:forKey:中key只能夠是NSString類型埋同,而setObject:forKey:的可以是任何類型
// (3)setObject:forkey:中value是不能夠?yàn)閚il的州叠,不然會(huì)報(bào)錯(cuò)。
[result setObject:valueStr forKey:keyStr];
}
return result;
}
問(wèn)題二:
因?yàn)槭倾y行項(xiàng)目,涉及到金錢問(wèn)題,所以只要涉及到輸入密碼的地方,都需要使用CFCA安全控件進(jìn)行加密.
雖然CFCA使用很簡(jiǎn)單,但是第一次使用,也是需要踩坑的.
我們需要使用CFCA提供的CFCASIPInputField來(lái)創(chuàng)建這個(gè)控件.下面是它的幾個(gè)屬性設(shè)置.
/** 代理 */
_password_textField.sipInputFieldDelegate = self;
/** 鍵盤動(dòng)作 */
_password_textField.bIsNeedKeyboardAnimation = NO;
/** 最小長(zhǎng)度 */
_password_textField.nMinInputLength = 6;
/** 最大長(zhǎng)度 */
_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;
/** 服務(wù)器隨機(jī)數(shù) */
_password_textField.strServerRandom = self.serverRandom_string;
看到服務(wù)器隨機(jī)數(shù)沒(méi)有,起初我認(rèn)為是在創(chuàng)建控件之前,必須拿到的數(shù)據(jù).然后傻傻的做了個(gè)控件創(chuàng)建延時(shí).后來(lái)發(fā)現(xiàn):
dispatch_async(dispatch_get_main_queue(), ^{ _password_textField.strServerRandom = self.serverRandom_string;
});
這么干也是可以的,真是嗶了~
問(wèn)題三:數(shù)據(jù)簽名問(wèn)題
簽名機(jī)制如下:
對(duì)報(bào)文中出現(xiàn)簽名域(signature)之外的所有數(shù)據(jù)元(數(shù)據(jù)元前后不能有空格)
采用key=value的形式按照key名稱進(jìn)行字典排序凶赁,然后以&作為連接符拼接成待簽名串咧栗。其次逆甜,對(duì)待簽名串使用SHA-1算法做摘要,再使用商戶RSA私鑰證書(shū)對(duì)摘要做簽名操作(簽名時(shí)算法選擇SHA-1)致板。最后交煞,對(duì)簽名做Base64編碼,將編碼后的簽名串放在簽名(signature)表單域里和其他表單域一起通過(guò)HTTP Post的方式傳輸給支付平臺(tái)斟或。
這些算法網(wǎng)上都可以找到,一步步跟著來(lái)就好.最后程序只要加簽就會(huì)崩潰,找了很多原因都沒(méi)找到,所幸后臺(tái)當(dāng)時(shí)允許不加簽測(cè)試.直到有一天我發(fā)現(xiàn),原來(lái)是ras.pfx導(dǎo)入方式不正確.
在這里導(dǎo)入,否則Xcode是識(shí)別不到的.之前都是拖拽進(jìn)去的.
問(wèn)題四:正則表達(dá)式
由于是金融類軟件,需要登記身份信息.所以就會(huì)產(chǎn)生很多對(duì)數(shù)據(jù)的合法性判斷.我認(rèn)為這部分判斷是在前臺(tái)執(zhí)行的,但是后臺(tái)告訴我,他們那邊處理.想著,在前臺(tái)處理,輸入正確,就會(huì)發(fā)送請(qǐng)求.輸入不正確就不發(fā)送請(qǐng)求.可以優(yōu)化一下程序.然后就加入了一些正則判斷.結(jié)果就是測(cè)試的人員不能任意的注冊(cè)賬號(hào)了.(-_-),必須輸入正確的手機(jī)號(hào)格式.
#pragma 正則匹配手機(jī)號(hào)
+ (BOOL)checkTelNumber:(NSString *) telNumber
{
/**
* 手機(jī)號(hào)碼
* 移動(dòng):134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
* 聯(lián)通: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 * 中國(guó)移動(dòng):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 * 中國(guó)聯(lián)通: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 * 中國(guó)電信:China Telecom
21 * 133,1349,153,180,189
22 */
NSString * CT = @"^1((33|53|8[09])[0-9]|349)\\d{7}$";
/**
25 * 大陸地區(qū)固話及小靈通
26 * 區(qū)號(hào):010,020,021,022,023,024,025,027,028,029
27 * 號(hào)碼:七位或八位
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 正則匹配用戶身份證號(hào)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 正則匹配銀行卡號(hào)是否正確
+ (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 正則只能輸入數(shù)字和字母
+ (BOOL) checkTeshuZifuNumber:(NSString *) CheJiaNumber{
NSString *bankNum=@"^[A-Za-z0-9]+$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",bankNum];
BOOL isMatch = [pred evaluateWithObject:CheJiaNumber];
return isMatch;
}
還有一個(gè)關(guān)于金額輸入的正則:
+ (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (string.length == 0) {
return YES;
}
//第一個(gè)參數(shù)素征,被替換字符串的range,第二個(gè)參數(shù)缕粹,即將鍵入或者粘貼的string稚茅,返回的是改變過(guò)后的新str,即textfield的新的文本內(nèi)容
NSString *checkStr = [textField.text stringByReplacingCharactersInRange:range withString:string];
//正則表達(dá)式
NSString *regex = @"^\\-?([1-9]\\d*|0)(\\.\\d{0,2})?$";
return [self isValid:checkStr withRegex:regex];
}
//檢測(cè)改變過(guò)的文本是否匹配正則表達(dá)式平斩,如果匹配表示可以鍵入亚享,否則不能鍵入
+ (BOOL) isValid:(NSString*)checkStr withRegex:(NSString*)regex
{
NSPredicate *predicte = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
return [predicte evaluateWithObject:checkStr];
}
問(wèn)題五:關(guān)于登錄界面邏輯
我們做的項(xiàng)目,必須登錄后才能使用功能.剛開(kāi)始我使用導(dǎo)航欄來(lái)做登錄界面跳轉(zhuǎn)邏輯,在登陸界面隱藏導(dǎo)航欄,跳轉(zhuǎn)后顯示.會(huì)出現(xiàn)這樣一個(gè)問(wèn)題:退出登錄后,登錄界面有時(shí)候會(huì)顯示導(dǎo)航欄.后來(lái)?yè)Q了一種思路:通過(guò)改變r(jià)ootViewController來(lái)實(shí)現(xiàn)登錄邏輯.這樣就不會(huì)產(chǎn)生任何問(wèn)題.
登錄界面:
self.window.rootViewController = loginVc;
登錄成功:
UIApplication.sharedApplication.delegate.window.rootViewController =tab;
退出登陸:
UIApplication.sharedApplication.delegate.window.rootViewController =loginVc;
問(wèn)題六:紅包
項(xiàng)目后期提出發(fā)紅包需求,發(fā)紅包首先需要添加好友.經(jīng)理要求我們自己用socket實(shí)現(xiàn).我們跟后臺(tái)討論了好久,基本上每一個(gè)接口都要調(diào)試好多次,只要調(diào)通,就立馬開(kāi)始下一個(gè)接口的調(diào)試,直到打通一條線.然后回過(guò)頭再去修補(bǔ)邏輯.
我使用的是GCDAnsySocket這個(gè)框架,使用的時(shí)候,需要綁定Ip與port.這里會(huì)有一個(gè)問(wèn)題:wifi狀態(tài)下和流量狀態(tài)下,手機(jī)的IP地址是變化的,這里就需要用到RealReachability這個(gè)工具,來(lái)檢測(cè)網(wǎng)絡(luò)變化:
- (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(@"這是手機(jī)Ip地址:%@",[GetWaanIp getIPAddress:YES]);
}
}
當(dāng)手機(jī)切換網(wǎng)絡(luò)的時(shí)候,重新和服務(wù)器綁定.
消息列表:用來(lái)顯示加好友信息和紅包消息,暫未加入聊天.
消息列表使用plist進(jìn)行存儲(chǔ),作為列表,存儲(chǔ)的都是小批量數(shù)據(jù),plist能夠滿足.
我們?cè)诖鎯?chǔ)消息的時(shí)候,先判斷plist是否存在,如果沒(méi)有,就創(chuàng)建.如果有,就先將plist中的數(shù)據(jù)解析成數(shù)組,將新消息(可能是之前的未讀消息,如果未讀,后臺(tái)會(huì)一直推送)與plist中的消息,進(jìn)行拼接,通過(guò)for循環(huán)剔除重復(fù)的消息內(nèi)容,然后進(jìn)行展示.
通過(guò)后臺(tái)一個(gè)標(biāo)識(shí)來(lái)判斷這個(gè)消息是未讀消息,還是已讀消息,如果是未讀,則會(huì)提示用戶.如果是已讀,需要我們將plist中這條消息改為已讀狀態(tài)(同時(shí)后臺(tái)也不再推送這條消息,因?yàn)轭I(lǐng)取紅包的時(shí)候,會(huì)向后臺(tái)發(fā)送一個(gè)請(qǐng)求,改變信息的狀態(tài)).
聊天界面則使用數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ),我使用的是WHC_SQLite.我們?cè)谙⒘斜碇悬c(diǎn)擊某一條消息的時(shí)候,才會(huì)往數(shù)據(jù)庫(kù)插入這條數(shù)據(jù).然后從數(shù)據(jù)庫(kù)重新查詢對(duì)應(yīng)數(shù)據(jù),進(jìn)行展示.
發(fā)送紅包的時(shí)候,在紅包界面點(diǎn)擊發(fā)送紅包,使用代理在聊天界面調(diào)用一次sendMessage的方法,用來(lái)展示紅包,同時(shí)將這條數(shù)據(jù)存入數(shù)據(jù)庫(kù).
紅包的領(lǐng)取界面及發(fā)送界面,我們是仿照的QQ的樣式.當(dāng)點(diǎn)擊紅包時(shí),這里有一個(gè)判斷,判斷是本人點(diǎn)擊的紅包還是對(duì)方點(diǎn)擊的紅包,如果是對(duì)方,那么會(huì)向后臺(tái)發(fā)送一條紅包狀態(tài)請(qǐng)求和領(lǐng)取紅包請(qǐng)求,如果是本人,只發(fā)送紅包狀態(tài)請(qǐng)求.領(lǐng)取紅包成功,紅包下方展示一條領(lǐng)取記錄.同時(shí),發(fā)紅包的人,哪里也會(huì)顯示這條領(lǐng)取記錄.