How to setup clusures inside function parameter closure? - ios

What I want to achieve:
func test() {
MyClass().setup {
$0.testClosure = {
print("1 closure called")
}
$0.test2Closure = {
print("2 closure called ")
}
}
}
here is my try but i'm getting error: Type of expression is ambiguous without more context
I'm just wonder if something like this is even possible?
class MyClass {
var testClosure: (() -> Void)
var test2Closure: (() -> Void)
func setup(_ setup: ((_ controller: MyClass) -> Void)) {
setup(self)
}

That code seems convoluted. Why have a setup instance method that takes another instance of your class as a parameter, and then call it with self? It's confusing and odd.
how about this as a much cleaner setup?
class MyClass {
typealias AClosure = (() -> Void)
var testClosure: AClosure?
var test2Closure: AClosure?
// Use this initializer if you want to assign the closures later
init() {
}
// Use this intializer to provide the closures when you create an instance.
init(testClosure: #escaping AClosure, test2Closure: #escaping AClosure) {
self.testClosure = testClosure
self.test2Closure = test2Closure
}
}
func test() {
let myClass = MyClass(
testClosure: {
print("1 closure called")
},
test2Closure: {
print("2 closure called")
}
)
myClass.testClosure?()
myClass.test2Closure?()
}
test()

Related

Cannot convert value of type '(ViewController) -> () -> ()' to expected argument type '() -> ()'

I have a class with a function that takes in closure.
class MyFetcher {
public func fetchData(searchText: String,
onResponse: #escaping () -> (),
showResult: #escaping (String) -> ())
}
}
Calling it as below is all good
class ViewController: UIViewController {
private func fetchData(searchText: String) {
wikipediaFetcher.fetchData(searchText: searchText,
onResponse: stopIndicationAnimation,
showResult: showResult)
}
private func stopIndicationAnimation() {
// Do something
}
private func showResult(data: String) {
// Do something
}
}
However when I change the closure as class parameter for MyFetcher as below
class MyFetcher {
private let onResponse: () -> ()
private let showResult: (String) -> ()
init (onResponse: #escaping () -> (),
showResult: #escaping (String) -> ()) {
self.onResponse = onResponse
self.showResult = showResult
}
public func fetchData(searchText: String)
}
}
Calling it as below is giving error stating
Cannot convert value of type '(ViewController) -> () -> ()' to expected argument type '() -> ()'
class ViewController: UIViewController {
private let wikipediaFetcher = WikipediaFetcher(
onResponse: stopIndicationAnimation, // Error is here
showResult: showResult // Error is here
)
private func stopIndicationAnimation() {
// Do something
}
private func showResult(data: String) {
// Do something
}
Anything I did wrong?
The error is occurring because you're initialising wikipediaFetcher as a property of ViewController before it's available wikipediaFetcher. Try loading it as lazy
class ViewController: UIViewController {
private lazy var wikipediaFetcher = WikipediaFetcher(
onResponse: stopIndicationAnimation,
showResult: showResult
)
private func stopIndicationAnimation() {
// Do something
}
private func showResult(data: String) {
// Do something
}
}

How to remove passed closure from UIViewController

sorry maybe title not so much informative, so here is my problem
I want to create ThemeManager and apply to all screens, theme can be changed in the app, thats why I added closureList which will fire and update all related screens
class ThemeManager {
static let shared = ThemeManager()
private(set) var theme: Theme
private var bindedList: [()->Void] = []
private init () {
self.theme = AppGreenTheme()
}
func apply(theme: Theme) {
self.theme = theme
}
func bind(closure: #escaping ()->Void) {
bindedList.append(closure)
}
func bindAndFire(closure: #escaping ()->Void) {
bind(closure: closure)
closure()
}
}
here is how I want to use it from any UIViewController, or any UIView
ThemeManager.shared.bindAndFire {
// here I will get theme changes and update my screen
}
so I wanted to know, in this case will I create reference cycle for UIViewController, or UIView, and which is the best approach to remove closures from the list after parent UIViewController or UIView, will be removed from memory.
Its safe as long as you pass your UIViewController as a weak reference, like so
ThemeManager.shared.bindAndFire { [weak self] in
guard let strongSelf = self else { return }
// here I will get theme changes and update my screen
}
But NotificationCenter is better approach for this to rely on, here is basic ThemeManager example
class ThemeManager {
static let shared = ThemeManager()
static let NotificationName = NSNotification.Name("Notifacation.ThemeManager")
var theme: Theme!
func switchTheme(_ theme: Theme) {
self.theme = theme
NotificationCenter.default.post(name: ThemeManager.NotificationName, object: self.theme)
}
}
Usage:
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(themeDidUpdate(_:)), name: ThemeManager.NotificationName, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
#objc func themeDidUpdate(_ notification: Notification) {
guard let theme = notification.object as? Theme else { return }
// here I will get theme changes and update my screen
}
}
Update-2 Example NotificationCenter with a closure
NotificationCenter.default.addObserver(forName: ThemeManager.NotificationName, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
guard let strongSelf = self, let theme = notification.object as? Theme else { return }
// here I will get theme changes and update my screen
}
You can wrap the closures with a struct that has also has a property you can check for equality and return the value of that property when a closure is added. The view controller can then pass this id if it wants to remove the closure.You can hide the wrapper from the rest of the code. You can also use UUID if you don't want to keep track of some counter. You can also use a dictionary to store the closure with the id as the key.
class ThemeManager {
private var counter = 0
private var closures: [ClosureWrapper] = []
private struct ClosureWrapper {
let id: Int
let closure: () -> Void
}
func bind(closure: #escaping () -> Void) -> Int {
counter += 1
let wrapper = ClosureWrapper(id: counter, closure: closure)
closures.append(wrapper)
return wrapper.id
}
func removeClosure(with id: Int) {
guard let index = closures.firstIndex(where: { $0.id == id }) else {
return
}
closures.remove(at: index)
}
}
Here's version where you don't need to keep track of an id for the closure. It uses NSMapTable with weak keys to store the closures. You can pass the view controller as the key and when it is deallocated the passed closure will be automatically removed from the map table.
class ThemeManager {
private let closureTable = NSMapTable<NSObject, ClosureWrapper>(keyOptions: .weakMemory, valueOptions: .strongMemory)
private class ClosureWrapper {
let closure: () -> Void
init(closure: #escaping () -> Void) {
self.closure = closure
}
}
func bind(from source: NSObject, closure: #escaping () -> Void) {
let wrapper = ClosureWrapper(closure: closure)
closureTable.setObject(wrapper, forKey: source)
}
func callClosures() {
for key in closureTable.keyEnumerator().allObjects {
let wrapper = closureTable.object(forKey: key as? NSObject)
wrapper?.closure()
}
}
}

How to change protocol to typealias or closure

In my code I do not want to use protocol I want use closures but I couldn't get it done because I am new on Swift.
Here is the example of class
class SplashPresenterImp: SplashPresenter, OnFinishedListener {
private var interactor: SplashInteractor
private var splashNetworkProtocol: SplashNetworkProtocol
init() {
interactor = SplashNetworking()
}
func startDownloadConfigs(splashNetworkProtocol: SplashNetworkProtocol){
if interactor != nil {
interactor.loadConfigs(listener: self)
self.splashNetworkProtocol = splashNetworkProtocol
}
}
func startDownloadDictionary(splashNetworkProtocol: SplashNetworkProtocol) {
if interactor != nil {
interactor.loadDictionary(listener: self)
self.splashNetworkProtocol = splashNetworkProtocol
}
}
func onFinishedGetDictionary(dictionary: Dictionary) {
//save dictionary
if splashNetworkProtocol != nil {
splashNetworkProtocol.onSuccess()
}
}
func onFinishedGetConfigs(config: Config) {
//save configs
if splashNetworkProtocol != nil {
splashNetworkProtocol.onSuccess()
}
}
func onFinishedWithError(error: AMError) {
if splashNetworkProtocol != nil {
splashNetworkProtocol.onError(error: error)()
}
}
}
Here is the protocol
protocol SplashNetworkProtocol: class {
func onSuccess()
func onError(error: AMError)
}
What I want to have on my viewcontroller to have closure when downloadConfig is complete to start downloadDictionary.
I know how it handle on Java here is the code
mPresenter.startDownloadConfigs(new SplashNetworkProtocol() {
#Override
public void onSuccess() {
downloadDictionary();
}
#Override
public void onError(final AMError error) {
}
});
I want to have same result in swift. Is anyone can give me advice how to do this?
More clearly I want get rid of SplashNetworkProtocol and use only closure.
swift result should be this
mPresenter.startDownloadConfigs(onSuccess: {} onError{}
Should be as simple as:
func startDownloadDictionary(onSuccess: () -> Void, onError: () -> Void)
But even better to use a single closure that handles both success and error. For instance, with an error as an optional parameter:
func startDownloadDictionary(onCompletion: (Error?) -> Void)
A full example:
func someOtherFunc() {
startDownloadDictionary(onCompletion: {(error) -> Void in
if let error = error {
print(error.localizedDescription)
}
//no error
})
}
func startDownloadDictionary(onCompletion: (Error?) -> Void)
{
//dostuff
var error: Error?
// if error happens, create it
onCompletion(error)
}
If you need help with Swift closure syntax, this is a good resource:
http://fuckingswiftblocksyntax.com/

xcode 8.3 error with generics

protocol PubSubEvent {
associatedtype EventResult
static func eventName() -> String
func event() -> EventResult
func send()
}
class BGEventBus: EventBus {
static let sharedInstance = BGEventBus()
init() {
super.init(queue: OperationQueue())
}
}
class BGEventBusEvent: PubSubEvent {
typealias EventResult = BGEventBusEvent
class func eventName() -> String {
return String(describing: self)
}
func send() {
BGEventBus.sharedInstance.send(event: self)
}
func event() -> BGEventBusEvent.EventResult {
return self
}
}
class BGDidLoginEvent: BGEventBusEvent {
typealias EventResult = BGDidLoginEvent
var password: String?
var facebookToken: String?
init(password: String? = nil, facebookToken: String? = nil) {
self.password = password
self.facebookToken = facebookToken
}
}
class EventBus {
var queue: OperationQueue
init(queue: OperationQueue) {
self.queue = queue
}
func send(event: AnyObject) {
}
func handleEvent<T: PubSubEvent>(target:EventBusObservable, handleBlock: ((T.EventResult) -> Void)!) where T.EventResult == T {
}
}
class EventBusObserver {
var objectProtocol: NSObjectProtocol?
func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: #escaping (Notification) -> Swift.Void) {
self.objectProtocol = NotificationCenter.default.addObserver(forName: name, object: obj, queue: queue, using: block)
}
deinit {
if let obj = self.objectProtocol {
NotificationCenter.default.removeObserver(obj)
}
self.objectProtocol = nil
print("deinit observer!")
}
}
protocol EventBusObservable {
func handleBGEvent<T: PubSubEvent>(handleBlock: ((T.EventResult) -> Void)!) where T.EventResult == T
}
extension EventBusObservable {
func handleBGEvent<T>(handleBlock: ((T) -> Void)!) where T : PubSubEvent, T.EventResult == T {
BGEventBus.sharedInstance.handleEvent(target: self, handleBlock:handleBlock)
}
}
class sample: EventBusObservable {
func test() {
self.handleBGEvent { (event: BGDidLoginEvent) in
}
}
}
Hello guys I updated the Xcode to 8.3 and now I'm getting some errors like this:
Cannot convert value of type '(BGDidLoginEvent) -> ()' to expected argument type '((_) -> Void)!''
can anybody help me?
here the sample file https://drive.google.com/open?id=0B1zPtsTG7crPQncxYnEyWTBpSXM
I think you have to write the generic requirement exactly the same way every time. So, in EventBus:
class EventBus {
// ...
func handleEvent<T>(target:EventBusObservable, handleBlock: ((T) -> Void)!) where T : PubSubEvent, T.EventResult == T {
}
}
In EventBusObservable:
protocol EventBusObservable {
func handleBGEvent<T>(handleBlock: ((T) -> Void)!) where T : PubSubEvent, T.EventResult == T
}
In the EventBusObservable extension:
extension EventBusObservable {
func handleBGEvent<T>(handleBlock: ((T) -> Void)!) where T : PubSubEvent, T.EventResult == T {
BGEventBus.sharedInstance.handleEvent(target: self, handleBlock: handleBlock)
}
}
That compiles. Finally we are left with your class sample. This one wasn't so easy; I found I had to declare event as a BGEventBusEvent:
class sample: EventBusObservable {
func test() {
self.handleBGEvent {
(event:BGEventBusEvent) in
}
}
}

Execute completion handler when condition is met

I have a function which relies on the completionHandler of another function. This completionHandler should be called when the delegate method completedLogin is called. Below is a snippet of my code:
class loginClass : LoginScreenDelegate {
var loginCompleted : Bool = false
func performLogin(completionHandler: (() -> Void)) {
...
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
while self.loginCompleted != true {
// Do nothing
}
completionHandler()
})
}
func didLogin(sender: LogInScreen, success: Bool) {
// Do nothing
}
func completedLogin(sender: LogInScreen) {
self.loginCompleted = true
}
}
However, using a while loop inside a background thread seems like a very resource intensive way. I have tried using NSTimer() but the problem is is that it executes another function so i cannot use my callback function anymore. Is there a better / resource friendly way to keep checking this?
You have to add a completion handler to the function which needs to be completed before the other like this:
func completedLogin(sender: LogInScreen, completionHandler :(evaluatedSuccessfully: Bool) -> ()){
self.loginCompleted = true
completionHandler(evaluatedSuccessfully: true)
}
And then you can just call this function in any other function like this:
completedLogin(sender: <your sender>){ success in
If success{
//do something
}
else{
//report an error
}
}
class loginClass : LoginScreenDelegate {
var loginCompleted : Bool = false
var completionHandler: (() -> Void)!
func performLogin(completionHandler: (() -> Void)) {
...
self.completionHandler = completionHandler
}
func didLogin(sender: LogInScreen, success: Bool) {
// Do nothing
}
func completedLogin(sender: LogInScreen) {
self.loginCompleted = true
self.completionHandler()
}
}

Resources