浅析 iOS 多线程

一、基本知识

1. 进程

  • 系统中正在运行的一个程序
  • 每个进程之间相互独立「运行在其专用且受保护的内存空间内」
  • 进程之间可以相互通讯

2. 线程

  • 1 个进程想执行任务,必须得有 1 条线程「每个进程至少有一条线程」
  • 1 个进程所有的任务都在线程里执行
  • 1 个线程的任务的执行都是串行「按顺序多个任务,同一时间内 1个线程只执行 1个任务」

I. 线程的状态

  • 启动线程 -(void)start;
  • 阻塞线程
// 让线程阻塞多久不做任务
+ (void)sleepUntilDate:(NSDate *)date;
// 示例 1:
[NSThread sleepUntilDate: [NSDate distanceFuture]]; //永远阻塞「当前语句所在的线程」
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:2]]; //阻塞 2秒「单位是秒,当前语句所在的线程」

+ (void)sleepForTimeInterval:(NSTimeInterval) ti;
// 示例2:
[NSThread sleepForTimeInterval:2]; //阻塞 2秒「单位是秒,当前语句所在线程」
  • 强制停止线程 +(void)exit;
    注意:一旦线程死亡了,不能在开启任务,只能重新创建新的线程来完成任务

II. 线程的通信

定义:

  • 1个线程传递数据给另 1个线程
  • 在 1个线程中执行完成特定任务后,转到 另一个线程 继续 执行任务

线程间的通信方法:

// 跳转回 主线程 的 哪个方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 跳转到 哪个线程的 哪个方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

// 利用 NSPort(端口对象) 通讯「了解」
// A线程操作 B线程,需要 B将 1个Port对象 给A,A通过 Port对象 操作 B

3. 多线程「掌握」

定义:1个进程 开启多条线程,每条线程可以 并行「同时」执行不同的任务
原理:

  • 同一时间 CPU只处理 1条线程
  • 多线程并发「同时」执行,其实是 CPU快速的在多条线程间调度「切换」

优点:适当提高 程序执行效率 和 资源利用率「CPU、内存利用率」
缺点:

  • 创建线程有开销「创建线程大约需要90毫秒的创建时间」
    iOS的主要开销
    • 内核数据结构「大约 1KB」
    • 栈空间「子线程 521KB,主线程 1MB」
  • 线程过多 会降低程序性能
  • 线程过多 CPU在线程上的开销越大
  • 使用多线程,程序设计更加复杂「线程通信、多线程数据共享」
  • 多个线程访问同一个资源的时候会引发 数据错乱数据安全 问题

I. 主线程

定义:

  • 程序运行后,默认开启 1条线程叫主线程「UI线程」
  • 主要作用:显示、刷新UI界面、处理UI事件

注意:耗时操作放在主线程会卡住主线程,严重影响UI流畅度

II. 互斥锁「使用线程同步技术」

线程同步 定义:多条线程在同一条线上执行「按顺序的执行任务」
互斥锁 格式:@synchronized ( 锁对象 ){ 需要锁定的代码 }
注意:

  • 使用前提:多线程共同抢夺同一个线程
  • 锁对象可以是任意对象「一般都是 self,即调用此方法的单例对象本身」
  • 锁定 1份代码,多个线程共用 1个锁对象「多个锁对象无效」

优点:防止多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的 CPU资源「一般不提倡使用」

III. 原子和非原子属性

在 @property 的属性中有

  • 原子属性 atomic:为 setter方法加同步锁「@property 属性 默认是 加锁」
    特点:线程安全,但需要消耗大量的资源
  • 非原子属性 nonatomic:不加 同步锁
    特点:非线程安全,适合内存小的移动设备

注:加锁、资源抢夺一般交给服务器处理,减少客户端压力

二、iOS中多线程的实现方案

1. pthread「了解」

// 创建线程
#import<pthread.h>
pthread_t thread;
pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict);

2. NSThread 「掌握」

I. 基本用法

  • + (NSThread *)currentThread;获得当前线程
  • + (NSThread *)mainThread; 获得主线程
  • - (BOOL)isMainThread; 查看 当前方法所在的类 是否为主线程
  • + (BOOL)isMainThread; 查看 当前方法所在的方法 是否为主线程

II. 创建和启动线程

  • 1 个NSThread对象就是 1条线程
  • 虽然这里的线程是局部变量,可是当线程开启后,线程会直到任务完成后销毁
  • 线程任务完成后会处于消亡状态,不能再次开启。只能重新创建新的线程在执行任务。
// 方法1. 创建线程
NSThread *thread_1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"run方法的参数,obj类型"];
// 以下这步可以省略
thread_1.name = @"这条线程的名字"; 
// 启动线程「线程一启动,就会在线程thread中执行 self的 run方法」
[thread start];

// 注:以下两种方法 不返回线程对象
// 优点:简单快捷
// 缺点:无法对线程进行详细的设置

// 方法2. 创建后自动分离出线程,并启动
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"run方法的参数, obj类型"];

// 方法3. 隐式创建,并启动后台线程
[self performSelectorInBackground:@selector(run) withObject:nil];

3. GCD「Grand Central Dispatch 宏大的中枢调度器」

优势

  • 纯 C 语言有许多强大的函数
  • 进行了 iOS 系统级的优化
  • 针对多核并行运算提出的解决方案「会自动利用更多的CPU内核」
  • 自动管理线程的生命周期「创建线程、调度任务、销毁线程」

使用步骤

  1. 创建队列或使用系统提供的队列
  2. 定制任务「线程操作」
  3. 将任务添加到 队列「存放任务」

1) 任务

任务的执行方式「GCD会自动从队列中取出任务,放到对应线程中执行」

I. 同步任务「synchronize」

简介

  • 只能在当前线程中执行任务,不能 擅自开启新线程
  • 注:当前任务中 添加了同步函数,先执行完 同步函数,后执行 同步函数后的当前任务操作
    在当前队列添加 同步任务,会阻塞当前队列

执行步骤

  1. 阻塞当前线程,保持和当前线程的同步
  2. 执行同步任务中的任务
  3. 回到当前队列,使当前队列不在阻塞,执行当前队列的任务

示例结果:1、2、3

// block:需要执行的任务
// dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

II. 异步任务「asynchronous」

简介

  • 可以在新的线程执行任务, 擅自开启新线程
  • 注:当前任务中 添加异步函数,先执行完 当前任务,后 执行异步函数

执行步骤

  1. 执行当前线程「不会阻塞当前线程」
  2. 执行异步任务

示例结果:1、3、2

// dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 每个有block的函数都有对应的 function函数
// dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

2)队列

I. 串行队列「Serial Dispatch Queue」

  • 任务一个接着一个执行
  • 代表:**主线程队列 **「不受 retain 和 release 的影响」
    放到主队列中的任务,会在主线程,UI界面的线程中执行
// 1. 获得主队列
dispatch_queue_t qu = dispatch_get_main_queue();

// 2. 将任务加入队列
// 2.1 主队列+异步函数:串行执行,不能开启新线程
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 主队列+同步函数:串行执行,不能开启新线程「如果执行的函数也在主队列则,队列阻塞,都无法执行」
dispatch_sync(qu, ^{ /*任务*/ });
  • 自己创建 1个串行队列,队列优先级默认为 DISPATCH_QUEUE_PRIORITY_DEFAULT
// 1. 创建 1个串行队列
// label: 队列名称「格式: 作用.名称.queue」,队列名称会在 CrashLog 里标明崩溃的队列名称
// dispatch_queue_attr_t: 队列类型「并发/同步」
// dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
// 属性:DISPATCH_QUEUE_SERIAL 等价于 NULL
dispatch_queue_t qu = dispatch_queue_create("down.Miao.queue", DISPATCH_QUEUE_SERIAL);

// 2. 将任务加入队列
// 2.1 串行队列+异步函数:不能并发,可以开启多线程
// 异步函数:任务执行完成后,不等待 任务执行的队列结束就返回得出的结果
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 串行队列+同步函数:能并发,不会开启新线程
// 同步函数:任务执行完成后,等待 任务执行的队列结束就返回得出的结果
dispatch_sync(qu, ^{ /*任务*/ });

// 3. 自己创建的队列需要手动释放「MRC模式」
dispatch_release(qu); // 当然 也有 dispatch_retain() 方法

II. 并发队列「Concurrent Dispatch Queue」

  • 让多个任务 并发(同时) 进行「自动开启多线程同时执行任务」
    并发只有在 异步函数 dispatch_async 下有效
  • 代表:全局并发队列 「不受 retain 和 release 的影响」
  • 所有应用程序都能使用并发队列
// 有 4 个执行优先级:
// - 高  :DISPATCH_QUEUE_PRIORITY_HIGH
// - 默认:DISPATCH_QUEUE_PRIORITY_DEFAULT
// - 低  :DISPATCH_QUEUE_PRIORITY_LOW
// - 后台:DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 不是自己创建的对象无法手动释放
  • 自己创建 1个并发队列
// 1. 创建 1个并发队列
dispatch_queue_t qu = dispatch_queue_create("com.Miao.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 将任务加入并发队列
// 2.1 并行队列+异步函数:可以并发,可以开启新线程
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 并行队列+同步函数:不能并发,不会开启新线程
dispatch_sync(qu, ^{ /*任务*/ });

III. 变更线程的优先级

  • 使用 dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
    多个串行队列的优先级都变更到同一队列的优先级,会导致
    原本互相可以并行的多个串行队列,在目标队列上只能同时执行一个处理
dispatch_set_target_queue(/*要设置优先级的对列*/, /*与期望的优先级相同的队列*/);

IV. 线程通讯

示例:下载图片

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1. 耗时的异步操作
    // 获取图片网络路径
    NSURL *url = [NSURL URLWithString:@"图片网络路径/图片名.图片格式"];
    // 加载图片
    NSData *data = [NSData dataWithContentsOfURL: url];
    // 生成图片
    UIImage *image = [UIImage imageWithData:data];

    // 2. 回到主线程「最好用异步函数」
    dispatch_async( dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

3)信号量

信号:多线程中的计数器,计数为 0 时线程等待,计数为 1 或者大于 1 时,减去 1 而不等待
示例:由于信号量为 10 在这个并行队列里,最多会有 10 个任务被执行

// 创建信号量,其值设为 10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);

for (int i = 0; i < 100; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 超时后,信号量会失效
        // 信号量为 0,线程被阻塞;信号量 不为 0,信号总量 -1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);        
        NSLog(@"__%d__",i);
        // 休眠
        [NSThread sleepForTimeInterval:3];
        // 信号量 +1
        dispatch_semaphore_signal(semaphore);
    });
}

4) GCD 其他方法

I. 障碍函数

使用限制

// 此方法阻塞的是 传入的 queue「不是当前线程」
// 参数 queue 不能是 全局的并发队列
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

执行步骤

  1. 阻塞传入的线程 queue
  2. queue 前面的任务执行完毕,执行 障碍函数
  3. 取消对线程 queue 的阻塞
dispatch_async(queue, ^{ /* 读 */ });
dispatch_async(queue, ^{ /* 读 */ });
dispatch_barrier_async(queue, ^{ /* 并行读入完成,开始并行写入 */ });
dispatch_async(queue, ^{ /* 写 */ });
dispatch_async(queue, ^{ /* 写 */ });

II. 延时函数「不影响现有的线程继续执行」

  • NSObject 方法:
    [self performSelector:@selector(函数1:) withObject:函数1的参数 afterDelay: 2.0];延迟 2秒
  • NSTimer 方法:
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(函数1:) userInfo:nil repeats:NO];
  • GCD 函数「非循环」:
dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), 
    // 2秒后到主线程执行任务
    dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});
  • GCD 定时器「循环」:GCD 的定时器不受 RunLoop 的 Mode影响
int count = 0;
// 1. 获得队列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue();
    
// 2. 创建一个定时器(dispatch_source_t本质还是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
// 3. 设置定时器的各种属性(几时开始任务,每隔多长时间执行一次)
//    GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
//    何时开始执行第一个任务
//    dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
    
// 4. 设置回调
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"------------%@", [NSThread currentThread]);
    count++;
    if (count == 4) {
        // 6. 取消定时器
        dispatch_cancel(self.timer);
        self.timer = nil;
     }
});    
// 5. 启动定时器
dispatch_resume(self.timer);

III. 一次性代码「保证代码在程序运行中,只执行一次」

static dispatch_one_t onceToken; // 标记是否执行的过
dispatch_once(&onceToken, ^{ /*要只执行一次的代码块,线程安全的已经加锁了*/ });

IV. 快读迭代「可以多线程同时遍历」

dispatch_queue_t qu = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 10为遍历的数组长度
dispatch_apply(10, qu,^(size_t index){
    // 遍历需要的操作
});

VI. 队列组

作用

  • 首先,异步执行多个耗时操作
  • 其次,等之前的操作全部完毕后,再回到主线程执行操作

示例:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行2个耗时的异步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

4. NSOperation「底层是GCD,做了面向对象的封装」

作用

  • 配合使用 NSOperation 和 NSOperationQueue 也能实现多线程编程
  • NSOperation 是抽象类,并不具备封装操作的能力,必须使用他的子类

1)NSOperation 的子类「了解」

I. NSInovcationOperation

注意:

  • 默认,调用 start方法后 不会开启一条新线程执行,在当前线程同步执行操作
  • 只有将 NSOperation 放到 NSOperationQueue 里,才会执行异步操作
// 创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

// 调用start方法开始执行操作
// 一旦执行操作,就会调用target的sel方法
- (void)start;

II. NSBlockOperation

注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作

// 创建NSBlockOperation对象
// 默认在当前线程执行
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 通过addExecutionBlock:方法添加更多的操作
// 添加额外的任务会开启新的线程异步执行
- (void)addExecutionBlock:(void (^)(void))block;

III. 自定义类「继承自NSOperation」

  • 要实现 NSOperation中的 -(void)main
    将自定义类添加到 NSOperationQueue 中时会自动调用 实现的main函数
    其中,要自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  • -(BOOL)isConcurrent 是否能够并发执行
  • -(BOOL)isCancelled 检测操作是否被取消,对取消做出响应

还有很多等等

2)NSOperationQueue「掌握」

作用:将 NSOperation 添加到 NSOperationQueue 中,系统会自动异步执行 NSOperation 中的操作

I. NSOperationQueue的队列类型

  • 主队列 [NSOperationQueue mainQueue]
    凡是添加到主队列中的任务,都会放到主线程中执行

  • 非主队列 [[NSOperationQueue alloc] init]
    同时包含了:串行、并发功能
    添加到这种队列,就会自动放到子线程中执行

// 1. 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 设置最大并发数,如果为 1 则为串行
queue.maxConcurrentOperationCount = 1;

// 2. 创建操作(任务)
// 2.1 创建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
   
// 2.2 创建NSBlockOperation
// 快速创建并添加方式
[queue addOperationWithBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];
// 普通方式
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];

// 2.3 创建自定义任务
Person *op3 = [[Person alloc] init];
  
// 3. 添加任务到队列中
[queue addOperation:op1]; // 不用调用start方法,自动开启线程
[queue addOperation:op2]; 
[queue addOperation:op3]; 

II. NSOperationQueue 的方法

  • - (void)cancelAllOperations;
    取消所有队列的操作,会移除 所有队列的操作
  • - (void)cancel;
    取消单个队列操作
  • @property (getter=isSuspended) BOOL suspended;
    暂停或恢复队列,不会移除队列

III. 线程的依赖和监听

  • 依赖的作用:保证执行顺序
    可以在 同一 / 不同队列中 创建依赖关系
// 线程操作B 会在 线程操作A执行完后 在执行
[operationB addDependency:operationA]; // 操作B依赖于操作A
  • 线程操作的监听
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];

op2.completionBlock = ^{
    NSLog(@"监听线程操作 op2执行完毕后要执行的操作");
};

[queue addOperation:op2]; 

IV. 线程间的通讯

[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
   // 图片的网络路径
   NSURL *url = [NSURL URLWithString:@"图片路径/图片名.格式"];
   // 加载图片
   NSData *data = [NSData dataWithContentsOfURL:url];
   // 生成图片
   UIImage *image = [UIImage imageWithData:data];
   // 回到主线程
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       self.imageView.image = image;
   }];
}];

5. 多线程的应用:单例设计模式「Singleton」

定义:

  • 类的对象成为系统中唯一的实例,提供一个访问点,供客户类 共享资源

使用情景:「操作频繁的时候,提升效率」

  1. 类只能有一个实例,必需从一个为人熟知的访问点访问,比如工厂方法
  2. 整个应用程序中,共享一份资源

注意:

  1. 某个类只能有一个实例
  2. 为保证实例唯一性,这个方法必须是静态类方法
  3. 单例方法名称一般以 share 或 default 开头
  4. 这个类必须自行创建这个对象
  5. 必须自行向整个系统提供这个实例

示例代码一、使用GCD

// 实现之外,先定义一个实例,为了防止野指针,赋空值
// 实现之外的静态变量,一个类只有一个「不能用继承来实现不同类的单例」
static Sample *_instance = nil;
@implement Sample
// alloc、allocWithZone方法都会调用 allocWithZone方法,所以只重写 allocWithZone方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    // 确保多线程的线程安全
    static dispach_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

// 直接返回单例对象
+ (instancetype)shareInstance{
    // 确保多线程的线程安全
    static dispatch_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

// 防止 Copy方法的误用
-(id)copyWithZone:(NSZone *)zone{
    return _instance; 
}
-(id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}

// 若是MRC模式下,需要添加一下代码
- (oneway void)release{
    // 为保证单例,只重写父类方法,什么都不需要做
}
- (instancetype)retain{
    return _instance;
}
- (NSInteger)retainCount{
    return MAXFLOAT; //为了方便程序员交流,这里返回一个很大的数    
}
@end

示例代码二、不使用GCD

static Sample _instance = nil;
@implement Sample

+ (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;
}

// 以下代码与使用GCD时重复,略
@end

将单例作为宏定义使用

  • 单例抽取为宏
    优点:每次定义单例的方式都是一样的,为了提高编程效率
    缺点:因为单例定义中含有全局变量 static,在程序运行的整个过程中只创建一份,所以在 使用宏时不能继承单例

  • 宏方法定义

// 声明方法,
// #define a(b) is_##b  的作用是 将 a(hehe) 自动替换为 is_hehe
#define interfaceSingleton(name) +(instancetype)share##name;

// 定义方法
// 系统自带 宏定义 判断是否为 arc模式
#if !__has_feature(obj_arc)

#define implememtationSingleton(name) \
static id _instance = nil;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
+ (instancetype)share##name{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [[self alloc] init];\
    });\
    return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{ return _instance; }\
- (id)mutableCopyWithZone:(NSZone *)zone{ return _instance; }\
- (oneway void)release{}\
- (instancetype)retain{ return _instance; }\
- (NSInteger)retainCount{ return (unsigned long)MAXFLOAT}

#else

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

推荐阅读更多精彩内容