> 不積跬步,無以至千里;不積小流,無以成江海。——荀子
[TOC]
2017-03-06
一、蘋果核 - 菜鳥不要怕,看一眼,你就會用GCD,帶你裝逼帶你飛
1.Serial Diapatch Queue 串行隊列
//第一個參數是隊列名字。第二個參數是隊列類型,如果設置為NULL,創建出來的是串行隊列
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
2.Concurrent Diapatch Queue 并發隊列
//與串行隊列剛好相反,他不會存在任務間的相互依賴。
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
3.Global Queue & Main Queue
Global Queue其實就是系統創建的Concurrent Dispatch Queue
Main Queue 其實就是系統創建的位于主線程的Serial Dispatch Queue
通常情況我們會把這2個隊列放在一起使用,也是我們最常用的開異步線程-執行異步任務-回主線程的一種方式
- Global Queue的優先級:
DISPATCH_QUEUE_PRIORITY_HIGH 2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
- 異步主線程 dispatch_async(dispatch_get_main_queue(), ^{});
在日常工作中,除了在其他線程返回主線程的時候需要用這個方法,還有一些時候我們在主線程中直接調用異步主線程,這是利用dispatch_async的特性:block中的任務會放在主線程本次runloop之后返回。 這樣,有些存在先后順序的問題就可以得到解決了。
4.dispatch_set_target_queue
dispatch_set_target_queue為自己創建的隊列設置優先級
5.dispatch_after
這個是最常用的,用來延遲執行的GCD方法,因為在主線程中我們不能用sleep來延遲方法的調用,所以用它是最合適的
6.dispatch_group
當我們需要監聽一個并發隊列中,所有任務都完成了,就可以用到這個group,因為并發隊列你并不知道哪一個是最后執行的,所以以單獨一個任務是無法監聽到這個點的,如果把這些單任務都放到同一個group,那么,我們就能通過dispatch_group_notify方法知道什么時候這些任務全部執行完成了。
通過把這個并發隊列任務統一加入group中,group每次runloop的時候都會調用一個方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用來檢查group中的任務是否已經完成,如果已經完成了,那么會執行dispatch_group_notify的block
7.dispatch_barrier
此方法的作用是在并發隊列中,完成在它之前提交到隊列中的任務后打斷,單獨執行其block,并在執行完成之后才能繼續執行在他之后提交到隊列中的任務。
這其實就起到了一個線程鎖的作用,在多個線程同時操作一個對象的時候,讀可以放在并發進行,當寫的時候,我們就可以用dispatch_barrier_async方法,效果杠杠的。
8.dispatch_sync
dispatch_sync 會在當前線程執行隊列,并且阻塞當前線程中之后運行的代碼,所以,同步線程非常有可能導致死鎖現象,我們這邊就舉一個死鎖的例子:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"有沒有同步主線程?");
});
根據FIFO(先進先出)的原則,block里面的代碼應該在主線程此次runloop后執行,但是由于他是同步隊列,所有他之后的代碼會等待其執行完成后才能繼續執行,2者相互等待,所以就出現了死鎖。
9.dispatch_apply
這個方法用于無序查找,在一個數組中,我們能開啟多個線程來查找所需要的值
10.dispatch_suspend & dispatch_resume
隊列掛起和恢復
11.Semaphore
我們可以通過設置信號量的大小,來解決并發過多導致資源吃緊的情況,以單核CPU做并發為例,一個CPU永遠只能干一件事情,那如何同時處理多個事件呢,聰明的內核工程師讓CPU干第一件事情,一定時間后停下來,存取進度,干第二件事情以此類推,所以如果開啟非常多的線程,單核CPU會變得非常吃力,即使多核CPU,核心數也是有限的,所以合理分配線程,變得至關重要,那么如何發揮多核CPU的性能呢?如果讓一個核心模擬傳很多線程,經常干一半放下干另一件事情,那效率也會變低,所以我們要合理安排,將單一任務或者一組相關任務并發至全局隊列中運算或者將多個不相關的任務或者關聯不緊密的任務并發至用戶隊列中運算,所以用好信號量,合理分配CPU資源,程序也能得到優化,當日常使用中,信號量也許我們只起到了一個計數的作用,真的有點大材小用。
dispatch_semaphore_t semapore = dispatch_semaphore_create(10);//為了讓一次輸出10個,初始信號量為10
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for(int i = 0; i < 20; i++){
//第二個參數為timeout 遞減信號量,如果信號量小于零,會一直等待到一個信號發生
dispatch_semaphore_wait(semapore, DISPATCH_TIME_FOREVER);//每進來1次,信號量-1;進來10次后就一直hold住,直到信號量大于0;
dispatch_async(queue, ^{
NSLog(@"%i",i);
sleep(2);
//遞增信號量的值,如果上一個值小于0,該方法返回前會喚醒一個等待的線程
dispatch_semaphore_signal(semapore);//由于這里只是log,所以處理速度非常快,我就模擬2秒后信號量+1;
});
}
12.dispatch_once
這個函數一般是用來做一個真的單例,也是非常常用的。
2017-03-07
一、 sunny分享
CFRunLoopSource
- Source是RunLoop的數據源抽象類(protocol)
- RunLoop定義了兩個Version的Source:
Source0:處理App內部事件、App自己負責管理(觸發),如UIEvent、CFSocket
Source1:由RunLoop和內核管理,Mach port驅動,如CFMachPort、CFMessagePort
CFRunLoopMode
RunLoop在同一段時間只能且必須在一種特定Mode下Run
更換Mode時,需要停止當前Loop,然后重啟新Loop
Mode是iOS App滑動順暢的關鍵
可以定制自己的Mode
NSDefaultRunLoopMode
默認狀態、空閑狀態
UITrackingRunLoopMode
滑動ScrollView時
UIInitializationRunLoopMode
私有,App啟動時
NSRunLoopCommonModes
Mode集合
2017-03-08
一、iOS 無需SDK,一行代碼調用主流地圖
高德地圖、百度地圖和騰訊地圖url規則:
/*
必要參數的意思:sourceApplication:該app名字; poi name:目的地名稱;lat/lon:目的地經緯度
dev 參數進行解釋:dev支持的值為"0"和"1",即是否需要進行國測局坐標加密。 如果傳入的坐標已經是國測局坐標則傳入0,如果傳入的是地球坐標,則該參數傳入1
*/
#define GaoDeNavUrl @"iosamap://navi?sourceApplication=%@&backScheme=%@&poiname=%@&poiid=BGVIS&lat=%@&lon=%@&dev=0&style=2"
#define GaoDeGPSUrl @"iosamap://viewMap?sourceApplication=%@&poiname=%@&lat=%@&lon=%@&dev=0"
/*
百度地圖的參數意思就比較簡單了,對mode做一個解釋,mode為調用地圖之后的導航方式,除了walking(步行)還有driving(駕車)和transit(公交)
origin=latlng:0,0 這個參數雖然意思上是要給一個當前坐標,但是可以隨意設置,這里設置兩個0,不影響導航
*/
#define BaiDuNavUrl @"baidumap://map/direction?origin=latlng:0,0|name:我的位置&destination=latlng:%@,%@|name:%@&mode=walking"
#define BaiDuGPSUrl @"baidumap://map/marker?location=%@,%@&title=我的位置&content=%@"
//騰訊地圖導航
#define TXGPSUrl @"qqmap://map/marker?marker=coord:%@,%@;title:%@;addr:%@"
#define TXNavUrl @"qqmap://map/routeplan?type=walk&from=我的位置&to=%@&tocoord=%@,%@&referer=%@"
2017-03-09
一、UIView形變動畫
當UIView發生變換(transformed)后,不要使用frame屬性,因為它往往不能代表視圖的正確位置,使用bounds和center屬性代替。見UIView(UIViewGeometry)中的說明
anchorPoint在視圖左上角為(0,0),在視圖右下角為(1,1),默認(0.5,0.5)。蘋果文檔中注釋是錯誤的(normalized layer coordinates - '(0, 0)' is the bottom left corner of the bounds rect)。
如果更改了一個圖層的anchorPoint,那么這個圖層會發生位移。iOS圍繞某點縮放或旋轉的AnchorPoint的設定
point、anchorPoint和frame三者的關系:
position.x = frame.origin.x + anchorPoint.x * bounds.size.width;
position.y = frame.origin.y + anchorPoint.y * bounds.size.height
這將是你最后一次糾結position與anchorPoint!
- 修改position和anchorPoint會影響frame屬性。
2017-03-10
一、iOS8.0之后的隊列優先級 MBProgressHUD庫中有用到
dispatch_queue_create函數,在iOS8之前,不能在創建時設定優先級,所以用dispatch_set_target_queue設置優先級,另外,如果兩個隊列dispatch_set_target_queue到同一個隊列,DISPATCH_QUEUE_SERIAL串行、DISPATCH_QUEUE_CONCURRENT并行對兩個隊列也有作用,可以參考demo中方法testGCDTarget;iOS 8之后用dispatch_queue_attr_make_with_qos_class直接創建隊列并設置優先級等;QOS_CLASS_USER_INITIATED:為iOS8之后的優先級,其它優先級為:
- QOS_CLASS_USER_INTERACTIVE: user interactive等級表示任務需要被立即執行以提供好的用戶體驗。使用它來更新UI,響應事件以及需要低延時的小工作量任務。這個等級的工作總量應該保持較小規模。
- QOS_CLASS_USER_INITIATED:user initiated等級表示任務由UI發起并且可以異步執行。它應該用在用戶需要即時的結果同時又要求可以繼續交互的任務。
- QOS_CLASS_UTILITY:utility等級表示需要長時間運行的任務,常常伴隨有用戶可見的進度指示器。使用它來做計算,I/O,網絡,持續的數據填充等任務。這個等級被設計成節能的。
- QOS_CLASS_BACKGROUND:background等級表示那些用戶不會察覺的任務。使用它來執行預加載,維護或是其它不需用戶交互和對時間不敏感的任務。
摘自:iOS用YYLabel中方法實現異步畫圖ChartView
2017-03-11
一、簡易音樂播放器(仿網易云音樂)
繞Z軸無限旋轉動畫代碼:
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.duration = duration;
rotationAnimation.repeatCount = INFINITY;
rotationAnimation.toValue = @(M_PI * 2);
[self.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
繼續旋轉和暫停旋轉代碼:
//繼續旋轉
CFTimeInterval pausedTime = [self.layer timeOffset];
self.layer.speed = 1.0;
self.layer.timeOffset = 0.0;
self.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.layer.beginTime = timeSincePause;
//暫停旋轉
CFTimeInterval currTimeoffset = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = currTimeoffset;
- 暫停就是將layer的速度設置為0,將timeOffset設置為暫停時動畫進行的時間。
- 繼續動畫就是將layer的速度設置為1,獲取暫停動畫時設置的timeOffset和動畫的運行時間,計算并設置動畫的beginTime。
二、CABasicAnimation使用總結
防止動畫結束后回到初始狀態
transformAnima.removedOnCompletion = NO;
transformAnima.fillMode = kCAFillModeForwards;
解釋:為什么動畫結束后返回原狀態?
首先我們需要搞明白一點的是,layer動畫運行的過程是怎樣的?其實在我們給一個視圖添加layer動畫時,真正移動并不是我們的視圖本身,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動,原始layer隱藏,動畫結束時,presentation layer從屏幕上移除,原始layer顯示。這就解釋了為什么我們的視圖在動畫結束后又回到了原來的狀態,因為它根本就沒動過。
這個同樣也可以解釋為什么在動畫移動過程中,我們為何不能對其進行任何操作。
所以在我們完成layer動畫之后,最好將我們的layer屬性設置為我們最終狀態的屬性,然后將presentation layer 移除掉。
2017-03-12
一、UISlider使用總結
1.UISlider默認track的高度是2,怎么修改其高度?
重寫其- (CGRect)trackRectForBounds:(CGRect)bounds
方法
- (CGRect)trackRectForBounds:(CGRect)bounds
{
return CGRectMake(0, (bounds.size.height - 2) * 0.5, bounds.size.width, 2);
}
2.UISlider默認的thumb的中心點不能滑動到UISlider的起點和終點,怎么做到像網易云音樂那樣可以滑動到起點和終點?
重寫其- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value
方法:
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value
{
CGRect frame = [super thumbRectForBounds:bounds trackRect:rect value:value];
CGFloat progress = value / (self.maximumValue - self.minimumValue);
CGFloat pastX = progress * self.frame.size.width;
frame.origin.x = pastX - frame.size.width * 0.5;
return frame;
}
3.如果像網易云音樂那樣拖拽UISlider的時候只更新時間,不播放對應時間點的音頻。而在拖拽結束后才播放該時間點處的音頻?
綁定UISlider的UIControlEventTouchUpInside|UIControlEventTouchUpOutside事件可監聽拖拽結束。
sliderValueChanged:
方法處更新時間。sliderTouchUp:
方法處開始播放。
[_slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[_slider addTarget:self action:@selector(sliderTouchUp:) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside];