What exactly is the default NotificationCenter property? - ios

I want to create an additional NotificationCenter instance to separate my internal notifications from system notifications. However, when I looked at the class implementation, I did not understand what I was looking at.
open class NotificationCenter : NSObject {
open class var default: NotificationCenter { get }
}
Where is the getter? What does default exactly get? This is what I was expecting to see:
extension NotificationCenter {
static let default = NotificationCenter()
}
Is the default property a static singleton that we just don't see and the getter is just returning it?
And to implement my own custom center, could I just declare it the following way?
extension NotificationCenter {
static let custom = NotificationCenter()
}

NotificationCenter.default gets the default NotificationCenter instance for your app, which is a singleton.
The iOS frameworks are not open source, so when you look at the definition of NotificationCenter.default you see the interface it exposes, not the implementation, which would probably look very much like the code you expected to see.
You can, indeed, create your own NotificationCenter instances using NotificationCenter()

Related

Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization?

Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!

How do you set the delegate for an SFSpeechRecognitionTask?

In most cases, setting a delegate is as simple as implementing the delegate protocol in a class and declaring an instance of that class as the delegate for an instance of whatever you're using.
I actually used this same basic concept for the SFSpeechRecognizer which belongs to the same speech framework in my code. (Pseudocode example):
class myViewControllerClass: SFSpeechRecognizerDelegate{
let mySpeechRecognizer = SFSpeechRecognizer(...)
viewDidLoad(){
mySpeechRecognizer.delegate = self
}
...
//SFSpeechRecognizerDelegate Functions here
...
}
//This works as expected, woo!
However, it seems that SFSpeechRecognitionTask has no delegate property which can be set. I tried implementing the 'SFSpeechRecognitionTaskDelegate' protocol in my class in hopes that it would just magically work. However the delegate functions don't seem to ever be called. Which kind of makes sense, because it has no way of knowing that my view controller should be the delegate so why would it!?
The apple documentation covers the protocol itself and how to use it:
https://developer.apple.com/reference/speech/sfspeechrecognitiontaskdelegate
But the documentation for the task itself doesn't identify any delegate property:
https://developer.apple.com/reference/speech/sfspeechrecognitiontask
Also for reference here's the SFSpeechRecognizer documentation which has the protocol AND identifies a delegate property as you'd expect:
https://developer.apple.com/reference/speech/sfspeechrecognizer
Is there some alternative way I'm supposed to be setting the delegate for an SFSpeechRecognitionTask? Or is it handled in some completely different way?
In SFSpeechRecognizer there is a method
func recognitionTask(with request: SFSpeechRecognitionRequest,
delegate: SFSpeechRecognitionTaskDelegate) -> SFSpeechRecognizerTask
Where you can pass on the delegate for the SFSpeechRecognizerTask.
I did it like this in ViewController:
recognitionTask = speechRecognizer?.recognitionTask(with: speechRecognitionRequest, delegate: self)

Protocol Oriented Programming, implicitly calling extension method

Having my first crack at POP. In this case I want to decorate some UIViewControllers so that any that they automatically raise a 'Page viewed' analytics event.
So I created a protocol, and and extension for that protocol:
protocol ReportPageViewedEvent {
func reportPageViewed()
var pageName : String? { get set }
}
extension ReportPageViewedEvent where Self: UIViewController
{
func reportPageViewed()
{
guard let pageName = self.pageName else
{
fatalError("UIViewController implements ReportPageViewEvent protocol but did not set pageName property")
}
let eventBusiness = EventBusiness.sharedInstance
eventBusiness.logUserViewedPage(pageName)
}
}
This works as I want, if I decorate a UIViewController with ReportPageViewedEvent like this:
class HomeView: UIViewController, ReportPageViewedEvent {
I get a compiler error unless I set 'pageName' which is exactly what I want.
Where I am getting unstuck is where and how to call the actual reportPageViewed() method. I really want it to be called from viewDidLoad which means I either have to modify every 'viewDidLoad' in every controller that uses it, or subclass and call the method in the super class which defies the point of using POP in the first place.
Is there a nice way to achieve this. I can't find an example like this in any tutorial/blog.
Basically, there is always some behaviour shared by all the screens of your app. So it is appropriate to create a class called (for example) BaseViewController so all the other view controllers will inherit from it.
In BaseViewController's viewDidLoad you can call the reportPageViewed() method.
However, this approach makes the Protocol Oriented Programming not needed. Protocols are useful when you need to assign some same behaviour to objects that have nothing in common (which is not the case for app screens).

In Swift, register "anywhere" to be a delegate of a protocol

I have a complicated view class,
class Snap:UIViewController, UIScrollViewDelegate
{
}
and the end result is the user can, let's say, pick a color...
protocol SnapProtocol:class
{
func colorPicked(i:Int)
}
class Snap:UIViewController, UIScrollViewDelegate
{
someDelegate.colorPicked(blah)
}
So who's going to handle it.
Let's say that you know for sure there is something up the responder chain, even walking through container views, which is a SnapProtocol. If so you can use this lovely code to call it
var r : UIResponder = self
repeat { r = r.nextResponder()! } while !(r is SnapProtocol)
(r as! SnapProtocol).colorPicked(x)
If you prefer, you can use this superlative extension
public extension UIResponder // walk up responder chain
{
public func next<T>() -> T?
{
guard let responder = self.nextResponder()
else { return nil }
return (responder as? T) ?? responder.next()
}
}
courtesy these guys and safely find any SnapProtocol above you,
(next() as SnapProtocol?)?.colorPicked(x)
That's great.
But. What if the object that wants to get the colorPicked is a knight-move away from you, down some complicated side chain, and/or you don't even know which object wants it.
My current solution is this, I have a singleton "game manager" -like class,
public class .. a singleton
{
// anyone who wants a SnapProtocol:
var useSnap:SnapProtocol! = nil
}
Some freaky class, anywhere, wants to eat SnapProtocol ...
class Dinosaur:NSObject, SnapProtocol
{
....
func colorPicked(index: Int)
{...}
... so, to set that as the desired delegate, use the singleton
thatSingleton.useSnap = dinosaur
Obviously enough, this works fine.
Note too that I could easily write a little system in the singleton, so that any number of users of the protocol could dynamically register/deregister there and get the calls.
But it has obvious problems, it's not very "pattern" and seem violently non-idiomatic.
So. Am I really, doing this the right way in the Swift milieu?
Have I indeed confused myself, and there is some entirely different pattern I should be using in today's iOS, to send out such "messages to anyone who wants them?" ... maybe I shouldn't even be using a protocol?
"Send out messages to anyone who wants them" is pretty much the description of NSNotificationCenter.
True, it's not an API that's designed from the beginning for Swift patterns like closures, strong typing, and protocol-oriented programming. (As noted in other comments/answers, the open source SwiftNotificationCenter is a good alternative if you really want such features.)
However, NSNotificationCenter is robust and battle-hardened — it's the basis for thousands of messages that get sent around between hundreds of objects on each pass through the run loop.
Here's a very concise how-to for using NSNotificationCenter in Swift:
https://stackoverflow.com/a/24756761/294884
There is no "protocol based" notification mechanism in the Swift standard
libraries or runtime. A nice implementation can be found here https://github.com/100mango/SwiftNotificationCenter. From the README:
A Protocol-Oriented NotificationCenter which is type safe, thread safe
and with memory safety.
Type Safe
No more userInfo dictionary and Downcasting, just deliver the concrete
type value to the observer.
Thread Safe
You can register, notify, unregister in any thread without crash and
data corruption.
Memory Safety
SwiftNotificationCenter store the observer as a zeroing-weak
reference. No crash and no need to unregister manually.
It's simple, safe, lightweight and easy to use for one-to-many
communication.
Using SwiftNotificationCenter, a (instance of a) conforming class could register itself for example like this:
class MyObserver: SnapProtocol {
func colorPicked(i: Int) {
print("color picked:", i)
}
init() {
NotificationCenter.register(SnapProtocol.self, observer: self)
}
}
and a broadcast notification to all conforming registered observers is
done as
NotificationCenter.notify(SnapProtocol.self) {
$0.colorPicked(x)
}

Swift class with only class methods and a delegate?

I want a class with all class methods. I would like to use a delegate so my view controllers (conforming to the protocol) can call AlertFactory.passwordResetSucess() and display the alert.
Is there a way to make this this work? A way to use the delegate from this class?
Is this bad practice? Bad form? Why?
What's a good way to make this happen? There will be other class methods used in several views.
Thanks!
protocol AlertFactoryDelegate
{
func showAlert(alert: UIAlertController)
}
class AlertFactory: NSObject {
let delegate: AlertFactoryDelegate!
class func passwordResetSuccess()
{
var alert = UIAlertController(title: "Success!", message: "Yay", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default, handler: nil))
delegate.showAlert(alert)
}
}
You can set delegate to be static, and access it with AlertFactory.delegate
class AlertFactory: NSObject {
static weak var delegate: AlertFactoryDelegate?
class func passwordResetSuccess() {
...
self.delegate?.showAlert(alert)
}
}
To create a delegate:
class SomeClass: AlertFactoryDelegate {
... // Implement everything the protocol requires
}
// Asssing a delegate
AlertFactory.delegate = SomeClass()
You cannot set an instance variable on a class without instantiating the class first. Is there a reason you don't want to instantiate the class, and just want to use class methods instead? If you feel that you have good reason to use class methods over instance methods, then you have two potential options:
Pass a delegate as a parameter to the class method.
Use Completion Blocks instead of Delegation. Here's a decent example in swift.
In my personal experience I find blocks to be preferable.
EDIT: I haven't used Swift that much lately, but as #Skrundz pointed out you can indeed use a static variable on a class in Swift.
You could use the SINGLETON command pattern, its a very common practice. You should read up on it, but basically means there is only 1 ever instance of it and you can call methods off of it when needed (or send things to it as needed). Common examples are EventSystem objects or GlobalObserver objects, Factories, and ContextManagers.
Here is information on it:
https://sourcemaking.com/design_patterns/singleton
Using a SINGLETON has tradeoffs, but can be very useful in many situations. This seems like a good pattern for your problem.
You need to initialize your delegate when your app starts. From then on, when another view controller needs to set that delegate you can assign it: (this assumes you made delegate public and its a SINGLETON)
_myViewController.actionThatRequiresDelegate = AlertFactory.delegate
As mentioned in one of the other answers, using Swifts trailing syntax closure system is a great way to use anonymous functions and you could do your logic in there, or communicate with your delegate in there.
update
Initialize in the AppDelegate.swift:
override init()
{
AlertFactory.init()
}
Then in your AlertFactory
public static AlertFactoryDelegate myDelegate
public static func init()
{
myDelegate = new AlertFactoryDelegate()
}

Resources