Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Commit

Permalink
Port ObservableObject implementation with Runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
kateinoigakukun committed Nov 8, 2020
1 parent 4977ca1 commit bccff3e
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ let package = Package(
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
],
dependencies: [.package(url: "https://github.com/MaxDesiatov/Runtime.git", from: "2.1.2")],
targets: [
.target(name: "COpenCombineHelpers"),
.target(
name: "OpenCombine",
dependencies: [
.target(name: "COpenCombineHelpers",
condition: .when(platforms: supportedPlatforms.except([.wasi])))
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
.product(name: "Runtime", package: "Runtime", condition: .when(platforms: [.wasi])),
],
exclude: [
"Publishers/Publishers.Encode.swift.gyb",
Expand Down
81 changes: 81 additions & 0 deletions Sources/OpenCombine/ObservableObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
// Created by Sergej Jaskiewicz on 08/09/2019.
//

#if canImport(Runtime)
import Runtime
#endif

/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` synthesizes an `objectWillChange` publisher that
Expand Down Expand Up @@ -42,10 +46,86 @@ public protocol ObservableObject: AnyObject {
var objectWillChange: ObjectWillChangePublisher { get }
}

#if swift(>=5.1) && canImport(Runtime)
private protocol _ObservableObjectProperty {
var objectWillChange: ObservableObjectPublisher? { get set }
}

extension _ObservableObjectProperty {

fileprivate static func installPublisher(
_ publisher: ObservableObjectPublisher,
on publishedStorage: UnsafeMutableRawPointer
) {
// It is safe to call assumingMemoryBound here because we know for sure
// that the actual type of the pointee is Self.
publishedStorage
.assumingMemoryBound(to: Self.self)
.pointee
.objectWillChange = publisher
}

fileprivate static func getPublisher(
from publishedStorage: UnsafeMutableRawPointer
) -> ObservableObjectPublisher? {
// It is safe to call assumingMemoryBound here because we know for sure
// that the actual type of the pointee is Self.
return publishedStorage
.assumingMemoryBound(to: Self.self)
.pointee
.objectWillChange
}
}

extension Published: _ObservableObjectProperty {}
#endif

extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
// swiftlint:disable let_var_whitespace
#if swift(>=5.1)
/// A publisher that emits before the object has changed.
#if canImport(Runtime)
public var objectWillChange: ObservableObjectPublisher {
var installedPublisher: ObservableObjectPublisher?
let info = try! typeInfo(of: Self.self)
for property in info.properties {
let storage = Unmanaged
.passUnretained(self)
.toOpaque()
.advanced(by: property.offset)

guard let fieldType = property.type as? _ObservableObjectProperty.Type else {
// Visit other fields until we meet a @Published field
continue
}

// Now we know that the field is @Published.
if let alreadyInstalledPublisher = fieldType.getPublisher(from: storage) {
installedPublisher = alreadyInstalledPublisher
// Don't visit other fields, as all @Published fields
// already have a publisher installed.
break
}

// Okay, this field doesn't have a publisher installed.
// This means that other fields don't have it either
// (because we install it only once and fields can't be added at runtime).
var lazilyCreatedPublisher: ObjectWillChangePublisher {
if let publisher = installedPublisher {
return publisher
}
let publisher = ObservableObjectPublisher()
installedPublisher = publisher
return publisher
}

fieldType.installPublisher(lazilyCreatedPublisher, on: storage)

// Continue visiting other fields.
}
return installedPublisher ?? ObservableObjectPublisher()
}
#else
@available(*, unavailable, message: """
The default implementation of objectWillChange is not available yet. \
It's being worked on in \
Expand All @@ -54,6 +134,7 @@ extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPu
public var objectWillChange: ObservableObjectPublisher {
fatalError("unimplemented")
}
#endif
#else
public var objectWillChange: ObservableObjectPublisher {
return ObservableObjectPublisher()
Expand Down

0 comments on commit bccff3e

Please sign in to comment.