前言什么是RunLoop

什么RunLoop? 跑圈?字面理解确实是这样的。

Apple官方文档这样解释RunLoop

RunLoop是与线程息息相关基本结构的一部分。RunLoop一个调度任务处理任务事件循环。RunLoop的目的是为了在有工作时候线程忙起来,而在没有工作时候线程进入休眠状态

之所以iOS的app能够持续响应从而让程序保持运行状态,在于其存在一个事件循环(Event Loop机制线程能够随时响应并处理事件机制,这种机制要求线程不能退出从而高效的完成事件调度和处理。

在iOS这种事件循环机制就叫做RunLoop

RunLoop实际上是一个对象,对象在循环中处理程序运行过程出现的各种事件比如触摸事件,UI刷新事件定时器事件Selector事件)从而保持程序持续运行并且让程序在没有事件处理的时候进入休眠状态,从而节省CPU资源达到提升程序性能的目的。

默认情况下主线程的RunLoop原理

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

return UIApplicationMain(argc, argv, nil, appDelegateClassName);这是 iOS 应用程序的主运行循环,它负责处理用户事件、界面更新应用程序的主要逻辑UIApplicationMain 函数创建应用程序对象和主运行循环,并传递控制权给应用程序委托类(AppDelegate)来处理应用程序逻辑

其中的UIApplicationMain函数内部我们开启主线程的RunLoop
UIApplicationMain内部拥有一个无限循环的代码

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

程序会一直在do-while循环中执行

Apple官方的RunLoop模型

在这里插入图片描述

RunLoop就是线程中的一个循环,RunLoop在循环中不断检测通过Input sources输入源)和Timer sources定时源)两种来源等待接受消息然后接收到的事件通知线程进行处理,并在没有事件的时候进行休息。

1. RunLoop对象

RunLoop对象是基于CFFoundation架的CFRunLoopRef类型封装的对象。

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

CoreFoundation框架CFRunLoopRef对象

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

在这里插入图片描述

那么对应的两种方式就是:

- (void)getRunLoop {
    NSRunLoop *runloop  = [ NSRunLoop currentRunLoop];
    NSRunLoop *manRlp = [NSRunLoop mainRunLoop];
    
    CFRunLoopRef cfRlp = CFRunLoopGetCurrent();
    CFRunLoopRef mainCfRlp = CFRunLoopGetMain();
}

看下CFRunLoopGetCurrentCFRunLoopGetMain的具体实现:

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

发现过程调用_CFRunLoopGet0这个函数,后面再进行讲解

CFRunLoopRef源码部分引入线程相关

CFRunLoopRef的源码部分

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort; //【通过函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread; //【RunLoop对应的线程】
    uint32_t _winthread;
    CFMutableSetRef _commonModes; // 【存储的是字符串记录所有标记common的mode
    CFMutableSetRef _commonModeItems;//【存储所有commonMode的item(sourcetimer、observer)】
    CFRunLoopModeRef _currentMode;//【当前运行的mode】
    CFMutableSetRef _modes;//【存储的是CFRunLoopModeRef】
    struct _block_item *_blocks_head;//【do blocks用到
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

对于一些属性之外,重点需要关注三个成员变量

pthread_t _pthread;【RunLoop对应的线程】
CFRunLoopModeRef _currentMode;当前运行的mode】
CFMutableSetRef _modes;存储的是CFRunLoopModeRef】

看看RunLoop和线程的关系

2. RunLoop和线程

先看一下_CFRunLoopGet0这个函数是怎么实现的,和RunLoop和线程有什么关系

//全局的Dictionarykey是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0是始终有效的“主线程”的同义词

//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    //pthread为空时,获取主线
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        //第一次进入时,创建一个临时字典dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //根据传入的主线获取主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    //保存主线程,将主线程-key和RunLoop-Value保存字典
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //此处NULL和__CFRunLoops指针指向NULL,匹配,所以将dict写到__CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    //释放dict
        CFRelease(dict);
    }
    //释放mainRunLoop
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //以上说明,第一次进来的时候,不管是getMainRunLoop还是get子线程的runLoop,主线程的runLoop总是会被创建

	//从全局字典获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
    //如果取不到,就创建一个新的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //创建好之后,以线程为key,runLoop为value一对存储字典中,下次获取的时候,则直接返回字典内的runLoop
    if (!loop) {
    //把newLoop存入字典__CFRunLoops,key是线程t
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
    }

	//如果传入线程就是当前线程
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        //注册一个回调,当线程销毁时,销毁对应的RunLoop
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

这段源码告诉我们

3. RunLoop相关的类

RunLoop相关的类有5个。

在这里插入图片描述

RunLoop结构和套娃一样,RunLoop里面装着ModeMode里面装着Souce / Observer / Timer

RunLoop相关类的实现

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
这句话其实就是5个相关类的关系

CFRunLoopModeRef

代表RunLoop的运行模式,但这里请理清概念我们RunLoop里可以装多个Mode,只是我们指定运行的时候要指定一个Mode

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名称,运行模式是通过名称识别
    Boolean _stopped; //mode是否终止
    char _padding[3];
    //整个结构体最核心部分
------------------------------------------
    CFMutableSetRef _sources0;//Sources0
    CFMutableSetRef _sources1;//Sources1
    CFMutableArrayRef _observers;//观察者
    CFMutableArrayRef _timers;//定时器
------------------------------------------
    CFMutableDictionaryRef _portToV1SourceMap;//字典    key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    CFIndex _observerMask;
};

五种运行模式

系统默认注册的五个Mode:

其中kCFRunLoopDefaultModeUITrackingRunLoopModekCFRunLoopCommonModes是我们开发中需要用到的模式。

CommonModes

RunLoop对象中,前面有一个CommonModes成员变量。

//简化版本
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;//存储的是字符串记录所有标记common的mode
    CFMutableSetRef _commonModeItems;//存储所有commonMode的item(sourcetimer、observer)
    CFRunLoopModeRef _currentMode;//当前运行的mode
    CFMutableSetRef _modes;//存储的是CFRunLoopModeRef对象,不同mode类型,它的mode名字不同
};

底层原理

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    //获取所有的_commonModeItems
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    //获取所有的_commonModes
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, modeName};
        //将所有的_commonModeItems逐一添加到_commonModes里的每一个Mode
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set);
    }
    } else {
    }
    __CFRunLoopUnlock(rl);
}

总体来说,这个方法的主要作用是将一个指定的 commonModeItems 集合添加到运行循环的共同模式集合中,并将 commonModeItems 添加到共同模式集合中的每个 Mode 中,以确保共同模式的事件源在多个 Mode 下都能得到处理。它涉及到 CoreFoundation 框架中运行循环的底层操作,用于管理运行循环中的事件和模式。

CFRunLoopSourceRef

CFRunLoopSourceRefRunLoop模型中提到的输入源,也就是事件产生的地方。

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;   //执行顺序
    CFMutableBagRef _runLoops;//包含多个RunLoop
    //版本
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

刚才上面提到souce存在 source0source1两个版本,他们分别做了什么

CFRunLoopTimerRef

CFRunLoopTimerRef: 定时源- 基于时间触发器

CFRunLoopTimerRef基于时间触发器,它和NSTimer可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入RunLoop时,RunLoop注册对应的时间点,当时间点到时,RunLoop会被唤醒执行那个回调。

当我们调用NSTimerscheduledTimerWithTimeInterval的时候

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

系统自动加入NSDefaltRunLoopMode.

等于如下代码

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

定时器滑动不准确

知乎日报的时候,滑动tableView造成了上面自动轮播图的定时器失效问题
常见问题就是:当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollViewNSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况。

原因就是:

当我们不做任何操作的时候,RunLoop处于NSDefaultRunLoopMode下。
当我们进行拖拽时,RunLoop就结束NSDefaultRunLoopMode切换到了UITrackingRunLoopMode模式下,这个模式下没有添加NSTimer,所以我们的NSTimer就不工作了。
当我们松开鼠标时候,RunLoop就结束UITrackingRunLoopMode模式,又切换NSDefaultRunLoopMode模式,所以NSTimer就又开始正常工作了。

解决

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

在 iOS 中,当你滑动 UITableView 或其他滚动视图时,主线程上的 RunLoop 切换UITrackingRunLoopMode,这是一个特殊的运行循环模式,用于处理用户交互事件,例如滚动手势。在默认情况下,如果你在主线程上使用定时器,它会在默认的运行循环模式 NSDefaultRunLoopMode 下运行。由于 RunLoop 一次只能处理一个运行循环模式,当你滑动时,NSDefaultRunLoopMode切换UITrackingRunLoopMode,导致定时器事件暂停,直到滑动结束。

为了解决这个问题,可以使用 kCFRunLoopCommonModes代表了一个 “common mode set”,它同时包括了 NSDefaultRunLoopModeUITrackingRunLoopMode。通过在定时器添加中使用 kCFRunLoopCommonModes,可以使定时器默认模式和追踪模式下都得到触发,从而避免滑动导致定时器暂停的问题。

CFRunLoopObserverRef

CFRunLoopObserverRef观察者每个Observer都包含了一个回调(函数指针),当RunLoop状态发生变化时,观察者就能通过回调接收到这个变化。

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;//监听的RunLoop
    CFIndex _rlCount;//添加该Observer的RunLoop对象个数
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;//同时间最多只能监听一个
    CFRunLoopObserverCallBack _callout;//监听的回调
    CFRunLoopObserverContext _context;//上下文用于内存管理
};

//观测的时间点有一下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

什么是Mode Item?

Mode到底包含哪些类型的元素
前面提到过 CFMutableSetRef _commonModeItems:存储所有commonModeitem (sourcetimerobserver)

上面的 Source/Timer/Observer 被统称为 mode item

所有的mode item都可以被添加Mode中,Mode中可以包含多个mode item,一个item也可以被加入多个mode。但一个item重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则RunLoop退出,不进入循环。

在这里插入图片描述

RunLoop休眠的实现原理

用户切换到内核态, 在内核态让线程进行休眠,有消息的时候唤起线程,回到用户态处理消息

RunLoop小结

RunLoop内部实际是一个do while循环,当调用CFRunLoopRun()的时候,线程就会一直停留在这个循环里面,当超时或者被手动调用的时候该函数才会返回

  • RunLoop的运行必定要指定一种mode,并且该mode必须注册任务事件。
  • RunLoop是在默认mode下运行的,当然也可以指定一种mode运行,但是只能在一种mode下运行。
  • RunLoop内部实际上是维护了一个do-while循环,线程就会一直留在这个循环里面,直到超时或者手动被停止。
  • RunLoop 的核心就是一个 mach_msg() RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态,否则线程处理事件。

4. RunLoop实际应用

  1. 控制线程生命周期(线程保活
  2. 解决NSTimer在滑动时停止工作的问题
  3. 监控应用卡顿
  4. 性能优化

RunLoop的启动方法

- (void)run; // 默认模式
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
  1. run:没有任何条件就可以启动,虽然简单,但是实际的操控结果并不影响,RunLoop是一个do while循环,倘若无条件的运行RunLoop将线程永远的放入循环,这就使我们没有办法控制循环本身,只能靠杀死进程来停止RunLoop
  2. runUnitDate:设置时间限制
  • 设置超时的时间,超过这个时间RunLoop就结束了。
  1. runMode:beforeDate:在特定模式下启动。
  • 可以指定runloop以哪种模式运行,但是它是单次调用的,超时时间到达或者一个输入源被处理,则runLoop就会自动退出,上述两种方式都是循环调用的

第一种会一直运行下去,并且一直在NSDefaultRunLoopMode模式下,重复调用runMode:beforeDate:方法
第二种会在超时时间之前一直在NSDefaultRunLoopMode模式下调用runMode:beforeDate:方法
第三种则会在超时时间到达或者第一个inputsource被处理前一直调用runMode:beforeDate:方法

RunLoop关闭

  1. 将运行的循环配置设置超时
  2. 手动停止。

这里需要注意,虽然删除runloop的输入源和定时器可能会导致运行循环的退出,但这并不是个可靠方法系统可能会添加输入源到runloop中,但在我们的代码中可能并不知道这些输入源,因此无法删除它们,导致无法退出runloop。

当我们通过 runUnitDate 和 runMode: beforeDate:方法启动RunLoop设置超时时间,但是如果需要对这个线程和它的RunLoop有着最精确控制,并不是依赖超时机制,我们可以通过 CFRunLoopStop()方法手动结束一个 RunLoop。但是 CFRunLoopStop()方法只会结束当前正在执行的这次runMode:beforeDate:调用,而不会结束后续runloop的调用。

imageView延迟显示

界面中含有UITableView,而且每个UITableViewCell里边都有图片。这是当我们滚动UITableView的时候,如果有一堆的图片需要显示,那么可能出现卡顿的情况。

我们应该推迟图片的实现,也就是ImageView推迟显示图片。当我们滑动时不要加载图片, 拖动结束再显示:

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

用户点击屏幕,在主线程中,三秒之后显示图片,但是当用户点击屏幕之后,如果此时用户又开始滚动tableview,那么就算过了三秒,图片也不会显示出来,当用户停止了滚动,才会显示图片。

这是因为限定了方法setImage只能在NSDefaultRunLoopMode模式下使用。而滚动tableview的时候,程序运行在tracking模式下面,所以方法setImage不会执行。

常驻线程

开发应用程序过程中,如果后台操作分频繁,比如后台播放音乐下载文件等等,我们希望执行后台代码的这条线程永远常驻内存,我们可以添加一条用于常驻内存的强引用子线程,在该线程的RunLoop下添加一个Sources,开启RunLoop:

@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end

 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
 [self.thread start];
- (void)runThread {
    NSLog(@"开启子线程:%@", [NSThread currentThread]);
// 子线程的RunLoop创建出来需要手动添加事件输入源和定时器 因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
    //下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    // 测试开始RunLoop
    // 未进入循环就会执行该代码
    NSLog(@"failed");
}

// 同时在我们自己新建立的这个线程中写一下touchesBegan这个方法测试点空白处会不会在子线程相应方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  {
    [self performSelector:@selector(runTest) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)runTest {
    NSLog(@"子线程点击空白:%@", [NSThread currentThread]);
}

可以看到RunLoop成功启动进入循环,点击屏幕的时候也是在子线程调用方法,这样子子线程启动完成之后就达到了常驻线程的目的。

线程保活

场景
平时创建子线程时,线程上的任务执行完这个线程就会销毁掉。
有时我们会需要经常在一个子线程中执行任务,频繁的创建和销毁线程就会造成很多的开销,这时我们可以通过runloop来控制线程的生命周期

在下面的代码中,因为runMode:beforeDate:方法是单次调用,我们需要给它加上一个循环,否则调用一次runloop就结束了,和不使用runloop的效果一样。

这个循环的条件默认设置成YES,当调用stop方法中,执行CFRunLoopStop()方法结束本次runMode:beforeDate:,同时将循环中的条件设置为NO,使循环停止,runloop退出。

@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL stopped;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor greenColor];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:button];
    [button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"执行任务" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 200, 100, 20);
    
    
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    [stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
    [stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
    stopButton.frame = CGRectMake(100, 400, 100, 20);
    
    self.stopped = NO;
    //防止循环引用
    __weak typeof(self) weakSelf = self;
    
    self.thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"Thread---begin");
        
        //向当前runloop添加Modeitem,添加timer、observer都可以。因为如果mode没有item,runloop就会退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"Thread---end");
    }];
    [self.thread start];
}
- (void)pressPrint {
    //子线程中调用print
    [self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}

//子线程需要执行的任务
- (void)print {
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

- (void)pressStop {
    //子线程中调用stop
    if (_stopped == NO ) {
        [self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    
}

//停止子线程的runloop
- (void)stop {
    //设置标记yes
    self.stopped = YES;
    
    //停止runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    //解除引用, 停止runloop这个子线程就会dealloc
    self.thread = nil;
}

- (void)dealloc {
    NSLog(@"%s", __func__);
}

NSTimer不准

timer在实际的开发中一般不在主线程的RunLoop里面存在,因为主线程在执行阻塞任务的时候timer的计时器也会导致不准确。

如果timer在主线程里面阻塞 如何解决timer不准确的问题。

  • 放入子线程中,但是需要开辟线程和控制线程的生命周期,成本较大。
  • 使用GCD的定时器计时,避免阻塞。

原文地址:https://blog.csdn.net/m0_63852285/article/details/132107089

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25550.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注