Search code examples
iosswiftexc-bad-accessuirefreshcontrol

Swift: Selector with @escaping closure return EXC_BAD_ACCESS


What I've been doing is I have a function that requesting data from an API and when the response I've separate to two conditions is .success and .failure as a default response by Alamofire. and I've been using escaping closure to check if the response .success, I'll display something otherwise return an error to the user. and it was working fine until I want to put it into selector that UIRefresh need to use.

Here is my code:

Get Data Function:

@objc func GetData(completion: @escaping (Bool)->Void){
    Alamofire.request("\(ConstanClass.http)/api/order?token=\(ConstanClass.token)").responseJSON { response in
        switch response.result {
        case .success:
            if let value = response.result.value{
                let json = JSON(value)
                //Geting Json
                completion(true)
            }
        case .failure(let error):

            self.setErrorForm(self)
            self.hud.dismiss(animated: true)
            print(error)
            completion(false)
        }
    }
}

Calling From Selector:

refresher.addTarget(self, action: #selector(MyOrderController.GetData(completion:)), for: UIControlEvents.valueChanged)

Here is the Error:

Thread 1: EXC_BAD_ACCESS (code=257, address=0x1a1b50997c9)

and this error pointing to completion(true) in .success.


Solution

  • The problem is that you can't do what you are doing. The selector you set for the UIRefreshControl and the "value changed" event must have a very specific signature. Please review the "Target-Action Mechanism" section of the UIControl documentation.

    The selector must take either zero, one, or two parameters and those parameters can only be very specific parameters. The first (if provided) must be a reference to the control (the sender). And the second (if provided) must be a UIEvent.

    You can't create a sender that takes a completion block. That is the cause of the crash. The one parameter is being treated as the refresh control but the code treats it as a closure, hence the EXC_BAD_ACCESS error.

    Consider this, given your use of GetData, where is the completion handler being passed in? What is handling the result of the completion handler?

    Given that there is nothing that can deal with this completion handler, simply change GetData (which should be named getData) to take no parameters and remove the uses of completion.

    @objc func getData(){
        Alamofire.request("\(ConstanClass.http)/api/order?token=\(ConstanClass.token)").responseJSON { response in
            switch response.result {
            case .success:
                if let value = response.result.value{
                    let json = JSON(value)
                    //Geting Json
                }
            case .failure(let error):
                self.setErrorForm(self)
                self.hud.dismiss(animated: true)
                print(error)
            }
        }
    }
    

    And update your use:

    refresher.addTarget(self, action: #selector(getData), for: UIControlEvents.valueChanged)