I found the weird crash when using Set in async queue closure below, confirmed that it only happens in async queue, but Array works.
func testA1() {
var set = Set<Int>()
for i in 0...10 {
DispatchQueue.global().async {
set.update(with: i) // Crash here: EXC_BAD_ACCESS
// set.insert(i)
}
}
print(set as Any)
}
func testA2() {
var set = Set<Int>()
for i in 0...10 {
DispatchQueue.global().sync {
set.update(with: i) // Works!
}
}
print(set as Any)
}
func testB() {
var array = [Int]() // Works!
for i in 0...10 {
DispatchQueue.global().async {
array.append(i)
}
}
print(array as Any)
}
Swift version:
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin20.2.0
My bad or swift bug? Why?
Most of datatypes in Swift aren't thread-safe.
You should interact using different ways.
protocol ThreadSafeExecutor {
var semaphore: DispatchSemaphore { get set }
func wait()
func signal()
}
extension ThreadSafeExecutor {
func wait() {
semaphore.wait()
}
func signal() {
semaphore.signal()
}
}
class YourClass: ThreadSafeExecutor {
func someMethod() {
wait()
defer {
signal()
}
/// thread-safe code
}
}
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……")
}
}
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'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.
I want do one action when one method is finished, I execute one method in other method and I want the second method stop until the first method is finished.
I have that method:
func ejecutarOBJC(){
let txtNombre = self.view.viewWithTag(4) as? UITextField
let textoNombre=txtNombre?.text
let txtContra = self.view.viewWithTag(5) as? UITextField
let textoContra=txtContra?.text
let instanceOfCustomObject: SQLViewController = SQLViewController()
instanceOfCustomObject.nombre = textoNombre;
instanceOfCustomObject.contra = textoContra;
instanceOfCustomObject.obtenerExistenciaUsuario()
}
And also the other method:
func otherMethod(){
ejecutarOBJC()
//I want continue with that method when the execution of the other method finish
}
This is how you would achieve this:
func methodOne() {
//Method one code here
methodTwo()
}
func methodTwo() {
//Method Two code here.
}
As per your comment, here is how to wait when using async code:
func methodOne() {
//Code goes here
methodTwo { () -> () in
//Method two has finished
}
}
func methodTwo(completion: () -> ()) {
//Code goes here
completion()
}
Use closures:
func callCompetionFunction()
{
// Tira mola meseda (do your stuff)
completionFunction("a parameter") { () -> Void in
print("function copleted")
}
}
func completionFunction(param: AnyObject, completion: ()->Void)
{
// Do your stuff
completion()
}
To test it in a view controller
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
callCompetionFunction()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func callCompetionFunction()
{
// Tira mola meseda (do your stuff)
print("do something here")
completionFunction("a parameter") { () -> Void in
print("function copleted")
}
}
func completionFunction(param: AnyObject, completion: ()->Void)
{
// Do your stuff
if param is String
{
print("parameter = \(param)")
}
print("going to execute completion closure")
completion()
}
}
I have a function written in Swift. I want the completion block to return a boolean. How can I go about doing this? I am using Grand Central Dispatch.
func myFunc() -> Bool
{
var success:Bool = false
// code here
dispatch_async(dispatch_get_main_queue(), {
return success
)}
)}
}
thanks!
Standard why of dealing with this async nature is not to return value, but pass in completion handler:
func myFunc(completion:(success: Bool) -> ()) {
var success:Bool = false
// code here
dispatch_async(dispatch_get_main_queue()) {
completion(success: success)
}
}
Then work with it:
myFunc({ (success) in
// ...
})
More swifty way (Swift 5):
func myFunc(completion: #escaping (Bool) -> Void) {
var success = false
// code...
completion(success)
}
}
Usage:
myFunc { success in
if success {
// code ...
}
}