Can you create anonymous inner classes in Swift?
Asked Answered
S

2

10

I'm tired of declaring entire classes as having the ability to handle UIAlertView clicks by making them extend UIAlertViewDelegate. It starts to feel messy and wrong when I have multiple possible UIAlertViews, and have to distinguish which was clicked in the handler.

What I really want is to create a single object that implements the UIAlertViewDelegate protocol, and give this one-off object to my UIAlertView when showing it.

I want something like this:

let confirmDelegate = UIAlertViewDelegate() {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        // Handle the click for my alertView
    }
}

And then use it when showing the alert:

let alertView = UIAlertView(title: "Confirm", message: "Are you sure?", delegate: confirmDelegate, cancelButtonTitle: "No", otherButtonTitles: "Yes")
alertView.show()

Is this possible without declaring a new class?

I understand I could do something like this:

class ConfirmDelegate: UIAlertViewDelegate {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        // ...
    }
}

And then instantiate a ConfirmDelegate(), but I'm just wondering if this is possible as one-liner class declaration and instantiation.

Sciurine answered 11/8, 2014 at 15:43 Comment(7)
I presume you're targeting iOS 7. But for anyone else running across this and targeting iOS 8. Don't forget about the new UIAlertController class that is block based.Hamil
Even if you could do it, what your code shows wouldn't be correct, because delegates of UIAlertView are not retained, and once your local reference disappears at the end of the function, the delegate will be deallocated.Mcinnis
@Mcinnis I wasn't aware that delegates are weak references (that's what you are saying?). That could be the reason this behavior doesn't seem to exist.Sciurine
@ChrisWagner I need this to work on IOS 7, yes, but I'm still learning IOS development and didn't know about UIAlertController, thank youSciurine
This is just about the only thing I've found that Android seems to do more neatly. Though the gradual switch from target/action to blocks seems to obviate that.Rotz
If you're open to 3rd party libraries, BlocksKit has a nice block-based category on UIAlertView that implements this behavior. (They don't use anonymous inner classes, though.)Vanward
@SeanAdkinson: Yes, in general, all delegates in Cocoa are weak references, with some exceptions like NSURLConnection delegate.Mcinnis
S
7

As @ChrisWagner states in his comment, you shouldn't need to do any of this in iOS8, at least for UIAlertView since there is a new UIAlertViewController that uses closures without any delegates. But from an academic point of view, this pattern is still interesting.

I wouldn't use anonymous class at all. I would just create a class that can be assigned as the delegate and accept closures to execute when something happens.

You could even upgrade this to accept a closure for each kind of action: onDismiss, onCancel, etc. Or you could even make this class spawn the alert view, setting itself as the delegate.

import UIKit

class AlertViewHandler: NSObject, UIAlertViewDelegate {
    typealias ButtonCallback = (buttonIndex: Int)->()
    var onClick: ButtonCallback?

    init(onClick: ButtonCallback?) {
        super.init()
        self.onClick = onClick
    }

    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        onClick?(buttonIndex: buttonIndex)
    }
}


class ViewController: UIViewController {

    // apparently, UIAlertView does NOT retain it's delegate.
    // So we have to keep it around with this instance var
    // It'll be nice when all of UIKit is properly Swift-ified :(
    var alertHandler: AlertViewHandler?

    func doSoemthing() {
        alertHandler = AlertViewHandler({ (clickedIndex: Int) in
            println("clicked button \(clickedIndex)")
        })

        let alertView = UIAlertView(
            title: "Test",
            message: "OK",
            delegate: alertHandler!,
            cancelButtonTitle: "Cancel"
        )
    }
}

Passing around closures should alleviate the need for anonymous classes. At least for the most common cases.

Serica answered 11/8, 2014 at 18:2 Comment(10)
Fancy syntax. That's definitely closer to what I'm looking for. On the typealias line, is that basically declaring a lambda that takes an Int and doesn't return anything? And what you are passing into the AlertViewHandler constructor, that's the syntax for an anonymous function that satisfies the lambda contract? My terminology and idioms may be off slightly. I'll have to read up on that syntax.Sciurine
Nice approach @AlexWayne, up vote to you! I am curious however if initializing the AlertViewHandler at assignment time like that would cause that instance to be deallocated after the runloop exits the doSomething scope? Wouldn't it need to be an instance variable to be retained properly?Hamil
As for deallocation, alertView should retain it's assigned delegate, and release it when it's done. So it should stick around. But if you want to reference the object in other ways, you are totally free to assign it to an instance variable. EDIT Appears I'm wrong about that! Might want to save it to an instance variable first. :(Serica
There are also a fair number of libraries that implement this concept and should work with Swift. cocoapods.org/?q=alert+blockCan
@AlexWayne maybe use object associations and just connect the object directly to the alert view? developer.apple.com/library/mac/documentation/Cocoa/Reference/… (EDIT: that's a reference to objc_setAssociatedObject; I've no idea why I imagined Apple's HTML would work properly)Rotz
I don't know anything about that :(Serica
@Rotz You could use an associated object but that could cause a retain cycle. It is probably safe to create a strong reference to the delegate while visible and then remove the reference after it disappears, similar to UIScrollView's behavior.Can
The questioner wants the alert view to own the proposed anonymous object; I assumed he was aware of potential retain cycle implications.Rotz
@Tommy, no, not familiar with retain cycles. I assume that is akin to java's garbage collection, and perhaps in this situation, something wouldn't be able to be cleaned up. Is that what you and @BrianNickel are saying? I.E. if I set an object association from UIAlertView to it's delegate with RETAIN, I'm burning memory? Hopefully IOS can catch detached circular reference subtrees I assume. If I'm missing a key concept, please let me know (I come from Java, just learning IOS dev).Sciurine
@AlexWayne from what I tested, the delegate is not retained. Your solution works because the delegate is an instance variable, but if you declare it in a function, it is collected and not called (at least on iOS9)Oviparous
H
2

Unfortunately as far as I understand it, no you cannot effectively create anonymous inner classes. The syntax you suggest would be really nice IMO though.

Here is my attempt at getting something close to what you want, it's no where near as clean though.

import UIKit

class AlertViewDelegate: NSObject, UIAlertViewDelegate {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {

    }

    func alertView(alertView: UIAlertView!, didDismissWithButtonIndex buttonIndex: Int) {

    }

    func alertView(alertView: UIAlertView!, willDismissWithButtonIndex buttonIndex: Int) {

    }

    func alertViewCancel(alertView: UIAlertView!) {

    }

    func alertViewShouldEnableFirstOtherButton(alertView: UIAlertView!) -> Bool {
        return true
    }
}


class ViewController: UIViewController {
    var confirmDelegate: AlertViewDelegate?

    func doSoemthing() {
        confirmDelegate = {

            class ConfirmDelegate: AlertViewDelegate {
                override func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
                    println("clicked button \(buttonIndex)")
                }
            }

            return ConfirmDelegate()
        }()

        let alertView = UIAlertView(title: "Test", message: "OK", delegate: confirmDelegate, cancelButtonTitle: "Cancel")
    }

}
Hamil answered 11/8, 2014 at 17:18 Comment(3)
I see, so you created a concrete class that implements the protocol, and then you can selectively override the functions you want. Unfortunately it is quite a bit more code, but may be a useful strategy in certain cases.Sciurine
Yeah, I'd personally prefer a solution that uses closures. Much like Alex's and many of the third party Objective-C wrappers on UIAlertView out there that make them block based.Hamil
I can't seem to compile an example like this - an inner class inside a block inside a function. Can you still compile this in Swift 1.0?Incitement

© 2022 - 2024 — McMap. All rights reserved.