符合协议的所有对象的调用方法

时间:2016-12-08 20:15:23

标签: ios swift

更新

正如maddy在评论中提到的那样,显然你需要一个对象的引用才能在其上调用一个方法。这可能实际上是我的问题:如何跟踪实现协议的所有对象?

回顾Objective-C我考虑使用类似于+load+initialize方法的东西,并将对象添加为特定NSNotification的观察者。但这不起作用,因为这些方法是类方法而不是实例方法。

因此,尝试更具体一点:是否有一种方法可以在创建后调用所有对象?一种方法,允许我将该对象添加到我管理的集合或作为特定NSNotification的观察者?

P.S:我没有尝试在这个问题上添加更多细节,因为我不想用坏的,无意义的想法来“警告”你。

原始

所以...想象一下这段代码:

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()

现在......我正在试图找出一种方法来从第三个对象上调用myMethod,而第三个对象不知道它们 - 这是第三个对象的简化示例:

class MyManager {

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

}

任何?

5 个答案:

答案 0 :(得分:4)

请注意,您需要的功能需要属于动态类型语言的功能,这意味着使用Objective-C运行时。这不会带来许多技术挑战,但会限制您可以使用的Swift实体的区域 - 基本上只有NSObject派生类。

简而言之,这就是你需要的:

  1. 支持MyManager注册新创建的对象
  2. 需要由MyProtocol
  3. 注册的所有MyManager个实例执行的一段代码
  4. 最重要的是,代码不会产生内存泄漏,因为在MyManager内注册对象会导致管理员无限期地保留该对象的风险。
  5. 您可以在下面找到解决问题的代码:

    // 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
        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
            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()
    

    总之,上面的代码:

    1. 调动init的{​​{1}},这样除了原NSObject之外,它还会将对象注册到您的经理。
    2. 保留一个闭包列表,它返回对象而不是对象本身,以便能够弱引用这些对象,从而避免让对象保持活力超过应有的位置
    3. 清除在调用init时取消分配对象的闭包。

答案 1 :(得分:0)

您可以让MyProtocol个对象收听来自NotificationCenter的通知,并使myMethod()成为收到通知时触发的方法。

然后,当您将通知发布到NotificationCenter时,当前存在并遵守该协议的所有对象都将触发该方法。

答案 2 :(得分:0)

我认为你的意思是,如果你不知道该方法是否存在于该对象上。如果是这样,只需在调用方法之前对该方法运行测试:

myObject.respondsToSelector(Selector("myMethod")) // true if it can respond to it

-----更新了最新问题的答案:

if(myObject && myObject.respondsToSelector(Selector("myMethod")))  
    [myMutableDic setObject:myObject forKey:@"myObject"];

if(myView && myView.respondsToSelector(Selector("myMethod")))
    [myMutableDic setObject:myView forKey:@"myView"];

// and call all myMethods on this set ------------------------------ 
for (id key in [myMutableDic allKeys]) {
    id obj = [myMutableDic objectForKey:key];
    [obj myMethod]; // since already checked before putting it into dictionary
}

// or use the block enumeration - does the same thing: 
[myMutableDic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
    [obj myMethod];
}];

// ps didn't test it may need to fix grammar

答案 3 :(得分:0)

这样你可以添加或删除准备使用的观察者方法。您还可以创建自定义init以在创建实例时调用此方法。

protocol MyProtocol: class {

    func myMethod()
    func addObserverForMyMethod()
    func removeMyMethodObserver()


}

extension MyProtocol {
    func addObserverForMyMethod() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.myMethod), name: Notification.Name("myMethodCall"), object: nil)
    }
    func removeMyMethodObserver() {
        NSNotificationCenter.default.removeObserver(self, name: "myMethodCall", object: nil)
    }
}

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()
myObject.addObserverForMyMethod()
myView.addObserverForMyMethod()

答案 4 :(得分:0)

没有。在所有对象上都没有调用可覆盖的initialize方法。实际上,除非从NSObject继承,否则在swift中根本没有基类继承的东西。这种类型的动力有目的地不存在于swift中。为了做这样的事情,我建议你使用目标C运行时。考虑使用Injection for Xcode的源作为跳出点:https://github.com/johnno1962/injectionforxcode/blob/master/InjectionPluginLite/Classes/BundleSweeper.h

但是这真的非常强烈地反对谷物。如果您愿意考虑类似的东西,这可能适合您,同时更安全类型(并且它具有允许异构swift类型的好处):

protocol X {
    static var insts:[X]{ get }
    static func create() -> X
}

final class A:X{
     static func create() -> X {
        let it = A()
        insts.append(it)
        return it
    }

    static var insts:[X] = []

}
struct B:X{
    static func create() -> X {
        let it = B()
        insts.append(it)
        return it
    }

    static var insts:[X] = []

}

var protocols:[X.Type] = [A.self, B.self]
for p in protocols {
    p.insts.forEach({print($0)})
}