多線程:GCD

来源:http://www.cnblogs.com/lurenq/archive/2017/06/13/7000297.html
-Advertisement-
Play Games

多線程是程式開發中非常基礎的一個概念,大家在開發過程中應該或多或少用過相關的東西。同時這恰恰又是一個比較棘手的概念,一切跟多線程掛鉤的東西都會變得複雜。如果使用過程中對多線程不夠熟悉,很可能會埋下一些難以預料的坑。 iOS中的多線程技術主要有NSThread, GCD和NSOperation。他們的 ...


多線程是程式開發中非常基礎的一個概念,大家在開發過程中應該或多或少用過相關的東西。同時這恰恰又是一個比較棘手的概念,一切跟多線程掛鉤的東西都會變得複雜。如果使用過程中對多線程不夠熟悉,很可能會埋下一些難以預料的坑。

iOS中的多線程技術主要有NSThread, GCD和NSOperation。他們的封裝層次依次遞增,其中:

  • NSThread封裝性最差,最偏向於底層,主要基於thread使用

  • GCD是基於C的API,直接使用比較方便,主要基於task使用

  • NSOperation是基於GCD封裝的NSObject對象,對於複雜的多線程項目使用比較方便,主要基於隊列使用

上篇文章介紹了NSThread的用法,NSThread已經屬於古董級別的東西了,欣賞一下可以,真正使用就不要麻煩他了。GCD是多線程中的新貴,比起NSThread更加強大,也更容易使用。由於GCD的東西比較多,我會分好幾篇文章介紹,這篇文章主要介紹GCD中的queue相關知識。

dispatch_queue_t

使用GCD之後,你可以不用再浪費精力去關註線程,GCD會幫你管理好一切。你只需要想清楚任務的執行方法(同步還是非同步)和隊列的運行方式(串列還是並行)即可。

任務是一個比較抽象的概念,表示一段用來執行的代碼,他對應到代碼里就是一個block或者一個函數。

隊列分為串列隊列和並行隊列:

  • 串列隊列一次只能執行一個任務。只有一個任務執行完成之後,下一個任務才能執行,主線程就是一個串列的隊列。

687474703a2f2f63646e342e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f53657269616c2d51756575652d343830783237322e706e67.png

  • 並行隊列可以同時執行多個任務,系統會維護一個線程池來保證並行隊列的執行。線程池會根據當前任務量自行安排線程的數量,以確保任務儘快執行。

687474703a2f2f63646e332e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f436f6e63757272656e742d51756575652d343830783237322e706e67.png

隊列對應到代碼里是一個dispatch_queue_t對象:

1 dispatch_queue_t queue;

對象就有記憶體。跟普通OC對象類似,我們可以用dispatch_retain()和dispatch_release()對其進行記憶體管理,當一個任務加入到一個queue中的時候,任務會retain這個queue,直到任務執行完成才會release。

值得高興的是,iOS6之後,dispatch對象已經支持ARC,所以在ARC工程之下,我們可以不用擔心他的記憶體,想怎麼玩就怎麼玩。

要申明一個dispatch的屬性。一般情況下我們只需要用strong即可。

1 @property (nonatomic, strong) dispatch_queue_t queue;

如果你是寫一個framework,framework的使用者的SDK有可能還是古董級的iOS6之前。那麼你需要根據OS_OBJECT_USE_OBJC做一個判斷是使用strong還是assign。(一般github上的優秀第三方庫都會這麼做)

1 2 3 4 5 #if OS_OBJECT_USE_OBJC @property (nonatomic, strong) dispatch_queue_t queue; #else @property (nonatomic, assign) dispatch_queue_t queue; #endif

async

GCD中有2個非同步的API

1 2 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

他們都是將一個任務提交到queue中,提交之後立即返回,不等待任務的的執行。提交之後,系統會對queue做retain操作,任務執行完成之後,queue再被release。兩個函數實際的功能是一樣的,唯一的區別在於dispatch_async接受block作為參數,dispatch_async_f接受函數。

使用dispatch_async的時候block會被copy,在block執行完成之後block再release,由於是系統持有block,所以不用擔心迴圈引用的問題,block裡面的self不需要weak

在dispatch_async_f中,context會作為第一個參數傳給work函數。如果work不需要參數,context可以傳入NULL。work參數不能傳入NULL,否則可能發生無法預料的事兒

非同步是一個比較抽象的概念,簡單的說就是將任務加入到隊列中之後,立即返回,不需要等待任務的執行。語言的描述比較抽象,我們用代碼加深一下對概念的理解

1 2 3 4 5 6 NSLog(@"this is main queue, i want to throw a task to global queue"); dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL); dispatch_async(globalQueue, ^{     // task }); NSLog(@"this is main queue, throw task completed");

上面這段代碼,會以這樣的方式運行,紅色表示正在執行的模塊,灰色表示未執行或者已經執行完成的模塊。

QQ截圖20160225094359.png

  1. 先在main queue中執行第一個nslog

  2. dispatch_async會將block提交到globalQueue中,提交成功之後立即返回

  3. main queue執行第二個nslog

  4. 等global queue中block前面的任務執行完成之後,block被執行。

sync

與非同步相似,GCD中同步的API也是2個

1 2 void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

2個API作用相同:將任務提交到queue中,任務加入queue之後不會立即返回,等待任務執行完成之後再返回。同sync類似,dispatch_sync與dispatch_sync_f唯一的區別在於dispatch_sync接收block作為參數,block被系統持有,不需要對self使用weak。dispatch_sync_f接受函數work作為參數,context作為傳給work函數的第一個參數。同樣,work參數也不能傳入NULL,否則會發生無法預料的事兒

同步表示任務加入到隊列中之後不會立即返回,等待任務完成再返回。語言的描述比較抽象,我們再次用代碼加深一下對概念的理解

1 2 3 4 5 6 NSLog(@"this is main queue, i want to throw a task to global queue"); dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL); dispatch_sync(globalQueue, ^{     // task }); NSLog(@"this is main queue, throw task completed");

我們來看看代碼的運行方式:

sync.gif

  1. 先在main queue中執行第一個nslog

  2. dispatch_sync會將block提交到global queue中,等待block的執行

  3. global queue中block前面的任務執行完成之後,block執行

  4. block執行完成之後,dispatch_sync返回

  5. dispatch_sync之後的代碼執行

由於dispatch_sync需要等待block被執行,這就非常容易發生死鎖。如果一個串列隊列,使用dispatch_sync提交block到自己隊列中,就會發生死鎖

 

1 2 3 4 5 6 7 8 9 dispatch_queue_t queue = dispatch_queue_create("com.liancheng.serial_queue", DISPATCH_QUEUE_SERIAL);   dispatch_async(queue, ^{     // 到達串列隊列           dispatch_sync(queue, ^{     //發生死鎖           }); });

dispatch_sync的代碼執行如圖所示

QQ截圖20160225094631.png

dispatch_sync需要等待block執行完成,同時由於隊列串列,block的執行需要等待前面的任務,也就是dispatch_sync執行完成。兩者互相等待,永遠也不會執行完成,死鎖就這樣發生了

從這裡看發生死鎖需要2個條件:

  1. 代碼運行的當前隊列是串列隊列

  2. 使用sync將任務加入到自己隊列中

如果queue是並行隊列,或者將任務加入到其他隊列中,這是不會發生死鎖的。

獲取隊列

獲取主線程隊列

主線程是我們最常用的線程,GCD提供了非常簡單的獲取主線程隊列的方法。

1 dispatch_queue_t dispatch_get_main_queue(void)

方法不需要傳入參數,直接返回主線程隊列。
假設我們要在主線程更新UI:

1 2 3 dispatch_async(dispatch_get_main_queue(), ^{     [self updateUI]; });

執行加入到主線程隊列的block,App會調用dispatch_main(), NSApplicationMain(),或者在主線程使用CFRunLoop。

獲取全局隊列

除了主線程隊列,GCD提供了幾個全局隊列,可以直接獲取使用

1 dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

dispatch_get_global_queue方法獲取的全局隊列都是並行隊列,並且隊列不能被修改,也就是說對全局隊列調用dispatch_suspend(), dispatch_resume(), dispatch_set_context()等方法無效

  • identifier: 用以標識隊列優先順序,推薦用qos_class枚舉作為參數,也可以使用dispatch_queue_priority_t

  • flags: 預留欄位,傳入任何非0的值都可能導致返回NULL

可以看到dispatch_get_global_queue根據identifier參數返回相應的全局隊列。identifier推薦使用qos_class枚舉

1 2 3 4 5 6 7 8 9 10 11 12 13 14 __QOS_ENUM(qos_class, unsigned int,     QOS_CLASS_USER_INTERACTIVE             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x21,     QOS_CLASS_USER_INITIATED             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x19,     QOS_CLASS_DEFAULT             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x15,     QOS_CLASS_UTILITY             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x11,     QOS_CLASS_BACKGROUND             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x09,     QOS_CLASS_UNSPECIFIED             __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x00, );

這個枚舉與NSThread中的NSQualityOfService類似

  • QOS_CLASS_USER_INTERACTIVE: 最高優先順序,交互級別。使用這個優先順序會占用幾乎所有的系統CUP和I/O帶寬,僅限用於交互的UI操作,比如處理點擊事件,繪製圖像到屏幕上,動畫等

  • QOS_CLASS_USER_INITIATED: 次高優先順序,用於執行類似初始化等需要立即返回的事件

  • QOS_CLASS_DEFAULT: 預設優先順序,當沒有設置優先順序的時候,線程預設優先順序。一般情況下用的都是這個優先順序

  • QOS_CLASS_UTILITY: 普通優先順序,主要用於不需要立即返回的任務

  • QOS_CLASS_BACKGROUND: 後臺優先順序,用於用戶幾乎不感知的任務。

  • QOS_CLASS_UNSPECIFIED: 未知優先順序,表示服務質量信息缺失

identifier除了使用qos_class枚舉,也可以用dispatch_queue_priority_t作為參數。

 

1 2 3 4 5 6 #define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN   typedef long dispatch_queue_priority_t;

INT16_MINtypedef long dispatch_queue_priority_t;

dispatch_queue_priority_t對應到qos_class枚舉有:

1 2 3 4 - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND

很多時候我們喜歡將0或者NULL傳入作為參數

1 dispatch_get_global_queue(NULL, NULL)

由於NULL等於0,也就是DISPATCH_QUEUE_PRIORITY_DEFAULT,所以返回的是預設優先順序

創建隊列

當無法獲取到理想的隊列時,我們可以自己創建隊列。

1 dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

如果未使用ARC,dispatch_queue_create創建的queue在使用結束之後需要調用dispatch_release。

  • label: 隊列的名稱,調試的時候可以區分其他的隊列

  • attr: 隊列的屬性,dispatch_queue_attr_t類型。用以標識隊列串列,並行,以及優先順序等信息

attr參數有三種傳值方式:

 

1 2 3 4 5 6 7 8 9 10 // 串列 #define DISPATCH_QUEUE_SERIAL NULL   // 並行 #define DISPATCH_QUEUE_CONCURRENT \         DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \         _dispatch_queue_attr_concurrent)           // 自定義屬性值 dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority);

DISPATCH_QUEUE_SERIAL或者NULL,表示創建串列隊列,優先順序為目標隊列優先順序。DISPATCH_QUEUE_CONCURRENT表示創建並行隊列,優先順序也為目標隊列優先順序。

dispatch_queue_attr_make_with_qos_class函數可以創建帶有優先順序的dispatch_queue_attr_t對象。通過這個對象可以自定義queue的優先順序。

  • attr: 傳入DISPATCH_QUEUE_SERIAL、NULL或者DISPATCH_QUEUE_CONCURRENT,表示串列或者並行

  • qos_class: 傳入qos_class枚舉,表示優先順序級別

  • relative_priority: 相對於qos_class的相對優先順序,qos_class用於區分大的優先順序級別,relative_priority表示大級別下的小級別。relative_priority必須大於QOS_MIN_RELATIVE_PRIORITY小於0,否則將返回NULL。從GCD源碼中可以查到QOS_MIN_RELATIVE_PRIORITY等於-15

使用dispatch_queue_attr_make_with_qos_class創建隊列時,需要註意,非法的參數可能導致dispatch_queue_attr_make_with_qos_class返回NULL,dispatch_queue_create傳入NULL會創建出串列隊列。寫代碼過程中需要確保這是否是預期的結果

設置目標隊列(2.25日更新,感謝@楊蕭玉HIT 指出問題,原文章有誤給大家致歉)

除了通過dispatch_queue_attr_make_with_qos_class設置隊列的優先順序之外,也可以使用設置目標隊列的方法,設置隊列的優先順序。當隊列創建時未設置優先順序,隊列將繼承目標隊列的優先順序。(不過一般情況下還是推薦使用dispatch_queue_attr_make_with_qos_class設置隊列的優先順序)

1 void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

調用dispatch_set_target_queue會retain新目標隊列queue,release原有目標隊列。設置目標隊列之後,block將會在目標隊列中執行。註意:當目標隊列串列時,任何在目標隊列中執行的block都會串列執行,無論原隊列是否串列。

假設有隊列A、B是並行隊列,C為串列隊列。A,B的目標隊列均設置為C,那麼A、B、C中的block在設置目標隊列之後最終都會串列執行。

499.png

例:隊列1並行,隊列2串列

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);   dispatch_async(queue1, ^{    // block1     for (int i = 0; i < 5; i ++) {         NSLog(@"+++++");     } }); dispatch_async(queue1, ^{ // block2     for (int i = 0; i < 5; i ++) {         NSLog(@"=====");     } }); dispatch_async(queue2, ^{    // block3     for (int i = 0; i < 5; i ++) {         NSLog(@"----");     } });

運行一下可知block1,block2,block3並行執行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2016-02-25 15:05:20.024 TGCD[1940:99120] +++++ 2016-02-25 15:05:20.024 TGCD[1940:99122] ===== 2016-02-25 15:05:20.024 TGCD[1940:99121] ---- 2016-02-25 15:05:20.025 TGCD[1940:99120] +++++ 2016-02-25 15:05:20.025 TGCD[1940:99121] ---- 2016-02-25 15:05:20.025 TGCD[1940:99122] ===== 2016-02-25 15:05:20.025 TGCD[1940:99120] +++++ 2016-02-25 15:05:20.025 TGCD[1940:99121] ---- 2016-02-25 15:05:20.025 TGCD[1940:99122] ===== 2016-02-25 15:05:20.025 TGCD[1940:99120] +++++ 2016-02-25 15:05:20.025 TGCD[1940:99121] ---- 2016-02-25 15:05:20.025 TGCD[1940:99122] ===== 2016-02-25 15:05:20.025 TGCD[1940:99120] +++++ 2016-02-25 15:05:20.025 TGCD[1940:99121] ---- 2016-02-25 15:05:20.025 TGCD[1940:99122] =====

如果將隊列1的目標隊列設置為隊列2,會發生什麼情況呢?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);   dispatch_set_target_queue(queue1, queue2);   dispatch_async(queue1, ^{     for (int i = 0; i < 5; i ++) {         NSLog(@"+++++");     } }); dispatch_async(queue1, ^{     for (int i = 0; i < 5; i ++) {         NSLog(@"=====");     } }); dispatch_async(queue2, ^{     for (int i = 0; i < 5; i ++) {         NSLog(@"----");     } });

block1,block2,block3變為了串列

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2016-02-25 15:06:57.215 TGCD[1974:100675] +++++ 2016-02-25 15:06:57.215 TGCD[1974:100675] +++++ 2016-02-25 15:06:57.215 TGCD[1974:100675] +++++ 2016-02-25 15:06:57.215 TGCD[1974:100675] +++++ 2016-02-25 15:06:57.216 TGCD[1974:100675] +++++ 2016-02-25 15:06:57.216 TGCD[1974:100675] ===== 2016-02-25 15:06:57.216 TGCD[1974:100675] ===== 2016-02-25 15:06:57.216 TGCD[1974:100675] ===== 2016-02-25 15:06:57.216 TGCD[1974:100675] ===== 2016-02-25 15:06:57.216 TGCD[1974:100675] ===== 2016-02-25 15:06:57.216 TGCD[1974:100675] ---- 2016-02-25 15:06:57.216 TGCD[1974:100675] ---- 2016-02-25 15:06:57.216 TGCD[1974:100675] ---- 2016-02-25 15:06:57.217 TGCD[1974:100675] ---- 2016-02-25 15:06:57.217 TGCD[1974:100675] ----

註意不要迴圈設置目標隊列,如A的目標隊列為B,B的目標隊列為A。這將會導致無法預知的錯誤

延時

GCD中有2個延時的API

1 2 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);

一定時間之後將block加入到queue中。when用於表示時間,如果傳入DISPATCH_TIME_NOW會等同於dispatch_async。另外不允許傳入DISPATCH_TIME_FOREVER,這會永遠阻塞線程。

通前面其他方法類似。dispatch_after接收block作為參數,系統持有block,block中self不需要weak。dispatch_after_f接收work函數作為參數,context作為work函數的第一個參數

需要註意的是這裡的延時是不精確的,因為加入隊列不一定會立即執行。延時1s可能會1.5s甚至2s之後才會執行。

dispatch_barrier

在並行隊列中,有的時候我們需要讓某個任務單獨執行,也就是他執行的時候不允許其他任務執行。這時候dispatch_barrier就派上了用場。

使用dispatch_barrier將任務加入到並行隊列之後,任務會在前面任務全部執行完成之後執行,任務執行過程中,其他任務無法執行,直到barrier任務執行完成

QQ截圖20160225095506.png

dispatch_barrier在GCD中有4個API

1 2 3 4 void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work); void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

如果API在串列隊列中調用,將等同於dispatch_async、dispatch_async_f、dispatch_sync、dispatch_sync_f,不會有任何影響。

dispatch_barrier最典型的使用場景是讀寫問題,NSMutableDictionary在多個線程中如果同時寫入,或者一個線程寫入一個線程讀取,會發生無法預料的錯誤。但是他可以在多個線程中同時讀取。如果多個線程同時使用同一個NSMutableDictionary。怎樣才能保護NSMutableDictionary不發生意外呢?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void)setObject:(id)anObject forKey:(id )aKey {     dispatch_barrier_async(self.concurrentQueue, ^{         [self.mutableDictionary setObject:anObject forKey:aKey];     }); }   - (id)objectForKey:(id)aKey {     __block id object = nil;    dispatch_sync(self.concurrentQueue, ^{         object = [self.mutableDictionary objectForKey:aKey];     });    return  object; }

當NSMutableDictionary寫入的時候,我們使用dispatch_barrier_async,讓其單獨執行寫入操作,不允許其他寫入操作或者讀取操作同時執行。當讀取的時候,我們只需要直接使用dispatch_sync,讓其正常讀取即可。這樣就可以保證寫入時不被打擾,讀取時可以多個線程同時進行

set_specific & get_specific

有時候我們需要將某些東西關聯到隊列上,比如我們想在某個隊列上存一個東西,或者我們想區分2個隊列。GCD提供了dispatch_queue_set_specific方法,通過key,將context關聯到queue上

1 void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
  • queue:需要關聯的queue,不允許傳入NULL

  • key:唯一的關鍵字

  • context:要關聯的內容,可以為NULL

  • destructor:釋放context的函數,當新的context被設置時,destructor會被調用

有存就有取,將context關聯到queue上之後,可以通過dispatch_queue_get_specific或者dispatch_get_specific方法將值取出來。

1 2 void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key); void *dispatch_get_specific(const void *key);
  • dispatch_queue_get_specific: 根據queue和key取出context,queue參數不能傳入全局隊列

  • dispatch_get_specific: 根據唯一的key取出當前queue的context。如果當前queue沒有key對應的context,則去queue的target queue取,取不著返回NULL,如果對全局隊列取,也會返回NULL

iOS 6之後dispatch_get_current_queue()被廢棄(廢棄的原因這裡不多解釋,如果想瞭解可以看這裡),如果我們需要區分不同的queue,可以使用set_specific方法。根據對應的key是否有值來區分

 原文鏈接:http://www.cocoachina.com/ios/20160225/15422.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、自己封裝URLConnection 連接請求類 public void downloadFile1() { try{ //下載路徑,如果路徑無效了,可換成你的下載路徑 String url = "http://c.qijingonline.com/test.mkv"; String path = ...
  • Android學習筆記目錄 ...
  • 前不久 項目 終於成功發佈上線了,最近就在給項目做優化,併排除一些線上軟體的 bug,因為項目中使用了友盟統計,所以在友盟給出的錯誤信息統計中能比較方便的找出客戶端異常的信息,可是很多像數組越界卻只給出了 *** -[__NSArrayM objectAtIndex:]: index 50 beyo ...
  • //獲得當前時間並且轉為字元串 //獲取當前時間轉為時間戳 //13位時間戳1469193006001(毫秒)轉 系統時間2016-08-11 08:55:36 如果只獲取當前的年月日,用NSDate 直接截取是不對的,以下方法提供了獲取當前的年月日等等 ...
  • 一般在使用RecyclerView的時候不免要修改RecyclerView的數據,使用notifyDataSetChanged()來刷新界面,但是當數據比較多,而只是修改了一點的數據,或者刷新比較頻繁,這樣就會導致界面的卡頓問題,用戶交互特別不好。 這個時候就需要只是修改需要修改的數據,不要將數據全 ...
  • 首先看一個API:setMaskFilter(MaskFilter maskfilter): 設置MaskFilter,可以用不同的MaskFilter實現濾鏡的效果,如濾化,立體等。 以下有兩個MaskFilter的子類可供選擇: BlurMaskFilter:指定了一個模糊的樣式和半徑來處理Pa ...
  • 微信小程式中不能直接操作window對象,document文檔,跟html的樹結構不相同。 實現類似導航的隱藏顯示,如圖效果: 點擊網路顯示或隱藏網路中包含的內容。其他類似。 如果是jquery很方便實現,能直接操作document。在微信小程式中實現思路是:在邏輯層定義變數,通過setData賦值 ...
  • 1、創建通知,最好在viewDidLoad的方法中創建 2、發送通知 3、移除通知,由那個控制器創建由那個控制器移除,誰創建誰移除,最好在dealloc方法中移除,如果通知不能及時的移除掉,當下次進入該控制器時會重覆創建NSNotificationCenter,在對應方法中發送通知給上一次創建的通知 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...