实践出真知,写代码是成长必经的过程,至死方休。初版——待补充
开发过程中代码整理
一. UI部分
1.根据Xib文件名称生成一个ViewController的类
- (instancetype)init {
self = [super init];
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:@"ViewController" owner:nil options:nil] firstObject];
}
return self;
}
2.ViewDidLoad减负
ViewDidLoad在ViewCotrollView被创建时执行,一般我们会在这个方法中进行初始化操作。但是这里我建议不要把过多的初始化代码直接写在该方法中,而是分成几个不同的初始化方法。如:initView()
、initData()
、initInfo()
、initSetting()
等等;另外提倡使用懒加载和预加载的方式初始化成员变量,具体使用方法,请点击此处进行学习。
3.WebView的代理方法
UIWebView是用来加载网页的一个控件,目前官方已经建议使用WKWebView来替代UIWebView进行网页的加载和管理,但是我今天只是描述一种概念,与实际使用哪个控件没有太大关系。
如果你需要使用UIWebView加载网页,请至少实现这个代理:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
这个代理的作用是当页面加载错误的时候做出相应的处理,因为这种情况是一定会出现的,比如网络问题或者网页服务端出现问题等等。如果不处理,页面就可能变成空白或者未知页面,这绝对不是我们预期的显示结果。至于其他的代理,请根据业务需求自行决定是否实现。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
4.导航栏的初始化
下面是在AppDelegate.m文件中对导航栏的一些初始化操作,其中涉及主题颜色设置、背景色设置、标题的属性设置和导航栏样式设置,没有什么要讲的,请自行领会;
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:57.0/255.0 green:141.0/255.0 blue:238.0/255.0 alpha:0.5]];
[[UINavigationBar appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, [UIFont systemFontOfSize:20], NSFontAttributeName, nil]];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
5.UITableView中Cell的重用问题
我开发的App中有一个页面是纵向列表嵌套横向列表,这个页面在使用真机调试的时候总是出现卡顿,而使用模拟器就不会
二. 数据部分
1.Model的构建——KVC
关于KVC的使用和技巧,有一篇详解KVC的文章,请点击上方标题或者点击此处自行学习,我就不班门弄斧了!
2.打印Model的信息
开发过程中我们经常会使用调试功能,在控制台使用po命令打印某个对象或者变量的信息,但是我们发现如果你尝试打印一个对象的信息时会出现这样的信息(以ResultModel为例):
<ResultModel: 0x60800019d5a0>
很显然这不是我们想看到的信息,我们希望看到的是ResultModel的数据结构。那么我们需要重构ResultModel的description方法,这个方法是对象的描述方法,可以理解为是对象的字符串表现形式。下面是重构代码:
- (NSString *)description {
return [NSString stringWithFormat:@"\ncode:%lu\nerror:%d\ndata:%@\nmessage:%@\nstatusCode:%@\n", self.code, self.error, self.data, self.message, self.statusCode];`
}
OK,我们再试一下po命令打印ResultModel:
code:0
error:0
data:{
addTime = "2017-08-14 09:56:59";
avatar = "<null>";
blackList = 0;
code = ea376934311645b188e9e789d96d03ab;
flag = 1;
id = 27;
imei = "2ADEF2A7-3090-42D6-888D-0D9BE1D35D3F";
inviter = 1;
isBindBonus = 1;
mobile = 18003860428;
password = qqqqqq;
promoCode = 41GI5;
qq = 511359588;
registerFrom = 2;
trademanager = wbxxmya;
username = "\U795d\U5f6d\U8f89";
vip = 0;
}
message:请求成功
statusCode:SUCCESS
搞定!结构清晰,数据一目了然。这才是我们想要的效果,这个方法适应任何一个对象,也就是说你可以轻松的打印出字典、数组、自定义Model甚至ViewController的详细信息。
3.截获所有通过App发送的请求和返回数据
如果你的项目中使用了WebView加载H5页面,如果你需要获取H5页面加载过程中发出的Ajax请求的数据,那么你必须会这项技能:NSURLCache获取所有通过App发出的请求信息及返回数据。
1、首先自定义一个类来继承NSURLCache:
//EEURLCache.h文件
@interface EEURLCache : NSURLCache
@end
2、然后在代理文件AppDelegate中设置URLCache代理为自定义的EEURLCache类:
[NSURLCache setSharedURLCache:[[EEURLCache alloc] init]];
3、接着在EEURLCache类中实现截获数据的方法:
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request {
//cachedResponse为请求结果,request则是请求的信息;
}
现在你已经拿到了所有通过App发出的网络请求,包括Ajax请求,下面你就可以使用这些数据了,因为这些数据是通过异步的方式拿到的,所以如果想在某个页面使用,请使用通知的方式把数据发送出去,在使用的地方接收这些数据就OK了,关于通知的使用方法,请自行学习。
4.宏还是常量?
开发过程中常常会用到宏定义或者常量,这两种方式都可以实现全局的使用,并且具备便于维护的特点。那么我们使用宏还是常量呢?我的建议是需要明确类型的,请一定要使用常量,因为宏事无类型的;如果需要很多步骤才能得到数据的,请使用宏;如果可以用方法代替的,请尽量使用工具类方法来实现。基本就是这样,下面我给出一个简单的常量定义和实现的方法:
//HGAppConst.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
extern NSString *const UMENG_KEY;
extern NSString *const SERVICE_ID;
extern NSString *const BAICHUAN_KEY;
extern NSString *const RONGCLOUD_KEY;
extern NSString *const BAIDU_API_KEY;
extern NSString *const BAIDU_SECRE_KEY;
/*反馈会话ID*/
extern NSString *const FEEDBACK_SESSION_ID;
*************我是一条分割线**************
//HGAppConst.m文件
#import "HGAppConst.h"
NSString *const UMENG_KEY = @"56ab2a07e0f55ac43e001d33";
NSString *const SERVICE_ID = @"KEFU146192494770355";
NSString *const BAICHUAN_KEY = @"23337006";
NSString *const RONGCLOUD_KEY = @"cpj2xarljnt5n";
NSString *const BAIDU_API_KEY = @"3VrbGNWXPfX7Oq0BOQIp8WEO";
NSString *const BAIDU_SECRE_KEY = @"1a9669e9b341282002967c641f23c686";
NSString *const FEEDBACK_SESSION_ID = @"15440428";
5.多类型数据或者多选项时,请尽可能使用枚举变量
一般在项目中不建议使用纯数字的,因为纯数字没有明确的含义,除非像0、1这种有一定特殊意义的值,否则尽量使用变量、宏、常量或者枚举变量来表示。而出现多类型数据或者多选项的情况时,请尽可能使用枚举变量,因为枚举变量可以统一管理同一类别(这里的类别指的是抽象出来的类型,不是数据类型,因为枚举变量的值都是整型)的数据,比如颜色、大小、字体类型、商品分类、请求类型等等都可以使用枚举的方式进行定义。
typedef enum {
ValidateTypeUnsignedNumber, //验证大于0数量
ValidateTypeSignedNumber, //验证有符号数量
ValidateTypeIDCard, //验证身份证号
ValidateTypePassword, //验证密码
ValidateTypeUnsignedMoney, //验证大于0金额
ValidateTypeSignedMoney, //验证有符号金额
} ValidateType;
6.关于数组
数组变量在使用时,虽然NSMutableArray比较方便,而且利于扩展,但是还是建议尽可能使用NSArray而不是NSMutableArray,因为NSMutableArray会比NSArray占用更多的内存。另外如果必须使用NSMutableArray,请一定记得初始化,否则你会发现你定义的NSMutableArray会一直为空。
NSMutableArray *mutableArray = [NSMutableArray array];
7.你知道结构体是什么吗?
大家知道什么是结构体吗?如果你有C语言基础,那么肯定是知道的,我在这里只是提一下,因为大家对这个结构体的概念基本都理解,如果还没有相关的知识,请自行百度。在此我只是想跟大家说一下使用方面的技巧,我们都知道数据转模型,就是通常我们所说的Model类,使用起来是挺方便的,只需要传入一个字典,然后就可以解析出一个Model类型的数据类,可以使用点语法直接调用成员变量。但是这种方式在部分场景中是不建议使用的,比如:这个Model只在一个ViewController中使用到,或者Model的使用频率不频繁,且数据结构简单,还有就是这个数据只被用到一次,不值得耗费时间来构建一个Model类并且配置好相应的初始化和构造解析方法。这个时候我们就需要使用结构体来代替Model类,这样既方便,又快捷,而且非常节省资源,也不需要考虑内存泄漏问题,具体原理不在这篇文档的讨论范围,以后会专门来讲。
三. 项目结构
1.CocoaPods的使用
第三方库的使用可以大幅度提升开发效率和优化使用体验,但是自主维护这些第三方库是超级麻烦的,所幸我们有CocoaPods,我么完全可以使用这个工具来管理我们大部分的第三方库。不过网上对这个工具的使用教程很多,讲的也很详细,我就不多加赘述了,请点击此处去学习。
2.单例类的作用和实现
单例类在我们开发的过程中会经常用到,单例类的具备这些优点:
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
3、允许可变数目的实例。
OK,现在我们以UserInfoModel为例来构建一个单例类:
//UserInfoModel.h文件
@interface UserInfoModel : NSObject
......UserInfoModel的数据属性
+ (UserInfoModel *)sharedInstance;
+ (instancetype)userInfoModelWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
//UserInfoModel.m文件
#import "HGUserInfoModel.h"
static UserInfoModel* sharedSingleton = nil;
@implementation UserInfoModel
+ (UserInfoModel *)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedSingleton = [[super allocWithZone:NULL] init];
});
return sharedSingleton;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
+ (instancetype)userInfoModelWithDict:(NSDictionary *)dict {
return [[self sharedInstance] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self == [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"key is undefinedKey!")
}
@end
四. 其他
1.获取URL Scheme信息
URL Scheme是iOS应用间进行互相唤起的一种机制,以下代理代码,在AppDelegate中实现,即可打印出所有唤起App的URL Scheme信息,包含唤起来源App的BundleID和请求信息等。
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
[BaseHelper EELogWithtype:EELogTypeNormalInfomation withTag:@"Calling Application Bundle ID" andMessage:sourceApplication];
[BaseHelper EELogWithtype:EELogTypeNormalInfomation withTag:@"URL" andMessage:url.description];
[BaseHelper EELogWithtype:EELogTypeNormalInfomation withTag:@"URL scheme" andMessage:url.scheme];
[BaseHelper EELogWithtype:EELogTypeNormalInfomation withTag:@"URL query" andMessage:url.query];
[BaseHelper EELogWithtype:EELogTypeNormalInfomation withTag:@"URL host" andMessage:url.host];
return YES;
}
这个URL Scheme的应用很广泛,包括唤起指定页面、传入数据、应用间通讯等等。但是我今天要介绍的是另一个高级用法。就是通过模拟其他App,来截获URL Scheme的详细信息。比如有我知道支付宝的所有URL Scheme,然后我建立一个测试项目,URL Scheme地址直接复制支付宝App的URL Scheme,然后我给自己的手机安装个项目,通过某些应用支付唤起支付宝会提示是否打开支付宝,神奇的事情就从这里开始,你点击打开就会发现打开了刚才安装的那个项目。这个时候,如果你实现了上面的代理方法,你就可以在获取到URL Scheme所有的信息,包含URL地址,query和所有参数。然后你就可以进行分析,做你想做的事情,比如:模拟支付宝交易,修改数据请求,使用自己开发的App进行二次中转等等,只有你想不到,没有做不到的。
2.退出App了,该释放的释放,该清理的清理
App如果退出,不论是主动退出还是意外退出,都需要对需要释放的资源和需要清空的数据做处理,这个并不困难,只要在AppDelegate.m文件中实现以下方法即可。
- (void)applicationWillTerminate:(UIApplication *)application {
//此处添加需要处理的事件即可;
}
3.一个三秒之内不能关闭的弹窗,你需要吗?
我们知道弹窗是经常出现在移动端App中的,弹窗一般分为两种:列表式弹窗和弹出式弹窗。弹出式弹窗又分两种,一种是提醒,一种是选择,而无论哪种都需要有至少一个UIAlertAction来接收点击事件,从而关闭这个弹窗,并且执行相应的事件。现在我给大家搞一个三秒之内不能关闭的弹窗,实现代码如下:
UIAlertController *alert = [UIPartHelper addAlertWithTile:@"提醒" andMessage:@"禁止使用返利网,淘宝客等一切返利行为,因为返利被投诉将处以淘客佣金1.5倍罚款并将淘客佣金退还商家!" haveActionTitle:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:nil]];
});
其中的UIPartHelper是我定义的工具类,不用太过关注这个,重点在使用dispatch_after方法延时三秒给alert添加一个按钮,在三秒之内用户根本找不到这个按钮,也就无法关闭弹窗,至于有什么作用,用得到的人就拿走,用不到就当没看到就行了。我写这个东西只是为了拓宽大家的思路,其实很多看似很复杂的功能都是可以通过这种发散思维的方式来实现的。