【问题标题】:Will this RAII-style Objective-C class work?这个 RAII 风格的 Objective-C 类会起作用吗?
【发布时间】:2023-03-30 20:54:02
【问题描述】:

在 C++ 下,我有一个Mutex 类,我使用这个 RAII 风格的类来确保互斥锁被解锁,不管方法返回的原因是什么:

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

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

是否有任何原因,并且在使用 ARC 时,等效的 Objective-C 类不能正常工作:

@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

可以通过以下方式使用:

NSLock *_lock;    // instance variable

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

    return;     // Whenever I like
}

我知道它在 Objective-C 异常的情况下不起作用,这与 C++ 版本不同,但假设所有 Objective-C 异常都是致命的,我并不担心。

更新刚刚完成了一个快速测试,它似乎工作正常。看到这个gist

【问题讨论】:

  • 我想到的一件事是 [Locker lockerWithLock:_lock] 可以返回一个自动释放的对象,当它超出范围时可能不会立即释放(取决于 ARC 编译器所做的优化)。
  • @MartinR 即使上面实现了return [[Locker alloc] initWithLock:lock];?
  • 是的(我刚刚尝试过),因为您将其包装在 [Locker lockerWithLock:_lock] 中。如果直接在myFunc中调用Locker *locker = [[Locker alloc] initWithLock:_lock],那么会立即释放。
  • @MartinR Locker *locker = [[Locker alloc] init]; locker.lock = _lock; 并在 setter 中执行锁定怎么样? (即摆脱类级别的便利方法)。
  • 我会保留initWithLocker:_lock 并在init 方法中执行锁定,这与在dealloc 中解锁它非常对称。如果您只是关心“未使用的变量”警告,那么您可以在函数体的末尾添加locker = nil

标签: c++ objective-c mutex raii


【解决方案1】:

更好的 API:使用块:

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

例子:

NSLock *someLock = ...;
performBlockWithLock(someLock, ^{
    // your code here
});

【讨论】:

  • +1 抢劫是个好主意。由于我的应用程序代码都是 Objective-C++,因此我一直在使用我的 C++ RAII 风格 MutexLock,以确保分心,无论如何。
【解决方案2】:

如果你想要 RAII 模式,你应该使用 Objective-C++ 并编写 C++ RAII 类。

ARC 不太可能给您想要的结果。如果某些事情导致它被自动释放,则该对象可能被释放得太晚。如果 ARC 优化器决定不再使用该对象,则该对象可能被过早地释放。

【讨论】:

  • 这当然是一种选择;无论如何,我当前的应用程序是 100% 的 Objective-C++,所以这不是问题。你认为它会更有弹性吗?我已经成功地对其进行了测试(请参阅我的问题中的要点),因此建议的 Objective-C 实现确实似乎可以正常工作。
【解决方案3】:

我会说类方法像

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

可能会导致 ARC 自动释放返回值(请参阅this article)。我认为它将被自动释放,除非方法名称以allocnewinitcopymutableCopy 开头(或者除非你使用特殊的宏来强制编译器不自动释放,NS_RETURNS_RETAINED) ,clang ARC documentation is pretty good。一个自动释放的对象显然是个问题,因为在自动释放池耗尽之前你的锁不会被解锁。

我一直认为 RAII 是一种 C/C++ 事物,您可以在其中静态分配对象。但我想你可以这样做,只要你确保对象没有自动释放。

【讨论】:

  • +1 同意,正如@MartinR 在 cmets 中指出的那样。我现在更改了实现以避免这种方法。你能想出它不起作用的任何原因吗?
  • 这让我有点害怕,但我真的什么都想不出来。一旦调用了 ARC 插入的“释放”,就应该释放对象,这应该是退出 Locker* 范围时...
  • 好吧,我暂时无法对其进行测试,因为它是新功能的一部分,但一旦准备好,我会对其进行良好的测试并返回结果。
  • 优化部分值得一读clang.llvm.org/docs/…
【解决方案4】:

不要这样做!我最近猎杀了一天左右的错误,直到我发现通过 arc 释放的对象的 tima 和顺序有点随机,在最后一个参考消失后的一段时间。似乎自动释放池是随机弹出的。也没有保留订单。即,仅由另一个对象引用的子对象在其父对象之后被释放。我最终通过自制的“破坏”消息进行了手动资源 deininit,并且可能最终将除了一些胶水代码之外的所有代码移植到 C++ 中。我很抱歉人们不得不使用 swift。基本上它的所有问题,你有 gc....

【讨论】:

    猜你喜欢
    • 2011-12-04
    • 2017-01-03
    • 1970-01-01
    • 2015-01-28
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多