IOS多線程

http://www.jianshu.com/p/0b0d9b1f1f19

?注冊
100作者?伯恩的遺產?2015.07.29 00:37*
寫了35249字,被2296人關注,獲得了1668個喜歡

關于iOS多線程,你看我就夠了

字數8596?閱讀92152?評論153?

在這篇文章中,我將為你整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案例,在實際使用中感受它們的區別。還有一點需要說明的是,這篇文章將會使用?Swift?和?Objective-c?兩種語言講解,雙語幼兒園。OK,let's begin!

概述

這篇文章中,我不會說多線程是什么、線程和進程的區別、多線程有什么用,當然我也不會說什么是串行、什么是并行等問題,這些我們應該都知道的。

在 iOS 中其實目前有?4?套多線程方案,他們分別是:

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

所以接下來,我會一一講解這些方案的使用方法和一些案例。在將這些內容的時候,我也會順帶說一些多線程周邊產品。比如:?線程同步、?延時執行、?單例模式?等等。

Pthreads

其實這個方案不用說的,只是拿來充個數,為了讓大家了解一下就好了。百度百科里是這么說的:

POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了創建和操縱線程的一整套API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統的線程。

簡單地說,這是一套在很多操作系統上都通用的多線程API,所以移植性很強(然并卵),當然在 iOS 中也是可以的。不過這是基于?c語言?的框架,使用起來這酸爽!感受一下:

OBJECTIVE-C

當然第一步要包含頭文件

#import <pthread.h>?

然后創建線程,并執行任務

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {pthread_t thread;//創建一個線程并自動執行pthread_create(&thread, NULL, start, NULL); } void *start(void *data) { NSLog(@"%@", [NSThread currentThread]); return NULL; }

打印輸出:

2015-07-27 23:57:21.689 testThread[10616:2644653] <NSThread: 0x7fbb48d33690>{number = 2, name = (null)}

看代碼就會發現他需要?c語言函數,這是比較蛋疼的,更蛋疼的是你需要手動處理線程的各個狀態的轉換即管理生命周期,比如,這段代碼雖然創建了一個線程,但并沒有銷毀。

SWIFT

很遺憾,在我目前的?swift1.2?中無法執行這套方法,原因是這個函數需要傳入一個函數指針?CFunctionPointer<T>?類型,但是目前 swift 無法將方法轉換成此類型。聽說?swift 2.0?引入一個新特性?@convention(c), 可以完成 Swift 方法轉換成 c 語言指針的。在這里可以看到

那么,Pthreads?方案的多線程我就介紹這么多,畢竟做 iOS 開發幾乎不可能用到。但是如果你感興趣的話,或者說想要自己實現一套多線程方案,從底層開始定制,那么可以去搜一下相關資料。

NSThread

這套方案是經過蘋果封裝后的,并且完全面向對象的。所以你可以直接操控線程對象,非常直觀和方便。但是,它的生命周期還是需要我們手動管理,所以這套方案也是偶爾用用,比如?[NSThread currentThread],它可以獲取當前線程類,你就可以知道當前線程的各種屬性,用于調試十分方便。下面來看看它的一些用法。

創建并啟動

  • 先創建線程類,再啟動

    OBJECTIVE-C
      // 創建NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; // 啟動 [thread start];
    SWIFT
      //創建let thread = NSThread(target: self, selector: "run:", object: nil) //啟動 thread.start()
  • 創建并自動啟動

    OBJECTIVE-C
      [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    SWIFT
      NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
  • 使用 NSObject 的方法創建并自動啟動

    OBJECTIVE-C
      [self performSelectorInBackground:@selector(run:) withObject:nil];
    SWIFT

    很遺憾 too! 蘋果認為?performSelector:?不安全,所以在 Swift 去掉了這個方法。

    Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.

其他方法

除了創建啟動外,NSThread 還以很多方法,下面我列舉一些常見的方法,當然我列舉的并不完整,更多方法大家可以去類的定義里去看。

OBJECTIVE-C
//取消線程
- (void)cancel;//啟動線程
- (void)start;//判斷某個線程的狀態的屬性 @property (readonly, getter=isExecuting) BOOL executing; @property (readonly, getter=isFinished) BOOL finished; @property (readonly, getter=isCancelled) BOOL cancelled; //設置和獲取線程名字 -(void)setName:(NSString *)n; -(NSString *)name; //獲取當前線程信息 + (NSThread *)currentThread; //獲取主線程信息 + (NSThread *)mainThread; //使當前線程暫停一段時間,或者暫停到某個時刻 + (void)sleepForTimeInterval:(NSTimeInterval)time; + (void)sleepUntilDate:(NSDate *)date;
SWIFT

Swift的方法名字和OC的方法名都一樣,我就不浪費空間列舉出來了。

其實,NSThread 用起來也挺簡單的,因為它就那幾種方法。同時,我們也只有在一些非常簡單的場景才會用 NSThread, 畢竟它還不夠智能,不能優雅地處理多線程中的其他高級概念。所以接下來要說的內容才是重點。

GCD

Grand Central Dispatch,聽名字就霸氣。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創建線程、調度任務、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是?c語言,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。所以基本上大家都使用?GCD?這套方案,老少咸宜,實在是居家旅行、殺人滅口,必備良藥。不好意思,有點中二,咱們繼續。

任務和隊列

在?GCD?中,加入了兩個非常重要的概念:?任務?和?隊列

  • 任務:即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式:?同步執行?和?異步執行,他們之間的區別是?是否會創建新的線程

    同步執行只要是同步執行的任務,都會在當前線程執行,不會另開線程。

    異步執行只要是異步執行的任務,都會另開線程,在別的線程執行。

    更新
    這里說的并不準確,同步(sync)?和?異步(async)?的主要區別在于會不會阻塞當前線程,直到?Block?中的任務執行完畢!
    如果是?同步(sync)?操作,它會阻塞當前線程并等待?Block?中的任務執行完畢,然后當前線程才會繼續往下運行。
    如果是?異步(async)操作,當前線程會直接往下執行,它不會阻塞當前線程。

  • 隊列:用于存放任務。一共有兩種隊列,?串行隊列?和?并行隊列

    串行隊列?中的任務會根據隊列的定義 FIFO 的執行,一個接一個的先進先出的進行執行。?

    更新:放到串行隊列的任務,GCD 會?FIFO(先進先出)?地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。

    并行隊列?中的任務?根據同步或異步有不同的執行方式。

    更新:放到并行隊列的任務,GCD 也會?FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務都是一起執行的。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。

雖然很繞,但請看下表:

?同步執行異步執行
串行隊列當前線程,一個一個執行其他線程,一個一個執行
并行隊列當前線程,一個一個執行開很多線程,一起執行

創建隊列

  • 主隊列:這是一個特殊的?串行隊列。什么是主隊列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執行,所以一般耗時的任務都要放到別的線程執行。

      //OBJECTIVE-Cdispatch_queue_t queue = ispatch_get_main_queue();//SWIFT let queue = ispatch_get_main_queue()
  • 自己創建的隊列凡是自己創建的隊列都是?串行隊列?其中第一個參數是標識符,用于 DEBUG 的時候標識唯一的隊列,可以為空。大家可以看xcode的文檔查看參數意義。?

    更新:自己可以創建?串行隊列, 也可以創建?并行隊列。看下面的代碼(代碼已更新),它有兩個參數,第一個上面已經說了,第二個才是最重要的。
    第二個參數用來表示創建的隊列是串行的還是并行的,傳入?DISPATCH_QUEUE_SERIAL或?NULL?表示創建串行隊列。傳入?DISPATCH_QUEUE_CONCURRENT?表示創建并行隊列。

      //OBJECTIVE-C//串行隊列dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL); dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL); //并行隊列 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT); //SWIFT //串行隊列 let queue = dispatch_queue_create("tk.bourne.testQueue", nil); let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL) //并行隊列 let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
  • 全局并行隊列這應該是唯一一個并行隊列,?只要是并行任務一般都加入到這個隊列。這是系統提供的一個并發隊列。

      //OBJECTIVE-Cdispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //SWIFT let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

創建任務

  • 同步任務:?不會另開線程?改:會阻塞當前線程 (SYNC)

    OBJECTIVE-C
      dispatch_sync(<#queue#>, ^{//code hereNSLog(@"%@", [NSThread currentThread]); });
    SWIFT
      dispatch_sync(<#queue#>, { () -> Void in//code hereprintln(NSThread.currentThread()) })
  • 異步任務:會另開線程?改:不會阻塞當前線程 (ASYNC)

    OBJECTIVE-C
      dispatch_async(<#queue#>, ^{//code hereNSLog(@"%@", [NSThread currentThread]); });
    SWIFT
      dispatch_async(<#queue#>, { () -> Void in//code hereprintln(NSThread.currentThread()) })

更新
為了更好的理解同步和異步,和各種隊列的使用,下面看兩個示例:

示例一:
以下代碼在主線程調用,結果是什么??

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in NSLog("sync - %@", NSThread.currentThread()) }) NSLog("之后 - %@", NSThread.currentThread())

答案:
只會打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main}?,然后主線程就卡死了,你可以在界面上放一個按鈕,你就會發現點不了了。
解釋:
同步任務會阻塞當前線程,然后把 Block 中的任務放到指定的隊列中執行,只有等到 Block 中的任務完成后才會讓當前線程繼續往下運行。
那么這里的步驟就是:打印完第一句后,dispatch_sync?立即阻塞當前的主線程,然后把 Block 中的任務放到?main_queue?中,可是?main_queue?中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了,所以 Block 中的任務就不能完成,它不完成,dispatch_sync?就會一直阻塞主線程,這就是死鎖現象。導致主線程一直卡死。

示例二:
以下代碼會產生什么結果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)NSLog("之前 - %@", NSThread.currentThread()) dispatch_async(queue, { () -> Void in NSLog("sync之前 - %@", NSThread.currentThread()) dispatch_sync(queue, { () -> Void in NSLog("sync - %@", NSThread.currentThread()) }) NSLog("sync之后 - %@", NSThread.currentThread()) }) NSLog("之后 - %@", NSThread.currentThread())

答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
很明顯?sync - %@?和?sync之后 - %@?沒有被打印出來!這是為什么呢?我們再來分析一下:

分析:
我們按執行順序一步步來哦:

  1. 使用?DISPATCH_QUEUE_SERIAL?這個參數,創建了一個?串行隊列
  2. 打印出?之前 - %@?這句。
  3. dispatch_async?異步執行,所以當前線程不會被阻塞,于是有了兩條線程,一條當前線程繼續往下打印出?之后 - %@這句, 另一臺執行 Block 中的內容打印?sync之前 - %@?這句。因為這兩條是并行的,所以打印的先后順序無所謂。
  4. 注意,高潮來了。現在的情況和上一個例子一樣了。dispatch_sync同步執行,于是它所在的線程會被阻塞,一直等到?sync?里的任務執行完才會繼續往下。于是?sync就高興的把自己 Block 中的任務放到?queue?中,可誰想?queue?是一個串行隊列,一次執行一個任務,所以?sync?的 Block 必須等到前一個任務執行完畢,可萬萬沒想到的是?queue?正在執行的任務就是被?sync?阻塞了的那個。于是又發生了死鎖。所以?sync?所在的線程被卡死了。剩下的兩句代碼自然不會打印。?

隊列組

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務都執行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。

OBJECTIVE-C
//1.創建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//3.多次使用隊列組的方法執行任務, 只有異步方法 //3.1.執行3次循環 dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@"group-01 - %@", [NSThread currentThread]); } }); //3.2.主隊列執行8次循環 dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 8; i++) { NSLog(@"group-02 - %@", [NSThread currentThread]); } }); //3.3.執行5次循環 dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 5; i++) { NSLog(@"group-03 - %@", [NSThread currentThread]); } }); //4.都完成后會自動通知 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 - %@", [NSThread currentThread]); });
SWIFT
//1.創建隊列組
let group = dispatch_group_create()
//2.創建隊列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //3.多次使用隊列組的方法執行任務, 只有異步方法 //3.1.執行3次循環 dispatch_group_async(group, queue) { () -> Void in for _ in 0..<3 { NSLog("group-01 - %@", NSThread.currentThread()) } } //3.2.主隊列執行8次循環 dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in for _ in 0..<8 { NSLog("group-02 - %@", NSThread.currentThread()) } } //3.3.執行5次循環 dispatch_group_async(group, queue) { () -> Void in for _ in 0..<5 { NSLog("group-03 - %@", NSThread.currentThread()) } } //4.都完成后會自動通知 dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in NSLog("完成 - %@", NSThread.currentThread()) }

打印結果

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] 完成 - <NSThread: 0x7f977240ba60>{number = 1, name = main}


這些就是 GCD 的基本功能,但是它的能力遠不止這些,等講完 NSOperation 后,我們再來看看它的一些其他方面用途。而且,只要你想象力夠豐富,你可以組合出更好的用法。?

更新:關于GCD,還有兩個需要說的:

  • func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    這個方法重點是你傳入的?queue,當你傳入的?queue?是通過?DISPATCH_QUEUE_CONCURRENT?參數自己創建的?queue?時,這個方法會阻塞這個?queue注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個?queue?中排在它前面的任務都執行完成后才會開始執行自己,自己執行完畢后,再會取消阻塞,使這個?queue?中排在它后面的任務繼續執行。
    如果你傳入的是其他的?queue, 那么它就和?dispatch_async?一樣了。?

  • func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    這個方法的使用和上一個一樣,傳入?自定義的并發隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞?queue,不同的是 這個方法還會?阻塞當前線程
    如果你傳入的是其他的?queue, 那么它就和?dispatch_sync?一樣了。

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。 大家可以看到?NSOperation 和 NSOperationQueue?分別對應 GCD 的?任務 和 隊列?。操作步驟也很好理解:

  1. 將要執行的任務封裝到一個?NSOperation?對象中。
  2. 將此任務添加到一個?NSOperationQueue?對象中。

然后系統就會自動在執行任務。至于同步還是異步、串行還是并行請繼續往下看:

添加任務

值得說明的是,NSOperation?只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation?和?NSBlockOperation?。創建一個 Operation 后,需要調用?start?方法來啟動任務,它會?默認在當前隊列同步執行。當然你也可以在中途取消一個任務,只需要調用其?cancel?方法即可。

  • NSInvocationOperation : 需要傳入一個方法名。

    OBJECTIVE-C
      //1.創建NSInvocationOperation對象NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; //2.開始執行 [operation start];
    SWIFT

    在 Swift 構建的和諧社會里,是容不下?NSInvocationOperation?這種不是類型安全的敗類的。蘋果如是說。這里有相關解釋

  • NSBlockOperation

    OBJECTIVE-C
      //1.創建NSBlockOperation對象NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"%@", [NSThread currentThread]); }]; //2.開始任務 [operation start];
    SWIFT
      //1.創建NSBlockOperation對象let operation = NSBlockOperation { () -> Void in println(NSThread.currentThread()) } //2.開始任務 operation.start()

    之前說過這樣的任務,默認會在當前線程執行。但是?NSBlockOperation?還有一個方法:addExecutionBlock:?,通過這個方法可以給 Operation 添加多個執行 Block。這樣 Operation 中的任務?會并發執行,它會?在主線程和其它的多個線程?執行這些任務,注意下面的打印結果:

    OBJECTIVE-C
          //1.創建NSBlockOperation對象NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"%@", [NSThread currentThread]); }]; //添加多個Block for (NSInteger i = 0; i < 5; i++) { [operation addExecutionBlock:^{ NSLog(@"第%ld次:%@", i, [NSThread currentThread]); }]; } //2.開始任務 [operation start];
    SWIFT
            //1.創建NSBlockOperation對象let operation = NSBlockOperation { () -> Void in NSLog("%@", NSThread.currentThread()) } //2.添加多個Block for i in 0..<5 { operation.addExecutionBlock { () -> Void in NSLog("第%ld次 - %@", i, NSThread.currentThread()) } } //2.開始任務 operation.start()
    打印輸出

    2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

    2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

    2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}

    2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}

    2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

    2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

    ?

    NOTEaddExecutionBlock?方法必須在?start()?方法之前執行,否則就會報錯:

    ‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'

    ?

    NOTE:大家可能發現了一個問題,為什么我在 Swift 里打印輸出使用?NSLog()?而不是?println()?呢?原因是使用?print() / println()?輸出的話,它會簡單地使用?流(stream)?的概念,學過 C++ 的都知道。它會把需要輸出的每個字符一個一個的輸出到控制臺。普通使用并沒有問題,可是當多線程同步輸出的時候問題就來了,由于很多?println()?同時打印,就會導致控制臺上的字符混亂的堆在一起,而NSLog() 就沒有這個問題。到底是什么樣子的呢?你可以把上面?NSLog()?改為?println()?,然后一試便知。?更多 NSLog() 與 println() 的區別看這里

  • 自定義Operation

    除了上面的兩種 Operation 以外,我們還可以自定義 Operation。自定義 Operation 需要繼承?NSOperation?類,并實現其?main()?方法,因為在調用?start()方法的時候,內部會調用?main()?方法完成相關邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實現什么功能都可以寫在里面。除此之外,你還需要實現?cancel()?在內的各種方法。所以這個功能提供給高級玩家,我在這里就不說了,等我需要用到時在研究它,到時候可能會再做更新。

創建隊列

看過上面的內容就知道,我們可以調用一個?NSOperation?對象的?start()?方法來啟動這個任務,但是這樣做他們默認是?同步執行?的。就算是?addExecutionBlock?方法,也會在?當前線程和其他線程?中執行,也就是說還是會占用當前線程。這是就要用到隊列?NSOperationQueue?了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調用任務的 start() 方法

  • 主隊列

    細心的同學就會發現,每套多線程方案都會有一個主線程(當然啦,說的是iOS中,像 pthread 這種多系統的方案并沒有,因為?UI線程?理論需要每種操作系統自己定制)。這是一個特殊的線程,必須串行。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理。

    //OBJECTIVE-C
    NSOperationQueue *queue = [NSOperationQueue mainQueue];//SWIFT
    let queue = NSOperationQueue.mainQueue()
  • 其他隊列

    因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。那么通過初始化產生的隊列就是其他隊列了,因為只有這兩種隊列,除了主隊列,其他隊列就不需要名字了。?

    注意:其他隊列的任務會在其他線程并行執行。

    OBJECTIVE-C
    //1.創建一個其他隊列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];//2.創建NSBlockOperation對象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); }]; //3.添加多個Block for (NSInteger i = 0; i < 5; i++) { [operation addExecutionBlock:^{ NSLog(@"第%ld次:%@", i, [NSThread currentThread]); }]; } //4.隊列添加任務 [queue addOperation:operation];
    SWIFT
    //1.創建其他隊列
    let queue = NSOperationQueue()//2.創建NSBlockOperation對象
    let operation = NSBlockOperation { () -> Void in NSLog("%@", NSThread.currentThread()) } //3.添加多個Block for i in 0..<5 { operation.addExecutionBlock { () -> Void in NSLog("第%ld次 - %@", i, NSThread.currentThread()) } } //4.隊列添加任務 queue.addOperation(operation)
    打印輸出

    2015-07-28 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

    2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

    2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}

    2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}

    2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

    2015-07-28 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

OK, 這時應該發問了,大家將?NSOperationQueue?與?GCD的隊列?相比較就會發現,這里沒有串行隊列,那如果我想要10個任務在其他線程串行的執行怎么辦??

這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue有一個參數?maxConcurrentOperationCount?最大并發數,用來設置最多可以讓多少個任務同時執行。當你把它設置為?1?的時候,他不就是串行了嘛!

NSOperationQueue?還有一個添加任務的方法,- (void)addOperationWithBlock:(void (^)(void))block;?,這是不是和 GCD 差不多?這樣就可以添加一個任務到隊列中了,十分方便。

NSOperation?有一個非常實用的功能,那就是添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了:?

OBJECTIVE-C
//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"下載圖片 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //2.任務二:打水印 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"打水印 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //3.任務三:上傳圖片 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"上傳圖片 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //4.設置依賴 [operation2 addDependency:operation1]; //任務二依賴任務一 [operation3 addDependency:operation2]; //任務三依賴任務二 //5.創建隊列并加入任務 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
//1.任務一:下載圖片
let operation1 = NSBlockOperation { () -> Void in NSLog("下載圖片 - %@", NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //2.任務二:打水印 let operation2 = NSBlockOperation { () -> Void in NSLog("打水印 - %@", NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //3.任務三:上傳圖片 let operation3 = NSBlockOperation { () -> Void in NSLog("上傳圖片 - %@", NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //4.設置依賴 operation2.addDependency(operation1) //任務二依賴任務一 operation3.addDependency(operation2) //任務三依賴任務二 //5.創建隊列并加入任務 let queue = NSOperationQueue() queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)
打印結果

2015-07-28 21:24:28.622 test[19392:4637517] 下載圖片 - <NSThread: 0x7fc10ad4d970>{number = 2, name = (null)}

2015-07-28 21:24:29.622 test[19392:4637515] 打水印 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

2015-07-28 21:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

  • 注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
  • 可以使用?removeDependency?來解除依賴關系。
  • 可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的,和隊列沒關系。

其他方法

以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

  • NSOperation

    BOOL executing; //判斷任務是否正在執行

    BOOL finished; //判斷任務是否完成

    void (^completionBlock)(void); //用來設置完成后需要執行的操作

    - (void)cancel; //取消任務

    - (void)waitUntilFinished; //阻塞當前線程直到此任務執行完畢

  • NSOperationQueue

    NSUInteger operationCount; //獲取隊列的任務數

    - (void)cancelAllOperations; //取消隊列中所有的任務

    - (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執行完畢

    [queue setSuspended:YES]; // 暫停queue?

    [queue setSuspended:NO]; // 繼續queue?

好啦,到這里差不多就講完了。當然,我講的并不完整,可能有一些知識我并沒有講到,但作為常用方法,這些已經足夠了。不過我在這里只是告訴你了一些方法的功能,只是怎么把他們用到合適的地方,就需要多多實踐了。下面我會說一些關于多線程的案例,是大家更加什么地了解。

其他用法

在這部分,我會說一些和多線程知識相關的案例,可能有些很簡單,大家早都知道的,不過因為這篇文章講的是多線程嘛,所以應該盡可能的全面嘛。還有就是,我會盡可能的使用多種方法實現,讓大家看看其中的區別。

線程同步

所謂線程同步就是為了防止多個線程搶奪同一個資源造成的數據安全問題,所采取的一種措施。當然也有很多實現方法,請往下看:

  • 互斥鎖?:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。

    OBJECTIVE-C
    @synchronized(self) {//需要執行的代碼塊
    }
    SWIFT
    objc_sync_enter(self)
    //需要執行的代碼塊
    objc_sync_exit(self)
  • 同步執行?:我們可以使用多線程的知識,把多個線程都要執行此段代碼添加到同一個串行隊列,這樣就實現了線程同步的概念。當然這里可以使用?GCD?和?NSOperation?兩種方案,我都寫出來。

    OBJECTIVE-C
  //GCD//需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中dispatch_sync(queue, ^{NSInteger ticket = lastTicket;[NSThread sleepForTimeInterval:0.1]; NSLog(@"%ld - %@",ticket, [NSThread currentThread]); ticket -= 1; lastTicket = ticket; }); //NSOperation & NSOperationQueue //重點:1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中 // 2. 設置 queue 的 maxConcurrentOperationCount 為 1 // 3. 如果后續操作需要Block中的結果,就需要調用每個操作的waitUntilFinished,阻塞當前線程,一直等到當前操作完成,才允許執行后面的。waitUntilFinished 要在添加到隊列之后! NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSInteger ticket = lastTicket; [NSThread sleepForTimeInterval:1]; NSLog(@"%ld - %@",ticket, [NSThread currentThread]); ticket -= 1; lastTicket = ticket; }]; [queue addOperation:operation]; [operation waitUntilFinished]; //后續要做的事
SWIFT

這里的 swift 代碼,我就不寫了,因為每句都一樣,只是語法不同而已,照著 OC 的代碼就能寫出 Swift 的。這篇文章已經老長老長了,我就不浪費篇幅了,又不是高中寫作文。

延遲執行

所謂延遲執行就是延時一段時間再執行某段代碼。下面說一些常用方法。

  • perform

    OBJECTIVE-C
      // 3秒后自動調用self的run:方法,并且傳遞參數:@"abc"[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
    SWIFT
    之前就已經說過,Swift 里去掉了這個方法。
  • GCD

    可以使用 GCD 中的?dispatch_after?方法,OC 和 Swift 都可以使用,這里只寫 OC 的,Swift 的是一樣的。

    OBJECTIVE-C
    // 創建隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 設置延時,單位秒 double delay = 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{ // 3秒后需要執行的任務 });
  • NSTimer

    NSTimer 是iOS中的一個計時器類,除了延遲執行還有很多用法,不過這里直說延遲執行的用法。同樣只寫 OC 版的,Swift 也是相同的。

    OBJECTIVE-C
    [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

單例模式

至于什么是單例模式,我也不多說,我只說說一般怎么實現。在 Objective-C 中,實現單例的方法已經很具體了,雖然有別的方法,但是一般都是用一個標準的方法了,下面來看看。

OBJECTIVE-C
@interface Tool : NSObject <NSCopying> + (instancetype)sharedTool; @end @implementation Tool static id _instance; + (instancetype)sharedTool { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[Tool alloc] init]; }); return _instance; } @end

這里之所以將單例模式,是因為其中用到了 GCD 的?dispatch_once?方法。下面看 Swift 中的單例模式,在Swift中單例模式非常簡單!想知道怎么從 OC 那么復雜的方法變成下面的寫法的,請看這里

SWIFT
class Tool: NSObject {static let sharedTool = Tool() // 私有化構造方法,阻止其他對象使用這個類的默認的'()'構造方法 private override init() {} }

從其他線程回到主線程的方法

我們都知道在其他線程操作完成后必須到主線程更新UI。所以,介紹完所有的多線程方案后,我們來看看有哪些方法可以回到主線程。

  • NSThread

    //Objective-C
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO]; //Swift //swift 取消了 performSelector 方法。
  • GCD

    //Objective-C
    dispatch_async(dispatch_get_main_queue(), ^{});//Swift
    dispatch_async(dispatch_get_main_queue(), { () -> Void in })
  • NSOperationQueue

    //Objective-C
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{}];//Swift
    NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in }

總結

好的吧,總算寫完了,純手敲6k多字,感動死我了。花了兩天,時間跨度有點大,所以可能有些地方上段不接下段或者有的地方不完整,如果你看著比較費力或者有什么地方有問題,都可以在評論區告訴我,我會及時修改的。當然啦,多線程的東西也不止這些,題目也就只是個題目,不要當真。想要了解更多的東西,還得自己去網上挖掘相關資料。多看看官方文檔。實在是編不下去了,大家好好看~。對了,看我寫的這么賣力,不打賞的話得點個喜歡也是極好的。

更新:第一次放出來的時候,有很多地方有錯誤,很感謝有朋友提出來了。如果你看到有錯誤的地方,一定記得指出來,這樣對大家都有幫助。還有一點對初學者來說,遇到不懂的方法,最好的辦法就是查看官方文檔,那里是最準確的,就算有幾個單詞不認識,查一下就好了,不會影響對整體的理解。
我看到有網站轉載了我的文章,但轉載的可能存在問題,而我只能在簡書上更新,所以如果要看?完整版本?還是到簡書來看吧:這里是地址。

?推薦拓展閱讀

您覺得這篇文章對你有幫助嗎?看我這么用心,鼓勵一下唄~

¥ 打賞支持?
153條評論?(?按時間正序·?按時間倒序·?按喜歡排序?)添加新評論
100

從今以后

2 樓 ·?2015.07.29 02:37

這是我的一些看法:

同步派發(sync)會盡可能地在當前線程派發任務.但如果在其他隊列往主隊列同步派發,任務會在主線程執行.
異步派發(async)也不絕對會另開線程.例如在主線程異步派發到主線程,派發依舊是異步的,任務也會在主線程執行.
我感覺同步異步的重要區別在于派發方法是否需要等待 block 完成后才能返回.

無論串行還是并發隊列,任務啟動順序都是按照 FIFO 的,只是并發隊列允許同一時間有多個任務執行都在執行.

創建隊列

也可以自己創建并發隊列.
dispatch_queue_create("我是私有串行隊列", DISPATCH_QUEUE_SERIAL);
dispatch_queue_create("我是私有并發隊列", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(dispatch_get_main_queue()) {
// Swift 中用尾閉包寫法感覺更美觀.
}

關于同步,還有這么個東西:
dispatch_barrier_async(privateConcurrentQueue, ^{
// 寫入操作會確保隊列前面的操作執行完畢才開始,并會阻塞隊列中后來的操作.
});

dispatch_sync(privateConcurrentQueue, ^{
// 只要沒有寫入操作,多個讀取操作是相對并行的.
});

伯恩的遺產:?@從今以后?哇,看了你說的,感覺對 gcd 理解更深刻了。好多東西寫的時候都沒注意,剛才看了下文檔才發現果然是的。非常感謝你的分享。?:beers:

南梔傾寒: 可以將樓上的補充 到文章去 因為同步異步并不是覺得開線程的問題 還有barrier這個操作叫做只寫鎖 其他同步或者異步都是并發寫操作的 如果是只讀的用sync 和async 就行 如果需要并發寫操作 就要注意barrier了

伯恩的遺產:?@南梔傾寒?白天有點事兒,我剛剛更新了。??

還有?9?條評論,?展開查看?添加新回復
100

c1ear

3 樓 ·?2015.07.29 09:16

gcd缺點:執行任務后無法cancel~~~

HaodanHuang:?@項羽Vip?其實可以cancel, 但沒有內建方法,只需要自己用一個iVariable在block被執行前判斷即可。

Raybon_lee:?@HaodanHuang?這個是什么方法,可否告知一下呢

qYong_:?@HaodanHuang?能具體說一下么,求告知

還有?2?條評論,?展開查看?添加新回復
100

Kimball

7 樓 ·?2015.07.29 10:55

對新手來說挺受用的?:smile:

100

Kent_Zhang

8 樓 ·?2015.07.29 11:13

線程互斥還可以介紹下Unix傳統手段嘛,文件鎖,信號量,線程mutex

13

dreday

10 樓 ·?2015.07.29 13:32

頂頂頂,好文必須頂

13

dreday

11 樓 ·?2015.07.29 13:32

@項羽Vip?贊同

100

Mars飄殤

12 樓 ·?2015.07.29 13:43

?

100

JanzTam

14 樓 ·?2015.07.29 22:09

評論的都是大神

100

龐大不小

15 樓 ·?2015.07.29 23:50

收益很多。讓我再重新復習了一下多線程,

100

isaced

17 樓 ·?2015.07.30 15:11

let't begin! 什么鬼?

伯恩的遺產:?@isaced? :joy:?不要在意這些細節

?添加新回復
100

像羽毛那樣輕

20 樓 ·?2015.07.30 17:05

有這么深的領悟,學成必成大神!

100

龍哥盟飛龍

22 樓 ·?2015.07.31 11:00

糾正。線程在游離態下,結束后自動銷毀。pthread庫中調用detach(),win32api用調用CloseHandle()。詳見《csapp》。

胖子程:?@龍哥盟飛龍?樓主為什么不回復你????:sweat:

?添加新回復
100

CRUFT

26 樓 ·?2015.08.02 22:11

“更新:放到串行隊列的任務,GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。”
是不是應該是“放到并行隊列的任務”?

“可以 main_queue 中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了”
“可以”是否應該是“可是”?

伯恩的遺產:?@d546e5af7f42?嗯嗯,已改正,你看的很仔細呀~

?添加新回復
100

685d7c56abb0

27 樓 ·?2015.08.03 09:57

...

11

馬來

28 樓 ·?2015.08.03 10:12

對于dispatch source 有用過嗎

伯恩的遺產:?@馬來?這個還沒用過

?添加新回復

加載更多?

登錄后發表評論

被以下專題收入,發現更多相似內容:
  • 180
    程序員

    如果你是程序員,或者有一顆喜歡寫程序的心,喜歡分享技術干貨、項目經驗、程序員日常囧事等等,歡迎投稿《程序員》專題。 專題主編:小...

    21820篇文章?· 152343人關注

  • 180
    首頁投稿

    玩轉簡書的第一步,從這個專題開始。 想上首頁熱門榜么?好內容想被更多人看到么?來投稿吧!如果被拒也不要灰心哦~入選文章會進一個隊...

    92920篇文章?· 124794人關注

  • 180
    iOS開發技巧

    【簡介】 專題內容主要包括OC、swift等涉及到iOS開發進階的內容。 swift可以關注下我的另一個專題: swift開發...

    1046篇文章?· 19086人關注

轉載于:https://www.cnblogs.com/lihaibo-Leao/p/5806808.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286499.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286499.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286499.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android之提示Failed to load WebView provider: No WebView installed

1 問題 Fatal Exception: android.util.AndroidRuntimeException: android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installedat android.webkit.WebViewFactory.getProviderClass(WebViewFactory.java:435)at a…

地理(GIS)教學神器:氣象地球生成器

地理教學中&#xff0c;不管是高中還是初中&#xff0c;都會涉及到大氣運動的相關教學&#xff0c;并且&#xff0c;高中階段的大氣運動知識對很多學生來說相對比較復雜&#xff0c;如&#xff1a; &#xff08;三圈環流&#xff09; &#xff08;青藏高原對西風帶的影響&#…

使用 Yarp 做網關

資料GitHub: https://github.com/microsoft/reverse-proxyYARP 文檔&#xff1a;https://microsoft.github.io/reverse-proxy/articles/getting-started.html主動和被動健康檢查 &#xff1a; https://microsoft.github.io/reverse-proxy/articles/dests-health-checks.html#ac…

Android之OkDownload里面的OKHttp提示java.lang.IllegalArgumentException: Invalid URL port: “image“

1 、問題 release版本線上奔潰如下 Fatal Exception: java.lang.IllegalArgumentException: Invalid URL port: "image"at okhttp3.t$a.a(HttpUrl.kt:63)at okhttp3.t$b.b(HttpUrl.kt:8)at okhttp3.y$a.b(Request.kt:5)at com.liulishuo.okdownload.j.e.b.<init…

【iVX 初級工程師培訓教程 10篇文拿證】05 畫布及飛機大戰游戲制作

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

【WEB API項目實戰干貨系列】- API登錄與身份驗證(三)

這篇我們主要來介紹我們如何在API項目中完成API的登錄及身份認證. 所以這篇會分為兩部分, 登錄API&#xff0c; API身份驗證. 這一篇的主要原理是&#xff1a; API會提供一個單獨的登錄API, 通過用戶名&#xff0c;密碼來產生一個SessionKey, SessionKey具有過期時間的特點, 系…

mysql數據庫建立的數據庫在哪個文件夾?

為什么80%的碼農都做不了架構師&#xff1f;>>> 一般在安裝目錄下的data文件夾下&#xff0c;或者在C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1\data&#xff08;你的可能是C:\Documents and Settings\All Users\Application D…

python 學習筆記01

python學習過程遇到一些問題記錄&#xff1a; 1、 IndentationError:expected an indented block錯誤的解決辦法 一句話 有冒號的下一行往往要縮進&#xff0c;該縮進就縮進 參考資料&#xff1a;http://blog.csdn.net/hongkangwl/article/details/16344749 2、17個新手常見Pyt…

ArcGIS實驗教程——實驗二十四:人口密度制圖

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據)》 一、實驗分析 人口密度是指單位土地面積上居住的人口數,通常以每平方千米或每公頃內的常住人口為單位計算。人口密度同資源、經濟密切結合,因此,科學準確地分析人口密度的分布情況,對合理制定…

Navicat 遠程連接ubuntu出現的問題

2003-Cantt connect to Mysql server to xxxxxxx 解決&#xff1a; vim /etc/mysql/my.cnf 修改bind-address 0.0.0.0 然后重啟mysql&#xff1a; 這時進入mysql可能會報錯&#xff1a; ERROR 2002 (HY000): Cant connect to local MySQL server through socket /v…

WPF效果第一百八十八篇之再玩Expander

大端午節的在屋里吹著空調擼著代碼真是酸爽;閑話也不多扯,直接看今天要分享的效果:1、關于簡單的布局設計:2、前臺先來個死布局,回頭ListBox改模板:<Expander ExpandDirection"Left" Header"控制卡" VerticalAlignment"Bottom" HorizontalAli…

Android之實現長按Webview頁面文字自定義復制、全選、分享、搜索、翻譯功能(支持多語言,博文也有Demo下載地址)

1 需求和效果爆照 瀏覽器app封裝了Webview,然后實現實現長按Webview頁面文字自定義復制、全選、分享、搜索、翻譯功能(支持多語言),都在自己的瀏覽器app里面進行搜索和翻譯,不跳到系統瀏覽器里面去 效果爆照如下,oppo手機效果如下 華為手機效果如下 2 Demo下載地址 De…

中國西北地區專題地圖合集(高清)

1. 西北地區概況圖 2. 西北地區植被類型分布圖 3. NDVI變化趨勢圖 4. 氣候與NDVI的相關性

Apache、tomcat、Nginx常用配置合集

配置文件地址&#xff1a; Apache&#xff1a; /etc/httpd/conf/httpd.conf tomcat&#xff1a; /usr/local/tomcat/conf/server.xml Nginx &#xff1a; /usr/local/nginx/conf/nginx.conf 開機啟動文件&#xff1a;/etc/rc.d/rc.local 啟動方式&#xff1a; Apache&#xff…

使用putty連接linux

使用putty連接linux 快照的使用 &#xff0c;做快照相當于做備份&#xff0c;比如配置好IP&#xff0c;快照一下&#xff0c;下次就可以在回到這里&#xff01; putty下載 最好去官網下載 下載putty.zip如圖所示 如何使用putty 如圖設置好IP然后 save 保存 如…

【WEB API項目實戰干貨系列】- API訪問客戶端(WebApiClient適用于MVC/WebForms/WinForm)(四)

目前最新的代碼已經通過Sqlite NHibernate Autofac滿足了我們基本的Demo需求. 按照既定的要求&#xff0c;我們的API會提供給眾多的客戶端使用, 這些客戶端可以是各種Web站點, APP, 或者是WinForm, WPF, Silverlight等諸如此類的應用&#xff0c;將來還有可能是各種Iot等物聯…

基于 Roslyn 實現代碼動態編譯

基于 Roslyn 實現代碼動態編譯Intro之前做的一個數據庫小工具可以支持根據 Model 代碼文件生成創建表的 sql 語句&#xff0c;原來是基于 CodeDom 實現的&#xff0c;后來改成使用基于 Roslyn 去做了。實現的原理在于編譯選擇的Model 文件生成一個程序集&#xff0c;再從這個程…

【GIS風暴】GIS拓撲關系原理詳解

目 錄 1. 拓撲關系的概念2. 拓撲元素3. 拓撲關系4. 拓撲關系的意義5. 拓撲在ArcGIS中實現1. 拓撲關系的概念 地圖上的拓撲關系是指圖形在保持連續狀態下的變形(縮放、旋轉和拉伸等),但圖形關系不變的性質。 2. 拓撲元素 對二維而言,矢量數據可抽象為點(節點)、線(鏈、…

Android之簡單的文件夾選擇器實現

1、效果爆照 2、代碼實現 前提需要保證app有讀寫權限 activity_select_folder.xml文件如下 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layo…

【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…