ios开发之多线程---GCD

一:基本概念

1:进程:正在运行的程序为进程。

2:线程:每个进程要想执行任务必须得有线程,进程中任务的执行都是在线程中。

3:线程的串行:一条线程里任务的执行都是串行的,假如有一个进程开辟了一条线程,此条线程里有ABC三个任务,则ABC三个任务的执行是串行的,ABC三个任务按顺序一个个执行

4:多线程:一个进程可以开辟多条线程去并行(同时)去执行任务,假如有ABC三个任务,则此进程会开辟多条线程去并发执行ABC三个任务,可以提高运行效率

5:线程的原理:同一时间,cpu只能处理一条线程,而且也只有一条线程在工作,多线程的并发执行,也就是cpu在多条线程间爱会切换调度。进程一启动,就默认创建了一条线程为主线程大约为1M,所创建的子线程大约为512KB。

6:多线程的缺点:1:大量开线程,会大量占用cpu资源,cpu在调度线程上的开销也就越大 2:大量开线程,会使每条线程的执行效率降低,程序的性能降低,对于移动开发来说,适当开辟线程,大量开线程会使程序的想能降低

7:ios开发中多线程的应用:程序一启动就默认会开辟一条线程,成为主线程,也就是UI线程,主线程主要负责,刷新和显示UI,处理按钮的点击事件,表格的滚动拖拽事件,尽量避免把耗时的操作放在主线程。凡在主线程执行的任务都是串行的,按照顺序一个个执行,若把耗时的操作放在主线程,则会卡主主线程,影响主线程的其他操作,造成很差的用户体验。正确的做法是:将比较耗时的操作放在子线程中去执行,不要影响主线程的其他操作。

8:获得当前线程:[NSThread currentThread]; 获得主线程:[NSThread mainThread];

9:ios中的多线程方案:1:Pthread:跨平台,适用于各个系统,线程的生命周期由程序员去管理。 2:NSThread 3:GCD:由系统自动去管理线程的声明周期   4:NSOperation:基于GCD封装,也是右系统去管理线程的生命周期。

二:线程的安全:

1:线程安全:当同一块资源被多条线程同时访问的时候,会涉及到线程安全的问题,例如卖票,银行取钱问题。解决方法:加互斥锁

 @synchronized(self) {被锁住的代码 }(加锁是很耗性能的,当只有多条线程访问统一资源的时候,才考虑去加互斥锁)。加锁的工作原理是:加互斥锁后,同一时间只许可一条线程访问该资源,其他线程无法访问。当某条线程访问该资源时,会加一把锁,被锁住的代码执行完毕后,锁会被打开,下个线程此时才可以访问该资源,注意:锁对象必须是唯一的,否则多条线程依然可以访问该资源。锁对象一般用self,因为在控制器中,控制器对象就是唯一的,保证了锁对象的唯一性。

2:线程同步:加锁用的就是线程同步技术,多条线程同一条线程上按顺序依次执行 线程异步:多条线程异步去执行

3:原子与非原子属性:atomic是原子属性,默认是线程安全的,在执行setter方法赋值的时候,会加一把互斥锁,等待赋值结束后,所才被打开,防止了多条线程同时访问造成的线程安全问题。虽然是线程安全的,但是加锁消耗了大量的资源 。nonatomic:非原子属性,线程不安全的,没有加锁,因为在实际移动开发中,调用setter方法大部分都是在主线程,所以不涉及多条线程同时访问的问题。若涉及了多线程同时访问该资源,可以加一把互斥锁,还可以用atomic原子属性去修饰

三:GDC

1:GCD有两个核心的概念:1:队列 2:任务,其中队列是用来存放任务,GCD的使用:创建任务直接将任务放到队列里,GCD会自动将任务从队列里取出,按照FIFO原则(先进先出,后进后出,栈是现进后出)取出任务,放到相应的线程中去执行任务。

2:GCD有两个常用的函数用来执行任务:1:同步函数: dispatch_sync

dispatch_sync(queue, ^{

    });

 2:异步函数:dispatch_async  

  dispatch_async(queue, ^{

    });

 其中queue为队列,block为任务。其中同步函数:在当前线程中按顺序串行执行任务,不具备开启线程的能力 异步函数:在新的线程中执行,具备开启新线程的能力

3:GCD两大队列:1:串行队列:队列中的任务按顺序执行,一个任务执行完毕才会去执行下一个任务  2:并发队列:可以并发同时执行多个任务,并发队列只有在异步函数 dispatch_async中才能起到并发执行的作用

总结:1:同步和异步主要影响能不能开启新的线程 2:串行队列和并发队列主要影响任务执行的方式:串行:按顺序依次执行,并发队列:多个任务同时执行 3:看一条线程,先看最外层是同步还是异步,若是同步,不能开启新的线程,只在当前的线程中执行,若是异步,则具备开启新线程的能力,可以在新县城中执行 再看内层:若是串行队列:则任务按顺序执行 若是并发队列:则可以并发同时还行任务,但只有在异步函数中才会并发执行任务。

4:队列的创建:

1:dispatch_queue_t queue = dispatch_queue_create(a,b);a参数为队列名称,C语言字符串,用"",b参数为队列,串行队列:DISPATCH_QUEUE_SERIAL,也可以这么创建串行队列

 dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

并发队列:DISPATCH_QUEUE_SERIAL NULL  注意:1:异步并发队列有三个任务,则并不会一定开启三条线程,开启线程的个数是由GCD内部去决定的。2:任务添加到队列后,会按FIFO原则将任务取出,并发执行,也就是三个任务的执行先后顺序是不确定的

2:并发队列的创建:1:可以获取到全局并发队列:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);第一个参数为队列优先级,默认为DEFAULT,也可以传0,第二个参数暂时用不到传0就可以,当涉及多条线程需要设定优先级时,可以设置第一个参数 2:也可以手动去创建并发队列dispatch_queue_create(a,b)

5:GCD的各种队列:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self syncMain];
}

/**
 * 同步函数 + 主队列:只打印begin,是因为同步函数放在串行队列中会卡主当前线程,造成死锁。同步函数不开启线程在当前线程中执行,且立即执行,而主线程的任务end要先执行完再去执行同步函数中任务,而同步函数又要立即执行,所以会造成死锁
 */
- (void)syncMain
{
    NSLog(@"syncMain ----- begin");
    
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
    NSLog(@"syncMain ----- end");
}

/**
 * 异步函数 + 主队列:只在主线程中执行任务。无论同步还是异步,只要将任务放到追队列里,任务就会在主线程中执行,且是串行执行任务,主队列是一种特殊的串行队列。因为123任务是后添加到主队列里的任务,所以先执行完 start 和 end 在执行 1,2,3
 */
- (void)asyncMain
{   NSLog(@"syncConcurrent--------start");
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.将任务加入队列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
     NSLog(@"syncConcurrent--------end");
}

/**
 * 同步函数 + 串行队列:同步不会开启新的线程,在当前线程串行执行任务,且立即执行任务。任务是串行的,执行完一个任务,再执行下一个任务。打印顺序:start 1,2,3 end
 */
- (void)syncSerial
{
    
    NSLog(@"syncConcurrent--------start");
    // 1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
   NSLog(@"syncConcurrent--------end");
}

/**
 * 异步函数 + 串行队列:异步函数会开启新的线程,但是任务是串行的,任务按顺序执行执行完一个任务,再执行下一个任务。所以只开启一条线程(在一条线程中任务是按照顺序一个接一个执行)。打印顺序:statrt end 1 2 3。相当于把asyncSerial任务放到了主队列中执行,任务则在主线程中执行,又将任务123放到了串行队列中执行,所以先执行完主线程的任务,在执行串行队列的任务,所以先打印start end 再按照顺序依次执行任务1,2,3
 */
- (void)asyncSerial

{    NSLog(@"syncConcurrent--------start");
    // 1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);
    
    // 2.将任务加入队列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
      NSLog(@"syncConcurrent--------end");
}

/**
 * 同步函数 + 并发队列:不会开启新的线程,在当前线程中串行执行任务且立即执行任务,因为并发队列只有在异步函数中才会起作用,打印顺序:start  1,2,3,end
 */
- (void)syncConcurrent
{
    NSLog(@"syncConcurrent--------start");
    // 1.获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.将任务加入队列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
    
    NSLog(@"syncConcurrent--------end");
}

/**
 * 异步函数 + 并发队列:1:可以同时开启多条线程并发同时执行任务,开启线程的个数不确定,并且并发队列只有在异步函数下才有效 2:代码执行的顺序是先打印 satrt 在打印 end 最后再打印子线程中的内容,先执行主线程中的任务,在执行异步线程的任务,123任务执行完成的先后顺序是不确定的。原因是:相当于把asyncConcurrent任务放到了主队列里,在主线程执行,任务是按照FIFO原则从队列中取出,所以先执行完start后,分别将任务123添加到了全局并发队列里,123任务是后添加到队列中的,所以先执行完主队列任务,再异步并发执行子线程中的任务。
 */
- (void)asyncConcurrent
{
    // 1.创建一个并发队列
    // label : 相当于队列的名字
//    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"asyncConcurrent--------start");
    
    // 1.获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.将任务加入队列
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"1-----%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"2-----%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"3-----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncConcurrent--------end");

}

@end

6:GCD中的线程间通信

    

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"---------------------------------------1");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"---------------------------------------3");
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成图片
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        NSLog(@"-------------------------------------5");
        });
        
       NSLog(@"---------------------------------------4");
        
        
    });
    
   NSLog(@"---------------------------------------2");
}

@end

打印顺序:1,2,3,4,5,先执行主线程中的任务,1,2 主线程中的任务执行完毕后再去异步执行子线程中的任务,先执行完子线程中的任务3,4 后,再回到主线程执行任务5。原因:相当于把touchBegan任务放到了主队列里,任务在主线程中执行,子线程中的任务是后面添加到并发队列里的,所以先回执行完主线程中的任务1,2 当执行完毕后再去执行子线程中的任务,在子线程中的任务5是后面加入到主队列的,所以先回执行完子线程中的任务3,4,再回到主线程里执行任务5

6:GCD的其他函数

#import "ViewController.h"
#import "XMGPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
/** 图片1 */
@property (nonatomic, strong) UIImage *image1;
/** 图片2 */
@property (nonatomic, strong) UIImage *image2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    XMGPerson *person1 = [[XMGPerson alloc] init];
    XMGPerson *person2 = [[XMGPerson alloc] init];
    XMGPerson *person3 = [[XMGPerson alloc] init];
    XMGPerson *person4 = [[XMGPerson alloc] init];
    
//    XMGPerson *p1 = [[XMGPerson alloc] init];
//    NSLog(@"%@", p1.books);
//    
//    XMGPerson *p2 = [[XMGPerson alloc] init];
//    NSLog(@"%@", p2.books);
    
}

void download(void * data)
{

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//    dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>);
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//    dispatch_async_f(queue, NULL, download);
    
//    [self group];
    [self barrier];
}
/**
 *    群组:1:群组中的任务是并发执行的,会等群组中的任务都执行完毕后,会调用 dispatch_group_notify,在并发队列中完成绘图操作,在主线程中去显示图片。1:首先创建群组: dispatch_group_t group = dispatch_group_create(); 2:将任务放到并发队列中,将队列放到群组中,12任务执行完不确定 3:都执行完调用dispatch_group_async,将耗时的绘图操作还放在异步线程,绘图完毕后,回到主线程显示UI
 */
- (void)group
{
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 1.下载图片1
    dispatch_group_async(group, queue, ^{
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成图片
        self.image1 = [UIImage imageWithData:data];
    });
    
    // 2.下载图片2
    dispatch_group_async(group, queue, ^{
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        
        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成图片
        self.image2 = [UIImage imageWithData:data];
    });
    
    // 3.将图片1、图片2合成一张新的图片
    dispatch_group_notify(group, queue, ^{
        // 开启新的图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        
        // 绘制图片
        [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        
        // 取得上下文中的图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        // 结束上下文
        UIGraphicsEndImageContext();
        
        // 回到主线程显示图片
        dispatch_async(dispatch_get_main_queue(), ^{
            // 4.将新图片显示出来 
            self.imageView.image = image;
        });
    });
}

/**
 * 快速迭代:dispatch_apply(subpaths.count, queue, ^(size_t index):快速迭代函数:第一个参数为迭代对象的个数,第二个参数为队列,index为返回的索引,快速迭代,并发执行,提高迭代效率
 */
- (void)apply
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSString *from = @"/Users/xiaomage/Desktop/From";
    NSString *to = @"/Users/xiaomage/Desktop/To";
    
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];
    
    dispatch_apply(subpaths.count, queue, ^(size_t index) {
        NSString *subpath = subpaths[index];
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
        // 剪切
        [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
        
        NSLog(@"%@---%@", [NSThread currentThread], subpath);
    });
}

/**
 * 传统文件剪切
 */
- (void)moveFile
{
    NSString *from = @"/Users/xiaomage/Desktop/From";
    NSString *to = @"/Users/xiaomage/Desktop/To";

    NSFileManager *mgr = [NSFileManager defaultManager];
    //此方法获得的是from路径下的所有文件路径,包括子文件夹下的文件路径
    NSArray *subpaths = [mgr subpathsAtPath:from];

    for (NSString *subpath in subpaths) {
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 将文件从一个文件夹移动到另一个文件夹:是个耗时的操作,座椅在子线程中执行
            [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
        });
    }
}
/**
 *    一次性函数:在整个项目中只执行一次
 */
- (void)once
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"------run");
    });
}

/**
 * 延迟执行:GCD延迟函数:dispatch_after所传的队列不同,就在不同的队列中执行延迟函数,延迟的方法有以下三种
 */
- (void)delay
{
    NSLog(@"touchesBegan-----");
    //    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    
    //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //        NSLog(@"run-----");
    //    });
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
}

- (void)run
{
    NSLog(@"run-----");
}


/**
 *    dispatch_barrier_async函数:能保证先12任务先执行完,在执行dispatch_barrier_async中的任务,最后在执行34任务,要想此函数起作用,queue不能是全局并发队列,因为queue是并发队列,所以12,34执行完的先后顺序不确定,但是能保证先执行12,在执行dispatch_barrier_async函数任务,最后执行34
 */
- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
   
    
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

@end

7:GCD下完整的单例模式:GCD模式下的单例保证了线程的安全,dispatch_once是线程安全的,防止同一时间多条线程初始化同一个变量

#import "XMGPerson.h"

@interface XMGPerson() <NSCopying>

@end

@implementation XMGPerson

/**
 *    模拟三种情况得到单例对象:
    1:alloc创建对象 2:通过对象copy得到一个新对象 3:直接调用sharedPerson得到一个新对象
    2:1:先用static定义一个下划线的成员变量 2:alloc方法内部会调用allocWithZone方法,用dispatch_once函数调用父类[super allocWithZone:zone]去开辟空间 3:当在类中实现copyWithZone方法时,需要类去遵守NSCopying协议,直接将成员变量返回,是因为调用copy之前该对象已经被初始化了
 */
static XMGPerson *_person;

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _person = [super allocWithZone:zone];
    });
    return _person;
}

+ (instancetype)sharedPerson
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _person = [[self alloc] init];
    });
    return _person;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _person;
}
@end

8:单例的宏定义:其中的表示下一行也属于宏定义

// .h文件
#define XMGSingletonH(name) + (instancetype)shared##name;

// .m文件
#define XMGSingletonM(name) 
static id _instance; 
 
+ (instancetype)allocWithZone:(struct _NSZone *)zone 
{ 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        _instance = [super allocWithZone:zone]; 
    }); 
    return _instance; 
} 
 
+ (instancetype)shared##name 
{ 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        _instance = [[self alloc] init]; 
    }); 
    return _instance; 
} 
 
- (id)copyWithZone:(NSZone *)zone 
{ 
    return _instance; 
}

9:非GCD模式下的单例:加锁的目的是保证线程安全,防止同一时间多条线程访问初始化同一个变量

#import "XMGPerson.h"

@interface XMGPerson()

@end

@implementation XMGPerson

static id _instance;

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}
@end
原文地址:https://www.cnblogs.com/cqb-learner/p/5751805.html