美文网首页
iOS 题目详解 部分二

iOS 题目详解 部分二

作者: 飞不越疯人院 | 来源:发表于2020-09-03 14:43 被阅读0次

    主要讲解子线程的保活方式, 以及 Autorelease 对象的释放时机

    iOS 题目详解 部分一
    iOS 题目详解 部分二
    iOS 题目详解 部分三

    iOS 题目简述 部分一

    题目1. 子线程如何保活?

    首先我们知道主线程中的Runloop是默认开启的, 而子线程中的Runloop是第一次获取的时候才会创建;另外Runloop中如果没有添加任何的Timers, Observers, PortsRunloop会立即退出;因为我们可以通过向子线程中添加如上的任意一种即可保活子线程;

    • Case1 : 首先看下正常用法:
    #case1  执行完毕后线程就会立即销毁
    XThread *thread1 = [[XThread alloc] initWithTarget:self selector:@selector(thread1Excute) object:nil];
    [thread1 start];
    
    - (void)thread1Excute {
        ///执行完毕后线程就销毁了
        NSLog(@"thread1执行 线程: %@", [NSThread currentThread]);
    }
    

    执行完成后打印结果如下, 单纯的子线程, 任务执行完毕, 退出线程, 线程销毁;

    2020-09-03 14:58:26.742282+0800 RunloopMore1[2914:447091] thread1执行 线程: <XThread: 0x281ce6340>{number = 6, name = (null)}
    2020-09-03 14:58:26.743149+0800 RunloopMore1[2914:447091] 线程销毁: -[XThread dealloc]
    
    • Case2 : 即使是被controller强引用, 线程内任务执行完毕不能再次执行其他任务, 最后跟着宿主对象一起销毁;
    #子线程中默认没有开启runloop, 所以即使是被controller强引用, 
    线程内任务执行完毕后也不能再次执行其他任务, 
    类似僵尸对象跟着controller的销毁一起销毁;
        self.thread2 = [[XThread alloc] initWithBlock:^{
            NSLog(@"Thread2 执行");
        }];
        [self.thread2 start];
    

    注意:[[XThread alloc] initWithTarget:self selector:@selector() object:nil];和方法
    [self performSelector:@selector() onThread:self.thread2 withObject:nil waitUntilDone:NO];一旦执行后线程会对当控制器self造成强引用;
    如果点击了下面执行此方法, 则会导致线程和controller都不能被释放, 因为有了循环引用;

    - (IBAction)thread2Run:(id)sender {
        ///即使再次调用也没用, 因为子线程内没有开启runloop, 并且会造成循环应用导致都不能释放
        [self performSelector:@selector(thread2Excute) onThread:self.thread2 withObject:nil waitUntilDone:NO];
    }
    
    • Case3 : 线程中添加Sources, Timers, Observers, Ports等保持线程存活;
        self.thread3 = [[XThread alloc] initWithBlock:^{
                /*
             我们知道runloop中如果没有任何source0/souce1/timer/observer 则runloop会立即退出;
             所以为runloop添加source1, 然后让runloop执行run;
             */
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        }];
        [self.thread3 start];
    

    这样的 话再次点击, 仍然可以执行任务

    - (void)thread3Excute {
        NSLog(@"thread3执行 线程: %@", [NSThread currentThread]);
    }
    - (IBAction)thread3Run:(id)sender {
        [self performSelector:@selector(thread3Excute) onThread:self.thread3 withObject:nil waitUntilDone:NO];
    }
    

    关于Runlooprun方法

    If no input sources or timers are attached to the run loop, this method exits immediately; 
    otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking 
    runMode:beforeDate:. In other words, this method effectively begins an infinite loop that 
    processes data from the run loop’s input sources and timers.
    

    大致意思为: 如果没有souces或者timers添加到runloop中则方法立即退出; 如果有,将在NSDefaultRunLoopMode模式下无限次执行runMode:beforeDate:来处理添加的soucestimers;
    注意: 调用runlooprun方法后则不再能取消runloop, 即使是调用了CFRunLoopStop(CFRunLoopGetCurrent()) 也只是停了其中一次循环;
    这种方式可以使线程保活, 但是同样有问题, 那就是无法停止Runloop进而导致线程无法销毁;

    • Case4 : 线程中添加Sources, Timers, Observers, Ports等保持线程存活;正确的使用方法如下:
         #不使用 runloop 的 run 方法;  自己通过runMode:beforeDate:方法来
         #控制 runloop 进而达到控制线程生命周期的目的;
        self.runloopStop = NO;
        self.thread4 = [[XThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
            while (!weakSelf.runloopStop) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
        [self.thread4 start];
    

    当要停止Runloop时执行如下代码

    - (IBAction)thread4Kill:(id)sender {
        self.runloopStop = YES;
        [self performSelector:@selector(thread4Dealloc) onThread:self.thread4 withObject:nil waitUntilDone:YES];
        NSLog(@"如果上面waitUntilDone = YES, 则线程内方法执行完毕才能执行这里, waitUntilDone = NO, 则是并行执行;");
    }
    


    题目2. 子线程中 Autorelease对象什么时候释放的?

    首先说下结论: Autorelease对象是通过AutoreleasePool管理, 跟着线程的退出而释放, 如果线程启用了Runloop保活, 则Autorelease一直不会被释放, 直到停止Runloop退出线程时才会释放;

    下面分三种情况验证; 单纯的子线程, 子线程启用Runloop一直不销毁, 子线程启用Runloop后再次退出Runloop;
    Case1: 单纯的子线程中的Autorelease对象, 通过Autorelease管理,跟着线程的销毁一起释放;验证代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.stopRunloop = NO;
        [self case0];
        NSLog(@"%s", __func__);
    }
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"%s", __func__);
    }
    - (void)case0 {
        self.thread0 = [[XThread alloc] initWithBlock:^{
            Model *model = [[[Model alloc] init] autorelease];
             NSLog(@"%@", [NSThread currentThread]);
        }];
        [self.thread0 start];
        [self.thread0 release];
    }
    

    打印结果如下:

    2020-09-03 17:20:21.213622+0800 Test[3103:487120] -[ViewController3 viewDidLoad]
    2020-09-03 17:20:21.213832+0800 Test[3103:487120] -[ViewController3 viewWillAppear:]
    2020-09-03 17:20:21.214471+0800 Test[3103:487396] <XThread: 0x283a80c00>{number = 7, name = (null)}
    2020-09-03 17:20:21.214687+0800 Test[3103:487396] -[Model dealloc]
    

    Case2: 子线程通过启用Runloop一直保持存活, 则Autorelease对象也不会被销毁;

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.stopRunloop = NO;
        [self case1];
        NSLog(@"%s", __func__);
    }
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"%s", __func__);
    }
    - (void)case1 {
        __block typeof(self) weakSelf = self;
        self.thread1 = [[XThread alloc] initWithBlock:^{
            Model *model = [[[Model alloc] init] autorelease];
             NSLog(@"%@", [NSThread currentThread]);
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        }];
        [self.thread1 start];
        [self.thread1 release];
    }
    

    打印结果为

    2020-09-03 17:24:50.316906+0800 Test[3114:489698] -[ViewController3 viewDidLoad]
    2020-09-03 17:24:50.317296+0800 Test[3114:489698] -[ViewController3 viewWillAppear:]
    2020-09-03 17:24:50.318205+0800 Test[3114:489740] <XThread: 0x280f29400>{number = 7, name = (null)}
    

    即使是退出当前的控制器, 由于线程没有销毁则Autorelease也不会被销毁;

    2020-09-03 17:27:35.287306+0800 Test[3114:489698] -[ViewController3 dealloc]
    

    Case3: 子线程通过启用Runloop而后退出Runloop, 则Autorelease对象跟着线程的退出而销毁;

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.stopRunloop = NO;
        [self case2];
        NSLog(@"%s", __func__);
    
    }
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"%s", __func__);
    }
    - (void)case2 {
        __block typeof(self) weakSelf = self;
        self.thread2 = [[XThread alloc] initWithBlock:^{
            Model *model = [[[Model alloc] init] autorelease];
             NSLog(@"%@", [NSThread currentThread]);
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            while (!weakSelf.stopRunloop) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
        [self.thread2 start];
        [self.thread2 release];
    }
    

    打印结果为

    2020-09-03 17:28:47.386353+0800 Test[3118:490612] -[ViewController3 viewDidLoad]
    2020-09-03 17:28:47.386698+0800 Test[3118:490612] -[ViewController3 viewWillAppear:]
    2020-09-03 17:28:47.387728+0800 Test[3118:490707] <XThread: 0x2807ed780>{number = 6, name = (null)}
    

    然后我们点击屏幕, 停止当前线程的Runloop

    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self performSelector:@selector(cancleRunloop) onThread:self.thread2 withObject:nil waitUntilDone:YES];
    }
    
    - (void)cancleRunloop {
        NSLog(@"%s", __func__);
        self.stopRunloop = YES;
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
    

    打印结果如下, Autorelease跟着线程的退出而被释放;

    2020-09-03 17:29:43.055258+0800 Test[3118:490707] -[ViewController3 cancleRunloop]
    2020-09-03 17:29:43.055442+0800 Test[3118:490707] -[Model dealloc]
    

    而后退出当前界面, 线程和当前控制器销毁;

    2020-09-03 17:30:31.577495+0800 Test[3118:490612] -[XThread dealloc]
    2020-09-03 17:30:31.577802+0800 Test[3118:490612] -[ViewController3 dealloc]
    


    题目3. 主线程中 Autorelease 对象什么时候释放的?

    结论是:

    • Autoreleasepool内的在autoreleasepool结束时就释放;
      如果没有显式的调用Autoreleasepool ,也是通过Autoreleasepool管理的, 其释放时机则如下:
    • ARC: 出了当前函数作用域就会被释放(创建的对象可以理解为系统帮开发者加了autorelease);
    • MRC:在当前Runloop周期结束后释放;

    例如如下代码, model肯定是在autoreleasepool结束后就被释放了;

        @autoreleasepool {
          Model *model = [[[Model alloc] init] autorelease];
        }
    

    那么如下代码局部变量model什么时候释放

    - (void)viewDidLoad {
        [super viewDidLoad];
        Model *model = [[[Model alloc] init] autorelease];
        NSLog(@"%s", __func__);
    }
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"%s", __func__);
    }
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        NSLog(@"%s", __func__);
    }
    

    打印结果如下;

    2020-08-18 16:34:13.534459+0800 Topic2[8275:1405961] -[ViewController1 viewDidLoad]
    2020-08-18 16:34:13.534614+0800 Topic2[8275:1405961] -[ViewController1 viewWillAppear:]
    2020-08-18 16:34:13.575772+0800 Topic2[8275:1405961] -[Model dealloc]
    2020-08-18 16:34:14.077758+0800 Topic2[8275:1405961] -[ViewController1 viewDidAppear:]
    

    不论MRC还是ARCautorelease对象本质上都是通过autoreleasepool来管理的;
    下面在MRC环境下验证它, 注意使用的iOS版本为10.0之前, 因为iOS10.0之后技术应该是有升级打印不直观;

    - (void)viewDidLoad {
        [super viewDidLoad];
        Model *model = [[[Model alloc] init] autorelease];
        NSLog(@"Runloop : %@", [NSRunLoop mainRunLoop]);
        NSLog(@"%s", __func__);
        // Do any additional setup after loading the view, typically from a nib.
    }
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"%s", __func__);
    }
    #打印的Runloop的结果中添加的observer有如下:
        observers = <CFArray 0x7fb0c870e840 [0x1079867b0]>{type = mutable-small, count = 6, values = (
        0 : <CFRunLoopObserver 0x7fb0c870e9e0 [0x1079867b0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = <CFArray 0x7fb0c870e870 [0x1079867b0]>{type = mutable-small, count = 0, values = ()}}
        1 : <CFRunLoopObserver 0x7fb0c840c270 [0x1079867b0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x107fe66ab), context = <CFRunLoopObserver context 0x0>}
        2 : <CFRunLoopObserver 0x7fb0c870e740 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x107b39a54), context = <CFRunLoopObserver context 0x7fb0ca002f30>}
        3 : <CFRunLoopObserver 0x7fb0c8407ab0 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10b6b4320), context = <CFRunLoopObserver context 0x0>}
        4 : <CFRunLoopObserver 0x7fb0c870e8a0 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x107b39a99), context = <CFRunLoopObserver context 0x7fb0ca002f30>}
        5 : <CFRunLoopObserver 0x7fb0c870ea80 [0x1079867b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x107b06c4e), context = <CFArray 0x7fb0c870e870 [0x1079867b0]>{type = mutable-small, count = 0, values = ()}}
    

    我们需要注意的是_wrapRunLoopWithAutoreleasePoolHandler这两个observer而他们对应Runloop状态实际分别为0x10xa0;
    下面我们看下Runloop中的各个状态;

    /* Run Loop Observer Activities */
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        #进入Runloop   
        kCFRunLoopEntry = (1UL << 0),
        #即将处理Timer
        kCFRunLoopBeforeTimers = (1UL << 1),
        #即将处理Sources
        kCFRunLoopBeforeSources = (1UL << 2),
        #即将进入休眠 (1UL << 5 = 10 0000)
        kCFRunLoopBeforeWaiting = (1UL << 5),
        #即将唤醒休眠
        kCFRunLoopAfterWaiting = (1UL << 6),
        #即将退出Runloop 1UL << 7 = 1000 0000
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    kCFRunLoopEntry 就是Runloop即将开始; 1UL << 0 = 0x1
    kCFRunLoopExit | kCFRunLoopBeforeWaiting 对应的就是Runloop即将休眠或者退出, 也就是当前Runloop周期的结束; (1UL << 5)|(1UL << 7) = 1010 0000 = 0xa0;
    至此确定autorelease对象在MRC下当前Runloop周期进行销毁, 并且通过autoreleasepool管理;

    另外关于Autorelease对象是通过AutoreleasePool管理的验证, 在其对象销毁时时候, 看下其函数调用栈也可得出结论, 具体可以看这篇文章


    参考文章
    子线程保活测试代码
    iOS Runloop 补充
    AutoreleasePool 的原理

    相关文章

      网友评论

          本文标题:iOS 题目详解 部分二

          本文链接:https://www.haomeiwen.com/subject/fzonsktx.html