2017-04-01 136 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

这工作。非常感谢! –