Search code examples
iosuiviewswiftuigesturerecognizernszombies

UITapGestureRecognizer Causing Crash With Differing Error Messages


I have a major issue that I am stuck with at work and would REALLY appreciate some help on. This has cost me 2 days already.

What I am trying to do is have a special image class fire it's assigned callback when it's touched. Thats it.

But when I touch the image it crashes, often without an error message just (lldb). Sometimes it says garbage like "[__NSCFData tapped:]: unrecognized selector sent to instance 0x1780af360". Sometimes it says "message sent to deallocated object".

I can run the app 10 times in a row and get one of these random messages just from tapping the same object on the screen each time.

The code is very simple:

//view controller
var k:K_PreviewImage!
override func viewDidLoad()
    {
        var image:iImage = iImage(imageName: "ja3.jpg")
        k = K_PreviewImage(image: image)
        k.touchCallback = nestedTap
        _view.addSubview(k.image)
    }

   func nestedTap(k:K_PreviewImage)
    {
        println("Successs")
    }

And here is the code inside K_PreviewImage (my clickable image). This does NOT inherit from anything, including NSObject

var touchCallback:((K_PreviewImage)->Void)
    {
        set{
            if(_touchCallback == nil)
            {
                var tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action:"tapped:")
                _image.addGestureRecognizer(tap)
            }

            _touchCallback = newValue
        }
        get{
            return _touchCallback
        }

  func tapped(tap:UITapGestureRecognizer)
    {
        println("here")
        if(_touchCallback != nil)
        {
            touchCallback(self)
        }
    }

The above code causes a crash 100% of the time.

If I add @objc too the tap event listener in the K_PreviewImage like this

 @objc func tapped(tap:UITapGestureRecognizer)
    {
        println("here")
        if(_touchCallback != nil)
        {
            touchCallback(self)
        }
    }

Then the code WORKS and the touch event is fired (both inside K_PreviewImage and the controllers callback function 'nestedTap'.

Not sure why that works, but it does. However I'm still up a creek without a paddle because when I make the K_PreviewImage a function variable instead of a class member variable, I get a crash again

 override func viewDidLoad()
    {
        var image:iImage = iImage(imageName: "ja3.jpg")
        var k:K_PreviewImage = K_PreviewImage(image: image)
        k.touchCallback = nestedTap
        _view.addSubview(k.image)
    }

So my first question is why did I need to add @objc to get the callback to fire instead of giving me an unclear 'deallocated' memory crash?

My second question is why is it when I moved the variable from a member variable to a function variable does it cause the crash all over again. If the object was deallocated it couldnt hear the tap event in the first place could it? Why would it still be on screen? And why is it getting dealloacted just because it's not a member variable!

How the heck can I create a custom object dynamically and have it fire click events!

UPDATE

Here is the full code block involving how my K_PreviewImage was being added with a touch event

var _array:Array<iImage>!

    override func viewDidLoad()
    {
        _array = Array<iImage>()
        var image:iImage = iImage(imageName: "ja3.jpg")
        var k:K_PreviewImage = K_PreviewImage(image: image)
        k.touchCallback = nestedTap
        _view.addSubview(k.image)
        _array.append(k.image)
    }

    func nestedTap(k:K_PreviewImage)
    {
        println("Successs")
    }

Solved:

override func viewDidLoad()
    {
        _array = Array<K_PreviewImage>()
        var image:iImage = iImage(imageName: "ja3.jpg")
        var k:K_PreviewImage = K_PreviewImage(image: image)
        k.touchCallback = nestedTap
        _view.addSubview(k.image)
        _array.append(k)
    }

    func nestedTap(k:K_PreviewImage)
    {
        println("Successs")
    }

Because I was not storing a reference to the K_PreviewImage 'k' even though the subview was added, even though it belongs to k, k was not being retained. By making the array store a reference to K_PreviewImage instead of iImage the compiler now retains a reference to the K_Preview Image


Solution

  • First off, it looks like you're trying to add an image as a subview. I don't know Swift all that well, but _view.addSubview(k.image) looks wrong to me and would make more sense as _view.addSubview(k).

    Secondly, you're not keeping your reference to your K_PreviewImage. You are assigning k.image as the subview, which causes k to be detected as "no longer used" by the system. It will clean it up and when you try to access it, you'll crash.