You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Address the rounds of feedback with some more behavioral refinements and an adjustment to the naming of Observed to now name as Observations (#2838)
* Address the rounds of feedback with some more behavioral refinements and an adjustment to the naming of `Observed` to now name as `Observations`
* Correct the next to last example to the latest mechansim's output
Picking the initializer apart first captures the current isolation of the
238
-
creation of the `Observed` instance. Then it captures a `Sendable` closure that
238
+
creation of the `Observations` instance. Then it captures a `Sendable` closure that
239
239
inherits that current isolation. This means that the closure may only execute on
240
240
the captured isolation. That closure is run to determine which properties are
241
241
accessed by using Observation's `withObservationTracking`. So any access to a
@@ -253,19 +253,19 @@ The closure has two other features that are important for common usage; firstly
253
253
the closure is typed-throws such that any access to that emission closure will
254
254
potentially throw an error if the developer specifies. This allows for complex
255
255
composition of potentially failable systems. Any thrown error will mean that the
256
-
`Observed` sequence is complete and loops that are currently iterating will
256
+
`Observations` sequence is complete and loops that are currently iterating will
257
257
terminate with that given failure. Subsequent calls then to `next` on those
258
258
iterators will return `nil` - indicating that the iteration is complete.
259
259
260
260
## Behavioral Notes
261
261
262
-
There are a number of scenarios of iteration that can occur. These can range from production rate to iteration rate differentials to isolation differentials to concurrent iterations. Enumerating all possible combinations is of course not possible but the following explanations should illustrate some key usages. `Observed` does not make unsafe code somehow safe - the concepts of isolation protection or exclusive access are expected to be brought to the table by the types involved. It does however require the enforcements via Swift Concurrency particularly around the marking of the types and closures being required to be `Sendable`. The following examples will only illustrate well behaved types and avoid fully unsafe behavior that would lead to crashes because the types being used are circumventing that language safety.
262
+
There are a number of scenarios of iteration that can occur. These can range from production rate to iteration rate differentials to isolation differentials to concurrent iterations. Enumerating all possible combinations is of course not possible but the following explanations should illustrate some key usages. `Observations` does not make unsafe code somehow safe - the concepts of isolation protection or exclusive access are expected to be brought to the table by the types involved. It does however require the enforcements via Swift Concurrency particularly around the marking of the types and closures being required to be `Sendable`. The following examples will only illustrate well behaved types and avoid fully unsafe behavior that would lead to crashes because the types being used are circumventing that language safety.
263
263
264
264
The most trivial case is where a single produce and single consumer are active. In this case they both are isolated to the same isolation domain. For ease of reading; this example is limited to the `@MainActor` but could just as accurately be represented in some other actor isolation.
@@ -353,7 +353,7 @@ The result of the observation may print the following output, but the primary pr
353
353
354
354
This case dropped the last value of the iteration because the accumulated differential exceeded the production; however the potentially confusing part here is that the sleep in the iterate competes with the scheduling in the emitter. This becomes clearer of a relationship when the boundaries of isolation are crossed.
355
355
356
-
Observed can be used across boundaries of concurrency. This is where the iteration is done on a different isolation than the mutations. The types however are accessed always in the isolation that the creation of the Observed closure is executed. This means that if the `Observed` instance is created on the main actor then the subsequent calls to the closure will be done on the main actor.
356
+
Observations can be used across boundaries of concurrency. This is where the iteration is done on a different isolation than the mutations. The types however are accessed always in the isolation that the creation of the Observations closure is executed. This means that if the `Observations` instance is created on the main actor then the subsequent calls to the closure will be done on the main actor.
357
357
358
358
```swift
359
359
@globalActor
@@ -362,7 +362,7 @@ actor ExcplicitlyAnotherActor: GlobalActor {
@@ -402,20 +402,20 @@ The values still will be conjoined as expected for their changes, however just l
402
402
403
403
If the `iterate` function was altered to have a similar `sleep` call that exceeded the production then it would result in similar behavior of the previous producer/consumer rate case.
404
404
405
-
The next behavioral illustration is the value distribution behaviors; this is where two or more copies of an `Observed` are iterated concurrently.
405
+
The next behavioral illustration is the value distribution behaviors; this is where two or more copies of an `Observations` are iterated concurrently.
@@ -452,18 +452,64 @@ This situation commonly comes up when the asynchronous sequence is stored as a p
452
452
453
453
```
454
454
A 0 0
455
+
B 0 0
455
456
B 1 1
456
457
A 1 1
457
-
B 2 2
458
458
A 2 2
459
+
B 2 2
459
460
A 3 3
460
461
B 3 3
461
-
A 4 4
462
462
B 4 4
463
+
A 4 4
463
464
```
464
465
465
466
The same rate commentary applies here as before but an additional wrinkle is that the delivery between the A and B sides is non-determinstic (in some cases it can deliver as A then B and other cases B then A).
466
467
468
+
There is one additional clarification of expected behaviors - the iterators should have an initial state to determine if that specific iterator is active yet or not. This means that upon the first call to next the value will be obtained by calling into the isolation of the constructing closure to "prime the pump" for observation and obtain a first value. This can be encapsulated into an exaggerated test example as the following:
469
+
470
+
```swift
471
+
472
+
@MainActor
473
+
funcexample() async {
474
+
let person =Person(firstName: "0", lastName: "0")
475
+
476
+
// @MainActor is captured here as the isolation
477
+
let names =Observations {
478
+
person.name
479
+
}
480
+
Task {
481
+
tryawait Task.sleep(for: .seconds(2))
482
+
person.firstName="1"
483
+
person.lastName="1"
484
+
485
+
}
486
+
Task {
487
+
forawait name in names {
488
+
print("A = \(name)")
489
+
}
490
+
}
491
+
Task {
492
+
forawait name in names {
493
+
print("B = \(name)")
494
+
}
495
+
}
496
+
try?await Task.sleep(for: .seconds(10))
497
+
}
498
+
499
+
awaitexample()
500
+
```
501
+
502
+
Which results in the following output:
503
+
504
+
```
505
+
A = 0 0
506
+
B = 0 0
507
+
B = 1 1
508
+
A = 1 1
509
+
```
510
+
511
+
This ensures the first value is produced such that every sequence will always be primed with a value and will eventually come to a mutual consistency to the values no matter the isolation.
512
+
467
513
## Effect on ABI stability & API resilience
468
514
469
515
This provides no alteration to existing APIs and is purely additive. However it
@@ -478,19 +524,19 @@ need to be disambiguated.
478
524
This proposal does not change the fact that the spectrum of APIs may range from
479
525
favoring `AsyncSequence` properties to purely `@Observable` models. They both
480
526
have their place. However the calculus of determining the best exposition may
481
-
be slightly more refined now with `Observed`.
527
+
be slightly more refined now with `Observations`.
482
528
483
529
If a type is representative of a model and is either transactional in that
484
530
some properties may be linked in their meaning and would be a mistake to read
485
531
in a disjoint manner (the tearing example from previous sections), or if the
486
532
model interacts with UI systems it now more so than ever makes sense to use
487
-
`@Observable` especially with `Observed` now as an option. Some cases may have
533
+
`@Observable` especially with `Observations` now as an option. Some cases may have
488
534
previously favored exposing those `AsyncSequence` properties and would now
489
-
instead favor allowing the users of those APIs compose things by using `Observed`.
535
+
instead favor allowing the users of those APIs compose things by using `Observations`.
490
536
The other side of the spectrum will still exist but now is more strongly
491
537
relegated to types that have independent value streams that are more accurately
492
538
described as `AsyncSequence` types being exposed. The suggestion for API authors
493
-
is that now with `Observed` favoring `@Observable` perhaps should take more
539
+
is that now with `Observations` favoring `@Observable` perhaps should take more
494
540
of a consideration than it previously did.
495
541
496
542
## Alternatives Considered
@@ -527,3 +573,14 @@ parameter of an isolation that was non-nullable this could be achieved for that
527
573
however up-coming changes to Swift's Concurrency will make this approach less appealing.
528
574
If this route would be taken it would restrict the potential advanced uses cases where
529
575
the construction would be in an explicitly non-isolated context.
576
+
577
+
A name of `Observed` was considered, however that type name led to some objections that
578
+
rightfully claimed it was a bit odd as a name since it is bending the "nouning" of names
579
+
pretty strongly. This lead to the alternate name `Observations` which strongly leans
580
+
into the plurality of the name indicating that it is more than one observation - lending
581
+
to the sequence nature.
582
+
583
+
It was seriously considered during the feedback to remove the initializer methods and only
584
+
have construction by two global functions named `observe` and `observeUntilFinished`
585
+
that would act as the current initializer methods. Since the types must still be returned
586
+
to allow for storing that return into a property it does not offer a distinct advantage.
0 commit comments