iOS底层--GCD应用

手动目录

  • GCD简介
  • 信号量
    信号量的理解
    信号量的使用
    信号量的代码实操
    信号量阻塞哪里的任务?
  • 栅栏函数
    dispatch_barrier_async
    dispatch_barrier_sync
    栅栏的实际应用
  • 调度组
  • dispatch_source
    dispatch_source_t 的计时器使用
    dispatch_source_t 计时器 注意点
  • 延时执行
  • 单利

iOS底层-- 进程、线程、队列里理清了线程、队列等基本概念。
说了这么多,都是为了多线程线程做准备。

GCD 简介

什么是GCD
全称:Grand Central Dispatch
纯C语言,提供了非常多强大的函数

GCD优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD是用的比较多的是用来解决多线程的问题。那么除了多线程,GCD还有那些应用?

信号量(Semaphore)

在头文件中找官方API说明

dispatch_semaphore_wait 的说明 有一个重点。

  • dispatch_semaphore_wai的作用是减少信号量。
  • 如果信号量小于0,会产生等待 :( If the resulting value is less than zero)

信号量的理解

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

举例来说
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

以上描述 引用于百度百科-信号量

简单来说 :
信号量小于0则阻塞线程,大于等于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程有序进行的目的。
如果创建的信号量为1 那么就可以通过信号量的操作,进行阻塞线程,来达到同步效果。

特别提醒:
信号量<0 才会阻塞线程,>=0 任务可以顺利通过
网上有些博客/文章里面都说信号量为0就会产生阻塞,其实是错误的,在头文件的wait API说明可以看到。

信号量的使用

信号量的使用很简单,就三步

  • dispatch_semaphore_create 创建一个semaphore
  • dispatch_semaphore_signal 发送一个信号
  • dispatch_semaphore_wait 等待信号
// 创建一个信号量 (创建一个同时可以停车50辆的停车场)
dispatch_semaphore_t sema = dispatch_semaphore_create(50);        

// 等待信号 (信号总量-1)   (停车场里进来一个一辆车)
// DISPATCH_TIME_FOREVER   等待时长
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

// 发送一个信号(信号总量+1) (停车场出来了一辆车)
dispatch_semaphore_signal(sema);

信号量的代码实操

有这样的一段代码

- (void)task {
    __block int a = 0;
    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            a ++;
        });
    }
    NSLog(@"a = %d",a);
}

猜想打印结果是什么?打印的结果是否是a的最终值?
结果是:a的值>=5。
这里有2个注意点: while 类似一个堵塞线程的作用,如果a<5 ,就不可能走出while循环,所以a的值至少=5,同时 async 异步开启线程,需要耗时,所以执行一次a++的时候,可能进行了多次while循环,所以a的值大概率是 >5 小概率=5。

a的值不是最终值。
因为异步耗时,那么while可能循环了1oo次,这1oo次的任务中可能还没有执行完,就进行了打印。

那么,要如何才让其进行有序循环呢?

  • 方案1、异步改同步
- (void)task {
    __block int a = 0;
    while (a < 5) {
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            a ++;
        });
    }
    NSLog(@"a = %d",a);
}
  • 方案2、信号量
    1、创建一个信号量为1 的信号:
    dispatch_semaphore_t sema = dispatch_semaphore_create(1);
    2、执行一次任务之后,进行信号量 -1
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    3、一个任务结束后,发送一个信号+1
    dispatch_semaphore_signal(sema);

问题在于:在哪里进行 wait,哪里进行 signal?
因为信号量是堵塞线程,起到同步的效果。所以应该在任务来了之后,就要开始阻塞,任务执行完,就要释放信号。最后的结果是:

- (void)task {
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    __block int a = 0;
    while (a < 5) {
        NSLog(@"1");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"2");
            a ++;
            dispatch_semaphore_signal(sema);
        });
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }
    NSLog(@"a = %d",a);
}

这样的话,打印的结果就是5

  • 错误的使用
    思考下面的信号量使用的最终结果
- (void)task {
       dispatch_semaphore_t sema = dispatch_semaphore_create(1);
       __block int a = 0;
    while (a < 5) {
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        NSLog(@"1");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"2");
            a ++;
            dispatch_semaphore_signal(sema);
        });
    }
//    sleep(1);
    NSLog(@"a = %d",a);
}

打印结果是

16:33:22.264515+0800  [94754:1216745] 1
16:33:22.264675+0800  [94754:1216745] 1
16:33:22.264692+0800  [94754:1216876] 2
16:33:22.264836+0800  [94754:1216745] 1
16:33:22.264855+0800  [94754:1216876] 2
16:33:22.264956+0800  [94754:1216745] 1
16:33:22.264966+0800  [94754:1216876] 2
16:33:22.265095+0800  [94754:1216745] 1
16:33:22.265110+0800  [94754:1216876] 2
16:33:22.265469+0800  [94754:1216745] 1
16:33:22.265487+0800  [94754:1216876] 2
16:33:22.266427+0800  [94754:1216745] a = 5
16:33:22.266446+0800  [94754:1216876] 2

最后的结果不是我们想要的
因为信号量<0 才会阻塞,所以 线程被阻塞的时候,已经进行了2次循环,第三次循环的时候,被阻塞在wait的位置,也就是说在 a++ 执行之前,有一个a++的任务在等待,所以在a == 5的时候,跳出了循环,但是还有一个a++任务要执行,所以最后 会先打印出 a = 5, 最后在执行一次 a++

信号量阻塞哪里的任务?

  • 信号量阻塞的是 dispatch_semaphore_wait所在的线程任务。

  • 1、验证子线程创建(creat),主线程阻塞(wait)

- (void)task1 {
    //在主线程写一段代码,并运行
    NSLog(@"1");
    __block dispatch_semaphore_t s;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        s = dispatch_semaphore_create(0);
    });
    
    sleep(1);
    
    dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
    NSLog(@"3");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_signal(s);
    });
}

这一段代码中,创建信号量 在子线程, wait 在 主线程,运行发现 主线程上的 NSLog(@"3")不能被执行 。

  • 2、验证主线程创建(creat),子线程阻塞(wait)
- (void)task2 {
    //在主线程写一段代码,并运行
    NSLog(@"1");
    dispatch_semaphore_t s = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
        NSLog(@"2");
    });
    sleep(1);
    NSLog(@"3");
}

这一段代码中,创建信号量 在主线程, wait 在 子线程,运行发现 主线程上的 NSLog(@"3")能被执行 。而与wait同一线程的 NSLog(@"2") 不能被执行

信号量阻塞的任务是 wait所在线程上的任务,其他线程的任务没有影响。

更多信号量练习 可看 这里

栅栏函数

先头文件中看官方API说明:

具体内容自查
大概意思是:

  • 这个API的机制是提交一个栅栏任务到一个分发队列。类似于dispatch_async()/dispatch_sync()
  • 栅栏任务 只有在提交到 创建的并发队列 才能起作用
  • 栅栏任务不会运行,只到在栅栏任务之前的任务全部执行完成。并且栅栏之后的任务只有在栅栏任务执行完成之后才会开始执行。
  • 如果使用的不是创建的并发队列。将按照dispatch_async()/dispatch_sync()的方式进行执行。
  • dispatch_barrier_async(queue, <#^(void)block#>)

提交一个任务并异步执行
阻塞队列的任务

  • dispatch_barrier_sync(queue, <#^(void)block#>)

提交一个任务并同步执行
阻塞队列的任务,同时阻塞当前线程的任务(因为同步)

dispatch_barrier_async

1、同一队列的多个任务,中间加栅栏

- (void)task3 {
    dispatch_queue_t t = dispatch_queue_create("queue.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(t, ^{
        NSLog(@"1");
    });
    dispatch_async(t, ^{
        sleep(1);
        NSLog(@"2");
    });
    dispatch_barrier_async(t, ^{
        NSLog(@"栅栏-------------");
    });
    dispatch_async(t, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
}

// 打印结果:
18:26:46.116986+0800 [57163:441792] 4
18:26:46.116995+0800 [57163:441931] 1
18:26:47.121611+0800 [57163:441928] 2
18:26:47.122013+0800 [57163:441928] 栅栏-------------
18:26:47.122441+0800 [57163:441928] 3

从打印结果看 1、2、3 和栅栏在同一队列,但是3在栅栏之后,所以即使2的任务耗时长,也依然是要等2执行完,才能执行3。 4在主线程,和其他的不在同一队列,所以4不受影响。

2、不同队列的任务,中间加栅栏。
上面的代码 多加一个不同队列的任务5

- (void)task4 {
    dispatch_queue_t t = dispatch_queue_create("queue.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t t1 = dispatch_queue_create("queue.com.test", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(t, ^{
        NSLog(@"1");
    });
    dispatch_async(t, ^{
        sleep(1);
        NSLog(@"2");
    });
    
    dispatch_barrier_async(t, ^{
        NSLog(@"栅栏-------------");
    });
    dispatch_async(t, ^{
        NSLog(@"3");
    });
    dispatch_async(t1, ^{        // 与上面的代码相比,多加了这个任务 与栅栏不在同一队列
           NSLog(@"5");
       });
    NSLog(@"4");
}

// 打印结果
18:38:39.506841+0800 [57630:448693] 4
18:38:39.506901+0800 [57630:448829] 1
18:38:39.507061+0800 [57630:448982] 5
18:38:40.510435+0800 [57630:448980] 2
18:38:40.510633+0800 [57630:448980] 栅栏-------------
18:38:40.510727+0800 [57630:448980] 3

从打印结果看,5和栅栏不在同一队列,执行顺序不受栅栏影响,5 在栅栏之前就执行完 3和栅栏在同一队列,虽然耗时比2短,但是依然需要等2执行完才开始执行。

3、 栅栏全局队列


- (void)task4 {
    dispatch_queue_t t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        // 与1 相比,1是创建的队列,这里是全局队列
    dispatch_async(t, ^{
        NSLog(@"1");
    });
    dispatch_async(t, ^{
        sleep(1);
        NSLog(@"2");
    });
    
    dispatch_barrier_async(t, ^{
        NSLog(@"栅栏-------------");
    });
    dispatch_async(t, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
}
// 打印结果
11:34:16.525421+0800 [93562:905235] 4
11:34:16.525445+0800 [93562:905321] 1
11:34:16.525581+0800 [93562:906088] 栅栏-------------
11:34:16.525541+0800 [93562:906089] 3
11:34:17.528636+0800 [93562:906087] 2

从打印结果看,如果栅栏任务是在全局队列上,表现与dispatch_async()相同。 也就是说 非创建的队列对起不到栅栏的作用

dispatch_barrier_sync

起到栅栏的作用 ,同时同步执行任务

- (void)task3 {
    dispatch_queue_t t = dispatch_queue_create("queue.com", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(t, ^{
        NSLog(@"1");
    });
    dispatch_async(t, ^{
        sleep(1);
        NSLog(@"2");
    });
    dispatch_barrier_sync(t, ^{
        NSLog(@"栅栏-------------");
    });
    NSLog(@"4");
}

// 打印结果
14:20:49.578362+0800 [2888:45160] 1
14:20:50.580597+0800 [2888:45158] 2
14:20:50.580851+0800 [2888:45015] 栅栏-------------
14:20:50.580971+0800 [2888:45015] 4

从打印结果看,dispatch_barrier_sync在阻塞相同队列的任务的同时,还在阻塞当前线程的任务。

栅栏的实际应用

对一个数组进行多次异步添加数据

- (void)task5 {
    dispatch_queue_t t = dispatch_queue_create("je", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0 ; i < 1000; i ++) {
        dispatch_async(t, ^{
            [self.array addObject:@(1)];
        });
    }
    NSLog(@"3");
}

这段代码运行是报错的,因为多个线程在同时对数组进行操作,必然会造成数据不安全。
为了避免这个情况,可以有多种操作。比如

dispatch_async(t, ^{
    @synchronized (self) {
        [self.array addObject:@(1)];
    }
});

还可以用栅栏来处理

dispatch_async(t, ^{
    dispatch_barrier_async(t, ^{
        [self.array addObject:@"1"];
    });
})

这里的执行顺序大概是这样的
-------- | -------------- | ---------- (| 表示的是栅栏任务 - 表示的是 async任务)

调度组

调度组是一个比较重要的知识点,平常开发中也有很多需要是可以用到的。面试中也经常会问

头文件API官方解释:

  • dispatch_group_create
    此函数用于创建可与块关联的新组。调度组可以用来等待任务的完成。
    调度组的内存会用 dispatch_release 自动释放掉

  • dispatch_group_async
    将一个给定的调度块提交给一个调度组
    queue : 将向其提交块以进行异步调用的调度队列。

  • dispatch_group_notify
    如果group里没有任务,则notify块将立即提交。

比如说: 有任务1、2、3、4 这几个任务,这几个任务无序,但是我需要在4个任务都完成之后做其他任务。就需要用到调度组。

- (void)task6 {
    
    dispatch_queue_t t1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t t2 = dispatch_queue_create("2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t t3 = dispatch_queue_create("22", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, t1, ^{
        NSLog(@" 1  thread = %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, t2, ^{
        NSLog(@" 2  thread = %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, t3, ^{
        NSLog(@" 3  thread = %@",[NSThread currentThread]);
    });
    //dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    dispatch_group_notify(group, t1, ^{
        NSLog(@" 4  thread = %@",[NSThread currentThread]);
    });
    NSLog(@"4");
}
// 打印结果
15:49:27.125111+0800 [5927:94697]  4
15:49:27.125358+0800 [5927:107740] 2  thread = <NSThread: 0x600003d54f80>{number = 10, name = (null)}
15:49:27.125379+0800 [5927:94739]  1  thread = <NSThread: 0x600003d59c80>{number = 3, name = (null)}
15:49:27.125418+0800 [5927:107741] 3  thread = <NSThread: 0x600003d40cc0>{number = 11, name = (null)}
15:49:27.125525+0800 [5927:107741] 4  thread = <NSThread: 0x600003d40cc0>{number = 11, name = (null)}

从打印结果来看
1、dispatch_group_notify 异步执行,不阻塞线程
2、每个组所在的队列并不要求在同一队列。
3、notify 所在的队列 决定最终的回调处于那个线程。一般使用主队列。

dispatch_source

dispatch_source的作用很多,包括响应式、计时器等,一般我们都是用来做计时器。

dispatch_source_t 的计时器使用

  • 创建
    1、创建dispatch_source_t 计时器
    2、设置定时器时间间隔 单位:NSEC_PER_SEC--秒 NSEC_PER_MSEC--毫秒
    3、监听计时器回调
    4、开启计时
@property (nonatomic) dispatch_source_t timer;
//     1. 创建
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));

// 2. 设置定时器的时间  单位:NSEC_PER_SEC--秒   NSEC_PER_MSEC--毫秒
//dispatch_source_set_timer(_timer,dispatch_time(DISPATCH_TIME_NOW, 0), 1 * NSEC_PER_SEC, 0);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0), 1 * NSEC_PER_SEC, 0);

// 3. 监听定时器的回调
dispatch_source_set_event_handler(_timer,^{
    // 将定时器的回调,传给外界
    NSLog(@"thread - %@",[NSThread currentThread]);
});
//    4. 运行计时器
dispatch_resume(_timer);
  • 暂停
    _timer 还可以重新开启。直接调用开启(dispatch_resume(_timer);)就可以生效
dispatch_resume(_timer);
  • 取消
    _timer 被取消,如果还要在用,需要重新创建,如果直接调用dispatch_resume(_timer); 会导致crash
dispatch_source_cancel(_timer);

dispatch_source_create 参数说明

  • 参数1: dispatch_source_type_t type
    为设置GCD源方法的类型,计时器使用 DISPATCH_SOURCE_TYPE_TIMER
  • 参数2: uintptr_t handle
    可以看API的参数说明,没用到,传0即可
  • 参数3: unsigned long mask,
    使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。
  • 参数4: dispatch_queue_t queue
    定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,回调将在子线程进行处理。

dispatch_source_set_timer 参数说明

  • 参数1:dispatch_source_t source
    这个没什么说的,就是创建的计时器
  • 参数2:dispatch_time_t start
    当我们使用dispatch_time(DISPATCH_TIME_NOW, 0) 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。
    使用 dispatch_walltime(NULL, 0) 可以让计时器按照真实时间间隔进行计时。
    详细信息可以看dispatch_walltime() 与 dispatch_time() API的说明
  • 参数3: uint64_t interval
    期望的回调间隔时间 比如 1 * NSEC_PER_SEC
    NSEC_PER_SEC--秒 NSEC_PER_MSEC--毫秒
    如果期望只回调一次,可传入DISPATCH_TIME_FOREVER(在dispatch_source_set_timer的API说明中 有提到此信息)。
  • 参数4: uint64_t leeway
    允许的回调时间间隔误差。一般传入0
    比如说 传入 0.5 * NSEC_PER_SEC ,表示的是:理想状态的下一次回调时间是a,那么下一次实际回调时间可能是 a~a + 0.5s
dispatch_source_t 计时器 注意点
  • 1、循环引用问题
    回调是个block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了self,self又持有了source的话,就会引起循环引用。
    而且因为 Timer 是多次回调,所以系统并没有对这个block 进行 releas 操作。
    正确的方法是使用weak+strong 打破循环引用

  • 2、计时器状态问题
    dispatch_resume(_timer) 与 dispatch_suspend(_timer) 调用需要平衡
    1)、未暂停的情况下, 连续调用2次 dispatch_resume 会 crash
    2)、调用a次 dispatch_suspend,必须要调用 a次 dispatch_resume 才会重新生效。

  • 3、VC释放问题
    dispatch_source_t 计时器 在挂起/未开启的状态下,如果VC被释放,那么会导致crash。
    1)、挂起(暂停)状态下不能直接cancel(dispatch_suspend)
    2)、未开启不能直接cancel(创建了dispatch_source 但是未调用dispatch_resume(_timer)).

    因为并没有API可以获得timer的状态(挂起/运行)。所以需要手动添加属性去记录当前timer的状态。如果是挂起状态,可以重新运行一次再取消

    ⚠️:但是如果重新运行的话,会再次产生一次回调。所以这里还需要自己加属性去判断。

    - (void)dealloc {
    NSLog(@"dealloc");
    if (_isSuspend) {
        dispatch_resume(_timer);        // 如果走到这一步,会重新产生一次回调  需要注意
    }
    dispatch_source_cancel(_timer);
    _timer = nil;
    }
    
  • 4、全局变量问题
    dispatch_source_t timer 需要作为全局变量去声明。
    如果是一个临时变量,那么计时器回调还没开始,tiemr就会被释放掉,就没有意义了。
    正确的声明:

@interface NavSecondVC () {
    // dispatch_source_t _timer;
}
@property (nonatomic) dispatch_source_t timer; 
@end
  • 4、线程问题
    在创建计时器的时候 有一个 queue的参数 ,
dispatch_source_create(dispatch_source_type_t type,
    uintptr_t handle,
    unsigned long mask,
    dispatch_queue_t _Nullable queue);
  • 可以传NULL,默认是在全局队列
    可以传入 创建并打队列、全局队列
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    回调结果在子线程
  • 可以传dispatch_get_main_queue() 回调结果在主线程

延时执行

只是一个简单的API。
dispatch_after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"5秒后 会执行这段代码");
    });

单利

static  ToolManager*_toolManager;
static dispatch_once_t onceModel;
+ (ToolManager *)standardDefault
{
    dispatch_once(&onceModel, ^{
        _toolManager = [[super allocWithZone:nil] init];;
    });
    return _toolManager;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [ToolManager standardDefault];
}

- (id)copyWithZone:(struct _NSZone *)zone {
   return [ToolManager standardDefault];
}

- (id)mutableCopyWithZone:(struct _NSZone *)zone {
   return [ToolManager standardDefault];
}

// 释放单利
- (void)freeStandardDetault {
    _toolManager = nil;
    onceModel = 0;
}

参考内容: iOS多线程:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用

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