A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 Simpon 于 2016-10-19 11:35 编辑

运行循环官方示意图
file:///Users/LilMing/Desktop/8%E6%9C%88%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/%E5%B9%BF%E5%B7%9E%E6%A0%A1%E5%8C%BA/images/001-%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF%E7%A4%BA%E6%84%8F%E5%9B%BE.png
基本概念
  • 什么是运行循环?
    • Runloop 是 运行循环 (消息循环)

运行循环(消息循环)的目的
  • 保持程序的 持续运行, 保证 程序不退出
  • 处理 App 运行中的各种事件(比如触摸事件、定时器事件、performSelector)
  • 如果没有 事件发生,程序自动进入 休眠 状态,该做事时做事,该休息时休息,达到 省电 的 目的

运行循环保证程序不退出
  • 新建一个 iOS 工程
  • 运行,发现程序启动,不做任何操作,程序 没有退出
    [Objective-C] 纯文本查看 复制代码
     #import <UIKit/UIKit.h>[/align] #import "AppDelegate.h"
     int main(int argc, char * argv[])
     {
        @autoreleasepool {
            NSLog(@"程序运行了.....");
            int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
            NSLog(@"程序即将退出了...., result = %d", result);
            return result;
        }
     }
  • RunLoop 保持了程序的 持续运行
  • 因为在 UIApplicationMain 自动开启了一个 RunLoop,所以 UIApplicationMain 函数一直没有 返回,保持了程序的 持续运行
  • 这个默认启动的 RunLoop 是跟 主线程 相关联的
  • 没有 运行循环,程序执行完 main 函数就退出了

    • 去掉 UIApplicationMain 代码
      [Objective-C] 纯文本查看 复制代码
      #import <UIKit/UIKit.h>[/align]#import "AppDelegate.h"
      int main(int argc, char * argv[]) {
          @autoreleasepool {
              NSLog(@"程序运行了.....");
              int result = -1;
              NSLog(@"程序即将退出了...., result = %d", result);
              return result;
          }
      }
    • 控制台输出 objc 01.运行循环概念[1538:74836] 程序运行了..... 01.运行循环概念[1538:74836] 程序即将退出了...., result = -1
    • 程序立马退出了
  • RunLoop 实现伪代码
    [Objective-C] 纯文本查看 复制代码
    int main(int argc, char * argv[]) {
        NSLog(@"程序运行了.....");
        BOOL running = YES;
        do {
            // 执行任务,处理事件 
            // 没事件就休眠
        } while (running);
            NSLog(@"程序即将退出了...., result = %d", result);
            return 0;
    }

RunLoop对象
  • iOS中有2套API来访问和使用 RunLoop
    • Foundation
      • NSRunLoop
    • Core Foundation
      • CFRunLoopRef

  • NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象
  • NSRunLoop 是基于 CFRunLoopRef 的 OC 封装
  • RunLoop 官方文档: 搜索 run loop 选择 Getting a Run Loop Object

RunLoop 与线程关系
  • 每条线程都有 唯一 的 一个 与之对应的 RunLoop 对象
  • 主线程的 RunLoop 在应用 启动 的时候就会 自动创建
  • 子线程的 RunLoop 需要 手动创建
  • RunLoop 在第一次获取时 创建 (懒加载),在 线程结束 时 销毁
获取 RunLoop 对象
  • Foundation

    • 获得当前线程的 RunLoop 对象: [NSRunLoop currentRunLoop];
    • 获得主线程的 RunLoop 对象: [NSRunLoop mainRunLoop];
      [AppleScript] 纯文本查看 复制代码
      (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
          // 获取当前线程的RunLoop对象 
          NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
          NSLog(@"currentRunLoop = %@", currentRunLoop);
          // 获取主线程的RunLoop对象 
          NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
          NSLog(@"mainRunLoop = %@", mainRunLoop);
          // 创建一个新的线程 
          [NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
          return YES;
      }
      (void)demo {// 获取当前线程的RunLoop对象, currentRunLoop是懒加载的,只有第一次访问的时候才去创建 
          NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
          NSLog(@"子线程currentRunLoop = %p", currentRunLoop);
      }

运行结果:
01.运行循环概念[1818:115628] currentRunLoop = 0x7fd8d2e04810
01.运行循环概念[1818:115628] mainRunLoop = 0x7fd8d2e04810
01.运行循环概念[1818:115684] 子线程 currentRunLoop = 0x7fd8d2e65300
  • Core Foundation

    • 获得当前线程的RunLoop对象: CFRunLoopGetCurrent();
    • 获得主线程的RunLoop对象: CFRunLoopGetMain();
      • [Objective-C] 纯文本查看 复制代码
        (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { // 获取当前线程的RunLoop对象
            CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
            NSLog(@"currentRunLoop = %@", currentRunLoop);
            // 获得主线程的RunLoop对象 
            CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
            NSLog(@"mainRunLoop = %@", mainRunLoop);
            // 创建一个新的线程 
            [NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
            return YES; }
      • [Objective-C] 纯文本查看 复制代码
        (void)demo {
            // 获取当前线程的RunLoop对象
            CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
            NSLog(@"子线程 currentRunLoop = %p", currentRunLoop);
        }
        

运行结果:
01.运行循环概念[1833:116567] currentRunLoop = 0x7f8f30e054f0
01.运行循环概念[1833:116567] mainRunLoop = 0x7f8f30e054f0
01.运行循环概念[1833:116643] 子线程 currentRunLoop = 0x7f8f30f09950

运行循环模式
  • RunLoop 在某一时刻只能且必须在 一种 特定的 模式 下运行
  • 获取 RunLoop 的模式: [[NSRunLoop currentRunLoop] currentMode];
  • 系统提供了 5个 Mode:

    • kCFRunLoopDefaultMode (NSDefaultRunLoopMode):
      • App 的默认 Mode,通常主线程是在这个 Mode 下运行
    • UITrackingRunLoopMode:
      • 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    • kCFRunLoopCommonModes (NSRunLoopCommonModes):
      • 包含 kCFRunLoopDefaultMode,UITrackingRunLoopMode 两种模式
    • UIInitializationRunLoopMode:
      • 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用(通常用不到)
    • GSEventReceiveRunLoopMode:
      • 接受系统事件的内部 Mode(通常用不到)

  • RunLoop 模式的切换时系统根据 App 的状态自动切换的,不需要程序员干预 file:///Users/LilMing/Desktop/8%E6%9C%88%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/%E5%B9%BF%E5%B7%9E%E6%A0%A1%E5%8C%BA/images/%E8%BF%90%E8%A1%8C%E6%A8%A1%E5%BC%8F.png

运行循环 的输入事件
file:///Users/LilMing/Desktop/8%E6%9C%88%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/%E5%B9%BF%E5%B7%9E%E6%A0%A1%E5%8C%BA/images/001-%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF%E7%A4%BA%E6%84%8F%E5%9B%BE.png * Runloop 接收输入事件来自两种不同的来源:输入源(input source)和 定时源(timer source) * 输入源 事件包括用户在屏幕上的 触摸 事件,NSPort 对象和 performSelector:onThread: * 定时源 指的是定时器 NSTimer
NSTimer
  • 通过定时器,了解消息循环的模式
    • 定时器执行的方法中不易执行太耗时的操作,否则就会降低用户体验,用户拖拽的时候会感觉到卡顿的现象
    • 定时器在不使用时,一定注意销毁,否则会出现内存泄漏
  • 理解运行循环的两种模式
    • NSDefaultRunLoopMode 默认模式
    • NSRunLoopCommonModes
      • NSDefaultRunLoopMode
      • UITrackingRunLoopMode


  • 使用消息循环的时候必须指定两件事情
    • 输入事件:输入源 或 定时源
    • 消息循环模式
  • 消息循环运行在某一种 消息循环模式 上,处理该模式里面的 输入源 或 定时源
NSTimer 练习
  • 在 ViewController 里面创建 NSTimer 对象 ```objc
[Objective-C] 纯文本查看 复制代码
(void)viewDidLoad {
    [super viewDidLoad];
    // 创建定时器 
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
    // 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式 
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
    
    /// NSTimer过1.0秒调用一次
(void)display {
    NSLog(@"定时器方法被调用了: runloop = %@", [[NSRunLoop currentRunLoop] currentMode]);
}

  • 运行结果:正常状态输出,主线程RunLoop默认的模式。当滚动 UITextView 时不会输出 objc 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode 01.运行循环概念[3663:491230] 定时器方法被调用了: runloop = kCFRunLoopDefaultMode
  • 当 UIScrollView 滚动时,进入 UITrackingRunLoopMode 模式
  • 将 NSTimer 添加到 NSRunLoopCommonModes 模式里面
[AppleScript] 纯文本查看 复制代码
(void)viewDidLoad {
    [super viewDidLoad];
// 创建定时器 
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
// 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式 // 
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 将定时器添加到 RunLoop 的 NSRunLoopCommonModes 模式里面,当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式 
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

  • NSRunLoopCommonModes 包含 NSDefaultRunLoopMode 和 UITrackingRunLoopMode 2中模式,所以在正常状态 和 UITextView 滚动时都能调用 NSTimer
  • 将 NSTimer 添加到 UITrackingRunLoopMode 模式里面
[Objective-C] 纯文本查看 复制代码
(void)viewDidLoad {
    [super viewDidLoad];
// 创建定时器 
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(display) userInfo:nil repeats:YES];
// 将定时器添加到 RunLoop 的 NSDefaultRunLoopMode 模式里面 主线程RunLoop默认的模式 
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 将定时器添加到 RunLoop 的 NSRunLoopCommonModes 模式里面,当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式 
//    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 当UIScrollView滚动时,进入 UITrackingRunLoopMode 模式, 将 timer 加入到 UITrackingRunLoopMode,只在 UITrackingRunLoopMode下才会执行timer, 在 NSDefaultRunLoopMode 模式里面不会执行
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
}

  • RunLoop 默认是在 NSDefaultRunLoopMode,所以不会输出,只有当 UITextView 滚动时进入 UITrackingRunLoopMode, NSTimer 才执行
子线程的消息循环
  • 新建一个 iOS 工程
  • 在 Main.storyboard 中拖入一个 UITextView 控件
  • 在 ViewController 控制器的 viewDidLoad 创建一个定时器
    [AppleScript] 纯文本查看 复制代码
    -(void)viewDidLoad {
        [super viewDidLoad]; // 创建定时器 
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES]; // 将定时器添加到主线程RunLoop里面,定时器的方法也是在主线程上面执行,会影响界面流畅 
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
  • 实现定时器方法 time,里面模拟耗时操作
    [Objective-C] 纯文本查看 复制代码
    /// 耗时的定时任务
    (void)time {
        NSLog(@"time, thread = %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    }
  • 运行可以看到 UI 界面滚动卡顿,因为定时器是添加在 主线程 的 RunLoop 上面, time 定时方法是在主线程上面执行 bash 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main} 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main} 02-子线程消息循环[2635:240723] time, thread = <NSThread: 0x7fe381f06c20>{number = 1, name = main}
  • 在子线程上面执行定时器任务
    [Objective-C] 纯文本查看 复制代码
    - (void)viewDidLoad {
        [super viewDidLoad]; // 创建定时器 // 
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES]; // 
        // 将定时器添加到主线程RunLoop里面,定时器的方法也是在主线程上面执行,会影响界面流畅 // 
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        // 在子线程上开启RunLoop,将定时器添加到子线程的RunLoop上面
        _thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        [_thread start];
     }
  • 在 viewDidLoad 中创建一个子线程 objc @interface ViewController () { NSThread *_thread; } @end

  • 定义子线程执行的方法 run
    [Objective-C] 纯文本查看 复制代码
    /// 子线程执行方法
    -(void)run {
        NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
        NSLog(@"子线程结束");
    }


  • 运行,可以看到子线程执行了 run 方法里面的代码就退出了
  • 在 run 方法中创建定时器
    [Objective-C] 纯文本查看 复制代码
    /// 子线程执行方法
    (void)run {
        NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
        // 创建定时器 
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];
    //     将 timer 定时器添加到当前线程 RunLoop 的 NSDefaultRunLoopMode 模式里面
    //    [NSRunLoop currentRunLoop] 当前线程/子线程 的RunLoop
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        NSLog(@"子线程结束");
    }


  • 运行,发现定时器并没有执行,run 方法依然直接结束了.
  • 开启当前线程的 RunLoop: [[NSRunLoop currentRunLoop] run];
    [AppleScript] 纯文本查看 复制代码
    /// 子线程执行方法
    (void)run {
        NSLog(@"子线程开始, thread = %@", [NSThread currentThread]);
        // 创建定时器 
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];
    //     将 timer 定时器添加到当前线程 RunLoop 的 NSDefaultRunLoopMode 模式里面
    //    [NSRunLoop currentRunLoop] 当前线程/子线程 的RunLoop
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 启动RunLoop
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"子线程结束");
    }


  • 运行: objc 01:46:20.195 02-子线程消息循环[2673:248643] 子线程开始, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:21.198 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:24.198 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)} 01:46:27.200 02-子线程消息循环[2673:248643] time, thread = <NSThread: 0x7f822ad90530>{number = 2, name = (null)}
  • 可以看到,RunLoop 启动了,在不停的处理 定时源,此时子线程就不会结束,直到 RunLoop 退出
  • 注释 将定时器添加到 RunLoop 的代码,如果只是单纯启动 RunLoop,RunLoop 没有定时源或输入源会立马就退出了
  • 所以在启动 RunLoop 时需要保证 RunLoop 有定时源或输入源
  • 在子线程执行特定的任务,取消 time 方法中的 线程睡眠
    [Objective-C] 纯文本查看 复制代码
    /// 耗时的定时任务
     (void)time {
        NSLog(@"time, thread = %@", [NSThread currentThread]);
        // [NSThread sleepForTimeInterval:2]; 
     }
    //    在 Main.storyboard 拖入一个按钮,连线到 ViewController 的 buttonClick: 方法
    /// 按钮点击事件
        (IBAction)buttonClick:(id)sender {
        }
  • 在 buttonClick: 中让 子线程 执行特定的方法
    [Objective-C] 纯文本查看 复制代码
    /// 按钮点击事件
    - (IBAction)buttonClick:(id)sender {
        [self performSelector:@selector(perform) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    - (void)perform {
        NSLog(@"perform, thread = %@", [NSThread currentThread]);
    }


  • 运行:可以看到,子线程 RunLoop 在执行定时器的同时,还能接受 performSelector:onThread:withObject:waitUntilDone 发来的 输入源 事件 objc 01:57:12.560 02-子线程消息循环[2767:257502] 子线程开始, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:13.582 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:14.581 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:15.506 02-子线程消息循环[2767:257502] perform, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:15.583 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:16.581 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:17.130 02-子线程消息循环[2767:257502] perform, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)} 01:57:17.582 02-子线程消息循环[2767:257502] time, thread = <NSThread: 0x7fd37ae0f470>{number = 2, name = (null)}


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马