Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call method on ALL objects conforming to a protocol

Tags:

ios

swift

Update

As maddy mentioned bellow in the comments it's obvious you need a reference to an object to be able to call a method on it. That is probably in fact my question: how do you keep track of all objects that implement a protocol?

Looking back at Objective-C I thought about using something similar to +load or +initialize methods and add the object as an observer for a specific NSNotification. But that wouldn't work since those methods are class methods and not instance methods.

So, trying to be even a little bit more specific: is there a method that get's called on all objects after they are created? A method that would allow me to add that object to a collection that I manage or as an observer for a specific NSNotification?

P.S: I haven't tried adding to much details to the problem 'cause I didn't want to "polute" you with my bad, non-sense ideas.

Original

So... Imagine this piece of code:

protocol MyProtocol: class {

    func myMethod()

}

public class MyClass: MyProtocol {

    func myMethod() {
        print("myMethod called on MyClass")
    }    

}

extension UIView: MyProtocol {

    func myMethod() {
        print("myMethod called on UIView")
    }

}

let myObject = MyClass()
let myView = UIView()

Now... I'm trying to figure out a way to call myMethod on both these objects from a 3rd one which is not aware of them - here's a simplified example of the 3rd one:

class MyManager {

    func callMyMethodOnAllObjecs() {
        // Do something here so that ALL objects present in memory that conform to MyProtocol get their myMethod called
    }        

}

Anyone?

like image 481
Mihai Fratu Avatar asked Dec 06 '25 19:12

Mihai Fratu


1 Answers

A heads up that what you need requires features belonging to dynamically typed languages, this means using the Objective-C runtime. This doesn't pose many technical challenges, but restricts the area of Swift entities you can use - basically only NSObject derived classes.

Briefly, here's what you need:

  1. support in MyManager for registering newly created objects
  2. a piece of code that needs to be executed by all MyProtocol instances that register themselves to MyManager
  3. most important, code that doesn't generate memory leaks, as registering an object within MyManager poses the risk of having the manager indefinitely retain that object.

Below you can find a code that solves the problem:

// Since we need ObjectiveC specific runtime features, we need to
// restrict the protocol to the NSObject protocol
protocol MyProtocol: NSObjectProtocol {
    func myMethod()
}

public class MyClass: NSObject, MyProtocol {
    func myMethod() {
        print("myMethod called on MyClass")
    }
    
}

extension UIView: MyProtocol {
    
    func myMethod() {
        print("myMethod called on UIView")
    }   
}

extension NSObject {
    // this is an alternative to the original init method, that besides the
    // original edit it registers the object within MyManager
    @objc func swizzledInit() -> NSObject {
        // this is not recursive, as init() in exchanged with swizzledInit()
        let `self` = swizzledInit()
        if let `self` = self as? MyProtocol {
            // the object is MyProtocol
            MyManager.shared.register(self)
        }
        return self
    }
}

class MyManager {
    
    static let shared = MyManager()
    private var objecters = [() -> MyProtocol?]()
    
    private init() {
        // let's swizzle init() with our custom init
        if let m1 = class_getInstanceMethod(NSObject.self, #selector(NSObject.init)),
           let m2 = class_getInstanceMethod(NSObject.self, #selector(NSObject.swizzledInit)) {
            method_exchangeImplementations(m1, m2)
        }
    }
    
    public func register(_ object: MyProtocol) {
        // registering a block in order to be able to keep a weak reference to
        // the registered object
        objecters.append({ [weak object] in return object })
    }
    
    
    func callMyMethodOnAllObjecs() {
        var newList =  [() -> MyProtocol?]()
        // go through the list of registered blocks, execute the method,
        // and retain only the ones for wich the object is still alive
        for object in objecters {
            if let o = object() {
                newList.append(object)
                o.myMethod()
            }
        }
        objecters = newList
    }
    
}

// This is to make sure the manager is instantiated first,
// and thus it swizzles the NSObject initializer
_ = MyManager.shared

let myObject = MyClass()
let myView = UIView()

// an instance of MyClass and one of UIView will print stuff
MyManager.shared.callMyMethodOnAllObjecs()

In summary, the above code:

  1. swizzles the init of NSObject so that besides the original init it also registers the object to your manager.
  2. keeps a list of closure that return objects instead of the object themselves, in order to be able to weakly reference those objects, thus to avoid keeping the objects alive more than they should be
  3. cleans up the closures whose objects got deallocated when callMyMethodOnAllObjecs is invoked.
like image 116
Cristik Avatar answered Dec 09 '25 14:12

Cristik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!