Skip to content

The framework to enhance KVO usage in Swift. Supports iOS, macOS, and tvOS.

License

Notifications You must be signed in to change notification settings

NSGolova/KVOMagic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KVOMagic

The framework to enhance KVO usage in Swift.

Features

  • Array KVO
  • Affecting property wrapper
  • AddObserver with auto removing (obj-c runtime only)

Installing

Submodule

You can add this project as a submodule and incorporate KVOMagic.xcodeproj.

Cocoapods

// TODO

Swift Package Manager

  1. Using Xcode go to File > Swift Packages > Add Package Dependency
  2. Paste the project URL: https://github.com/radulov/KVOMagic.git
  3. Click on next and select the project target:
    • KVOMagic

If you have doubts, please, check the following links:

How to use

Creating Swift Packages

After successfully retrieved the package and added it to your project, just import KVOMagic and you can get the full benefits of it.

Binary

Grab last artifacts from GithubActions or from Release page

Usage (Obj-c runtime based)

Array KVO

You can add an observer for every object in an array by using a special keyPath prefix - "$". Typical usage: .arrayKVO + #keyPath(arrayOwner.arrayProperty.propertyOfObjectInArray)

For example you have such structure:

class MyObject: NSObject {
    @objc dynamic var intProperty: Int
}
class MyObjectsCollection: ArrayOwner {
    @objc dynamic var array: [MyObject]
}

You can add an observer for every object in array by one line:

let collection = MyObjectsCollection()
collection.startObserving(.arrayKVO + #keyPath(MyObjectsCollection.array.intProperty)) { _, _ in
    \\ Do some stuff here
}

So whenever intProperty will be changed, you will receive a callback. For everything to work, you need to subclass ArrayOwner and make sure all your properties support objc runtime.

Array KVO supports all kinds of usage, such as:

  • KeyPathForValuesAffecting
  • Mutable array
  • Affecting property wrapper
  • Nested observing

Affecting property wrapper

Property wrapper for encapsulating KeyPathForValuesAffecting() and support for swift KeyPath. It will also cache computed value and change it only on new KVO notifications.

Classical example:

class Contact: WrapperOwner {
    @objc dynamic var name: String?
    @objc dynamic var surname: String?
    
    @Computed2({ $0 + " " + $1 }, self, \.name, \.surname) @objc dynamic var fullname
}

There are four flavours:

@Computed1({ $0 }, self, \.keyPath) @objc dynamic var name
@Computed2({ $0, $1 }, self, \.keyPath1, \.keyPath2) @objc dynamic var name
@Computed3({ $0, $1, $2 }, self, \.keyPath1, \.keyPath2, \.keyPath3) @objc dynamic var name

These three are type-safe and use Swift KeyPath. Every time value for any of provided properties will change, the computing block will be called with an appropriate count of arguments.

@Computed({ `self` in }, self, #keyPath("stringKeypath")) @objc dynamic var name

This one is using String keyPath and will pass self as an argument to "computing block". This property wrapper can accept various count of affecting keyPaths.

AddObserver with auto removing

New observe() function analogue called startObserving(). The key differences are:

  • No need for storing an observer object. An observer will remove automatically in deinit().
  • Fixed self-observing crash. You can safely observe properties from the self object.
  • Fixed removing observer in .initial callback. You can safely call stopObserving() even in the first callback called immediately after startObserving()

There are two flavours:

func startObserving<Value>(_ keyPath: KeyPath<Self, Value>, _ owner: Any? = nil, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, KeyValueObservedChange<Value>) -> Void) {}

Typesafe variant with swift KeyPath.

func startObserving(_ keyPath: String, _ owner: Any? = nil, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, KeyValueObservedChange<Any>) -> Void)

Obj-c equivalent with string keyPath.

Code snippets

Copy content of the Code snippets folder to ‘~/Library/Developer/Xcode/UserData/CodeSnippets’.

Usage (Combine based)

Array KVO

Use @ObservableArray property wrapper to send notifications from owner for every change in any object in array. Example:

class Contact: ObservableObject {
    @Published var name: String?
    @Published var surname: String?
}
class ContactsOwner: ObservableObject {
    @ObservableArray var contacts = [Contact]()
}

let owner = ContactsOwner()
owner.contacts[2].name = "test" // This will send notification for `owner`

Note, owner and objects in array should conforms to ObservableObject

Affecting property wrapper

Use @FromArray property wrapper. It's syntax similar to @ComputedX. Example:

class Contact: ObservableObject {
    @Published var name: String?
}
class ContactsOwner: PureWrapperOwner {
    @ObservableArray var contacts = [Contact]()
    @FromArray({ contacts in contacts.first { $0.name == "John" }}, /ContactsOwner.$contacts) var anyJohn
}

let owner = ContactsOwner()
owner.contacts[2].name = "John" // This will send notification for `anyJohn`

Note:

  • owner should conforms to WrapperOwnerProtocol or PureWrapperOwner
  • objects in array should conforms to ObservableObject
  • array should use @ObservableArray

Samples

Check unit tests for the samples.

Requirements to Build the Project

  1. Pull this repository.
  2. Change Apple ID account in Signing&Capabilities;
  3. Specific macOS and Xcode versions (see KVOMagic.xcodeproj, Xcode 12.4, and MacOS 10.15.7 for now).

Development Process

Git Branching Model

Git-flow

Coding Style

Versioning

Every version number is described as:

x.y.z

Where:

  • x – major version;
  • y – minor version;
  • z – bugfix version.

About

The framework to enhance KVO usage in Swift. Supports iOS, macOS, and tvOS.

Resources

License

Stars

Watchers

Forks

Releases

No releases published