Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

About synchronized scope in Swift

Objective-C:

@synchronized {
    return;
}
NSLog(@"This line of code does not run.");

Objective-C's @synchronized is a language-level directive and does not introduce a new function scope.

Swift:

synchronized {
    return
}
print("This line of code does run.")


func synchronized(lock: AnyObject, @noescape closure: () -> Void) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

Synchronized uses closures which do introduce a function scope.

Question:

Can I do the same in Swift? Does not introduce a new function scope.

Real Problem

public func stopRecording() {

    synchronized(self) {
        guard status == .Recording else { return }
        status = .StoppingRecording
    }

    // Many somethings need to do, and I don`t want to write into the closure.
    finishRecording()
    ......
    ......
    ......
}

Workaround:

if (synchronized(self) { return true }) {
    return
}

print(@"This line of code does not run.")


func synchronized(lock: AnyObject, @noescape closure: () -> Bool) -> Bool {
    objc_sync_enter(lock)
    defer {
        objc_sync_exit(lock)
    }
    return closure()
}

Someone has a elegant solution?

like image 701
Limon Avatar asked Mar 21 '26 11:03

Limon


1 Answers

I just put together a solution that isn't too bad. We cannot introduce a custom language construct that doesn't introduce a new scope, therefore we need to use one of the existing ones. The if statement should work for that. I made a small class for that, which does but one thing: Lock the given object and check that the unlock method got called before it deallocates (along with some additions for debugging):

final class LockingManager {
    let obj: AnyObject

    let file: StaticString
    let line: UInt

    var locked = true

    init(obj: AnyObject, file: StaticString, line: UInt) {
        self.obj = obj
        self.file = file
        self.line = line
        objc_sync_enter(obj)
    }

    func unlock() {
        objc_sync_exit(obj)
        locked = false
    }

    deinit {
        precondition(!locked, "Object \(obj) not unlocked", file: file, line: line)
    }
}

With a function like this:

func lock(obj: AnyObject, file: StaticString = #file, line: UInt = #line) -> (() -> Void)? {
    return LockingManager(obj: obj, file: file, line: line).unlock
}

We can use it like this:

if let unlock = lock(obj: self) {
    defer { unlock() }
    // Do stuff
}

Since method never returns nil, you don't ever want an else part. Since the unlock method, which holds the only reference to the LockingManager, is only available within the if statement, after the statement the LockingManager gets deallocated. Upon deallocation, it checks whether the unlock method was called and if not, throws an error.

I understand that this isn't a super nice solution, but it does work in that it doesn't introduce a new scope (and additionally checks for correct usage).

Another solution would be to just use simple function calls and a do block:

do {
    objc_sync_enter(self)
    defer { objc_sync_exit(self) }

    // Do stuff
}

I personally prefer the second approach (with some better named functions), because it's much clearer what it does.

like image 130
Kametrixom Avatar answered Mar 24 '26 04:03

Kametrixom



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!