Swift: loading data from url causes semaphore_wait_trap to freeze

In my application, clicking a button allows me to download data from an internet site. The site is a list of links containing binary data. Sometimes the first link may not contain the correct data. In this case, the application takes the next reference in the array and gets data from there. The links are correct.

The problem is that often (not always) the app freezes for a few seconds when I press the button. After 5-30 seconds, it defrosts and loads the instruments normally. I understand something is blocking the main thread. When you stop the process in xCode I get this (marked by semaphore_wait_trap):

enter image description here

This is how I do it:

// 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;
    }

      

I've been trying to fix this for the past few weeks, but to no avail. Any help would be much appreciated!

+3


source to share


2 answers


The stack trace tells you what it stops at String(contentsOf:)

, gets called readWebToString

, gets called makeGribWebAddress

.

The problem is what String(contentsOf:)

makes a synchronous network request. If this request takes some time, it will block this thread. And if you call this from the main thread, your application might hang.



In theory, you could just send this process to the background, but that just hides a deeper problem: you are making a network request with a synchronous, non-invalidate API and you are not providing meaningful error reporting.

You should really be making asynchronous requests from URLSession

, as elsewhere. Avoid using String(contentsOf:)

with a remote url.

+2


source


enter image description here

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

      

You are calling String(contentsOf: url)

on the main thread (main queue). This loads the content of the url into the string synchronously. The main thread is used to control the user interface, running synchronous network code will freeze the user interface. It's a big no, no .

You don't have to call readWebToString()

in the main queue. Execution DispatchQueue.main.async { self.downloadWindsAloftData() }

precisely puts the block on the main queue, which we should avoid. ( async

just means "do it later", it runs on anyway Dispatch.main

.)



You should just run downloadWindsAloftData

on the global queue instead of the main queue

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

      

Run DispatchQueue.main.async

if you want to update the interface.

+3


source







All Articles