iOS编程重要知识之 load method swizzled

本文主要为您介绍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.请看我下面的分析:

  1. 对于上面的第一种情况的代码:
    参数有pageNo 和pageSize两个参数. 在控制器中拥有这两个对象作为属性,在下拉刷新和或者上拉刷新会传递对应的参数给请求方法,在这两个参数中pageNo参数是必须得,但是pageSize这个参数可能是可有可无的,因为如果你不传递的话,后台服务器会指定一个默认的值, 如10, 而如果我们传递该参数到后台之后,就会覆盖后台服务器的这个默认参数. 其实原因就是在这种情况下, 我们用的是NSNumber作为参数,所以如果我们不传递参数,NSNumber就会默认为nil,这样就不会覆盖后台服务器的 10 的默认参数.

  2. 对于上面的第二种情况的代码:
    在第二种情况中,如果我们不给pageSize赋值的话,基本数据类型pageSize在C 和 Objective-C语言中会被赋予0 . 那么这样的话我们没给pageSize赋值的话传到后台服务器之后就会覆盖默认值,那就让后台设置的默认值失效.

所以我们在使用的请求参数的时候,一定要对基本数据类型说Never

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

  1. 如上文所说在网络请求中使用的好处
  2. 在plist 和NSKeyedArchieve归档时我们都应该使用NSNumber
  3. 基本数据类型的NSInteger 在iPhone5s 以下的手机中的适配问题

这里写图片描述
我们这样看貌似没有什么不妥的,但是如果我们把设备切换到5s以下,也就是32位机. 就会出现如下警告
这里写图片描述

为什么会出现呢? 让我们来看一下NSInteger的头文件
这里写图片描述
解释如下, 在64位机下 NSInteger是 long类型的,但是在32位机下是 int 型,所以会发出类型不匹配的警告.

我们知道苹果不支持64位的app上架, 但貌似我们都没有做过32位与64位的适配.

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

这里写图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值