2017-04-01 6 views
3

私のアプリケーションでは、ボタンをタップするとインターネットサイトからデータがダウンロードされます。このサイトは、バイナリデータを含むリンクのリストです。場合によっては、最初のリンクに適切なデータが含まれていないことがあります。この場合、アプリケーションは配列内の次のリンクを取得し、そこからデータを取得します。リンクは正しいです。Swift:urlからデータをダウンロードするとsemaphore_wait_trap freezeが発生する

私が持っている問題は、ボタンをタップするとアプリケーションが頻繁に(常にそうとは限りませんが)数秒間フリーズすることです。 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

ここでは、操作キューの使用は完全に不要であり、非効率的です。あなたは各要求(それは奇妙です...新しいキューを作成しています... "キュー"の目的は1つの操作しかありませんか?)、ネットワーク要求の開始を追加します。およびその処理)をそのキューに追加します。私はその操作キューのものを完全に失うことをお勧めします。 'downloadWindsAloftData'への呼び出しのネストされたディスパッチは、非同期メソッド自体でも非常に奇妙です。 – Rob

+0

Rob。申し訳ありません、コードが追加されました。キューがなければ、私は自分のタスクを実装することはできませんでした。これは、適切なデータがロードされていない場合、次のリンクに行きます。何をお勧めしますか? –

+1

ここでカスタム操作キューコードを削除するだけです。あなたの 'URLSessionTask'を作成し、' resume'するだけです。完了ハンドラはあなたのバックグラウンドスレッドで非同期に実行されます。そして、あなたが 'OperationQueue.main.addOperation {...}'を実行するところでは 'DispatchQueue.main.async {...}'だけを実行するべきです。非同期コードに問題がある場合は、シンプルな[MCVE](http://stackoverflow.com/help/mcve)を作成し、そのトピックに関する新しい質問を作成してください。 – Rob

答えて

2

スタックトレースは、readWebToStringで呼び出され、makeGribWebAddressによって呼び出され、String(contentsOf:)で停止していることを示しています。

問題は、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上で実行されます。)

あなただけ更新したい場合にのみDispatchQueue.main.asyncを実行

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

代わりにメインキューのグローバルキューにdownloadWindsAloftDataを実行する必要がありますUI。

+0

これは機能します。どうもありがとうございます! –

関連する問題