Trying to call selector to static function in swift - swift

I'm trying to achieve the following but am running into issues :-)
create a protocol that UIViewController and UIView subclass can adopt
which contain one static method to be called on this class (call it
configuration
I then want to use the objectiveC runtime to find the classes that adopt this protocol
On each of those class I want to call the configuration method
The configuration method is to return a dictionary (key: a description string, value: a selector to be called on the class)
So far I was able to create the protocol, find the class implementing the protocol but i'm running into compiling issues.
Here is the protocol
#objc public protocol MazeProtocol: NSObjectProtocol{
#objc static func configurations() -> NSDictionary
}
Here is the extension to adopt the protocol on one of my class:
extension MapCoordinatorViewController: MazeProtocol {
static func configurations() -> NSDictionary {
let returnValue = NSMutableDictionary()
returnValue.setObject(#selector(test), forKey: "test" as NSString)
return returnValue
}
#objc static func test() {
print("test")
}}
and here is the code i'm using to try to call the selector returned from the configuration method:
let selectorKey = controllerClass.configurations().allKeys[indexPath.row]
let selector = controllerClass.configurations().object(forKey: selectorKey)
controllerClass.performSelector(selector) <================ error here
ControllerClass is declared as let controllerClass: MazeProtocol.Type
I get the following compile warning:
Instance member 'performSelector' cannot be used on type 'MazeProtocol'
What am I missing?

You can technically force this to work. Please don't. This is horrible Swift. To get this to work, you have to undermine everything Swift is trying to do. But yes, with warnings, you can technically get this to compile and work. Please, please don't.
First, you need to make selector be a Selector. You're using an NSDictionary, which is terrible in Swift, and so you get Any? back. But, yes, you can as! cast it into what you want:
let selector = controllerClass.configurations().object(forKey: selectorKey) as! Selector
And then, defying all the type gods, you can just declare that classes are actually NSObjectProtocol, because why not?
(controllerClass as! NSObjectProtocol).perform(selector)
This will throw a warning "Cast from 'MapCoordinatorViewController.Type' to unrelated type 'NSObjectProtocol' always fails", but it will in fact succeed.
After all that "don't do this," how should you do this? With closures.
public protocol MazeProtocol {
static var configurations: [String: () -> Void] { get }
}
class MapCoordinatorViewController: UIViewController {}
extension MapCoordinatorViewController: MazeProtocol {
static let configurations: [String: () -> Void] = [
"test": test
]
static func test() {
print("test")
}
}
let controllerClass = MapCoordinatorViewController.self
let method = controllerClass.configurations["test"]!
method()

Related

How to define a variable function and call it from an extension file in swift?

I created an extension swift file, and in that file I wrote extension UIViewController{ ... } in my project, rather than writing all lines in every view controller. Everything worked ok. Later on I created a public func(i.e myFunc), with the same name inside each of the 30 view controllers, and having different outputs. I am trying to call this common named function from the extension file, but I get an error "Value of type 'UIViewController' has no member 'myFunc'. On the extension file, how can I call this common named function which executes different outputs on each different view controller?
Extension file Code:
extension UIViewController {
func sampleFunc {
let viewController = self.navigationController?.visibleViewController {
........
viewController.myFunc() }. *//error: Value of type 'UIViewController?' has no member 'myFunc'*
}
}
Note: I don't want to use like this:
if let viewController = self as? ViewController1 {viewController.myFunc()}
if let viewController = self as? ViewController2 {viewController.myFunc()}
if let ... 3,
4....30
Or maybe is there a way to check whether the function exists, and if it exists then execute command without receiving that kind of errors ?
use of unresolved identifier
or
value of type 'UIViewController?' has no member 'myFunc'
The viewController variable in the extension is could be any VC, couldn't it? Not just the 30 you created. You can't guarantee that any random VC will have a method called myFunc, so you can't call it in the extension.
One way to resolve this problem is to create a protocol that your 30 VCs all implement:
protocol MyFuncable : UIViewController { // please come up with a better name
func myFunc ()
}
This is an example of how you implement the protocol:
class ViewController: UIViewController, MyFuncable {
func myFunc() {
// do whatever you want...
print("myFunc executed")
}
}
Now we guarantee that everything that implements myFuncable will have a method called myFunc, so now in your extension, you can check whether the VCs are MyFuncable:
extension UIViewController {
func sampleFunc() {
// here is where the checking happens:
if let viewController = self.navigationController?.visibleViewController as? MyFuncable {
viewController.myFunc()
}
}
}
Try this code below.
extension UIViewController {
func sampleFunc() {
if let viewController = self.navigationController?.visibleViewController {
print("Class name: \(NSStringFromClass(type(of: viewController)))")
let anyObject = viewController as AnyObject
if anyObject.responds(to: #selector(anyObject.myFunc)) {
anyObject.myFunc()
}
}
}
}

Cannot cast value of type MyClass to MyDelegate

I have a class that needs to set a component to self. That component requires the class to implement a protocol MyDelegate. Eventually, it fails (SIGNAL SIGABRT).
// I need the class to be a NSObject for unrelated requirements
class MyClass: NSObject {
// I force the compilation, but it then breaks apart at runtime anyway
private let myComponent =
Component(requiresAnObjectofTypeMyDelegate: self as! MyDelegate)
}
// in the same file
extension MyClass: MyDelegate {
func myUsefulDelegateCall() {
}
}
Why?
The problem is the usage of self inside the stored property myComponent.
Typically, it is not allowed to hand out self before the initializer has finished intializing the whole object. Therefore, your problem has nothing to do with protocols or extensions. More simple:
import Foundation
class Component {
init(requiresAnObjectofTypeMyClass:MyClass) {
}
}
// I need the class to be a NSObject for unrelated requirements
class MyClass : NSObject {
// I force the compilation, but it then breaks apart at runtime anyway
private let myComponent =
Component(requiresAnObjectofTypeMyClass : self as! MyClass)
}
let m = MyClass()
also crashes.
If you leave out the NSObject subclassing, you get a compiler error:
use of unresolved identifier 'self'
Component(requiresAnObjectofTypeMyClass : self as! MyClass)
This shows the problem: You must not use self here.
I think it's just an Xcode bug; Xcode seems to ignore the syntax error when subclassing NSObject. The cast as! MyClass is also a hint that we are looking for a strange workaround that finally get's Xcode to it's knees and causes the runtime crash.
To work-around, you could create a lazy property, which will be evaluated after the initialization process and therefore will allow self to be handed into the Component initializer:
private(set) lazy var myComponent = Component(requiresAnObjectofTypeMyClass:self)
Here, you also do not need the cast. Unfortunately, lazy let is not allowed in swift (and nobody knows why), so private(set) is close to it's semantic.
It's easy to transfer this code to your protocol example.
A type cast is not needed. As MyClass adopts MyDelegate it is also MyDelegate.
And initialize the property lazily to be able to use self on the top level at all.
private lazy var myComponent = Component(requiresAnObjectofTypeMyDelegate: self)

why do I get "Attempted to unregister unknown __weak variable" when copying an instance variable?

I noticed this today when playing with NSOutlineView and NSTableHeaderCell, but when this specific configuration is made, an error/warning(?) is printed:
objc[2774]: Attempted to unregister unknown __weak variable at 0x1016070d0. This is probably incorrect use of objc_storeWeak() and objc_loadWeak(). Break on objc_weak_error to debug.
here's the snippet:
class Foo: NSCell {
weak var weak: NSView?
override func copy(with zone: NSZone? = nil) -> Any {
// according to NSCopying documentation:
// If a subclass inherits NSCopying from its superclass and declares
// additional instance variables, the subclass has to override copy(with:)
// to properly handle its own instance variables, invoking the superclass’s implementation first.
let copy = super.copy(with: zone) as! Foo
// this produces "Attempted to unregister unknown __weak variable"
copy.weak = self.weak
return copy
}
}
let view = NSView(frame: NSRect.zero)
let foo = Foo()
foo.weak = view
let copy = foo.copy() as! Foo
this also happens if I substitute NSCell with: NSEvent, NSImage, NSImageCell
but this doesn't happen to NSColor, NSDate, NSIndexPath
I started learning Swift without prior knowledge of Obj-C. could someone help me understand why this is? is it safe to ignore? who has the blame in this case?
This is a framework bug. It's easy to reproduce with the following crasher:
import Cocoa
class Cell: NSCell {
var contents: NSString?
override func copy(with zone: NSZone? = nil) -> Any {
let newObject = super.copy(with: zone) as! Cell
newObject.contents = contents
return newObject
}
}
func crash() {
let cell = Cell()
cell.contents = "hello world"
cell.copy() // crashes while releasing the copied object
}
crash()
When you use a weak var instead, you get the error message that you showed.
My gut feeling is that there is something in the copy implementation of NSCell (and possibly of NSEvent and NSImage) that does not handle subclassing for types that have non-trivial constructors. Accordingly, if you change let newObject = super.copy(...) with let newObject = Cell(), the crash is avoided. If your superclass's copy logic is simple enough, you should probably do that for now.
If you hit this problem, you should file a bug report separately of mine, but you can probably reuse my sample.

The type of self in Swift and its use with respect to two-phase initialization

Consider the following code, which adds a gesture recognizer to a view.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
let test1 = self
#objc func handleGesture(gesture: UITapGestureRecognizer) {
// some code
print("hello")
}
override func viewDidLoad() {
let test2 = self
super.viewDidLoad()
imageView.addGestureRecognizer(gesture)
}
}
As per this question, the above code does not work because I'm trying to use self (in the gesture recognizer's initializer) when not fully initialized, and this is so because of Swift's two-phase initialization.
I'm not interested in the easy fix to make this work, but this triggers a couple of questions:
1) Why does the compiler allow us to use self here if self is not ready to be used? Shouldn't I get a compiler error if I'm trying to use self too soon?
2) We can't directly inspect the type of self with alt+click in XCode. However, we can inspect the types of my ad hoc variables test1 and test2. While test2's type is ViewController, as expected, test1's type is (ViewController) -> () -> ViewController (i.e., a closure that takes a ViewController and returns a closure that takes nothing and returns a ViewController). What is that and why does self have two different types within the same class?
1)
Shouldn't I get a compiler error if I'm trying to use self too soon?
I do agree. You may send a bug report to swift.org.
Why does the compiler allow us to use self here if self is not ready to be used?
Unfortunately, there's another self in the descendants of NSObject, the method self() of NSObject.
2)
What is that and why does self have two different types within the same class?
The current Swift interprets the initial value expression in the class context, not in the instance context.
You know method names can be used as closures in Swift:
class ViewController: UIViewController {
//..
func aMethod() {
//...
}
func anInstanceMethod() {
let meth = aMethod // () -> ()
}
}
Swift can also refer to an instance method in the class context, which generates a so-called unapplied method reference (see SE-0042), which currently returns a curried function:
class ViewController: UIViewController {
//...
func aMethod() {
//...
}
class func aClassMethod() {
let meth = aMethod // (ViewController) -> () -> ()
}
}
The method self() as well.
Generally we do not need self() method and this behavior should be changed, I think.
This is interesting behaviour that works for Objective-C objects. Let's take these three examples:
class Object: NSObject {
let test = self // compiles
}
class NonNSObject {
// let test = self // errors
lazy var lazyTest = self // compiles
}
struct NonClass {
// let test = self // errors
lazy var lazyTest = self // errors
}
NonNSObject exhibits what you'd escape:
The object cannot reference itself until it is fully initialized, and let bindings must all be initialized before full initialization, so this failed.
However, NSObject happens to have an Objective-C method - (instancetype)self; which returns self. We can model this on NonNSObject as so:
func returnSelf() -> NonNSObject {
return self
}
This is where we start to see the answer to 2).
If we reference this method returnSelf on the Class we get the signature (NonNSObject) -> () -> NonNSObject. You can do this with any instance method as so:
let test = NonNSObject.returnSelf
The signature makes sense in this context:
The argument is the object we actually want to call the method on
Then we "apply" the function (with no arguments, in this case)
And we finally get our return value
let curriedFunction = NonNSObject.returnSelf // (Self) -> () -> Self
let readyToCall = curriedFunction(NonNSObject()) // () -> Self
let finallyApplied = readyToCall() // Self
Putting all the pieces together, we can see that in the case of ViewController (which inherits from UIViewController which way up the chain inherits from NSObject) there is an instance method self which the compiler is assuming you meant, so it uses that instead of the instance itself (as that would be an error). Its signature is thus a natural consequence of using an instance method on the class itself—it needs an instance, which is the first argument.
In summary:
1) Instead of assuming you made an error, the Swift compiler finds a function self on NSObject and returns the curried form.
2) This is the curried form of a function, in particular, an instance method which returns its own type.
2.5) It's still highlighted in pink because Swift-ObjC interop is mildly hacky, and self is both a method and, well, self.
As a bonus, the struct cannot reference itself at all, even lazily.

Get variable value or run function from different Object - Swift

I am trying to: get the value of a few variables, as well as run some functions which live in Object A, all from Object B.
I have tried for hours now to make it work with delegates and protocols. No luck.
I can't do something like this:
var delegate: MyDelegate = ViewController()
Because it seems to create a new instance of ViewController. And I want the values from the instance that is already running.
I also cannot do:
var delegate: MyDelegate?
Because the ViewController object never responds. So I get a nil anytime I call delegate?.somefunction()
I don't want a segue between screens. I just need to start a function from another object.
I bet this is an easy fix. I just can't get it. Thanks for your help.
Some of my code:
class PauseButtonView: NSView{
var delegate: PauseButtonDelegate?
...
var result = delegate?.startFunction()
}
protocol PauseButtonDelegate {
func startFunction() -> String
}
class ViewController: NSViewController, PauseButtonDelegate {
func startFunction() -> String {
let myString = "Hello World!"
return myString
}
}
If you don't want either classes to have a reference to the other, you could use internal notifications to communicate between them:
// in your PauseButtonView
let object:[String:AnyObject] = [ "aParameter" : 42 ]
let startNotification = NSNotification(name: "startFunction:", object: object)
NSNotificationCenter.defaultCenter().postNotification(startNotification)
// in the view controller
func startFunction(notification:NSNotification)
{
let object = notification.object as? [String:AnyObject]
//...
}