7

Under C++, I have a Mutex class, and I use this RAII-style class to ensure the mutex is unlocked, regardless of the reason for the method return:

class MutexLock {
protected:
    Mutex &m_mutex;
public:
    MutexLock(Mutex &mutex) :
        m_mutex(mutex) {
        m_mutex.lock();
    }

    ~MutexLock() {
        m_mutex.unlock();
    }
};

Is there any reason, and when using ARC, that an equivalent Objective-C class wouldn't work just as well:

@interface Locker : NSObject {
    NSLock *_lock;
}
- (void)setLock:(NSLock *)lock;
@end

@implementation Locker

- (void)setLock:(NSLock *)lock {
    [_lock unlock];
    _lock = lock;
    [_lock lock];
}

- (void)dealloc {
    self.lock = nil;
}
@end

Which might be used in the following way:

NSLock *_lock;    // instance variable

- (void)myFunc {
    Locker *locker = [[Locker alloc] init];
    locker.lock = _lock;

    return;     // Whenever I like
}

I understand it won't work in the case of Objective-C exceptions, unlike the C++ version, but assuming all Objective-C exceptions are fatal, I'm not worried about that.

UPDATE Just knocked-up a quick test, and it appears to be working fine. See this gist.

trojanfoe
  • 120,358
  • 21
  • 212
  • 242
  • One thing that comes into my mind is that `[Locker lockerWithLock:_lock]` could return an autoreleased object, which might not be immediately deallocated when it goes out of scope (depending on the optimizations done by the ARC compiler). – Martin R Oct 23 '13 at 21:02
  • @MartinR Even with the above implementation of `return [[Locker alloc] initWithLock:lock];`? – trojanfoe Oct 23 '13 at 21:05
  • Yes (I just tried it), because you wrap it in `[Locker lockerWithLock:_lock]`. If you directly call `Locker *locker = [[Locker alloc] initWithLock:_lock]` in `myFunc` then it will be released immediately. – Martin R Oct 23 '13 at 21:07
  • @MartinR How about `Locker *locker = [[Locker alloc] init]; locker.lock = _lock;` and performing the lock in the setter? (i.e. getting rid on the class-level convenience method). – trojanfoe Oct 23 '13 at 21:09
  • I would keep `initWithLocker:_lock` and perform the lock in the init method, that is nicely symmetric with unlocking it in dealloc. If you are just concerned about the "unused variable" warning then you can add `locker = nil` at the end of the function body. – Martin R Oct 23 '13 at 21:19
  • @MartinR Yeah, that's kind of what I'm attempting to avoid; there is no need for this kind of class of course, except when I forget to do the crucial thing. I have changed the implementation, which I think is both terse and does what it should (unless I've missed something else). – trojanfoe Oct 23 '13 at 21:22
  • Yes, that looks good to me. So the only thing was the autorelease problem, but you already got an answer for that (I should write answers instead of comments :-) – Martin R Oct 23 '13 at 21:24
  • This is a clever idea! Why the explicit checks for non-nil in the setter? – jscs Oct 23 '13 at 21:47
  • @JoshCaswell It's so we are sure to lock and unlock when we have been passed a lock or are clearing the lock. – trojanfoe Oct 24 '13 at 06:14
  • I don't think you need them, unless you think it makes it more readable. If your ivar, `_lock`, is `nil` at the start of the setter, `unlock` won't do anything, and likewise after `_lock = lock`; if `lock` was `nil` -- `[nil lock];` is a no-op. – jscs Oct 24 '13 at 08:49
  • @JoshCaswell Yeah that is true actually. Thanks. – trojanfoe Oct 24 '13 at 09:01

4 Answers4

7

Better API: use a block:

void performBlockWithLock(NSLock *lock, void (^block)(void)) {
    [lock lock];
    block();
    [lock unlock];
}

Example:

NSLock *someLock = ...;
performBlockWithLock(someLock, ^{
    // your code here
});
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • +1 Not a bad idea rob. As my app's code is all Objective-C++ I've been using my C++ RAII-style `MutexLock` instead, to guarentee distraction, no matter what. – trojanfoe Feb 09 '14 at 22:19
4

If you want RAII patterns, you should use Objective-C++ and write C++ RAII classes.

ARC is unlikely to give you the result you want. The object may be deallocated too late, if something causes it to be autoreleased. The object may be deallocated too early, if the ARC optimizer decides the object is no longer used.

Greg Parker
  • 7,972
  • 2
  • 24
  • 21
  • This is certainly an option; my current app is 100% Objective-C++ anyway, so that wouldn't be a problem. Do think it would be more resilient? I have tested it successfully (see the gist in my question), so the proposed Objective-C implementation does *appear* to work fine. – trojanfoe Oct 24 '13 at 10:20
3

I would say that class methods like

+ (Locker *)lockerWithLock:(NSLock *)lock;

would probably cause ARC to autorelease the return value (see this article). I think it will be autoreleased unless the method name begins with alloc, new, init, copy, mutableCopy (or unless you use special macros to force the compiler into not autoreleasing, NS_RETURNS_RETAINED), the clang ARC documentation is pretty good. An autoreleased object would obviously be a problem given your lock wouldn't be unlocked until the autorelease pool is drained.

I always thought of RAII as being a C/C++ thing where you can allocate objects statically. But I guess you can do it this way, as long as you make well sure that the objects are not autoreleased.

Community
  • 1
  • 1
jbat100
  • 16,757
  • 4
  • 45
  • 70
  • +1 Agreed, as pointed out by @MartinR in the comments. I have changed the implementation now to avoid such methods. Can you think of any reason why it wouldn't work? – trojanfoe Oct 23 '13 at 21:22
  • It scares me a bit, but I really can't think of anything. The object should be deallocated as soon as the ARC-inserted 'release' is called, which should be when the Locker* scope is exited... – jbat100 Oct 23 '13 at 21:37
  • Well I cannot test it for the moment, as it's part of a big new piece of functionality, but once that's ready I'll give it a good test and come back with the results. – trojanfoe Oct 24 '13 at 06:14
  • It my be worth reading the optimization section http://clang.llvm.org/docs/AutomaticReferenceCounting.html#optimization – jbat100 Oct 24 '13 at 07:33
-1

Don't do this! I recently hunted bugs for a day or so until i found out tima and order of objects released via arc is kinda random, some time after the last referece goes away. it seems auto release pools are popped kinda at random. also order is not preserved. i.e. a child object referenced only by one other object was deallocated after its parent object. I ended up doing manual resource deininit via self-made "destruct" messages, and will propably end up porting all but some glue code to c++. i am sorry for people having to use swift. basically its all the problems that you have with gc....

maddanio
  • 87
  • 5