【iOS】—— RunLoop线程常驻和线程保活

文章讲述了在iOS开发中,如何通过RunLoop保持线程常驻以避免频繁创建和销毁线程造成的资源浪费。通过添加NSPort或者定时器到RunLoop,可以使线程长时间存活,同时介绍了如何控制RunLoop的启动和停止以进行线程保活,并注意防止内存泄漏问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

没有线程常驻会怎么样?

我们一般写一个子线程,子线程执行完分配的任务后就会自动销毁,比如下面这个情况:
我们先重写一下NSThread里面的dealloc方法,打印什么时候会调用dealloc方法。

#import "NSThread+NewDealloc.h"

@implementation NSThread (NewDealloc)
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

在ViewController里调用方法:

@implementation FirstViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomeThing) object:nil];
    [thread start];
}
- (void)doSomeThing {
    NSLog(@"%s", __func__);
}

在这里插入图片描述
根据打印结果我们可以看到,在子线程执行完任务后线程自动销毁。
而我们有时会需要经常在一个子线程中执行任务,频繁的创建和销毁线程就会造成资源浪费,这时候就要用到RunLoop来使线程长时间存活了

线程常驻

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

@interface FirstViewController ()
@property (nonatomic, strong) NSThread *thread;
@end
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
    [self.thread start];
- (void)run1 {
    NSLog(@"----run1-----");

    /*如果不加这句,会发现runloop创建出来就挂了,因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
          下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉*/

    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // 方法1 ,2,3实现的效果相同,让runloop无限期运行下去
    // 方法2
//    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // 方法3
//    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
        
    [[NSRunLoop currentRunLoop] run];
        // 测试是否开启了RunLoop,如果开启RunLoop,则来不了这里,因为RunLoop开启了循环。
        NSLog(@"未开启RunLoop");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 利用performSelector,在self.thread的线程中调用run2方法执行任务
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)run2 {
    NSLog(@"----run2------");
}

在这里插入图片描述
无论点击屏幕多少次都不会dealloc线程。

我们必须保证线程不消亡,才可以在后台接受时间处理,所以如果没有实现添加NSPort或者NSTimer,会发现执行完run方法,线程就会消亡,后续再执行touchbegan方法无效。

实现了上面三个方法之一,就可以发现执行完了run方法,这个时候再点击屏幕,可以不断执行test方法,因为线程self.thread一直常驻后台,等待事件加入其中,然后执行。

线程保活

RunLoop的启动和关闭方法在上一篇博客的最后讲过【iOS】—— RunLoop初学
我们直奔主题:

通过以上关于RunLoop启动和关闭的方法分析,我们大概有这样一个思路:

  • 我们想要控制RunLoop,就需要使用runMode:beforeDate:方法,因为其他两种方法一个无法停止一个只能依赖超时机制
  • CFRunLoopStop() 方法只会结束当前的一次的runMode:beforeDate:方法调用,我们必须再做点什么

针对以上疑问,有以下解答:

  • 首先,因为runMode:beforeDate:方法是单次调用,我们需要给它加上一个循环,否则调用一次就over了,和不使用RunLoop的效果大同小异
  • 这个循环的条件可以默认设置为YES,当调用stop方法时,执行CFRunLoopStop() 方法并且将循环条件改为NO,就可以使循环停止,RunLoop退出
#import "SecondViewController.h"

@interface SecondViewController ()
@property (nonatomic, strong) NSThread *aThread;
@property (nonatomic, assign) BOOL stopped;
@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 添加一个停止RunLoop的按钮
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    stopButton.frame = CGRectMake(180, 180, 100, 50);
    stopButton.titleLabel.font = [UIFont systemFontOfSize:20];
    [stopButton setTitle:@"stop" forState:UIControlStateNormal];
    stopButton.tintColor = [UIColor blueColor];
    [stopButton addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
    
    // 用于返回的按钮
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:backButton];
    backButton.frame = CGRectMake(180, 380, 100, 50);
    backButton.titleLabel.font = [UIFont systemFontOfSize:20];
    [backButton setTitle:@"back" forState:UIControlStateNormal];
    backButton.tintColor = [UIColor orangeColor];
    [backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];

    
    self.stopped = NO;
    __weak typeof(self) weakSelf = self;
    self.aThread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"go");
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"ok");
    }];
    [self.aThread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(doSomething) onThread:self.aThread withObject:nil waitUntilDone:NO];
}

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

- (void)stop {
    // 在子线程调用stop
    if (self.aThread) {
        // 在子线程调用stop
        [self performSelector:@selector(stopThread) onThread:self.aThread withObject:nil waitUntilDone:YES];
    }
}

// 用于停止子线程的RunLoop
- (void)stopThread {
    // 设置标记为NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    self.aThread = nil;
}

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


- (void)back {
    [self stop];
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

需要注意的一点是,如果我们的ViewController已经被销毁了,线程并没有死,这就造成了内存泄漏了。所以我们要注意,在ViewController被dealloc之前,先stop线程。
在这里插入图片描述

<think>好的,我现在需要回答如何在Swift中实现常驻线程的问题。首先,常驻线程通常指的是不会被销毁的线程,能够持续处理任务,比如处理后台事件或定时任务。在iOS开发中,主线程是自动带有RunLoop的,而其他线程需要手动配置RunLoop才能跃状态,避免执行完任务后退出。 根据引用[3],要确目标线程有启动的RunLoop,并且任务要在applicationDidFinishLaunching:之前完成,否则主线程结束会导致子线程终止。同时,引用[4]提到在Swift中使用NSThread需要注意继承NSObject使用@objc修饰方法,以兼容Objective-C的运行时。 首先,我需要创建一个继承自NSObject的自定义线程类,并添加@objc修饰的方法。然后,在该线程的入口方法中启动RunLoop,这样RunLoop会一直运行,线程。同时,需要添加一个端口(如NSPort)到RunLoop,防止它立即退出。 另外,要注意线程的安全启动。在初始化线程后,通过调用start方法启动线程,并在入口方法中配置RunLoop。同时,为了向常驻线程发送任务,可以使用performSelector(on:with:waitUntilDone:)方法,但需要确目标线程RunLoop正在运行,并正确处理方法的调用。 最后,需要验证线程是否常驻,可以通过多次执行任务来测试线程是否重复使用而没有重新创建。如果线程RunLoop正确配置,每次执行任务时应该使用同一个线程实例。 现在将这些步骤整理成代码示例,并解释每个部分的作用。同时,回答用户的问题时,需要引用相关的引用内容,特别是关于RunLoop的配置线程启动的注意事项。</think>在Swift中实现常驻线程需要结合`RunLoop`机制,确线程在执行任务后不会立即销毁。以下是具体实现方法及示例: --- ### 实现步骤 1. **自定义线程类**:继承`NSObject`以兼容Objective-C运行时[^4]。 2. **启动RunLoop**:在子线程中手动运行`RunLoop`,添加输入源(如`NSPort`)防止立即退出[^3]。 3. **任务执行方法**:使用`@objc`修饰方法,确能被Objective-C的消息机制调用[^4]。 --- ### 代码示例 ```swift import Foundation // 1. 继承NSObject以兼容Objective-C运行时 class PermanentThread: NSObject { private var innerThread: Thread? override init() { super.init() innerThread = Thread(target: self, selector: #selector(threadEntry), object: nil) innerThread?.start() } // 2. 线程入口方法(启动RunLoop) @objc private func threadEntry() { // 添加一个端口防止RunLoop立即退出 RunLoop.current.add(Port(), forMode: .default) // 启动RunLoop常驻核心) RunLoop.current.run() } // 3. 执行任务的方法(外部调用) func executeTask(_ task: @escaping () -> Void) { perform(#selector(doTask(_:)), on: innerThread!, with: task, waitUntilDone: false) } // 4. 具体任务实现 @objc private func doTask(_ task: Any?) { guard let closure = task as? () -> Void else { return } closure() } // 5. 停止线程(手动终止RunLoop) func stop() { perform(#selector(stopThread), on: innerThread!, with: nil, waitUntilDone: true) } @objc private func stopThread() { CFRunLoopStop(CFRunLoopGetCurrent()) innerThread = nil } } // 使用示例 let permanentThread = PermanentThread() permanentThread.executeTask { print("任务在子线程执行: \(Thread.current)") } ``` --- ### 关键解释 1. **RunLoop配置**:通过`RunLoop.current.run()`启动循环,并添加`NSPort`作为输入源维持跃状态[^1]。 2. **Objective-C兼容性**:使用`@objc`标记方法继承`NSObject`,确方法可被线程调用[^4]。 3. **任务投递**:通过`perform(_:on:with:waitUntilDone:)`向指定线程发送任务[^3]。 4. **线程终止**:调用`CFRunLoopStop`手动停止RunLoop,需注意线程安全[^1]。 --- ### 注意事项 - **应用生命周期**:确线程任务在`applicationDidFinishLaunching`之后仍有效。 - **内存管理**:常驻线程需手动停止,避免内存泄漏。 - **任务同步**:若需等待任务完成,设置`waitUntilDone: true`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值