Post-creation callback: How to run code immediately after ctor? - swift

I wonder if I can get a callback after init(), or perhaps before deinit().
I have this repeating pattern in my code. First init() is run. Next fire() function is run.
class ParentViewController: UIViewController {
#IBAction func redButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.redColor(), name: "RED").fire()
}
#IBAction func greenButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.greenColor(), name: "GREEN").fire()
}
#IBAction func blueButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.blueColor(), name: "BLUE").fire()
}
#IBAction func resetButtonAction(sender: AnyObject) {
ResetEvent().fire()
}
}
Desired code, without calling the fire() function. I want it to autofire after creation. My code always run on the main-thread, so I guess I could use a one-shot timer for this. However I'm looking for a solution without timers.
class ParentViewController: UIViewController {
#IBAction func redButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.redColor(), name: "RED")
}
#IBAction func greenButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.greenColor(), name: "GREEN")
}
#IBAction func blueButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.blueColor(), name: "BLUE")
}
#IBAction func resetButtonAction(sender: AnyObject) {
ResetEvent()
}
}
Is it possible to get rid of the fire() function?
Update 1 (thanks feedback from #luk2302)
The event classes inherit from a protocol. I could change the protocol to a class or a struct, and call fire() within the base class. However then I decide wether the subclasses can be struct or class.
When just invoking ResetEvent() without using the result, then I get the Result of initializer is unused. I wish there was a reserved keyword for suppressing that warning.

you can use fire function after you initialised all stored properties as you can call any other function defined in your class and / or struct
protocol P {}
extension P {
func fire(s: String) {
print("fire from \(s)")
}
}
class C:P {
var i: Int
init(i:Int) {
self.i = i
foo()
fire("class init i: \(self.i)")
}
deinit {
fire("class deinit i: \(i)")
}
func foo() {
i += 1
}
}
struct S:P {
var i: Int
init(i: Int){
self.i = i
foo()
fire("struct init i: \(self.i)")
}
mutating func foo() {
i += 10
}
}
C(i: 5)
S(i: 1)
/*
fire from class init i: 6
fire from struct init i: 11
fire from class deinit i: 6
*/
If you want to avoid compiler warning "Result of initializer is unused", just use next syntax
_ = C(i: 5)
_ = S(i: 1)

Related

Weak reference implementing a (generic) protocol

I want to create an array of weak references implementing a protocol for observable functionality.
If possible this protocol should have an associated type, so I would not need to create identical ones for each type.
However in Swift 4 this does not seem to work, what I want is basically this:
protocol DelegateBase: AnyObject {
associatedtype Item
func onDataUpdated(data: [Item])
}
protocol Delegate : DelegateBase where Item == Int {
// Will have func onDataUpdated(data: [Int])
}
// T should be a class implementing Delegate, but cannot find a way to
// define T in a way that the Swift compiler accepts it
class WeakListener<T> {
weak var listener : T?
init(listener: T) {
self.listener = listener
}
}
class Implementation {
val listeners = [WeakListener<Delegate>]()
}
If I define everything as non-generic I can make it work, but I would like the convenience of not having to copy paste a concrete version of DelegateBase for each type I want to support.
// This works but is clunky
protocol DelegateA {
func onDataUpdated(data: [Int])
}
// DelegateB with type Double, DelegateC with type X
class WeakListenerA {
weak var listener : DelegateA?
init(listener: DelegateA) {
self.listener = listener
}
}
// WeakListenerB with type Double, WeakListenerC with type X
class ImplementationA {
val listeners = [WeakListenerA]()
}
// ImplementationB with type Double, ImplementationC with type X
EDIT: Intended usage
Basically I want to implement remote-backed repository where UIViewControllers can listen in on events happening inside the repository even though they aren't actively requesting data.
I want the listener to be generic, so I don't need to clone everything for each type. I want weak references since then no manual work is needed for managing them apart from adding listeners.
protocol DelegateBase: AnyObject {
associatedtype Item
func onDataModified(data: [Item])
func onDataRefreshed(data: [Item])
}
protocol IntDelegate : DelegateBase where Item == Int {
// Will have
//func onDataModified(data: [Int])
//func onDataRefreshed(data: [Int])
}
// Listener should implement the delegate T, that extends DelegateBase
// with a type
class WeakListener<T : DelegateBase> {
weak var listener : T?
init(listener: T) {
self.listener = listener
}
}
class IntRepository {
var listeners = [WeakListener<IntDelegate>]()
var data = [Int]()
func addListener(_ listener: IntDelegate /* UIViewController implementing IntDelegate*/) {
listeners.add(listener)
}
func add() {
// data is updated, notify weak listeners
data.append(1)
for listener in listeners {
listener.listener?.onDataModified(data: data)
}
}
func refresh() {
// data refreshed from a remote source
for listener in listeners {
listener.listener?.onDataRefreshed(data: data)
}
}
fileprivate func cleanupListeners() {
self.listeners = self.listeners.filter({$0.listener != nil})
}
// Singleton for example
fileprivate static var _instance: IntImplementation!
public class func shared() -> IntImplementation {
if _instance == nil {
_instance = IntImplementation()
}
return _instance!
}
}
class IntViewController: UIViewController, IntDelegate {
override func viewDidLoad() {
IntImplementation.shared().addListener(self)
}
func onDataModified(data: [Int]) {
// update UI
}
func onDataRefreshed(data: [Int]) {
// update UI
}
}
Because the DelegateBase (and by extension, the Delegate) protocol has an associated type (unlike DelegateA), you cannot use it as a type; you can only use some concrete type that conforms to it.
For what you're trying to do, you don't actually need the Delegate protocol. You seem to want a WeakListener generic with respect to Item type, which you can declare like so:
class WeakListener<T: DelegateBase> {
weak var listener : T?
init(listener: T) {
self.listener = listener
}
}
In other words, the above works with some concrete T type that conforms to DelegateBase.
Let's say you had such a concrete type for Int:
class ConcreteIntDelegateA: DelegateBase {
func onDataUpdated(data: [Int]) {
// ...
}
}
Then you could create a WeakListener for ConcreteIntDelegateA because it conforms to DelegateBase and further specifies that an Item is Int
let listener: WeakListener<ConcreteIntDelegateA>
So, it works for a concrete type, but you cannot do this for a protocol DelegateBase, because DelegateBase doesn't conform to DelegateBase (protocols don't conform to protocols, including themselves):
let listener: WeakListener<DelegateBase> // ERROR
This gives you the ability to create array of listeneres for a specific concrete type, e.g. [WeakListener<ConcreteIntDelegateA>] or [WeakListener<ConcreteIntDelegateB>], but not mixed, because these types are unrelated even though both ConcreteDelegateX conform to DelegateBase.
To get around it is to create a type-erased type, e.g. class AnyDelegate<Item>: DelegateBase, which takes a concrete type in its init:
class AnyDelegate<Item>: DelegateBase {
private let onDataUpdated: ([Item]) -> Void
init<C: DelegateBase>(_ c: C) where C.Item == Item {
self.sonDataUpdated = c.onDataUpdated(data:)
}
func onDataUpdated(data: [Item]) {
onDataUpdated(data)
}
}
let anyIntA = AnyDelegate(ConcreteIntDelegateA())
let anyIntB = AnyDelegate(ConcreteIntDelegateB())
let anyIntListeners: [WeakListener<AnyDelegate<Int>>] = [anyIntA, anyIntB]
What I ended up doing was just throwing away the DelegateBase. To provide some type conformance I created a generic base repository and enforce the delegate type during runtime when adding listeners
import UIKit
protocol IntDelegate : AnyObject {
func onDataModified(data: [Int])
func onDataRefreshed(data: [Int])
}
class WeakListener {
weak var listener : AnyObject?
init(listener: AnyObject) {
self.listener = listener
}
}
class RepositoryBase<T> {
private var _listeners = [WeakListener]()
var listeners: [T] {
get {
// Remove dead listeners
self._listeners = self._listeners.filter({($0.listener as? T) != nil})
return self._listeners.map({ $0.listener as! T })
}
}
func addListener(_ listener: AnyObject) {
assert(listener is T)
_listeners.append(WeakListener(listener: listener))
}
}
class IntRepository : RepositoryBase<IntDelegate> {
var data = [Int]()
func add() {
// data is updated, notify weak listeners
data.append(1)
for listener in self.listeners {
listener.onDataModified(data: data)
}
}
func refresh() {
// data refreshed from a remote source
for listener in self.listeners {
listener.onDataRefreshed(data: data)
}
}
// Singleton for example
fileprivate static var _instance: IntRepository!
public class func shared() -> IntRepository {
if _instance == nil {
_instance = IntRepository()
}
return _instance!
}
}
class IntViewController: IntDelegate {
func viewDidLoad() {
IntRepository.shared().addListener(self)
IntRepository.shared().add()
}
func onDataModified(data: [Int]) {
print("modified")
}
func onDataRefreshed(data: [Int]) {
print("refreshed")
}
}
IntViewController().viewDidLoad() // Prints "modified"

Swift closure defindet as class property cannot access other class properies

When directly assigning / implementing a Swift closure it is no problem to access class properties. But when I try to define the closure as class propertie as well, access to other class properties is not possible. Why is this?
Here is an example:
While the closure directly assigned to editorVC.completionBlock can access the class property tableView without any problem, the same code within leads to an error when the closure is defined as class property editorCompletionBlock:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
// Directly assign closure - Works without any problem
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
present(editorVC, animated: true, completion: nil)
}
// Define closure as class property ==> Error
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
// ERROR: Value of type '(MyViewController) -> () -> MyViewController' has no member 'tableView'
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}
Reason:
You can't access self until the initialization process of a type is completed.
In your code, editorCompletionBlock is a stored property and you're trying to access self.tableView inside it. This is the reason it is giving compile time error.
Solution:
Instead, make editorCompletionBlock as a lazy property to get that working.
lazy var editorCompletionBlock: EditorCompletionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
}
Solution 1:
place your editorCompletionBlock in viewDidLoad(:_) and it should work like charm:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
}
present(editorVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
self.tableView.reloadData() ///should work :)
}
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}
Solution 2:
Or you can just declare your closure as lazy:
lazy var editorCompletionBlock: EditorCompletionBlock = { (result)
in
self.tableView.reloadData()
doSomething(withResult: result)
}

Swift #objc protocol - distinguish optional methods with similar signature

Let's say we have a protocol in Swift:
#objc protocol FancyViewDelegate {
optional func fancyView(view: FancyView, didSelectSegmentAtIndex index: Int)
optional func fancyView(view: FancyView, shouldHighlightSegmentAtIndex index: Int) -> Bool
}
Note that both methods are optional and have the same prefix signature.
Now our FancyView class looks like this:
class FancyView: UIView {
var delegate: FancyViewDelegate?
private func somethingHappened() {
guard let delegateImpl = delegate?.fancyView else {
return
}
let idx = doALotOfWorkToFindTheIndex()
delegateImpl(self, idx)
}
}
The compiler jumps in our face:
We could change somethingHappened() to this:
private func somethingHappened() {
let idx = doALotOfWorkToFindTheIndex()
delegate?.fancyView?(self, didSelectSegmentAtIndex: idx)
}
However, as you can see we risk doing a lot of work only to throw away the index afterwards, because the delegate does not implement the optional method.
The question is: How do we if let or guard let bind the implementation of two optional methods with a similar prefix signature.
First, your objective C protocol needs to confirm to NSObjectProtocol to ensure we can introspect if it supports a given method.
Then when we want to call specific method, check if that method is supported by conforming object and if yes, then perform necessary computations needed to call that method. I tried this code for instance-
#objc protocol FancyViewDelegate : NSObjectProtocol {
optional func fancyView(view: UIView, didSelectSegmentAtIndex index: Int)
optional func fancyView(view: UIView, shouldHighlightSegmentAtIndex index: Int) -> Bool
}
class FancyView: UIView {
var delegate: FancyViewDelegate?
private func somethingHappened() {
if delegate?.respondsToSelector("fancyView:didSelectSegmentAtIndex") == true {
let idx :Int = 0 //Compute the index here
delegate?.fancyView!(self, didSelectSegmentAtIndex: idx)
}
}
}

Is self captured within a nested function

With closures I usually append [weak self] onto my capture list and then do a null check on self:
func myInstanceMethod()
{
let myClosure =
{
[weak self] (result : Bool) in
if let this = self
{
this.anotherInstanceMethod()
}
}
functionExpectingClosure(myClosure)
}
How do I perform the null check on self if I'm using a nested function in lieu of a closure (or is the check even necessary...or is it even good practice to use a nested function like this) i.e.
func myInstanceMethod()
{
func nestedFunction(result : Bool)
{
anotherInstanceMethod()
}
functionExpectingClosure(nestedFunction)
}
Unfortunately, only Closures have "Capture List" feature like [weak self]. For nested functions, You have to use normal weak or unowned variables.
func myInstanceMethod() {
weak var _self = self
func nestedFunction(result : Bool) {
_self?.anotherInstanceMethod()
}
functionExpectingClosure(nestedFunction)
}
Does not seem to be the case anymore. This is valid in swift 4.1:
class Foo {
var increment = 0
func bar() {
func method1() {
DispatchQueue.main.async(execute: {
method2()
})
}
func method2() {
otherMethod()
increment += 1
}
method1()
}
func otherMethod() {
}
}
The question remains: How is self captured ?

How to pass callback functions in Swift

I have a simple class which init method takes an Int and a callback function.
class Timer {
var timer = NSTimer()
var handler: (Int) -> Void
init(duration: Int, handler: (Int) -> Void) {
self.duration = duration
self.handler = handler
self.start()
}
#objc func someMethod() {
self.handler(10)
}
}
Then in the ViewController I have this:
var timer = Timer(duration: 5, handler: displayTimeRemaining)
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
This doesn't work, I get the following:
'Int' is not a subtype of 'SecondViewController'
Edit 1: Adding full code.
Timer.swift
import UIKit
class Timer {
lazy var timer = NSTimer()
var handler: (Int) -> Void
let duration: Int
var elapsedTime: Int = 0
init(duration: Int, handler: (Int) -> Void) {
self.duration = duration
self.handler = handler
self.start()
}
func start() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("tick"),
userInfo: nil,
repeats: true)
}
func stop() {
timer.invalidate()
}
func tick() {
self.elapsedTime++
self.handler(10)
if self.elapsedTime == self.duration {
self.stop()
}
}
deinit {
self.timer.invalidate()
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet var cycleCounter: UILabel!
var number = 0
var timer = Timer(duration: 5, handler: displayTimeRemaining)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnIncrementCycle_Click(sender: UIButton){
cycleCounter.text = String(++number)
println(number)
}
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
}
I just started with Swift so I'm very green. How are you supposed to pass callbacks? I've looked at examples and this should be working I think. Is my class defined incorrectly for the way I'm passing the callback?
Thanks
Ok, now with the full code I was able to replicate your issue. I'm not 100% sure what the cause is but I believe it has something to do with referencing a class method (displayTimeRemaining) before the class was instantiated. Here are a couple of ways around this:
Option 1: Declare the handler method outside of the SecondViewController class:
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
class SecondViewController: UIViewController {
// ...
var timer = Timer(duration: 5, handler: displayTimeRemaining)
Option 2: Make displayTimeRemaining into a type method by adding the class keyword to function declaration.
class SecondViewController: UIViewController {
var timer: Timer = Timer(duration: 5, handler: SecondViewController.displayTimeRemaining)
class func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
Option 3: I believe this will be the most inline with Swift's way of thinking - use a closure:
class SecondViewController: UIViewController {
var timer: Timer = Timer(duration: 5) {
println($0) //using Swift's anonymous method params
}
Simplest way
override func viewDidLoad() {
super.viewDidLoad()
testFunc(index: 2, callback: { str in
print(str)
})
}
func testFunc(index index: Int, callback: (String) -> Void) {
callback("The number is \(index)")
}
Your problem is in the line:
var timer = NSTimer()
You cannot instantiate an NSTimer in this way. It has class methods that generate an object for you. The easiest way to get around the problem in this case is to make the definition lazy, like so:
lazy var timer = NSTimer()
In this way the value for timer won't actually be touched until the start method which sets it up properly. NOTE: There is probably a safer way to do this.