So I have the following partial function implementation:
func addObserver(with notificationType: String) -> (#escaping (Notification) -> (Void)) -> NSObjectProtocol
{
return { (function: #escaping (Notification) -> (Void)) in
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: notificationType), object: nil, queue: nil, using: function)
}
}
Which basically would be used as such to create a full function such as:
let aoSelectCountry = addObserver(with: selectCountryNotification)
Which would then be used as:
self.selectCountryObserver = aoSelectCountry{ (notification) in
if (notification.object != nil) {
//do things
}
}
This is effectively the same as writing:
self.selectCountryObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: selectCountryNotification), object: nil, queue: nil) { (notification) in
if (notification.object != nil) {
//do things
}
Now all of this worked in XCode 8. But when I try to compile in XCode 9, I get the following error:
Cannot convert value of type '(Notification) -> (Void)' to expected argument type '(Notification) -> Void'
Removing the brackets around the (Void) part of the initial function just changes the error message to:
Cannot convert value of type '(Notification) -> Void' to expected argument type '(Notification) -> Void'
I have a strong feeling that this is a compiler bug, but I do desperately need a work around. I appreciate anyone's efforts. Thanks!
Related
I'm using the swift package OnboardKit and it requires a specific closure type that I cannot figure out.
The class OnboardPage requires a type OnboardPageAction for the parameter action.
public typealias OnboardPageCompletion = ((_ success: Bool, _ error: Error?) -> Void)
public typealias OnboardPageAction = (#escaping OnboardPageCompletion) -> Void
OnboardPage(title: "Title",
description: "description",
action: action)
This is my latest attempt, I tried several variations along those lines.
let action: ((_ success: Bool, _ error: Error?) -> ()) = {_,_ in
print("help")
}
XCode fails with the error message:
Cannot convert value of type '(Bool, Error?) -> Void' to expected
argument type 'OnboardPageAction?' (aka 'Optional<(#escaping (Bool,
Optional) -> ()) -> ()>')
What am I doing wrong here? Is the mistake in my closure definition or in the way I use it in the OnboardPage() call? Thanks :)
(I learned details about closures here Closures Swift How To, but I am not able to define the right closure type that the package expects)
Judging from the context, I guess that the purpose of the action parameter is to allow you run some code when an OnboardPage is presented. This "action" might take some time (it might not finish when action returns), so it gives you a completion handler parameter that you can call to indicate that what you want to do is done.
If you just want to print Hello, you can just call the parameter after you printed hello to indicate that you finished what you want to do:
OnboardPage(title: "Title",
description: "description",
action: { completion in
print("Hello")
completion(true, nil)
})
or simply
OnboardPage(title: "Title",
description: "description") {
print("Hello")
$0(true, nil)
}
The first argument indicates whether the action is successful, the second contains an optional error for when it fails.
The declaration of an action should look like this to match the defintions provided:
let action: OnboardPageAction = { (_ closure: ((_ success: Bool, _ error: Error?) -> Void)) in
print("action")
}
I am trying to write an extension for NotificationCenter
I find the syntax a little meaty and "boilerplatey" and would like to provide a simple extension that simplifies posting and observing.
I can dispatch an event like so
NotificationCenter.dispatch(key: <#T##String#>, payload: <#T##[String : String]#>)
However I would like to observe an event in a similar fashion.
I am trying to create something like
NotificationCenter.observe(key: <#T##String#>, handler: <#T##() -> Void#>)
However this is not correct. I am unsure how I can handler passing in my selector function that should be triggered on an observation?
This is my attempt so far.
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: ()->Void) {
NotificationCenter.default.addObserver(
self, selector: handler, name: NSNotification.Name(rawValue: key), object: nil
)
}
}
It sounds like you need something like
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
self.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: NSNotification.Name(rawValue: key), object: nil, queue: .main, using: handler)
}
}
You can use something like this:
extension NotificationCenter {
class func observe(name: NSNotification.Name, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: name, object: nil, queue: .main, using: handler)
}
}
Then you can use it like this:
NotificationCenter.observe(name: UIResponder.keyboardDidShowNotification) { notification in
// do something
}
You should definitely check out https://developer.apple.com/documentation/foundation/notificationcenter/1411723-addobserver regarding unregistering observations though.
NotificationCenter - a useful but bulky looking interface, feels a bit bloating in code, as someone coming from UNIX-like thread synchronization semantics... So, these extensions are based on stuff on S.O. & the 'Net, with a couple of twists.
extension Notification.Name {
static let patientlyWaiting = Notification.Name("patientlyWaiting")
// .
// . list your arbitrary waiter names here...
// .
}
extension NotificationCenter {
static func wait(_ name : Notification.Name) async {
for await _ in NotificationCenter.default.notifications(named: name) {
break;
}
}
static func post(_ name : Notification.Name) {
NotificationCenter.default.post(name: name, object: nil)
}
static func postProcessing(_ name: Notification.Name, using block: #escaping (Notification) -> Void) {
NotificationCenter.default.addObserver(forName: name, object: nil, queue: OperationQueue.main, using: block)
}
}
To use it, something like this:
Waiting for notification:
#IBAction func doSomethingAsychronouslyButtonPushed(_ sender: Any) {
doSomethingAsynchronously()
NotificationCenter.postProcessing(.patientlyWaiting, using: { _ in
print("doSomethingAsynchronouslyButton completion handler called")
})
}
Notifying:
func doSomethingAsynchronously() {
for n in ["Time", " ", "consuming", " - ", "whatever"] {
print("\(n)", terminator: "")
}
print("\n")
NotificationCenter.post(.patientlyWaiting)
}
Note: You could avoid using a closure and do an inline wait using Swift language's recently-added async/await support (see the 'wait()function in the extensions shown above), but I didn't provide an example of using that because I haven't been able to invokeasyncfunctions from selectors, such as from aUIButtonpress, directly or indirectly; because, any function that usesawaitneeds to be declaredasync. Specifically, #selector(afunc(_:)) doesn't match functions declared async, and I'm unaware of a function signature syntax that accounts for an asyncin function declaration (to pass to#selector()`). If someone knows, let me know in the comments and I'll update the answer.
This is the observer I have added,
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "XYZ"), object: nil, queue: nil) { [weak self] (notification) -> Void in
// self.somemethods()
}
This where I post notification,
self.fetchData({ (data) -> Void in
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "XYZ"), object: nil)
//use data
}, failure: { (error) -> Void in
})
When I post this the observer methods are not called. I have not removed the observer, also I ensured that notification is posted from main thread. Please help.
My build completes successfully. Then after some time this error pops up:
"Ambiguous use of 'addObjectsDidChangeNotificationObserver(handler:)'"
What I do not understand is why this happens because the addObjectsDidChangeNotificationObserver method is only declared once in the project and the second occurence shown by Xcode is the use of the method itself.
Here is the code where the error is shown and which Xcode also shows me as first candidate:
public init?(object: Managed, changeHandler: #escaping (ChangeType) -> ()) {
guard let moc = object.managedObjectContext else { return nil }
objectHasBeenDeleted = !type(of: object).defaultPredicate.evaluate(with: object)
token = moc.addObjectsDidChangeNotificationObserver(handler: {
[unowned self] note in
guard let changeType = self.changeType(of: object, in: note) else { return }
self.objectHasBeenDeleted = changeType == .delete
changeHandler(changeType)
})
}
and the implementation of addObjectsDidChangeNotificationObserver(), which Xcode shows me as second candidate:
extension NSManagedObjectContext {
public func addObjectsDidChangeNotificationObserver(handler: #escaping (ObjectsDidChangeNotification) -> ()) -> NSObjectProtocol {
let nc = NotificationCenter.default
return nc.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: self, queue: nil) { note in
let wrappedNote = ObjectsDidChangeNotification(note: note)
handler(wrappedNote)
}
}
}
Ok, the problem seems to be solved now.
Apparently, I messed up with the access modifiers, but good to know that something like that can cause an ambiguous error
In one of my app I have used block for webservice calling and getting response. Now I want to write this app in swift, but I am getting trouble to use blocks/Closure in Swift.
Here is my objective C code which I want to migrate in swift:
calling a class method of Communicator
[[Communicator sharedInstance]callWebService:WS_LOGIN withMethod:POST_METHOD andParams:params showLoader:YES completionBlockSuccess:^(id obj) {
//Do play with data
}completionBlockFailiure:^(id obj) {
//Show alert with error
}];
in communicator class
-(void)callWebService:(NSString *)serviceName withMethod:(NSString *)methodName andParams:(NSDictionary *)params showLoader:(BOOL)showLoader completionBlockSuccess:(void (^)(id))aBlock completionBlockFailiure:(void (^)(id))aFailBlock
{
if (showLoader) {
// show loader
}
[self performRequestWithServiceName:serviceName method:methodName andParams:params successblock:aBlock failureblock:aFailBlock];
}
- (void)performRequestWithServiceName:(NSString *)serviceName method:(NSString*)methodName andParams:(NSDictionary*)params
successblock:(void (^)(id obj))successBlock
failureblock:(void (^)(id obj))failBlock {
if(callSuceess){
successBlock(#"Success");
}else{
successBlock(nil);
}
}
For Swift. Use AnyObject for id objc type.
func callWebservice (serviceName: String, withMethod method: String, andParams params: NSDictionary, showLoader loader: Bool, completionBlockSuccess aBlock: ((AnyObject) -> Void), andFailureBlock failBlock: ((AnyObject) -> Void)) {
if loader {
// Show loader
}
performRequestWithServiceName(serviceName, method: method, andParams: params, success: aBlock, failure: failBlock)
}
func performRequestWithServiceName(serviceName: String, method methodName: String, andParams params: NSDictionary, success successBlock: ((AnyObject) -> Void), failure failureBlock: ((AnyObject) -> Void)) {
if callSuceess {
successBlock("Success")
}else {
successBlock(nil)
}
}
UPDATE: An example when you want call web service. See code below
callWebservice("your-service-name", withMethod: "your-method", andParams: ["your-dic-key": "your dict value"], showLoader: true/*or false*/, completionBlockSuccess: { (success) -> Void in
// your successful handle
}) { (failure) -> Void in
// your failure handle
}
Your code might look like this:
func callWebService(serviceName: String, method: String, params: [String : AnyObject], showLoader: Bool, success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
if showLoader {
// show loader
}
performRequest(serviceName, method: method, params: params, success: success, failure: failure)
}
func performRequest(serviceName: String, method: String, params: [String : AnyObject], success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
}
I replaced NSDictionary with [String : AnyObject]. If you can replace any of the uses of AnyObject with more specific types, your code will be cleaner and more stable.
For Swift Closures we have to use ( ) -> ( )
For example:
func yourFunction(success: (response: AnyObject!) -> Void, failure: (error: NSError?) -> Void) {
}
You can call it as:
yourFunction({(response) -> Void in
// Success
}) { (error) -> Void in
// Handle Errors
}
Hope it will help you to create Closures with your requirements.
In the communicator class the method that cals the webservice would be defined something like this depending on the type of object you want to return
func performRequest(serviceName: NSString, methodName: NSString,paramaters:NSDictionary, successblock: (String)->(), failureBlock: () -> ()) {
if(callSuccess) {
successblock("Success")
} else {
failureBlock()
}
We define the success and failure blocks types as by their function signatures in the case above success is defined as a method that takes a string as an input parameter and returns nothing so we can then call successBlock passing in a string. The failure block is defined above as a block that takes no parameters and returns nothing.
To call this method
func callWebService(serviceName: NSString, method: NSString and parameters: NSDictionary, showLoader: Bool, completionBlockSuccess:(String) -> (), completionBlockFailiure:() -> ()) {
if (showLoader) {
// show loader
}
performRequest(serviceName: serviceName, methodName: method, parameters, successBlock:completionBlockSuccess, failureBlock: completionBlockFailiure)
}
Finally to call this
Communicator.sharedInstance().callWebService(serviceName: WS_LOGIN , method: POST_METHOD and parameters: params, showLoader: true, completionBlockSuccess:{ returnedString in
//Do play with data
}, completionBlockFailiure:{
//Show alert with error
})
For the completion block we define a variable returnedString to allow us to manipulate that input parameter (in the above example it would be the string "Success"). I assume your data is not just returning a string though so you will probably need to play around with they type depending on what your service returns.
Also here I tried to match your method signatures by using NSString and NSDictionary though depending on your needs the Swift equivalents String and [String: AnyObject] could be more appropriate.
func processingWithAnyObject(input: String, completion: #escaping (_ result: AnyObject) -> Void) {
...
completion(response.result.value! as AnyObject)
}
processingWithAnyObject("inputString") {
(result: AnyObject) in
print("back to caller: \(result)")
}