进程与线程
进程:进程是操作系统的基础,是一次程序的执行;它是操作系统动态执行的基本单元,在传统的操作系统中,进程是基本的分配单元,也是基本的执行单元。
线程:线程有时被称为轻量进程,是进程执行流中的最小单元。
iOS程序中默认只有一个线程,也称做主线程,所有的用户交互、界面刷新都必须在主线程中进行。
若在主线程中执行复杂长时间的操作,会阻塞主线程,大大降低用户体验;此时可以开启后台线程来执行这些操作,保持主线程流畅。
系统、进程、线程结构:
线程安全
简介
当多个线程同时访问一个资源时,会出现线程安全问题。为避免出现线程安全问题,需要在代码中保证数据、变量的线程安全。
UI Kit 中的所有对象默认都是非线程安全的,需要在主线程刷新UI界面元素。
iOS中保证变量线程安全的方法
atomic 特性属性声明。
@synchronized 关键字。
NSLock 线程锁。
iOS中多线程的实现方式
iOS有三种多线程编程的技术,分别是:
NSThread
NSOperation
GCD (Grand Central Dispatch)
NSThread
NSThread可以简单开辟一个线程处理需要放到后台的操作。
NSThread 比其他两个轻量级。
初始化方法
// 1、类方法
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
// 2、实例化方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
常用属性
name:线程标识
isMainThread:是否主线程
常用方法
// 1、取消线程
- (void)cancel NS_AVAILABLE(10_5, 2_0);
// 2、执行线程
- (void)start NS_AVAILABLE(10_5, 2_0);
NSOperation
NSOperation将一个操作封装成对象,并开辟一个新线程执行。
可以将NSOperation对象加入NSOperationQueue队列中进行统一管理,放入队列中的NSOperation将并发执行。
使用NSOperation需要子类化,并重写main方法,加入自定义操作。
NSOperation提供了子类来更加方便的初始化一个NSOperation对象:NSBlockOperation、NSInvocationOperation。
NSBlockOperation使用
// 执行队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// 开辟线程做什么?
}];
[blockOperation setCompletionBlock:^{
// 线程执行结束后做什么?
}];
[queue addOperation:blockOperation];
NSInvocationOperation使用
// 执行队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 开辟线程
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadWithURL:) object:IMGURL];
[queue addOperation:invocationOperation];
GCD
GCD是一种较为底层的多线程实现方式,其原理是将操作封装为block,并加入指定队列中开辟新线程执行。
NSOperation以及NSOperationQueue都是对GCD机制的高层封装。
- 使用GCD可以实现更加灵活的多线程处理。
Tips
1、 若需使用多线程处理,简单使用而不可滥用
2、 多线程中需要手动创建一个NSAutoreleasePool并在适当时刻释放。
任务
- 同步执行:只要是同步执行的任务,都会在当前线程执行,不会另开线程。如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
dispatch_async(dispatch_queue_t queue, <#^(void)block#>)
- 异步执行:只要是异步执行的任务,都会另开线程,在别的线程执行。如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
dispatch_sync(dispatch_queue_t queue, <#^(void)block#>)
队列
队列用于存放任务。一共有三种队列, 串行队列、并行队列和主队列。
主队列:dispatch_get_main_queue()
并行队列:dispatch_get_global_queue(long identifier, unsigned long flags)
串行队列:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
GCD线程应用示例
// 创建一个并行队列并设置优先级为默认
dispatch_queue_t globaQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步执行队列
dispatch_async(globaQueue, ^{
// 执行线程操作
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新主界面
});
});
本地通知 UILocalNotification
使用步骤
* 1.注册通知 didFinishLaunchingWithOptions(UIUserNotificationSettings -》registerUserNotificationSettings)
* 2.初始化通知 alloc + init
* 3.配置通知
* 4.添加通知 scheduleLocalNotification
使用示例
// 配置通知设置项(注册)
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]];
// 初始化通知
UILocalNotification *notification = [[UILocalNotification alloc] init];
// 设置定时启动器
notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:8.0];
// 通知声音
notification.soundName = UILocalNotificationDefaultSoundName;
// 通知标题
notification.alertTitle = @"尊敬的XXXX用户:";
// 通知icon显示图示(显示当前有几条信息)
notification.applicationIconBadgeNumber = 10;
// 通知信息
notification.alertBody = [NSString stringWithFormat: @"您有%ld条未读消息!", notification.applicationIconBadgeNumber];
// 通知时区
notification.timeZone = [NSTimeZone defaultTimeZone];
// 添加通知
[application scheduleLocalNotification:notification];
接收到通知
// 接收到通知
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:notification.alertBody preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[_window.rootViewController presentViewController:alertController animated:YES completion:nil];
}
注册通知的另一种形式
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[application registerUserNotificationSettings:notificationSettings];
}
多线程使用案例
效果展示
storyboard 搭建界面
代码示例
#import "ViewController.h"
#define IMGURL @"http://f.hiphotos.baidu.com/image/pic/item/2e2eb9389b504fc2d87ff00be6dde71190ef6d9f.jpg"
@interface ViewController () {
// 线程安全
NSLock *_lock;
NSInteger _count;
// 执行队列
NSOperationQueue *_operationQueue;
}
@property (strong, nonatomic) IBOutlet UILabel *stateLabel;
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UIImageView *indictorView;
- (void)initializeDataSource; /**< 初始化数据源 */
- (void)initializeUserInterface; /**< 初始化用户界面 */
- (void)loadWithURL:(NSString *)URLString; /**< 数据下载 */
- (void)updateUserInterfaceWhenBeforeLoadData;
- (void)updateUserInterfaceWithData:(NSData *)data; /**< 界面刷新 */
- (void)startThreadMethodsUseGCD; /**< 线程方法之:GCD */
- (void)startThreadMethodsUseNSThread; /**< 线程方法之:NSThread */
- (void)startThreadMethodsUseNSOperation; /**< 线程方法之:NSOperation */
- (void)threadSafety; /**< 线程安全 */
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initializeDataSource];
[self initializeUserInterface];
}
#pragma mark *** Initialize ***
- (void)initializeDataSource {
_lock = [[NSLock alloc] init];
_operationQueue = [[NSOperationQueue alloc] init];
_indictorView.image = [UIImage imageNamed:@"loading"];
// 添加动画
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
basicAnimation.duration = 1.0;
basicAnimation.toValue = @(M_PI * 2);
basicAnimation.repeatCount = CGFLOAT_MAX;
[_indictorView.layer addAnimation:basicAnimation forKey:@"rotation"];
}
- (void)initializeUserInterface {
// 打印数字举例
// [self printNumber];
// [self performSelectorInBackground:@selector(printNumber) withObject:nil];
// NSLog(@"flag");
[self threadSafety];
}
#pragma mark *** Private ***
/**< 打印数字 */
- (void)printNumber {
for (int i = 0; i < 20; i++) {
NSLog(@"%d", i);
}
}
- (void)updateUserInterfaceWhenBeforeLoadData {
// 改变状态显示
_stateLabel.text = @"下载中,请稍后...";
// 显示活动指示器
_indictorView.hidden = NO;
}
- (void)updateUserInterfaceWithData:(NSData *)data {
_stateLabel.text = @"下载完成";
_indictorView.hidden = YES;
_imageView.image = [UIImage imageWithData:data];
}
#pragma mark *** Thread Safety ***
- (void)threadSafety {
/**
* 当多个线程同时访问一个资源时,会出现线程安全问题;
* 上锁是为了防止当一个线程在执行的时候被另一个线程访问;
*
* 保证线程安全的时候我们可以用lock锁 或用关键字@synchronized
*/
_count = 20;
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(downCount) object:nil];
thread1.name = @"线程1";
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(downCount) object:nil];
thread2.name = @"线程2";
[thread2 start];
}
/**< 线程安全 */
- (void)downCount
{
// 1.线程锁
while (1) {
[_lock lock];
if (_count > 0) {
NSLog(@"%@:%ld", [NSThread currentThread].name, _count --);
}else{
break;
}
[_lock unlock];
}
// 2.关键字synchronized
@synchronized(self) {
while (1) {
if (_count > 0) {
NSLog(@"%@:%ld", [NSThread currentThread].name, _count --);
}else {
break;
}
}
}
}
#pragma mark *** Data Load ***
- (void)loadWithURL:(NSString *)URLString {
// 注意:在子线程中,我们无法将对象添加到主线程的自动释放池,不过可以使用在线程中初始化的自动释放池
@autoreleasepool {
// 获取当前线程的名字
// NSString *currentThreadName = [NSThread currentThread].name;
// 线程休眠
// [NSThread sleepForTimeInterval:3.0];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:IMGURL]];
// 刷新界面
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUserInterfaceWithData:data];
});
}
}
#pragma mark *** Events ***
- (IBAction)respondsToButton:(id)sender {
// 刷新界面
[self updateUserInterfaceWhenBeforeLoadData];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 1、NSThread
// [self startThreadMethodsUseNSThread];
// 2、NSOperation
// [self startThreadMethodsUseNSOperation];
// 3、GCD
[self startThreadMethodsUseGCD];
});
}
#pragma mark - thread methods
/**< 线程方法之:NSThread */
- (void)startThreadMethodsUseNSThread {
// 1、类方法
// [NSThread detachNewThreadSelector:@selector(loadWithURL:) toTarget:self withObject:IMGURL];
// 2、实例化方法
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(loadWithURL:)
object:IMGURL];
// 设置线程名字作为标记
thread.name = @"myThreadName";
// 开始执行线程
[thread start];
// [thread cancel];
}
/**< 线程方法之:NSOperation */
- (void)startThreadMethodsUseNSOperation {
// 一般用NSOperation的子类NSBlockOperation、NSInvocationOperation开辟线程。
// 注意:在通过NSOperation开辟线程之前我们需要声明NSOperationQueue队列,因为NSOperation的子类对象需要放到操作队列中。
// 1、NSBlockOperation
/*
__block NSData *data = nil;
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// 开辟线程做什么
data = [NSData dataWithContentsOfURL:[NSURL URLWithString:IMGURL]];
}];
[blockOperation setCompletionBlock:^{
// 线程执行完做什么
[self performSelectorOnMainThread:@selector(updateUserInterfaceWithData:)
withObject:data
waitUntilDone:NO];
}];
[_operationQueue addOperation:blockOperation];
*/
// 2、NSInvocationOperation
/*
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(loadWithURL:)
object:IMGURL];
[invocationOperation setCompletionBlock:^{
}];
[_operationQueue addOperation:invocationOperation];
*/
}
/**< 线程方法之:GCD */
- (void)startThreadMethodsUseGCD {
/**
Grand Central Dispatch
1、任务
同步执行:只要是同步执行的任务,都会在当前线程执行,不会另开线程。如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
异步执行:只要是异步执行的任务,都会另开线程,在别的线程执行。如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
dispatch_async(dispatch_queue_t queue, <#^(void)block#>)
dispatch_sync(dispatch_queue_t queue, <#^(void)block#>)
2、队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。
主队列:dispatch_get_main_queue()
并行队列:dispatch_get_global_queue(long identifier, unsigned long flags)
串行队列:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
*/
// 创建一个并行队列并设置优先级为默认
dispatch_queue_t globaQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步执行队列
dispatch_async(globaQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:IMGURL]];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUserInterfaceWithData:data];
});
});
}
@end