iOS多线程(5)-NSRunLoop

本文部分引自:https://www.jianshu.com/p/d260d18dd551
实例下载:https://github.com/PeaceWanghp/RunLoop.git

  • RunLoop简介:

    可以理解为字面意思:Run表示运行,Loop表示循环。结合在一起就是运行的循环的意思。
    RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。

  • RunLoop和线程:

    RunLoop和线程是息息相关的,我们知道线程的作用是用来执行特定的一个或多个任务,但是在默认情况下,线程执行完之后就会退出,就不能再执行任务了。这时我们就需要采用一种方式来让线程能够处理任务,并不退出。所以,我们就有了RunLoop。

    一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。
    我们只能在当前线程中操作当前线程的RunLoop,而不能去操作其他线程的RunLoop。
    RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候。
    主线程的RunLoop对象系统自动帮助我们创建好了(原理如下),而子线程的RunLoop对象需要我们主动创建。

  • RunLoop相关类:

    下面我们来了解一下Core Foundation框架下关于RunLoop的5个类,只有弄懂这几个类的含义,我们才能深入了解RunLoop运行机制。

    CFRunLoopRef:代表RunLoop的对象
    CFRunLoopModeRef:RunLoop的运行模式
    CFRunLoopTimerRef:就是RunLoop模型图中提到的定时源
    CFRunLoopObserverRef:观察者,能够监听RunLoop的状态改变
    CFRunLoopSourceRef:就是RunLoop模型图中提到的输入源/事件源

    他们的关系是:
    一个RunLoop对象(CFRunLoopRef)中包含若干个运行模式(CFRunLoopModeRef)。而每一个运行模式下又包含若干个输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef)。

    每次RunLoop启动时,只能指定其中一个运行模式(CFRunLoopModeRef),这个运行模式(CFRunLoopModeRef)被称作CurrentMode。
    如果需要切换运行模式(CFRunLoopModeRef),只能退出Loop,再重新指定一个运行模式(CFRunLoopModeRef)进入。
    这样做主要是为了分隔开不同组的输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef),让其互不影响 。

    (1)CFRunLoopRef

    CFRunLoopRef就是Core Foundation框架下RunLoop对象类。我们可通过以下方式来获取RunLoop对象:

    //Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    
    //当然,在Foundation框架下获取RunLoop对象类的方法如下:
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    
    (2)CFRunLoopModeRef

    五种模式:

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

    其中kCFRunLoopDefaultMode、UITrackingRunLoopMode、kCFRunLoopCommonModes是我们开发中需要用到的模式,具体使用方法我们在下面来演示说明。

    (3)CFRunLoopTimerRef

    CFRunLoopTimerRef是定时源(RunLoop模型图中提到过),理解为基于时间的触发器,基本上就是NSTimer(哈哈,这个理解就简单了吧)。下面我们来演示下CFRunLoopModeRef和CFRunLoopTimerRef结合的使用用法,从而加深理解。

    NSDefaultRunLoopMode:

    //只在ScrollView非滚动时执行
    - (void)timerAction {
      [NSTimer scheduledTimerWithTimeInterval:2.0 
                                       target:self
                                     selector:@selector(timerRunAction) 
                                     userInfo:nil
                                      repeats:YES];
      或    
      NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 
                                               target:self 
                                           selector:@selector(timerRunAction) 
                                           userInfo:nil
                                            repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    }
    
    - (void)timerRunAction {
      NSLog(@"Timer run! = %f",[NSDate date].timeIntervalSince1970);
    }
    
    

    UITrackingRunLoopMode:

    //只在ScrollView滚动时执行
    - (void)trackAction {
      NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 
                                               target:self 
                                           selector:@selector(trackRunAction) 
                                          userInfo:nil 
                                           repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    }
    
    - (void)trackRunAction {
      NSLog(@"Track timer run! = %f",[NSDate date].timeIntervalSince1970);
    }
    

    NSRunLoopCommonModes:

    //同时处理闲时和滚动时
    - (void)commonAction {
      NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 
                                               target:self 
                                           selector:@selector(commonRunAction) 
                                           userInfo:nil 
                                            repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    - (void)commonRunAction {
      NSLog(@"Track timer run! = %f",[NSDate date].timeIntervalSince1970);
    }
    
    (4)CFRunLoopObserverRef

    CFRunLoopObserverRef是观察者,用来监听RunLoop的状态改变
    CFRunLoopObserverRef可以监听的状态改变有以下几种:

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
      kCFRunLoopEntry = (1UL << 0),               // 即将进入Loop:1
      kCFRunLoopBeforeTimers = (1UL << 1),        // 即将处理Timer:2    
      kCFRunLoopBeforeSources = (1UL << 2),       // 即将处理Source:4
      kCFRunLoopBeforeWaiting = (1UL << 5),       // 即将进入休眠:32
      kCFRunLoopAfterWaiting = (1UL << 6),        // 即将从休眠中唤醒:64
      kCFRunLoopExit = (1UL << 7),                // 即将从Loop中退出:128
      kCFRunLoopAllActivities = 0x0FFFFFFFU       // 监听全部状态改变  
    };
    

    设置观察者:

    - (void)observerAction {
      CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          NSLog(@"RunLoop Changed Activity---%zd",activity);
      });
      CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
      CFRelease(observer);
    }
    

    输出:

    2018-03-28 12:37:36.067551+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.067733+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.067853+0800  RunLoop Changed Activity---32
    2018-03-28 12:37:36.068480+0800  RunLoop Changed Activity---64
    2018-03-28 12:37:36.068645+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.068762+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.068895+0800  RunLoop Changed Activity---32
    2018-03-28 12:37:36.569414+0800  RunLoop Changed Activity---64
    2018-03-28 12:37:36.569646+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.569752+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.569848+0800  RunLoop Changed Activity---32
    2018-03-28 12:37:36.570096+0800  RunLoop Changed Activity---64
    2018-03-28 12:37:36.570202+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.570301+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.570406+0800  RunLoop Changed Activity---32
    2018-03-28 12:37:36.570512+0800  RunLoop Changed Activity---64
    2018-03-28 12:37:36.570637+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.570741+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.570854+0800  RunLoop Changed Activity---32
    2018-03-28 12:37:36.571032+0800  RunLoop Changed Activity---64
    2018-03-28 12:37:36.571130+0800  RunLoop Changed Activity---2
    2018-03-28 12:37:36.571222+0800  RunLoop Changed Activity---4
    2018-03-28 12:37:36.571322+0800  RunLoop Changed Activity---32
    
    (5)CFRunLoopSourceRef

    事件分为:
    source0:非基于Port的,用于用户主动触发的事件。
    source1:基于Port的,通过内核和其他线程相互发送消息 。

    例子:source0通过点击按钮
    iOS多线程(5)-NSRunLoop_第1张图片
    事件触发source0.png
  • 实际应用场景

    (1)TableView显示图片过多时可采用闲时更新
    #pragma mark -
    #pragma mark -- TableView
    - (void)tableView:(int)index {
      float y = 20 + index * 50;
      UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, y, self.view.frame.size.width, self.view.frame.size.height-y) style:UITableViewStylePlain];
      tableView.delegate = self;
      tableView.dataSource = self;
      tableView.rowHeight = 165;
      [self.view addSubview:tableView];
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return 500;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      static NSString *identifier = @"TableViewCell";
      SpaceCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
      if (!cell) {
          cell = [[SpaceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
      }
      cell.topLabel.text =  [NSString stringWithFormat:@"%zd - show the title ", indexPath.row];
      cell.bottomLabel.text = [NSString stringWithFormat:@"%zd - show the bottom title text.", indexPath.row];
      
      [cell.imgV1 setImage:nil];
      [cell.imgV2 setImage:nil];
      [cell.imgV3 setImage:nil];
      
      //滚动很流畅
      [cell.imgV1 performSelector:@selector(setImage:) withObject:getImg() afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
      [cell.imgV2 performSelector:@selector(setImage:) withObject:getImg() afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
      [cell.imgV3 performSelector:@selector(setImage:) withObject:getImg() afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
      
    //明显卡顿
    //    cell.imgV1.image = getImg();
    //    cell.imgV2.image = getImg();
    //    cell.imgV3.image = getImg();
      
      return cell;
    }
    
    UIImage *getImg() {
      NSString *path1 = [[NSBundle mainBundle] pathForResource:@"beauty" ofType:@"jpg"];
      UIImage *img = [UIImage imageWithContentsOfFile:path1];
      return img;
    }
    
    (2)常驻线程

    如果我们有某些任务需要频繁的操作,但是每次新起一个临时线程又较耗费资源,这时我们创建一个常驻后台的线程就能解决这些问题。

    //创建线程和添加任务
    - (void)strongeThreadAction {
      if (!self.thread) {
          self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(strongeThread) object:nil];
          [self.thread start];
      }
      else {
          [self performSelector:@selector(addActionToThread) onThread:self.thread withObject:nil waitUntilDone:NO];
      }
    }
    
    //开启线程runloop
    - (void)strongeThread {
      NSLog(@"----add run loop-----");
      
      [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
      [[NSRunLoop currentRunLoop] run];
    }
    
    //添加任务
    - (void)addActionToThread {
      NSDate *date = [NSDate date];
      NSLog(@"----Do Action1-----%f",date.timeIntervalSince1970);
      sleep(3);
      NSLog(@"----Do Action2-----%f",date.timeIntervalSince1970);
    }
    

你可能感兴趣的:(iOS多线程(5)-NSRunLoop)