OCiOS开发:多线程与消息通知

进程与线程

  • 进程:进程是操作系统的基础,是一次程序的执行;它是操作系统动态执行的基本单元,在传统的操作系统中,进程是基本的分配单元,也是基本的执行单元。

  • 线程:线程有时被称为轻量进程,是进程执行流中的最小单元。

  • 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
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值