2017-04-01 45 views
3

在我的應用程序中,按鈕觸擊從Internet站點下載數據。該網站是包含二進制數據的鏈接列表。有時,第一個鏈接可能不包含正確的數據。在這種情況下,應用程序將獲取數組中的下一個鏈接並從中獲取數據。鏈接是正確的。Swift:從url下載數據會導致semaphore_wait_trap凍結

我遇到的問題是,當我點擊按鈕時,應用程序頻繁(並非總是)會凍結數秒。 5-30秒後,正常解凍並下載工具。我明白,有些東西阻止了主線程。當停止在Xcode的過程中,我得到這個(semaphore_wait_trap指出):

enter image description here

這是我要做的事:

// Button Action 
@IBAction func downloadWindNoaa(_ sender: UIButton) 
    { 
      // Starts activity indicator 
      startActivityIndicator() 

      // Starts downloading and processing data 

      // Either use this 
      DispatchQueue.global(qos: .default).async 
       { 
        DispatchQueue.main.async 
         { 
          self.downloadWindsAloftData() 
         } 
       } 


      // Or this - no difference. 
      //downloadWindsAloftData() 
     } 
    } 

func downloadWindsAloftData() 
    { 
     // Creates a list of website addresses to request data: CHECKED. 
     self.listOfLinks = makeGribWebAddress() 

     // Extract and save the data 
     saveGribFile() 
    } 

// This downloads the data and saves it in a required format. I suspect, this is the culprit 

    func saveGribFile() 
    { 
     // Check if the links have been created 
     if (!self.listOfLinks.isEmpty) 
     { 
      /// Instance of OperationQueue 
      queue = OperationQueue() 

      // Convert array of Strings to array of URL links 
      let urls = self.listOfLinks.map { URL(string: $0)! } 

      guard self.urlIndex != urls.count else 
      { 
       NSLog("report failure") 
       return 
      } 

      // Current link 
      let url = urls[self.urlIndex] 

      // Increment the url index 
      self.urlIndex += 1 

      // Add operation to the queue 
      queue.addOperation {() -> Void in 

       // Variables for Request, Queue, and Error 
       let request = URLRequest(url: url) 
       let session = URLSession.shared 

       // Array of bytes that will hold the data 
       var dataReceived = [UInt8]() 

       // Read data 
       let task = session.dataTask(with: request) {(data, response, error) -> Void in 

        if error != nil 
        { 
         print("Request transport error") 
        } 
        else 
        { 
         let response = response as! HTTPURLResponse 
         let data = data! 

         if response.statusCode == 200 
         { 
          //Converting data to String 
          dataReceived = [UInt8](data) 
         } 
         else 
         { 
          print("Request server-side error") 
         } 
        } 

        // Main thread 
        OperationQueue.main.addOperation(
         { 
          // If downloaded data is less than 2 KB in size, repeat the operation 
          if dataReceived.count <= 2000 
          { 
           self.saveGribFile() 
          } 

          else 
          { 
           self.setWindsAloftDataFromGrib(gribData: dataReceived) 

           // Reset the URL Index back to 0 
           self.urlIndex = 0 
          } 
         } 
        ) 
       } 
       task.resume() 
      } 
     } 
    } 


// Processing data further 
func setWindsAloftDataFromGrib(gribData: [UInt8]) 
    { 
     // Stops spinning activity indicator 
     stopActivityIndicator() 

     // Other code to process data... 
    } 

// Makes Web Address 

let GRIB_URL = "http://xxxxxxxxxx" 

func makeGribWebAddress() -> [String] 
    { 
     var finalResult = [String]() 

     // Main address site 
     let address1 = "http://xxxxxxxx" 

     // Address part with type of data 
     let address2 = "file=gfs.t"; 
     let address4 = "z.pgrb2.1p00.anl&lev_250_mb=on&lev_450_mb=on&lev_700_mb=on&var_TMP=on&var_UGRD=on&var_VGRD=on" 

     let leftlon = "0" 
     let rightlon = "359" 
     let toplat = "90" 
     let bottomlat = "-90" 

     // Address part with coordinates 
     let address5 = "&leftlon="+leftlon+"&rightlon="+rightlon+"&toplat="+toplat+"&bottomlat="+bottomlat 

     // Vector that includes all Grib files available for download 
     let listOfFiles = readWebToString() 

     if (!listOfFiles.isEmpty) 
     { 
      for i in 0..<listOfFiles.count 
      { 
       // Part of the link that includes the file 
       let address6 = "&dir=%2F"+listOfFiles[i] 

       // Extract time: last 2 characters 
       let address3 = listOfFiles[i].substring(from:listOfFiles[i].index(listOfFiles[i].endIndex, offsetBy: -2)) 

       // Make the link 
       let addressFull = (address1 + address2 + address3 + address4 + address5 + address6).trimmingCharacters(in: .whitespacesAndNewlines) 

       finalResult.append(addressFull) 
      } 
     } 

     return finalResult; 
    } 


func readWebToString() -> [String] 
    { 
     // Final array to return 
     var finalResult = [String]() 

     guard let dataURL = NSURL(string: self.GRIB_URL) 
      else 
     { 
      print("IGAGribReader error: No URL identified") 
      return [] 
     } 

     do 
     { 
      // Get contents of the page 
      let contents = try String(contentsOf: dataURL as URL) 

      // Regular expression 
      let expression : String = ">gfs\\.\\d+<" 
      let range = NSRange(location: 0, length: contents.characters.count) 

      do 
      { 
       // Match the URL content with regex expression 
       let regex = try NSRegularExpression(pattern: expression, options: NSRegularExpression.Options.caseInsensitive) 
       let contentsNS = contents as NSString 
       let matches = regex.matches(in: contents, options: [], range: range) 

       for match in matches 
       { 
        for i in 0..<match.numberOfRanges 
        { 
         let resultingNS = contentsNS.substring(with: (match.rangeAt(i))) as String 
         finalResult.append(resultingNS) 
        } 
       } 

       // Remove "<" and ">" from the strings 
       if (!finalResult.isEmpty) 
       { 
        for i in 0..<finalResult.count 
        { 
         finalResult[i].remove(at: finalResult[i].startIndex) 
         finalResult[i].remove(at: finalResult[i].index(before: finalResult[i].endIndex)) 
        } 
       } 
      } 
      catch 
      { 
       print("IGAGribReader error: No regex match") 
      } 

     } 
     catch 
     { 
      print("IGAGribReader error: URL content is not read") 
     } 


     return finalResult; 
    } 

我一直在試圖解決這個問題,在過去幾個星期但徒勞無益。任何幫助將非常感激!

+1

無關,這裏使用操作隊列是完全不必要的,效率低下。您正在爲每個請求新的隊列(這是奇怪的......那是什麼,只會曾經有一個操作?「排隊」的目的),然後加入網絡請求的起點(但響應的不接收並處理它)到該隊列。我建議完全丟失這個操作隊列的東西。調用'downloadWindsAloftData',異步方法本身的嵌套的調度也很奇怪。 – Rob

+0

Rob。對不起,添加了代碼。我覺得沒有隊列,我沒有設法執行我的任務,如果沒有加載正確的數據,這將進入下一個鏈接。你會建議什麼? –

+1

這裏的自定義操作隊列代碼應該被刪除。只需創建你的'URLSessionTask'和'resume',它的完成處理程序就會在後臺線程上異步運行。而你在'OperationQueue.main.addOperation {...}的位置,你應該只做'DispatchQueue.main.async {...}'。如果您的異步代碼有問題,請創建一個簡單的[MCVE](http://stackoverflow.com/help/mcve)並針對該主題創建一個新問題。 – Rob

回答

2

你的堆棧跟蹤告訴你它正在停止在String(contentsOf:),由readWebToString調用,調用makeGribWebAddress

問題是String(contentsOf:)執行同步網絡請求。如果該請求需要一段時間,它將阻止該線程。如果您從主線程調用此應用程序,您的應用程序可能會凍結。

從理論上講,您可以將該進程調度到後臺隊列,但這只是隱藏了更深層次的問題,您正在使用同步的,不可取消的API進行網絡請求,並且不提供有意義的錯誤報告。

你真的應該和URLSession一樣做異步請求,就像你在其他地方一樣。避免使用帶有遠程URL的String(contentsOf:)

+0

非常感謝!救了我! )) –

3

enter image description here

 let contents = try String(contentsOf: dataURL as URL) 

您呼叫的主線程(主隊列)上String(contentsOf: url)。這將URL的內容同步下載到字符串中主線程用於驅動UI,運行同步網絡代碼將凍結UI。 This is a big no-no

您不應該在主隊列中調用readWebToString()。做DispatchQueue.main.async { self.downloadWindsAloftData() }準確地把塊放在我們應該避免的主隊列中。 (async只是意味着「執行這個後來的」,它仍然在Dispatch.main執行。)

你應該只在全局隊列運行downloadWindsAloftData代替主隊列

DispatchQueue.global(qos: .default).async { 
     self.downloadWindsAloftData() 
    } 

的僅運行DispatchQueue.main.async當你想更新UI。

+0

這工作。非常感謝! –