iOS系統崩潰的捕獲

来源:https://www.cnblogs.com/kaede31416/p/18133113
-Advertisement-
Play Games

iOS系統崩潰的捕獲 相信大家在開發iOS程式的時候肯定寫過各種Bug,而其中最為嚴重的Bug就是會導致崩潰的Bug(一般來說妥妥的P1級)。在應用軟體大大小小的各種異常中,崩潰確實是最讓人難以接受的行為。畢竟崩潰意味著用戶將丟失應用程式運行中的所有上下文環境,丟失其所有未保存的數據,會帶給用戶最糟 ...


iOS系統崩潰的捕獲

相信大家在開發iOS程式的時候肯定寫過各種Bug,而其中最為嚴重的Bug就是會導致崩潰的Bug(一般來說妥妥的P1級)。在應用軟體大大小小的各種異常中,崩潰確實是最讓人難以接受的行為。畢竟崩潰意味著用戶將丟失應用程式運行中的所有上下文環境,丟失其所有未保存的數據,會帶給用戶最糟糕的使用體驗。

所以在應用的開發階段,我們一定要杜絕此類可能造成應用程式無法使用的崩潰。但是很多崩潰並不是自己在開發階段就能預料到的,此時就需要一種能夠線上獲取崩潰日誌並且上報的機制,這就是所謂的崩潰捕獲和上報體系。

今天我們不研究SuperApp中的崩潰上報,主要研究一下崩潰捕獲是如何實現的。

iOS系統中如何捕獲崩潰

首先,iOS系統中,並沒有通用的能夠捕獲所有崩潰的處理函數。捕獲崩潰主要有以下三種方式:

  • NSSetUncaughtExceptionHandler
  • Unix Signal捕獲函數
  • Mach(讀音為[mʌk])異常捕獲函數

關於如何用上述的方式捕獲崩潰,不是本次分享的重點,大家可以自行查閱博客中的代碼。我們主要需要理解的是這三者各自的原理和應用場景。

NSSetUncaughtExceptionHandler

首先我們寫一個會導致崩潰的Objective-C代碼片段:

NSDictionary *userinfo = @{
  @"username": @"TP-LINK",
  @"email": @"[email protected]",
  @"tel": @"15015001500"
};
NSMutableArray<NSDictionary *> *memberarray = [NSMutableArray arrayWithArray:@[userinfo]];
for (NSDictionary *dic in memberarray) {
  if ([[dic valueForKey:@"username"] isEqualToString:@"TP-LINK"]) {
    [memberarray removeObject:dic];
  }
}

運行程式,不出意外的話,程式在執行到片段的時候就會立刻崩潰,然後我們會在控制台裡面看到如下列印:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0xb550c30> was mutated while being enumerated.'

相信不少同學都會這串提示很熟悉,從字面上來看,程式崩潰是因為有個異常沒有被捕獲到,異常的類型是NSGenericException,導致異常的原因則是因為在遍歷集合的時候嘗試去修改裡面的元素。

NSGenericException是一個繼承自NSException的類,表示觸發的是一種通用的異常,除了這個類,還有很多其他的子類,像是NSRangExceptionNSInvalidArgumentException等,基本上只要看到名稱就知道異常大致是什麼原因導致的。

NSException是可以被我們手動捕獲的,例如:

@try {
    for (NSDictionary *dic in memberarray) {
        if ([[dic valueForKey:@"username"] isEqualToString:@"TP-LINK"]) {
            [memberarray removeObject:dic];
        }
}
@catch (NSException *exception) {
    NSLog(@"Caught %@: %@", [exception name], [exception reason]);
}

但是在寫實際項目的時候,我們通常不會手動寫這類處理異常的代碼,Objective-C也並沒有強制要求我們寫此類的異常處理程式。

其實,這主要是因為異常代表的通常是我們編寫的程式存在邏輯錯誤,通常不可恢復,需要我們在發佈給用戶使用之前由開發者進行處理,所以NSException又被稱為應用級異常。NSSetUncaughtExceptionHandler實際上是給我們提供了一個手段,對這些我們未捕獲的異常進行一個最終的處理,但如果這些錯誤是在用戶使用的時候發生的,我們也無法立刻進行處理。

或許也是因為這個原因,Swift語言拋棄了NSException,而只保留了Error。

由於NSSetUncaughtExceptionHandler不是萬能的,比如我們寫一段Swift的強制解包代碼:

var userName= fetchUserName()
printUserName(userName!)

上述代碼假設fetchUserName()函數返回nil,並且printUserName()函數只接受非空參數,那麼在程式運行時,由於強制解包失敗,應用程式會崩潰並且NSSetUncaughtExceptionHandler也無法捕獲此類崩潰,這時候就需要其他的機制來捕獲此類異常。

Mach異常

要瞭解Mach異常,首先要瞭解什麼是Mach!首先上一張mac OS X的架構圖:

mac OS X的核心操作系統被稱為“Darwin”,其由系統組件和內核構成。其中內核被稱為"xnu",他是一個混合型的內核,包括了Mach和BSD兩個部分,其中BSD實現了文件系統、網路、NKE(Network Kernel Extension,實現註入通信加密、虛擬網路介面等網路方面的擴展功能)、POSIX介面等功能,而Mach則實現了I/O組件和驅動程式。xnu內核是開源的。

從圖裡面可以看到,內核的下麵就是硬體,所以由Mach內核拋出的異常也被稱為是最底層的異常,造成異常的原因通常是硬體導致的異常,比如:

  • 試圖訪問不存在的記憶體
  • 試圖訪問違反地址空間保護的記憶體
  • 由於非法或未定義的操作代碼或操作數而無法執行指令
  • 產生算術錯誤,例如被零除、上溢、或者下溢
  • ……

關於Mach拋出異常的流程,我們可以結合以下圖來理解:

如果出錯的線程觸發了一個硬體級別的錯誤,處於內核的陷阱處理程式就會調用exception_deliver()函數依次嘗試將異常投遞到thread、task和host。

這裡插入一個小話題,在Mach內核中,為了和thread、task和host打交道,或者他們互相之間打交道,提供了一種基於埠的IPC手段,這個手段在Cocoa上層也有對應的抽象,就是NSMachPort。這個mach port大家可能聽說過,不知大家是否有印象?

當異常發生的時候,一條包含異常的mach message,例如異常類型、發生異常的線程等等,都會被髮送到一個異常埠。而thread、task、host都會維護一組異常埠,當Mach Exception機制傳遞異常消息的時候,它會按照thread → task → host 的順序傳遞異常消息。這是通過上面的mach_exc_raise()類函數來實現的。

如果thread、task都沒有處理異常,那麼就會由host也就是操作系統內核來處理異常,操作系統處理異常的方式就是上圖Exception Handler中的流程,可以看到,handler是一個迴圈處理消息的機制,mach_msg_receive()函數負責接受消息;mach_exc_server()函數內有catch_mach_exception_raise()函數,這個函數通過ux_exception()將mach異常轉換為Unix的Signal,並通過threadsignal()將其發送到對應的線程上去。

這一系列過程中,我們可控的部分是thread,我們可以新建一條thread並且通過mach port監聽異常埠來實現崩潰的捕獲。

有時候,Debugger會在程式崩潰的時候,給出Mach異常的類型:

上述代碼試圖給一個assign類型的property賦值,由於引用計數為0,對象在賦值之後就被立刻釋放了,所以這行代碼就崩潰了

Debugger給出的標紅信息,可以這麼理解:

一些其他常見的Mach異常類型及其對應的原因如下表:

Exception Notes
EXC_BAD_ACCESS 訪問了不該訪問的記憶體
EXC_BAD_INSTRUCTION 線程執行非法指令
EXC_ARITHMETIC 算術異常
EXC_SOFTWARE 軟體生成的異常
EXC_BREAKPOINT 跟蹤或者斷點

關於code大家可能會存在疑惑,它代表的其實是內核函數的返回值,其中,code=1代表的是地址不可用,其定義如下:

#define KERN_INVALID_ADDRESS            1

由於code的種類有很多,其他code對應的含義,可以翻閱kern_return.h頭文件進行查閱

以下為蘋果的崩潰日誌,裡面也包含類似信息:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000000000b8

將兩者進行結合,一般就可以判斷崩潰的原因究竟是什麼。瞭解以上知識相信會對大家日後解決Bug帶來一定的幫助。

Unix Signal

Signal是Unix、類Unix以及其他POSIX相容的操作系統中進程間通訊的一種有限制的方式。它是一種非同步的通知機制,用來提醒進程一個事件已經發生。信號的作用有很多,比如可以用來進程間通信(IPC)、用於Debugger調試等,當然也可以用來報告異常。

既然Mach已經實現了硬體導致的異常,為什麼還需要將其轉化為Unix Signal,繼續報告一次呢?

原因很簡單,因為xnu包含了BSD和Mach,為了實現POSIX相容,讓用戶可以使用BSD提供的POSIX API,就需要做這樣一層轉換。

Mach異常和Unix Signal兩者的轉換關係如下表:

Mach 異常 Unix Signal 原因
EXC_BAD_INSTRUCTION SIGILL 非法指令,比如數組越界,強制解包可選形等等
EXC_BAD_ACCESS SIGSEVG、SIGBUS SIGSEVG、SIGBUS兩者都是錯誤記憶體訪問,但是兩者之間是有區別的:SIGBUS(匯流排錯誤)是記憶體映射有效,但是不允許被訪問,比如訪問一個結構體但是起始地址有誤; SIGSEVG(段地址錯誤)是記憶體地址映射都失效,比如野指針
EXC_ARIHMETIC SIGFPE 運算錯誤,比如浮點數運算異常
EXC_BREAKPOINT SIGTRAP trace、breakpoint等等,比如說使用Xcode的斷點
EXC_SOFTWARE SIGABRT、SIGPIPE、SIGSYS、SIGKILL 軟體錯誤,其中SIGABRT最為常見。

問1:既然Mach異常可以轉換為unix異常,而signal也是可以由我們自由處理的,那是否可以不處理Mach異常,只處理unix的signal就可以了?

答案是不行,因為某些異常,比如EXC_GUARD 異常(這是一種違反了受保護資源的防護而導致的異常,比如訪問SQLite文件的時候關閉了它的文件描述符),是沒有映射到Unix Signal的,這種異常就沒法通過signal處理。

問2:那是不是處理了Mach異常,就不需要處理signal異常了呢?

答案是也不行,因為如果底層有些異常類型只能通過signal處理,比如直接調用了 __pthread_kill函數直接向某條線程發送了SIGABRT這個signal,這類異常不能被Mach所捕獲

為什麼沒有通用的異常處理函數

現在我們可以回答這個問題了。總結一下,iOS系統中,崩潰有可能是以下兩種方式產生的:

  • 應用級異常,比如NSException
  • 硬體級異常,比如野指針訪問

對於前者,我們只能使用NSSetUncaughtExceptionHandler進行捕獲,對於後者,我們需要使用以下機制:

  • Mach異常處理機制
  • Unix Signal異常處理機制

因為以上兩者作用域也無法互相覆蓋,所以以上兩者也需要結合使用。

正是因為這三種處理機制覆蓋了不同的領域,並且處理機制也不盡相同,因此iOS中沒有通用的異常處理函數。

然而,事情沒有那麼簡單

上述三個函數的功能十分強大,但是實際上設計一個崩潰捕獲系統沒有那麼容易。一般來說,捕獲系統除了捕獲崩潰,還需要記錄崩潰時的現場信息,比如崩潰時的iOS系統版本、應用版本、崩潰時間、異常信息、程式堆棧等等:

{"app_name":"TP-LINK物聯","timestamp":"2023-02-16 15:40:40.00 +0800","app_version":"4.12.1","slice_uuid":"d146125f-f904-3e39-940a-0f7dd32d6071","adam_id":0,"build_version":"41201","platform":2,"bundleID":"net.tplink.surveillancesystem","share_with_app_devs":0,"is_first_party":0,"bug_type":"109","os_version":"iPhone OS 14.0.1 (18A393)","incident_id":"70E8ABFF-6F0F-4094-BF31-EE929EFA78DD","name":"TP-LINK物聯"}
Incident Identifier: 70E8ABFF-6F0F-4094-BF31-EE929EFA78DD
CrashReporter Key:   8c905de38d4cd4ff6ad692cc4ca4f6b1f41a50af
Hardware Model:      iPhone12,8
Process:             TP-LINK物聯 [2002]
Path:                /private/var/containers/Bundle/Application/F17C1188-8ED4-4C72-8E46-FE7ABE28DDA1/TP-LINK物聯.app/TP-LINK物聯
Identifier:          net.tplink.surveillancesystem
Version:             41201 (4.12.1)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           net.tplink.surveillancesystem [564]


Date/Time:           2023-02-16 15:40:39.7011 +0800
Launch Time:         2023-02-16 15:37:15.6262 +0800
OS Version:          iPhone OS 14.0.1 (18A393)
Release Type:        User
Baseband Version:    2.00.01
Report Version:      104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000000000b8
VM Region Info: 0xb8 is not in any region.  Bytes before following region: 4375183176
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                        104c80000-1077ac000        [ 43.2M] r-x/r-x SM=COW  ...app/TP-LINK物聯

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [2002]
Triggered by Thread:  15

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	0x00000001d0bbfdd0 0x1d0bbc000 + 15824
1   libsystem_kernel.dylib        	0x00000001d0bbf184 0x1d0bbc000 + 12676
2   CoreFoundation                	0x00000001a4bb6cf8 0x1a4b19000 + 646392
3   CoreFoundation                	0x00000001a4bb0ea8 0x1a4b19000 + 622248
4   CoreFoundation                	0x00000001a4bb04bc 0x1a4b19000 + 619708
5   GraphicsServices              	0x00000001bb635820 0x1bb632000 + 14368
6   UIKitCore                     	0x00000001a7554734 0x1a69d7000 + 12048180
7   UIKitCore                     	0x00000001a7559e10 0x1a69d7000 + 12070416
8   TP-LINK物聯                     	0x0000000104c89ff0 0x104c80000 + 40944
9   libdyld.dylib                 	0x00000001a4877e60 0x1a4877000 + 3680
……

在iOS系統中,如果直接在上述的崩潰處理函數中進行這些信息的記錄,並不安全,這主要是因為iOS中App被限制在一個進程中運行,如果應用崩潰,那崩潰的線程將會立刻暫停執行,那就會導致如下問題:

  • 記憶體可能被破壞(比如某些數值溢出導致的崩潰,記憶體會被溢出的數據覆蓋)
  • 鎖可能正在被暫停執行的線程持有著
  • 數據結構可能只更新一半

這樣的不穩定環境,大部分函數都不能保證能夠正確運行,導致崩潰處理程式能夠調用的庫函數非常有限,你將無法做到:

  • 通過malloc等函數分配堆記憶體
  • 通過backtrace函數獲取調用棧信息

如果破解這些限制?我們不妨研究下SuperApp中集成的Breakpad是怎麼操作的。

Breakpad的整體構成

如上圖所示,Breakpad主要由三部分構成:

  • symbol dumper:符號提取器。應用程式在構建的時候會包含debug相關的信息,它能夠提取這些信息並生成專屬的符號文件。
  • client:客戶端是一種包含在你應用程式裡面的第三方庫,它能夠捕獲當前各線程的狀態以及當前載入的共用庫等信息,將其寫入minidump文件中。
  • processor:處理器主要用來讀取minidump文件和符號文件,將其翻譯為人類可讀的格式

符號文件是程式編譯的產物,裡面會包含函數或數據的名稱、地址、大小、類型等。由於Breakpad是一個跨平臺的方案,因此沒有採用XCode編譯產生的符號表文件,而是使用了自定義的格式。minidump則是一種微軟開發的文件格式,它被用在微軟的崩潰上傳體系中,包含了可執行文件和共用庫的列表、進程中的各線程列表信息、調用棧信息等。

Breakpad如何解決上述問題

1.如何安全分配記憶體

以下為Breakpad啟動代碼:

ProtectedMemoryAllocator* gMasterAllocator = NULL;
ProtectedMemoryAllocator* gKeyValueAllocator = NULL;
ProtectedMemoryAllocator* gBreakpadAllocator = NULL;

BreakpadRef BreakpadCreate(NSDictionary* parameters) {
  try {
    gMasterAllocator =
        new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);

    gKeyValueAllocator =
        new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
            ProtectedMemoryAllocator(sizeof(LongStringDictionary));
   
    int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
    if (mutexResult == 0) {
      int breakpad_pool_size = 4096;

      gBreakpadAllocator =
          new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
              ProtectedMemoryAllocator(breakpad_pool_size);

      NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
      Breakpad* breakpad = Breakpad::Create(parameters);

      if (breakpad) {
        gMasterAllocator->Protect();
        gKeyValueAllocator->Protect();
        gBreakpadAllocator->Protect();

        [pool release];
        return (BreakpadRef)breakpad;
      }

      [pool release];
    }
  } catch(...) { 
    fprintf(stderr, "BreakpadCreate() : error\n");
  }
  ...
}

上述代碼片段已經包含了對記憶體分配問題的解決,其 核心思路是:既然崩潰時無法分配記憶體,那麼只要在崩潰前提前分配好崩潰處理程式所需的記憶體並將其保護起來避免被崩潰所破壞即可

ProtectedMemoryAllocator這個類相當於一個記憶體池,它允許分配記憶體,但是分配記憶體無法被回收。此外,它還提供了一個Protect()方法用於將記憶體池設置為只讀,這樣一來這塊記憶體就不會在崩潰發生的時候被各種原因覆蓋。

通過源碼,我們可以一窺其實現的原理,首先是構造函數:

ProtectedMemoryAllocator::ProtectedMemoryAllocator(vm_size_t pool_size) 
  : pool_size_(pool_size),
    next_alloc_offset_(0),
    valid_(false) {
  
  kern_return_t result = vm_allocate(mach_task_self(),
                                     &base_address_,
                                     pool_size,
                                     TRUE
                                     );
  
  valid_ = (result == KERN_SUCCESS);
  assert(valid_);
}

vm_allocate是一個內核函數,用於申請虛擬記憶體,由於Breakpad需要直接申請一塊較大的記憶體,用於整個模塊的記憶體使用,因此它直接使用了該函數,而不是malloc。該類申請的記憶體大小是由參數pool_size決定的,記憶體分配之後,base_address_指向記憶體池的起始地址。

再看看Protect()方法的實現:

kern_return_t  ProtectedMemoryAllocator::Protect() {
  kern_return_t result = vm_protect(mach_task_self(),
                                    base_address_,
                                    pool_size_,
                                    FALSE,
                                    VM_PROT_READ);
  
  return result;
}

其同樣調用了內核函數vm_protect,將申請的虛擬記憶體設置為只讀,這樣就實現了記憶體的保護。

2.如何獲取調用棧信息

Breakpad內部有一個MinidumpGenerator類專門用於寫入minidump,其中包括了我們關心的線程調用棧信息。由於涉及到minidump格式問題,我們不深入分析這個類,只是簡單介紹下原理。

首先,我們需要理解線程調用棧的結構:

線程的調用棧分為若幹棧幀(stack frame),每個棧幀對應一個函數調用。上圖包含了兩個棧幀,DrawLine和DrawSquare。

棧幀主要由三部分組成:函數參數、返回地址、幀內的本地變數。上述DrawSquare函數調用DrawLine函數的時候,首先函數的參數入棧,然後把返回地址入棧,最後是函數內部本地變數。

這裡要註意的是,有兩個特殊的指針:Stack Pointer指向了調用棧的棧頂,Frame Pointer則指向了當前棧幀。

此外,我們還需要瞭解一下iOS系統中虛擬記憶體的相關知識:

我們知道,操作系統會對虛擬記憶體進行分頁。在iOS系統中,為了更好的管理記憶體頁,系統會將一組連續的記憶體頁關聯到一個VMObject上,也稱為VM Region。我們可以通過XCode的Instruments工具,查看當前App的虛擬記憶體分配情況,其中就包含了VM Region的相關信息:

可以看到,VM Region被分為不同的Category,其中有一種Category叫做VM Stack,其包含的就是線程調用棧的信息。

為了獲取VM Stack中的信息,Breakpad大致做了以下操作:

  1. 通過內核函數task_threads獲取當前進程的所有線程

  2. 通過內核函數thread_get_state獲取目標線程的thread_state_t,這個結構中包含了該線程調用棧的棧頂指針Stack Point

    _STRUCT_ARM_THREAD_STATE64
    {
    	__uint64_t __x[29]; /* General purpose registers x0-x28 */
    	__uint64_t __fp;    /* Frame pointer x29 */
    	__uint64_t __lr;    /* Link register x30 */
    	__uint64_t __sp;    /* Stack pointer x31 */
    	__uint64_t __pc;    /* Program counter */
    	__uint32_t __cpsr;  /* Current program status register */
    	__uint32_t __pad;   /* Same size for 32-bit or 64-bit clients */
    };
    
  3. 由Stack Pointer的地址作為起始地址,獲取下一個VM Region,如果其Category為VM Stack,將此塊記憶體的信息記錄下來,寫入minidump


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

-Advertisement-
Play Games
更多相關文章
  • MySQL安裝 官方: https://www.mysql.com/ MySQL官方提供了兩種不同的版本: 社區版本(MySQL Community Server) 免費, MySQL不提供任何技術支持 商業版本(MySQL Enterprise Edition) 收費,可以使用30天,官方提供技術 ...
  • 本次按照目前最新版本Sqlserver2022進行記錄 先決條件 任何受支持的 Linux 發行版上的 Docker 引擎 1.8 及更高版本。 有關詳細信息,請參閱 Install Docker(安裝 Docker)。 有關硬體要求和處理器支持的詳細信息,請參閱SQL Server 2022:硬體 ...
  • 運算符用於執行程式代碼運算,會針對一個以上操作數項目來進行運算。 考慮以下計算: 7 + 5 = 12 以上實例中 7、5 和 12 是操作數。 運算符 + 用於加值。 運算符 = 用於賦值。 TypeScript 主要包含以下幾種運算: 算術運算符 邏輯運算符 關係運算符 按位運算符 賦值運算符 ...
  • Android Studio製作簡單登錄界面 應用線性佈局設計登錄界面,要求點擊輸入學號時彈出數字鍵盤界面,點擊輸入密碼時彈出字母鍵盤,出現的文字、數字、尺寸等全部在values文件夾下相應.xml文件中設置好,使用時直接引用。當用戶名或密碼為空,顯示一個提示信息“用戶名與密碼不能為空!”,當用戶名... ...
  • 一、Tabs Tabs組件是一種常見的用戶界面(UI)組件,它是一個可以容納多個選項卡的容器組件。每個選項卡通常包含一個面板和一個標簽,用戶可以通過點擊標簽來切換面板。Tabs組件通常用於展示多個相關但又不需要同時展示的數據集合或功能集合,以提高頁面的可維護性和可用性。 Tabs組件的主要功能包 ...
  • 變數是一種使用方便的占位符,用於引用電腦記憶體地址。 我們可以把變數看做存儲數據的容器。 TypeScript 變數的命名規則: 變數名稱可以包含數字和字母。 除了下劃線 _ 和美元 $ 符號外,不能包含其他特殊字元,包括空格。 變數名不能以數字開頭。 變數使用前必須先聲明,我們可以使用 var 來 ...
  • 一、Navigation Navigation組件通常作為頁面的根容器,支持單頁面、分欄和自適應三種顯示模式。開發者可以使用Navigation組件提供的屬性來設置頁面的標題欄、工具欄、導航欄等。 在Navigation組件中,頁面分為主頁和內容頁。主頁由標題欄、內容區和工具欄組成,並且可以使用 ...
  • TypeScript 包含的數據類型如下表: 註意: TypeScript 和 JavaScript 沒有整數類型。 Any 類型 任意值是 TypeScript 針對編程時類型不明確的變數使用的一種數據類型,它常用於以下三種情況。 1、變數的值會動態改變時,比如來自用戶的輸入,任意值類型可以讓這些 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...