本文主要为您介绍4个问题
一. load妙用
二. AOP面向切面编程
三. NSNumber 和 int 使用哪一个
四. 适配64位
一. 学会使用load方法
在开发过程当中,我们可能可能遇到老板经常修改需求. 可能为了提高逼格,老板说要我们可以将我们的app分享出去,诺! 现在不是要接入统计吗?我们又需要在我们的AppDelegate中接入一段代码; 一段时间后,老板又想要接入统计功能,我代码结构这么好,是不是想死的心都有了.
需求是无穷无尽的,我需要bug统计(fir hud),提醒用户评分系统(iRate),推送(Jpush). 当初你一心想把所有的代码封装好,现在是不是被老板的需求给完全打败了.
别担心,现在我来教你你一些小技巧.
也许您还没有用过IQKeyBoardManager和IRate这种智能库.大牛在readme中写了这么一段话
1.CodeLess , zero line of Code 不需要写任何代码
2.Works Automatically 自动工作
3.No more scrollView 不需要scrollView
4.No more subClass 不需要继承父类
5.No more manual Work 不需要配置
6.No more #import 不需要导入
其实也不是什么很神奇的东西,只是大牛用到了 + (load)方法
学习OC的人都应该了解到, load方法是在一个类被加载到运行库中时会被自 动调用. 这不就实现了自动调用. 接下来我直接上代码:
#import <Foundation/Foundation.h>
@interface ThirdPartService : NSObject
@end
#import "ThirdPartService.h"
#import "UMSocial.h"
#import "UMSocialWechatHandler.h"
#import "UMSocialQQHandler.h"
#import <MobClick.h>
#import <FIR/FIR.h>
@implementation ThirdPartService
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// TODO 这里是我自己测试的 fir hud
[FIR handleCrashWithKey:@"XX"];
// 友盟
[UMSocialData setAppKey:@"XX"];
// 隐藏未安装的平台
[UMSocialConfig hiddenNotInstallPlatforms:@[UMShareToQQ,UMShareToQzone,UMShareToWechatSession,UMShareToWechatTimeline]];
// 注册微信
[UMSocialWechatHandler setWXAppId:@"XX" appSecret:@"XX" url:@""];
// 注册QQ
// TODO QQ的不是真的
[UMSocialQQHandler setQQWithAppId:@"XX" appKey:@"XX" url:@""];
// TODO UM统计
[MobClick startWithAppkey:@""];
[MobClick setCrashReportEnabled:NO];
NSLog(@"第三方服务注册完毕");
});
}
@end
看上面我们就可以把我们要配置的一些代码都写在load方法里面,这样我们就可以将服务和模块完全分离了.
但是有的服务,如APNS 需要在launchOptions里面写,那就只能在appDelegate中写了,不过这样的话我们同样已经摘除了许多冗余的代码,只剩下几个固定的. 到时候我们在修改一下里面的代码就行了.
二. AOP面向切面的编程
接着上面的讲,我们在接入了友盟统计, 友盟统计最基本的东西就是统计页面的pv
/**
* 自动页面时长统计,开始记录某个页面展示的时长
* 使用方法是: 必须配对调用beginLogPageView: 和
*
* endLogPageView: 两个函数来完成自动统计, 若是调用某个函
*数不回生成有效数据. 在该页面展示时调用beginLogPageView, 在该页面退出时调用endLogPageView完成统计
*
*/
+ (void)beginLogPageView :(NSString *)pageName seconds :(int)seconds;
/**
* 自动页面时长统计,开始记录某个页面展示的时长
* 使用方法是: 必须配对调用beginLogPageView: 和
*
* endLogPageView: 两个函数来完成自动统计, 若是调用某个函
*数不回生成有效数据. 在该页面展示时调用beginLogPageView, 在该页面退出时调用endLogPageView完成统计
*
*/
+ (void)endLogPageView: ;
这样写那么看起来不就是很简单吗 ? 我打开ViewController在代码里面加上这几句话就行了啦!
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
#ifndef DEBUG
[MobClick beginLogPageView:NSStringFromClass([self class])];
#endif
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
#ifndef DEBUG
[MobClick endLogPageView:NSStringFromClass([self class])];
#endif
}
但是你想象一下一个项目中有多少个控制器,每一个控制器我们不都是要加上代码吗? 那不是整个项目我们都要进行重新翻开写吗?
自然而然地, 我们想到了继承, 我们可以写一个自定义的MyBaseViewController,我们在这里面把我们需要写的代码都写在这里面,然后我们就修改项目的继承关系就行了! 但是你想过没有,有些控制器并不是继承UIViewController,它们其中有些是继承自UINavigationController,这种情况就更糟糕了!
我们的解决办法就是 Method Swizzling
直接上代码:
#import "UIViewController+AOP.h"
#import <objc/runtime.h>
#define GLOABAL_NAVIGATION_BAR_TIN_COLOR [UIColor yellowColor]
@implementation UIViewController (AOP)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// swizzleMethod
swizzleMethod(class, @selector(viewDidLoad), @selector(aop_viewDidLoad));
swizzleMethod(class, @selector(viewDidAppear:), @selector(aop_viewDidAppear:));
swizzleMethod(class, @selector(viewWillAppear:), @selector(aop_viewWillAppear:));
swizzleMethod(class, @selector(viewWillDisappear:), @selector(aop_viewWillDisappear:));
});
}
void swizzleMethod(Class class, SEL originalSelector,SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)aop_viewDidAppear:(BOOL)animated {
[self aop_viewDidAppear:animated];
}
-(void)aop_viewWillAppear:(BOOL)animated {
[self aop_viewWillAppear:animated];
#ifndef DEBUG
[MobClick beginLogPageView:NSStringFromClass([self class])];
#endif
}
-(void)aop_viewWillDisappear:(BOOL)animated {
[self aop_viewWillDisappear:animated];
#ifndef DEBUG
[MobClick endLogPageView:NSStringFromClass([self class])];
#endif
}
- (void)aop_viewDidLoad
{
[self aop_viewDidLoad];
if ([self isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController *)self;
nav.navigationBar.translucent = NO;
nav.navigationBar.barTintColor = GLOABAL_NAVIGATION_BAR_TIN_COLOR;
nav.navigationBar.tintColor = [UIColor whiteColor];
NSDictionary *titleAtt = @{
NSForegroundColorAttributeName : [UIColor whiteColor]
};
[[UINavigationBar appearance] setTitleTextAttributes:titleAtt];
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];
}
self.navigationController.interactivePopGestureRecognizer.delegate = (id <UIGestureRecognizerDelegate>)self;
}
@end
我们充分利用了黑魔法达到了面向切面编程的好处
黑魔法非毒药,遵守一个规范写出来的代码是不会Crash的,只要能帮我们解决问题及时好东西.
黑魔法性能有瓶颈吗? 都到runtime的底层了,你还担心有瓶颈,少年安心使用好了,不服用timeProfile测试. 黑魔法也非万能,像我们在导航控制器要封装手势,同意管理左侧按钮,这些东西还是基层来得好.
三. NSNumber OR Int
NSNumber 和 Int 在网络请求中作为参数时我们到底用哪一个?
下面我们来看看一段代码
+ (void)getDataAtPageNo:(NSNumber *)pageNo PageSize:(NSNumber *)pageSize
complete:(CompleteBlock)complete {
NSMutableDictionary *param = [NSMutableDictionary dictionary];
if (pageSize) {
[param setObject:pageSize forKey:@"pageSize"];
}
[param setObject:pageNo forKey:@"pageNo"];
// SendRequest
}
+ (void)getData2AtPageNo:(long )pageNo PageSize:(long )pageSize
complete:(CompleteBlock)complete {
NSMutableDictionary *param = [NSMutableDictionary dictionary];
[param setObject:@(pageSize) forKey:@"pageSize"];
[param setObject:@(pageNo) forKey:@"pageNo"];
// SendRequest
}
在访问网络请求时,对于有参数的请求主要由上述两种方式:
1. 使用对象作为参数
2. 使用基本数据类型作为参数
一般情况下, 这是没有什么太大的区别,但是在此 我想申明一点,对基本数据要说 Never.请看我下面的分析:
对于上面的第一种情况的代码:
参数有pageNo 和pageSize两个参数. 在控制器中拥有这两个对象作为属性,在下拉刷新和或者上拉刷新会传递对应的参数给请求方法,在这两个参数中pageNo参数是必须得,但是pageSize这个参数可能是可有可无的,因为如果你不传递的话,后台服务器会指定一个默认的值, 如10, 而如果我们传递该参数到后台之后,就会覆盖后台服务器的这个默认参数. 其实原因就是在这种情况下, 我们用的是NSNumber作为参数,所以如果我们不传递参数,NSNumber就会默认为nil,这样就不会覆盖后台服务器的 10 的默认参数.对于上面的第二种情况的代码:
在第二种情况中,如果我们不给pageSize赋值的话,基本数据类型pageSize在C 和 Objective-C语言中会被赋予0 . 那么这样的话我们没给pageSize赋值的话传到后台服务器之后就会覆盖默认值,那就让后台设置的默认值失效.
所以我们在使用的请求参数的时候,一定要对基本数据类型说Never
四. NSNumber比基本数据类型的好处? 64 位适配的问题
- 如上文所说在网络请求中使用的好处
- 在plist 和NSKeyedArchieve归档时我们都应该使用NSNumber
- 基本数据类型的NSInteger 在iPhone5s 以下的手机中的适配问题
我们这样看貌似没有什么不妥的,但是如果我们把设备切换到5s以下,也就是32位机. 就会出现如下警告
为什么会出现呢? 让我们来看一下NSInteger的头文件
解释如下, 在64位机下 NSInteger是 long类型的,但是在32位机下是 int 型,所以会发出类型不匹配的警告.
我们知道苹果不支持64位的app上架, 但貌似我们都没有做过32位与64位的适配.
其实拿到一个NSNumber我们并不知道他到底是int long unsigned int Bool 直接针对某个类型转换是有风险的 但是其实Clang 给我们提供了个非常好用的Macro @()