【OC】一份理解引用计数、runloop、子线程保活比较好的调试代码

以下提供了一份ViewController.m的源代码,调试工程可以做成:

AppDelegate.rootViewController = NavigationController(rootController: rootVC)

然后在rootVC中点击屏幕,self.navigationController push: viewController,然后就可以调试代码进行理解。

这份调试代码呢,最好是这样操作,代码后面有分情况的操作和原因分析,先不要去看原因分析,先按照分类情况从头到尾执行下看看日志现象,然后分析的时候按照分类情况从最后一种情况开始往上进行分析。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, assign) BOOL isAborted;
@property (nonatomic, strong) NSThread *thread1;

@end

@implementation ViewController

- (void)dealloc {
    NSLog(@"%s, %d", __func__, __LINE__);
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    NSLog(@"CFGetRetainCount-self-viewWillDisappear: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UISwitch *swit = [[UISwitch alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
    [self.view addSubview:swit];
    
    NSLog(@"CFGetRetainCount-self-1: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread1 start];
    
    NSLog(@"CFGetRetainCount-self-2: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"begin: %s, %@", __func__, [NSThread currentThread]);
    
    NSLog(@"CFGetRetainCount-self-3: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
    
    if (nil == self.thread1) {
        NSLog(@"nil == self.thread1");
    } else {
        NSLog(@"self.thread.isExcuting = %d", self.thread1.isExecuting);
        [self performSelector:@selector(test1) onThread:self.thread1 withObject:nil waitUntilDone:NO];
    }
    
    NSLog(@"CFGetRetainCount-self-4: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));

    NSLog(@"end: %s, %@", __func__, [NSThread currentThread]);
}

- (void)run {
    
    NSLog(@"CFGetRetainCount-self-5: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
    
    @autoreleasepool {
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        while (NO == _isAborted) {
            
            NSLog(@"CFGetRetainCount-self-6: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
            [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            NSLog(@"CFGetRetainCount-self-7: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
        }
        NSLog(@"CFGetRetainCount-self-8: %ld", CFGetRetainCount((__bridge CFTypeRef)(self)));
    }
}

- (void)test1 {
    [self performSelector:@selector(test2) onThread:self.thread1 withObject:nil waitUntilDone:NO];
}

- (void)test2 {
    _isAborted = YES;
//    CFRunLoopStop(CFRunLoopGetCurrent());
//    self.thread1 = nil;
    
}

/*
 1、只要dealloc方法没有调用,就能充分说明这个对象没有释放
 2、retainCount可以作为一个参考值
 3、将_isAborted = YES、self.thread1 = nil
 (1)如果不点击App屏幕,直接推出控制器,控制器不释放
 原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放
 (2)如果点击App屏幕1次,推出控制器,控制器能释放
 原因:点击一次会让循环结束,循环结束,runloop就不再持有self对象;循环结束,这个任务就可结束了,子线程执行完任务后就无效了,释放了对self的引用,并且将子线程置nil,被销毁释放内存
 (3)如果点击App屏幕2次及以上,self的引用计数不变,推出控制器,控制器能释放
 原因:因为子线程已经为nil,任务是放不进去了的,并且runloop也早就释放了self对象
 
 4、将_isAborted = YES
 (1)如果不点击App屏幕,直接推出控制器,控制器不释放
 原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放
 (2)如果点击App屏幕1次,推出控制器,控制器能释放
 原因:点击一次会让循环结束,循环结束,runloop就不在持有self对象;循环结束,这个任务就可结束了,子线程执行完任务后就无效了,虽然自身没有被销毁释放内存,但是释放了对self的引用
 (3)如果点击App屏幕2次及以上,self的引用计数持续+1,推出控制器,控制器不能释放
 原因:子线程执行完任务后就无效了,此时再往该线程中放置任务,会再次持有self对象,但是因为该线程无效无法启动,任务没有没执行,就无法释放对self的引用,
 并且不断往线程中放置任务,就不断得再次持有self对象,self的引用计数就会不断增加
 
 5、将self.thread1 = nil
 (1)如果不点击App屏幕,直接推出控制器,控制器不释放
 原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放
 (2)如果点击App屏幕1次,推出控制器,控制器不能释放
 原因:虽然子线程已经为nil,但是里面的runloop对象仍然一直持有self对象没有释放
 (3)如果点击App屏幕2次及以上,self的引用计数不变,推出控制器,控制器不能释放
 原因:因为子线程已经为nil,任务是放不进去了的,所以就不会出现「点击屏幕会再放入一个任务,self引用计数+1,任务执行完之后就-1」这样的情况
 
 6、不设置_isAborted = YES、self.thread1 = nil
 (1)如果不点击App屏幕,直接推出控制器,控制器不释放
 原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放
 (2)如果点击App屏幕1次,推出控制器,控制器不能释放
 原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放,点击屏幕会再放入一个任务,因此self引用计数+1,但是这个任务执行完之后就-1
 (3)如果点击App屏幕2次及以上,self的引用计数持续+1后-1,推出控制器,控制器不能释放
原因:子线程执行的是循环任务,任务没有结束前,线程就会一直持有self对象没有释放,点击屏幕会再放入一个任务,因此self引用计数+1,但是这个任务执行完之后就-1
 
 7、既然一切都是由于让线程保活引发的,那么把run方法中代码注释掉
 (1)如果不点击App屏幕,直接推出控制器,控制器释放
 原因:子线程执行完任务后就无效了,虽然自身没有被销毁释放内存,但是释放了对self的引用
 (2)如果点击App屏幕1次,推出控制器,控制器不能释放
 原因:子线程执行完任务后就无效了,此时再往该线程中放置任务,会再次持有self对象,但是因为该线程无效无法启动,任务没有没执行,就无法释放对self的引用
 (3)如果点击App屏幕2次及以上,self的引用计数持续+1,推出控制器,控制器不能释放
 原因:在第(2)基础上,不断往线程中放置任务,就不断得再次持有self对象,self的引用计数就会不断增加
 */


@end

 

posted @ 2024-01-18 11:59  码出境界  阅读(36)  评论(0编辑  收藏  举报