iOS-RunLoop详解(三):使用RunLoop线程保活方案

iOS-RunLoop详解(三):使用RunLoop线程保活方案

如果经常要在子线程中做事情,不使用保活,就会一直创建、销毁子线程,这样很耗性能,所以经常在子线程做事情最好使用线程保活。

实现线程保活

创建线程类,表示需要经常执行的任务

***********************🐱 MJThread.h 🐱**************************
@interface MJThread : NSThread

@end

#import "MJThread.h"

@implementation MJThread

***********************🐱 MJThread.m 🐱**************************
- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

***********************🐱 ViewController.m 🐱**************************
    
@implementation ViewController
    
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // NSThread 频繁创建线程
        MJThread *thread = [[MJThread alloc]initWithTarget:self selector:@selector(test) object:nil];
        [thread start];

    //    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
@end

RUN> 🏇🏇🏇

我们每次点击,都会去执行任务:NSThread 频繁创建线程

2021-05-14 14:55:19.054958+0800 Interview03-线程保活[4068:166884] -[ViewController test] <MJThread: 0x600000634900>{number = 7, name = (null)}
2021-05-14 14:55:19.056474+0800 Interview03-线程保活[4068:166884] -[MJThread dealloc]
2021-05-14 14:55:21.627640+0800 Interview03-线程保活[4068:166923] -[ViewController test] <MJThread: 0x60000062ddc0>{number = 8, name = (null)}
2021-05-14 14:55:21.627815+0800 Interview03-线程保活[4068:166923] -[MJThread dealloc]

可以看到,任务一执行完,线程就释放了.并且这样频繁的创建线程,很消耗资源

我们可以用RunLoop来延长线程的生命周期,不让线程挂掉

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"%s ----end----", __func__);
}

RUN> 🏇🏇🏇

2021-05-14 15:01:54.818875+0800 Interview03-线程保活[4128:171606] -[ViewController test] <MJThread: 0x60000147d100>{number = 7, name = (null)}
2021-05-14 15:01:54.819344+0800 Interview03-线程保活[4128:171606] -[ViewController test] ----end----
2021-05-14 15:01:54.821043+0800 Interview03-线程保活[4128:171606] -[MJThread dealloc]

运行一下,发现线程执行完任务还是会结束线程([MJThread dealloc])

这是因为,我们虽然获取了当前的RunLoop,并且调用run方法让RunLoop跑起来了,而run方法底层调用的是- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;方法,这个方法会把RunLoop添加到NSDefaultRunLoopMode模式下.Model中如果没有任何Source0 , Source1 , Timer , Observer,RunLoop会立马退出. 所以我们需要往RunLoop中添加任务,任何任务都可以.

我们需要往RunLoop中添加任务,任何任务都可以.

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"%s ----end----", __func__);
}

RUN> 🏇🏇🏇

2021-05-14 15:05:54.882701+0800 Interview03-线程保活[4172:174678] -[ViewController test] <MJThread: 0x600003c90f00>{number = 7, name = (null)}
2021-05-14 15:05:54.883756+0800 Interview03-线程保活[4172:174678] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅

在运行线程就不会执行完任务就挂掉了,而是执行完任务就休眠:

每一次点击屏幕,都是创建了[[MJThread alloc]initWithTarget:self selector:@selector(test) object:nil];经过[[NSRunLoop currentRunLoop] addPort[[NSRunLoop currentRunLoop] run]; 线程进入休眠了,但是我们现在没办法唤醒线程和执行线程任务,程序继续修改。

上面的代码thread是一个局部变量,每次执行任务都会重新创建,所以我们把线程设置成成员属性。

***********************🐱 ViewController.m 🐱**************************
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}

//触碰屏幕事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
    
}

// 这个方法的目的:线程保活
- (void)run {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    NSLog(@"---------- start -----------");
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"%s ----end----", __func__);
}
@end

RUN> 🏇🏇🏇

2021-05-14 15:14:16.185593+0800 Interview03-线程保活[4221:180238] -[ViewController test] <MJThread: 0x6000005ecb40>{number = 7, name = (null)}
2021-05-14 15:14:16.185768+0800 Interview03-线程保活[4221:180238] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅
2021-05-14 15:14:17.821116+0800 Interview03-线程保活[4221:180238] -[ViewController test] <MJThread: 0x6000005ecb40>{number = 7, name = (null)}
2021-05-14 15:14:17.821240+0800 Interview03-线程保活[4221:180238] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅
2021-05-14 15:14:20.824925+0800 Interview03-线程保活[4221:180238] -[ViewController test] <MJThread: 0x6000005ecb40>{number = 7, name = (null)}
2021-05-14 15:14:20.825090+0800 Interview03-线程保活[4221:180238] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅

上面的代码有两个问题:

  1. self和thread会造成循环引用,都不会释放
  2. thread一直不会死

首先解决循环引用:

#import "ViewController.h"
#import "MJThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
        //如果使用如下方式创建thread,self会引用thread,thread会引用self,会造成循环引用。
        //[[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    
        self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        
        //线程会一直阻塞这这一行,永远不会销毁
        [[NSRunLoop currentRunLoop] run]; 

        //当把NSRunLoop停掉之后,代码就会从下一行往下走,这时候任务执行完成,线程该死的时候就会死了。
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

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

    //就算把thread清空,thread也不会销毁,因为任务还没结束,线程就不会死。
    //self.thread = nil; 
}

运行后,在当前界面返回

RUN> 🏇🏇🏇

2021-05-14 15:21:44.228798+0800 Interview03-线程保活[4480:196517] <MJThread: 0x600002a04080>{number = 9, name = (null)}----begin----
2021-05-14 15:21:47.517069+0800 Interview03-线程保活[4480:195322] -[ViewController dealloc]

可以发现ViewController销毁了,但是thread还是没被销毁

很奇怪,控制器都释放了,按理说控制器内部的所有东西都应该释放了呀,我们在ViewControllerdealloc方法中把thread置为nil:

2021-05-14 15:25:09.585499+0800 Interview03-线程保活[4520:199781] <MJThread: 0x600003b4ab40>{number = 8, name = (null)}----begin----
2021-05-14 15:25:13.501110+0800 Interview03-线程保活[4520:199781] -[ViewController test] <MJThread: 0x600003b4ab40>{number = 8, name = (null)}
2021-05-14 15:25:13.501288+0800 Interview03-线程保活[4520:199781] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅
2021-05-14 15:25:16.475845+0800 Interview03-线程保活[4520:199617] -[ViewController dealloc]

thread强制置为nil,thread还是没有释放.

 self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        
        //线程会一直阻塞这这一行,永远不会销毁
        [[NSRunLoop currentRunLoop] run]; 

        //当把NSRunLoop停掉之后,代码就会从下一行往下走,这时候任务执行完成,线程该死的时候就会死了。
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];

这是因为RunLoop在 [[NSRunLoop currentRunLoop] run]这一行一直阻塞,一直不会打印----end----,这时候任务一直在进行,任务还没有完成线程就不会死,就算在ViewController的dealloc方法里面把thread清空,thread也不会死。

如果我们想要精准的控制线程的生命周期,比如说控制器销毁的时候,线程也销毁,那应该怎么做呢?我们可以像下面这样手动停止RunLoop:

- (IBAction)stop {
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

run> 🏇🏇🏇

2021-05-14 15:34:46.499408+0800 Interview03-线程保活[4566:205814] <MJThread: 0x600001f1b900>{number = 9, name = (null)}----begin----
2021-05-14 15:34:55.230839+0800 Interview03-线程保活[4566:205814] -[ViewController test] <MJThread: 0x600001f1b900>{number = 9, name = (null)}
2021-05-14 15:34:55.230956+0800 Interview03-线程保活[4566:205814] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅
2021-05-14 15:34:57.846728+0800 Interview03-线程保活[4566:205814] -[ViewController stopThread] <MJThread: 0x600001f1b900>{number = 9, name = (null)}
2021-05-14 15:35:14.203913+0800 Interview03-线程保活[4566:205687] -[ViewController dealloc]

stopRunLoop 虽然执行了,并且ViewController 也已经销毁了,但是thread 仍然没有销毁,这是为什么呢?

线程不会死的原因就是有个RunLoop一直在运行,线程一直有任务做,所以想让线程死掉,就把RunLoop停掉,当把RunLoop停掉之后,代码就会从 [[NSRunLoop currentRunLoop] run]往下走,当线程执行完任务后,线程该死的时候(当前控制器销毁后)就会死了。

我们看run方法的解释:

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.

翻译过来就是:
它通过反复调用runMode:beforeDate:在NSDefaultRunLoopMode中运行接收器。换句话说,这个方法有效地开始了一个无限循环,处理来自运行循环的输入源和计时器的数据。

可以看出,通过run方法运行的RunLoop是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)。

既然这样,那我们可以模仿run方法,写个while循环,内部也调用runMode:beforeDate:方法,如下:

while (!weakSelf.isStoped) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
//while的条件判断中要使用weakSelf,不然self强引用thread,thread强引用block,block强引用self,产生循环引用

不使用run方法,我们就能停掉RunLoop了,停掉RunLoop系统有提供API是CFRunLoopStop(CFRunLoopGetCurrent()),但是这个API不能在ViewController的dealloc方法里面写,因为ViewController的dealloc方法是在主线程调用的,我们要保证在子线程调用CFRunLoopStop(CFRunLoopGetCurrent())。

#import "ViewController.h"
#import "MJThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;

    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);

        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }

        NSLog(@"%@----end----", [NSThread currentThread]);

        // NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
        //        [[NSRunLoop currentRunLoop] run];
        /*
         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
         */

    }];
    [self.thread start];
}



- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
}

- (IBAction)stop {
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    //就算把thread清空,thread也不会销毁,因为任务还没结束,线程就不会死。
    self.thread = nil;
    
//    [self stop];
}

@end

RUN> 🏇🏇🏇

2021-05-14 15:39:47.441947+0800 Interview03-线程保活[4606:209299] <MJThread: 0x600000f47580>{number = 8, name = (null)}----begin----
2021-05-14 15:39:48.914864+0800 Interview03-线程保活[4606:209299] -[ViewController test] <MJThread: 0x600000f47580>{number = 8, name = (null)}
2021-05-14 15:39:48.915021+0800 Interview03-线程保活[4606:209299] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅
2021-05-14 15:39:50.900749+0800 Interview03-线程保活[4606:209299] -[ViewController stopThread] <MJThread: 0x600000f47580>{number = 8, name = (null)}
2021-05-14 15:39:50.901004+0800 Interview03-线程保活[4606:209299] <MJThread: 0x600000f47580>{number = 8, name = (null)}----end----
2021-05-14 15:39:59.322333+0800 Interview03-线程保活[4606:209161] -[ViewController dealloc]
2021-05-14 15:39:59.322455+0800 Interview03-线程保活[4606:209161] -[MJThread dealloc]

上面要使用weakself,不然self强引用thread,thread强引用block,block强引用self,产生循环引用(使用weakself之后,就是self强引用thread,thread强引用block,block弱引用self,不会产生循环引用)。

运行代码,进入界面,打印:

2021-05-14 15:39:47.441947+0800 Interview03-线程保活[4606:209299] <MJThread: 0x600000f47580>{number = 8, name = (null)}----begin----

说明线程开始工作了。

点击空白,打印:

2021-05-14 15:42:22.608171+0800 Interview03-线程保活[4606:211084] -[ViewController test] <MJThread: 0x600000f38540>{number = 9, name = (null)}
2021-05-14 15:42:22.610916+0800 Interview03-线程保活[4606:211084] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅

说明RunLoop接收到事件,开始处理事件。

点击stop打印:

2021-05-14 15:42:35.860716+0800 Interview03-线程保活[4606:211084] -[ViewController stopThread] <MJThread: 0x600000f38540>{number = 9, name = (null)}
2021-05-14 15:42:35.861011+0800 Interview03-线程保活[4606:211084] <MJThread: 0x600000f38540>{number = 9, name = (null)}----end----

可以看出,执行了CFRunLoopStop,并且线程任务完成,打印了----end----。

点击stop之后再退出当前VC,打印:

2021-05-14 15:44:08.504631+0800 Interview03-线程保活[4606:209161] -[ViewController dealloc]
2021-05-14 15:44:08.504806+0800 Interview03-线程保活[4606:209161] -[MJThread dealloc]

可以发现,当前VC和thread都被销毁了。

上面代码还有一个问题,就是我们每次都要先点击停止再返回当前VC,这样很麻烦,可能你会说可以把[self stop]方法写在ViewController的dealloc方法里面,试了下,发现报错坏内存访问:

image-20210514155504725

这是为什么呢?这就是我们上面讲的waitUntilDone造成的.我们在[self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:NO];中把waitUntilDone设置为NO,就表示在子线程执行的stopRunLoop函数和在主线程执行的- (IBAction)stop函数是同时执行的.一旦- (IBAction)stop函数先执行完,那么ViewControllerdealloc函数也会立马执行完毕,ViewController就会释放.这时候再去执行stopRunLoop就会报坏内存访问,因为ViewController已经释放了.为什么会崩溃到[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];这一行呢?

现在你应该明白为什么会在RunLoop那行代码报坏内存访问错误了吧!

解决办法也很简单,dealloc方法里面调用[self stop],并且将上面NO改成YES。

- (IBAction)stop {
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
    NSLog(@"%s", __func__);
    
   [self stop];
}

运行代码,直接返回当前VC,打印:

2021-05-14 15:59:38.055083+0800 Interview03-线程保活[4787:225673] -[ViewController dealloc]
2021-05-14 15:59:38.055307+0800 Interview03-线程保活[4787:225794] -[ViewController stopThread] <MJThread: 0x600000a8dd00>{number = 8, name = (null)}

我们点击返回退出控制器后ViewController释放了,但是没有看到线程释放

其实那个RunLoop的确停掉了,但是停掉之后,他会再次来到while循环判断条件:

我们在while循环中打一个断点:

image-20210514160136836

这时候当前控制器已经被销毁,weakSelf指针已经被清空,这时候!nil获取的就是YES,所以会再次进入循环体启动RunLoop,RunLoop又跑起来了,线程又有事情干了,所以线程不会销毁。

解决办法:

while (weakSelf && !weakSelf.isStoped) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

再次运行项目,返回当前VC

2021-05-14 16:03:29.471773+0800 Interview03-线程保活[4863:229852] <MJThread: 0x600000452e40>{number = 9, name = (null)}----end----
2021-05-14 16:03:29.472196+0800 Interview03-线程保活[4863:229852] -[MJThread dealloc]

再次运行项目,点击暂停,返回当前VC,这时候又崩了

image-20210514160513785

点击暂停之后RunLoop肯定停掉了,RunLoop停掉后,这时候的线程就不能用了,runloop停止掉后它的任务就执行完了,线程的生命周期已经结束了,这时候它已经不能再执行任务了.但是这时候thread还没销毁(还没调用dealloc),因为thread还被self引用着,我们点击返回按钮,又让子线程去执行stopRunLoop任务就会报错,这时候访问一个不能用的thread就会报坏内存访问错误。

解决办法也很简单,暂停RunLoop后把thread指针置为nil,并且如果发现子线程为nil就不在子线程执行任务了。

image-20210514160945789

最后的完整代码

#import "ViewController.h"
#import "MJThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;

    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);

        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }

        NSLog(@"%@----end----", [NSThread currentThread]);

        // NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
        //        [[NSRunLoop currentRunLoop] run];
        /*
         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
         */

    }];
    [self.thread start];
}



- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
}

- (IBAction)stop {
    // 在子线程调用stop
    if (!self.thread) return;
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.thread = nil;
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

@end
特别备注

本系列文章总结自MJ老师在腾讯课堂iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容