I am trying to understand the difference between doing a task in serial async DispatchQueue and then doing the same task in a method/function by referencing it through a weak self from the same DispatchQueue.
Code - 1
An asyncAfter block is executed after 1.5 seconds while task() is processing which dismisses the view controller but deinit() is only called after task() in finished.
class NewViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
self.dismiss(animated: false, completion: nil)
})
let queue = DispatchQueue(label: "myQueue", qos: .default)
queue.async {[weak self] in
self?.task()
}
}
private func task() {
print("start")
for i in 0...10000000 {
if i%500 == 0 {
}
}
print("stop")
}
deinit {
print("deinit")
}
}
Console output = start stop deinit
Code - 2
In this case deinit() is called as expected just after the view controller is dismissed.
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
self.dismiss(animated: false, completion: nil)
})
let queue = DispatchQueue(label: "myQueue", qos: .default)
queue.async {[weak self] in
print("start")
for i in 0...10000000 {
if i%500 == 0 {
}
}
print("stop")
}
}
deinit {
print("deinit")
}
Console output = start deinit stop
Can somebody please explain why is there a difference between the two?
Although both closures use weak self, the first invokes a function on the view controller. This causes the view controller to be retained until that function returns.
If it wasn't then the view controller would be released while the function was still executing, which would be a Bad Thing
In the second closure, there is no reference to self so the closure is simply retained by the dispatch queue.
Related
Crashes when the class is destructed, how to handle unfinished semaphore?
class CrashTestViewCtrl: UIViewController {
private var semaphore = DispatchSemaphore(value: 2)
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async { [weak self] in
self?.semaphore.wait()
// do something ......
}
}
deinit {
print("……deinit……")
}
}
It crashes because of lack of semaphore.signal() and wrong setup of semaphore.
You should call semaphore.signal() after you are done with the async method.
You should add semaphore.wait() outside of the async method.
In your case, it could be like this;
class CrashTestViewCtrl: UIViewController {
private var semaphore = DispatchSemaphore(value: 2)
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async { [weak self] in
// do something ......
self?.semaphore.signal()
}
semaphore.wait()
}
deinit {
print("……deinit……")
}
}
I have this code, where I'm trying to set a self variable (self?.users) from a view model call. The code snippet looks like this.
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.global().async { [weak self] in
self?.model?.findAll() { [weak self] users, exception in // network call
guard users != nil, self?.users = users else { // Optional type ()? cannot be used as a boolean; test for !=nil instead
}
}
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
I'm capturing [weak self] twice, is that okay?, can I capture it once as weak in the enclosing closure?
Should I use this instead of guard statement?
self?.model?.findAll() { [weak self] users, exception in
if exception != nil {
self?.users = users
}
}
DispatchQueue closures don't cause retain cycles so capture lists are not necessary.
Something like this, to avoid confusion I'd recommend to rename the incoming users and the code to reload the table view must be inside the closure
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.model?.findAll() { [weak self] foundUsers, exception in // network call
guard let foundUsers = foundUsers else { return }
self?.users = foundUsers
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
And don’t forget to call super
When i try print(self.navigationController) in viewWillApear or viewDidLoad all ok. But when delegate return response from API print(self.navigationController) return nil. What could it be?
extension EnterpriseList: APIDataDelegate {
func successRequest() { //print(self.navigtionController) == nil
DispatchQueue.main.async {
self.navigationController?.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
Alert.showWarningAlert(withTitle: "Внимание!", andMessage: "Ошибка получения данных, попробуйте чуть позже", whereSender: self)
}
}
}
class EnterpriseList: UIViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSource.makeAPICall()
}
}
extension EnterpriseList: APIDataDelegate {
func successRequest() {
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
}
}
}
protocol APIDataDelegate: class {
func successRequest()
func badRequest()
}
class DataSource {
var delegate: APIDataDelegate?
private let queue = DispatchQueue(label: "Test", qos: .background, attributes: [], autoreleaseFrequency: .inherit, target: nil)
func makeAPICall() {
queue.asyncAfter(deadline: .now() + 2) {[weak self] in
self?.delegate?.successRequest()
}
queue.asyncAfter(deadline: .now() + 10) {[weak self] in
self?.delegate?.successRequest()
}
}
}
The crash is because you are calling the method on deallocated object.
Here is how to fix this:
Make your delegate is weak and you are correctly deallocating the objects
weak var delegate: APIDataDelegate?
Thx #Manoj for answers it's halped me. But i found solution in other. Besides SurveyList i have UserMenu where i create static let view controllers. Removing static i solved my problem.
I whipped up the below struct as a way to alert the user when there's a slow network connection.
When a function is going to make a call to the server, it creates a ResponseTimer. This sets a delayed notification, which only fires if the responseTimer var isOn = true. When my function get's a response back from the server, set responseTimer.isOn = false.
Here's the struct:
struct ResponseTimer {
var isOn: Bool
init() {
self.isOn = true
self.setDelayedAlert()
}
func setDelayedAlert() {
let timer = DispatchTime.now() + 8
DispatchQueue.main.asyncAfter(deadline: timer) {
if self.isOn {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: toastErrorNotificationKey), object: self, userInfo: ["toastErrorCase" : ToastErrorCase.poorConnection])
}
}
}
And here's how I'd use it
func getSomethingFromFirebase() {
var responseTimer = ResponseTimer()
ref.observeSingleEvent(of: .value, with: { snapshot in
responseTimer.isOn = false
//do other stuff
})
}
Even in cases where the response comes back before the 8 second delay completes, the notification is still fired. What am I doing wrong here??? Is there a better pattern to use for something like this?
Thanks for the help!
A better way is to use DispatchSourceTimer which can be cancelled
var timer : DispatchSourceTimer?
func startTimer()
{
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline: .now() + .seconds(8))
timer!.setEventHandler {
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: toastErrorNotificationKey), object: self, userInfo: ["toastErrorCase" : ToastErrorCase.poorConnection])
}
self.timer = nil
}
timer!.resume()
}
}
func getSomethingFromFirebase() {
startTimer()
ref.observeSingleEvent(of: .value, with: { snapshot in
self.timer?.cancel()
self.timer = nil
//do other stuff
})
}
There are a few approaches:
invalidate a Timer in deinit
Its implementation might look like:
class ResponseTimer {
private weak var timer: Timer?
func schedule() {
timer = Timer.scheduledTimer(withTimeInterval: 8, repeats: false) { _ in // if you reference `self` in this block, make sure to include `[weak self]` capture list, too
// do something
}
}
func invalidate() {
timer?.invalidate()
}
// you might want to make sure you `invalidate` this when it’s deallocated just in
// case you accidentally had path of execution that failed to call `invalidate`.
deinit {
invalidate()
}
}
And then you can do:
var responseTimer: ResponseTimer?
func getSomethingFromFirebase() {
responseTimer = ResponseTimer()
responseTimer.schedule()
ref.observeSingleEvent(of: .value) { snapshot in
responseTimer?.invalidate()
//do other stuff
}
}
Use asyncAfter with DispatchWorkItem, which you can cancel:
class ResponseTimer {
private var item: DispatchWorkItem?
func schedule() {
item = DispatchWorkItem { // [weak self] in // if you reference `self` in this block, uncomment this `[weak self]` capture list, too
// do something
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
}
func invalidate() {
item?.cancel()
item = nil
}
deinit {
invalidate()
}
}
Use DispatchTimerSource, which cancels automatically when it falls out of scope:
struct ResponseTimer {
private var timer: DispatchSourceTimer?
mutating func schedule() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.setEventHandler { // [weak self] in // if you reference `self` in the closure, uncomment this
NotificationCenter.default.post(name: notification, object: nil)
}
timer?.schedule(deadline: .now() + 8)
timer?.activate()
}
mutating func invalidate() {
timer = nil
}
}
In all three patterns, the timer will be canceled when ResponseTimer falls out of scope.
I've got a function which is called by observing the NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(observedPosition(_: ), name: "calculatePosition", object: nil)
and then the function:
#objc func observedPosition(_ notification: NSNotification) {
if let data = notification.object as? Int {
self.sendPosition(from: data)
}
As this function can be called multiple times in very short time periods I would like to add it to the queue and call sendPosition() only once the previous sendPosition() has finished.
I tried something like this but dunno if it's a correct approach:
#objc func observedPosition(_ notification: NSNotification) {
let queue = DispatchQueue(label: queueLabel, attributes: [], targer: nil)
queue.sync {
if let data = notification.object as? Int {
self.sendPosition(from: data)
}
}
}
Details
Xcode Version 10.3 (10G8), Swift 5
Key features
Implemented own queue which will execute functions one by one
All operations (closures) stored in array
Thread safety
Solution
// MARK: - StackableOperationsQueue performs functions from the stack one by one (serial performing)
class StackableOperationsQueue {
private let semaphore = DispatchSemaphore(value: 1)
private lazy var operations = [QueueOperation]()
private lazy var isExecuting = false
fileprivate func _append(operation: QueueOperation) {
semaphore.wait()
operations.append(operation)
semaphore.signal()
execute()
}
func append(operation: QueueOperation) { _append(operation: operation) }
private func execute() {
semaphore.wait()
guard !operations.isEmpty, !isExecuting else { semaphore.signal(); return }
let operation = operations.removeFirst()
isExecuting = true
semaphore.signal()
operation.perform()
semaphore.wait()
isExecuting = false
semaphore.signal()
execute()
}
}
// MARK: - StackableOperationsCuncurentQueue performs functions from the stack one by one (serial performing) but in cuncurent queue
class StackableOperationsCuncurentQueue: StackableOperationsQueue {
private var queue: DispatchQueue
init(queue: DispatchQueue) { self.queue = queue }
override func append(operation: QueueOperation) {
queue.async { [weak self] in self?._append(operation: operation) }
}
}
// MARK: QueueOperation interface
protocol QueueOperation: class {
var сlosure: (() -> Void)? { get }
var actualityCheckingClosure: (() -> Bool)? { get }
init (actualityCheckingClosure: (() -> Bool)?, serialClosure: (() -> Void)?)
func perform()
}
extension QueueOperation {
// MARK: - Can queue perform the operation `сlosure: (() -> Void)?` or not
var isActual: Bool {
guard let actualityCheckingClosure = self.actualityCheckingClosure,
self.сlosure != nil else { return false }
return actualityCheckingClosure()
}
func perform() { if isActual { сlosure?() } }
init (actualIifNotNill object: AnyObject?, serialClosure: (() -> Void)?) {
self.init(actualityCheckingClosure: { return object != nil }, serialClosure: serialClosure)
}
}
class SerialQueueOperation: QueueOperation {
let сlosure: (() -> Void)?
let actualityCheckingClosure: (() -> Bool)?
required init (actualityCheckingClosure: (() -> Bool)?, serialClosure: (() -> Void)?) {
self.actualityCheckingClosure = actualityCheckingClosure
self.сlosure = serialClosure
}
}
Usage example
class TEST {
private lazy var stackableOperationsQueue: StackableOperationsCuncurentQueue = {
let queue = DispatchQueue(label: "custom_queue", qos: .background,
attributes: [.concurrent], autoreleaseFrequency: .workItem, target: nil)
return StackableOperationsCuncurentQueue(queue: queue)
}()
private func addOperationToQueue(closure: (() -> Void)?) {
let operation = SerialQueueOperation(actualIifNotNill: self) { closure?() }
stackableOperationsQueue.append(operation: operation)
print("!!!! Function added ")
}
private func simpleFunc(index: Int) {
print("Func \(index) started")
sleep(UInt32(index+1));
print("Func \(index) ended")
}
func run() {
(0...3).forEach { index in
addOperationToQueue { [weak self] in self?.simpleFunc(index: index) }
}
}
}
let test = TEST()
test.run()
Usage example results
// qos: .background
!!!! Function added
!!!! Function added
!!!! Function added
!!!! Function added
Func 0 started
Func 0 ended
Func 1 started
Func 1 ended
Func 2 started
Func 2 ended
Func 3 started
Func 3 ended
// qos: .userInitiated
!!!! Function added
Func 0 started
!!!! Function added
!!!! Function added
!!!! Function added
Func 0 ended
Func 1 started
Func 1 ended
Func 2 started
Func 2 ended
Func 3 started
Func 3 ended
That is correct, so long as you ensure the same queue is being used to schedule all sendPosition method calls. For example, if this queue were a local variable, it would be of no use at all.