一、线程失活
- 新建OC工程, 定义
BWThread
继承自NSThread
, 重写-dealloc
方法如下图
Main.storyboard
中结构如下
ViewController
中代码如下
- 运行程序,
push
到ViewController
中, 有如下打印
- 当创建的子线程执行完
block
后, 会立即释放掉
二、Runloop线程保活
- 每一条线程都有与之相对应的唯一一个
RunLoop
, 只有在主动获取RunLoop
时才会创建(主线程中的RunLoop由系统自动创建) - 我们可以通过在线程中使用
RunLoop
来对线程保活 - 当然, 我们在使用
RunLoop
对线程进行保活的时候, 不能仅仅运行就行了, 因为RunLoop
当前执行的_currentModel
中如果没有Sources0, Sources1, Timers, Observers
, 那么RunLoop
会自动退出
- 所以我们需要创建一个事件让
RunLoop
处理, 这样RunLoop
才不会退出 - 运行程序,
thread
留在了内存中, 没有被释放
- 我们可以引用
thread
, 然后在touchesBegan:withEvent:
方法中,给子线程添加事件
- 运行后点击屏幕, 可以看到
thread
保持活性, 依然在可以事件
- 如果去掉
RunLoop
, 可以看到,不论怎么点击屏幕, 都不会再有事件, 此时thread
就会失活, 所以就算被ViewController
强引用, 依然无法保持thread
的活性
三、释放RunLoop保活的线程
- 在
ViewController
重写-dealloc
方法, 可以发现当ViewController
退出被销毁时,thread
依然留在了内存中, 没有被释放, 说明thread
发生了内存泄漏
-
我们可以通过停止
RunLoop
的运行, 来释放thread
-
在
ViewController
上添加一个按钮, 我们要在点击释放
按钮时, 将RunLoop
停止
ViewController
中代码如下, 当点击释放
按钮时, 在thread
中停止RunLoop
- 运行程序, 点击
释放
按钮, 可以发现RunLoop
并没有被停止, 点击屏幕依然触发事件
- 我们可以查看
RunLoop
的-run
方法的介绍
- 可以看到
-run
方法里面, 实际上是一个死循环, 在不停的调用-runMode:beforeDate:
方法 - 而我们通过
CFRunLoopStop(CFRunLoopGetCurrent())
释放掉的, 只不过是其中某一次循环中的-runMode:beforeDate:
而已 - 所以我们在调用
RunLoop
的时候, 需要使用-runMode:beforeDate:
方法, 而不是-run
方法
- 运行程序, 可以看到, 当点击屏幕后,
thread
在执行一次事件之后就会失活, 所以我们需要对-runMode:beforeDate:
进行循环处理
- 再次运行程序, 可以看到点击屏幕后可以连续的响应事件, 只不过
释放
之后RunLoop
依然在工作
-
这是因为
-runMode:beforeDate:
被停止后, 通过循环又进行了一次-runMode:beforeDate:
-
所以, 我们需要使用一个标识来控制是否循环
-runMode:beforeDate:
- 再次运行程序, 就可以控制
thread
的存活了
- 将
ViewController
销毁, 可以看到thread
也销毁了
设置thread
随着ViewController
释放一起释放
- 运行程序, 进入
ViewController
后直接退出, 可以发现ViewController
被释放了, 而thread
并没有释放
- 在
-dealloc
中执行-freeThread:
方法, 执行程序
- 根据打印的信息, 可以看到执行了
stop
方法后,thread
依然没有被释放 - 这主要是因为, 在
ViewController
的-dealloc
方法执行时,ViewController
已经处于被释放的状态, 当需要执行到stop
时,ViewController
已经被释放 - 所以, 我们需要设置当
thread
的任务-stop
执行完之后, 在执行后面的代码
- 接着我们执行程序, 可以发现,
-stop
执行完之后才执行的-dealloc
, 说明-stop
执行完之后,ViewController
才被释放 - 只是此时
thread
依然没有被释放
- 这是因为, 等到
RunLoop
被停止, 再次进入while
循环判断的时候ViewController
已经被释放, 此时的weakSelf
的值为nil
, 所以!weakSelf.isStop
的值为YES
, 再次进入了循环, 启动了RunLoop
- 所以, 在开启RunLoop的循环条件中, 加入
weakSelf
必须有值的条件语句
- 这样, 就可以在
ViewController
退出时,thread
也跟着正常释放
解决: 点击释放后退出ViewController
时崩溃的问题
- 当进入
ViewController
后, 先点击释放
按钮, 使thread
失活后再次pop
掉ViewController
, 就会发生运行时错误
- 这主要是因为
waitUntilDone
设置为YES
, 程序会等thread
执行完之后再执行后面的代码, 此时thread
已经失活, 所以才会出现运行时错误 - 我们可以再停止
thread
之后, 将self.thread
置空, 再加入下面的判断, 就可保证程序的正常运行
- 运行程序, 进入
ViewController
后, 先点击释放
按钮, 使thread
失活后再次pop
掉ViewController
, 可以看到程序正常运行
- 其中
ViewController
和thread
也都被释放了