Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

The most flexible and easiest to implement Observer Pattern platform for the Swift language (includes fully-functional Observable Thread!)

License

NotificationsYou must be signed in to change notification settings

Flowduino/Observable

Repository files navigation

Collection of carefully-prepared Classes and Protocols designed to imbue your inheriting Object Types with efficient, protocol-driven Observer Pattern Behaviour.As of version 2.0.0, this includes support forKeyed Observers (see usage examples below for details)

Installation

Xcode Projects

SelectFile ->Swift Packages ->Add Package Dependency and enterhttps://github.com/Flowduino/Observable.git

Swift Package Manager Projects

You can useObservable as a Package Dependency in your own Packages'Package.swift file:

letpackage=Package(    //...    dependencies:[.package(            url:"https://github.com/Flowduino/Observable.git",.upToNextMajor(from:"2.0.0")),],    //...)

From there, refer toObservable as a "target dependency" in any ofyour package's targets that need it.

targets:[.target(        name:"YourLibrary",        dependencies:["Observable",],        //...),    //...]

You can then doimport Observable in any code that requires it.

Usage

Here are some quick and easy usage examples for the features provided byObservable:

ObservableClass

You can inherit fromObservableClass in yourown Class Types to provide out-of-the-box Observer Pattern support.This not only works for@ObservedObject decorated Variables in a SwiftUIView, but also between your Classes (e.g. between Services, or Repositories etc.)

First, you would define a Protocol describing the Methods implemented in yourObserver Class Type that yourObservable Class can invoke:

/// Protocol defining what Methods the Obverable Class can invoke on any ObserverprotocolDummyObservable:AnyObject{ // It's extremely important that this Protocol be constrained to AnyObjectfunc onFooChanged(oldValue:String, newValue:String)func onBarChanged(oldValue:String, newValue:String)}

Note - It is important that our Protocol define theAnyObject conformity-constraint as shown above.

Now, we can define ourObservable, inheriting fromObservableClass:

/// Class that can be ObservedclassDummy:ObservableClass{privatevar_foo:String="Hello"publicvarfoo:String{get{return _foo}set{            // Invoke onFooChanged for all current ObserverswithObservers{(observer:DummyObservable)in                observer.onFooChanged(oldValue: _foo, newValue: newValue)}            _foo= newValue            objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)}}privatevar_bar:String="World"publicvarbar:String{get{return _bar}set{            // Invoke onBarChanged for all current ObserverswithObservers{(observer:DummyObservable)in                observer.onBarChanged(oldValue: _bar, newValue: newValue)}            _bar= newValue            objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)}}}

We can now define anObserver to register with theObservable, ensuring that we specify that it implements ourDummyObservable protocol:

classDummyObserver:DummyObservable{    /* Implementations for DummyObservable */func onFooChanged(oldValue:String, newValue:String){print("Foo Changed from\(oldValue) to\(newValue)")}func onBarChanged(oldValue:String, newValue:String){print("Bar Changed from\(oldValue) to\(newValue)")}}

We can now produce some simple code (such as in a Playground) to put it all together:

// Playground Code to use the abovevarobservable=Dummy() // This is the Object that we can Observevarobserver=DummyObserver() // This is an Object that will Observe the Observableobservable.addObserver(observer) // This is what registers the Observer with the Observable!observable.foo="Test 1"observable.bar="Test 2"

ObservableThreadSafeClass

ObservableThreadSafeClass works exactly the same way asObservableClass. The internal implementation simply encapsulates theObserver collections behind aDispatchSemaphore, and provides aRevolving Door mechanism to ensure unobstructed access is available toaddObserver andremoveObserver, even whenwithObservers is in execution.

Its usage is exactly as shown above inObservableClass, only you would substitute the inheritence ofObservableClass to instead inherit fromObservableThreadSafeClass.

ObservableThread

ObservableThread provides you with a Base Type for any Thread Types you would want to Observe.

Note -ObservableThread does implement theObservableObject protocol, and istechnically compatible with the@ObservedObject property decorator in a SwiftUIView. However, to use it in this way, anywhere you would invokeobjectWillUpdate.send() you must instead usenotifyChange(). Internally,ObservableThread will executeobjectWillChange.send()but enforce that it must execute on theMainActor (as required by Swift)

Let's now begin taking a look at how we can useObservableThread in your code.The example is intentionally simplistic, and simply generates a random number every 60 seconds within an endless loop in the Thread.

Let's begin by defining our Observation Protocol:

protocolRandomNumberObserver:AnyObject{func onRandomNumber(_ randomNumber:Int)}

Any Observer for our Thread will need to conform to the RandomNumberObserver protocol above.

Now, let's define our RandomNumberObservableThread class:

classRandomNumberObservableThread:ObservableThread{init(){self.start() // This will start the thread on creation. You aren't required to do it this way, I'm just choosing to!}publicoverridefunc main(){ // We must override this methodwhileself.isExecuting{ // This creates a loop that will continue for as long as the Thread is running!letrandomNumber=Int.random(in:-9000..<9001) // We'll generate a random number between -9000 and +9000            // Now let's notify all of our Observers!withObservers{(observer:RandomNumberObserver)in                observer.onRandomNumber(randomNumber)}Self.sleep(forTimeInterval:60.00) // This will cause our Thread to sleep for 60 seconds}}}

So, we now have a Thread that can be Observed, and will notify all Observers every minute when it generates a random Integer.

Let's now implement a Class intended to Observe this Thread:

classRandomNumberObserverClass:RandomNumberObserver{publicfunc onRandomNumber(_ randomNumber:Int){print("Random Number is:\(randomNumber)")}

We can now tie this all together in a simple Playground:

varmyThread=RandomNumberObservableThread()varmyObserver=RandomNumberObserverClass()myThread.addObserver(myObserver)

That's it! The Playground program will now simply print out the new Random Number notice message into the console output every 60 seconds.

You can adopt this approach for any Observation-Based Thread Behaviour you require, becauseObservableThread will always invoke the Observer callback methods in the execution context their own threads! This means that, for example, you can safely instantiate an Observer class on the UI Thread, while the code execution being observed resides in its own threads (one or many, per your requirements).

Keyed Observation Pattern

As of version 1.1.0, you can now register and notifyKeyed Observers.Note: Version 2.0.0 modified the Interface significantly to eliminate the need for Generic Typing of the Key. Key Types are now inferred for you.

This functionality is an extension of the standard Observer Pattern, and is implemented in the following classes from which you can extend:

  • KeyedObservableClass instead ofObservableClass
  • KeyedObservableThread instead ofObservableThread
  • KeyedObservableThreadSafeClass instead ofObservableThreadSafeClass

Remember, Keyed Observation is anextension of the basic Observation Pattern, so any Keyed Observable is also inherently able to register and notify non-Keyed Observers

You would use Keyed Observation whenever your Observers care about a specific context of change. A good example would be for a Model Repository, where an Observer may only care about changes to a specific Model contained in the Repository. In this scenario, you would used Keyed Observation to ensure the Observer is only being notified about changes corresponding to the given Key.

Key Types must always conform to theHashable protocol, just as must any Key Type used for aDictionary collection.

Let's take a look at a basic usage example.

We shall provide a basic usage example to synchronize an Observer's internal Dictionary for specific keys only with the values from the Observable's internal Dictionary.

First, we would begin with an Observation Protocol:

protocolTestKeyedObservable:AnyObject{func onValueChanged(key:String, oldValue:String, newValue:String)}

The above Observation Protocol provides the methodonValueChanged which takes thekey (in this case aString value) and provides the correspondingoldValue andnewValue values for thatkey.OurObserver will implementTestKeyedObservable to provide an implementation for this function.

Now, let's define a simpleKeyed Observable to house the master Dictionary we will be selectively-synchronizing with one or moreObservers.

classTestKeyedObservableClass:KeyedObservableClass<String>{privatevarkeyValues:[String:String]=["A":"Hello","B":"Foo","C":"Ping"]func setValue(key:String, value:String){withKeyedObservers(key: key){(key, observer:TestKeyedObservable)in            observer.onValueChanged(key: key, oldValue:self.keyValues[key]!, newValue: value)}self.keyValues[key]= value}}

The above class inherits fromKeyedObservableClass and specializes theTKey generic to be aString. In other words, the Keys for this Observable must always beString values.It includes a simpleString:String dictionary (String key with aString value)

ThesetValue method will simply notify all observers usingwithKeyedObservers any time a specifickey the Obsever(s) is(are) observing is updated, passing along theoldValue andnewValue values. It will then update its internal Dictionary (keyValues) so that it always contains the latest value.

Note the use ofwithKeyedObservers instead ofwithObservers. You will use this syntax in your own Keyed Observables, changing only the declared Observer Protocol (TestKeyedObservable in this example) with the Observer Protocol representing your own observation methods.

Now that we have a Keyed Observable that will notify Observers each time the value of a key changes, let's define an Observer.

classTestKeyedObserverClass:TestKeyedObservable{publicvarkeyValues:[String:String]=["A":"Hello","B":"Foo"]func onValueChanged(key:String, oldValue:String, newValue:String){keyValues[key]= newValue}}

So,TestKeyedObserverClass is a simple class, implementing ourTestKeyedObservable Observer Protocol.For this example, we are going to presume that there are 2 pre-defined Keys with known initial values (there do not have to be... you can have as many keys as you wish)

You will notice that we initialized both the Observable and Observer classes to have identicalkeyValues dictionaries. This is solely for the sake of simplifying this example by ensuring there is always anoldValue. You don't need to do this in your own implementations.

So, now that we have theObservable and theObserver types, let's produce a simple bit of Playground code to tie it together.

letobservable=TestKeyedObservableClass() // Creates our Observableletobserver= TestKeyedObserverClass // Creates a single Observer instance

At this point, we need to consider what Key or Keys ourobserver is going to Observe.

For example, we can Observe just one key:

observable.addKeyedObserver(key:"A", observer)

The above means thatobserver would only have itsonValueChanged method invoked when the value of keyA is modified inobservable.

Likewise, if we only care about keyB, we can do:

observable.addKeyedObserver(key:"B", observer)

If we care aboutboth known keys, we can simply register them both:

observable.addKeyedObserver(keys:["A","B"], observer)

Also, we can do something particularly clever and basically register the Observer for every Key known to its own Dictionary:

observable.addKeyedObserver(keys:Array(observer.keyValues.keys), observer)

The above would registerobserver withobservable for everykey contained inobserver'skeyValues dictionary.

Ultimately, you can register theobserver with theobservable for any keys you want:

observable.addKeyedObserver(key:"Foo", observer)

Let's output the initial values of all of our keys before we invoke any code that would modify their values:

for(key, value)in observer.keyValues{print("Key: '\(key)' has a value of '\(value)'")}

This would output:

Key: 'A' has a value of 'Hello'Key: 'B' has a value of 'Foo'

So, now that we can register the Keyed Observer with the Observer for whatever key or keys we wish, let's trigger the Observer Pattern in theobserver:

observable.setValue(key:"A","World")

The above will then update the value ifA from "Hello" to "World".

If we repeat the following code:

for(key, value)in observer.keyValues{print("Key: '\(key)' has a value of '\(value)'")}

This would output:

Key: 'A' has a value of 'World'Key: 'B' has a value of 'Foo'

Okay, so what if we change the value for key "C"? What will happen?

observable.setValue(key:"C","Pong")

Now, if we repeat the following code:

for(key, value)in observer.keyValues{print("Key: '\(key)' has a value of '\(value)'")}

This would output:

Key: 'A' has a value of 'World'Key: 'B' has a value of 'Foo'

Note that theobserver was not notified about the change to the value of keyC. This is becauseobserver is not observingobservable for changes to keyC.

This is the value of Keyed Observation Pattern. Put simply: not all Observations are meaningful to all Observers. So, as you have now seen, Keyed Observeration enables ourObservers to be notified specifically of changes relevant to thatObserver.

OverloadedaddObserver,removeObserver,addKeyedObserver, andremoveKeyedObserver methods

As of version 1.1.0, all useful combination overloads for the above-specified methods ofObservableClass,ObservableThread,ObservableThreadSafeClass,KeyedObservableClass,KeyedObservableThread, andKeyedObservableThreadSafeClass have been provided to streamline the adding and removal ofObservers with/from anObservable.

Adding a singleObserver to anObservable

observable.addObserver(myObserver)

Adding multipleObservers to anObservable

observable.addObserver([myObserver1, myObserver2, myObserver3])

Adding a singleKeyed Observer to aKeyed Observable with a singleKey

keyedObservable.addKeyedObserver("MyKey", myKeyedObserver)

Adding a singleKeyed Observer to aKeyed Observable with multipleKeys

keyedObservable.addKeyedObserver(["Key1","Key2","Key3"], myKeyedObserver)

Adding multipleKeyed Observers to aKeyed Observable with a singleKey

keyedObservable.addKeyedObserver("MyKey",[myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])

Adding multipleKeyed Observers to aKeyed Observable with multipleKeys

keyedObservable.addKeyedObserver(["Key1","Key2","Key3"],[myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])

removeObserver andremoveKeyedObserver also provide the same overloads as shown above.

Additional Useful Hints

There are a few additional useful things you should know about this Package.

A singleObservable can invokewithObservers for any number ofObserver Protocols

This library intentionally performs run-time type checks against each registeredObserver to ensure that it conforms to the explicitly-definedObserver Protocol being requested by yourwithObservers Closure method.

Simple example protocols:

protocolObserverProtocolA:AnyObject{func doSomethingForProtocolA()}protocolObserverProtocolB:AnyObject{func doSomethingForProtocolB()}

Which can then both be used by the sameObservableClass,ObservableThreadSafeClass, orObservableThread descendant:

withObservers{(observer:ObserverProtocolA)in    observer.doSomethingForProtocolA()}withObservers{(observer:ObserverProtocolB)in    observer.doSomethingForProtocolB()}

Any number ofObserver Protocols can be marshalled by any of ourObservable types, and onlyObservers conforming to the explicitly-specifiedObserver Protocol will be passed into yourwithObservers Closure method.

License

Observable is available under the MIT license. See theLICENSE file for more info.

Join us on Discord

If you require additional support, or would like to discussObservable, Swift, or any other topics related to Flowduino, you canjoin us on Discord.

About

The most flexible and easiest to implement Observer Pattern platform for the Swift language (includes fully-functional Observable Thread!)

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp