4

I'm using a UITableView to display a paginated list and I'm using a UIRefreshControl to indicate that a network request is taking place.

However, for some reason self.refreshControl.beginRefreshing()/self.refreshControl.endRefreshing() causes a crash on os_unfair_lock_lock(_lock). If I comment out both of these lines, the code works fine.

The following code can be used to reproduce the issue:

import UIKit
import ReactiveSwift
import Result

class ViewController: UIViewController {

    @IBOutlet var tableView : UITableView!
    var refreshControl : UIRefreshControl!

    var i : Int = 0
    var action : Action<Int,[String],NoError>!
    var words = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

        refreshControl = UIRefreshControl()
        tableView.addSubview(refreshControl)

        action = Action<Int,[String],NoError>{ pageNumber in
            return SignalProducer{ sink,_ in
                let deadlineTime = DispatchTime.now() + .seconds(2)
                DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
                    if pageNumber == 1{
                        sink.send(value: ["Apple","Bananas","Clementines"])
                    }else if pageNumber == 2{
                        sink.send(value: ["Dodo","Eels","French"])
                    }
                    sink.sendCompleted()
                }
            }
        }

        action.values.observeValues { [weak self](moreWords) in
            if self?.words.isEmpty ?? true {
                self?.words = moreWords
            }else{
                self?.words.append(contentsOf: moreWords)
            }
            self?.tableView?.reloadData()
        }

         action.isExecuting.signal.observe(on: UIScheduler()).observeValues { [weak self](loading) in
            if loading{
                self?.refreshControl.beginRefreshing()
            }else{
                self?.refreshControl.endRefreshing()
            }
            print("loading \(loading)")
        }
    }
}

extension ViewController : UITableViewDataSource {

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return self.words.count
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
        let identifier = "Whatever"
        var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
        if cell == nil{
            cell = UITableViewCell(style: .default, reuseIdentifier: identifier)
        }
        cell!.textLabel?.text = self.words[indexPath.row]
        return cell!
    }
}

extension ViewController : UITableViewDelegate{
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) ){
           if !self.action.isExecuting.value {
                i += 1
                self.action.apply(i).start()
            }
        }
    }
}
W.K.S
  • 9,787
  • 15
  • 75
  • 122
  • 2
    Have you tried to use the code `self?.refreshControl.beginRefreshing() & self?.refreshControl.endRefreshing()` on Main thread? – Imad Ali Dec 27 '17 at 05:13
  • 3
    That's interesting, using `QueueScheduler.main` instead of `UIScheduler()` fixed the problem. Reading up on the documentation, it seems it's because `QueueScheduler.main` preserves order hence eliminating the race condition I guess. Thanks, can you please write that as an answer so that I can accept it? – W.K.S Dec 27 '17 at 05:26

3 Answers3

5

Use the beginRefreshing & endRefreshing on main thread as:

QueueScheduler.main {
    if loading {
        self?.refreshControl.beginRefreshing()
    } else {
        self?.refreshControl.endRefreshing()
    }
}
emrcftci
  • 3,355
  • 3
  • 21
  • 35
Imad Ali
  • 3,261
  • 1
  • 25
  • 33
1

In my case, I got a crash in the UnfairLock too but the root cause was different. I had a typo like:

foo.bar <~ foo.bar.producer.skip(first: 1)

In fact, I assumed something like:

self.bar <~ foo.bar.producer.skip(first: 1)

Thus try to find self-bindings in your code.

Vladimir Vlasov
  • 1,860
  • 3
  • 25
  • 38
0

This is caused by a race condition. Using QueueScheduler.main solved the issue for me:

SignalProducer
            .combineLatest(...)
            .observe(on: QueueScheduler.main)
Bogdan Razvan
  • 1,497
  • 1
  • 16
  • 16