iOS多线程_01_简介和NSThread

我去, 好蛋疼, 刚刚写好的博客就因为手贱在触控板上右划了一下, 写的全丢了, 还得重新写, 博客园就没有针对这种情况的解决方案吗?都不想写了

一、iOS中多线程的实现方案有四种

iOS多线程_01_简介和NSThread_第1张图片

  1、NSThread陷阱非常多, 有缺陷, 不过是OC的, 偶尔用一下

  2、GCD是在iOS4推出的, 能充分利用设备的多核, 而且不用考虑线程, 性能比NSThread好的多

       GCD研究起来就比较深了, 所以在面试的时候会经常被问到

  3、NSOperation封装了很多实用的功能, 某些情况下, GCD实现起来比较难的反而用NSOperation实现起来很简单, 苹果推荐大家使用NSOperation

  因为NSOperation封装的非常好, 所以使用起来非常简单, 没有什么技术难度, 在面试中就不怎么问

二、NSThread

  NSThread使用起来简单, 比较灵活, 虽然不经常用, 但是必须要看看下面的用法, 有很多技术点要注意

  (1) 通用的方法:currentThread

NSThread *thread = [NSThread currentThread];

  当前线程, 在所有的(指NSThread\GCD\NSOpertion)多线程技术中都可以使用来获得当前线程

  (2) 第一种方法, 最简单的一种

  /** 不带参数的实例化initWithTarget方法 */

// 1. 实例化线程对象, run 是一个耗时的任务,放在其他线程工作,就不会影响主线程了!
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    
// 2. 启动线程,会立即调用run方法
[thread start];

  实例化一个线程对象, 把耗时任务放在其它线程

  必须start才会执行run方法

  /** 带参数的实例化initWithTarget方法 */

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"hehe"];
thread.name = @"thread initWithTarget";

  注意, 带参数@selector(run:)加冒号

  可以设置thread对象的name属性

  (3) 第二种方法

  /** 不带参数的detach方法 */

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
- (void)run

  会立即启动新的线程, 运行run方法

/** 带参数的detach方法 */

// object是调用方法的第一个参数
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"hello"];
- (void)run:(id)obj

  注意, 带参数@selector(run:)加冒号

  (4) 第三种方法

// NSObject定义的方法,隐式的多线程调用
[self performSelectorInBackground:@selector(run:) withObject:@"NSObject"];

  隐式的多线程调用, 隐式创建并启动线程

  在NSThread.h文件中, 有这个performSelectorInBackground方法

  注意: 是self调用此方法

  (5) 说明: NSThread就这三种方式创建子线程, 其实并不难

  后2种创建线程方式的优缺点
  优点:简单快捷
  缺点:无法对线程进行更详细的设置

  (6) 线程的调度优先级

  + (double)threadPriority;

  + (BOOL)setThreadPriority:(double)p;

  - (double)threadPriority;

  - (BOOL)setThreadPriority:(double)p;

  调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高

  自己开发时,不要修改优先级,否则一旦出现“优先级反转”会非常麻烦

  (7) 控制线程状态

  • 启动线程

  - (void)start;

  // 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

  • 阻塞(暂停)线程

  + (void)sleepUntilDate:(NSDate *)date;

  + (void)sleepForTimeInterval:(NSTimeInterval)ti;

  // 进入阻塞状态

  比如让线程休眠3s

  // 线程休眠(3s)
  [NSThread sleepForTimeInterval:3.0];
  • 强制停止线程

  + (void)exit;

  // 进入死亡状态

  // 强行终止当前线程(杀掉)
  [NSThread exit];

  注意:一旦线程停止(死亡)了,就不能再次开启任务

  (8) NSThread 的注意事项 (非常重要)

  ① NSObject的多线程方法使用的是NSThread的多线程技术

  ② NSThread创建的线程,不会自动使用@autoreleasepool(主线程默认是有自动释放池的,在main.m文件中可以看到,但是NSThread创建的子线程默认没有自动释放池

  ③ 在使用NSObject或NSThread的多线程技术时,如果涉及到对象分配,需要手动添加@autoreleasepool,不添加有可能内存泄露。

  (9) 补充一下@autoreleasepool的知识

  • iOS开发中的内存管理

  ① 在iOS开发中,并没有JAVA或C#中的垃圾回收机制

  ② 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加了retain、release和autorelease

  • 自动释放池的工作原理

  ① 标记为autorelease的对象在出了作用域范围后,会被添加到最近一次创建的自动释放池中

  ② 当自动释放池被销毁或耗尽时,会向自动释放池中的所有对象发送release消息

  ③ 每个线程都需要有@autoreleasepool,否则可能会出现内存泄漏,但是使用NSThread多线程技术,并不会为后台线程创建自动释放池

  • 自动释放池常见面试代码
  for (int i = 0; i < largeNumber; ++i)
  {
      NSString *str = @"Hello World";
      str = [str stringByAppendingFormat:@" - %d", i];
      str = [str uppercaseString];
      NSLog(@"%@", str);
  }

  问:以上代码存在什么样的问题?如果循环的次数非常大时,应该如何修改?

  先要明白,str 存放在哪儿,因为 str 是常量字符串对象,所以存放在文字常量区存放常量字符串,程序结束后由系统释放),即静态区

  分别打印 str 的地址来分析一下

    NSString *str = @"hello";
    NSLog(@"1. %p", str);
                
    // 转换成大写
    str = [str uppercaseString];
    NSLog(@"2. %p", str);
                
    // 拼接字符串
    str = [NSString stringWithFormat:@"%@-123", str];
    NSLog(@"3. %p", str);

 

  打印结果是:

  1. 0x1000012c8

  2. 0x100500320

  3. 0x1005003a0

  可以看到1的地址跟2、3的地址有很大不同,因为在调用 uppercaseString 这个方法的时候,就在堆中复制了一份 str ,调用拼接字符串方法中又复制了一份,因为已经不是在文字常量区操作数据了,常量字符串对象str已经不是当初的自己了,需要用到内存管理,因此这段代码的主要问题是没有使用自动释放池来管理对象。为 for 外部添加一个自动释放池:

        @autoreleasepool {
            for (int i = 0; i < 1; i++) {
                NSString *str = @"hello";
                NSLog(@"1. %p", str);
                
                // 转换成大写
                str = [str uppercaseString];
                NSLog(@"2. %p", str);
                
                // 拼接字符串
                str = [NSString stringWithFormat:@"%@-123", str];
                NSLog(@"3. %p", str);
            }
        }

 

  如果循环的次数非常大,自动释放池无法存放所有循环产生的变量,那么就在 for 内部增加自动释放池,每次循环都释放一次变量,代码如下:

        for (int i = 0; i < 1; i++) {
            @autoreleasepool {
                NSString *str = @"hello";
                NSLog(@"1. %p", str);
                
                // 转换成大写
                str = [str uppercaseString];
                NSLog(@"2. %p", str);
                
                // 拼接字符串
                str = [NSString stringWithFormat:@"%@-123", str];
                NSLog(@"3. %p", str);
            }
        }

你可能感兴趣的:(thread)