0

[EDIT]: I think I have solved the lockup issue. While refactoring the didSelectRowAt prep for exit to be a blocking task in the ping async queue, I noticed that successful transitions would consist of one instantaneous move to rootVC followed by another animated transition to rootVC again. Made me realize I had two segues from my tableVC: one being the generic controller to controller segue (which is the intended segue method) and one from selecting the table cell.

I am unsure whether it was just the removal of the extra segue or the combination of multiple attempted solutions AND the removal of the segue that solved the problem. My guess is the latter as memory tells me that there was a time where there was only one segue and "fixes" weren't working then either.

tl;dr: Make sure you have no competing segues and that your async code is thread safe/has proper clean up.

[PROBLEM]:

I have a TableViewController which is a child VC of a rootVC under a Navigation Controller. In other words NavController[rootVC, tableVC].

The app starts in rootVC and is segued into tableVC through a UIButton. At tableVC's viewDidLoad, a network scan is done if no previous scan was completed before. Devices on the network are pinged at a certain port asynchronously as they are discovered. If these pings are successful, the IP Address associated with the device is used to populate a cell in the Table View. If this cell is selected, some app settings are changed through didSelectRowAt indexPath and an unwind segue is done to return to rootVC. viewWillDisappear is utilized as well to pass on values through isMovingFromParentViewController (previously done through prepare for segue but error was also present). The user is able to select the IP Address cell as soon as it appears in the table view, which is intended design.

That functionality all works. Most of the time. However, the app locks up SOMETIMES after a cell is selected WHEN a cell is selected while scanning is not done (this would mean that there can still be async pings happening/queued). I have a print at the beginning of didSelectAtRow which never happens when the lock up occurs. I also have an activity indicator view in the table view, which continues spinning when everything else is unresponsive. There is no crash or break from Xcode's side. It seems like that didSelectAtRow just does not happen and cannot proceed for some reason.

This is how my didSelectAtRow looks like

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Cell was selected")
    if possibleDevices.count != 0 {
        selectedTableCellAddress = possibleDevices[indexPath.row]
        let defaults = UserDefaults.standard
        // Set the current cell's IP Address to the setting's IP Address
        if selectedTableCellAddress != nil {
            pingOperationsShouldStop = true
            lanScanner.stop()
            defaults.set(selectedTableCellAddress, forKey: "address")
        }
    }
}

Note that lanScanner is an instance of MMLanScan

My rookie knowledge gives me a guess that this is due to my async pings (the ping results are the last things that appear on the console). The async functions happen when a device is found on the LAN. It is encapsulated in a function like this:

    DispatchQueue.global(qos: .userInteractive).async {
        print("pingOperationsShouldStop? \(self.pingOperationsShouldStop)")
        if self.pingOperationsShouldStop {
            print("Skipping ping for \(addr)")
            return
        }
        self.pingOperationsHaveStopped = false
        let port = Int32(self.portStr)
        let client = TCPClient(address: address, port: port!)
        switch client.connect(timeout: 1) {
        case .success:
            print("connected")
            DispatchQueue.main.sync {
                self.possibleDevices.append(address)
                self.numberOfPossibleDevicesCounted += 1
                self.pingOperations.leave()
            }
            client.close()
        case .failure(let error):
            print("failed due to \(error) for \(addr)")
            DispatchQueue.main.sync {
                self.numberOfPossibleDevicesCounted += 1
                client.close()
                self.pingOperations.leave()
            }
        }
    }

Note that possibleDevices has a didSet property observer that calls reloadData.

At first I thought that it only occurred through cell selection (i.e. NavCon's Back worked fine). So I programmatically called the popping of the view, to not avail. I tried reproducing the lock up through NavCon's Back button and have been able to do it once.

I'm not sure what code is relevant to share, so let me know if you need more information.

Thanks!

  • First guess: yep, it's something in your async pigs (heh heh)... What's happening there? Do the pings call something on the UI? Are they calling reloadData on the table? Are they updating an array of data, which is in turn calling reload ? – DonMag Mar 27 '17 at 18:47
  • Is there any sort of error? or is it just freezing? What does `lanScanner.stop()` do? – Taylor M Mar 27 '17 at 18:58
  • @DonMag I added the code block that contains the async calls. Technically it does call reloadData, but only through the main thread synchronously through the updating of a variable with a didSet property observer. – phantommbot Mar 28 '17 at 12:02
  • @TaylorM There are no errors that I can find. Xcode is responsive. The app "freezes" but animations that were happening continue to animate. lanScanner is an instance of [MMLanScan](https://github.com/mavris/MMLanScan) and stop is one of the object functions that stops a network scan. – phantommbot Mar 28 '17 at 12:04
  • @phantommbot - "If this cell is selected, some app settings are changed through didSelectRowAt indexPath and an unwind segue is done to return to rootVC." ... I don't see that in your `didSelectRow` code you have here. Do you have this 'unwind segue' connected to the table cell? – DonMag Mar 28 '17 at 12:26
  • @DonMag apologies for being unclear. I did an unwind by hooking up an IBAction as the exit segue for the view controller. I followed the method [here](https://developer.apple.com/library/content/technotes/tn2298/_index.html) – phantommbot Mar 28 '17 at 14:29
  • @phantommbot - ok... try this: "disconnect" the exit segue.... in `didSelectRowAt` stop the scanner / clean up background processes... *then* call `self.performSegueWithIdentifier("theUnwindID", sender: self)`... this way, you know your `didSelectRowAt` code is being executed before the segue starts – DonMag Mar 28 '17 at 14:36
  • @DonMag Hey, I tried following your procedure and unfortunately it's still the same behaviour (as far as I can tell). The segue is being started by `didSelectRowAt` now but the lock up can still happen. I edited my main post to clarify that the lock up only SOMETIMES happens when a cell is selected while the network scan isn't complete yet. – phantommbot Mar 28 '17 at 16:22
  • I still think the issue has to do with your background / async functions modifying your data or trying to update the UI *while* the segue is happening. When you *do* get a "lockup," can you hit Pause in Xcode and review threads to get an idea of what might be going on? – DonMag Mar 28 '17 at 16:38
  • @DonMag I believe that is my issue too but I have no idea how to deal with it. I have tried dispatch groups with bools and shouldPerformSegue as well, which works to prevent the lockups. However this method doesn't allow the user to select a cell as soon as it is available (shouldPerformSegue only returns true if the program has detected all ping operations are done). I don't know how to tell the program to clear up all async operations when a cell is selected. – phantommbot Mar 28 '17 at 17:23

0 Answers0