ios多线程

342 阅读22分钟

本文主要从 1、 ios三种创建方式特点,场景。 2、再到串行,并行,同步,异步,概念介绍,搭配使用。 3、三种方式具体代码实现,搭配特点。 4、线程安全。 逐一的了解iOS分线程。既是对自己线程阶段的学习归纳,也是一个学习的过程的分享,如有什么地方说错了,或不好的地方,希望并欢迎大家指出一起交流。

1、iOS多线程三种方式

说起iOS多线程,如果线程的某些难点不知道这也很正常,但是你不知道iOS操作线程的三种方式,那就是没有学过iOS多线程啊。那本文就先讲一下三种方式的基本概念,使用场景。

一、NSThread

NSThread是iOS提供的可以开辟一个线程的类,是完全面向对象的,可以直接操控线程对象和方法,非常直观和方便。

  • 特点

1、使用更加面向对象 2、简单易用,可直接操作线程对象 3、OC语言

  • 缺点

线程需要管理线程的生命周期、同步、加锁问题,会导致一定的性能开销。同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)。

由于NSThread需要管理的太多,容易出错,所以在日常的开发中用到的并不多。 但是NSThread为我们提供几种方法用于调试十分方便。

1、[NSThread currentThread] 跟踪任务所在线程 2、[NSThread isMainThread] 当前是否是主线程 3、[NSThread sleepForTimeInterval:0.5]; 线程休眠

二、GCD

GCD用来解决多核编程问题,IOS4之后提出的,是基于C语言的底层API。GCD是苹果为多核编程的并行运算提出的解决方案,所以会自动合理的利用更多的CPU内核。

  • 特点

1、自动管理线程的生命周期,创建线程、调度任务、销毁线程。 2、使用block来定义任务,使用起来非常灵活。 3、通过GCD可以创建串行、并发、主队列,可以同步和异步执行任务。 4、基于C语言的底层API,充分利用设备的多核(自动)

  • 缺点

并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂。任务之间有依赖关系或者想要监听任务完成状态相对NSOperation比较难以实现

所以综合优缺点,在线程没有依赖关系和没有想要监听任务状态的情况下优先使用GCD。因为他更高效,更合理的使用CPU,是实际开发中使用最多的操作线程方式

三、NSOperation

它是苹果公司对GCD的封装,是面向对象的线程技术。

  • 特点

1、只需将任务放到对应的队列中,不必关心线程管理、同步等问题。 2、NSOperation是抽象类,调用只能使用子类NSInvocationOperation和NSBlockOperation。相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。 3、能更好的控制线程总数及线程依赖关系,监听线程状态。 4、基于GCD(底层是GCD)比GCD多了一些更简单实用的功能

所以NSOperation相对GCD是在一个比较复杂的线程逻辑环境(例如线程依赖关系,监听线程状态)的时候使用。


2、 串行,并行,同步,异步。

前言:上节三种线程的创建方式是iOS线程的基础。这节同样线程的基本运行逻辑也是很重要的。两个队列,两个执行方式,还有主队列,全局队列的混搭使用是比较容易弄混的。下面我们就逐一解释介绍一下。

1、基本概念

队列分为串行和并行: 队列只是负责任务的调度,而不负责任务的执行
  • 串行

让任务一个接着一个地执行 串行队列的任务都是有序的 一个一个的执行的。

  • 并行

让多个任务并发(同时)执行 只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是都有任务在执行。 只要有线程可以利用,队列就会调度任务。

任务的执行分为同步和异步
  • 同步

不会开启新的线程,任务按顺序执行

  • 异步

会开启新的线程,任务可以并发的执行

2、搭配使用

1、 串行队列同步执行 主线程中执行

同步不会开启新的线程,则串行队列同步执行只是按部就班的 one by one 执行。

2、 串行队列异步执行 分线程中执行 只开辟一个分线程

虽然队列中存放的是异步执行的任务,但是结合串行队列的特点,前一个任务不执行完毕,队列不会调度,所以串行队列异步执行也是 one by one 的执行

3、 并行队列同步执行 主线程中执行

虽然并行队列可以不需等待前一个任务执行完毕就可调度下一个任务,但是任务同步执行不会开启新的线程,所以任务也是 one by one 的执行

4、 并行队列异步执行 分线程中执行 开辟很多个分线程

异步执行是任务可以开启新的线程,所以这中组合可以实现任务的并发,再实际开发中也是经常会用到的 在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3~5条最为合理。

5、 主队列(串行)异步执行 主队列中执行 主队列执行完才能执行

虽然是异步但是添加到了主队列中,需要等待主队列任务执行完全,才能执行任务,所以实际开发用到的很少。

6、 主队列(串行)同步执行 死锁

同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,就会和主线程形成一个相互等待的结果。从而造成死锁。

7、 全局队列(并行)同步和异步执行

和并行队列同步和异步执行效果一样。这里可以使用dispatch_get_global_queue直接获取主队列不需要创建。系统为每个应用程序提供四个并发调度队列。这些队列对应用程序是全局的,并且仅通过优先级来区分。因为它们是全局的,所以不需要显式的创建它们。

3、NSOperation中的队列

1、Operation Queues 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。 2、操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。 3、NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。


3、代码实现

一步一步的敲击键盘是理解程序的最好方式

一、NSThread
第一种创建线程的方式:alloc init.    //特点:需要手动开启线程,可以拿到线程对象进行详细设置
NSThread  *thread = [[NSThread alloc]initWithTarget:self selector:@selector(jisuanNumber) object:nil];
[thread start];

第二种创建线程的方式:分离出一条子线程 //特点:自动启动线程,无法对线程进行更详细的设置
[NSThread detachNewThreadSelector:@selector(jisuanNumber) toTarget:self withObject:nil];

第三种创建线程的方式:后台线程        //特点:自动启动县城,无法进行更详细设置
[self performSelectorInBackground:@selector(jisuanNumber) withObject:nil];

从子线程回到主线程
[self performSelectorOnMainThread:@selector(result:) withObject:@(sum) waitUntilDone:YES];

设置线程的名称
thread.name = @"线程A";

设置线程的优先级,
thread.threadPriority = 1.0;
注意线程优先级的取值范围为0.0~1.0之间,1.0表示线程的优先级最高,如果不设置该值,那么理想状态下默认为0.5

[NSThread exit];  //退出当前线程
[NSThread currentThread]; // 跟踪任务所在线程
[NSThread isMainThread]; // 当前是否是主线程
[NSThread sleepForTimeInterval:0.5]; // 线程休眠

二、GCD

最重要的一部分,直接上代码

  • 1、获取队列,创建队列
 /**  主队列 (串行)  **/
dispatch_queue_t queueMain = dispatch_get_main_queue();
    
/**  全局并行队列  第一个参数优先级  第二个参数目前用不到**/
dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
/**  创建串行队列  **/
dispatch_queue_t queueSerial = dispatch_queue_create("serial",DISPATCH_QUEUE_SERIAL);
    
/**  创建并行队列  **/
dispatch_queue_t queueConcu = dispatch_queue_create("concu", DISPATCH_QUEUE_CONCURRENT);
  • 2、同步搭配队列
 /**  同步 - 串行  **/
for (int  i = 0 ; i < 10; i ++) {
  dispatch_sync(queueSerial, ^{  /**  在主线程,与主队列顺序执行 **/
         NSLog(@"%d %@  %d",i, [NSThread currentThread],[NSThread isMainThread]);
     });
   }
    
/**  同步 --- 并行   **/
for (int  i = 0 ; i < 10; i ++) {
     dispatch_sync(queueConcu, ^{ /**  在主线程,与主队列顺序执行 **/
         NSLog(@"%d %@  %d",i, [NSThread currentThread],[NSThread isMainThread]);
     });
}
    
/**  同步 --- 全局并行队列   **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_sync(queueGlobal, ^{ /**  在主线程,与主队列顺序执行 **/
        NSLog(@"%d %@",i, [NSThread currentThread]);                           
    });
}
    
/**  同步 --- 主队列   **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_sync(queueMain, ^{ /**  死锁 **/
        NSLog(@"%d %@  %d",i, [NSThread currentThread],[NSThread isMainThread]); 
   });
}
总结:同步添加到队列是立即执行的,无论是添加到什么队列都是在主线程中执行,不会开辟新的线程。

  • 3、异步搭配队列
/**  异步 - 串行  **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_async(queueSerial, ^{
        NSLog(@"%d %@",i, [NSThread currentThread]);  /**  不在主线程,同一个线程 **/
    });
}

/**   异步 --- 并行  **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_async(queueConcu, ^{
       NSLog(@"%d %@",i, [NSThread currentThread]);  /**  不在主线程,不同的线程  **/
    });
}
    
/**  异步 --- 全局并行队列  **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_async(queueGlobal, ^{
        NSLog(@"%d %@",i, [NSThread currentThread]); /**  不在主线程,不同的线程 **/
    });
}

/**  异步 -- 主队列  **/
for (int  i = 0 ; i < 10; i ++) {
    dispatch_async(queueMain, ^{
        NSLog(@"%d %@",i, [NSThread currentThread]); /**  在主队列 (所以必须等主线程执行完成才能执行) **/
    });
}
异步在主队列一样不会开辟分线程,并且需要等待主队列执行完毕。在其他队列都会开辟新的线程用来高效执行程序。 
异步并行是最常用的分线程方式。
  • 4、线程栅栏,控制线程执行顺序
dispatch_async(queueConcu, ^{
     for (int i = 0 ; i < 5; i ++) {
         NSLog(@" op1 ------->>>");
     }
 });
    
dispatch_async(queueConcu, ^{
    for (int i = 0 ; i < 5; i ++) {
        NSLog(@" op2 ------->>>");
    }
});
    
NSLog(@"同步栅栏开始");
dispatch_barrier_sync(queueConcu, ^{
    for (int i = 0; i < 5; i ++) {
        NSLog(@" 同步栅栏 --------->>");
    }
});
NSLog(@"同步栅栏结束"); /**   同步栅栏在 线程1 2 和栅栏执行代码没有执行完,不会走这一步  **/
    
dispatch_async(queueConcu, ^{
    for (int i = 0 ; i < 5; i ++) {
        NSLog(@" op3 ------->>>");
    }
});
    
NSLog(@"异步栅栏开始");
dispatch_barrier_async(queueConcu, ^{
    for (int i = 0; i < 5; i ++) {
        NSLog(@" 异步栅栏 --------->>");
    }
});
NSLog(@"异步栅栏结束");  /**   异步栅栏执不用等线程3 执行完就会走到这一步 **/

dispatch_async(queueConcu, ^{
    for (int i = 0 ; i < 5; i ++) {
        NSLog(@" op4 ------->>>");
    }
});
NSLog(@"主线程结束");
  • 5、线程组
    /**  GCD 线程组  **/
dispatch_queue_t queue = dispatch_queue_create("queue.group",DISPATCH_QUEUE_CONCURRENT); //一个并行队列
dispatch_group_t groupGCD = dispatch_group_create(); //一个线程组

dispatch_group_async(groupGCD, queue, ^{
    NSLog(@"开始 :task1");
    for (int i = 10; i <= 20 ; i ++) {
        NSLog(@"当前线程名称:%@ ——%d",[NSThread currentThread].name,i);
    }
});

dispatch_group_async(groupGCD, queue, ^{
    NSLog(@"开始 :task2");
    for (int i = 20; i <= 30 ; i ++) {
        NSLog(@"当前线程名称:%@ ——%d",[NSThread currentThread].name,i);
    }
});

dispatch_group_async(groupGCD, queue, ^{
    for (int i = 30; i <= 40 ; i ++) {
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"当前线程名称:%@ ——%d",[NSThread currentThread].name,i);
    }
});

dispatch_group_notify(groupGCD, queue, ^{  //线程组的监听通知
    NSLog(@"回调之后,所在线程还是子线程, 这个子线程是以上group中开辟的线程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"开始 :task3");
        for (int i = 40; i <= 50 ; i ++) {
            NSLog(@"当前线程名称:%@ ——%d",[NSThread currentThread].name,i);
            if (i == 50) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"回到主线程");
                });
            }
        }
    }); 
});
  • 6、和延迟加载
//CGD延迟
dispatch_time_t  timeQueue = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*5);
dispatch_after(timeQueue, dispatch_get_main_queue(), ^{
    NSLog(@"***********  延迟");
});
三、NSOperation
  • 1、NSBlockOperation 创建线程 和 基本属性
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
// 2.添加额外的操作
[op addExecutionBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
    
[op addExecutionBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
NSLog(@"main");
2018-05-23 15:35:00.786681+0800 [88478:7181463] 3---<NSThread: 0x60400066e100>{number = 4, name = (null)}
2018-05-23 15:35:00.786681+0800 [88478:7181465] 2---<NSThread: 0x600000469600>{number = 3, name = (null)}
2018-05-23 15:35:00.786707+0800 [88478:7181252] 1---<NSThread: 0x60400007f140>{number = 1, name = main}
2018-05-23 15:35:00.786882+0800 [88478:7181465] 2---<NSThread: 0x600000469600>{number = 3, name = (null)}
2018-05-23 15:35:00.786880+0800 [88478:7181252] 1---<NSThread: 0x60400007f140>{number = 1, name = main}
2018-05-23 15:35:00.786880+0800 [88478:7181463] 3---<NSThread: 0x60400066e100>{number = 4, name = (null)}
2018-05-23 15:36:23.205236+0800 [88859:7190213] main
从打印结果可以看出 addExecutionBlock 的执行是在新的分线程里面。 不过主线程一直在等他们执行完毕。
    
op1.queuePriority = NSOperationQueuePriorityHigh; // 优先级
[op1 cancel]; // 取消操作
[op1 isFinished];  // 操作是否结束
[op1 isCancelled]; // 操作是否取消
[op1 isExecuting]; // 操作是否正在运行
[op1 isReady];     // 操作是否进入准备就绪状态
[op1 waitUntilFinished]; // 阻塞当前线程,直到该操作结束。
[op1 setCompletionBlock:^{
    NSLog(@"-- 线程1  执行完毕");
}]; // 当前操作执行完执行
  • 2、线程队列 和 队列常用属性方法
/**  获取主线程  **/
NSOperationQueue *queueMain = [NSOperationQueue mainQueue];

/**  自定义队列  **/
NSOperationQueue *queueCustom = [[NSOperationQueue alloc] init];

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
    
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"4===%@", [NSThread currentThread]); // 打印当前线程
    }
}];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"5===%@", [NSThread currentThread]); // 打印当前线程
    }
}];
    
/**  完全乱序,不在主线程。  **/
[queueCustom addOperation:op];
[queueCustom addOperation:op1];
[queueCustom addOperation:op2];

/**    maxConcurrentOperationCount
默认情况下为-1,表示不进行限制,可进行并发执行。
为1时,队列为串行队列。只能串行执行。
大于1时,队列为并发队列。
注意:maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。
**/
queueCustom.maxConcurrentOperationCount = 2;
从执行结果可以看出,当maxConcurrentOperationCount 为2的时候,最多两个分线程在队列中同时执行,单并不是只有两个分线程。
如果maxConcurrentOperationCount为1 的时候这个队列就相当于串行队列,但是不同的是会有不同的分线程。

队列也可以直接添加执行线程 :
   
/**  完全乱序,不在主线程。  **/
[queueCustom addOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"op1 :    %@", [NSThread currentThread]);
    }
}];

[queueCustom addOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
       NSLog(@"op2 :    %@", [NSThread currentThread]);
    }
}];

/**  NSOperationQueue 常用属性方法  **/
[queueCustom cancelAllOperations]; // 取消队列所有操作

[queueCustom setSuspended:YES]; //   可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
[queueCustom isSuspended];      // 队列是否处于暂停状态

[queueCustom waitUntilAllOperationsAreFinished]; // 阻塞当前线程,直到队列中的操作全部执行完毕。

[queueCustom operations]; // 当前在队列中的操作数组
[queueCustom operationCount]; // 长度

[NSOperationQueue currentQueue]; // 获取当前队列
[NSOperationQueue mainQueue];  // 获取主队列

  • 3、线程依赖
线程依赖其实很好理解,B依赖A 。意思就是B等A结束了才能执行。
但是线程依赖可以引出的就是NSOperation的准备就绪状态。
例如上面的依赖B依赖A进入队列,则A进入准备就绪装填,而B需要
等A执行完成才能执行,所以B并不在准备就绪状态。
当然与第一部分提到的 queuePriority 优先级相比。
执行顺序 依赖大于优先级,例如上面的B依赖A,如果B的优先级比A高,
一样会先执行A在执行B。

NSOperationQueue *queueCustom = [[NSOperationQueue alloc] init];
    
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"op1 :    %@", [NSThread currentThread]);
    }
}];
    
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"op2 :    %@", [NSThread currentThread]);
    }
}];
    
NSBlockOperation *op3 =[NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"op3 :    %@", [NSThread currentThread]);
    }
}];
    
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 10; i ++) {
        NSLog(@"op4 :    %@", [NSThread currentThread]);
   }
}];
    
[op2 addDependency:op1];
[op3 addDependency:op2];
[op3 removeDependency:op2];
[queueCustom addOperations:@[op1, op2, op3, op4] waitUntilFinished:NO];

4、线程安全

伴随多线程的出现,不同的线程访问同一资源,在保证资源的正确性的前提下,线程锁的概念就运应而生。

一、首先需要介绍锁的类型。

1、二元信号量:占用非占用

他只有两种状态,占用非占用。 有一个线程在访问资源状态就会给为 占用状态, 第二个就不能访问。

2、互斥量: 占用非占用 获取就要释放

互斥量与信号量非常类似,区别就在于信号量可以被任意的线程获取并释放,互斥量则要求哪个线程获取了互斥量,哪个线程就要负责释放。

3、临界区: 占用非占用 获取就要释放 本进程

比互斥锁更为严格,临界区的作用范围仅限于本进程,其余线程不可获取该锁。

4、读写锁: 自由,共享,独占

有三种状态,自由,共享,独占。 当处于自由,共享状态的时候可以访问,当以独占方式去获取锁时,线程必须等待锁被所有资源释放后,才可以获取;

5、条件变量: 满足条件的线程才会被唤醒

该锁类似一个塞子,不同的线程均可以等待相同的条件,但只有满足条件的线程才会被唤醒。

二、基本概念

  • 死锁

两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。

  • 线程安全

一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题。 非线程安全的只能按次序被访问。

  • atomic

原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。

  • nonatomic

非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。

三、资源加锁,线程安全

最经典的案例就是卖火车票啦。 很多个窗口同时卖票相当于多个线程同时执行,而他们需要访问的资源是共用的票。
所以票这个东西就属于共享资源,必须让他们一个一个访问,不然会乱套。下面就是其中的一种加锁解决方式,线程安全。

- (void)initTicketStatusSave {
    
    self.ticketSurplusCount = 50;
    
    self.lock = [[NSLock alloc] init];  // 初始化 NSLock 对象

    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;

    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;
    
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicket];
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicket];
    }];
    
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicket {
    while (1) {
        
        [self.lock lock];   /**  加锁  **/
        
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }
        [self.lock unlock]; /**  开锁  **/
        
        if (self.ticketSurplusCount <= 0) {
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}

进程

一个进程有独立的内存空间、系统资源、端口等。在进程中可以生成多个线程、这些线程可以共享进程中的资源。

进程通信

iOS系统是相对封闭的系统,App各自在各自的沙盒(sandbox)中运行,每个App都只能读取iPhone上iOS系统为该应用程序程序创建的文件夹AppData下的内容,不能随意跨越自己的沙盒去访问别的App沙盒中的内容。 所以iOS的系统中进行App间通信的方式也比较固定,常见的App间通信方式以及使用场景总结如下。

  1. Port (local socket)

    在两个进程都是活跃状态使用TCP和端口号进行通信,数据传输。尴尬的一点是iOS系统会给每个TCP在后台600秒的网络通信时间,600秒后APP会进入休眠状态。

  2. URL Scheme

    App1在info.plist中配置LSApplicationQueriesSchemes,指定目标App2的scheme;然后在目标App2的info.plist中配置好URL types,表示该App接受何种URL Scheme的唤起。

  3. Keychain

    iOS系统的Keychain是一个安全的存储容器,它本质上就是一个sqllite数据库。它所保存的所有数据都是经过加密的,可以用来为不同的App保存敏感信息,比如用户名,密码等。

    那Keychain用于App间通信的一个典型场景也和App的登录相关,就是统一账户登录平台。使用同一个账号平台的多个App,只要其中一个App用户进行了登录,其他app就可以实现自动登录不需要用户多次输入账号和密码。一般开放平台都会提供登录SDK,在这个SDK内部就可以把登录相关的信息都写到Keychain中,这样如果多个App都集成了这个SDK,那么就可以实现统一账户登录了。

  4. UIPasteboard

    iOS的原生控件UITextView,UITextField 、UIWebView,我们在使用时如果长按,就会出现复制、剪切、选中、全选、粘贴等功能,这个就是利用了系统剪切板功能来实现的。而每一个App都可以去访问系统剪切板,所以就能够通过系统剪贴板进行App间的数据传输了。

  5. UIDocumentInteractionController

UIDocumentInteractionController主要是用来实现同设备上App之间的共享文档,以及文档预览、打印、发邮件和复制等功能。

let url = Bundle.main.url(forResource: "test", withExtension: "pdf")
if url != nil {
    let documentInteractionController = UIDocumentInteractionController.init(url: url!)
    documentInteractionController.delegate = self
    documentInteractionController.presentOpenInMenu(from: self.view.bounds, in: self.view, animated: true)
}
  1. AirDrop
  2. UIActivityViewController
  3. App Groups

App Group用于同一个开发团队开发的App之间,包括App和Extension之间共享同一份读写空间,进行数据共享。同一个团队开发的多个应用之间如果能直接数据共享,大大提高用户体验。

线程池

线程池的执行流程如: 首先,启动若干数量的线程,并让这些线程处于睡眠状态 其次,当客户端有新的请求时,线程池会唤醒某一个睡眠线程,让它来处理客户端的请求 最后,当请求处理完毕,线程又处于睡眠状态 所以在并发的时候,同时能有多少线程在跑是有线程池的线程缓存数量决定的。

GCD的线程池缓存数量是64条 NSOperation 也是但是他可以设置线程池的线程数量

多线程同步问题

1、GCD中信号量 DispatchSemaphore

每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。

  semp.wait()    信号 -1 
  semp.signal()  信号 +1

2、 NSLock

比如一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。

let lock = NSLock.init()
lock.lock()
lock.unlock()

3、@synchronized 互斥锁

swift 里面没有

其实@synchronized在幕后做的事情是调用了objc_sync中的objc_sync_enter和objc_sync_exit方法,我可以直接调用这两个方法去实现。

        //加锁,关门,执行任务
        objc_sync_enter(self)
        
        //开锁,开门,让其他任务可以执行
        objc_sync_exit(self)

4、GCD 栅栏方法:dispatch_barrier_async

第一组操作执行完之后,才能开始执行第二组操作。

5、NSOperation 的 addDependency

6、POSIX互斥锁

pthread_mutex_lock(&lock)

//开门,让其他任务可以执行
pthread_mutex_unlock(&lock)

当然实现锁的类型有很多,比如还有NSRecursiveLock(递归锁)、NSConditionLock(条件锁)、NSDistributedLock(分布式锁)、OSSpinLock(自旋锁)等方式,就不用代码一一实现了。

注意点

  • 串行队列一次执行一个任务,任务按顺序执行,先进先出,这个好理解。那并发几个任务同时执行也是先进先出,这个怎么理解呢。因为并发执行任务,先进去的任务并不一定先执行完,但是即使后面的任务先执行完,也是要等前面的任务退出。这是由队列的性质决定的。

  • 并行的关键是你有同时处理多个任务的能力。 并发是一种能力,处理多个任务的能力。并行是状态,多个任务同时执行的状态。 可以看到并发和并行并不是同一类概念,所以不具有比较性,并发包含并行,就比如水果是包含西瓜一样。并发的不一定是并行,并行的一定是并发。

  • OperationQueue没有实现串行队列的方法,也没有像GCD那样实现了一个全局队列。 只有并发队列的实现和主队列的获取。

  • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。 maxConcurrentOperationCount这个值不应超过系统限制(64),即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。

参考链接