Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for complete strict concurrency and Swift 6 migration #456

Draft
wants to merge 25 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ea93b87
Fixed some crashes
martinmitrevski Jul 3, 2024
d7dc395
Compiling on both Xcodes
martinmitrevski Jul 3, 2024
5863a99
Fixed some crashes
martinmitrevski Jul 4, 2024
8367da6
Fixed tests
martinmitrevski Jul 4, 2024
fb4f338
Small fix for Swift6
martinmitrevski Jul 4, 2024
b807384
Fixed a crash
martinmitrevski Jul 4, 2024
776ed9d
Merge branch 'develop' of https://github.com/GetStream/stream-video-s…
martinmitrevski Jul 8, 2024
bd7393b
Replaced usage of dispatchqueue.main with main actor task
martinmitrevski Jul 9, 2024
98f5941
Removed executeOnMain helper
martinmitrevski Jul 9, 2024
11d4888
PR remarks
martinmitrevski Jul 9, 2024
14cd09b
Merge branch 'develop' of https://github.com/GetStream/stream-video-s…
martinmitrevski Jul 9, 2024
ad54f07
Fixed issues with the latest Xcode beta
martinmitrevski Jul 10, 2024
6934f20
Fixed LLC warnings
martinmitrevski Jul 10, 2024
84c548a
Fixed some warnings
martinmitrevski Jul 10, 2024
edf9fb9
Updated Xcode version for CI
martinmitrevski Jul 10, 2024
e193941
Fixed some warnings
martinmitrevski Jul 11, 2024
c06c002
Merged develop
martinmitrevski Jul 23, 2024
ea9e454
PR remarks
martinmitrevski Jul 23, 2024
02dd40b
PR remarks
martinmitrevski Jul 23, 2024
35809ba
PR remarks
martinmitrevski Jul 25, 2024
2ae5031
Fixed some warnings
martinmitrevski Jul 26, 2024
da5fd90
Changed the smoke checks Xcode version to 15.3
martinmitrevski Jul 30, 2024
abf3932
Merge branch 'develop' of https://github.com/GetStream/stream-video-s…
martinmitrevski Jul 30, 2024
3f1bc36
Some fixes
martinmitrevski Jul 30, 2024
0be8e17
Fixed tests
martinmitrevski Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions DemoApp/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import StreamVideo
import SwiftUI
import UIKit

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
class AppDelegate: NSObject, UIApplicationDelegate {

@Injected(\.streamVideo) var streamVideo

Expand Down Expand Up @@ -95,15 +95,16 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
// MARK: - Private Helpers

private func setUpRemoteNotifications() {
UNUserNotificationCenter
.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
Task { @MainActor in
do {
let granted = try await UNUserNotificationCenter.current().requestAuthorization()
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
UIApplication.shared.registerForRemoteNotifications()
}
} catch {
log.error("Error requesting authorization: \(error.localizedDescription)")
}
}
}

private func setUpPerformanceTracking() {
Expand All @@ -118,3 +119,9 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
}
}
}

#if swift(>=6.0)
extension AppDelegate: @preconcurrency UNUserNotificationCenterDelegate {}
#else
extension AppDelegate: UNUserNotificationCenterDelegate {}
#endif
28 changes: 14 additions & 14 deletions DemoApp/Sources/Components/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension AppEnvironment {
var isTest: Bool { self == .test }
}

static var configuration: Configuration = {
static let configuration: Configuration = {
#if STREAM_RELEASE
return .release
#elseif STREAM_E2E_TESTS
Expand All @@ -34,7 +34,7 @@ extension AppEnvironment {

extension AppEnvironment {

indirect enum BaseURL: Debuggable, CaseIterable {
indirect enum BaseURL: Debuggable, CaseIterable, Sendable {
case pronto
case prontoStaging
case staging
Expand Down Expand Up @@ -99,7 +99,7 @@ extension AppEnvironment {
}
}

static var allCases: [BaseURL] = [
static let allCases: [BaseURL] = [
.pronto,
.prontoStaging,
.staging,
Expand All @@ -108,7 +108,7 @@ extension AppEnvironment {
]
}

static var baseURL: BaseURL = {
nonisolated(unsafe) static var baseURL: BaseURL = {
switch configuration {
case .test:
return .demo
Expand All @@ -126,7 +126,7 @@ extension AppEnvironment {
case universal = "streamvideo"
}

static var appURLScheme: String = { AppURLScheme.universal.rawValue }()
static let appURLScheme: String = { AppURLScheme.universal.rawValue }()
}

extension AppEnvironment {
Expand All @@ -137,7 +137,7 @@ extension AppEnvironment {
var url: URL { URL(string: rawValue)! }
}

static var authBaseURL: URL = { AuthBaseURL.universal.url }()
static let authBaseURL: URL = { AuthBaseURL.universal.url }()
}

extension AppEnvironment {
Expand Down Expand Up @@ -192,7 +192,7 @@ extension AppEnvironment {
}
}

static var loggedInView: LoggedInView = {
nonisolated(unsafe) static var loggedInView: LoggedInView = {
switch configuration {
case .test:
return .detailed
Expand Down Expand Up @@ -223,7 +223,7 @@ extension AppEnvironment {
}
}

static var performanceTrackerVisibility: PerformanceTrackerVisibility = {
nonisolated(unsafe) static var performanceTrackerVisibility: PerformanceTrackerVisibility = {
.hidden
}()
}
Expand All @@ -243,7 +243,7 @@ extension AppEnvironment {
}
}

static var chatIntegration: ChatIntegration = {
nonisolated(unsafe) static var chatIntegration: ChatIntegration = {
.enabled
}()
}
Expand Down Expand Up @@ -283,7 +283,7 @@ extension AppEnvironment {
}
}

static var supportedDeeplinks: [SupportedDeeplink] = {
nonisolated(unsafe) static var supportedDeeplinks: [SupportedDeeplink] = {
switch configuration {
case .debug:
return [.pronto, .demo, .staging, .legacy]
Expand All @@ -310,7 +310,7 @@ extension AppEnvironment {
}
}

static var pictureInPictureIntegration: PictureInPictureIntegration = {
nonisolated(unsafe) static var pictureInPictureIntegration: PictureInPictureIntegration = {
.enabled
}()
}
Expand Down Expand Up @@ -360,7 +360,7 @@ extension AppEnvironment {
}
}

static var tokenExpiration: TokenExpiration = {
nonisolated(unsafe) static var tokenExpiration: TokenExpiration = {
switch configuration {
case .debug:
return .oneMinute
Expand Down Expand Up @@ -412,7 +412,7 @@ extension AppEnvironment {
}
}

static var callExpiration: CallExpiration = .never
nonisolated(unsafe) static var callExpiration: CallExpiration = .never
}

extension AppEnvironment {
Expand Down Expand Up @@ -440,5 +440,5 @@ extension AppEnvironment {
}
}

static var autoLeavePolicy: AutoLeavePolicy = .default
nonisolated(unsafe) static var autoLeavePolicy: AutoLeavePolicy = .default
}
2 changes: 1 addition & 1 deletion DemoApp/Sources/Components/Chat/DemoChatAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct DemoChatAdapter {
extension DemoChatAdapter {
/// Returns the current value for the `StreamVideo` instance.
struct InjectionKey: StreamChatSwiftUI.InjectionKey {
static var currentValue: DemoChatAdapter?
nonisolated(unsafe) static var currentValue: DemoChatAdapter?
}
}

Expand Down
9 changes: 8 additions & 1 deletion DemoApp/Sources/Components/Chat/DemoChatViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import StreamChatSwiftUI
import class StreamVideoSwiftUI.CallViewModel
import SwiftUI

final class DemoChatViewFactory: ViewFactory {
@MainActor
final class DemoChatViewFactory {

@Injected(\.chatClient) var chatClient: ChatClient

Expand All @@ -33,3 +34,9 @@ final class DemoChatViewFactory: ViewFactory {
)
}
}

#if swift(>=6.0)
extension DemoChatViewFactory: @preconcurrency ViewFactory {}
#else
extension DemoChatViewFactory: ViewFactory {}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation
import StreamVideo

struct DemoChatViewModelInjectionKey: InjectionKey {
static var currentValue: DemoChatViewModel?
nonisolated(unsafe) static var currentValue: DemoChatViewModel?
}

extension InjectedValues {
Expand Down
20 changes: 11 additions & 9 deletions DemoApp/Sources/Components/CodeScanner/CodeScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ struct CodeScannerView_Previews: PreviewProvider {
}
}

final class ScannerViewController: UIViewController, UINavigationControllerDelegate, AVCaptureMetadataOutputObjectsDelegate,
UIAdaptivePresentationControllerDelegate {
final class ScannerViewController: UIViewController, UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate {
private let photoOutput = AVCapturePhotoOutput()
private var isCapturing = false
private var handler: ((UIImage) -> Void)?
Expand Down Expand Up @@ -214,9 +213,7 @@ final class ScannerViewController: UIViewController, UINavigationControllerDeleg
reset()

if (captureSession.isRunning == false) {
DispatchQueue.global(qos: .userInteractive).async {
self.captureSession?.startRunning()
}
self.captureSession?.startRunning()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove the background async?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

}
}

Expand Down Expand Up @@ -322,9 +319,7 @@ final class ScannerViewController: UIViewController, UINavigationControllerDeleg
super.viewDidDisappear(animated)

if (captureSession?.isRunning == true) {
DispatchQueue.global(qos: .userInteractive).async {
self.captureSession?.stopRunning()
}
captureSession?.stopRunning()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove the background async? Can we replace it instead with a simple task to the GlobalActor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

NotificationCenter.default.removeObserver(self)
Expand Down Expand Up @@ -491,7 +486,14 @@ final class ScannerViewController: UIViewController, UINavigationControllerDeleg
}
}

extension ScannerViewController: AVCapturePhotoCaptureDelegate {
#if swift(>=6.0)
extension ScannerViewController: @preconcurrency AVCapturePhotoCaptureDelegate,
@preconcurrency AVCaptureMetadataOutputObjectsDelegate {}
#else
extension ScannerViewController: AVCapturePhotoCaptureDelegate, AVCaptureMetadataOutputObjectsDelegate {}
#endif

extension ScannerViewController {

func photoOutput(
_ output: AVCapturePhotoOutput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Foundation
import StreamVideo

struct DeeplinkInfo: Equatable {
struct DeeplinkInfo: Equatable, Sendable {
var url: URL?
var callId: String
var callType: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum LogQueue {
}
}

final class Queue<T>: ObservableObject {
final class Queue<T>: ObservableObject, @unchecked Sendable {

@Published private(set) var elements: [T] = []
var maxCount: Int { didSet { resizeIfNeeded() } }
Expand Down
6 changes: 6 additions & 0 deletions DemoApp/Sources/Components/Reactions/ReactionsAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,15 @@ final class ReactionsAdapter: ObservableObject {
}
}

#if swift(>=6.0)
extension ReactionsAdapter: @preconcurrency InjectionKey {
static var currentValue: ReactionsAdapter = .init()
}
#else
extension ReactionsAdapter: InjectionKey {
static var currentValue: ReactionsAdapter = .init()
}
#endif

extension InjectedValues {
var reactionsAdapter: ReactionsAdapter {
Expand Down
6 changes: 3 additions & 3 deletions DemoApp/Sources/Components/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import StreamVideoNoiseCancellation
#endif

@MainActor
final class Router: ObservableObject {
final class Router: ObservableObject, @unchecked Sendable {

// MARK: - Properties

Expand Down Expand Up @@ -234,9 +234,9 @@ final class Router: ObservableObject {
appState.connectUser()
}

private func refreshToken(
nonisolated private func refreshToken(
for userId: String,
_ completionHandler: @escaping (Result<UserToken, Error>) -> Void
_ completionHandler: @Sendable @escaping (Result<UserToken, Error>) -> Void
) {
Task {
do {
Expand Down
10 changes: 7 additions & 3 deletions DemoApp/Sources/Components/SessionTimer/SessionTimer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ import SwiftUI
}
}

private var timer: Timer?
private var sessionEndCountdown: Timer?
nonisolated(unsafe) private var timer: Timer?
nonisolated(unsafe) private var sessionEndCountdown: Timer?

private let alertInterval: TimeInterval

Expand Down Expand Up @@ -132,8 +132,12 @@ import SwiftUI
}
}

deinit {
nonisolated private func cleanup() {
timer?.invalidate()
sessionEndCountdown?.invalidate()
}

deinit {
cleanup()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import StreamVideo
import UIKit

final class LocalParticipantSnapshotViewModel: NSObject, AVCapturePhotoCaptureDelegate,
AVCaptureVideoDataOutputSampleBufferDelegate {
martinmitrevski marked this conversation as resolved.
Show resolved Hide resolved
AVCaptureVideoDataOutputSampleBufferDelegate, @unchecked Sendable {

private actor State {
private(set) var isCapturingVideoFrame = false
Expand Down Expand Up @@ -173,10 +173,15 @@ final class LocalParticipantSnapshotViewModel: NSObject, AVCapturePhotoCaptureDe
}

/// Provides the default value of the `LocalParticipantSnapshotViewModel` class.
#if swift(>=6.0)
struct LocalParticipantSnapshotViewModelKey: @preconcurrency InjectionKey {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how the environmenrkey on SwiftUI handles that? Is it assigned on MainActor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this part is ugly, we have to find a better way

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's meet and dicsuccs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can create a small package with the DI capabilities. Reuse it in both SwiftUI and Video, with the @preconcurrency import. That way, we can probably avoid this stuff.

@MainActor static var currentValue: LocalParticipantSnapshotViewModel = .init()
}
#else
struct LocalParticipantSnapshotViewModelKey: InjectionKey {
@MainActor
static var currentValue: LocalParticipantSnapshotViewModel = .init()
@MainActor static var currentValue: LocalParticipantSnapshotViewModel = .init()
}
#endif

extension InjectedValues {
/// Provides access to the `LocalParticipantSnapshotViewModel` class to the views and view models.
Expand Down
9 changes: 8 additions & 1 deletion DemoApp/Sources/Components/Snapshot/SnapshotTrigger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import StreamVideo
import StreamVideoSwiftUI
import SwiftUI

final class StreamSnapshotTrigger: SnapshotTriggering {
final class StreamSnapshotTrigger: SnapshotTriggering, @unchecked Sendable {
lazy var binding: Binding<Bool> = Binding<Bool>(
get: { [weak self] in
self?.currentValueSubject.value ?? false
Expand All @@ -30,10 +30,17 @@ final class StreamSnapshotTrigger: SnapshotTriggering {
}

/// Provides the default value of the `StreamSnapshotTrigger` class.
#if swift(>=6.0)
struct StreamSnapshotTriggerKey: @preconcurrency InjectionKey {
@MainActor
static var currentValue: StreamSnapshotTrigger = .init()
}
#else
struct StreamSnapshotTriggerKey: InjectionKey {
@MainActor
static var currentValue: StreamSnapshotTrigger = .init()
}
#endif

extension InjectedValues {
/// Provides access to the `StreamSnapshotTrigger` class to the views and view models.
Expand Down
2 changes: 1 addition & 1 deletion DemoApp/Sources/Extensions/DemoApp+Sentry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func configureSentry() {
}
}

private final class SentryLogDestination: LogDestination {
private final class SentryLogDestination: LogDestination, @unchecked Sendable {
func write(message: String) {
// TODO: remove me once this function is gone from the protocol
}
Expand Down
Loading
Loading