4.加密&webView & OC与JS互调

People Lack Willpower,Rather Than Strength!

1.MD5加密

  • 背景: 传统的GET/POST请求提交用户的隐私数据,不能解决安全问题. 利用软件(例如:Charles)设置代理服务器,拦截查看手机数据. 因此, 提交用户的隐私数据时,一定不要明文提交,要加密处理后再提交.
  • 常见的加密算法: MD5 \ SHA \ DES \ 3DES \ RC2和RC4 \ RSA \ IDEA \ DSA \ AES.
  • 加密算法的选择: 一般公司都会有一套自己的加密方案.
  • 什么是MD5?
    • Message Digest Algorithm 5,译为“消息摘要算法第5版”;
    • 对输入信息生成唯一的128位散列值(32个字符).
  • MD5特点:
    • 输入两个不同的明文不会得到相同的输出值
    • 根据输出值,不能得到原始的明文,即其过程不可逆.
  • MD5应用:

    • 由于MD5加密算法具有较好的安全性,而且免费,因此该加密算法被广泛使用.
    • 主要应用在:数字签名,文件完整性验证和口令加密方面.
    • 在注册/登录上的应用.


      1444030883349.png
  • 使用前,需要先导入框架

  • 为什么要导入框架?

    • 由于Apple 提供给我们的是C 语言API, 并不好用.
  • 主要内容

    • 简单加密
    • 加盐
    • 双重加密
    • 注意点: MD5加密是不可逆的.
    • 代码示例:
// 先导入框架
#import "NSString+Hash.h"
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)
    // c语言的,不好用,我们使用框架
    // 1.常规输出
    NSString *string = @"testString";
    NSLog(@"%@",string);
    /*2015-09-10 16:15:53.857 01-MD5加密[2435:144621] testString
     */

    // 2.简单md5加密输出
    NSLog(@"%@",string.md5String);
    /*2015-09-10 16:16:41.657 01-MD5加密[2463:145280] 536788f4dbdffeecfbb8f350a941eea3
    */

    // 3.加盐输出salt,在原pwd基础上自己额外添加字符串
    string = @"testString+PJ";
    NSLog(@"%@",string.md5String);
    /*2015-09-10 16:21:04.357 01-MD5加密[2509:147089] 799ebd673b9ac12ff429aa66ef102eff
     */

    // 4.双重加密
    NSLog(@"%@",string.md5String.md5String);
    /* 2015-09-10 16:22:20.985 01-MD5加密[2536:147930] 504650a386e5a98086271fe8e8a9f6c4
     */
}
  • 网上随意一个网站, 我们可以看到MD5破解,其实质无外乎,就是收集足够多的md5值
md5破解.png
  • 加盐后,亦然可以破解


    md5加盐后还是可以破解.png

2.HTTPs

  • 背景: 从安全考虑, 在iOS9 beta1中,苹果将原http协议改成了https协议, 所以访问https网站时, 我们需要对网站返回的证书做处理.

  • 注意点: TLS1.2 SSL加密请求数据

    • 访问加密个人服务器(即https://www.xxx.com网站), 可能访问不到,因为个人服务器使用证书版本低,我们在xcode中即使使用https方式访问也没用.需要做如下配置:
  • 模拟器连不上自己服务器,因为个人服务器SSL加密方法还是TLS1.1版本.需要在Xcode的info.plist中添加对应的键值对.


    1444032745764.png
  • 原理


    https.png
  • 自己安装证书

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 创建request
//    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.apple.com/"]];

    // 1.创建session会话--监听请求过程
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]                                   delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    // 2.根据session创建请求任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest: request];
    // 3.Task resume
    [task resume];
}

#pragma mark - NSURLSessionDataDelegate
// 服务器响应时调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    // 表示接受服务器返回数据
    completionHandler(NSURLSessionResponseAllow);
    NSLog(@"didReceiveResponse");
}
// 服务器返回数据时调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    NSLog(@"didReceiveData");
}
// 完成数据下载时调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"didCompleteWithError");
}
/*
 对于常规网站,不需要我们按照证书,直接反馈请求:例如www.baidu.com
 2015-09-10 16:43:12.992 02-HTTPs[2707:158966] didReceiveResponse
 2015-09-10 16:43:12.992 02-HTTPs[2707:158966] didReceiveData
 2015-09-10 16:43:12.992 02-HTTPs[2707:158966] didReceiveData
 2015-09-10 16:43:12.992 02-HTTPs[2707:158966] didCompleteWithError

 对于需要按照证书的网站,我们发送请求时,如果没有安装证书,则如下结果:12306
 2015-09-10 16:45:20.908 02-HTTPs[2740:160452] NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
 2015-09-10 16:45:20.909 02-HTTPs[2740:160342] didCompleteWithError
 */
/*=========对于HTTPs网站,当需要我们安装证书时,我们要如下操作=========*/
// 只要访问的是HTTPS的路径就会调用
// 该方法的作用就是处理服务器返回的证书, 需要在该方法中告诉系统是否需要安装服务器返回的证书

// NSURLAuthenticationChallenge : 授权问询
//     + 受保护空间
//           + 服务器返回的证书类型
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
     // 1.判断服务器返回的受保护空间里的证书是否是服务器信任的;(服务器自己返回的证书,自己怎么会不信任那???😖)
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {   
       // 2.根据服务器返回的受保护空间里的证书类型创建一个证书
       NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
       // 3.安装证书
       completionHandler( NSURLSessionAuthChallengeUseCredential,credential);
       /*
        (NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
        第一个参数:表示如何处理证书;
        第二个参数:表示要处理哪个证书;
        */
    }
}

  • 使用AF提供方法
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    // AFN框架中有现成...改改就好了
    // 在框架中搜NSURLSessionAuthChallengeDisposition可以找到类似如下代码:
    // 稍作改动就和上面自己写的一样了:
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
      credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
       if (credential) {
           disposition = NSURLSessionAuthChallengeUseCredential;
       } else {
           disposition = NSURLSessionAuthChallengePerformDefaultHandling;
       }

    } else
    {
       disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

3.UIWebView

  • 什么是UIWebView

    • UIWebView是iOS内置的浏览器控件
  • 系统自带的Safari浏览器就是通过UIWebView实现的

    • 注意:webview 会内存泄露,这是苹果的问题,Safari就是使用webview做的!
  • UIWebView不但能加载远程的网页资源,还能加载绝大部分的常见文件

    • html\htm
    • pdf、doc、ppt、txt
    • mp4
    • … …
  • UIWebView常用的加载资源的方法

// 开发趋势,UI+ 网页,因为网页是跨平台的,一次开发一劳永逸!
- (void)viewDidLoad {
    [super viewDidLoad];

    // 2.监控请求过程,需要给webView添加代理
    self.webView.delegate = self;

    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //1. 使用webView发送请求,向服务器申请数据
    // 注意:webview 会内存泄露,这是苹果的问题,Safari就是使用webview做的!
    [self.webView loadRequest:request];

}           
  • 监听OC内部请求和webView内部请求: webView的代理方法
#pragma mark - UIWebViewDelegate
// 重点注意这个方法❤️
// 每次发送请求前,总会来这个方法:
// 如果return YES,表示允许请求;
// 如果return NO,表示不允许请求;
// 专门用来拦截请求的❤️
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSLog(@"%@",request.URL);

    // 我们拦截hao123请求
    NSString *urlStr = request.URL.absoluteString;
    if ([urlStr containsString:@"hao123"]) {
        return NO;
    }

    return YES;

    /*结果可见:
     请求百度首页时,发送了两次请求:
     request1.url --> http://www.baidu.com/
     request2.url --> bout:blank
     2015-09-10 22:30:30.804 03-UIWebView[6747:323693] http://www.baidu.com/
     2015-09-10 22:30:30.805 03-UIWebView[6747:323693] -[ViewController webViewDidStartLoad:]
     2015-09-10 22:30:30.966 03-UIWebView[6747:323693] about:blank
     2015-09-10 22:30:30.966 03-UIWebView[6747:323693] -[ViewController webViewDidStartLoad:]
     2015-09-10 22:30:30.967 03-UIWebView[6747:323693] -[ViewController webViewDidFinishLoad:]
     2015-09-10 22:30:31.131 03-UIWebView[6747:323693] -[ViewController webViewDidFinishLoad:]
     */
}

// 开始加载网页
// 注意:这里监控的不仅仅是我们指定的url请求,还有网页内部的其他请求
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    // webview提供了属性,可以及时监控是否可以前进/后退
    self.goforwardBtn.enabled = self.webView.canGoForward;
    self.backwordBtn.enabled = self.webView.canGoBack; 
    NSLog(@"%s",__func__);
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSLog(@"%s",__func__);

}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    NSLog(@"%s",__func__);

}

/*例如这里:已进入百度首页,打印两次请求,说明除了我们发起的一次请求外,百度首页也做了请求.
 2015-09-10 22:17:26.020 03-UIWebView[6651:317434] -[ViewController webViewDidStartLoad:]
 2015-09-10 22:17:26.202 03-UIWebView[6651:317434] -[ViewController webViewDidStartLoad:]
 2015-09-10 22:17:26.203 03-UIWebView[6651:317434] -[ViewController webViewDidFinishLoad:]
 2015-09-10 22:17:26.384 03-UIWebView[6651:317434] -[ViewController webViewDidFinishLoad:]

 */
  • webView已经提供了前进/回退的方法,直接使用即可.
#pragma mark - 内部控制方法
- (IBAction)goforward:(id)sender {
    [self.webView goForward];
}
- (IBAction)backword:(id)sender {
    [self.webView goBack];
}

- (IBAction)refresh:(id)sender {

    [self.webView reload];
}
  • webView常见功能和常见属性
    • 展示图片

      • 自适应
    • 展示视频

      • 自适应
    • 展示HTML

      • 自适应屏幕
      • 特殊字符自动识别
    • 实例代码

/* 3.展示本地html */
- (void)webViewDisplayMetaHTML
{
    // 2.监控请求过程,需要给webView添加代理
    self.webView.delegate = self;

    // 1.1.url--->加载本地HTML请求
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"test.html" withExtension:nil];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 1.2 设置webView属性
    // 调整webView的contentSize,以适应屏幕
    self.webView.scalesPageToFit = YES;
    // 用于识别网页中特定的信息,例如网址...(号码会被默认是电话号码)
    self.webView.dataDetectorTypes = UIDataDetectorTypeAll;

    //1. 使用webView发送请求,向服务器申请数据
    [self.webView loadRequest:request];
}

/* 2.视频展示*/
- (void)webViewDisplayVideo
{
    // 2.监控请求过程,需要给webView添加代理
    self.webView.delegate = self;

    // 1.1.url--->加载本地HTML请求
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 1.2 设置webView属性
    // 调整webView的contentSize,以适应屏幕
    self.webView.scalesPageToFit = YES;

    //1. 使用webView发送请求,向服务器申请数据
    [self.webView loadRequest:request];
}

/*  1.展示图片*/
- (void)webViewDisplayPicture
{
    // 2.监控请求过程,需要给webView添加代理
    self.webView.delegate = self;

    // 1.1.url--->加载图片请求
    NSURL *url = [NSURL fileURLWithPath:@"/Users/PlwNs/Desktop/Snip20150910_6.png"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 1.2 设置webView属性
    // 调整webView的contentSize,以适应屏幕
    self.webView.scalesPageToFit = YES;

    //1. 使用webView发送请求,向服务器申请数据
    [self.webView loadRequest:request];

}


4.OC与JS互调

  • 关键: 在OC中,通过拿到webView, 给其设置代理,在代理方法中拦截/执行webView的一些事件.
    • 代理方法中只能用来拦截JS/OC中的加载/跳转网页请求(load请求).

      • 从代理方法的名字也可以看出:

        ....shouldStartLoadWithRequest:
        
        (void)webViewDidStartLoad:..
        
        webViewDidFinishLoad:...
        
        didFailLoadWithError:...
        
        //都是和load相关,意味着只有OC/JS中发起加载/跳转网页时,才能来到代理方法.
        
    • 同时可以执行JS代码.

    • 但是不可以监控HTML中的标签事件.

    • 调用OC中无参数的函数

// 这里OC调用JS函数!!!❤️❤️f
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSLog(@"%s",__func__);
    // webView自己监听标签事件依然OK. 当然,我们还可以在OC中调用/拦截一些JS,在OC中执行JS
    // 在该方法中,我们可以调用网页中的JS函数❤️
    // 弹窗-->❤️分号可有可无❤️
    [webView stringByEvaluatingJavaScriptFromString:@"ocShow();"];
    // 打印数字
    NSString *str = [webView stringByEvaluatingJavaScriptFromString:@"sum()"];
    NSLog(@"%@",str);
    
}
// 但是,注意: ⚠️
 网页上的button.onclick事件,OC代理方法中没办法代为执行.
 // ======================网页代码==============
 <head>
    <metacharset="UTF-8">
    <title>毛线网页</title>
    <!--在script中设置调用函数,注意这里只是监控button点击事件,和请求不一样!!❤️-->
    <script>
        function show()
        {
            <!--alert(document.title);-->
            alert(sum());
        }
        function sum()
        {
            return 2+3;
        }
        function ocShow()
        {
            alert(document.title);
        }

    </script>
</head>       
<!-- 网页具体内容-->
<body>
   <!-- 在网页上添加一个按钮-->
   <button style="background:green; height:200px; width:200px" onclick="show()">按钮</button>
</body>
  • JS调用OC方法
    • 单个参数
// HTML中======================
 <head>
       <script>     
           function repost()
           {
           //在html中发送的请求,可以是下载资源,访问网站,调用某个方法...
           // 前缀一定不能大写!系统会默认这里是网址,在请求中将大写转换为小写!❤️ 
           // 这里,注意调用OC的方法名,因为:在HTML中有特殊含义,我们用"_"取代,多个参数用&隔开
               location.href = "request://callWithNumber?1204774094";
           }
       </script>
   </head>   
   <body>
      <button style="background:green; height:200px; width:200px" onclick="repost()">按钮</button>
   </body>

// OC中======================
// 被调用的某个OC方法
- (void)callWithNumber:(NSString *)number
{
    NSLog(@"%@",number);
}

// 在OC中该方法拦截JS请求!❤️
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{   
   // 1⃣️.单个参数
   // 1.1 判断请求中是否有我的标记
   NSString *schem = @"request://";
   NSString *urlStr = request.URL.absoluteString;
   if ([urlStr containsString:schem]) {

       // 1.2 取出请求中标记后部分的字符串
       NSString *methodStr = [urlStr substringFromIndex:schem.length];
       // 1.3 取出方法名字符串和参数
       // 注意: 如果指定的用于切割的字符串不存在, 那么就会返回整个字符串❤️
       NSArray *arr = [methodStr componentsSeparatedByString:@"?"];
       NSString *methodName = [arr firstObject];
       methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];

       // 1.4 生成方法名
       SEL aSelector = NSSelectorFromString(methodName);

       // ❤️考虑到我们在JS中传入的方法可能带参数也可能不带,所以这里需要做个判断
       NSString *para = nil;
       if (arr.count == 2) {
           para = [arr lastObject];
       }
#pragma clang diagnostic push
       // 下面这一行代码是用于指定需要忽略的警告信息
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
       // 1.5 执行带参数方法
       [self performSelector:aSelector withObject:para];
       // 忽略警告信息的作用范围结束
#pragma clang diagnostic pop

       // 1.6 不要忘记,这里拦截完JS请求后,不要让其继续执行,因为他根本就执行不了❤️
       return NO;

       //   2015-09-11 22:22:36.552 03-UIWebView[1485:41469] -[ViewController webViewDidStartLoad:]
       //   2015-09-11 22:22:36.589 03-UIWebView[1485:41469] -[ViewController webViewDidFinishLoad:]
       //   2015-09-11 22:22:38.265 03-UIWebView[1485:41469] 1204774094
   }
}
  • 多个参数 --核心代码
// 同样道理
// HTML===============================
.......
function repost()
{
     location.href = "request://callWithNumber_content_?1204774094&myQQ";
 }

// OC==============================
// 被调用方法
- (void)callWithNumber:(NSString *)number content:(NSString *)content
{
    NSLog(@"number=%@,content=%@",number,content);
}
// 拦截
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{   
    // 2.多个参数
    // 2.1 判断请求中是否有个人标记
    NSString *schem = @"request://";
    NSString *urlStr = request.URL.absoluteString;
    if ([urlStr hasPrefix:schem]) {
        // 2.2 取出方法和参数字符串
        NSString *subPath = [urlStr substringFromIndex:schem.length];
        // 2.2 分割方法名和参数
        NSArray *subPaths = [subPath componentsSeparatedByString:@"?"];
        // 2.3 取出方法名,转换为方法
        NSString *methodName = [subPaths firstObject];
        methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
        SEL aSelector = NSSelectorFromString(methodName); 
        // 2.4 取出参数
        if (subPaths.count == 2) {     
            NSString *para = [subPaths lastObject];
            NSArray *paras = [para componentsSeparatedByString:@"&"];
            NSString *para1 = [paras firstObject];
            NSString *para2 = [paras lastObject];   
            // 2.5执行方法----->可见系统提供的performSelector...方法最多有两个参数!!!需要我们自己写perform...方法
            [self performSelector:aSelector withObject:para1 withObject:para2];
            return NO; 
        }
    }   
}
  • 问题:
    • 从上面两种JS调用OC方法,我们发现[self performSelector:aSelector withObject:para1 withObject:para2];方法, 最多只能同时执行带两个参数的方法. 如果N多参数,怎么办? 下面我们引出NSInvocation 对象, 来解决该问题.

5.NSInvocation

  • 概念:

    • NSInvocation 专门用来包装方法和对应的对象,他可以用来存储方法的名称和对应的对象,参数;❤️
  • 那么,NSInvocation对象如何包装 方法那??
// 需要被包装的方法
- (void)sendMessageWithNumber:(NSString *)number andContent:(NSString *)content status:(NSString *)status
{
   NSLog(@"发信息给%@, 内容是%@, 发送的状态是%@", number, content, status);
} 

// 包装
- (void)invocationUsage
{
    // 0.创建标签
    //  Signature签名: 在创建NSInvocation的时候, 必须传递一个签名对象
    //  签名对象的作用 : 用于获取参数的个数和方法的返回值length/type
    // 注意⚠️这里需要包装谁的方法,就让谁创建标签❤️
    // 注意不要用方法methodSignatureForSelector:创建标签🈲
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:@selector(sendMessageWithNumber:andContent:status:)];

    // 1.创建NSInvocation对象
    // NSInvocation 专门用来包装方法和对应的对象,他可以用来存储方法的名称和对应的对象,参数;❤️
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    // 2.1 设置invocation中保存方法所属对象
    invocation.target = self;
    // 2.3 设置保存的方法
    // 注意:这里的方法名必须和标签中保存的方法名一致!❤️
    invocation.selector = @selector(sendMessageWithNumber:andContent:status:);
    // 2.4 设置参数
    // 第一个参数: 需要给指定方法传递的值
    //           + 第一个参数需要接收一个指针, 也就是传递值的时候需要传递地址
    // 第二个参数: 需要给指定方法的第几个参数传值
    // 注意:方法中的0/1位置分别被隐式变量self和_cmd占据
    NSString *number = @"10086";
    [invocation setArgument:&number atIndex:2];
    NSString *content = @"love";
    [invocation setArgument:&content atIndex:3];
    NSString *status = @"success";
    [invocation setArgument:&status atIndex:4];
    // 3.执行
    // 只要调用invocation的invoke方法, 就代表需要执行 NSInvocation对象中指定对象的指定方法, 并且传递指定的参数
    [invocation invoke];
}
  • 以上,可见执行一个方法甚是麻烦,因此我们需要对整个NSInvocation包装/执行过程封装,以期达到复用效果❤️

  • NSInvocation 的封装

    • 如何封装?
  • 考虑到任何一个对象都应该具有调用多参数方法的能力, 所以我们应该将NSInvocation对象封装到NSObject分类的一个方法中 : performSelector:(SEL)aSelector withArguments:(NSArray *)paras ;
// .h文件
#import <Foundation/Foundation.h>
@interface NSObject (performSelector)
- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
@end

// .m 文件
#import "NSObject+performSelector.h"
@implementation NSObject (performSelector)
- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects
{
    // 0.创建标签
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];  
    // 判断传入方法名是否存在
    // 这里标签根据传入的方法,存储方法中的参数个数和返回值长度/类型,所以如果方法不存在,就得不到标签
    if (signature == nil) {
        // 传入的方法不存在,抛异常
        NSString *info = [NSString stringWithFormat:@"-[%@ %@]:unrecognized selector sent to instance",[self class],NSStringFromSelector(aSelector)];

        NSException *exception = [NSException exceptionWithName:@"方法不存在" reason:info userInfo:nil];

        @throw exception;
    }

    // 1.创建NSInvocation对象
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

    // 2.设置方法所属对象/参数/方法名
    invocation.target = self;
    invocation.selector = aSelector;

    /*
     当前如果直接遍历参数数组来设置参数, 会存在问题
     如果参数数组元素多余参数个数, 那么就会报错
     */
    NSUInteger argCount = signature.numberOfArguments - 2; // 这里记得减去self/_cmd两个参数
    //如果参数的个数大于了参数值的个数, 那么数组会越界

    NSUInteger paraCount = objects.count;
    /*参数和参数值, 谁少就遍历谁*/
    NSUInteger count = MIN(argCount, paraCount);
    for (int i=0; i < count; i++) {
         NSObject *para = objects[i];
        if ([para isKindOfClass:[NSNull class]]) {
            para = nil;
        }
        [invocation setArgument:&para atIndex:2+i];
    }

    // 3.执行invocation
    [invocation invoke];

    // 4.获得返回值
    id res = nil;
    // 为了避免方法没有返回值情况,这里需要判断当前方法是否有返回值
    if (signature.methodReturnLength) {
        [invocation getReturnValue:&res];
    }
    return res;
}
@end

6.捕获异常

  • 对于异常,我们除了可以@throw 之外, 我们还可以使用NSSetUncatchExeptionHandler捕获异常.
  • 注意: webView不行,因为默认情况下,webView是不接受点击事件的.也就是说,我们不能往webView上加控件,然后点击这个控件. 但是webView上的标签控件,还是可以点击的!❤️.
  • 在AppDelegate.m中实现,异常捕获代码
// 异常提示
void handle()
{
    NSLog(@"我捕获异常了");
    [[UIApplication sharedApplication].delegate performSelector:@selector(showError)];
}
- (void)showError
{  
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"对不起" message:@"我自身问题" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; 
    [alert show];

    // 必须要开启一个循环,不然程序崩掉时,主循环是死掉了,那么这个弹窗就做不了了!❤️
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // 1表示非正常退出
    exit(1);
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSSetUncaughtExceptionHandler(handle);
    return YES;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • OC调用JS 首先我们创建一个WebView,加载一个本地的HTML文件 在webview加载完成时-(void)...
    45b645c5912e阅读 1,093评论 2 5
  • http://blog.csdn.net/y550918116j/article/details/49619847...
    F麦子阅读 895评论 2 0
  • 随着H5技术的兴起,在iOS开发过程中,难免会遇到原生应用需要和H5页面交互的问题。其中会涉及方法调用及参数传值等...
    Chris_js阅读 3,060评论 1 8
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,029评论 4 62
  • 因为去台湾的这几天基本上都在下雨╯﹏╰(天气预报说我们回去后晴天的上海也要开始下雨了,朋友都开玩笑让我别回去了……...
    冰镇火药阅读 432评论 1 2