浅析 iOS 多线程

作者: _凉风_ | 来源:发表于2016-06-23 20:10 被阅读344次

    一、基本知识

    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) 
    

    相关文章

      网友评论

      本文标题:浅析 iOS 多线程

      本文链接:https://www.haomeiwen.com/subject/zwcodttx.html