Skip to content

Commit 9fba0fc

Browse files
authored
Merge pull request #1376 from OneSignal/fix_swift_concurrency_issues
Improve Swift concurrency safety
2 parents d15d403 + c45c081 commit 9fba0fc

File tree

9 files changed

+255
-108
lines changed

9 files changed

+255
-108
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
03E56DD328405F4A006AA1DA /* OneSignalAppDelegateOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 03E56DD228405F4A006AA1DA /* OneSignalAppDelegateOverrider.m */; };
5454
16664C4C25DDB195003B8A14 /* NSTimeZoneOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 16664C4B25DDB195003B8A14 /* NSTimeZoneOverrider.m */; };
5555
37E6B2BB19D9CAF300D0C601 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37E6B2BA19D9CAF300D0C601 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
56+
3C05904B2B86BC9E00450D48 /* UnfairLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C05904A2B86BC9E00450D48 /* UnfairLock.swift */; };
5657
3C0EF49E28A1DBCB00E5434B /* OSUserInternalImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0EF49D28A1DBCB00E5434B /* OSUserInternalImpl.swift */; };
5758
3C115165289A259500565C41 /* OneSignalOSCore.docc in Sources */ = {isa = PBXBuildFile; fileRef = 3C115164289A259500565C41 /* OneSignalOSCore.docc */; };
5859
3C115171289A259500565C41 /* OneSignalOSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C115163289A259500565C41 /* OneSignalOSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -943,6 +944,7 @@
943944
1AF75EAD1E8567FD0097B315 /* NSString+OneSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+OneSignal.m"; sourceTree = "<group>"; };
944945
37747F9319147D6500558FAD /* libOneSignal.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOneSignal.a; sourceTree = BUILT_PRODUCTS_DIR; };
945946
37E6B2BA19D9CAF300D0C601 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
947+
3C05904A2B86BC9E00450D48 /* UnfairLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfairLock.swift; sourceTree = "<group>"; };
946948
3C0EF49D28A1DBCB00E5434B /* OSUserInternalImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSUserInternalImpl.swift; sourceTree = "<group>"; };
947949
3C115161289A259500565C41 /* OneSignalOSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OneSignalOSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
948950
3C115163289A259500565C41 /* OneSignalOSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalOSCore.h; sourceTree = "<group>"; };
@@ -1638,6 +1640,14 @@
16381640
name = Frameworks;
16391641
sourceTree = "<group>";
16401642
};
1643+
3C0590492B86BC5300450D48 /* Utils */ = {
1644+
isa = PBXGroup;
1645+
children = (
1646+
3C05904A2B86BC9E00450D48 /* UnfairLock.swift */,
1647+
);
1648+
path = Utils;
1649+
sourceTree = "<group>";
1650+
};
16411651
3C115162289A259500565C41 /* OneSignalOSCore */ = {
16421652
isa = PBXGroup;
16431653
children = (
@@ -1651,6 +1661,7 @@
16511661
isa = PBXGroup;
16521662
children = (
16531663
3C115163289A259500565C41 /* OneSignalOSCore.h */,
1664+
3C0590492B86BC5300450D48 /* Utils */,
16541665
3C115188289ADEA300565C41 /* OSModelStore.swift */,
16551666
3C115186289ADE7700565C41 /* OSModelStoreListener.swift */,
16561667
3C115184289ADE4F00565C41 /* OSModel.swift */,
@@ -3327,6 +3338,7 @@
33273338
3C115187289ADE7700565C41 /* OSModelStoreListener.swift in Sources */,
33283339
3CE5F9E3289D88DC004A156E /* OSModelStoreChangedHandler.swift in Sources */,
33293340
3C2D8A5928B4C4E300BE41F6 /* OSDelta.swift in Sources */,
3341+
3C05904B2B86BC9E00450D48 /* UnfairLock.swift in Sources */,
33303342
3C11518D289AF5E800565C41 /* OSModelChangedHandler.swift in Sources */,
33313343
3C8E6DF928A6D89E0031E48A /* OSOperationExecutor.swift in Sources */,
33323344
);

iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ public class OSOperationRepo: NSObject {
3636
public static let sharedInstance = OSOperationRepo()
3737
private var hasCalledStart = false
3838

39+
// The Operation Repo dispatch queue, serial. This synchronizes access to `deltaQueue` and flushing behavior.
40+
private let dispatchQueue = DispatchQueue(label: "OneSignal.OSOperationRepo", target: .global())
41+
3942
// Maps delta names to the interfaces for the operation executors
4043
var deltasToExecutorMap: [String: OSOperationExecutor] = [:]
4144
var executors: [OSOperationExecutor] = []
42-
var deltaQueue: [OSDelta] = []
45+
private var deltaQueue: [OSDelta] = []
4346

4447
// TODO: This could come from a config, plist, method, remote params
4548
var pollIntervalMilliseconds = Int(POLL_INTERVAL_MS)
@@ -62,7 +65,7 @@ public class OSOperationRepo: NSObject {
6265
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo calling start()")
6366
// register as user observer
6467
NotificationCenter.default.addObserver(self,
65-
selector: #selector(self.flushDeltaQueue),
68+
selector: #selector(self.addFlushDeltaQueueToDispatchQueue),
6669
name: Notification.Name(OS_ON_USER_WILL_CHANGE),
6770
object: nil)
6871
// Read the Deltas from cache, if any...
@@ -76,7 +79,7 @@ public class OSOperationRepo: NSObject {
7679
}
7780

7881
private func pollFlushQueue() {
79-
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(pollIntervalMilliseconds)) { [weak self] in
82+
self.dispatchQueue.asyncAfter(deadline: .now() + .milliseconds(pollIntervalMilliseconds)) { [weak self] in
8083
self?.flushDeltaQueue()
8184
self?.pollFlushQueue()
8285
}
@@ -101,14 +104,21 @@ public class OSOperationRepo: NSObject {
101104
return
102105
}
103106
start()
104-
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo enqueueDelta: \(delta)")
105-
deltaQueue.append(delta)
107+
self.dispatchQueue.async {
108+
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo enqueueDelta: \(delta)")
109+
self.deltaQueue.append(delta)
110+
// Persist the deltas (including new delta) to storage
111+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_OPERATION_REPO_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
112+
}
113+
}
106114

107-
// Persist the deltas (including new delta) to storage
108-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_OPERATION_REPO_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
115+
@objc public func addFlushDeltaQueueToDispatchQueue(inBackground: Bool = false) {
116+
self.dispatchQueue.async {
117+
self.flushDeltaQueue(inBackground: inBackground)
118+
}
109119
}
110120

111-
@objc public func flushDeltaQueue(inBackground: Bool = false) {
121+
private func flushDeltaQueue(inBackground: Bool = false) {
112122
guard !paused else {
113123
OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSOperationRepo not flushing queue due to being paused")
114124
return
@@ -122,16 +132,17 @@ public class OSOperationRepo: NSObject {
122132
OSBackgroundTaskManager.beginBackgroundTask(OPERATION_REPO_BACKGROUND_TASK)
123133
}
124134

125-
start()
126-
if !deltaQueue.isEmpty {
127-
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo flushDeltaQueue in background: \(inBackground) with queue: \(deltaQueue)")
135+
self.start()
136+
137+
if !self.deltaQueue.isEmpty {
138+
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo flushDeltaQueue in background: \(inBackground) with queue: \(self.deltaQueue)")
128139
}
129140

130141
var index = 0
131-
for delta in deltaQueue {
132-
if let executor = deltasToExecutorMap[delta.name] {
142+
for delta in self.deltaQueue {
143+
if let executor = self.deltasToExecutorMap[delta.name] {
133144
executor.enqueueDelta(delta)
134-
deltaQueue.remove(at: index)
145+
self.deltaQueue.remove(at: index)
135146
} else {
136147
// keep in queue if no executor matches, we may not have the executor available yet
137148
index += 1
@@ -141,17 +152,16 @@ public class OSOperationRepo: NSObject {
141152
// Persist the deltas (including removed deltas) to storage after they are divvy'd up to executors.
142153
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_OPERATION_REPO_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
143154

144-
for executor in executors {
155+
for executor in self.executors {
145156
executor.cacheDeltaQueue()
146157
}
147158

148-
for executor in executors {
159+
for executor in self.executors {
149160
executor.processDeltaQueue(inBackground: inBackground)
150161
}
151162

152163
if inBackground {
153164
OSBackgroundTaskManager.endBackgroundTask(OPERATION_REPO_BACKGROUND_TASK)
154165
}
155-
156166
}
157167
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Modified MIT License
3+
4+
Copyright 2024 OneSignal
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
1. The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
2. All copies of substantial portions of the Software may only be used in connection
17+
with services provided by OneSignal.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
*/
27+
28+
// Taken from https://swiftrocks.com/thread-safety-in-swift
29+
// Read http://www.russbishop.net/the-law for more information on why this is necessary
30+
public final class UnfairLock {
31+
private var _lock: UnsafeMutablePointer<os_unfair_lock>
32+
33+
public init() {
34+
_lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
35+
_lock.initialize(to: os_unfair_lock())
36+
}
37+
38+
deinit {
39+
_lock.deallocate()
40+
}
41+
42+
public func locked<ReturnValue>(_ closure: () throws -> ReturnValue) rethrows -> ReturnValue {
43+
os_unfair_lock_lock(_lock)
44+
defer { os_unfair_lock_unlock(_lock) }
45+
return try closure()
46+
}
47+
}

0 commit comments

Comments
 (0)