iOS多线程_01_简介和NSThread

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

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

  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);
            }
        }
posted @ 2014-08-21 19:09  微博_裕之都  阅读(270)  评论(0编辑  收藏  举报