iOS開發-- RunLoop的基本概念與例子分析

来源:http://www.cnblogs.com/azuo/archive/2016/10/26/5975479.html
-Advertisement-
Play Games

【本次開發環境: Xcode:7.2 iOS Simulator:iphone6 By:啊左 本文Demo下載鏈接:RunLoop-Demo】 基本概念 一、RunLoop簡介 RunLoop,跑圈。在iOS開發中,也就是運行迴圈。 在應用需要的時候自己跑起來運行,在用戶沒有操作的時候就停下來休息。 ...


本次開發環境: Xcode:7.2     iOS Simulator:iphone6   By:啊左     本文Demo下載鏈接:RunLoop-Demo

 

-----------------------------基本概念-----------------------------

一、RunLoop簡介

RunLoop,跑圈。在iOS開發中,也就是運行迴圈。

在應用需要的時候自己跑起來運行,在用戶沒有操作的時候就停下來休息。充分節省CPU資源,提高程式性能。

 

. RunLoop的概念與作用

概念:一般來講,一個線程一次只能執行一個任務,執行完成後線程就會退出。但是有時候我們需要線程能夠一直“待命”隨時處理事件而不退出,這就需要一個機制來完成這樣的任務。

這樣一種機制的代碼邏輯如下:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
} 

這種模型通常被稱作 Event Loop。 Event Loop 在很多系統和框架里都有實現。而實現這種模型的關鍵點在於:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立刻被喚醒。

例如一個應用放那裡,不進行操作就像靜止休息一樣,點擊按鈕,就有響應,就像“隨時待命”一樣,這就是RunLoop的功勞。

所以RunLoop 實際上就是一個對象,這個對象管理了其需要處理的事件和消息,並提供了一個入口函數來執行RunLoop 的邏輯。 

線程開始這個函數之後,便一直會處於此函數 "接受消息->等待->處理" 的迴圈中:(有事:做出反應;   木事:休眠省電;   再次有事:重新喚醒、處理事件。)

直到這個迴圈結束(比如傳入 quit 的消息),最後函數返回。

作用:

  1. 保持程式持續運行:例如程式一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應的RunLoop,RunLoop保證主線程不會被銷毀,也就保證了程式的持續運行;
  2. 處理App中的各種事件(比如:觸摸事件,定時器事件,Selector事件等 );
  3. 節省CPU資源,優化程式性能:程式運行起來時,當什麼操作都沒有做的時候RunLoop就通知系統,現在沒有事情做,然後進行休息待命狀態,這時系統就會將其資源釋放出來去做其他的事情。當有事情做,也就是一有響應的時候RunLoop就會立馬起來去做事情;

RunLoop,最重要的作用,也就是用來管理線程的。可以說,沒有線程,也就沒有RunLoop的存在必要。

當線程的RunLoop一開啟,RunLoop便開始對線程進行管理工作:在線程執行完任務後,線程便會進入休眠狀態,並且不會退出,隨時等待新的任務。

 

三、RunLoop與線程的關係

1.每條線程都有唯一的一個與之對應的RunLoop對象;

2.RunLoop在第一次獲取時創建,線上程結束時銷毀;只能在一個線程的內部獲取其 RunLoop(主線程除外)。

3.主線程的RunLoop系統預設啟動,子線程的RunLoop需要主動開啟;

 

其實在我們每次建立項目的時候,就已經使用上了RunLoop。

在程式的啟動入口main函數中有這樣一段熟悉的代碼:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

實際上UIApplicationMain 函數內部就啟動了一個與主線程相關聯的RunLoop,我們可以做一下驗證,

1,在UIApplicationMain函數前後加入一段輸出代碼:

點擊運行,輸出“開始”,卻始終沒有運行到“結束”這個輸出,那是因為當運行完 "NSLog(@"開始");"這一段代碼後,系統運行UIApplicationMain函數,並且進入了:

主線程的運行迴圈,RunLoop使得主線程一直處在迴圈中而不會跳出來進入下一段執行。

(並且,可以看到Xcode會對 “NSLog(@"結束");” 這段代碼警告“Code will never be executed”,提示代碼不會執行到這一步。)

2,在“Main.storyboard”中隨意放置幾個按鈕控制項,main.m文件代碼再次修改如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"開始");
        return 0;
    }
}

點擊運行,輸出“開始”後,模擬器界面也是一片空白。“stop”按鈕也點不下去了:

因為當輸出“開始”後,“return 0”,之後沒有進入主線程運行迴圈,程式一啟動就結束了,控制項與其他程式有關的都沒有執行,所以界面空白。

說明瞭在UIApplicationMain函數中,開啟了一個和主線程相關的RunLoop,導致UIApplicationMain不會返回,一直在運行中,也就保證了程式的持續運行。

這也是為什麼應用能夠在我們無任何操作時休息,在我們進行操作的時候又能夠立刻進行響應活動,恰恰因為應用處於RunLoop的“等待命令”的狀態。

 

四、RunLoop對象與相關類。

對象:

RunLoop的概念,我們可以知道RunLoop 實際上就是一個管理著線程對象。那麼,如何獲取RunLoop對象呢?

Foundation框架中:

[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop];    // 獲得主線程的RunLoop對象

Core Foundation框架中:

CFRunLoopGetCurrent();  // 獲得當前線程的RunLoop對象
CFRunLoopGetMain();     // 獲得主線程的RunLoop對象

文檔中的相關類:

CFRunLoopRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopModeRef
CFRunLoopObserverRef

 他們的關係如下圖

  • 1.一個 RunLoop 包含若幹個 Mode,而每個 Mode 又包含若幹個 Source/Timer/Observer
  • 2.RunLoop每次只能指定一種Mode。而且如果需要切換 Mode,只能退出當前 Loop。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
  • 3.如果一個 mode 中一個 “Source/Timer/Observer” 都沒有,則 RunLoop 會直接退出,不進入迴圈。

CFRunLoopSourceRef   輸入源

事件產生的地方,函數調用棧上Source有兩個版本:Source0 和 Source1。

  • Source0:非基於埠port,例如觸摸,滾動,selector選擇器等用戶觸發的事件;(只包含了一個回調函數,它並不能主動觸發事件)
  • Source1:基於埠port,一些系統事件; (包含了一個 mach_port 和一個回調函數,被用於通過內核和其他線程相互發送消息。能主動喚醒 RunLoop 的線程) 

CFRunLoopTimerRef    定時源

 基於時間的觸發器,與NSTimer可混用。

 包含了一個時間長度和一個回調函數。當其加入到 RunLoop 時,RunLoop會註冊對應的時間點,當時間點到時,RunLoop會被喚醒以執行那個回調。

CFRunLoopModeRef   mode類型

事實上CFRunLoopModeRef 類並沒有對外暴露,而如果在Xcode中查看CFRunLoopRef,可以看到CFRunLoopModeRef 類,通過 CFRunLoopRef 的介面進行了封裝。 

CFRunLoopModeRef有5種形式:(當然,還有一些開發中基本用不到的更多的蘋果內部的 Mode:Mode介紹

kCFRunLoopDefaultMode 預設模式,通常主線程在這個模式下運行

UITrackingRunLoopMode 界面跟蹤Mode,用於追蹤Scrollview觸摸滑動時的狀態。

kCFRunLoopCommonModes 占位符,帶有Common標記的字元串,比較特殊的一個mode;

UIInitializationRunLoopMode:剛啟動App時進入的第一個Mode,啟動後不在使用。

GSEventReceiveRunLoop:內部Mode,接收系事件。 

從關係圖,我們可以知道RunLoop一次只能指定一種Mode,且能夠讓不同組的 Source/Timer/Observer互不影響,具體的實現後面會用一個項目例子來參考。

CFRunLoopObserverRef  觀察者

RunLoop的觀察者,能夠監聽RunLoop的狀態改變。

每個 Observer 都包含了一個回調(函數指針),當 RunLoop 的狀態發生變化時,觀察者就能通過回調接受到這個變化,可以觀察到不同時刻的狀態有以下幾個:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即將進入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒 kCFRunLoopExit = (1UL << 7), // 即將退出Loop };


-----------
------------------例子-----------------------------

測試一、二的UI設計界面如下:

測試一:RunLoop的運用。

在“ViewController.m”中創建一個子線程,線上程方法中一直開啟RunLoop。並在“Main.storyboard”中添加一個名為“showSource”的按鈕控制項,創建RunLoop事件源,使得RunLoop進入迴圈:

 1 @interface ViewController ()
 2 
 3 @property (strong,nonatomic)NSThread *thread;  //記得使用Strong屬性
 4 - (IBAction)showSource:(id)sender;     //點擊按鈕,添加RunLoop事件源用。
 5 
 6 @end
 7 
 8 @implementation ViewController
 9 
10 - (void)viewDidLoad {
11     [super viewDidLoad];
12     //創建自定義的子線程
13     self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil];
14     [self.thread start];  //啟動子線程
15 }
16 -(void)threadMethod
17 {
18     NSLog(@"打開子線程方法");
19     while (1) {
20         
21         //條件一:run,進入迴圈,如果沒有source/timer就直接退出,不進入迴圈,後面加上source才能進入工作。
22         /*【原因:如果線程中有需要處理的源,但是響應的事件沒有到來的時候,線程就會休眠等待相應事件的發生;
23            這就是為什麼run loop可以做到讓線程有工作的時候忙於工作,而沒工作的時候處於休眠狀態。】
24          */
25         [[NSRunLoop currentRunLoop]run];
26         
27         //上面一行代碼等於加了參數為1的while,所以當有source進入迴圈,下麵這條代碼的就不會運行。
28         NSLog(@"這裡是threadMethod:%@", [NSThread currentThread]);
29         //如果要測試“二、addTime”按鈕的話,建議註釋掉上面這句代碼。
30     }
31 }
32 
33 #pragma mark -- 測試一:子線程Selector源的啟動
34 - (IBAction)showSource:(id)sender {
35     
36     //註意:在這個方法裡面輸出的是main主線程,因為是主線程運行的UI控制項行為。
37     NSLog(@"這裡是主線程:%@",[NSThread currentThread]);
38     /*
39        在沒有run之前,一直處於休眠狀態。所以如果要運行selector方法,還需要threadMethod中條件一不斷迴圈的Run!
40        在我們指定的線程中調用方法,此處相當於增加了一個帶source的mode,有內容,實現了RunLoop迴圈運行成立的條件二。
41      */
42     //試著在這句之前添加[[NSRunLoop currentRunLoop]run];是不能啟動子線程的RunLoop,因為此處是在main主線程上。
43     [self performSelector:@selector(threadSelector) onThread:self.thread withObject:nil waitUntilDone:NO];
44 }
45 -(void)threadSelector//【此處運行在子線程】
46 {
47     NSLog(@"打開子線程Selector源");
48     NSLog(@"此處是threadSelector源:%@",[NSThread currentThread]);
49 }

輸出結果:

2016-10-24 10:48:24.971 RunLoop演示[18111:752173] 打開子線程方法
2016-10-24 10:48:24.973 RunLoop演示[18111:752173] 這裡是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}
2016-10-24 10:48:26.256 RunLoop演示[18111:752173] 這裡是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}
........
2016-10-24 10:48:26.260 RunLoop演示[18111:752173] 這裡是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}
2016-10-24 10:48:26.261 RunLoop演示[18111:751978] 這裡是主線程:<NSThread: 0x7fc830402b30>{number = 1, name = main}
2016-10-24 10:48:26.261 RunLoop演示[18111:752173] 這裡是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}
2016-10-24 10:48:26.263 RunLoop演示[18111:752173] 打開子線程Selector源
2016-10-24 10:48:26.264 RunLoop演示[18111:752173] 此處是threadSelector源:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}

分析代碼:

第3行為什麼子線程thread需要用到strong屬性?

       如果使用weak,子線程調用不了,子線程thread一創建就立刻銷毀了。如果我們使用自己自定義的線程,並且重寫線程的“-(void)dealloc”方法,我們會看到其實子線程thread一創建就調用dealloc立刻銷毀了。

19-28行為什麼要用到while?

  重點:Run loop的管理並不完全是自動的。我們在設計子線程代碼的時候,必須符合以下條件才能進入迴圈:

     1.RunLoop處於開啟狀態;

     2.正確響應輸入事件;

所以第一步我們需要使用while/for語句來驅動run loop,以便能夠進行迴圈。

第37行

通過輸出線程的對象信息,我們可以發現,此時處於UI控制項按鈕的事件其實屬於主線程main,

(在這裡有個疑問,如何把Run驅動RunLoop的代碼放在此處的話,還能不能performSelector創建事件源呢?

答案是不能的,因為此時是在主線程里。也就是:Run的不是子線程:self.thread。因此也不會執行threadSelector方法)

第43行:

我們在while中使RunLoop一直處在開啟的狀態,所以當創建一個Selector源時,滿足條件2:RunLoop進入迴圈中,執行子線程的threadSelector方法,在這個RunLoop子線程處於運行迴圈管理中,如“while(1)”死迴圈一般,便不會執行後面那句輸出代碼,也即是停止輸出 “這裡是threadMethod:.........”。

(是不是類似文章開頭關於main函數的測試,當進入迴圈後,便不會執行後面輸出“結束”那段代碼了。區別是主線程是預設自動開啟的,而子線程的RunLoop則需要我們手動開啟。)

 

測試二:mode模式與定時源的同步性

在“Main.storyboard”中進行timer事件測試。

a.添加一個用於顯示內容的名為textView”的文本控制項,b.再添加一個名為“addTime”的按鈕控制項

@interface ViewController ()
//測試一
@property (strong,nonatomic)NSThread *thread;
- (IBAction)showSource:(id)sender;
//測試二 @property (weak, nonatomic) IBOutlet UITextView *textView; - (IBAction)addTime:(UIButton *)sender; @end

然後在“ViewController.m”中threadSelector方法後面添加以下代碼;

#pragma mark -- 二、Time測試
- (IBAction)addTime:(UIButton *)sender {
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
//添加timer到RunLoop [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; }
-(void)showTimer //【在主線程】 { NSLog(@"調用time的線程:%@",[NSThread currentThread]); [self showText:@"-------time-------"]; } #pragma mark --在文本控制項textView後面增加str字元串 -(void)showText:(NSString *)str //註意:因為UI控制項需要在主線程裡面,嘗試一下,如果是在子線程threadMethod方法執行此段代碼則運行報錯。 { NSString *text = self.textView.text; self.textView.text = [text stringByAppendingString:str]; }

關於mode模式

操作:當點擊addTime按鈕後,textView控制項上不斷顯示“-------time-------”,但是當我們拖拽textView進度條上下移動時,會發現"-(void)showTime:"不會執行,textView控制項上的內容不再增加“-------time-------”,就像“卡住了,死機了”一樣。當我們停止對textView進行拖拽後,控制項上的內容又不斷添加更新了。

解決方案:修改mode類型:把預設模式NSDefaultRunLoopMode改為占位符NSRunLoopCommonModes

發現如果修改成這樣,那麼即使我們對textView進行拖拽,內容會一直增加“-------time-------”,再也不會由於拖拽而被牽制住了。

原因:每次RunLoop只能支持一種mode。當我們點擊addtime按鈕後,定時源(timer)加入到RunLoop中,而當滑動textView時,RunLoop自動切換成UITrackingRunLoopMode模式,定時器就停止了響應。

NSRunLoopCommonModes等效於NSDefaultRunLoopModeNSEventTrackingRunLoopMode兩種模式的結合

所以當我們在帶有 “Common ”標記的NSRunLoopCommonModes模式下添加定時源(timer)後。即使我們對textView進行滾動操作,也不會影響到內容的顯示了。

另外提一下,還有另一種添加time的方法:

 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];

//使用scheduledTimerWithTimeInterval方法,會自動添加到RunLoop,所以可以不寫以下代碼,只是會預設為NSDefaultRunLoopMode模式 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

關於同步:

當我們觀察控制台的輸出,可以發現,其實調用 "-(void)showTimer" 輸出的是在主線程mian中。

這是因為輸入源使用傳遞非同步事件,且通常消息來自於其他線程或程式。

定時源是在以同步方式傳遞信息的。

 

 

-----------------------------其他補充-----------------------------

1.RunLoop輸入源的結構圖如下:

 

RunLoop接收輸入事件來自兩種不同的來源:輸入源(input source)和定時源(timer source)。

 輸入源:傳遞非同步事件,通常消息來自於其他線程或程式。

輸入源有3種類型:

  • Selector源:如例子按鈕事件中的performSelector,當在子線程中執行Selector時,目標線程必須RunLoop處於開啟狀態,不然Selector就一直處於休眠狀態;
  • 基於埠的輸入源:就是之前提到的Source1。通過內置的埠相關的對象和函數,創建配置基於埠的輸入源。 例如可以使用NSPort的方法把該埠添加到                                   RunLoop;
  • 自定義輸入源:創建custom輸入源,必須使用Core Foundation裡面的CFRunLoopSourceRef類型相關的函數來創建,並自定義自己的行為和消息傳遞機制;

在測試一中,當我們點擊按鈕後,執行UI按鈕控制項的事件,此時“performSelector”一個Selector輸入源,所以,系統執行Selector方法。

2.RunLoop的內部流程的邏輯如下:

 

所以在測試一中,處於while一直進行著的語句:

[[NSRunLoop currentRunLoop]run];

每次的Run都代表著:進行一次消息輪詢,如果沒有任務需要處理的消息源,則直接返回;

 

---------------

本文主要闡述基本概念與應用,如果有興趣的童鞋可以:

1.RunLoop的官方文檔

2.參考ibireme的文章,關於RunLoop背後的底層原理的詳解:

http://blog.ibireme.com/2015/05/18/runloop/

3、以及這篇關於輸入源定時源的詳解介紹:

http://blog.csdn.net/ztp800201/article/details/9240913


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

-Advertisement-
Play Games
更多相關文章
  • 你的代碼可能包含語法錯誤,邏輯錯誤,如果沒有調試工具,這些錯誤比較難於發現。 通常,如果 JavaScript 出現錯誤,是不會有提示信息,這樣你就無法找到代碼錯誤的位置。 在程式代碼中尋找錯誤叫做代碼調試。 JavaScript 調試工具 調試很難,但幸運的是,很多瀏覽器都內置了調試工具。 內置的 ...
  • 有時候,我們需要得到視窗拖動或者滑鼠移動的距離,此時可以通過計算滑鼠前後在頁面中的位置來得到想要的結果,下麵介紹幾個事件屬性: 1、客戶區坐標位置 滑鼠事件都是在瀏覽器視口中的特定位置上發生的。這個位置信息保存在事件對象的 clientX 和 clientY 屬性中。它們的值表示事件發生時滑鼠指針在 ...
  • 1.defer標簽 只支持IE defer屬性的定義和用法: 屬性規定是否對腳本執行進行延遲,直到頁面載入為止。有的 javascript 腳本 document.write 方法來創建當前的文檔內容,其他腳本就不一定是了。如果您的腳本不會改變文檔的內容,可將 defer 屬性加入到 <script ...
  • JavaScript實現跨瀏覽器的一些事件綁定、移除、屬性獲取的方法 ...
  • 工作中遇到的小問題,做個筆記 實現springMVC + jsp + ajax 上傳文件 HTML javascript springMVC.xml java ...
  • 0 問題描述 由於需要演示觸控操作,採購了SurfacePro,SurfacePro的推薦解析度為2736×1824,且預設縮放比例為200%,IE瀏覽器的預設縮放比例也是200%,這樣就導致右側出現了豎直滾動條。整個界面的高度是通過計算得出並控制的,按理來說不應該出現這個垂直方向的滾動條。 正常情 ...
  • <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> </body> <script type="text/javascript"> //日期對象可以儲存任意一個日期, 並且可以精確到毫 ...
  • JS是個神奇的語言,藉助Node.js的後端環境,我們可以進行相應的爬蟲開發,如這篇 基於Node.js實現一個小小的爬蟲 但搭建後臺環境始終略為麻煩,拿到一臺新電腦,不用配環境,可不可以直接在瀏覽器客戶端直接實現呢? 可以可以,這裡就簡單地說一下在瀏覽器客戶端實現的爬蟲抓取頁面數據 一、概念理解 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...