Swift: Call closure complete method in a different function - ios

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

Related

How do you write a Swift completion block that can only be called once?

Let's say I have a Swift class that stores a completion block, and does a few asynchronous tasks.
I want that block to be called by whichever of the tasks finishes first, but only that one - I don't want it to be called again when the second task finishes.
How can I implement this in a clean way?
As long as you don't need this to be thread safe, you can solve this problem with a fairly straightforward #propertyWrapper.
#propertyWrapper
struct ReadableOnce<T> {
var wrappedValue: T? {
mutating get {
defer { self._value = nil }
return self._value
}
set {
self._value = newValue
}
}
private var _value: T? = nil
}
Mark the completion block var with #ReadableOnce, and it will be destroyed after the first time it's value is read.
Something like this:
class MyClass {
#ReadableOnce private var completion: ((Error?) -> Void)?
init(completion: #escaping ((Error?) -> Void)) {
self.completion = completion
}
public func doSomething() {
// These could all be invoked from different places, like your separate tasks' asynchronous callbacks
self.completion?(error) // This triggers the callback, then the property wrapper sets it to nil.
self.completion?(error) // This does nothing
self.completion?(error) // This does nothing
}
}
I wrote up more of a detailed discussion of this here but the key thing to be aware of is that reading the value sets it to nil, even if you don't invoke the closure! This might be surprising to someone who isn't familiar with the clever property wrapper you've written.
There is already a standard expression of onceness. Unfortunately the standard Objective-C is unavailable in Swift (GCD dispatch_once), but the standard Swift technique works fine, namely a property with a lazy define-and-call initializer.
Exactly how you do this depends on the level at which you want onceness to be enforced. In this example it's at the level of the class instance:
class MyClass {
// private part
private let completion : (() -> ())
private lazy var once : Void = {
self.completion()
}()
private func doCompletionOnce() {
_ = self.once
}
// public-facing part
init(completion:#escaping () -> ()) {
self.completion = completion
}
func doCompletion() {
self.doCompletionOnce()
}
}
And here we'll test it:
let c = MyClass() {
print("howdy")
}
c.doCompletion() // howdy
c.doCompletion()
let cc = MyClass() {
print("howdy2")
}
cc.doCompletion() // howdy2
cc.doCompletion()
If you promote the private stuff to the level of the class (using a static once property), the completion can be performed only once in the lifetime of the entire program.

Completion callback using closures in Swift [duplicate]

In Objective-C, you can define a block's input and output, store one of those blocks that's passed in to a method, then use that block later:
// in .h
typedef void (^APLCalibrationProgressHandler)(float percentComplete);
typedef void (^APLCalibrationCompletionHandler)(NSInteger measuredPower, NSError *error);
// in .m
#property (strong) APLCalibrationProgressHandler progressHandler;
#property (strong) APLCalibrationCompletionHandler completionHandler;
- (id)initWithRegion:(CLBeaconRegion *)region completionHandler:(APLCalibrationCompletionHandler)handler
{
self = [super init];
if(self)
{
...
_completionHandler = [handler copy];
..
}
return self;
}
- (void)performCalibrationWithProgressHandler:(APLCalibrationProgressHandler)handler
{
...
self.progressHandler = [handler copy];
...
dispatch_async(dispatch_get_main_queue(), ^{
_completionHandler(0, error);
});
...
}
So I'm trying to do the equivilant in Swift:
var completionHandler:(Float)->Void={}
init() {
locationManager = CLLocationManager()
region = CLBeaconRegion()
timer = NSTimer()
}
convenience init(region: CLBeaconRegion, handler:((Float)->Void)) {
self.init()
locationManager.delegate = self
self.region = region
completionHandler = handler
rangedBeacons = NSMutableArray()
}
The compiler doesn't like that declaration of completionHandler. Not that I blame it, but, how do I define a closure that can be set and used later in Swift?
The compiler complains on
var completionHandler: (Float)->Void = {}
because the right-hand side is not a closure of the appropriate signature, i.e. a closure taking
a float argument. The following would assign a "do nothing" closure to the
completion handler:
var completionHandler: (Float)->Void = {
(arg: Float) -> Void in
}
and this can be shortened to
var completionHandler: (Float)->Void = { arg in }
due to the automatic type inference.
But what you probably want is that the completion handler is initialized to nil
in the same way that an Objective-C instance variable is inititialized to nil. In Swift
this can be realized with an optional:
var completionHandler: ((Float)->Void)?
Now the property is automatically initialized to nil ("no value").
In Swift you would use optional binding to check of a the
completion handler has a value
if let handler = completionHandler {
handler(result)
}
or optional chaining:
completionHandler?(result)
Objective-C
#interface PopupView : UIView
#property (nonatomic, copy) void (^onHideComplete)();
#end
#interface PopupView ()
...
- (IBAction)hideButtonDidTouch:(id sender) {
// Do something
...
// Callback
if (onHideComplete) onHideComplete ();
}
#end
PopupView * popupView = [[PopupView alloc] init]
popupView.onHideComplete = ^() {
...
}
Swift
class PopupView: UIView {
var onHideComplete: (() -> Void)?
#IBAction func hideButtonDidTouch(sender: AnyObject) {
// Do something
....
// Callback
if let callback = self.onHideComplete {
callback ()
}
}
}
var popupView = PopupView ()
popupView.onHideComplete = {
() -> Void in
...
}
I've provide an example not sure if this is what you're after.
var completionHandler: (_ value: Float) -> ()
func printFloat(value: Float) {
print(value)
}
completionHandler = printFloat
completionHandler(5)
It simply prints 5 using the completionHandler variable declared.
Closures can be declared as typealias as below
typealias Completion = (Bool, Any, Error) -> Void
If you want to use in your function anywhere in code; you can write like normal variable
func xyz(with param1: String, completion: Completion) {
}
In Swift 4 and 5. I created a closure variable containing two parameter dictionary and bool.
var completionHandler:([String:Any], Bool)->Void = { dict, success in
if success {
print(dict)
}
}
Calling the closure variable
self.completionHandler(["name":"Gurjinder singh"],true)
This works too:
var exeBlk = {
() -> Void in
}
exeBlk = {
//do something
}
//instead of nil:
exeBlk = {}
Depends on your needs there is an addition to accepted answer. You may also implement it like this:
var parseCompletion: (() ->Void)!
and later in some func assign to it
func someHavyFunc(completion: #escaping () -> Void){
self.parseCompletion = completion
}
and in some second function use it
func someSecondFunc(){
if let completion = self.parseCompletion {
completion()
}
}
note that #escaping parameter is a mandatory here
For me following was working:
var completionHandler:((Float)->Void)!

Using 'self' on RxSwift closures... What about instance methods as param?

In other stack overflow questions, it was emphasized that the capture [weak self] should be used for closures that aren't owned by the class because self could be nil before the closure completes. An alternative when the closure is owned by the class itself is [unowned self].
My question is do I need to use [unowned self] when the function I pass as a parameter is an instance method of the current class?
Example
import RxSwift
class Person {
var name = "Default name"
class func getPersons() -> Observable<Person> {
// ...
}
}
class MyController: UIViewController {
let disposeBag = DisposeBag()
// I know this right
func unownedDisplayPeople() {
Person.getPersons()
.subscribeNext { [unowned self ] person in
self.displayName(person)
}
.addDisposableToBag(disposeBag)
}
// But what about this?
func whatAboutThisDisplayPeople() {
Person.getPersons()
.subscribeNext(displayName)
.addDisposableToBag(disposeBag)
}
// Or this?
func orThisDisplayPeople() {
Person.getPersons()
.subscribeNext(self.displayName)
.addDisposableToBag(disposeBag)
}
func displayName(person: Person) {
print("Person name is \(person.name)")
}
}
If I still need to think about the reference counting when I just pass an instance method, how do I do it? Where do i put the [unowned self]? Or is it considered [unowned self] already when I just pass the instance method?
Unfortunately, passing an instance method to subscribeNext will retain self. To be more generic, storing a reference to an instance method will increase the retain count of the instance.
let instance = ReferenceType()
print(CFGetRetainCount(instance)) // 1
let methodReference = instance.method
print(CFGetRetainCount(instance)) // 2
The only solution here is do what you have done in unownedDisplayPeople.
let instance = ReferenceType()
print(CFGetRetainCount(instance)) // 1
let methodReference = { [unowned instance] in instance.method() }
print(CFGetRetainCount(instance)) // 1

Using Self as generic parameter

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

Lazy function in Swift

Could anyone tell me why in this 'odd' code (I'm having fun with Swift ;D) in lazy functions runEngine and stopEngine, print method is never executed?
(please run this code in playground).
Thanks!
protocol EngineDelegate {
func engineDidStart()
func engineDidStop()
}
class Engine {
var delegate: EngineDelegate?
lazy var runEngine : () -> () = {
print("Engine has been started")
self.delegate?.engineDidStart()
}
lazy var stopEngine : () -> () = {
print("Engine has been stoped")
self.delegate?.engineDidStop()
}
}
class Car: EngineDelegate {
let engine = Engine()
init() {
engine.delegate = self
}
func engineDidStop() {
print("MyOwnStop")
}
func engineDidStart() {
print("MyOwnStart")
}
}
let car = Car()
car.engine.runEngine()
The code runs as expected for me.
At first I thought that the lazy modifier was unnecessary but it is. When Engine is instantiated, its delegate is nil and that value is what is captured by the closure. Using lazy deferred that capture until its use which by that time engine.delegate had been set. While we might be able to use #autoclosure somehow, the best solution is to just make runEngine and stopEngine functions.
func runEngine() {
print("Engine has been started")
delegate?.engineDidStart()
}
func stopEngine() {
print("Engine has been stoped")
delegate?.engineDidStop()
}

Resources