Skip to content

Commit 0708658

Browse files
authored
fix: memory leaks (#27)
* fix: memory leaks * use locks for dictionary acess * more defenses * deadlock
1 parent b7b080f commit 0708658

File tree

7 files changed

+66
-32
lines changed

7 files changed

+66
-32
lines changed

CompassSDK/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>2.14.0</string>
18+
<string>2.14.1</string>
1919
<key>CFBundleVersion</key>
2020
<string>$(CURRENT_PROJECT_VERSION)</string>
2121
</dict>

CompassSDK/Tick/TikOperation.swift

+42-21
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ protocol ConversionsProvider: AnyObject {
1515
func getConversions(_ completion: @escaping ([String]) -> ())
1616
}
1717
typealias DataBuilderCompletion = (_ res: Encodable) -> Void
18-
typealias DataBuilder = (_ completion: @escaping DataBuilderCompletion) -> Encodable?
18+
typealias DataBuilder = (_ completion: @escaping DataBuilderCompletion) -> Void
1919

2020
class TikOperation: Operation, @unchecked Sendable {
2121
private let dataBuilder: DataBuilder
@@ -24,6 +24,9 @@ class TikOperation: Operation, @unchecked Sendable {
2424
private let path: String
2525
private let contentType: ContentType
2626

27+
private var timer: DispatchSourceTimer?
28+
private let lock = NSRecursiveLock()
29+
2730
init(
2831
dataBuilder: @escaping DataBuilder,
2932
dispatchDate: Date,
@@ -39,31 +42,47 @@ class TikOperation: Operation, @unchecked Sendable {
3942
super.init()
4043
}
4144

42-
private var timer: DispatchSourceTimer?
43-
44-
private var running: Bool = false {
45-
didSet {
46-
willChangeValue(forKey: "isFinished")
47-
willChangeValue(forKey: "isExecuting")
48-
didChangeValue(forKey: "isFinished")
49-
didChangeValue(forKey: "isExecuting")
45+
private var _running: Bool = false
46+
private var running: Bool {
47+
get {
48+
lock.lock()
49+
defer { lock.unlock() }
50+
return _running
51+
}
52+
set {
53+
var didChange = false
54+
lock.lock()
55+
if _running != newValue {
56+
willChangeValue(forKey: "isFinished")
57+
willChangeValue(forKey: "isExecuting")
58+
_running = newValue
59+
didChange = true
60+
}
61+
lock.unlock()
62+
63+
if didChange {
64+
didChangeValue(forKey: "isFinished")
65+
didChangeValue(forKey: "isExecuting")
66+
}
5067
}
5168
}
52-
69+
5370
override var isAsynchronous: Bool { true }
54-
5571
override var isFinished: Bool { !running }
56-
5772
override var isExecuting: Bool { running }
5873

5974
override func start() {
60-
guard !isCancelled else { return }
75+
guard !isCancelled else {
76+
finish()
77+
return
78+
}
79+
6180
running = true
6281

6382
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility))
6483
let timeInterval = dispatchDate.timeIntervalSinceNow
65-
let deadline = DispatchTime.now() + timeInterval
66-
84+
let deadline = DispatchTime.now() + max(timeInterval, 0)
85+
6786
timer.schedule(deadline: deadline)
6887

6988
timer.setEventHandler { [weak self] in
@@ -86,23 +105,25 @@ class TikOperation: Operation, @unchecked Sendable {
86105
self.finish()
87106
}
88107

89-
let data = self.dataBuilder(track)
90-
91-
if data != nil {
92-
track(data)
93-
}
108+
self.dataBuilder(track)
94109
}
95110

96111
self.timer = timer
97112
timer.resume()
98113
}
99114

100115
override func cancel() {
101-
finish()
102116
super.cancel()
117+
finish()
103118
}
104119

105120
private func finish() {
121+
lock.lock()
122+
defer { lock.unlock() }
123+
124+
guard running else { return }
125+
126+
timer?.setEventHandler {}
106127
timer?.cancel()
107128
timer = nil
108129
running = false

CompassSDK/Tracker/AbstractTracker.swift

+17-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
public class Tracker {
1111
private let queueName: String
1212
private var finishObserver: [Int: NSKeyValueObservation] = [:]
13+
private let observerLock = NSLock()
1314

1415
init(queueName: String) {
1516
self.queueName = queueName
@@ -32,19 +33,31 @@ public class Tracker {
3233
internal func observeFinish(for operation: Operation, cb: (() -> Void)?) {
3334
let opId = getOperationId(for: operation)
3435

36+
observerLock.lock()
37+
defer { observerLock.unlock() }
3538
finishObserver[opId] = operation.observe(\Operation.isFinished, options: .new) { [weak self] (operation, change) in
36-
guard !operation.isCancelled, operation.isFinished else {return}
37-
38-
self?.finishObserver.removeValue(forKey: opId)
39+
guard !operation.isCancelled, operation.isFinished else { return }
40+
41+
self?.invalidateObserver(for: opId)
3942
cb?()
4043
}
4144
}
4245

46+
private func invalidateObserver(for opId: Int) {
47+
observerLock.lock()
48+
defer { observerLock.unlock() }
49+
finishObserver[opId]?.invalidate()
50+
finishObserver.removeValue(forKey: opId)
51+
}
52+
4353
internal func stopObserving(for operation: Operation) {
44-
finishObserver.removeValue(forKey: getOperationId(for: operation))
54+
invalidateObserver(for: getOperationId(for: operation))
4555
}
4656

4757
internal func stopObserving() {
58+
observerLock.lock()
59+
defer { observerLock.unlock() }
60+
finishObserver.values.forEach { $0.invalidate() }
4861
finishObserver.removeAll()
4962
}
5063
}

CompassSDK/Tracker/Core/CompassTracker.swift

+4-3
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ private extension CompassTracker {
357357
func doTik() {
358358
guard trackInfo.pageUrl != nil else { return }
359359

360-
getConversions { [self] conversions in
360+
getConversions { [weak self] conversions in
361+
guard let self = self else { return }
362+
361363
let dispatchDate = Date(timeIntervalSinceNow: deadline)
362364

363365
let dispatchGroup = DispatchGroup()
@@ -389,7 +391,6 @@ private extension CompassTracker {
389391
}
390392
self.tick += 1
391393
}
392-
return nil
393394
},
394395
dispatchDate: dispatchDate,
395396
path: TIK_PATH,
@@ -404,7 +405,7 @@ private extension CompassTracker {
404405

405406
func restart(pageName: String?) {
406407
stopObserving()
407-
operationQueue.cancelAllOperations()
408+
operationQueue.operations.forEach{ $0.cancel() }
408409
trackInfo.pageUrl = pageName
409410
pageVars.removeAll()
410411
tick = 0

CompassSDK/Tracker/Multimedia/CompassTrackerMultimedia.swift

-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ private extension CompassTrackerMultimedia {
9797
tik: tik
9898
))
9999
}
100-
101-
return nil
102100
},
103101
dispatchDate: dispatchDate,
104102
path: TIK_PATH,

MarfeelSDK-iOS.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
1616
#
1717

1818
spec.name = "MarfeelSDK-iOS"
19-
spec.version = "2.14.0"
19+
spec.version = "2.14.1"
2020
spec.summary = "iOS version of MarfeelSDK."
2121

2222

Playground/Playground/AllPostsView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ struct AllPosts: View {
2828
scrollView = $0
2929
}
3030
.onAppear(perform: {
31+
CompassTracker.shared.setLandingPage("landing page")
3132
CompassTracker.shared.setUserType(.logged)
3233
CompassTracker.shared.addUserSegment("segment1")
3334
CompassTracker.shared.addUserSegment("segment1")

0 commit comments

Comments
 (0)