iOS apprentice中文版 Chapter 35: 異步聯(lián)網(wǎng) Asynchronous networking

你的應用程序可以進行網(wǎng)絡搜索,運行得很好铃拇。同步網(wǎng)絡調(diào)用并沒有那么糟糕钞瀑,不是嗎?
是的,我來告訴你為什么!你有沒有注意到慷荔,每當你進行搜索時雕什,應用程序就會變得沒有響應?當網(wǎng)絡請求發(fā)生時,您不能向上或向下滾動表視圖,也不能在搜索欄中鍵入任何新內(nèi)容监徘。應用程序完全凍結了幾秒鐘晋修。

如果你的網(wǎng)絡連接非常快凰盔,你可能沒有見過這種情況墓卦,但如果你在野外使用iPhone,網(wǎng)絡速度會比家里或辦公室的Wi-Fi慢得多户敬,搜索很容易就會花費10秒甚至更多時間落剪。

對于大多數(shù)用戶來說,沒有響應的應用程序就是崩潰的應用程序尿庐。用戶可能會按下home鍵再試一次——或者更有可能的情況是忠怖,刪除你的應用程序部逮,在應用程序商店給它打個差評游沿,然后切換到一個與之競爭的應用程序吹埠。
因此寂曹,在本章中蹭越,您將學習如何使用異步網(wǎng)絡來解決UI響應問題怔接。你可以這樣做:

  1. 極端同步網(wǎng)絡: 了解同步網(wǎng)絡如何通過將同步網(wǎng)絡調(diào)到最大來影響應用程序的性能盘寡。
  2. 活動指示器:添加一個活動指示器來顯示正在進行的搜索蚌父,以便用戶知道正在發(fā)生什么惹资。
  3. 異步:更改web服務請求在后臺線程上運行的代碼贺纲,這樣它就不會鎖定應用程序。

極端同步網(wǎng)絡

還不相信同步網(wǎng)絡的弊端嗎?”讓我們放慢網(wǎng)絡連接速度褪测,假裝這個應用程序運行在iPhone上猴誊,而某人可能在公交車或火車上使用這個應用程序,而不是在快速家庭或辦公室網(wǎng)絡的理想條件下侮措。
首先懈叹,您將增加應用程序接收的數(shù)據(jù)量——通過向URL添加一個“l(fā)imit”參數(shù),您可以設置web服務將返回的結果的最大數(shù)量萝毛。默認值是50项阴,最大值是200。

?打開SearchViewController.swift并在iTunesURL(searchText:)中笆包,將web服務URL更改為:

let urlString = String(format: 
  "https://itunes.apple.com/search?term=%@&limit=200", 
  encodedText)

您向URL添加&limit=200环揽。正如您所知道的,url中的參數(shù)由&符號分隔庵佣,也稱為“and”或“& and”符號歉胶。
如果你現(xiàn)在運行這個應用程序,搜索應該會慢一些巴粪。

網(wǎng)絡連結調(diào)節(jié)器(The network link conditioner)

還是太快了通今,看不到任何應用程序響應問題?那就使用網(wǎng)絡鏈接調(diào)節(jié)器network link conditioner粥谬。這是蘋果提供的一個額外的開發(fā)工具,它允許你模擬不同的網(wǎng)絡條件辫塌,比如糟糕的手機網(wǎng)絡漏策,以便測試你的iOS應用程序。
但首先臼氨,在使用它之前掺喻,您可能必須安裝Network Link Conditioner,因為這不是默認安裝的東西储矩,既不是macOS的一部分感耙,也不是Xcode安裝的一部分。

?從Xcode菜單中選擇Open Developer Tool→More Developer Tool


這應該會在你的默認瀏覽器中打開蘋果開發(fā)者的下載網(wǎng)頁——你可能會被要求先登錄蘋果開發(fā)者門戶持隧,因為這是一個只有注冊的蘋果開發(fā)者才能使用的資源即硼。


正如截圖所示,搜索“additional tools”屡拨。您應該得到一個不同下載的列表只酥。根據(jù)發(fā)布日期選擇最新版本,下載洁仗,打開DMG文件层皱,切換到DMG上的硬件文件夾,然后雙擊Network Link Conditioner.prefPane來安裝它赠潦。
現(xiàn)在可以使用Network Link Conditioner作為系統(tǒng)首選項面板選項。



Let’s simulate a really slow connection.
? Click on Manage Profiles and create a new profile by clicking the plus button on the bottom left. Add the following settings:
Name: Very slow connection
Downlink Bandwidth: 48 Kbps
Downlink Packets Dropped: 0 %
Downlink Delay: 5000 ms, i.e. 5 seconds

Press OK to add this profile and return to the main screen. Make sure this new profile is selected and flick the switch to ON to start the Network Link Conditioner.
? Now run the app and search for something. The Network Link Conditioner tool will delay the HTTP request by 5 seconds in order to simulate a slow connection, and then downloads the data very slowly.

Tip: If the download still appears very fast, then try searching for some term you haven’t used before; the system may be caching the results from a previous search.

Notice how the app totally doesn’t respond during this time? It feels like something is wrong. Did the app crash or is it still doing something? It’s impossible to tell and very confusing to your users when this happens.
Even worse, if your program is unresponsive for too long, iOS may actually force kill it, in which case it really does crash. You don’t want that to happen!
“Ah,” you say, “l(fā)et’s show some type of animation to let the user know that the app is communicating with a server. Then at least they will know that the app is busy.”
That sounds like a decent thing to do, so let’s get to it.

Tip: Even better than pretending to have a lousy connection on the Simulator is to use Network Link Conditioner on your device, so you can also test bad network connections on your actual iPhone. You can find it under Settings → Developer → Network Link Conditioner. Using these tools to test whether your app can deal with real-world network conditions is a must! Not every user has the luxury of broadband…
Also, if you do not see the Developer option in the iOS Settings app, you might need to connect your iPhone to your computer via a USB cable, and launch the Xcode Devices and Simulators window so that your device is recognized by Xcode as a developer device.

活動指標(The activity indicator)

你之前在MyLocations中使用過旋轉(zhuǎn)活動指示器草冈,向用戶顯示應用程序正在忙她奥。讓我們創(chuàng)建一個新的表格視圖單元格,在應用程序查詢iTunes商店時顯示怎棱。它會是這樣的:


活動指示表視圖單元格
創(chuàng)建一個新的空nib文件哩俭。稱之為LoadingCell.xib。
?將一個新的TableViewCell拖放到畫布上拳恋。設置寬度為320點凡资,高度為80點。
?將單元格的重用標識符設置為LoadingCell谬运,并將Selection屬性設置為None隙赁。
?拖拽一個新的Label到單元格中。將標題設置為Loading…并將字體更改為System 15梆暖。標簽的文本顏色應該是50%不透明的黑色伞访。
?將一個新的Activity Indicator View拖拽到單元格中,并將其放在標簽旁邊轰驳。把它的Style設為Gray厚掷,給它打上100的tag弟灼。


要使此單元格在較大的屏幕上正常工作,您將添加一些約束冒黑,以保持標簽和活動微調(diào)器位于單元格的中心田绑。最簡單的方法是將這兩項放到容器視圖中并居中

?選擇Label和Activity Indicator View——按住?選擇多個項目。從Xcode菜單欄中抡爹,選擇Editor→Embed In→View Without Inset掩驱。這將在選定的視圖后面放置一個白色視圖。


注意:如果你想知道在 Editor菜單中Embed In → View 和Embed In → View Without Inset之間有什么不同豁延,試試它昙篙,你應該會看到發(fā)生了什么。第一個選項添加了一個比它所包含的項稍大一些的視圖诱咏,因為它已經(jīng)嵌入了新視圖苔可,以便在所包含的項周圍添加一些填充。第二個選項袋狞,就是你用的那個焚辅,簡單地把所有的東西都包起來,不加任何標簽苟鸯。

選中這個新的容器視圖后同蜻,單擊Align按鈕,復選標記Horizontally in Container與Vertically in Container以創(chuàng)建新的約束早处。


最終會出現(xiàn)一些紅色約束湾蔓。這是沒有好;我們想看藍色的。你的新約束是紅色的原因是自動布局不知道這個容器視圖應該有多大;您只是為視圖的位置添加了約束砌梆,而不是它的大小默责。
要解決這個問題,您還需要向標簽和活動指示器添加約束咸包,這樣容器視圖的寬度和高度就由其中兩個元素的大小決定桃序。

這對于以后你要把應用翻譯成另一種語言時尤其重要。如果Loading……文本變大或變小烂瘫,那么容器視圖也應該變大或變小媒熊,以便保持單元格內(nèi)的居中。

?選擇標簽并點擊Add New Constraints按鈕坟比。只需將它釘在所有四個邊芦鳍,并按下添加4約束。

?在活動指示器視圖中重復這個動作温算。您不需要將它固定在左邊怜校,因為這個約束已經(jīng)存在——將標簽固定在左邊添加了它。
現(xiàn)在標簽和活動指示器的約束應該都是藍色的注竿。
此時茄茁,容器視圖可能仍然有橙色的線魂贬,表示約束沒有問題,但是視圖的框架沒有處于適當?shù)奈恢萌雇纭H绻歉对铮x擇它并選擇“Editor → Resolve Auto Layout Issues → Update Frames - under Selected Views。這將把容器視圖移動到它的約束所指定的位置愈犹。
酷键科,你現(xiàn)在有了一個可以自動調(diào)整自身大小的屏幕。

使用活動指示Cell

要使這個特殊的表視圖單元格出現(xiàn)漩怎,您將按照與“Nothing Found”單元格相同的步驟勋颖。
?將下面的一行添加到SearchViewController.swift的TableView.Cellidentifier結構中。

static let loadingCell = "LoadingCell

?并在viewDidLoad()中注冊nib:

cellNib = UINib(nibName: TableView.CellIdentifiers.loadingCell, 
                bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: 
                   TableView.CellIdentifiers.loadingCell)

現(xiàn)在勋锤,您必須想辦法讓table view的數(shù)據(jù)源知道應用程序目前正處于從服務器下載數(shù)據(jù)的狀態(tài)饭玲。最簡單的方法是添加另一個布爾標志。如果該變量為真叁执,則應用程序正在下載內(nèi)容并顯示新的load . cell;如果變量為false茄厘,則顯示table視圖的常規(guī)內(nèi)容。

?添加一個新的實例變量:

var isLoading = false

? 將tableView(_:numberOfRowsInSection:) 修改為:

func tableView(_ tableView: UITableView, 
               numberOfRowsInSection section: Int) -> Int {
  if isLoading {
    return 1
  } else if !hasSearched {
    . . . 
  } else if . . . 

if isLoading條件返回1谈宛,因為需要一行來顯示單元格次哈。

?將tableView(_:cellForRowAt:)更新如下:

func tableView(_ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  // New code 
  if isLoading {
    let cell = tableView.dequeueReusableCell(withIdentifier: 
        TableView.CellIdentifiers.loadingCell, for: indexPath)
        
    let spinner = cell.viewWithTag(100) as! 
                  UIActivityIndicatorView
    spinner.startAnimating()
    return cell
  } else 
  // End of new code
  if searchResults.count == 0 {
    . . .

您添加了一個if條件來返回新Loading…單元格的實例。它還通過tag查找UIActivityIndicatorView吆录,然后告訴轉(zhuǎn)輪開始動畫窑滞。方法的其余部分保持不變。

?將tableView(_:willSelectRowAt:)改為:

func tableView(_ tableView: UITableView, 
     willSelectRowAt indexPath: IndexPath) -> IndexPath? {
  if searchResults.count == 0 || isLoading {    // Changed
    return nil
  } else {
    return indexPath
  }
}

你在if語句中添加了||恢筝。就像您不希望用戶選擇“Nothing Found”單元格一樣葛假,您也不希望他們選擇“Loading…”單元格,因此在這兩種情況下都返回nil滋恬。
現(xiàn)在只剩下一件事:在向iTunes服務器發(fā)出HTTP請求之前,應該將isLoading設置為true抱究,并重新加載table視圖恢氯,使Loading…cell出現(xiàn)。

?將searchBarSearchButtonClicked(_:)更改為:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  if !searchBar.text!.isEmpty {
    searchBar.resignFirstResponder()
    // New code
    isLoading = true                    
    tableView.reloadData()
    // End of new code
    . . .
    isLoading = false                     // New code
    tableView.reloadData()
  }
}

在執(zhí)行網(wǎng)絡請求之前鼓寺,將isLoading設置為true并重新加載表以顯示活動指示器勋拟。
在請求完成并得到搜索結果之后,將isLoading設置回false并重新加載表以顯示SearchResult對象妈候。
很有道理,對吧?讓我們啟動這個應用程序敢靡,看看它是如何運行的!

測試新的加載單元

運行應用程序并執(zhí)行搜索。當搜索正在進行時苦银,帶有旋轉(zhuǎn)活動指示器的單元格應該出現(xiàn)…
……還是應該? !
可悲的事實是沒有一個轉(zhuǎn)輪可以看到啸胧。在不太可能的情況下它才會出現(xiàn)在你面前赶站,它現(xiàn)在不會旋轉(zhuǎn)——嘗試啟用Network Link Conditioner。

為了說明原因纺念,首先改變searchBarSearchButtonClicked(_:)如下:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  if !searchBar.text!.isEmpty {
    searchBar.resignFirstResponder()
    isLoading = true
    tableView.reloadData()
    /*
       . . . the networking code (commented out) . . . 
     */
  }
}

注意贝椿,您不需要從代碼中刪除任何內(nèi)容——只需在第一次調(diào)用tableView.reloadData()之后注釋掉所有內(nèi)容。

運行應用程序并進行搜索∠萜祝現(xiàn)在活動轉(zhuǎn)輪出現(xiàn)了!
所以至少你知道這部分代碼運行良好烙博。但啟用網(wǎng)絡代碼后,應用程序不僅對用戶的任何輸入完全沒有響應烟逊,而且也不想重繪屏幕渣窜。這是怎么回事?

主線程

舊款iPhone和iPad的CPU(中央處理器)只有一個核心,這意味著它一次只能做一件事宪躯。最近的模型有一個CPU有兩個核心乔宿,這允許同時進行兩個驚人的計算。你的Mac可能有4個內(nèi)核眷唉。
由于可用的內(nèi)核如此之少予颤,為什么現(xiàn)代計算機可以同時運行更多的應用程序和其他進程(我的Mac上現(xiàn)在有287個活動進程)?

為了克服只有一兩個CPU核心的硬件限制,包括iPhone和iPad在內(nèi)的大多數(shù)電腦都采用搶占式的多任務處理和多線程技術冬阳,給人一種可以同時做很多事情的錯覺蛤虐。

多任務處理是發(fā)生在不同應用程序之間的事情。每個應用程序都有自己的進程肝陪,每個進程每秒鐘都有一小部分CPU時間來執(zhí)行其任務驳庭。然后它被暫時停止,或被搶占氯窍,控制權被交給下一個進程饲常。
每個進程包含一個或多個線程。我剛才提到每個進程都有一點CPU時間來完成它的工作狼讨。進程在它的線程之間分割時間贝淤。每個線程通常執(zhí)行自己的工作,并且盡可能獨立于該進程中的其他線程政供。
一個應用程序可以有多個線程播聪,CPU在它們之間切換:


如果你進入Xcode調(diào)試器并暫停應用程序,調(diào)試器會顯示當前哪些線程處于活動狀態(tài)布隔,以及在你停止它們之前它們在做什么离陶。
對于StoreSearch應用程序,顯然在截屏時有六個線程:


大多數(shù)線程都是由iOS自己管理的衅檀,你不必擔心招刨。此外,您可能會看到少于或多于6個線程哀军。然而沉眶,有一個線程需要特別注意:主線程打却。在上面的圖像中,這是線程1沦寂。

主線程是應用程序的初始線程学密,所有其他線程都從這里派生出來。主線程負責處理用戶界面事件传藏,并繪制UI腻暮。應用程序的大多數(shù)活動都是在主線程上進行的。每當用戶點擊應用程序中的按鈕時毯侦,執(zhí)行操作方法的線程就是主線程哭靖。

因為它非常重要,所以您應該小心不要阻塞主線程侈离。如果action方法的運行時間超過幾分之一秒试幽,那么在主線程上執(zhí)行所有這些計算就不是一個好主意,因為這會鎖定主線程卦碾。

當你讓主線程忙于做其他事情時铺坞,它無法處理任何UI事件,如果操作時間太長洲胖,應用程序甚至可能被系統(tǒng)殺死济榨,因此應用程序變得無響應。

在StoreSearch中绿映,您要在主線程上執(zhí)行冗長的網(wǎng)絡操作擒滑。它可能需要許多秒,甚至幾分鐘來完成叉弦。

將isLoading標志設置為true后丐一,告訴tableView重新加載它的數(shù)據(jù),這樣用戶就可以看到旋轉(zhuǎn)動畫淹冰。但這從未實現(xiàn)库车。告訴表視圖重新加載調(diào)度了一個“重繪”事件,但是主線程沒有機會處理該事件樱拴,因為您立即啟動網(wǎng)絡操作凝颇,使主線程長時間處于繁忙狀態(tài)。

這就是為什么當前的同步網(wǎng)絡方法是不好的:永遠不要阻塞主線程疹鳄。這是iOS編程的主要原罪之一!

使其異步

為了防止阻塞主線程,任何可能需要一段時間才能完成的操作都應該是異步的芦岂。這意味著操作發(fā)生在后臺線程中瘪弓,同時主線程可以自由地處理新事件。
這并不是說您應該創(chuàng)建自己的線程禽最。如果你以前在其他平臺上編程過腺怯,你可能會毫無猶豫的創(chuàng)建新線程袱饭,但在iOS上這通常不是最好的解決方案。

你看呛占,線程是很棘手的虑乖。不是線程本身,而是并行地做事情晾虑。我不會在這里詳細介紹疹味,但是一般來說,您希望避免兩個線程同時修改同一塊數(shù)據(jù)的情況帜篇。這可能會導致非常令人驚訝但不是非常令人愉快的結果糙捺。

iOS有幾種更方便的方式啟動后臺進程,而不是自己創(chuàng)建線程笙隙。對于這個應用程序洪灯,您將使用隊列和Grand Central Dispatch (GCD)。GCD大大簡化了需要并行編程的任務竟痰。您已經(jīng)在MyLocations中簡單地使用了GCD签钩,但是現(xiàn)在您將更好地使用它。

簡而言之坏快,GCD有許多具有不同優(yōu)先級的隊列铅檩。要在后臺執(zhí)行作業(yè),可以將作業(yè)放入閉包中假消,然后將該閉包傳遞給隊列柠并,然后忘記它。就這么簡單富拗。

GCD將一個接一個地從隊列中獲取閉包(或它所調(diào)用的“塊”)臼予,并在后臺執(zhí)行它們的代碼。它是如何做到的并不重要啃沪,你只能保證它發(fā)生在某個后臺線程上粘拾。隊列與線程并不完全相同,但是它們使用線程來完成它們的工作创千。


將web請求放在后臺線程中

要使web服務請求異步缰雇,您需要將searchBarSearchButtonClicked(_:)中的網(wǎng)絡部分放到一個閉包中,然后將該閉包放在一個中等優(yōu)先級隊列中追驴。

?將searchBarSearchButtonClicked(_:)修改如下:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  if !searchBar.text!.isEmpty {
    . . .
    searchResults = []
    // Replace all code after this with new code be“l(fā)ow
    // 1
    let queue = DispatchQueue.global()
    let url = self.iTunesURL(searchText: searchBar.text!)
    // 2
    queue.async {
      
      if let data = self.performStoreRequest(with: url) {
        self.searchResults = self.parse(data: data)
        self.searchResults.sort(by: <)
        // 3
        print("DONE!")
        return
      }
    }
  }
}
  • 這將獲取對隊列的引用械哟。您正在使用一個“全局”隊列,這是一個由系統(tǒng)提供的隊列殿雪。你也可以創(chuàng)建你自己的隊列暇咆,但是使用一個標準的隊列對于這個應用來說是可以的。

  • 一旦有了隊列,就可以對它分派閉包——隊列之間的所有內(nèi)容爸业。async{和close}是閉包其骄。閉包中的任何代碼都將放在隊列上,并在后臺異步執(zhí)行扯旷。在調(diào)度此閉包之后拯爽,主線程立即可以繼續(xù)。它不再被阻塞钧忽。

  • 在閉包中毯炮,我刪除了在搜索完成后重新加載表視圖的代碼,以及錯誤處理代碼惰瓜。目前否副,這已經(jīng)被print()語句所取代。這是有原因的崎坊,我們馬上就會講到备禀。首先,讓我們再次嘗試應用程序奈揍。

運行應用程序并進行搜索曲尸。“Loading…”單元格應該是可見的男翰,完成動畫微調(diào)!過了一會兒另患,你應該看到“完成!”消息出現(xiàn)在控制臺中。
當然蛾绎,裝載過程……Cell會一直存在昆箕,因為你還沒有讓它離開。

將UI更新放在主線程上

我從閉包中刪除所有用戶界面代碼——并將搜索URL移到閉包之外——的原因是UIKit有一條規(guī)則租冠,UI代碼應該總是在主線程上執(zhí)行鹏倘。這是很重要的!

從多個線程訪問相同的數(shù)據(jù)會造成各種各樣的痛苦,因此UIKit的設計者決定不允許從其他線程更改UI顽爹。這意味著您不能從這個閉包中重新加載表視圖纤泵,因為它運行在后臺線程(而不是主線程)上的隊列上。

碰巧镜粤,還有一個與主線程相關聯(lián)的“主隊列”捏题。如果您需要從后臺隊列對主線程執(zhí)行任何操作,您可以簡單地創(chuàng)建一個新的閉包并在主線程上調(diào)度主線程操作肉渴。

?將searchBarSearchButtonClicked(_:)中顯示print(“完成!”)的一行替換為:

DispatchQueue.main.async {
  self.isLoading = false
  self.tableView.reloadData()
}

在DispatchQueue.main.async您可以在主隊列上調(diào)度一個新的閉包公荧。這個新的閉包將isLoading設置回false并重新加載表視圖。注意self是必需的同规,因為這段代碼位于閉包中稚矿。
試一試庸诱。有了這些變化,您的網(wǎng)絡代碼就不再占據(jù)主線程晤揣,應用程序就會突然變得響應性更強!

各種各樣的隊列

當處理GCD隊列時,您經(jīng)常會看到這樣的模式:

let queue = DispatchQueue.global()
queue.async {
  // code that needs to run in the background
  
  DispatchQueue.main.async {
    // update the user interface
  }
}

基本上朱灿,當你在后臺線程中工作時昧识,你仍然必須切換到主線程來進行任何用戶界面更新。事情就是這樣盗扒。

還有排隊跪楞。同步,沒有“a”侣灶,它從隊列中獲取下一個閉包并在后臺執(zhí)行它甸祭,但是會讓您等待閉包完成。在某些情況下褥影,這可能很有用池户,但大多數(shù)情況下,您希望使用queue.async凡怎。沒有人喜歡等待!

The main thread checker

我之前提到過校焦,不應該在后臺線程上運行UI代碼。然而统倒,在Xcode 9之前寨典,沒有一種簡單的方法可以發(fā)現(xiàn)在后臺線程上運行的UI代碼,只能通過費力地逐行搜索源代碼來確定哪些代碼在主線程上運行房匆,哪些在后臺線程上運行耸成。

在Xcode 9中,蘋果引入了一個新的診斷設置浴鸿,名為主線程檢查器井氢,如果后臺線程上運行了任何UI代碼,它會發(fā)出警告赚楚。默認情況下毙沾,這個設置應該是啟用的,但如果沒有啟用宠页,您可以很容易地啟用它——我建議您隨時啟用它左胞,因為它可能非常有價值。

?點擊Xcode工具欄中的scheme下拉菜單举户,選擇Edit scheme……



?在左側(cè)面板中選擇Run烤宙,切換到Diagnostics選項卡,并確保主線程檢查器在運行時API檢查下被選中俭嘁。

現(xiàn)在躺枕,把下面這句話從結束語outside the closure中移開:

let url = self.iTunesURL(searchText: searchBar.text!)

在封閉的內(nèi)部就像這樣:

queue.async {
    let url = self.iTunesURL(searchText: searchBar.text!)
    ...
} 

運行StoreSearch并對一個項目進行搜索,你應該會在Xcode控制臺看到如下內(nèi)容

Main Thread Checker: UI API called on a background thread: -[UISearchBar text]
PID: 12986, TID: 11267540, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 0
Backtrace:
4   StoreSearch                         0x000000010bccfa75 $S11StoreSearch0B14ViewControllerC09searchBarB13ButtonClickedyySo08UISearchF0CFyycfU_ + 469
5   StoreSearch                         0x000000010bcd0101 $S11StoreSearch0B14ViewControllerC09searchBarB13ButtonClickedyySo08UISearchF0CFyycfU_TA + 17
6   StoreSearch                         0x000000010bcd02bd $SIeg_IeyB_TR + 45
7   libdispatch.dylib                   0x000000010f3a1225 _dispatch_call_block_and_release + 12
8   libdispatch.dylib                   0x000000010f3a22e0 _dispatch_client_callout + 8
9   libdispatch.dylib                   0x000000010f3a4d8a _dispatch_queue_override_invoke + 1028
10  libdispatch.dylib                   0x000000010f3b2daa _dispatch_root_queue_drain + 351
11  libdispatch.dylib                   0x000000010f3b375b _dispatch_worker_thread2 + 130
12  libsystem_pthread.dylib             0x000000010f791169 _pthread_wqthread + 1387
13  libsystem_pthread.dylib             0x000000010f790be9 start_wqthread + 13
2018-07-28 11:39:02.726132+0200 StoreSearch[12986:11267540] [reports] Main Thread Checker: UI API called on a background thread: -[UISearchBar text]
PID: 12986, TID: 11267540, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 0
Backtrace:
4   StoreSearch                         0x000000010bccfa75 $S11StoreSearch0B14ViewControllerC09searchBarB13ButtonClickedyySo08UISearchF0CFyycfU_ + 469
5   StoreSearch                         0x000000010bcd0101 $S11StoreSearch0B14ViewControllerC09searchBarB13ButtonClickedyySo08UISearchF0CFyycfU_TA + 17
6   StoreSearch                         0x000000010bcd02bd $SIeg_IeyB_TR +bcd02bd $SIeg_IeyB_TR + 45
7   libdispatch.dylib                   0x000000010f3a1225 _dispatch_call_block_and_release + 12
8   libdispatch.dylib                   0x000000010f3a22e0 _dispatch_client_callout + 8
9   libdispatch.dylib                   0x000000010f3a4d8a _dispatch_queue_override_invoke + 1028
10  libdispatch.dylib                   0x000000010f3b2daa _dispatch_root_queue_drain + 351
11  libdispatch.dylib                   0x000000010f3b375b _dispatch_worker_thread2 + 130
12  libsystem_pthread.dylib             0x000000010f791169 _pthread_wqthread + 1387
13  libsystem_pthread.dylib             0x000000010f790be9 start_wqthread + 13

你可能還注意到Xcode工具欄的activity視圖現(xiàn)在有一個紫色的圖標,在跳轉(zhuǎn)欄的右下角有一個紫色的圖標拐云,通常會顯示錯誤罢猪。


如果你點擊activity視圖中的圖標,你將被帶到問題導航器的Runtime選項卡叉瘩,在那里你可以點擊列出的問題膳帕,然后被帶到源代碼中有問題的那一行



你終于看到問題出在哪里了——你從后臺線程的UI控件搜索欄訪問數(shù)據(jù)。在主線程中這樣做可能更好薇缅。由于我們創(chuàng)建這個問題是為了演示后臺線程檢查器危彩,所以修復很簡單,只需將代碼行移回原來的位置:]

提交代碼

我認為有了這個重要的改進泳桦,應用程序應該有一個新的版本號汤徽。因此,提交更改并為v0.2創(chuàng)建一個tag灸撰。您必須按照兩個單獨的步驟來完成此操作——首先創(chuàng)建一個包含適當消息的提交谒府,然后為您的最新提交創(chuàng)建一個tag。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梧奢,一起剝皮案震驚了整個濱河市狱掂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亲轨,老刑警劉巖趋惨,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惦蚊,居然都是意外死亡器虾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門蹦锋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兆沙,“玉大人,你說我怎么就攤上這事莉掂「鹌裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵憎妙,是天一觀的道長库正。 經(jīng)常有香客問我,道長厘唾,這世上最難降的妖魔是什么褥符? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮抚垃,結果婚禮上喷楣,老公的妹妹穿的比我還像新娘趟大。我一直安慰自己,他們只是感情好铣焊,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布逊朽。 她就那樣靜靜地躺著,像睡著了一般曲伊。 火紅的嫁衣襯著肌膚如雪惋耙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天熊昌,我揣著相機與錄音,去河邊找鬼湿酸。 笑死婿屹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的推溃。 我是一名探鬼主播昂利,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼铁坎!你這毒婦竟也來了蜂奸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤硬萍,失蹤者是張志新(化名)和其女友劉穎扩所,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朴乖,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡祖屏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了买羞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁勺。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖畜普,靈堂內(nèi)的尸體忽然破棺而出期丰,到底是詐尸還是另有隱情,我是刑警寧澤吃挑,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布钝荡,位于F島的核電站,受9級特大地震影響儒鹿,放射性物質(zhì)發(fā)生泄漏化撕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一约炎、第九天 我趴在偏房一處隱蔽的房頂上張望植阴。 院中可真熱鬧蟹瘾,春花似錦、人聲如沸掠手。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喷鸽。三九已至众雷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間做祝,已是汗流浹背砾省。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留混槐,地道東北人编兄。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像声登,于是被迫代替她去往敵國和親狠鸳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.設計模式是什么? 你知道哪些設計模式脯厨,并簡要敘述铅祸?設計模式是一種編碼經(jīng)驗,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,151評論 0 12
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,101評論 1 32
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時俄认,會觸發(fā)此異常个少。 O...
    我想起個好名字閱讀 5,312評論 0 9
  • 翻譯自“Collection View Programming Guide for iOS” 0 關于iOS集合視...
    lakerszhy閱讀 3,861評論 1 22
  • 難過,其實并沒有那么堅不可摧眯杏,沒有那么偉大夜焦,一次一次麻木感被擊敗,看著身邊的朋友一個一個脫單岂贩,朋友之間的親密感一點...
    mynbx閱讀 164評論 0 0