I am excited by the general direction but I have some concerns about the scope 
of the design at this time; specifically, it seems like it would benefit a lot 
from having some flexible-and-efficient way for behaviors to “reach upward” 
back into their container from within their custom methods (without forcing the 
caller to pass-in the container to each such call, of course).

I took a stab at mocking up one of the behaviors I’d like to be able to write 
and hit a lot of roadblocks due to the above; I’ve included it below with some 
commentary. 

Even though this is perhaps a rather extreme/niche behavior to want to 
implement, I think the issues it encountered are actually general enough that 
other useful behaviors will also encounter them under the proposal as sketched.

Here’s the sample use, starting with motivation.

For some APIs — e.g. CoreBluetooth — you often wind up with highly-stateful 
objects that receive callbacks on a specific queue, and typically also do their 
state-maintenance while on that same queue; these objects typically also have a 
“public API” with methods that are only meant for use while off the queue (e.g. 
from the main queue, to update the UI…).

You thus wind up with each method-and-property pretty clearly being one and 
only one of these:

- “on-queue”, e.g. *only* meant for use while on the object’s queue
- “off-queue”, e.g. *only* meant for use while off the object’s queue

…with concurrency-and-synchronization logic essentially amounting to only 
calling / using each method-and-property while appropriately on/off queue.

For a concrete example, for an implementer of CBCentralManagerDelegate:

- all the CBCentralManagerDelegate methods are "on-queue"
- all the BT-state-management methods (called in reaction to BT events) are 
also “on-queue”
- the public methods (e.g. for UI use, or for asking the object to do stuff) 
are “off-queue”
- some of the basic properties (status, is-peripheral-foo-connected?) are 
oddballs, and get:
  - private backing properties for use/update while on-queue
  - public off-queue accessors that do a dispatch_sync, read the backing 
property, and return it

…and so on.

This can all be handled today "by hand” — it just requires being careful — but 
it’d be nice to have a custom behavior that would streamline both the 
implementation of on/off queue access for properties, and make each site-of-use 
more self-documenting/self-verifying vis-a-vis on/off-queue status.

Here’s my best attempt (issues flagged in ALL-CAPS):

/// Object assumed to have private queue it uses for synchronization.
protocol PrivateQueueOwner : class {

  // we don’t want to leak the actual queue to the wider world,
  // so we have to bubble these up to the public API:
  func dispatchSync<R>(@noescape action: () -> R) -> R
  func dispatchBarrierSync<R>(@noescape action: () -> R) -> R
  func dispatchAsync(action: () -> ())
  func dispatchBarrierAsync(action: () -> ())

  // we assume we are managing our queues s.t. we can
  // actually get the below to work reliably:
  func isOnPrivateQueue() -> Bool

}

/// Behavior used to enforce a particular use-pattern around
/// a property of an object that uses a private queue for synchronization:
struct QueueAccess<Value> {
  var value: Value
  
  // THIS PART IS ONLY-KINDA OK:
  subscript<Container:PrivateQueueOwner>(varIn container: Container> {
    get {
      if container.isOnPrivateQueue() {
        return self.value
      } else {
        return self.container.dispatchSync() {
          return self.value
          // ^ DOES THIS ACTUALLY WORK AS I’D WANT, IF I AM A STRUCT?
        }
      }
    }
    set {
      if container.isOnPrivateQueue() { 
        self.value = newValue
      } else {
        container.dispatchBarrierAsync() {
          self.value = newValue
          // ^ DOES THIS ACTUALLY WORK AS I’D WANT, IF I AM A STRUCT?
        }
      }
    }
  }

  // EVERYTHING FROM HERE ON DOWN IS MOSTLY PROBLEMATIC:

  func onQueueUpdate(newValue: Value) { 
    assert(self.container.isOnPrivateQueue()) // <- HOW?
    self.value = newValue
  }
  
  func offQueueUpdate(newValue: Value) {
    assert(self.container.isOffPrivateQueue()) // <- HOW?
    self.container.dispatchBarrierAsync() { // <- HOW?
       self.value = newValue
       // ^ DOES THIS EVEN WORK IF I AM A STRUCT?   
    }
  }

  func offQueueAccess() -> Value {
    assert(self.container.isOffPrivateQueue()) // <- HOW?
    return self.container.dispatchSync() { // <- HOW?
      return self.value
    }
  }

  func onQueueAcccess() -> Value {
    assert(self.container.isOnPrivateQueue()) // <- HOW?
    return self.value
  }

  func offQueueAccess<R>(@noescape transform: (Value) -> R) -> R {
    assert(self.container.isOffPrivateQueue()) // <- HOW?
    return self.container.dispatchSync() { // <- HOW?
      return transform(self.value)
    }
  }

  func onQueueAcccess<R>(@noescape transform: (Value) -> R) -> R {
    assert(self.container.isOnPrivateQueue()) // <- HOW?
    return transform(self.value)
  }

}

….which if it was implementable, would wind up used like so:

public class BTCentralManagerController : NSObject, CBCentralManagerDelegate, 
PrivateQueueOwner {

  internal lazy var centralManager: CBCentralManager = CBCentralManager(
    delegate: self, 
    queue: self.privateQueue, 
    options: self.centralManagerOptions()
  )
  
  private let privateQueue: dispatch_queue_t 
  
  public private(set) var (queueAccess) centralManagerState: 
CBCentralManagerState = .Unknown
  internal private(set) var (queueAccess) peripheralControllers: 
[NSUUID:BTPeripheralController] = [:]
  
  // internal API sample:

  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    self.centralManagerState.queueAccess.onQueueUpdate(central.state)
  }
 
  // public API sample

  public func peripheralControllerForUUID(uuid: NSUUID) -> 
BTPeripheralController? {
    // this is an explicitly “off-queue” method:
    self.currentState.queueAccess.offQueueAccess() {
      return $0[uuid]
    }
  }

}

…giving us:

- safe defaults for attempted direct-property access
- self-dcoumenting/self-validating customized getters-and-setters for all 
internal-use scenarios

But, as the # of ALL-CAPS comments should indicate, this behavior seems *well 
beyond* what the proposal can provide in a natural way (e.g. we can get closer 
by passing-in the container to each method, but that’s a lot less natural and a 
lot clunkier).
 
Moreover, even if the “ability to access the container” were to be addressed, I 
also don’t like having to use a protocol like `PrivateQueueOwner` to make my 
`queueAccess` behavior re-usable; at least at present, adopted-protocol 
visibility is the same visibility as the type itself, so that e.g. if a public 
class conforms to `PrivateQueueOwner` then all the methods in 
`PrivateQueueOwner` are also public.

This is undesirable, as although I’d want such classes to be public, I wouldn’t 
want such low-level implementation details to be part of their public API.

Ideally, rather than forcing the container to adopt a protocol, I could instead 
do something *more* like this:

public class BTCentralManagerController : NSObject, CBCentralManagerDelegate {

  private let privateQueue: dispatch_queue_t 
  
  // configures `queueAccess` behavior to use `self.privateQueue` (with 
on-queue check also as-specified)
  // ...note that if this becomes possible, the syntax might need to change, 
b/c the below is not very readable!
  public private(set) var (queueAccess(queue: `self.privateQueue`, onQueue: 
`self.isOnPrivateQueue()`)) currentState: CBCentralManagerState = .Unknown
  internal private(set) var (queueAccess(queue: `self.privateQueue`, onQueue: 
`self.isOnPrivateQueue()`)) peripheralControllers: 
[NSUUID:BTPeripheralController] = [:]

}

// which somehow interacts with a declaration like this:
struct QueueAccess<Value> {

  var value: Value

  // declaration stating we expect a `queue` expression during configuration, 
with the 
  // following type (and the accessor automagically-synthesized via compiler 
magic)
  container var queue: dispatch_queue_t { get }

  // declaration stating we expect an `onQueue` expression during 
configuration, with the 
  // following type (and the implementation automagically-synthesized via 
compiler magic)
  container func onQueue() -> Bool

   func onQueueUpdate(newValue: Value) { 
    assert(self.onQueue()) 
    self.value = newValue
  }
  
  func offQueueUpdate(newValue: Value) {
    assert(!self.onQueue())
    dispatch_barrier_async(self.queue) {
      self.value  = newValue
      // ^ NOTE: this may still be a problem for struct-based behaviors...?
    }
  }
 
  // etc...
 
}

…which, if possible, would obviously make behaviors a lot more specialized than 
they are under the current proposal (e.g. they would seemingly need a lot of 
specialized help from the compiler to be able to access those variables without 
either creating a strong reference to the container or wasting a lot of space 
with redundant stored properties, and might be their own specialized type, 
rather than an otherwise-vanilla struct-or-class as per the current proposal). 

But, if the above were possible, the behaviors would be a *lot* more re-usable, 
and it’d be unnecessary to have the container adopt a particular protocol.

Note also that even if the “unwanted API pollution” issue were resolved — e.g. 
by making it possible to somehow privately-adopt a protocol, or equivalent — 
there’d still be the issue of getting efficient access to the container to 
address, if these use cases are to be supported.

So that’s my reaction; if you read it this far, thanks for your attention.

I’d *completely* understand if the reaction here is simply that such uses are 
out-of-scope for this proposal; that seems perfectly reasonable!

But keep in mind, the general issue of reaching-up from custom methods of 
behaviors can show up in simpler contexts:

// convenience logic:
private extension UIViewController {

  func viewHasEnteredWindow() -> Bool {
     return self.viewIfLoaded()?.window != nil ?? false
  }
}

// custom behavior:
struct MustSetBeforeVisibility<Value> {
  value: Value?
  
  // THIS PART OK:
  subscript<Container:UIViewController>(varIn container: Container> -> Value? {
    get {
      if container.viewHasEnteredWindow() {
        guard let v = self.value else { 
          fatalError(“Should’ve set property \(self) by now, b/c our VC’s view 
is in the window (vc: \(container))”)
        }
        return v
      } else {
        return self.value // perfectly-cromulent to be unset at this point in 
lifecycle
      }
    }
    set {
      if !container.viewHasEnteredWindow() {
        self.value = newValue
      } else {
        fatalError(“Not meant to be set after we have become 
potentially-visible!")
      }
    }
  }

  // HOW TO DO THIS PART:
  /// non-optional convenience accessor; only meant for use once our view has 
  /// become potentially-visible
  func direct() -> Value {
    if !container.viewHasEnteredWindow() { // <- HOW?
       fatalError(“Trying to do direct-access on \(self) too soon!")
    } else if let v = self.value {
       return v
    } else {
       fatalError(“Trying to do direct-access, but never set the value for 
\(self) appropriately!")
    }
  }

}

…which is basically another take on a “smarter” implicitly-unwrapped-optional. 
You don’t *need* a function like `direct()`, but you might want it, and it 
might be nice to be able to differentiate “using too soon” and “forgot to set 
the value”.


> On Dec 17, 2015, at 11:37 AM, Joe Groff via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Hi everyone. Chris stole my thunder already—yeah, I've been working on a 
> design for allowing properties to be extended with user-defined delegates^W 
> behaviors. Here's a draft proposal that I'd like to open up for broader 
> discussion. Thanks for taking a look!
> 
> -Joe
> 
> https://gist.github.com/jckarter/f3d392cf183c6b2b2ac3 
> <https://gist.github.com/jckarter/f3d392cf183c6b2b2ac3>
> 
> Lazy
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#lazy>
> Memoization
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#memoization>
> Delayed Initialization
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#delayedinitialization>
> Resettable properties
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#resettableproperties>
> Synchronized Property Access
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#synchronizedpropertyaccess>
> NSCopying
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#nscopying>
> Referencing Properties with Pointers
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#referencingpropertieswithpointers>
> Property Observers
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#propertyobservers>
> Detailed design
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#detaileddesign>
> Impact on existing code
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#impactonexistingcode>
> Alternatives considered/to consider
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#alternativesconsideredtoconsider>Declaration
>  syntax
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#declarationsyntax>
> Syntax for accessing the backing property
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#syntaxforaccessingthebackingproperty>
> Defining behavior requirements using a protocol
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#definingbehaviorrequirementsusingaprotocol>
> A behavior declaration
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#abehaviordeclaration>
> Naming convention for behaviors
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#namingconventionforbehaviors>
> TODO
>  
> <file:///Users/jgroff/src/s/swift-evolution/proposals/XXXX-property-delegates.md#todo>
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to