iOS NSThread 如何安全传递参数?

99ANYc3cd6
预计阅读时长 29 分钟
位置: 首页 参数 正文

核心思想

NSThread 的初始化方法 - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument; 中,object 参数就是我们传递参数的入口。

ios nsthread 带参数
(图片来源网络,侵删)
  • target: 将要执行任务的对象(通常是 self)。
  • selector: 目标对象上要执行的方法(这个方法必须有且只有一个参数)。
  • object: 传递给 selector 方法的参数,它必须是 id 类型,也就是任何 Objective-C 对象。

关键在于如何设计你的 selector 方法来接收这个 object 参数。


直接传递简单参数(最直接)

这是最简单直接的方式,适用于传递简单的、不可变的对象,如 NSString, NSNumber, NSArray, NSDictionary 等。

步骤:

  1. 定义一个方法:这个方法接收一个 id 参数。
  2. 创建并启动线程:使用 initWithTarget:selector:object: 将参数通过 object 传递。

代码示例:

ios nsthread 带参数
(图片来源网络,侵删)
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 1. 准备要传递的参数
    NSString *message = @"Hello from a new thread!";
    NSNumber *count = @100;
    // 2. 创建线程,并传递 message 作为参数
    // 注意:这里的 object 参数会作为 - (void)myTask:(id)param 方法的参数
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(myTask:) object:message];
    [thread1 setName:@"MyFirstThread"];
    [thread1 start];
    // 3. 再创建一个线程,传递 count 作为参数
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(myTask:) object:count];
    [thread2 setName:@"MySecondThread"];
    [thread2 start];
}
// 4. 定义一个接收 id 类型参数的方法
- (void)myTask:(id)param {
    // 获取当前线程信息
    NSLog(@"%@ - Task started with param: %@", [NSThread currentThread], param);
    // 模拟耗时操作
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"%@ - Task finished", [NSThread currentThread]);
}
@end

控制台输出:

2025-10-27 10:30:00.123 MyFirstThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyFirstThread} - Task started with param: Hello from a new thread!
2025-10-27 10:30:00.124 MySecondThread[12345:67891] <NSThread: 0x600002d8d000>{number = 4, name = MySecondThread} - Task started with param: 100
2025-10-27 10:30:02.125 MyFirstThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyFirstThread} - Task finished
2025-10-27 10:30:02.125 MySecondThread[12345:67891] <NSThread: 0x600002d8d000>{number = 4, name = MySecondThread} - Task finished

优点:

  • 简单、直观,易于理解。

缺点:

  • 只能传递一个参数,如果你的任务需要多个参数,这种方法就不适用了。
  • 参数必须是对象,不能直接传递基本数据类型(如 int, float),你需要将它们包装成 NSNumber 对象。

传递包含多个参数的字典(最常用)

当你需要传递多个参数时,创建一个 NSDictionary 来容纳所有参数是最佳实践。

ios nsthread 带参数
(图片来源网络,侵删)

步骤:

  1. 创建一个字典:将所有需要传递的参数以 key-value 的形式存入字典。
  2. 创建并启动线程:将整个字典作为 object 参数传递。
  3. 在任务方法中:从字典中取出对应的参数。

代码示例:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 1. 创建一个参数字典
    NSDictionary *params = @{
        @"username": @"Alice",
        @"age": @30,
        @"taskID": @101
    };
    // 2. 创建线程,传递整个字典作为参数
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(processUserData:) object:params];
    [thread start];
}
// 3. 定义一个接收字典参数的方法
- (void)processUserData:(NSDictionary *)params {
    NSLog(@"%@ - Processing user data started.", [NSThread currentThread]);
    // 4. 从字典中取出参数
    NSString *username = params[@"username"];
    NSInteger age = [params[@"age"] integerValue];
    NSInteger taskID = [params[@"taskID"] integerValue];
    NSLog(@"Username: %@, Age: %ld, TaskID: %ld", username, (long)age, (long)taskID);
    // 模拟耗时操作
    [NSThread sleepForTimeInterval:1.5];
    NSLog(@"%@ - Processing user data finished.", [NSThread currentThread]);
}
@end

控制台输出:

2025-10-27 10:35:00.123 Thread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = (null)} - Processing user data started.
2025-10-27 10:35:00.124 Thread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = (null)} - Username: Alice, Age: 30, TaskID: 101
2025-10-27 10:35:01.624 Thread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = (null)} - Processing user data finished.

优点:

  • 可以轻松传递任意数量的参数。
  • 代码结构清晰,参数有明确的 key,不易出错。

缺点:

  • 需要手动管理 keykey 拼写错误,会导致运行时错误(nil 值)。

使用 Block(现代、灵活、推荐)

这是目前最推荐、最现代的方式,它将数据和代码封装在一起,避免了 target-action 模式的分离,代码更具可读性和可维护性。

步骤:

  1. 创建一个 Block,在 Block 内部定义你的任务逻辑和参数。
  2. 使用 performSelector:inThread:withObject:waitUntilDone: 方法,将 Block 作为参数传递给新线程。

代码示例:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 1. 创建一个新的线程对象(但不立即启动)
    NSThread *thread = [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
    [thread setName:@"MyBlockThread"];
    // 2. 定义一个包含参数和逻辑的 Block
    // 注意:Block 本身也是一个对象
    void (^myTaskBlock)(void) = ^{
        NSLog(@"%@ - Block task started.", [NSThread currentThread]);
        // 在这里可以直接使用 Block 作用域内的变量
        NSString *message = @"This is a block-based task.";
        NSInteger iterations = 5;
        for (int i = 0; i < iterations; i++) {
            NSLog(@"%@ - Iteration %d", [NSThread currentThread], i);
            [NSThread sleepForTimeInterval:0.5];
        }
        NSLog(@"%@ - Block task finished.", [NSThread currentThread]);
    };
    // 3. 启动线程,并将 Block 作为参数传递
    // withObject: 可以传递任何对象,这里我们传递 Block
    [thread performSelector:@selector(start) inThread:thread withObject:nil waitUntilDone:NO];
    // 或者,如果你想在主线程上执行一个任务在新线程中:
    [self performSelector:@selector(runTaskOnThread:) onThread:thread withObject:myTaskBlock waitUntilDone:NO];
}
// 这个方法只是用来接收 Block 并在新线程中执行它
- (void)runTaskOnThread:(void (^)(void))taskBlock {
    if (taskBlock) {
        taskBlock(); // 执行 Block
    }
}
@end

控制台输出:

2025-10-27 10:40:00.123 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Block task started.
2025-10-27 10:40:00.123 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Iteration 0
2025-10-27 10:40:00.624 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Iteration 1
2025-10-27 10:40:01.125 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Iteration 2
2025-10-27 10:40:01.625 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Iteration 3
2025-10-27 10:40:02.126 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Iteration 4
2025-10-27 10:40:02.626 MyBlockThread[12345:67890] <NSThread: 0x600002d8c000>{number = 3, name = MyBlockThread} - Block task finished.

优点:

  • 代码内聚:数据和操作逻辑在一起,非常清晰。
  • 支持多个参数:无需包装,Block 可以直接访问其外部的局部变量。
  • 现代、灵活:是苹果推荐的开发模式之一。

缺点:

  • 相比 target-action,语法稍微复杂一点,需要理解 Block 的概念。

总结与最佳实践

方法 优点 缺点 适用场景
直接传递简单参数 简单直接 只能传一个参数,必须是对象 传递单个、简单的数据
传递参数字典 可传递多个参数,结构清晰 需要管理 key,拼写错误易导致 bug 需要传递多个不同类型参数的复杂任务
使用 Block 现代、灵活、可读性高、支持多参数 语法稍复杂,需理解 Block 强烈推荐,几乎所有需要传参的场景都适用

重要提醒:线程安全

在任何多线程环境中,当你在线程间共享数据(修改一个实例变量)时,都必须考虑线程安全问题,如果多个线程同时读写同一个变量,可能会导致数据错乱或程序崩溃。

解决线程安全问题的最简单方法是使用 @synchronized 块:

// 假设 self.sharedData 是一个实例变量
@synchronized(self) {
    // 在这里执行对 sharedData 的读写操作
    self.sharedData = @"New Value";
}

对于现代 iOS 开发,除非有特殊需求(如兼容旧代码或需要精细控制线程生命周期),否则 更推荐使用 GCD (Grand Central Dispatch),GCD 的语法更简洁,性能更好,并且内置了强大的线程安全机制(如 dispatch_barrier_async)。

用 GCD 实现上面的 Block 示例会非常简单:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 这里的代码就在一个后台线程中执行
    // 可以直接访问外部变量
    NSString *message = @"This is a GCD task.";
    NSLog(@"%@ - %@", [NSThread currentThread], message);
});
-- 展开阅读全文 --
头像
方太智能升降油烟机多少钱
« 上一篇 2025-12-31
latitude e7480 拆机
下一篇 » 2025-12-31

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]