目錄
一. 多線程的概念(程序, 進(jìn)程, 線程)
二. 為什么使用多線程
三. 怎樣創(chuàng)建線程(2種方法)
四. 怎樣監(jiān)聽線程的結(jié)束, 取消線程
五. 線程的優(yōu)先級
六. 線程的同步鎖
- MyAccount類
- ViewController.m(此時執(zhí)行就會出現(xiàn)取款超出余額的問題, 需要添加同步鎖)
- 第一種添加線程鎖的方法(在MyAccount類中的- (void)withDraw:(float)tmpMoney方法中用@synchronized(self)添加鎖)
- 第二種添加線程鎖的方法(設(shè)一個成員變量:NSLock的對象)
七. NSOperation創(chuàng)建線程
- NSInvocationOperation
- NSBlockOperation
- 自定義NSBlockOperation的子類
八. Block
- 沒有返回值的block
- 有返回值, 返回值類型是基本類型(int為例)
- 有返回值, 返回值類型是基本類型(int為例)
- 測試
一. 多線程的概念
程序: 一段代碼, 是一個靜態(tài)的文件
進(jìn)程: 一個運(yùn)行起來的程序, 進(jìn)程實(shí)惠占用內(nèi)存的
線程: 進(jìn)程的組成部分, 一個進(jìn)程至少需要一個線程, 是進(jìn)程處理邏輯的基本單元, iOS程序運(yùn)行起來后, 默認(rèn)創(chuàng)建一個主線程, 自動維護(hù)這個主線程; 如果需要使用多線程, 必須自己手動創(chuàng)建和維護(hù)
二. 為什么使用多線程
將下載或者數(shù)據(jù)庫操作等放在了主線程里面, 會阻塞主線程, 造成一種假死的現(xiàn)象
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *button1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"下載數(shù)據(jù)" backgroundImageName:nil target:self action:@selector(downloadAction)];
[self.view addSubview:button1];
// 按鈕2: 點(diǎn)擊打印一條信息
UIButton *button2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"點(diǎn)擊" backgroundImageName:nil target:self action:@selector(clickButton)];
[self.view addSubview:button2];
}
- (void)downloadAction
{
NSLog(@"%s", __func__);
}
- (void)clickButton
{
// 讓當(dāng)前的線程睡眠
// 模擬下載數(shù)據(jù)需要的時間
[NSThread sleepForTimeInterval:10];
// 將下載放在了主線程里面, 會阻塞主線程
// 造成了一種假死的現(xiàn)象
// 類似下載或者數(shù)據(jù)庫操作的邏輯, 占用時間加長, 需要在主線程以外的線程中去處理
NSLog(@"%s", __func__);
}
三. 怎樣創(chuàng)建線程
1. 使用NSThread來創(chuàng)建線程1
// 1. 點(diǎn)擊按鈕, 創(chuàng)建一個線程
UIButton *btn1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"創(chuàng)建線程1" backgroundImageName:nil target:self action:@selector(createThreadOne)];
[self.view addSubview:btn1];
// 創(chuàng)建線程1
- (void)createThreadOne
{
// detachNewThreadSelector:<#(SEL)#> toTarget:<#(id)#> withObject:<#(id)#>
/*
創(chuàng)建了一個線程, 同時將線程啟動
第一個參數(shù): 線程的執(zhí)行體方法
第二個參數(shù): 線程執(zhí)行體方法所屬的對象
第三個參數(shù): 線程的執(zhí)行體方法的參數(shù)
*/
NSNumber *n = @100;
[NSThread detachNewThreadSelector:@selector(threadOne:) toTarget:self withObject:n];
}
// 線程1的執(zhí)行體方法
- (void)threadOne:(NSNumber *)n
{
for (int i = 0; i < n.intValue; i++) {
NSLog(@"線程1:%d", i);
// 讓當(dāng)前線程睡眠
[NSThread sleepForTimeInterval:5];
}
}
2. 使用NSThread來創(chuàng)建線程2
// 2. 創(chuàng)建線程的第二種方式
UIButton *btn2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"創(chuàng)建線程2" backgroundImageName:nil target:self action:@selector(createThreadTwo)];
[self.view addSubview:btn2];
NSLog(@"%@", [NSThread currentThread].name);
- (void)createThreadTwo
{
/*
創(chuàng)建了一個線程, 線程沒有自動啟動
第一個參數(shù): 線程執(zhí)行體方法所屬的對象
第二個參數(shù): 線程的執(zhí)行體方法
第三個參數(shù): 線程的執(zhí)行體方法的參數(shù)
*/
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
// 設(shè)置線程的名字
thread2.name = @"下載線程";
// 手動啟動線程
[thread2 start];
}
// 線程2的執(zhí)行體方法
- (void)threadTwo
{
for (int i = 0; i < 100; i++) {
// 獲取當(dāng)前的線程對象
NSThread *currentThread = [NSThread currentThread];
NSLog(@"%@ %d", currentThread.name, i+1);
}
}
四. 怎樣監(jiān)聽線程的結(jié)束, 取消線程
1. 第1種取消線程方法
#import "ViewController.h"
@interface ViewController ()
{
// 線程2是否取消
BOOL _isThreadTwoCancel;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建2個線程, 并啟動
// 第一個線程執(zhí)行for循環(huán), 第二個線程執(zhí)行死循環(huán)
// 第一個線程結(jié)束后取消第二個線程
// 程序接收到第二個線程結(jié)束的信息后, 停止第二個線程的執(zhí)行
// 創(chuàng)建第一個線程
[NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
// 創(chuàng)建第二個線程
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[t2 start];
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"執(zhí)行線程1");
if (i == 999) {
NSLog(@"線程1執(zhí)行完畢");
// 取消線程2
_isThreadTwoCancel = YES;
}
}
}
- (void)threadTwo
{
int i = 0;
while (true) {
// 退出線程的方法需要放到循環(huán)里面
if (_isThreadTwoCancel) {
// 結(jié)束當(dāng)前的線程
[NSThread exit];
}
NSLog(@"執(zhí)行線程2: %d", i+1);
i++;
}
}
2. 第2種取消線程方法
@interface ViewController ()
{
…………………………………………………………………………………………
NSThread *_t2;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
…………………………………………………………………………………………
// 創(chuàng)建第二個線程
// NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
// [t2 start];
_t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[_t2 start];
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"執(zhí)行線程1");
// if (i == 999) {
// NSLog(@"線程1執(zhí)行完畢");
// // 取消線程2
// _isThreadTwoCancel = YES;
// }
if (i == 999) {
NSLog(@"線程1執(zhí)行完畢");
// 取消線程2
[_t2 cancel];
}
}
}
- (void)threadTwo
{
int i = 0;
while (true) {
if ([_t2 isCancelled]) {
// 結(jié)束當(dāng)前的線程
[NSThread exit];
}
NSLog(@"執(zhí)行線程2: %d", i+1);
i++;
}
}
3. 通過通知中心監(jiān)聽線程的結(jié)束(給線程1和線程2先設(shè)置name屬性)
- (void)viewDidLoad {
[super viewDidLoad];
…………………………………………………………………………………………
_t2.name = @"II";
// 監(jiān)聽線程是否結(jié)束
// NSThreadWillExitNotification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillExit:) name:NSThreadWillExitNotification object:nil];
}
// 在線程結(jié)束的時候執(zhí)行一些操作
- (void)threadWillExit:(NSNotification *)n
{
NSThread *t = [n object];
NSLog(@"線程%@結(jié)束", t.name);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)threadOne
{
[NSThread currentThread].name = @"I";
…………………………………………………………………………………………
}
五. 線程的優(yōu)先級
每個線程創(chuàng)建之后都有優(yōu)先級
優(yōu)先級在0-1之間, 值越大, 優(yōu)先級越高
默認(rèn)優(yōu)先級: 0.5
優(yōu)先級高的線程執(zhí)行的機(jī)會更大
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 每個線程創(chuàng)建之后都有優(yōu)先級
// 優(yōu)先級在0-1之間, 值越大, 優(yōu)先級越高
// 默認(rèn)優(yōu)先級: 0.5
// 優(yōu)先級高的線程執(zhí)行的機(jī)會更大
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
t1.name = @"線程I";
[t1 start];
t1.threadPriority = 0;
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
t2.name = @"線程II";
[t2 start];
t2.threadPriority = 1;
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"執(zhí)行了%@: %d", [NSThread currentThread].name, i);
}
}
- (void)threadTwo
{
for (int i = 0; i < 1000; i++) {
NSLog(@"執(zhí)行了%@: %d", [NSThread currentThread].name, i);
}
}
六. 線程的同步鎖
模擬銀行賬戶取錢
創(chuàng)建一個賬戶對象, 模擬同時有2個人取錢
1. MyAccount類
#import <Foundation/Foundation.h>
@interface MyAccount : NSObject
/*
accountNo: 賬戶的號碼
money: 賬戶的余額
*/
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money;
- (void)withDraw:(float)tmpMoney;
@end
#import "MyAccount.h"
@implementation MyAccount
{
// 賬戶的號碼
NSString *_accountNo;
// 余額
float _money;
}
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
self = [super init];
if (self) {
// 給成員變量賦值
_accountNo = accountNo;
_money = money;
}
return self;
}
// 取錢的操作
- (void)withDraw:(float)tmpMoney
{
if (_money >= tmpMoney) {
// 模擬取錢操作時需要一些時間
[NSThread sleepForTimeInterval:0.01];
// 取錢
_money -= tmpMoney;
NSLog(@"%@取了%f元錢", [NSThread currentThread].name, tmpMoney);
} else {
NSLog(@"余額不足");
}
}
@end
2. ViewController.m(此時執(zhí)行就會出現(xiàn)取款超出余額的問題, 需要添加同步鎖)
#import "ViewController.h"
#import "MyAccount.h"
// 系統(tǒng)還有NSCondition 實(shí)現(xiàn)多線程的方式
@interface ViewController ()
{
// 公共的賬戶
MyAccount *_account;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建一個賬戶
_account = [[MyAccount alloc] initWithAccountNo:@"Yuen" money:9000000];
// 創(chuàng)建兩個線程
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[t2 start];
}
- (void)threadOne
{
[NSThread currentThread].name = @"Yank";
[_account withDraw:8000000];
}
- (void)threadTwo
{
[NSThread currentThread].name = @"Alfred";
[_account withDraw:2000000];
}
@end
3. 第一種添加線程鎖的方法(在MyAccount類中的- (void)withDraw:(float)tmpMoney
方法中用@synchronized(self)添加鎖)
- (void)withDraw:(float)tmpMoney
{
// @synchronized(self)添加鎖
// 保證_money成員變量在同一時刻只有一個線程修改
@synchronized(self) {
if (_money >= tmpMoney) {
// 模擬取錢操作時需要一些時間
[NSThread sleepForTimeInterval:0.01];
// 取錢
_money -= tmpMoney;
NSLog(@"%@取了%f元錢", [NSThread currentThread].name, tmpMoney);
} else {
NSLog(@"余額不足");
}
}
}
4. 第二種添加線程鎖的方法(設(shè)一個成員變量:NSLock的對象)
#import "MyAccount.h"
@implementation MyAccount
{
…………………………………………………………………………………………
// 線程鎖
NSLock *_lock;
}
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
self = [super init];
if (self) {
…………………………………………………………………………………………
// 初始化線程鎖
_lock = [[NSLock alloc] init];
}
return self;
}
// 取錢的操作
- (void)withDraw:(float)tmpMoney
{
[_lock lock];
…………………………………………………………………………………………
// 釋放鎖
[_lock unlock];
}
七. NSOperation創(chuàng)建線程
1. NSInvocationOperation
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建線程的隊(duì)列
_queue = [[NSOperationQueue alloc] init];
// 1. NSInvoationOperation
/*
第一個參數(shù): 線程的執(zhí)行體由哪個對象執(zhí)行
第二個參數(shù): 線程的執(zhí)行體對象的方法
第三個參數(shù): 線程的執(zhí)行體方法需要傳遞的實(shí)參
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
// 線程執(zhí)行完成后調(diào)用的block
[op1 setCompletionBlock:^{
NSLog(@"線程I執(zhí)行完成");
}];
// 把線程添加到隊(duì)列中
[_queue addOperation:op1];
}
- (void)threadOne
{
for (int i = 0; i < 100; i++) {
NSLog(@"線程一:%d", i);
}
}
- (void)dealloc
{
// 取消所有線程
[_queue cancelAllOperations];
}
2. NSBlockOperation
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建線程的隊(duì)列
_queue = [[NSOperationQueue alloc] init];
// 2. NSBlockOperation
// 參數(shù)是一個代碼塊, 是線程的執(zhí)行體
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++) {
NSLog(@"執(zhí)行了線程II:%d", i);
}
}];
// 線程執(zhí)行完成后調(diào)用的block
[op1 setCompletionBlock:^{
NSLog(@"線程II執(zhí)行完成");
}];
[_queue addOperation:op2];
}
- (void)dealloc
{
// 取消所有線程
[_queue cancelAllOperations];
}
3. 自定義NSBlockOperation的子類
-
ImageOperation類
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface ImageOperation : NSOperation // 圖片的URL字符串 @property (nonatomic, strong) NSString *urlString; // 圖片視圖對象 @property (nonatomic, strong) UIImageView *imageView; @end #import "ImageOperation.h" @implementation ImageOperation // 自定義NSOperation類型, 需要實(shí)現(xiàn)main方法 // 這個方法是線程的執(zhí)行體 - (void)main { NSURL *url = [NSURL URLWithString:self.urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; // 在主線程刷新UI [self performSelectorOnMainThread:@selector(refreshUI:) withObject:data waitUntilDone:YES]; } - (void)refreshUI:(NSData *)data { self.imageView.image = [UIImage imageWithData:data]; } @end
-
ViewController.m
#import "ViewController.h" #import "ImageOperation.h" @interface ViewController () { NSOperationQueue *_queue; UIImageView *_myImageView; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 創(chuàng)建線程的隊(duì)列 _queue = [[NSOperationQueue alloc] init]; _myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 240, 320)]; [self.view addSubview:_myImageView]; // 第三種創(chuàng)建線程的方式 // 創(chuàng)建一個線程, 下載一張圖片, 顯示到視圖上 UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; btn.frame = CGRectMake(100, 40, 80, 40); [btn setTitle:@"下載" forState:UIControlStateNormal]; [self.view addSubview:btn]; [btn addTarget:self action:@selector(downloadAction) forControlEvents:UIControlEventTouchUpInside]; } - (void)downloadAction { // 創(chuàng)建線程下載圖片 ImageOperation *op = [[ImageOperation alloc] init]; // 圖片的URL op.urlString = @"http://img3.3lian.com/2006/027/08/007.jpg"; // 圖片視圖 op.imageView = _myImageView; // 添加到隊(duì)列里面 [_queue addOperation:op]; } @end
八. Block
1. 沒有返回值的block
- (void)testNoReturnBlock
{
// 1. 沒有參數(shù)的
// 聲明
void (^block1)(void);
// 賦值
block1 = ^{
NSLog(@"沒有返回值沒有參數(shù)");
};
// 使用
block1();
// 2. 有一個參數(shù), 參數(shù)是基本類型
// 聲明
void (^block2)(int a);
// 賦值
block2 = ^(int a){
NSLog(@"a = %d", a);
};
// 使用
block2(10);
// 3. 有一個參數(shù), 參數(shù)是對象類型
// 聲明
void (^block3)(NSString *);
// 賦值
block3 = ^(NSString *str){
NSLog(@"%@", str);
};
// 使用
block3(@"張三");
// 4. 兩個參數(shù), 一個基本類型, 一個對象類型
// 聲明
void (^block4)(int age, NSString *name);
// 賦值
block4 = ^(int age, NSString *name){
NSLog(@"%@'s %d years old", name, age);
};
// 使用
block4(54, @"Lau");
}
2. 有返回值, 返回值類型是基本類型(int為例)
- (void)testIntValueReturn
{
// 1. 沒有參數(shù)
// 聲明
int (^block1)(void);
// 賦值
block1 = ^{
return 10;
};
// 使用
NSLog(@"%d", block1());
// 2. 有一個參數(shù), 參數(shù)是基本類型
// 聲明
int (^block2)(int);
// 賦值
block2 = ^(int a){
return a;
};
// 使用
NSLog(@"%d", block2(5));
// 3. 有一個參數(shù), 參數(shù)是對象類型
// 聲明
int (^block3)(NSString *);
block3 = ^(NSString *str){
return (int)str.length;
};
NSLog(@"%d", block3(@"Sunshine"));
// 4. 有兩個參數(shù), 都是對象類型
// 聲明
int (^block4)(NSString *str1, NSString *str2);
// 賦值
block4 = ^(NSString *str1, NSString *str2){
return (int)(str1.length + str2.length);
};
NSLog(@"%d", block4(@"Sunshine", @"Rain"));
}
3. 有返回值, 返回值類型是基本類型(int為例)
- (void)testNSStringValueReturn
{
// 1. 沒有參數(shù)
// 聲明
NSString *(^block1)(void);
// 賦值
block1 = ^{
return @"Nexus";
};
// 使用
NSLog(@"%@",block1());
// 2. 有一個參數(shù), 參數(shù)是基本類型
// 聲明
NSString *(^block2)(int);
// 賦值
block2 = ^(int a){
return [NSString stringWithFormat:@"%d", a];
};
// 使用
NSLog(@"%@", block2(5));
// 3. 有一個參數(shù), 參數(shù)是對象類型
// 聲明
NSString *(^block3)(NSString *);
block3 = ^(NSString *str){
return str;
};
NSLog(@"%@", block3(@"Sunshine"));
// 4. 有兩個參數(shù), 都是對象類型
// 聲明
NSString *(^block4)(NSString *str1, NSString *str2);
// 賦值
block4 = ^(NSString *str1, NSString *str2){
return [NSString stringWithFormat:@"%@ %@", str1, str2];
};
NSLog(@"%@", block4(@"Sunshine", @"Rain"));
}
4. 測試
- (void)test
{
// 1. 沒有返回值, 有2個參數(shù), 一個基本類型, 一個對象類型
// 聲明并賦值
void (^block1)(int age, NSString *name) = ^(int age, NSString *name){
NSLog(@"%@'s %d years old", name, age);
};
// 使用
block1(23, @"Yuen");
// 2. 返回值為int類型, 有2個參數(shù), 參數(shù)是NSString類型
// 聲明并賦值
int (^block2)(NSString *, NSString *) = ^(NSString *str1, NSString *str2){
return (int)(str1.length + str2.length);
};
// 使用
NSLog(@"%d", block2(@"Sunshine", @"Rain"));
// 3. 返回值是NSString類型, 有2個參數(shù), 參數(shù)是NSString類型
NSString *(^block3)(NSString *,NSString *) = ^(NSString *str1, NSString *str2){
return [NSString stringWithFormat:@"%@%@", str1, str2];
};
// 使用
NSLog(@"%@", block3(@"Sunshine", @"Rain"));
}