zoukankan      html  css  js  c++  java
  • iOS开发日记24-详解RunLoop

    今天博主有一个RunLoop的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步.

    RunLoop是什么?

    从字面上理解,Runloop指的就是运行循环,iOS中,只要程序启动, 就会创建Runloop,用来处理各种事件(比如定时器事件, 触摸事件等).

    它的主要作用有以下几个:

    1.保持程序的运行:如果没有它, 程序一启动就over了;

    2.事件的处理: 包括按钮点击事件, 屏幕的点击事件等;

    3.定时器处理: NSTimer的处理;

    4.节约CPU, 提高性能:有任务的时候干活,没有任务时候休息.

    在iOS中, 可以通过两套API来访问Runloop:

    1.Foundation框架: OC中的框架, 直接访问NSRunLoop对象.

    2.Core Foundation框架: c语言的框架, 通过CFRunLoopRef访问.

    实际上, NSRunLoop和CFRunLoopRef是同一个对象, 只不过通过不同的API来访问的,NSRunLoop是对CFRunLoopRef的封装.

     

    OC中, 使用下面代码就可以获得Runloop对象:

    // Foundation

    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象

    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

    // Core Foundation

    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象

    CFRunLoopGetMain(); // 获得主线程的RunLoop对象

     

    Nslog打印Runloop对象, 里面的内容如下:(由于内容比较多, 只将重点内容列出)

     // runloop对象
     <CFRunLoop 0x7fb13172abc0 [0x10a5aa180]>{wakeup port = 0x1003, stopped = false, ignoreWakeUps = true,
    
    // 当前模式
    current mode = UIInitializationRunLoopMode,
    common modes = <CFBasicHash 0x7fb13172ac80
    // common modes标记了哪两个模式(后面会讲为啥是标记?)
        0 : <CFString 0x10b27ce50 [0x10a5aa180]>{contents = "UITrackingRunLoopMode"}
        2 : <CFString 0x10a586080 [0x10a5aa180]>{contents = "kCFRunLoopDefaultMode"}
    }
    // source: 源
    6 : <CFRunLoopSource 0x7fb131415300 [0x10a5aa180]>
    // observers: 观察者
    observers = <CFArray 0x7fb13141d300 [0x10a5aa180]>
    // timer: 定时器
    timers = (null),
    // 两种不同的模式
    3 : <CFRunLoopMode 0x7fb13172dbb0 [0x10a5aa180]>{name = GSEventReceiveRunLoopMode, port set = 0x2003, timer port = 0x2103,
        sources0 = <CFBasicHash 0x7fb13172d260 [0x10a5aa180]>{type = mutable set, count = 1,
    entries =>
        0 : <CFRunLoopSource 0x7fb13172da30 [0x10a5aa180]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10d8236c8)}}
    }

    要想知道上面内容是什么意思, 先看下面的内容:

    Core Foundation中关于RunLoop的5个类
    1.CFRunLoopRef
    2.CFRunLoopModeRef
    3.CFRunLoopSourceRef
    4.CFRunLoopTimerRef
    5.CFRunLoopObserverRef
    可以这理解为, 一个Runloop对象里面, 必不可少的是CFRunLoopModeRef, Mode里面可以包含timer/source/oberver(只有包含了其中一个, Runloop才能在启动后保证不死)

    层级结构如下图:

    接下来, 对里面的内容说明一下:

    一.CFRunLoopModeRef:代表RunLoop的运行模式
        1.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
        2.每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作CurrentMode
        3.这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
    系统默认注册了5个Mode:
        1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
        2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
        3.UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
        4.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
        5.kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode, 被标记为kCFRunLoopCommonModes格式的都可以成为commonMode, 其中包含1.2两种模式.
    
    二.CFRunLoopSourceRef:是事件源(输入源)
        现在主要分为两种:
        Source0:非基于Port的
        Source1:基于Port的(如GCD底层实现也是通过Port的, 主要用于线程之间的通信)
    
    三.CFRunLoopTimerRef:是基于时间的触发器, 基本上说的就是NSTimer
    
    四.CFRunLoopObserverRef:观察者, 可以用来监听Runloop的状态, 下面列出了Runloop的状态
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0), // 即将进入
        kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理timer
        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源
        kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
        kCFRunLoopExit = (1UL << 7), // 即将退出Runloop
    };

    下面图片描述了Runloop的运行和状态:

    Runloop的寄生于线程:一个线程只能有唯一对应的runloop;但这个根runloop里可以嵌套子runloops;
    自动释放池寄生于Runloop:程序启动后,主线程注册了两个Observer监听runloop的进出与睡觉。一个最高优先级OB监测Entry状态;一个最低优先级OB监听BeforeWaiting状态和Exit状态。
    线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)

    Mach是XNU的内核,进程、线程和虚拟内存等对象通过端口发消息进行通信,Runloop通过mach_msg()函数发送消息,如果没有port 消息,内核会将线程置于等待状态 mach_msg_trap() 。如果有消息,判断消息类型处理事件,并通过modeItem的callback回调。

    Runloop有两个关键判断点,一个是通过msg决定Runloop是否等待,一个是通过判断退出条件来决定Runloop是否循环。

    应用1:滑动与图片刷新:

    当tableview的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode下进行,当滚动tableView的时候,RunLoop是在 UITrackingRunLoopMode 下进行,不去设置图片,而是当停止的时候,再去设置图片。

    - (void)viewDidLoad { 

    [super viewDidLoad];  // 只在NSDefaultRunLoopMode下执行(刷新图片) 

    [self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];  

     }

    应用2:常驻子线程,保持子线程一直处理事件
    为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    
    + (NSThread *)networkRequestThread {
        static NSThread *_networkRequestThread = nil;
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
            [_networkRequestThread start];
        });
        return _networkRequestThread;
    }
    - (void)start {
        [self.lock lock];
        if ([self isCancelled]) {
            [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        } else if ([self isReady]) {
            self.state = AFOperationExecutingState;
            [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        }
        [self.lock unlock];
    }


    http://www.cnblogs.com/66it/p/4719701.html?utm_source=tuicool
    http://blog.ibireme.com/2015/05/18/runloop/
  • 相关阅读:
    Python格式化输出
    每天写点shell脚本 (持续更新)
    linux limits.conf 配置
    ELK 日志分析系统
    开源大数据处理工具
    glusterFS分布式存储部署流程
    glusterFS的部署流程
    parted命令详解
    /proc文件系统
    /proc文件系统(二):/proc/<pid>/stat
  • 原文地址:https://www.cnblogs.com/Twisted-Fate/p/4831510.html
Copyright © 2011-2022 走看看