Skip to content
145 changes: 68 additions & 77 deletions Proposals/0011-concurrency-safe-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,36 +209,37 @@ For `MainActorMessage`:
@available(FoundationPreview 0.5, *)
extension NotificationCenter {
// e.g. addObserver(of: workspace, for: .willLaunchApplication) { message in ... }
public func addObserver<I: MessageIdentifier, M: MainActorMessage>(of subject: M.Subject,
for identifier: I,
using observer: @escaping @MainActor (M) -> Void)
-> ObservationToken where I.MessageType == M,
M.Subject: AnyObject
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
of subject: Message.Subject,
for identifier: Identifier,
using observer: @escaping @MainActor (Message) -> Void
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: AnyObject

public func addObserver<I: MessageIdentifier, M: MainActorMessage>(of subject: M.Subject,
for identifier: I,
using observer: @escaping @MainActor (M) -> Void)
-> ObservationToken where I.MessageType == M,
M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
of subject: Message.Subject,
for identifier: Identifier,
using observer: @escaping @MainActor (Message) -> Void
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier

// e.g. addObserver(of: NSWorkspace.self, for: .willLaunchApplication) { message in ... }
public func addObserver<I: MessageIdentifier, M: MainActorMessage>(of subject: M.Subject.Type,
for identifier: I,
using observer: @escaping @MainActor (M) -> Void)
-> ObservationToken where I.MessageType == M
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
of subject: Message.Subject.Type,
for identifier: Identifier,
using observer: @escaping @MainActor (Message) -> Void
) -> ObservationToken where Identifier.MessageType == Message

// e.g. addObserver(for: NSWorkspace.WillLaunchApplication.self) { message in ... }
public func addObserver<M: MainActorMessage>(of subject: M.Subject? = nil,
for messageType: M.Type,
using observer: @escaping @MainActor (M) -> Void)
-> ObservationToken where M.Subject: AnyObject

public func addObserver<M: MainActorMessage>(of subject: M.Subject? = nil,
for messageType: M.Type,
using observer: @escaping @MainActor (M) -> Void)
-> ObservationToken where M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier
public func addObserver<Message: MainActorMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
using observer: @escaping @MainActor (Message) -> Void
) -> ObservationToken where Message.Subject: AnyObject

public func addObserver<Message: MainActorMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
using observer: @escaping @MainActor (Message) -> Void
) -> ObservationToken where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
}
```

Expand All @@ -247,34 +248,35 @@ And for `AsyncMessage`:
```swift
@available(FoundationPreview 0.5, *)
extension NotificationCenter {
public func addObserver<I: MessageIdentifier, M: AsyncMessage>(of subject: M.Subject,
for identifier: I,
using observer: @escaping @Sendable (M) async -> Void)
-> ObservationToken where I.MessageType == M,
M.Subject: AnyObject

public func addObserver<I: MessageIdentifier, M: AsyncMessage>(of subject: M.Subject,
for identifier: I,
using observer: @escaping @Sendable (M) async -> Void)
-> ObservationToken where I.MessageType == M,
M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier

public func addObserver<I: MessageIdentifier, M: AsyncMessage>(of subject: M.Subject.Type,
for identifier: I,
using observer: @escaping @Sendable (M) async -> Void)
-> ObservationToken where I.MessageType == M
public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
of subject: Message.Subject,
for identifier: Identifier,
using observer: @escaping @Sendable (Message) async -> Void
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: AnyObject

public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
of subject: Message.Subject,
for identifier: Identifier,
using observer: @escaping @Sendable (Message) async -> Void
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier

public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
of subject: Message.Subject.Type,
for identifier: Identifier,
using observer: @escaping @Sendable (Message) async -> Void
) -> ObservationToken where Identifier.MessageType == Message

public func addObserver<M: AsyncMessage>(of subject: M.Subject? = nil,
for messageType: M.Type,
using observer: @escaping @Sendable (M) async -> Void)
-> ObservationToken where M.Subject: AnyObject
public func addObserver<Message: AsyncMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
using observer: @escaping @Sendable (Message) async -> Void
) -> ObservationToken where Message.Subject: AnyObject

public func addObserver<M: AsyncMessage>(of subject: M.Subject? = nil,
for messageType: M.Type,
using observer: @escaping @Sendable (M) async -> Void)
-> ObservationToken where M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier
public func addObserver<Message: AsyncMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
using observer: @escaping @Sendable (Message) async -> Void
) -> ObservationToken where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
}
```

Expand Down Expand Up @@ -302,40 +304,31 @@ extension NotificationCenter {
of subject: Message.Subject,
for identifier: Identifier,
bufferSize limit: Int = 10
)
-> some AsyncSequence<Message, Never> where Identifier.MessageType == Message,
Message.Subject: AnyObject
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message, Message.Subject: AnyObject

public func messages<Identifier: MessageIdentifier, Message: AsyncMessage>(
of subject: Message.Subject,
for identifier: Identifier,
bufferSize limit: Int = 10
)
-> some AsyncSequence<Message, Never> where Identifier.MessageType == Message,
Message.Subject: Identifiable,
Message.Subject.ID == ObjectIdentifier {}
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier {}

public func messages<Identifier: MessageIdentifier, Message: AsyncMessage>(
of subject: Message.Subject.Type,
for identifier: Identifier,
bufferSize limit: Int = 10
)
-> some AsyncSequence<Message, Never> where Identifier.MessageType == Message
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message

public func messages<Message: AsyncMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
bufferSize limit: Int = 10
)
-> some AsyncSequence<Message, Never> where Message.Subject: AnyObject
) -> some AsyncSequence<Message, Never> where Message.Subject: AnyObject

public func messages<Message: AsyncMessage>(
of subject: Message.Subject? = nil,
for messageType: Message.Type,
bufferSize limit: Int = 10
)
-> some AsyncSequence<Message, Never> where Message.Subject: Identifiable,
Message.Subject.ID == ObjectIdentifier
) -> some AsyncSequence<Message, Never> where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
}
```

Expand Down Expand Up @@ -366,27 +359,25 @@ extension NotificationCenter {
// MainActorMessage post()

@MainActor
public func post<M: MainActorMessage>(_ message: M, subject: M.Subject)
where M.Subject: AnyObject
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject)
where Message.Subject: AnyObject

@MainActor
public func post<M: MainActorMessage>(_ message: M, subject: M.Subject)
where M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject)
where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier

@MainActor
public func post<M: MainActorMessage>(_ message: M, subject: M.Subject.Type = M.Subject.self)
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject.Type = Message.Subject.self)

// AsyncMessage post()

public func post<M: AsyncMessage>(_ message: M, subject: M.Subject)
where M.Subject: AnyObject
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject)
where Message.Subject: AnyObject

public func post<M: AsyncMessage>(_ message: M, subject: M.Subject)
where M.Subject: Identifiable,
M.Subject.ID == ObjectIdentifier
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject)
where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier

public func post<M: AsyncMessage>(_ message: M, subject: M.Subject.Type = M.Subject.self)
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject.Type = Message.Subject.self)
}
```

Expand Down
77 changes: 77 additions & 0 deletions Proposals/0026-preferredLocales.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Locale.preferredLocales

* Proposal: [SF-0026](0026-preferredLocales.md)
* Authors: [करन मिश्र · Karan Miśra](https://github.com/karan-misra)
* Review Manager: TBD
* Status: **Awaiting review**
* Review: ([pitch](https://forums.swift.org/t/pitch-introduce-locale-preferredlocales/79900))

## Introduction

Add `Locale.preferredLocales` as an alternative to `Locale.preferredLanguages` that returns `[Locale]` instead of `[String]`.

## Motivation

Currently, `Locale.preferredLanguages` is the only way to retrieve the list of languages that the user has specified in Language & Region settings. This follows its predecessor `+[NSLocale preferredLanguages]` and returns an array of `String`s instead of `Locale`s. Processing and manipulating strings is complex and errorprone for clients.

This proposal introduces `Locale.preferredLocales` as a way to retrieve the same information, but in the form of an array of `Locale`s which will allow clients to use the information more easily and with fewer errors, specifically when used to customize the presentation of data within their apps such that content in the user’s preferred languages is more prominent.

## Proposed solution

We propose adding `preferredLocales` as a static variable on `Locale`, similarly to `preferredLanguages`. One of the primary use cases is to allow apps to build language selection menus in which the user’s preferred locales are bubbled up to the top. This can be achieved with the proposed `preferredLocales` API as follows:

```swift
// When building a language selection menu, `matchedLocales` would be shown at the top, and `otherLocales` would be shown below, with a visual divider.
var matchedLocales = []
var otherLocales = []
let availableLocales = // ... array of Locale objects ...
for locale in availableLocales {
var foundMatch = false
for preferredLocale in preferredLocales {
if locale.language.isEquivalent(to: preferredLocale.language) {
matchedLocales.append(locale)
foundMatch = true
break
}
}
if !foundMatch {
otherLocales.append(locale)
}
}
```

## Detailed design

```swift
public struct Locale : Hashable, Equatable, Sendable {

/// Returns a list of the user’s preferred locales, as specified in Language & Region settings, taking into account any per-app language overrides.
@available(FoundationPreview 6.2, *)
public static var preferredLocales: [Locale]
}
```

## Source compatibility

There is no impact on source compatibility or existing code.

## Implications on adoption

This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility.

## Future directions

In order to further support the use case of building language selection UIs, we can consider adding convenience functions on `Locale` that allow sorting and splitting a list of available `Locale`s into `preferred` and `remaining`, which can then be used to directly populate the UI.

```swift
public static func sorted(_ available: [Locale]) -> (preferred: [Locale], remaining: [Locale])
```

We can also consider adding APIs that work with `Locale.Language` in addition to `Locale` since in many use cases, the developer is handling a list of languages does not need the additional functionality in `Locale`.

Lastly, we can choose to deprecate `Locale.preferredLanguages` since it returns the same information but using `String` which is not a good container for a language identifier and leads to incorrect usage.

## Alternatives considered

* Naming-wise, another possibility was `Locale.preferred`. However, following the current naming convention, this would be confused as returning `Locale` and not `[Locale]`. Additionally, it would be best to keep `Locale.preferred` open in case we need a way to get the first, most preferred `Locale` in the future.
* Deprecate `preferredLanguages` and encourage developers to only use `preferredLocales`.
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ extension AttributeContainer {

@available(FoundationPreview 6.2, *)
extension AttributeContainer {
/// Returns an attribute container storing only the attributes in `self` with the `inheritedByAddedText` property set to `true`
/// Returns a copy of the attribute container with only attributes that specify the provided inheritance behavior.
/// - Parameter inheritedByAddedText: An `inheritedByAddedText` value to filter. Attributes matching this value are included in the returned container.
/// - Returns: A copy of the attribute container with only attributes whose `inheritedByAddedText` property matches the provided value.
public func filter(inheritedByAddedText: Bool) -> AttributeContainer {
var storage = self.storage
for (key, value) in storage.contents {
Expand All @@ -120,9 +122,9 @@ extension AttributeContainer {
return AttributeContainer(storage)
}

/// Returns an attribute container storing only the attributes in `self` with a matching run boundary property
///
/// Note: if `nil` is provided then only attributes not bound to any particular boundary will be returned
/// Returns a copy of the attribute container with only attributes that have the provided run boundaries.
/// - Parameter runBoundaries: The required `runBoundaries` value of the filtered attributes. If `nil` is provided, only attributes not bound to any specific boundary will be returned.
/// - Returns: A copy of the attribute container with only attributes whose `runBoundaries` property matches the provided value.
public func filter(runBoundaries: AttributedString.AttributeRunBoundaries?) -> AttributeContainer {
var storage = self.storage
for (key, value) in storage.contents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ extension AttributeScope {
Self.scopeDescription.markdownAttributes
}

/// A list of all attribute keys contained within this scope and any sub-scopes.
@available(FoundationPreview 6.2, *)
public static var attributeKeys: some Sequence<any AttributedStringKey.Type> {
Self.scopeDescription.attributes.values
Expand Down
Loading