寒哥教你学 iOS - 经验漫谈(转)

转自http://www.cocoachina.com/ios/20150907/13339.html

本篇文章主要讲解 4个问题

  1. load妙用

  2. aop面向切面编程

  3. NSNumber Or Int

  4. @()适配64位

1 让appDelegate 减少负担

经过漫长时间的学习 你终于掌握了iOS大法 你找到了份iOS开发的工作 信誓旦旦的要开始你的coding生涯 老板对你非常器重 然后告诉你 我觉得你的技术 是非常***的 那这个项目就你自己来搞吧 啊哦这就意味着这个项目你就从头到尾处理 从软件的架构 到页面的展示 都交给你喽 ??

用着自己的半吊子水平 papapa的 coding 决心一定要把代码封装好 写的漂亮 (其实是听大神说 封装 其实自己不太懂)

项目到了尾声 老板告诉你我们的app 我们的app 将来得来个分享到朋友圈的功能吧 不然怎么体现我产品的牛逼

然后你听说友盟比较好使(有广告的嫌疑) 你去友盟看了他们的文档 他告诉你你要在 appdelegate didFinishLaunch方法里面写了这个东西

1
2
3
4
5
6
7
 [UMSocialData setAppKey:@"XX"];
    //     注册微信
 
 [UMSocialWechatHandler setWXAppId:@"XXX"  appSecret:@"XX" url:@""];
    //    注册QQ
 
 [UMSocialQQHandler setQQWithAppId:@"XXX" appKey:@"XXX" url:@""];

过了几天 老板又说 我们需要统计下我页面的信息 你接入了友盟的统计 在appdelegate didFinishLaunch又 多了行代码

需求是无穷无尽 我需要bug统计(fir hud) 提醒用户评分系统(iRate) 推送(jPush 信鸽 个推。。)

当初你决心一定要把代码封装的完美 写的漂亮的心早就被老板的需求彻底打败了

别担心 寒哥教你小技巧

不知道你们用过 IQKeyBoardManageiRate这种智能库没

大牛的readme 写了这段话

1
2
3
4
5
6
7
Key Features
1) CODELESS, Zero Line Of Code 不需要写任何代码
2) Works Automatically //自动工作
3) No More UIScrollView //不需要scrollview
4) No More Subclasses //不需要继承父类
5) No More Manual Work //不需要配置
6) No More #imports //不需要导入

其实不神奇 只是大牛用了 + load这个方法

学习OC都知道这个代码会在一个类被加载到运行库中就会被自动调用 这不就实现了 自动调用

写一个类继承自NSObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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</fir></mobclick.h></foundation>

类似于定位也可以这样写

blob.png

模块和服务完全拆开

但是有的服务 如APNS需要LaunchOption 那就只能写在appdDelegate 不过这样的话已经摘除很多代码了 只剩下几个固定的 到时候再修改appDelegate就会感觉非常清晰 了

2 ViewController继承?

接着上面讲 我们接入了友盟统计 友盟统计最基本的东西就是 统计页面的pv

blob.png

友盟的这样写 对于新手的我们就觉得这不就so easy吗

我打开了某个vc(HomeViewController)

在代码里面写上了这句

1
2
3
4
5
6
7
-(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}

然后我一个项目中可能有几十个 甚至上百个页面需要统计pv 我总不能每个节目都这样写吧

聪明的我们想到了继承

如MyBaseViewController:UIViewController

这样就要做一件事 把我们项目中所有继承自UIViewController的类全部改为继承自MyBaseViewController 然而你真的觉得这样好吗 我们一个项目中有几十个控制器 我就要把每个控制器改一遍

这种重复性的工作一是无聊 而是容易出错 你复制着复制者就会遗漏掉某个类 重要的是 我们项目中很多类并不是直接继承自UIViewController 有的可能是UITableViewController UICollectionViewContr0ller UINavigationController 甚至不常用的UISearchDisPlayController UIPopoverController ?UIPresentController 是不是突然觉得这么多啊啊 ??

这也不是坑的 坑的是将来你混成了大牛 招了个小弟 你告诉他你所有的类都要继承自我写的各种父类 新手总是会不经意见犯错误 有些类忘记继承了 后期查起来难度非常大 浪费时间 所以这种设计是不合理的

  • 寒哥再次教你黑魔法 Method swizzling

关于这个是干嘛的 自行百度

这里有一篇来自NSHipster博主的文章 英文

中文翻译

还有一篇解释runtime的文章传送门

实践

用方法交叉 我们就可以拦截吸引的方法了 上代码了

这样就做到了面向切面编程(AOP)的思想

上代码

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#import <uikit uikit.h="">@interface UIViewController (AOP)#warning  运行时 改变一下方法 做一些切面编程比如 统计 等等
 @end
 
 
 
 #import "UIViewController+AOP.h"
 #import <objc runtime.h="">#import <mobclick.h>
 @implementation UIViewController (AOP)
 
 + (void)load {   static dispatch_once_t onceToken;  dispatch_once(&onceToken, ^{
    Class class = [self class];    // When swizzling a class method, use the following:
    // Class class = object_getClass((id)self);
    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 = GLOBAL_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.view.backgroundColor = [UIColor whiteColor];self.navigationController.interactivePopGestureRecognizer.delegate = (id<uigesturerecognizerdelegate>)self;
 } @end</uigesturerecognizerdelegate></mobclick.h></objc></uikit>

图片代码一份 方便观看

blob.png

blob.png

我们充分利用了黑魔法达到了面向切面编程的好处

思想来源这里http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html

黑魔法非毒药 遵守一个规范写出来的代码是不会Crash的 只要能帮我们解决问题就是好东西

黑魔法性能 有瓶颈? 都到runtime的底层了 你还担心有瓶颈 ?少年安心使用就好了 ? 不服 可以用Time Profiel测试

黑魔法也非万能 ?像 我们在导航控制器要封装手势 统一管理左侧返回按钮 ?这些东西 还是继承来得好

技术就是工具 黑猫,白猫,抓住老鼠就是好猫

3 网络访问参数到底用基本数据类型还是对象

下面看两个方法

blob.png

1
2
3
4
5
6
7
8
9
10
11
 + (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出现基本数据类型

一般情况下 开发者可能觉得并没有什么区别 下面我给大家举个例子

在设计一个分页展示数据的时候: 在页面上的逻辑就是 默认加载第一页 每页长度为10 (Server端的同学一般都很友好 默认情况下 不传每页的长度就是10个) 但是传了就会覆盖掉后台写的默认参数 如传了20 Server就吐20条数据

  1. 在第一中设计方案中: 可能在某个控制器中保留一个PageNo PageSize 的对象的成员变量 ,在下拉刷新或者上拉加载的时候 会传递对应的参数给请求方法 , 如果没有特殊需求的话pageSize 对象可有可无 也就是有可能为nil ,那在对应的param可能就没有这个参数传递给Server 。

    Server 就会交还给我们某页的20条数据

  2. 在 第二种设计方案中 : 可能也在某个控制器中保留一个PageNo pageSize的基本数据类型的成员变量, 在访问网络请求时交给对应的方法 , 一般没有特殊需求我们也不会对PageSize专门设值 但是 基本数据类型在OC 和C语言这种传统的编程语言中是有默认值的 为0,虽然我们没有给pageSize 赋值 但是默认系统默认给了0这个初始值 那么传递到Server的时候 就会覆盖掉Server 写的默认pageSize=10 这样的请求既不会报错 也不会返回数据

超级难调试

所以在网络访问中 寒哥给出的意见就是Never出现基本数据类型

4 用NSNumber比基本数据类型的好处 ? 64位适配问题

我们一般都用来当做网络请求的参数 缓存或者展示到页面

  1. 对于网络请求的参数 因为NSDictionary只能放对象 所以NSNumber最好的方式

  2. 缓存 无论缓存到plist 还是KeyArchive 都是需要对象的所以NSNumber也非常合适

3 展示到页面

我见过这样给页面上赋值的朋友

blob.png

我们看到这样貌似并没有什么不妥

但是我们把设备切换到iPhone5S以下 也就是32位的设备

blob.png

注意这里有Warning

为什么呢 我们来看下NSInteger的头文件

blob.png

在32下设备是Int 在64位是long

我们都知道苹果不允许不支持64位的app上架 但是貌似我们从来没有为32位和64位做适配

其实不然 在printf 和NSLog 时 对应%d %zd %f 占位符是非常严格的 如果不对项目就会造成意外的结果

blob.png

blob.png

其实拿到一个NSNumber我们并不知道他到底是int long unsigned int Bool 直接针对某个类型转换是有风险的 但是其实Clang 给我们提供了个非常好用的Macro @()

blob.png

NSNumber并不是一个简单的类 它是cocoa 中 类簇的实现参考资料

http://www.cocoachina.com/ios/20140109/7681.html

http://www.cocoachina.com/ios/20150106/10848.html

http://www.cocoachina.com/ios/20141218/10688.html

posted @ 2015-09-08 13:19  cc412  阅读(204)  评论(0编辑  收藏  举报