Swift 3 CFRunLoopRun in Thread? - swift

I just made a simple testing app to display keycode of keystrokes along with modifiers. It works fine for 3 keystrokes, then the app crashes. When it crashes, debug console just shows (LLDB) at the end. Any suggestion what might be causing this? Maybe something has to do with thread or pointer, but I'm not sure how I can fix this. I'm including the code below. I'd really appreciate any help! Thanks!
import Cocoa
import Foundation
class ViewController: NSViewController {
#IBOutlet weak var textField: NSTextFieldCell!
let speech:NSSpeechSynthesizer = NSSpeechSynthesizer()
func update(msg:String) {
textField.stringValue = msg
print(msg)
speech.startSpeaking(msg)
}
func bridgeRetained<T : AnyObject>(obj : T) -> UnsafeRawPointer {
return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque())
}
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let parent:ViewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeRetainedValue()
if [.keyDown].contains(type) {
let flags:CGEventFlags = event.flags
let pressed = Modifiers(rawValue:flags.rawValue)
var msg = ""
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlphaShift.rawValue)) {
msg+="caps+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskShift.rawValue)) {
msg+="shift+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskControl.rawValue)) {
msg+="control+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlternate.rawValue)) {
msg+="option+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskCommand.rawValue)) {
msg += "command+"
}
if pressed.contains(Modifiers(rawValue:CGEventFlags.maskSecondaryFn.rawValue)) {
msg += "function+"
}
var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
msg+="\(keyCode)"
DispatchQueue.main.async {
parent.update(msg:msg)
}
if keyCode == 0 {
keyCode = 6
} else if keyCode == 6 {
keyCode = 0
}
event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
}
return Unmanaged.passRetained(event)
}
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: myCGEventCallback, userInfo: UnsafeMutableRawPointer(mutating: self.bridgeRetained(obj: self))) else {
print("failed to create event tap")
exit(1)
}
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
}
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}

The main problem is the reference counting: You create a retained
reference to the view controller when installing the event handler, this happens exactly once.
Then you consume a reference in the callback, this happens for every
tap event. Therefore the reference count drops to zero eventually and
the view controller is deallocated, causing a crash.
Better pass unretained references to the callback, and take care that
the event handler is uninstalled when the view controller is deallocated.
Also there is no need to create a separate runloop for an OS X application, or to asynchronously dispatch the handler creation.
Make the callback a global function, not a method. Use
takeUnretainedValue() to get the view controller reference:
func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let viewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeUnretainedValue()
if type == .keyDown {
var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
let msg = "\(keyCode)"
DispatchQueue.main.async {
viewController.update(msg:msg)
}
if keyCode == 0 {
keyCode = 6
} else if keyCode == 6 {
keyCode = 0
}
event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
}
return Unmanaged.passRetained(event)
}
In the view controller, keep a reference to the run loop source
so that you can remove it in deinit, and use
passUnretained() to pass a pointer to the view controller to
the callback:
class ViewController: NSViewController {
var eventSource: CFRunLoopSource?
override func viewDidLoad() {
super.viewDidLoad()
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
let userInfo = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
if let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap,
options: .defaultTap, eventsOfInterest: CGEventMask(eventMask),
callback: myCGEventCallback, userInfo: userInfo) {
self.eventSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), self.eventSource, .commonModes)
} else {
print("Could not create event tap")
}
}
deinit {
if let eventSource = self.eventSource {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, .commonModes)
}
}
// ...
}
Another option would be to install/uninstall the event handler in
viewDidAppear and viewDidDisappear.

Related

How to properly dispose of Connected Display Window and View?

I have code that opens a window and displays a view on a connected display. My goal is to detect a connection/disconnection of a connected display and show/remove the view accordingly. I have that part working fine.
The problem I am having is closing the window upon disconnection, but then if a subsequent connection is made, and upon creating the window and view again, I get a EXC_BAD_ACCESS error.
I tried a different approach by setting the connectedDisplayWindow and connectedDisplayView to nil, after calling close() on the window when a connected display is removed. Maybe I am misunderstanding the close() method?
Apple Documentation
If the window is set to be released when closed, a release message is sent to the object after the current event is completed. For an NSWindow object, the default is to be released on closing, while for an NSPanel object, the default is not to be released. You can use the isReleasedWhenClosed property to change the default behavior...
Just to make sure, I tried setting the isReleasedWhenClosed to true, but it did not change the problem.
The other thing I see in the console is about seven repeated error strings immediately upon disconnection of the connected display: 2022-04-10 10:28:11.044155-0500 External Display[95744:4934855] [default] invalid display identifier 67EE0C44-4E3D-3AF2-3447-A867F9FC477D before the notification is fired, and one more after the notification occurs: 2022-04-10 10:28:11.067555-0500 External Display[95744:4934855] [default] Invalid display 0x4e801884. Could these be related to the issues I am having?
Full example code:
ViewController.swift
import Cocoa
let observatory = NotificationCenter.default
class ViewController: NSViewController {
var connectedDisplay: NSScreen?
var connectedDisplayWindow: NSWindow?
var connectedDisplayView: NSView?
var connectedDisplayCount: Int = 0
var connectedDisplayID: UInt32 = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupObservatory()
if NSScreen.screens.count > 1 {
handleDisplayConnectionChange(notification: nil)
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func viewWillDisappear() {
connectedDisplayWindow?.close()
}
func setupObservatory() {
observatory.addObserver(self, selector: #selector(handleDisplayConnectionChange), name: NSApplication.didChangeScreenParametersNotification, object: nil)
observatory.addObserver(forName: .setupConnectedDisplayWindow, object: nil, queue: nil, using: setupConnectedDisplayWindow)
}
#objc func handleDisplayConnectionChange(notification: Notification?) {
if connectedDisplayCount != NSScreen.screens.count {
if connectedDisplayCount < NSScreen.screens.count {
print("There is a connected display.")
connectedDisplayCount = NSScreen.screens.count
if let _ = NSScreen.screens.last {
if connectedDisplay != NSScreen.screens.last {
connectedDisplayID = NSScreen.screens.last!.displayID!
connectedDisplay = NSScreen.screens.last!
}
} else {
connectedDisplayID = 0
}
if connectedDisplayID != 0 && !connectedDisplayIsActive {
observatory.post(name: .setupConnectedDisplayWindow, object: nil)
}
} else if connectedDisplayCount > NSScreen.screens.count {
print("A connected display was removed.")
connectedDisplayCount = NSScreen.screens.count
connectedDisplayIsActive = false
connectedDisplayWindow?.close()
//connectedDisplayView = nil <- causes error #main in AppDelegate
//connectedDisplayWindow = nil <- causes error #main in AppDelegate
connectedDisplay = nil
connectedDisplayID = 0
}
}
}
func setupConnectedDisplayWindow(notification: Notification) {
if NSScreen.screens.count > 1 && !connectedDisplayIsActive {
connectedDisplay = NSScreen.screens.last
let mask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
connectedDisplayWindow = NSWindow(contentRect: connectedDisplay!.frame, styleMask: mask, backing: .buffered, defer: true, screen: connectedDisplay) // <- causes error on subsequent connection
connectedDisplayWindow?.level = .normal
connectedDisplayWindow?.isOpaque = false
connectedDisplayWindow?.backgroundColor = .clear
connectedDisplayWindow?.hidesOnDeactivate = false
let viewRect = NSRect(x: 0, y: 0, width: connectedDisplay!.frame.width, height: connectedDisplay!.frame.height)
connectedDisplayView = ConnectedDisplayView(frame: viewRect)
connectedDisplayWindow?.contentView = connectedDisplayView
connectedDisplayWindow?.orderFront(nil)
connectedDisplayView?.window?.toggleFullScreen(self)
connectedDisplayIsActive = true
observatory.post(name: .setupConnectedDisplayView, object: nil)
}
}
}
extension Notification.Name {
static var setupConnectedDisplayWindow: Notification.Name {
return .init(rawValue: "ViewController.setupConnectedDisplayView")
}
static var setupConnectedDisplayView: Notification.Name {
return .init(rawValue: "ConnectedDisplayView.setupConnectedDisplayView")
}
}
extension NSScreen {
var displayID: CGDirectDisplayID? {
return deviceDescription[NSDeviceDescriptionKey(rawValue: "NSScreenNumber")] as? CGDirectDisplayID
}
}
ConnectedDisplayView.swift
import Cocoa
var connectedDisplayIsActive: Bool = false
class ConnectedDisplayView: NSView {
var imageView: NSImageView!
override init(frame: NSRect) {
super.init(frame: frame)
setupObservatory()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupObservatory() {
observatory.addObserver(forName: .setupConnectedDisplayView, object: nil, queue: nil, using: setupConnectedDisplayView)
}
func setupConnectedDisplayView(notification: Notification) {
let imageURL = URL(fileURLWithPath: "/Users/Shared/my image.png")
if let image = NSImage(contentsOf: imageURL) {
imageView = NSImageView(image: image)
imageView.wantsLayer = true
imageView.frame = self.frame
imageView.alphaValue = 1
self.addSubview(imageView)
}
}
}
I commented out the nil settings for the connectedDisplayWindow and connectedDisplayView objects and the error at #main in AppDelegate went away, but then I get an error when trying to reinitialize the connectedDisplayWindow if the connected display is removed or the connection is momentarily interrupted.
The default value of isReleasedWhenClosed is true and connectedDisplayWindow?.close() releases the window. Setting connectedDisplayWindow to nil or to another window releases the window again and causes a crash. Solution: set isReleasedWhenClosed to false.

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

How to showing line one by one in async code?

I want to do for-loop code with async. For simulating the async, I added some delays.
import Cocoa
class ViewController: NSViewController {
private let semaphore = DispatchSemaphore(value: 0)
private let concurrentQueue = DispatchQueue.global()
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
if run(i) {
break
}
}
}
func run(_ i:Int) -> Bool{
let seed = arc4random()
let isSuccess = seed % 5 == 1
let delayInSeconds = Int(seed % 3)
concurrentQueue.asyncAfter(wallDeadline: .now() + .seconds(delayInSeconds)) {
DispatchQueue.main.async {
self.textView.string += "\(i)\t\(seed)\n"
}
self.semaphore.signal()
}
semaphore.wait()
return isSuccess
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBOutlet var textView: NSTextView!
}
The problem is, when the code runs, all lines were added at once. I want it added one by one. Any idea?
The problem is solved. The reason that update UI part doesn't work is because that func run() is running on main thread, which is blocked by semaphore.wait() always. So to solve the problem, just added it to another thread.
override func viewDidLoad() {
super.viewDidLoad()
concurrentQueue.async {
for i in 1...10 {
if self.run(i) {
break
}
}
}
}

WKCrownSequencer not working after going back to main interface controller

I have a WKCrownSequencer that triggers an action in my pushed interface controller and the first time through everything works fine. When I go back to root interface controller regardless of the method (pop or reloadRootcontrollers) the digital crown no longer works in first interface controller nor the second one. The StartInterfaceController is the rootInterfaceController and the MidWorkoutInterfaceController is the pushed one.
import WatchKit
import Foundation
class StartInterfaceController:
WKInterfaceController,CLLocationManagerDelegate {
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
#IBAction func start() {
WKInterfaceController.reloadRootControllers(
withNames: ["midWorkout"], contexts: []
)
}
The second interface controller is below.
import WatchKit
import Foundation
class MidWorkoutInterfaceController: WKInterfaceController, WKCrownDelegate {
override func awake(withContext context: Any?) {
super.awake(withContext: context)
print("viewdidAwake")
print("ViewWillActivate")
crownSequencer.delegate = self
crownSequencer.focus()
WKInterfaceDevice.current().play(.success)
currentPhase = 0
let workoutType = UserDefaults.standard.object(forKey: "CurrentType") as? [String] ?? [ "Swimming", "T1"]
orderOfEventsSetter = workoutType
updateCurrentPhase()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
crownSequencer.focus()
}
var clockTimer: Timer!
func workoutStarted(){
clockTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
self?.totalTimeOutlet.setText( String(describing: -1 * Int(self!.startDate!.timeIntervalSinceNow)))
self?.splitTimeOutlet.setText( String(describing: -1 * Int(self!.currentStartDate!.timeIntervalSinceNow)))
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
var startDate: Date?
var currentStartDate: Date?
//Outlets
#IBOutlet var currentPhaseOutlet: WKInterfaceLabel!
#IBOutlet var totalTimeOutlet: WKInterfaceLabel!
#IBOutlet var splitTimeOutlet: WKInterfaceLabel!
#IBOutlet var currentPaceOutlet: WKInterfaceLabel!
#IBOutlet var totalDistanceOutlet: WKInterfaceLabel!
var orderOfEventsSetter: Array<String>{
get{
return orderOfEvents
}
set{
var tempArray = ["GPS Locking In"]
for phase in newValue {
tempArray.append(phase)
}
orderOfEvents = tempArray
}
}
private var orderOfEvents: [String] = []
var currentPhase = 0 {
didSet{
if !orderOfEvents.isEmpty {
updateCurrentPhase()
}
}
}
func updateCurrentPhase(){
currentPhaseOutlet.setText(orderOfEvents[currentPhase])
}
//timing for location requests
//Corelocation Section
//CoreMotion Section
///crown control
var currentDialRotation = 0.0
let dialRotationRange = Range(uncheckedBounds: (lower: -Double.pi / 4, upper: Double.pi / 4))
let constantForTimer: TimeInterval = 0.1
var justTransitioned = false
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
currentDialRotation += rotationalDelta
if !dialRotationRange.contains(currentDialRotation){
currentDialRotation = 0.0
justTransitioned = true
makeTransition()
//make so two transitions cannot happen right after each other
}
print(currentDialRotation)
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
print(String(describing: orderOfEvents[currentPhase]))
print("crown stopped")
}
func makeTransition(){
print(currentPhase)
print(orderOfEvents.count)
if (currentPhase) == orderOfEvents.count - 1 {
endWorkout()
}
else if (currentPhase == 0){
WKInterfaceDevice.current().play(.start)
let dateFormat = DateFormatter()
dateFormat.dateFormat = "mm/dd/yyyy"
startDate = Date()
currentStartDate = Date()
workoutStarted()
currentPhase += 1
}
else{
WKInterfaceDevice.current().play(.start)
print("transitioning to " + String(describing: orderOfEvents[currentPhase + 1]))
currentStartDate = Date()
stopTimers()
currentPhase += 1
}
}
#IBAction func endWorkoutButton() {
endWorkout()
}
func endWorkout(){
stopTimers()
clockTimer.invalidate()
alerts()
}
func alerts(){
let saveAction = WKAlertAction(title: "Save",
style: WKAlertActionStyle.default) {
self.goToStartScreen()
}
let discardAction = WKAlertAction(title: "Discard Workout",
style: WKAlertActionStyle.cancel) {
self.goToStartScreen()
}
presentAlert(withTitle: "Workout Complete",
message: "Would you like to save the workout?",
preferredStyle: WKAlertControllerStyle.alert,
actions: [saveAction, discardAction])
}
func goToStartScreen(){
crownSequencer.resignFocus()
self.popToRootController()
}
func stopTimers(){
if orderOfEvents[currentPhase] == "Running"{
}
if orderOfEvents[currentPhase] == "Biking"{
}
if currentPhase == orderOfEvents.count {
clockTimer.invalidate()
}
}
}
According to Apple's document here:
"...Only one object in your interface can have focus at any given time, so if your interface also contains picker objects or has scrollable scenes, you must coordinate changes in focus accordingly. For example, calling the sequencer's focus method causes any picker objects or interface controllers to resign focus. When the user taps on a picker object, the currently active sequencer resigns focus, and the selected picker object gains the focus... "
And you are to lose the focus at any time unpredictable...
"...If the user taps a picker object or a scrollable scene in your interface, the system automatically removes the focus from any active crown sequencer..."

Block not really executing?

I'm trying to use a subclass of NSThread to run some commands. Before you recommend NSOperation or GCD, YES I need to use threads.
Below is my code and my output. The block is being created and added to the array and supposedly is being dequeued and run by the thread, but I don't see any output resulting from the running of my block. Why not?
import UIKit
class ViewController: UIViewController {
private let _queue = dispatch_queue_create("com.AaronLBratcher.ALBQueue", nil)
private let _thread = TestThread()
private let _lock = NSCondition()
override func viewDidLoad() {
super.viewDidLoad()
_thread.start()
var openSuccessful = false
dispatch_sync(_queue) {[unowned self] () -> Void in
self._lock.lock()
self._thread.openFile("file path here", completion: { (successful) -> Void in
print("completion block running...")
openSuccessful = successful
self._lock.signal()
self._lock.unlock()
})
self._lock.wait()
}
print("open operation complete")
print(openSuccessful)
}
final class TestThread:NSThread {
var _iterations = 0
var _lock = NSCondition()
var _blocks = [Any]()
func openFile(FilePath:String, completion:(successful:Bool) -> Void) {
print("queueing openFile...")
let block = {[unowned self] in
self._iterations = self._iterations + 1
print("opening file...")
completion(successful: true)
}
addBlock(block)
}
func addBlock(block:Any) {
_lock.lock()
_blocks.append(block)
_lock.signal()
_lock.unlock()
}
override func main() {
_lock.lock()
while true {
while _blocks.count == 0 {
print("waiting...")
_lock.wait()
}
print("extracting block...")
if let block = _blocks.first {
_blocks.removeFirst()
_lock.unlock()
print("running block...")
block;
}
_lock.lock()
}
}
}
}
Output:
queueing openFile...
waiting...
extracting block...
running block...
waiting...
The block isn't running because you just have block. You need block(), e.g.:
if let block = _blocks.first as? ()->() {
_blocks.removeFirst()
_lock.unlock()
print("running block...")
block()
}