美文网首页iOSiOSiOS
寒哥教你学iOS - 经验漫谈

寒哥教你学iOS - 经验漫谈

作者: 南栀倾寒 | 来源:发表于2015-08-22 15:43 被阅读9551次
  • 顺便来个广告

iOS开发者 群532084214 给大家提供一个交流技术 也可以聊天打屁的平台

  • 本篇文章主要讲解 4个问题
  1. load妙用
  2. aop面向切面编程
  3. NSNumber Or Int
  4. @()适配64位

1 让appDelegate 减少负担

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

用着自己的半吊子水平 papapa的 coding 决心一定要把代码封装好 写的漂亮 (其实是听大神说 封装 其实自己不太懂)
项目到了尾声 老板告诉你我们的app 我们的app 将来得来个分享到朋友圈的功能吧 不然怎么体现我产品的牛逼
然后你听说友盟比较好使(有广告的嫌疑) 你去友盟看了他们的文档 他告诉你你要在 appdelegate didFinishLaunch方法里面写了这个东西

 [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 写了这段话

Key Features

  1. CODELESS, Zero Line Of Code 不需要写任何代码
  1. Works Automatically //自动工作
  1. No More UIScrollView //不需要scrollview
  1. No More Subclasses //不需要继承父类
  1. No More Manual Work //不需要配置
  1. No More #imports //不需要导入

其实不神奇 只是大牛用了 + load这个方法
学习OC都知道这个代码会在一个类被加载到运行库中就会被自动调用 这不就实现了 自动调用

写一个类继承自NSObject

#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

类似于定位也可以这样写

Paste_Image.png

模块和服务完全拆开

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


2 ViewController继承?

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

Paste_Image.png

友盟的这样写 对于新手的我们就觉得这不就so easy吗
我打开了某个vc(HomeViewController)
在代码里面写上了这句

-(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)的思想

上代码

#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

图片代码一份 方便观看

Paste_Image.png
Paste_Image.png

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

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

黑魔法非毒药 遵守一个规范写出来的代码是不会Crash的 只要能帮我们解决问题就是好东西
黑魔法性能 有瓶颈? 都到runtime的底层了 你还担心有瓶颈 少年安心使用就好了 不服 可以用Time Profiel测试
黑魔法也非万能 像 我们在导航控制器要封装手势 统一管理左侧返回按钮 这些东西 还是继承来得好

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


                                 华丽的分割线

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

下面看两个方法

Paste_Image.png
 + (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 展示到页面
    我见过这样给页面上赋值的朋友
Paste_Image.png

我们看到这样貌似并没有什么不妥
但是我们把设备切换到iPhone5S以下 也就是32位的设备

Paste_Image.png

注意这里有Warning
为什么呢 我们来看下NSInteger的头文件

Paste_Image.png

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

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

Paste_Image.png Paste_Image.png

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

Paste_Image.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


最后再来个广告 受到 公众号主人邀请 我的文章也会被发布到这个公众号

                        ** 加个欢迎微信扫码关注吧**
Paste_Image.png

相关文章

网友评论

  • SpursGo:很赞
  • 神秘用户的蜕变:这货很干,支持 :+1:
  • 指尖猿:自己在开始学网络线程了 看的不是一般的吃力
    南栀倾寒:@指尖猿 什么事都顺心 你在原地踏步
  • 清水昏昏:小白学习了, :relaxed: :relaxed:
  • Scott_Mr:貌似黑魔法那个navigationViewController,UIViewController都会调用一次的
    南栀倾寒:@Scott_Mr 统计的是self 对于一个控制器带导航控制器 会统计 导航+1 self +1 但是 打个比方 但是统计这个东西 我们只关心页面 没有人关心导航栏 所以这个在数据层是无意义 也不会影响真正需要统计的部分
    Scott_Mr:@南栀倾寒 那这样的话统计,不是会出现进入一个带导航栏的控制器,会加2
    南栀倾寒:@Scott_Mr 所有vc都会调用
  • _____柠檬:关于 swizzleMethod 那里。
    如果使用category不是能实现一样的功能么?
    南栀倾寒:@柠檬沟酸奶 category 能掉super吗 能拦截吗?你可以试试
  • edison0951:想请问下楼主,关于AOP那个地方,我把那些通用逻辑写到BaseVC的坏处是什么呢?
    南栀倾寒:@edison0951 文中说了 会造成基类很多 比如有时候忘记继承 还有这种统计 都不算业务 只能算服务 没必要继承 等到你需要自定义页面的操作时再使用
  • 无名氏_1:请教一个问题 :设计方面有时候有这样的需求 某某view的上下边框是一条灰色的线 , 或者 某个文字前边后边有一条 长60 高 1的 红线 。
    这个时候就有点尴尬 之前 我是用一个view 填充背景色来做, 再后来 我是在drawRect 方法里边 直接用CG 绘制 , 不知道还有没有更好的方法呢?
    木木烈少:@姚宁 也可以用CALayer来做哈。在drawRect里面绘制的话如果不想每次都重绘,就用个变量来记录是否需要变化,然后再绘制吧
    无名氏_1:也是在drawRect 里边画的吗?
    但是每次需要重新绘制view的时候 都会执行一次 不知道这样好不好呀
    南栀倾寒:@姚宁 然而我也是这样做的
    哈哈
  • 街灯下de橱窗:虽然文中有的地方还不太懂,谢谢分享
  • 杏仁丶:还是个小菜鸟,不过感觉这些很实用,学习了
  • fcdbb524fa75:关于第1点 补充一下,launch option 的话有一个 UIApplicationDidFinishLaunchingNotification , 可以取到。同时由于load执行的比didlaunch早, 所以在只要在load里面注册接收这个消息的话,就可以进行响应了, 不用写在appdelegate。
    南栀倾寒:@天狼星online 还可以考虑加到分类 完全减压了 哈哈
  • _白丁_:算了、你专门开一个iOS相关的微博吧! :clap:
  • _白丁_:叼叼叼!看到的人还是太少...发微博上。
  • 伯恩的遗产:非常实用 :heart_eyes:
  • hhhhxy:我想问一下,为什么在aop_viewDidLoad方法里面仍然要调用一次 [self aop_viewDidLoad];求解。
    hhhhxy:@南栀倾寒 sorry,还有一篇博文没看到,现在懂了,谢谢啊!
    hhhhxy:@南栀倾寒 看了一下,还是不太明白,“swizzle 一个方法就是改变类的分发表”,如果说aop_viewDidLoad已经替换了UIViewController中的viewDidLoad,那么为啥还要调用 [self aop_viewDidLoad];这个方法呢,或者调用的这个方法不是aop_viewDidLoad?
    南栀倾寒:@iOS_huangxy 我在文章中补了几个链接 你看下
  • bb64f1afd431:求教下,最近在整本地数据持久化,用sqlite,框架是fmdb,我创建了子线程专门处理从资源文件将数据插入数据库,也创建了FMDataBaseQueue的单例来处理,但是还是导致数据库被锁。难道是因为数据量比较大的原因吗?5个关联表,最多一个30000条左右
    南栀倾寒:@fanatic 去objcio.cn看文章 有篇讲的不错 具体这么大的数据量我也没测过
  • 叶舞清风:完全看不懂啊
    _白丁_:@叶舞清风 开个玩笑.. :joy:
    _白丁_:@叶舞清风 你是🐷么...?
    南栀倾寒:@叶舞清风 。。。
  • 6fdb0c58ceca:略屌,期待下一篇
    南栀倾寒:@碧野MAX 这一篇我还没写完呢
  • 98a4bd98f323:干货先马
  • 何予風語:先顶再看

本文标题:寒哥教你学iOS - 经验漫谈

本文链接:https://www.haomeiwen.com/subject/ijyzfttx.html