常用操作
多行注釋: Command + option + /
獲取當前的時間:CACurrentMediaTime()
后臺運行程序:[self performSelectorInBackground:@selector(longOperation) withObject:nil];
創建子線程,讓longOperation
方法在子線程異步執行
target-select:
多線程基礎
- 空的for循環不耗時
- 操作棧區內存空間不耗時,因為棧區的內存空間是連續的,不需要尋址
- 操作常量區內存空間不耗時,但是比操作棧區空間耗時一些,尋址只做一次
- 操作堆區的內存空間相對于棧區和常量區是耗時的,因為堆區的內存空間不是連續的,需要尋址
- I/O操作也是非常耗時的
- 耗時操作對UI影響:會卡死UI
- 如何解決耗時操作卡死UI的問題:使用多線程技術
- 多線程的核心思想: 就是把耗時操作放在后臺執行,避免耗時操作卡死UI
- 在實際開發中,網絡操作時非常耗時的,一般會把網絡操作(下載,上傳...)放在后臺執行
- 學習多線程就是為了學習網絡做準備的
I/O操作:是把內存的數據輸出到外接設備(屏幕,磁盤)output,把外接設備的數據輸入到內存input
同步&異步
同步
和 異步
是任務執行的兩種方式
同步:多個任務按序依次執行
異步:多個任務同時執行,就是異步執行(后臺執行就是異步執行)
進程&線程
進程:
在系統中 正在運行
的一個應用程序就是一個進程
通過 活動監視器
可以查看MAC系統中 正在運行 的所有應用程序
每個進程之間都是 獨立
的,均運行在其 專用
且 受保護
的內存空間
兩個進程之間是無法通信的,迅雷無法幫助我們下載正在播放的音樂
進程可以類比成正在 正常運行 的公司
線程:
線程可以類比成公司中的員工
進程要想執行任務,必須要有線程,且每個進程至少有一個線程
線程是進程的 基本執行單元
,進程中的所有任務都在線程中執行
程序啟動(進程開啟)會默認開啟一條線程
1個進程中可以有多個線程
多線程:
一個進程中可以開啟多條線程,多條線程可以同時執行不同的任務
進程-公司,線程-員工
多線程可以解決程序阻塞的問題
多線程可以提高程序的執行效率,給用戶良好的使用體驗
多線程執行原理:
單核CPU同一時間,CPU只能處理1個線程,只有1個線程在執行任務
多線程的同時執行:其實就是CPU在多線程之間快速切換(調度任務)
如果CPU調度線程的速度足夠快,就造成了多線程同時 執行 的假象
如果線程非常多,CPU會在多線線程之間不斷的調度任務,結果就是消耗了大量的CPU資源,CPU會趴下
每個線程調度的頻率會降低
線程的執行效率會下降
多線程的優缺點
實際開發中,能不用多線程就不用,主線程夠用
如果必須使用,就簡單使用
優點
- 能夠適當提高程序的執行效率
- 能適當提高CPU和內存的利用率
- 線程上的任務執行完成后,線程會自動銷毀,節省內存
缺點
- 開啟線程需要占用一定的內存空間,如果開啟的線程過多,會占用大量的CPU資源,降低程序的性能
2.占用內存空間:默認情況下,子線程512K,主線程1M,PS:IOS8中,主線程521K - 線程越多,CPU調度線程的開銷就越大
時間開銷
空間開銷 - 程序設計更加復雜:比如多線程之間的通信,多線程的數據共享
主線程:
- 程序一起動就會創建主線程,主線程會執行main函數,
- 一個程序運行后,默認會開啟1個線程,稱為
主線程
或UI
線程 - 主線程一般用來刷新UI界面,處理UI事件
- 處理UI事件:點擊 滾動 拖拽
- 主線程使用注意:
別將耗時的操作放在主線程中
耗時操作會卡主主線程,嚴重影響UI的流暢度,給用戶一種卡的壞體驗,影響UI交互質量
凡是跟UI相關的都是在主線程執行的(子線程創建就有,不創建就沒有)
pthread
pthread創建線程
/*
創建一個線程
參數1:子線程的ID(標識)
在C語言中,一般帶`_t`/`_ref`標識數據類型
參數2:子線程的一個屬性,一般傳入NULL
NULL:表示空地址,一般在C語言中使用
nil:表示空對象,一般在OC中使用
其實,NULL和nil本質上沒有半點區別
參數3:子線程需要執行的函數
void *(*)(void *)表示指向函數的指針,即函數名:函數名就是表示函數地址;數組地址就是數組名或者數組第0個角標元素的地址
void*:表示可以指向任何地址的指針,代表任意數據類型,類似于OC的id
void * (*) (void *):
返回值 函數名 函數參數
參數4:子線程需要執行的函數的參數
返回值:int;在很多C語言框架中,不是遵守非零即真,因為成功的結果只有一個,0是唯一的;失敗的原因有很多
線程調試:查看方法執行的線程是否是主線程或者子線程
{number = 1, name = main}:表示主線程
{number = 3, name = (null)}:表示子線程,主要number != 1,就表示子線程
提示:千萬不要糾結number到底等于幾,系統分配的
__bridge:在c語言和oc語言混合開發時,需要做數據類型轉換,有時候需要使用橋接
橋接作用:在做數據類型轉換時,告訴編譯器如何管理c語言的內存
提示:在ARC環境下,編譯器在編譯時,不會自動管理C語言申請的內存空間
提示:加上__bridge 表示告訴編譯器,c語言申請的內存也是自動管理的,因為大環境是ARC的
MRC環境下,不需要使用__bridge,因為手動管理內存
*/
NSLog(@"%@",[NSThread currentThread]);
//參數1
pthread_t ID;
//參數4
NSString *str = @"hello";
//創建了一個子線程
int result = pthread_create(&ID, NULL, demo, (__bridge void *)(str));
//判斷子線程創建是否成功
if(result == 0){
NSLog(@"創建子線程成功");
}else{
NSLog(@"創建子線程失敗");
}
/**
子線程執行的函數
*/
void *demo(void *param){
NSString *str = (__bridge NSString *)(param);
//currentThread:查看當前線程
NSLog(@"demo %@ - %@",[NSThread currentThread],str);
return NULL;
}
NSThread
創建線程的3種方式
//分類方法創建子線程
//不可以拿到線程對象
//不需要手動啟動線程
-(void)threadDemo3{
//方便所有繼承自NSObject的對象,可以直接調用線程的方法(swift里面沒有這個分類,也沒有GCD)
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}
//構造方法創建子線程
//不可以拿到線程對象
//不需要手動啟動線程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self threadDemo2];
}
-(void)threadDemo2{
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}
//構造方法創建子線程
//可以拿到線程對象
//需要手動啟動線程
-(void)threadDemo1{
//創建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
//啟動線程
[thread start];
}
-(void)demo:(id)param{
//子線程執行的方法
NSLog(@"%@-%@",param,[NSThread currentThread]);
}
target和selector的關系
- 執行哪個對象的哪個方法
- 需求:執行Person對象的run方法
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"alloc"];
線程生命周期/線程狀態
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"喜大普奔,神舟十一");
//注意:千萬不要在主線中,使用這個方法,會使主線程死亡
//[NSThread exit];
[self threadDemo];
}
-(void)threadDemo{
//提示:程序員只能夠做新建和就緒,其他的都由系統來處理
//創建線程對象:新建狀態
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
//啟動線程:就緒狀態(把線程對象添加到可調度線程池,等待被CPU調度執行)
[thread start];
}
-(void)demo{
//NSLog(@"%@",[NSThread currentThread]);
for (NSInteger i = 0; i < 5; i++) {
//線程每次執行到這里就休眠1秒鐘:for循環,每循環一次就休息1秒鐘
//sleepForTimeInterval:使當前的線程休眠到指定時長
//sleepForTimeInterval:使用場景就是模擬網絡延遲操作,僅僅是模擬,開發中不會使用的
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%zd - %@",i,[NSThread currentThread]);
if(i == 2){
//sleepUntilDate:使當前線程休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
NSLog(@"藍瘦,香菇");
}
if(i == 3){
//使當前線程強制死亡
[NSThread exit];
}
}
}
線程屬性
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"主:%tu",[NSThread currentThread].stackSize/1024);
//新建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
//設置線程對象的name屬性,標識一個唯一的線程對象,方便跟蹤
thread.name = @"t1";
//設置線程對象的優先級:浮點數0.0-1.0,最高是1.0,默認是0.5
//線程優先級不能決定線程執行的先后順序,只能決定某個線程有更多機會被CPU先調度執行完,概率事件
//注意:實際開發中,千萬不要設置優先級,或者服務器質量,會出現意想不到的問題;使用默認的,讓系統自己來處理
//thread.threadPriority = 1.0;
//threadPriority:在目前即將被廢棄,使用qualityOfService替代
thread.qualityOfService = NSQualityOfServiceUserInteractive;
//就緒狀態
[thread start];
//新建線程
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
thread2.name = @"t2";
thread2.threadPriority = 0.1;
//就緒狀態
[thread2 start];
}
-(void)demo{
//stackSize:線程占用內存空間大小
NSLog(@"子:%tu",[NSThread currentThread].stackSize/1024);
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"%zd -- %@",i,[NSThread currentThread]);
}
/*模擬崩潰:演示name屬性
NSMutableArray *arrM = [NSMutableArray array];
NSObject *obj = nil;
[arrM addObject:obj];
*/
}
資源共享-線程安全
共享資源
資源:一個全局對象,一個全局變量,一個文件
共享:可以被多個對象訪問
共享資源:可以被多個對象訪問的資源,比如全局對象,變量,文件
在多線程的環境下,共享的資源,可能被多個線程共享,也就是多個線程可能會操作同一塊資源
@interface ViewController ()
//總票數:共享資源
@property (assign,nonatomic) NSInteger tickets;
@end
//需求驅動開發:沒有需求,無從開發
//需求:開發買票系統
//先要有開發邏輯,后有開發代碼
//開發邏輯:先干什么,后干什么
//分析需求,得到發的邏輯
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化總票數
self.tickets = 20;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//[self sellTickers];
//售票口1
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
thread1.name = @"t1";
[thread1 start];
//售票口1
NSThread * thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
thread2.name = @"t2";
[thread2 start];
}
//賣票主方法
-(void)sellTickers{
while (YES) {
//互斥鎖/同步鎖:使用了線程同步技術
//特點:可以保證被鎖定的代碼,同一時間只有一個線程可以訪問
//self:表示互斥鎖的參數,互斥鎖的參數,又叫做鎖對象
//鎖對象:任何繼承自NSObject的對象,都可以作為互斥鎖的參數,內部有把鎖,默認是開啟的
//鎖對象必須是全局的對象;self是最方便獲取的全局的鎖對象
//局部鎖對象是鎖不住的,因為每次線程進來之前會新建一把鎖
//提示:加鎖的事情,不能再客戶端操作;是服務器加鎖的,多線程資源共享,絕大多數是在服務器發生;
//提示:加鎖是犧牲了性能,保證了安全,客戶端的性能不能輕易犧牲
//創建一個局部的鎖對象
NSObject *obj = [[NSObject alloc] init];
@synchronized (self) {
//判斷是否有余票
if(self.tickets > 0 ){
//模擬網絡延遲:沒有實際意義,僅僅是模擬延遲而已,可以忽略
//[NSThread sleepForTimeInterval:1.0];
//如果有余票,賣一張
self.tickets = self.tickets - 1;
//提醒余票
NSLog(@"%zd--%@",self.tickets,[NSThread currentThread]);
}else{
//如果沒有余票,提醒用戶無票
NSLog(@"無票了");
break;
}
}
}
}
原子屬性
@interface ViewController ()
//非原子屬性
@property (nonatomic,strong) NSObject *obj1;
//原子屬性
@property (strong) NSObject *obj2;//一旦重寫了getter和setter方法,系統不會自動生成帶下劃線的成員變量
//需要自己合成帶下劃線的成員變量
@end
/*
原子屬性:單寫多讀
單寫多讀:同一時間只有一個線程可以訪問setter方法,但是可以有多個線程訪問getter方法
注意:原子屬性的setter方法是線程安全的,getter方法是線程非安全的
setter方法內部有自旋鎖
自旋鎖:看不見的,由系統封裝的
可以保證被鎖定的代碼,同一時間只能有一個線程可以訪問
一旦外面的線程,發現代碼被自旋鎖鎖定,外面的線程會以死循環的方式等待開鎖
互斥鎖:
可以保證鎖定的代碼,同一時間只能有一個線程可以訪問
一旦外面的線程,發現代碼被互斥鎖鎖定,外面的線程就會進入就緒狀態,
*/
@implementation ViewController
@synthesize obj2 = _obj2;
-(void)setObj2:(NSObject *)obj2{
//由于自旋鎖看不見,所以可以使用互斥鎖替換,演示單寫多讀
@synchronized (self) {
_obj2 = obj2;
}
}
-(NSObject *)obj2{
return _obj2;
}
異步下載網絡圖片
@interface ViewController ()
//根視圖
@property (strong,nonatomic) UIScrollView *scrollView;
//圖片子視圖
@property (nonatomic,weak) UIImageView *imgView;
@end
//需求:異步下載網絡圖片,圖片可以滾動,滾動視圖要是根視圖
//分析需求:下載是耗時的操作,需要在子線程異步執行
//準備控件的工作UIImageView/UIScrollView(根視圖)
//
@implementation ViewController
/*
loadView:優先于viewDidLoad
loadView:當self.view == nil 時調用
loadView:不需要調用super
*/
-(void)loadView{
//創建根視圖
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
//把根視圖替換成scrollView
self.view = self.scrollView;
self.scrollView.backgroundColor = [UIColor redColor];
//創建圖片子視圖
UIImageView *imgView = [[UIImageView alloc] init];
[self.view addSubview:imgView];
//給屬性賦值:一定不能少
self.imgView = imgView;
}
- (void)viewDidLoad {
[super viewDidLoad];
//[self laodImageData];
[self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}
//在子線程下載圖片,在主線程更新UI,是線程間通信的一種
//線程間通信:一個線程把他執行的結果,傳遞到另外的一個線程
//下載圖片的主方法
-(void)loadImageData{
//URL
NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
//發送網絡請求,獲取圖片二進制數據,是個耗時的操作
NSData *data = [NSData dataWithContentsOfURL:url];
//image就是子線程執行的結果,需要傳遞到主線程
UIImage *image = [UIImage imageWithData:data];
//下載完成后,通知主線程刷新UI
//waitUntilDone:是否等到updateUI執行完,再執行后面的代碼,一般傳入NO
[self performSelectorOnMainThread:@selector(updataUI:) withObject:image waitUntilDone:NO];
NSLog(@"后面的代碼");
}
-(void)updataUI:(UIImage *)image{
//下載完成后,刷新UI
self.imgView.image = image;
[self.imgView sizeToFit];
self.scrollView.contentSize = image.size;
}
異步下載
info.plist中添加代碼,允許請求網絡
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>