一、寻找最近公共View

我们将一个路径中的所有点先放进 NSSet 中。因为 NSSet 的内部实现是一个 hash 表,所以查找元素的时间复杂度变成了 O(1),我们一共有 N 个节点,所以总时间复杂度优化到了 O(N)。

+ (UIView *)commonView_2:(UIView *)viewA andView:(UIView *)viewB {
   NSArray *arr1 = [self superViews:viewA];
   NSArray *arr2 = [self superViews:viewB];
   NSSet *set = [NSSet setWithArray:arr2];
   for (NSUInteger i = 0; i < arr1.count; ++i) {
       UIView *targetView = arr1[i];
       if ([set containsObject:targetView]) {
           return targetView;
       }
   }
   return nil;
}

除了使用 NSSet 外,我们还可以使用类似归并排序的思想,用两个「指针」,分别指向两个路径的根节点,然后从根节点开始,找第一个不同的节点,第一个不同节点的上一个公共节点,就是我们的答案。代码如下:

/* O(N) Solution */
+ (UIView *)commonView_3:(UIView *)viewA andView:(UIView *)viewB {
   NSArray *arr1 = [self superViews:viewA];
   NSArray *arr2 = [self superViews:viewB];
   NSInteger p1 = arr1.count - 1;
   NSInteger p2 = arr2.count - 1;
   UIView *answer = nil;
   while (p1 >= 0 && p2 >= 0) {
       if (arr1[p1] == arr2[p2]) {
           answer = arr1[p1];
       }
       p1--;
       p2--;
   }
   return answer;
}

二、什么时候在block中不需要使用weakSelf

我们知道,在使用 block 的时候,为了避免产生循环引用,通常需要使用 weakSelf 与 strongSelf,写下面这样的代码:

__weak typeof(self) weakSelf = self;
[self doSomeBlockJob:^{
   __strong typeof(weakSelf) strongSelf = weakSelf;
   if (strongSelf) {
       ...
   }
}];

答案

当 block 本身不被 self 持有,而被别的对象持有,同时不产生循环引用的时候,就不需要使用 weak self 了。最常见的代码就是 UIView 的动画代码,我们在使用 UIView 的 animateWithDuration:animations 方法 做动画的时候,并不需要使用 weak self,因为引用持有关系是:

  • UIView 的某个负责动画的对象持有了 block
  • block 持有了 self

因为 self 并不持有 block,所以就没有循环引用产生,因为就不需要使用 weak self 了。

[UIView animateWithDuration:0.2 animations:^{
   self.alpha = 1;
}];

当动画结束时,UIView 会结束持有这个 block,如果没有别的对象持有 block 的话,block 对象就会释放掉,从而 block 会释放掉对于 self 的持有。整个内存引用关系被解除。

三、为什么weakSelf需要配合strongSelf使用

我们知道,在使用 block 的时候,为了避免产生循环引用,通常需要使用 weakSelf 与 strongSelf,写下面这样的代码:

__weak typeof(self) weakSelf = self;
[self doSomeBackgroundJob:^{
   __strong typeof(weakSelf) strongSelf = weakSelf;
   if (strongSelf) {
       ...
   }
}];

那么请问:为什么 block 里面还需要写一个 strong self,如果不写会怎么样?

答案

在 block 中先写一个 strong self,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。

我们以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代码举例:

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
   __strong __typeof(weakSelf)strongSelf = weakSelf;    strongSelf.networkReachabilityStatus = status;
   if (strongSelf.networkReachabilityStatusBlock) {
       strongSelf.networkReachabilityStatusBlock(status);
   } };

如果没有 strongSelf 的那行代码,那么后面的每一行代码执行时,self 都可能被释放掉了,这样很可能造成逻辑异常。

特别是当我们正在执行 strongSelf.networkReachabilityStatusBlock(status); 这个 block 闭包时,如果这个 block 执行到一半时 self 释放,那么多半情况下会 Crash。

这里有一篇文章详细解释了这个问题:https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/

昨天的读者中,拓荒者 和 陈祥龙 同学在评论中也正确回答出了本题。

另外,还有读者提了两个有意思的问题,大家可以思考一下:

  • Yuen 提问:“数组” 和 “字典” 的 enumeratXXXUsingBlock: 是否要使用 weakSelf 和 strongSelf 呢?

  • 潇湘雨同学提问:block 里 strong self 后,block 不是也会持有 self 吗?而 self 又持有 block ,那不是又循环引用了?

四、block什么时候需要构造循环引用

需要不使用 weak self 的场景是:你需要构造一个循环引用,以便保证引用双方都存在。比如你有一个后台的任务,希望任务执行完后,通知另外一个实例。在我们开源的 YTKNetwork 网络库的源码中,就有这样的场景。

在 YTKNetwork 库中,我们的每一个网络请求 API 会持有回调的 block,回调的 block 会持有 self,而如果 self 也持有网络请求 API 的话,我们就构造了一个循环引用。虽然我们构造出了循环引用,但是因为在网络请求结束时,网络请求 API 会主动释放对 block 的持有,因此,整个循环链条被解开,循环引用就被打破了,所以不会有内存泄漏问题。代码其实很简单,如下所示:

//  YTKBaseRequest.m
- (void)clearCompletionBlock {
   // nil out to break the retain cycle.
   self.successCompletionBlock = nil;
   self.failureCompletionBlock = nil;
}

总结来说,解决循环引用问题主要有两个办法:

  • 第一个办法是「事前避免」,我们在会产生循环引用的地方使用 weak 弱引用,以避免产生循环引用。
  • 第二个办法是「事后补救」,我们明确知道会存在循环引用,但是我们在合理的位置主动断开环中的一个引用,使得对象得以回收。

这篇文章通过一步步指导,教你彻底学会『iOS应用间相互跳转』问题。文末有Github的学习Demo。

1. 应用间相互跳转简介

在iOS开发的过程中,我们经常会遇到需要从一个应用程序A跳转到另一个应用程序B的场景。这就需要我们掌握iOS应用程序之间的相互跳转知识。

下面来看看我们在开发过程中遇到的应用场景。

2. 应用间相互跳转应用场景

  1. 使用第三方用户登录,跳转到需授权的App。如QQ登录,微信登录等。

    • 需要用户授权,还需要"返回到调用的程序,同时返回授权的用户名、密码"。

  2. 应用程序推广,跳转到另一个应用程序(本机已经安装),或者跳转到iTunes并显示应用程序下载页面(本机没有安装)。

  3. 第三方支付,跳转到第三方支付App,如支付宝支付,微信支付。

  4. 内容分享,跳转到分享App的对应页面,如分享给微信好友、分享给微信朋友圈、分享到微博。

  5. 显示位置、地图导航,跳转到地图应用。

  6. 使用系统内置程序,跳转到打电话、发短信、发邮件、Safari打开网页等内置App中。

那么我们如何实现应用间的相互跳转呢?先来看下原理。

3. 应用间相互跳转实现原理

在iOS中打开一个应用程序只需要拿到这个应用程序的协议头即可,所以我们只需配置应用程序的协议头即可。

假设有应用A应用B两个应用,现在需要从应用A跳转到应用B中。

  • 原理:通过设置跳转到应用B的URL Schemes(自定义的协议头),应用B将其自身“绑定”到一个自定义URL Schemes上,就可以从应用A中利用应用B的URL Schemes启动应用B了。

具体怎么做呢,下面一步步来教你,先来个简单点的:从应用A跳转到应用B。

4. 应用A跳转到应用B

  1. 首先我们用Xcode创建两个iOS应用程序项目,项目名称分别为App-A、App-B。

  2. 选择项目App-B -> TARGETS -> Info -> URL Types -> URL Schemes,设置App-B的URL Schemes为AppB。

    设置App-B的URL Schemes
  3. 在应用程序App-A中添加一个用来点击跳转的Button,并监听点击事件,添加跳转代码。

    添加跳转按钮
    - (IBAction)jumpToAppB:(id)sender {
       // 1.获取应用程序App-B的URL Scheme
       NSURL *appBUrl = [NSURL URLWithString:@"AppB://"];    // 2.判断手机中是否安装了对应程序
       if ([[UIApplication sharedApplication] canOpenURL:appBUrl]) {
           // 3. 打开应用程序App-B
           [[UIApplication sharedApplication] openURL:appBUrl];
       } else {
           NSLog(@"没有安装");
       }
    }
  4. 如果是iOS9之前的模拟器或是真机,那么在相同的模拟器中先后运行App-B、App-A,点击按钮,就可以实现跳转了。

  5. 如果是iOS9之后的模拟器或是真机,那么则需要再在应用程序App-A中将App-B的URL Schemes添加到白名单中,原因和做法如下。

    • iOS9引入了白名单的概念。

    • 在iOS9中,如果使用 canOpenURL:方法,该方法所涉及到的 URL Schemes 必须在"Info.plist"中将它们列为白名单,否则不能使用。key叫做LSApplicationQueriesSchemes ,键值内容是对应应用程序的URL Schemes。

具体做法就是在App-A的Info文件中,添加LSApplicationQueriesSchemes数组,然后添加键值为AppB的字符串。

添加LSApplicationQueriesSchemes数组,然后添加键值为AppB的字符串

添加白名单之后在相同的模拟器中先后运行App-B、App-A,点击按钮,就可以实现跳转了。

具体效果如下图所示。

App-A跳转到App-B

下边学习以下从应用A跳转到应用B的特定界面。

5. 应用A跳转到应用B的特定界面

很多时候,我们做应用程序之间的跳转并不只是跳转到其他程序就可以了,而是要跳转到其他程序的特定页面上。比如我们在浏览网页时,会有分享到微信朋友圈或是分享给微信朋友,这就需要跳转到微信朋友圈界面或是微信朋友选择界面。

具体如何做呢?

  1. 首先我们先来为App-B搭建两个页面Page1Page2。这里用导航控制器Push两个ViewController,通过Storyboard Segue设置两个ViewController的标识符绑定,分别为"homeToPage1"和"homeToPage2"。

    搭建两个页面Page1Page2

    设置Page1ViewController的标识符
  2. 在应用程序App-A中添加两个用来点击跳转的Button,一个跳转到Page1,一个跳转到Page2,并监听点击事件,添加跳转代码。

添加两个跳转页面按钮
- (IBAction)jumpToAppBPage1:(id)sender {
    // 1.获取应用程序App-B的Page1页面的URL
    NSURL *appBUrl = [NSURL URLWithString:@"AppB://Page1"];     // 2.判断手机中是否安装了对应程序
    if ([[UIApplication sharedApplication] canOpenURL:appBUrl]) {
        // 3. 打开应用程序App-B的Page1页面
        [[UIApplication sharedApplication] openURL:appBUrl];
    } else {
        NSLog(@"没有安装");
    }
} - (IBAction)jumpToAppBPage2:(id)sender {
    // 1.获取应用程序App-B的Page2页面的URL
    NSURL *appBUrl = [NSURL URLWithString:@"AppB://Page2"];     // 2.判断手机中是否安装了对应程序
    if ([[UIApplication sharedApplication] canOpenURL:appBUrl]) {
        // 3. 打开应用程序App-B的Page2页面
        [[UIApplication sharedApplication] openURL:appBUrl];
    } else {
        NSLog(@"没有安装");
    }
}

3.在应用App-B中通过AppDelegate监听跳转,进行判断,执行不同页面的跳转

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    // 1.获取导航栏控制器
    UINavigationController *rootNav = (UINavigationController *)self.window.rootViewController;
    // 2.获得主控制器
    ViewController *mainVc = [rootNav.childViewControllers firstObject];     // 3.每次跳转前必须是在跟控制器(细节)
    [rootNav popToRootViewControllerAnimated:NO];        // 4.根据字符串关键字来跳转到不同页面
    if ([url.absoluteString containsString:@"Page1"]) { // 跳转到应用App-B的Page1页面
        // 根据segue标示进行跳转
        [mainVc performSegueWithIdentifier:@"homeToPage1" sender:nil];
    } else if ([url.absoluteString containsString:@"Page2"]) { // 跳转到应用App-B的Page2页面
        // 根据segue标示进行跳转
        [mainVc performSegueWithIdentifier:@"homeToPage2" sender:nil];
    }        return YES;
}

具体效果如下:

App-A跳转到App-B的特定界面

6.从应用B跳转回应用A

1. 步骤分析:

  1. 我们想要从应用B再跳转回应用A,那么在跳转到应用B的时候,还应将应用A的URL Schemes传递过来。这样我们才能判断应该跳转回哪个应用程序。

    • 这样我们指定一个传递URL的规则:协议头://应用B的URL Schemes?应用A的URL Schemes。即:AppB://Page1?AppA

    • 说明:

      • AppB是跳转过来的应用App-B的URL Schemes;

      • Page1是用来区别跳转页面的标识;

      • ? 是分割符;

      • AppA是跳转回的应用App-A的URL Schemes

  2. 我们根据传递来的数据,进行反跳回去。

    1. 之前我们在应用App-B中通过AppDelegate执行不同页面的跳转。在对应方法中我们可以拿到完整的URL,在主控制器ViewController中设定一个属性,将该URL保存在主控制器中。

    2. 在主控制器中我们可以通过- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;方法获取将要跳转的页面控制器。

    3. 在将要跳转的页面控制器中定义一个属性,用于接受、截取出跳转回的应用(即App-A)的URL Schemes,执行跳转。

2. 具体步骤:

1. 准备步骤:
  1. 因为我们想要跳转回应用A,首先我们要先设置应用App-A的URL Schemes,将其设置为AppA。同时在应用App-B中添加白名单。具体操作和之前相似。

  2. 在App-B项目中的Page1和Page2两个页面各添加一个Button,用于跳转回App-A。同时添加Page1和Page2的页面控制器Page1ViewController和Page2ViewController。

添加Page1和Page2的页面控制器Page1ViewController和Page2ViewController
2. 实现步骤
  1. 在App-A中修改传递的URL。

    • 分别修改为:@"AppB://?AppA"@"AppB://Page1?AppA"@"AppB://Page2?AppA"

  2. 在App-B的主控制器ViewController中增加一条属性@property (nonatomic, copy) NSString *urlString;,并在App-B中通过AppDelegate中保存完整的URL。

  3. 在将要跳转的页面控制器Page1ViewController和Page2ViewController中定义一个属性@property (nonatomic, copy) NSString *urlString;,用于接受、截取出跳转回的应用(即App-A)的URL Schemes,执行跳转。

  4. 重写App-B的主控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
       if ([segue.identifier isEqualToString:@"homeToPage1"]) {
           // 获得将要跳转的界面Page1的控制器
           Page1ViewController *Page1Vc = segue.destinationViewController;
           // 保存完整的App-A的URL给跳转界面Page1
           Page1Vc.urlString = self.urlString;
       } else if ([segue.identifier isEqualToString:@"homeToPage2"]) {
           // 获得将要跳转的界面Page2的控制器
           Page2ViewController *Page2Vc = segue.destinationViewController;
           // 保存完整的App-A的URL给跳转界面Page1
           Page2Vc.urlString = self.urlString;
       }
    }
  5. 在对应界面控制器Page1ViewController和Page2ViewController中实现跳转代码
    - Page1ViewController.m

    - (IBAction)page1BackToAppA:(id)sender {
       // 1.拿到对应应用程序的URL Scheme
       NSString *urlSchemeString = [[self.urlString componentsSeparatedByString:@"?"] lastObject];
       NSString *urlString = [urlSchemeString stringByAppendingString:@"://"];    // 2.获取对应应用程序的URL
       NSURL *url = [NSURL URLWithString:urlString];    // 3.判断是否可以打开
       if ([[UIApplication sharedApplication] canOpenURL:url]) {
           [[UIApplication sharedApplication] openURL:url];
       }
    }

    - Page2ViewController.m

    - (IBAction)page2BackToAppA:(id)sender {
       // 1.拿到对应应用程序的URL Scheme
       NSString *urlSchemeString = [[self.urlString componentsSeparatedByString:@"?"] lastObject];
       NSString *urlString = [urlSchemeString stringByAppendingString:@"://"];    // 2.获取对应应用程序的URL
       NSURL *url = [NSURL URLWithString:urlString];    // 3.判断是否可以打开
       if ([[UIApplication sharedApplication] canOpenURL:url]) {
           [[UIApplication sharedApplication] openURL:url];
       }
    }

具体效果如下:

App-B跳转回App-A

还不太明白可参考下我的Github上Demo地址:YSC-AppAJumpToAppB

iOS开发技巧的更多相关文章

  1. iOS开发技巧系列---详解KVC(我告诉你KVC的一切)

    KVC(Key-value coding)键值编码,单看这个名字可能不太好理解.其实翻译一下就很简单了,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值.而不需 ...

  2. 【转】几点 iOS 开发技巧

    [译] 几点 iOS 开发技巧 原文:iOS Programming Architecture and Design Guidelines 原文来自破船的分享 原文作者是开发界中知晓度相当高的 Mug ...

  3. 几点iOS开发技巧

    转自I'm Allen的博客   原文:iOS Programming Architecture and Design Guidelines   原文来自破船的分享   原文作者是开发界中知晓度相当高 ...

  4. iOS开发技巧系列---使用链式编程和Block来实现UIAlertView

    UIAlertView是iOS开发过程中最常用的控件之一,是提醒用户做出选择最主要的工具.在iOS8及后来的系统中,苹果更推荐使用UIAlertController来代替UIAlertView.所以本 ...

  5. iOS开发技巧 -- 复用代码片段

    如果你是一位开发人员在开发过程中会发现有些代码无论是在同一个工程中还是在不同工程中使用率会很高,有经验的人会直接封装在一个类里,或者写成一个宏定义或者把这些代码收集起来,下次直接使用,或者放到xcod ...

  6. iOS开发技巧 - Size Class与iOS 8多屏幕适配(一)

    0. 背景: 在iOS开发中,Auto Layout(自动布局)能解决大部分的屏幕适配问题. 但是当iPhone 6和iPhone 6 Plus发布以后, Auto Layout已经不能解决复杂的屏幕 ...

  7. iOS 开发技巧收藏贴 链接整理

    54个技巧 57个技巧 正则表达式

  8. iOS开发技巧-2

    1,打印View所有子视图 po [[self view]recursiveDescription] 2,layoutSubviews调用的调用时机 * 当视图第一次显示的时候会被调用 * 当这个视图 ...

  9. IOS开发技巧快速生成二维码

    随着移动互联网的发展,二维码应用非常普遍,各大商场,饭店,水果店 基本都有二维码的身影,那么ios中怎么生成二维码呢? 下面的的程序演示了快速生成二维码的方法: 在ios里面要生成二维码,需要借助一个 ...

随机推荐

  1. pod install出现[!] /bin/bash -c错误,Installing Realm报错

    pod install出现错误,具体错误信息如下: Installing Realm () [!] /bin/bash -c set -e sh build.sh cocoapods-setup co ...

  2. cxf client在后台不通且chunk设置为false的时候不能在控制台输出请求日志

    场景: 服务编排框架支持编排webservice服务.call webservice的client是基于cxf做的.为了使用服务编排的开发者调试与定位问题方便,需要将webservice的请求与响应报 ...

  3. Linux安装vim失败的解决办法

    最近想了解一下linux编程,于是linux系统下输入vim,发现竟然没有安装.好吧,那就安装吧.命令: sudo apt-get install vim 百度百科:apt-get是一条linux命令 ...

  4. 产品Backlog

    产品BACKLOG ID Name Imp Est How to demo Notes 1 界面(首页.订单.资料) 50 2 进入界面,选择需要的界面 使用分栏界面 2 首页里的功能按钮 40 6 ...

  5. !! 据说年薪30万的Android程序员必须知道事

    http://www.th7.cn/Program/Android/201512/742423.shtml Android中国开发精英 目前包括: Android开源项目第一篇——个性化控件(View ...

  6. 关注微信 即可连上wifi 的设计思路

    这个功能之前是在知乎上有人在询问后台的实现逻辑,然后才知道的。其实对微信的各种关注,实在是不想沾惹。 但是这个功能很有意思,当我关注了你,那么就可以在你的店里上wifi 。如果取消则立刻不能上网。 这 ...

  7. SQL Server数据库性能优化(三)之 硬件瓶颈分析

    参考文献 http://isky000.com/database/mysql-performance-tuning-hardware 由于对DBA 工作了解不多    所以只从网上简单的看了下  硬件 ...

  8. JSP-10-JSTL标准标签库

    JSTL (jsp 标准标签库) 包含用于编写和开发JSP页面的一组标准标签,它可为用户提供一个无脚本的环境. JSTL 提供了4个主要的标签库: 核心标签库.国际化(I18N)与格式化标签库.XML ...

  9. .net环境下ckeditor与ckfinder中文文件链接乱码的问题

    .net环境下ckeditor与ckfinder中文文件链接乱码的问题 将ckfinder.js中的getUrl:function(){return this.folder.getUrl()+enco ...

  10. 设置Ubuntu 14.04右键终端的方法

    设置Ubuntu 14.04右键终端的方法如下: 首先要安装一个包,即可在右键里面添加一个"打开终端"的菜单. sudo apt-get install nautilus-open ...