Skip to content

Instantly share code, notes, and snippets.

@andersio
Last active December 16, 2016 18:19
Show Gist options
  • Select an option

  • Save andersio/a7b5eb9939ce3f99c29ec028456de4aa to your computer and use it in GitHub Desktop.

Select an option

Save andersio/a7b5eb9939ce3f99c29ec028456de4aa to your computer and use it in GitHub Desktop.

Unify Property types.

Since RAC 3.0, ReactiveSwift has a clear split between mutable properties and read-only properties, as represented by MutableProperty and AnyProperty. To achieve encapsulation, the pattern of wrapping a private MutableProperty in a public AnyProperty is usually encouraged.

public class Library {
	public private(set) lazy var books: Property<[Book]> = .init(self._books)
	private let _books = MutableProperty<[Book]>([])
}

However, it does not interoperate well with the native Swift experience of defining stored properties with access control modifiers.

Therefore, here comes a proposal (with its caveat) to address this issue. A special thanks to @robertjpayne who had brought it up before.

Unified Property type.

The new unified Property type:

  1. is a struct; but
  2. does not have pure value semantics.

Operating model

It behaves like a reference type, except with an exception to interoperate with the immutable model:

  1. A Property instance can have an immutable backing, or a mutable backing.
  2. Copies of a Property all refers to the same backing with one exception (rule 3).
  3. Mutating a copy with an immutable backing would replace its backing with a mutable one that observes the original immutable one.

Mutable properties

var books = Property<[Book]>([])

var books2 = book
let books3 = books

book2.value.append(Book())
assert(books3.value.count) == 1

Notice that:

  1. it is var instead of let — we are taking advantage of the Swift immutable model here.
  2. No matter how many copies are made, they all share the same backing.

Constant properties

let count = Property(constant: 1)

var count2 = count
count2.value += 2

This is just an optimization hint. If one mades a copy of count and mutate it, count still remains unchanged, while the copy creates a new mutable backing.

Composed properties

let price = viewModel.flatMap { $0.price }.map(priceFormatter.format)

var price2 = price
price2.value = "$0.00 FAKE SALE"

Similar to constant properties, mutating a copy of price would not change price in any way. The copy would create instead a new mutable backing that observes price.

Pros

It can now be controlled with ACL modifiers.

class Library {
	public private(set) var books = Property<[Book]>([])
}

It kinda behaves like stored properties now.

Passing it around with read-only restriction does not require explicit wrapping.

Cons

Cases that implicit Sharing would cause an issue.

For example, create a shadow copy and mutate the copy.

func decrement(_ books: Property<[Book]>) {
    var books = books
	books.append(Book())
}

The local books mutation would affect the Property if the property passed in has a mutable backing. The problem can be partially offset by encouraging the use of inout for passing mutable properties:

func decrement(_ books: inout Property<[Book]>) {
	books.append(Book())
}

This clarifies the intention.

The problem of implicit sharing still exists, however. We would have to educate users not to mutate properties that you do not own, and are not passed to you inout.

Alternatives

  1. Property behaviors with "out-of-band" behaviour members.
  2. Leave it as is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment