Skip to content

Commit

Permalink
Add Sendable conformance and support for Concurrency (#74)
Browse files Browse the repository at this point in the history
* Update CI

* Update project settings and fix new SwiftLint warnings

* Add Sendable conformance to UINotification

* make UINotification sendable

* Make more types sendable or conform to MainActor

* Change UINotificationCenter to work with a configuration instance and become MainActor

* Rewrite notificationQueue to become an actor instead

* Make init public

* Add test plan for UINotifications

* Fix tests to work with concurrency

* Remove @mainactor attribute to UINotificationCenter and make more types Sendable

* Mark notificationcenter as sendable

* Update the readme

* Remove double whitespace

* Remove unneeded yield

* Fix CI
  • Loading branch information
AvdLee authored Oct 17, 2023
1 parent 5b109b8 commit f57bed0
Show file tree
Hide file tree
Showing 25 changed files with 441 additions and 153 deletions.
71 changes: 71 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/UINotifications.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "UINotifications"
BuildableName = "UINotifications"
BlueprintName = "UINotifications"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:UINotifications.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "UINotifications"
BuildableName = "UINotifications"
BlueprintName = "UINotifications"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
10 changes: 8 additions & 2 deletions Example/UINotifications-Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -120,8 +120,9 @@
50042BC61F18BC85007209B7 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 1220;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = WeTransfer;
TargetAttributes = {
50042BCD1F18BC85007209B7 = {
Expand Down Expand Up @@ -166,6 +167,7 @@
/* Begin PBXShellScriptBuildPhase section */
5078C7891F18C5A1006EB23F /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down Expand Up @@ -216,6 +218,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down Expand Up @@ -249,6 +252,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -277,6 +281,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down Expand Up @@ -310,6 +315,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-concurrency-extras",
"repositoryURL": "https://github.com/pointfreeco/swift-concurrency-extras.git",
"state": {
"branch": null,
"revision": "ea631ce892687f5432a833312292b80db238186a",
"version": "1.0.0"
}
}
]
},
"version": 1
}
12 changes: 10 additions & 2 deletions Example/UINotifications-Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ enum NotificationStyle: UINotificationStyle {
}

var thumbnailSize: CGSize {
return .init(width: 50, height: 50)
return CGSize(width: 50, height: 50)
}

/// The height of the notification which applies on the notification view.
Expand Down Expand Up @@ -113,7 +113,15 @@ final class ViewController: UIViewController {
})
}

let notification = UINotification(content: UINotificationContent(title: title, subtitle: subtitle, image: image), style: style, action: action)
let notification = UINotification(
content: UINotificationContent(
title: title,
subtitle: subtitle,
image: image
),
style: style,
action: action
)

if addButtonSwitch.isOn {
let button = UIButton(type: .system)
Expand Down
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-concurrency-extras",
"repositoryURL": "https://github.com/pointfreeco/swift-concurrency-extras.git",
"state": {
"branch": null,
"revision": "ea631ce892687f5432a833312292b80db238186a",
"version": "1.0.0"
}
}
]
},
"version": 1
}
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ let package = Package(
products: [
.library(name: "UINotifications", targets: ["UINotifications"])
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras.git", from: "1.0.0")
],
targets: [
.target(name: "UINotifications", path: "Sources"),
.testTarget(
name: "UINotificationsTests",
dependencies: ["UINotifications"],
dependencies: [
"UINotifications",
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras")
],
path: "Tests",
resources: [.copy("Resources/iconToastChevron.png")]
)
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ manualDismissTrigger.trigger() // Dismiss
- Set your custom view on the `UINotificationCenter`:

```swift
UINotificationCenter.current.defaultNotificationViewType = MyCustomNotificationView.self
UINotificationCenter.current.configuration = UINotificationCenterConfiguration(
defaultNotificationViewType: MyCustomNotificationView.self
)
```

### Use a custom UIButton
Expand All @@ -163,7 +165,9 @@ Create a custom presenter to manage presentation and dismiss animations.
- Set your custom presenter on the `UINotificationCenter`:

```swift
UINotificationCenter.current.presenterType = MyCustomPresenter.self
UINotificationCenter.current.configuration = UINotificationCenterConfiguration(
presenterType: MyCustomPresenter.self
)
```

*Checkout `UINotificationEaseOutEaseInPresenter` for an example.*
Expand All @@ -174,7 +178,9 @@ By default, notifications which are already queued will not be queued again. Thi
To disable this setting:

```swift
UINotificationCenter.current.isDuplicateQueueingAllowed = true
UINotificationCenter.current.configuration = UINotificationCenterConfiguration(
isDuplicateQueueingAllowed: true
)
```

## Communication
Expand Down
40 changes: 30 additions & 10 deletions Sources/UINotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public protocol UINotificationAction {
}

/// Defines a style which will be applied on the notification view.
public protocol UINotificationStyle {
public protocol UINotificationStyle: Sendable {
var titleFont: UIFont { get }
var subtitleFont: UIFont { get }
var titleTextColor: UIColor { get }
Expand All @@ -39,6 +39,7 @@ public protocol UINotificationStyle {
}

/// Handles changes in UINotification
@MainActor
protocol UINotificationDelegate: AnyObject {
// Called when Notification is updated.
func didUpdateContent(in notificaiton: UINotification)
Expand All @@ -48,14 +49,22 @@ protocol UINotificationDelegate: AnyObject {
}

/// An UINotification which can be showed on top of the `UINavigationBar` and `UIStatusBar`
public final class UINotification: Equatable {
/// `@unchecked Sendable` as we synchronize access using a lock queue.
public final class UINotification: Equatable, @unchecked Sendable {

static let lockQueue = DispatchQueue(
label: "wetransfer.uinotification.lock.queue",
qos: .userInitiated,
target: .global(qos: .userInitiated)
)

/// Defines the height which will be applied on the notification view.
public enum Height {
public enum Height: Sendable {
case statusBar
case navigationBar
case custom(height: CGFloat)

@MainActor
internal var value: CGFloat {
switch self {
case .statusBar:
Expand All @@ -69,16 +78,23 @@ public final class UINotification: Equatable {
}

/// The content of the notification.
public var content: UINotificationContent

public var content: UINotificationContent {
Self.lockQueue.sync { notificationContent }
}

/// A private backup property to synchronize access using a lock queue.
private var notificationContent: UINotificationContent

/// The style of the notification which applies on the notification view.
public let style: UINotificationStyle

/// The button to display on the right side of the notification, if any.
/// Setting this property will add the button, even if the notification is already visible.
public var button: UIButton? {
didSet {
delegate?.didUpdateButton(in: self)
Task { @MainActor in
delegate?.didUpdateButton(in: self)
}
}
}

Expand All @@ -88,23 +104,27 @@ public final class UINotification: Equatable {
weak var delegate: UINotificationDelegate?

public init(content: UINotificationContent, style: UINotificationStyle = UINotificationSystemStyle(), action: UINotificationAction? = nil) {
self.content = content
self.notificationContent = content
self.style = style
self.action = action
}

/// Updates the content of the notification
public func update(_ content: UINotificationContent) {
self.content = content
delegate?.didUpdateContent(in: self)
Self.lockQueue.sync {
self.notificationContent = content
}
Task { @MainActor in
delegate?.didUpdateContent(in: self)
}
}

public static func == (lhs: UINotification, rhs: UINotification) -> Bool {
return lhs.content == rhs.content
}
}

public struct UINotificationContent: Equatable {
public struct UINotificationContent: Equatable, Sendable {
/// The title which will be showed inside the notification.
public let title: String

Expand Down
Loading

0 comments on commit f57bed0

Please sign in to comment.