在大学里接触了一部分编程语言,如C++,Java,C#,虽然学的不是很好,但是每每提到多线程,总会感觉很高大上,毕竟学语言的一开始都不会接触到这一部分。
需要明白的是,每一个程序不是一个线程,而是一个进程,CPU将时间片分配给进程,然后是进程在将时间片分给自己的线程,也就是说一个系统有多个进程,一个进程又有多个线程。
学过的操作系统说过,所谓的多线程,实际就是CPU在处理各种事务的时候分成了时间片,时间片的间隔时机极短,就是说人根本感觉不出来,所以会认为是多线程,也就是并发操作。实际还是按照系统内部的非分配方法来操作的。
为什么要使用多线程呢,比如当某个任务需要长时间运行,但是如果只是单线程,那么后面的程序就要等到它执行完毕之后才可以运行,这样就浪费了很多的事件,也会造成用户体验的不爽。
Objective-C中的多线程,一般讲的时候都是分3类,楼主学的时候也是,但是学完之后会觉得还是GCD比较好用,现在看来比较高大上,通过一个简单的小例子来看,会简单去多,那么依旧采用之前的风格,通过实例来说明东西。
例子就是说,通过点击一个按钮,将进度条动态的前进。为了方便演示(demonstrate),楼主用stroyboard来布局。
NSThread(线程类)
通过名字来看,直接了当,就是一个线程的类,那么首先来了解一下NSThread的基本用法
/**
* 创建一个线程,是一个目标动作回调的方法
*
* @param object 表示需要传入的参数是什么,一般指线程的名字
*/
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(test1) object:nil];
//越高表示cpu的时间优先分配,0 ~ 1,并不是说越高就立马执行
thread.threadPriority = 1;
//线程休眠时间为1秒,表示让出主线程队列的位置,休眠结束后,继续在主线程的队列排队
[NSThread sleepForTimeInterval:1];
//进入就绪队列,但不代表能够立即执行,需要在线程队列中排队
[thread start];
首先在button的点击方法中创建一个线程
//start按钮被点击时
- (IBAction)startButtonClick:(id)sender
{
//创建一个线程
NSThread * thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(threadFun) object:nil];
//添加到就绪队列
[thread1 start];
}
接下来完成线程的回调方法
//子线程执行的方法
-(void)threadFun
{
for (NSInteger i = 0; i <= 100; i++)
{
//休眠0.01秒
[NSThread sleepForTimeInterval:0.02];
[self updateProgress];
}
}
更新进度条的方法如下
//progress更新的办法
-(void)updateProgress
{
self.progressView.progress += 0.01;
}
那么看一下结果吧,是不是想象的那么样呢
结果恐怕不是之前想的那样,这样子是在2秒后突然进度条就满了,原因呢:
1、progressView是在主线程中创建的,所以子线程没有权利修改,即使有的时候可以改,但是一般是错误的。
2、在子线程中执行的方法,那么这个方法就是子线程的。
3、最本质的原因,就是在runloop中,当按钮点击了之后,他的首响应是button,无暇顾及progress进度,只有当button的回调方法结束后,才可以管progressView,但是那个时候progress早已经运行完了,所以2秒后突然进度条满了。
怎么修改呢,只需要子线程执行的方法中改一句话即可
//子线程执行的方法
-(void)threadFun
{
for (NSInteger i = 0; i <= 100; i++)
{
//休眠0.01秒
[NSThread sleepForTimeInterval:0.02];
//在主线程执行该方法
[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];//waitUntilDone 表示下面如果还有语句的话,为YES表示必须执行完这个方法之后才能运行下面的语句
}
}
这样用NSThread就完成了。
NSOperation(操作类)
NSOperation(操作类)楼主感觉比较麻烦,但是又不能不知道,所以也按照自己的理解来描述一下,如果不对,也请及时告诉我,不要继续误导其他人
首先用到这个类,要写一个类继承于NSOoeration,并且要重写它的 main( ) 方法,这个方法不同于C语言的main方法,它仅仅是一个方法的名字,只不过当operation执行的时候会自动的调用这个main方法。
然后就是有的特点和NSThread有点相似,都是将进程或者操作放入系统的队列中进行排队,比如下面是button点击时候的回调方法
//start按钮点击的回调
- (IBAction)buttonPressed:(id)sender
{
//判断是否已经存在
if (!self.operation)
{
//创建一个operation操作类
self.operation = [[MyOperation alloc]init];
}
//创建一个操作队列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
//如果没有完成这个操作,也就是说一旦开始,就不能将进程杀死,就要等他执行完毕才可
if ([self.operation isFinished] == NO)
{
//将操作放入操作队列
[queue addOperation:self.operation];//运行的时候自动调用MyOperation的main方法,必须是main方法
}
}
因为之前也提到过,子线程中是不能够改变主线程创建的组件的属性的,那么如何在子线程中告诉主线程呢,首先就想到了回调,但是楼主技术不达标,所以最熟悉的Block回调不能解决相关问题,所以选择了委托回调的方式。
委托模式必须要有协议,那么协议如下
@protocol MyOperationDelegate <NSObject>
@optional
/**
* 回调返回的进度增量
*
* @param myOperation 当前的操作类对象
* @param i 进度条的增量
*/
-(void)myOperation:(MyOperation *)myOperation updateProess:(CGFloat)i;
@end
接着就开始继承于NSOperation(操作类)的自定义类MyOperation
//
// MyOperation.h
// 多线程 NSOperation
//
// Created by YueWen on 15/10/1.
// Copyright (c) 2015年 YueWen. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@protocol MyOperationDelegate;
@interface MyOperation : NSOperation
//履行协议的归零弱引用的代理对象
@property(nonatomic,weak)id<MyOperationDelegate> delegate;
@end
因为说过用NSOperation,要重写main()方法,重写如下
-(void)main
{
for (NSInteger i = 0; i <= 100; i++)
{
//休眠0.02秒
[NSThread sleepForTimeInterval:0.02];
//判断代理是否有这个方法
if ([self.delegate respondsToSelector:@selector(myOperation:updateProess:)])
{
//用主线程执行代理的委托方法
[self performSelectorOnMainThread:@selector(updateProessd) withObject:nil waitUntilDone:NO];
}
}
}
-(void)updateProessd
{
[self.delegate myOperation:self updateProess:0.01];
}
最后是viewController了,不要忘记设置代理啊,楼主表示经常忘,导致没有相关的效果
//设置代理
self.operation.delegate = self;
#pragma mark - MyOperation Delegate
//履行协议方法
-(void)myOperation:(MyOperation *)myOperation updateProess:(CGFloat)i
{
self.progressView.progress += i;
}
来看看效果图吧
GCD (Grand Gentral Dispatch 中央处理)
上面的两种方法,第一种NSThread类,好理解,但是感觉不是高大上,第二种NSOperation类,有感觉繁琐,因为用到了回调,那有没有一种代码简介又显得高大上的方法呢,自然是有的,就是这里说的GCD(又中央来对线程进行派发处理)
第一看会觉得很麻烦,因为出现了许多的‘_’,所以会有这种感觉,首先分开了解一下
//首先获得一个派发队列,queue即为获得的派发队列,是个单例,后面的参数...(楼主表示不懂,但是都是这么写的0.0),是一个C语言类型,不带*
dispatch_queue_attr_t queue = dispatch_get_global_queue(0, 0);
//在派发队列上放置一个子线程的异步操作
dispatch_async(myQueue, ^{
NSLog(@"我是子线程的异步操作");
});
如何获得主线程,来进行操作呢,毕竟progressView的修改是在主线程中进行修改的
//获得主线程
dispatch_async(dispatch_get_main_queue(), ^{
//主线程的操作
NSLog(@"我是主线程的操作");
});
dispatch_async(dispatch_queue_t queue, ^(void)block);//表示不需要等待上面的代码执行完毕
dispatch_sync(dispatch_queue_t queue, ^(void)block);//表示必须等待上面的代码执行完毕才可执行,有点同步的意思,区别就和方法中的waitUntilDo相似
//按钮点击时的回调方法
- (IBAction)startButtonClick:(id)sender
{
[self updateProgress];
}
接着是按钮点击后执行的方法
//更新进程
-(void)updateProgress
{
//首先获得一个派发队列,queue即为获得的派发队列,是个单例,后面的参数...(楼主表示不懂,但是都是这么写的0.0)
dispatch_queue_attr_t myQueue = dispatch_get_global_queue(0, 0);
//在派发队列上放置一个子线程的异步操作
dispatch_async(myQueue, ^{
for (int i = 0; i <= 100; i++)
{
//休眠0.02秒
[NSThread sleepForTimeInterval:0.02];
//获得主线程
dispatch_async(dispatch_get_main_queue(), ^{
//主线程的操作
self.progressView.progress += 0.01;
});
}
});
}
这样子看起来是不是怪怪的,那么组合起来用会显得更加高大上,如下
//更新进程
-(void)updateProgress
{
//在派发队列中添加子线程异步操作
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i <= 100; i++)
{
//进程休眠0.02秒
[NSThread sleepForTimeInterval:0.02];
//主线程修改进度条
dispatch_async(dispatch_get_main_queue(), ^{
//修改进度条
self.progressView.progress += 0.01;
});
}
});
}
看一下效果吧
以上看法以及用法属于个人理解,如果有错误,请指正,避免误导其他人,3Q