Using Self as generic parameter - ios

I have simple generic class:
class MyClass<T> {
let closure: (T -> Void)
init(closure: T -> Void) {
self.closure = closure
}
}
I would like to write an extension for UIView which would apply closure to any subclass of UIView:
extension UIView {
func apply(c: MyClass<Self>) -> Self {
c.closure(self)
return self
}
}
But it gives me an error: 'Self' is only available in a protocol or as the result of a method in a class.
Is there any solution to fix this code?

You can achieve this by creating a protocol that UIView and in turn all subclasses will adopt:
protocol View {}
extension UIView:View {}
extension View {
func apply(c:MyClass<Self>) -> Self {
c.closure(self)
return self
}
}
let m = MyClass<UILabel>(closure: {t in})
let l = UILabel().apply(m) // UILabel returned

Related

Swift : Create a multi-function multicast delegate

I'm wanting to use a multicast delegate to inform multiple objects when things change. The tutorials I've read that explain this, have a protocol that only has one function that is called directly on the array of delegates. That works fine when there is only one function defined. My Protocol has 6 functions. I want to avoid creating 6 separate functions and reuse a single function that can be applied to my array of delegates.
Quick example: (I understand this is none working, but I just want to get my idea across.
protocol MyProtocol {
func method1()
func method2()
func method3()
}
class TestClass {
var delegates = [MyProtocol]()
func invokeDelegates(delegateMethod: () -> ()) {
for delegate in delegates {
delegate.delegateMethod()
}
}
}
The obvious problem is the compiler complains that "delegateMethod" isn't defined in the original protocol. Is there a way that I cast the method as being part of MyProtocol and the compiler will trust me?
Is this even possible?
Here is a gist of an Multicast Delegate pattern that I use in my projects. It also prevents from having strong reference cycles (memory leaks). WeakWrapper handles this.
Ok. In some of the solutions I see mistakes (strong retain cycles, race conditions, ...)
Here is what I combine based on 1 day research. For the stack of delegates I used NSHashTable, so all the delegates are having weak reference.
class MulticastDelegate <T> {
private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()
func add(delegate: T) {
delegates.add(delegate as AnyObject)
}
func remove(delegate: T) {
for oneDelegate in delegates.allObjects.reversed() {
if oneDelegate === delegate as AnyObject {
delegates.remove(oneDelegate)
}
}
}
func invoke(invocation: (T) -> ()) {
for delegate in delegates.allObjects.reversed() {
invocation(delegate as! T)
}
}
}
func += <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.add(delegate: right)
}
func -= <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.remove(delegate: right)
}
How to set delegate:
object.delegates.add(delegate: self)
How to execute function on the delegates:
instead of
delegate?.delegateFunction
you use
delegates.invoke(invocation: { $0.delegateFunction })
You need to change the signature of invokeDelegates to take a closure of type (MyProtocol) -> (), and then you need to pass each delegate to the closure.
protocol MyProtocol {
func method1()
func method2()
func method3()
}
class TestClass {
var delegates = [MyProtocol]()
func invokeDelegates(delegateMethod: (MyProtocol) -> ()) {
for delegate in delegates {
delegateMethod(delegate)
}
}
}
The closure should just invoke the appropriate delegate method on its argument. Swift can infer the argument and return types of the closure, and you can use the shorthand $0 to refer to the argument, so the closure can be quite short:
let tester = TestClass()
tester.invokeDelegates(delegateMethod: { $0.method1() })
On the other hand, you could just use Collection.forEach directly on the delegates array (if it's accessible) and skip the invokeDelegates method:
tester.delegates.forEach { $0.method1() }

Swift protocol defines an init that doesn't work for UIViewController

Here is a simple protocol:
protocol StringsInitiable {
init(strings: [String])
}
Trying to use the initializer in an extension works when constraint to NSObject...
extension StringsInitiable where Self: NSObject {
func test() {
let _ = Self(strings: [])
}
}
...but not when constraint to UIViewController. It then complains that the initializer should be labeled 'coder', referring to the mandatory initializer from NSCoding.
extension StringsInitiable where Self: UIViewController {
func test() {
let _ = Self(strings: []) // error label 'strings:' expected 'coder:'
}
}
Is there a way to use the initializer declared in the protocol even when being a UIViewController subclass?
EDIT
It seems to be working when constraining the extension to a base class (NSObject or any Swift class that doesn't inherit from anything) but not when constraining the extension to a child class.
I'm not entirely convinced, but this smells like a bug. Have a look at this example which doesn't compile:
protocol P {
init(n: Int)
}
class A {}
class B : A {}
extension P where Self : B {
func f() -> Self {
return Self(n: 3) // Error
}
}
But this compiles:
extension P where Self : A {
func f() -> Self {
return Self(n: 3)
}
}
Probably you don't want a protocol for that anyways, since you even named it StringsViewController. You should probably subclass UIViewController:
class StringsViewController : UIViewController {
convenience init(strings: [String]) {
self.init()
}
}
extension StringsViewController {
func test() {
let _ = StringsViewController(strings: [])
}
}
Or if you really want a protocol you can do something like this:
protocol HasView {
var view : UIView! { get }
}
protocol StringsInitable {
init(strings: [String])
}
extension UIViewController : HasView {}
extension HasView where Self : StringsInitable {
func test() {
let n = Self(strings: [])
print(n.view)
}
}
UIViewController doesn't have such an initialiser, because you haven't implemented the StringsViewController protocol. You would not be able to implement this protocol for UIViewController, because you cannot declare a designed initialiser into an extension. On the other hand you need a designated initialiser in order to conform to a init requirement of a protocol.

Swift: Call closure complete method in a different function

I'd like to use a method that return a result asynchronously using the delegate pattern within a closure.
Is it possible to reference the complete block within another function within the same class?
class A {
func performASyncTask(input:String, complete:(result:String) -> Void) {
let obj = Loader()
obj.delegate = self
obj.start()
// Loader() returns loaderCompleteWithResult(result:String) when completed
}
func loaderCompleteWithResult(result:String){
// Call complete function in performASyncTask .e.g
complete(result); // Calls the complete function in performASyncTask
}
}
I don't really understand what do you want to achieve. But you can declare function property and use it later:
class A {
var closureSaver: ((result:String) -> Void)?
func performASyncTask(input:String, complete:(result:String) -> Void) {
let obj = Loader()
obj.delegate = self
obj.start()
closureSaver = complete
complete(result: "a")
}
func loaderCompleteWithResult(result:String){
closureSaver?(result:result)
}
}

How do I pass in a void block to objc_setAssociatedObject in swift

I'm trying to add tap gesture support to UIView via an extension. This is pretty straight forward using Objective-C, but I'm getting the following error when trying to set the void return block on the runtime property.
error: type '() -> Void' does not conform to protocol 'AnyObject'
Here is the computed property:
var tapAction: (() -> Void)? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.SNGLSActionHandlerTapBlockKey)
}
set {
objc_setAssociatedObject(
self,
&AssociatedKeys.SNGLSActionHandlerTapBlockKey,
newValue,
UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)
)
}
}
I've tried to set the tapAction as a typealias, but still receive the same error.
The problem is closures are not objects that conform to AnyObject so you can't store them like that from Swift.
You could look into wrapping the closures in a class with a single property. Something like:
class ClosureWrapper {
var closure: (() -> Void)?
init(_ closure: (() -> Void)?) {
self.closure = closure
}
}
var tapAction: (() -> Void)? {
get {
if let cl = objc_getAssociatedObject(self, "key") as? ClosureWrapper {
return cl.closure
}
return nil
}
set {
objc_setAssociatedObject(
self,
"key",
ClosureWrapper(newValue),
UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
)
}
}
It's a little ugly and could use a nice typealias but it's one way to do it.

block in Swift : return error " is not convertible to "

I made a mistake but I cannot see how to solve it. I would like to load all the assets from GameScene and send a Bool in a completion method. I use typealias : should it be renamed twice for the two files (gameScene and gameController)?
Then I have got an error on this line GameScene.loadSceneAssetsWithCompletionHandler{ :
((Bool) -> Void) is not convertible to 'GameScene'
Here is the code :
//gameController:
typealias OnComplete = (Bool) -> ()
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler{ (success:Bool)->Void in
println("2/ yes")
return
}
//gameScene : rewrite typealias?
typealias OnComplete = (Bool) -> ()
func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
self.loadSceneAssets()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("1/ yes")
completion(true)
})//main
})//global
}
I read some threads that said to add a "return", but it does not solve the error here.
Thanks
It's almost working, but you've got a couple things going wrong here. First of all, you can't redeclare a typealias. Second of all you're calling loadSceneAssetsWithCompletionHandler as a class function when it's set up as an instance function. Note changes:
typealias OnComplete = (Bool) -> ()
class GameController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler { success in
println("2/ yes")
return
}
}
}
class GameScene: UIViewController {
func loadSceneAssets() {
}
class func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let gameScene = GameScene()
gameScene.loadSceneAssets()
dispatch_async(dispatch_get_main_queue()) {
println("1/ yes")
completion(true)
}
}
}
}

Resources