在 Objective-C 块中泄漏 C++ shared_ptr
Leaking C++ shared_ptr in Objective-C Block
总结:
在下面的示例应用程序中,shared_ptr
在 Objective-C 块中被捕获。 Objective-C 块正在分配给使用 Objective-C 运行时 API object_setIvarWithStrongDefault
动态创建的 class 的 ivar
。当 Objective-C 对象被释放时,shared_ptr
正在泄漏并且它保留的 C++ 对象没有被删除。这是为什么?
当使用 object_setIvar
时,可以防止泄漏,但是一旦块超出范围,ivar
就会指向垃圾,因为 object_setIvar
假定分配了 unsafe_unretained
.
我假设这与Objective-C如何捕获C++对象、复制块以及shared_ptr
如何处理被复制有关,但我希望有人比下面列出的文档更能说明这一点。
参考文献:
Apple's Blocks and Variables Documentation 包含一个关于 C++ 对象的简短部分,但我并不完全清楚它如何影响共享指针。
LLVM's Documentation on Blocks & C++ Support比苹果的详细一点...
objc-class.mm 包含 object_setIvarWithStrongDefault
的实现
背景故事:
此示例代码是从一个更大的项目中提取的,并且已大大减少到显示问题所需的最低限度。该项目是一个 Objective-C macOS 应用程序。该应用程序包含几个被美化 key/value 存储的整体 C++ 对象。每个对象都是相同 class 的一个实例,但以键类型为模板。我想动态创建一个 Objective-C class,其中包含由 C++ class 支持的类型化 属性 getter。
(是的,这一切都可以通过自己编写大量的 getter 来手动完成,但我不想这样做。C++ class 有足够的信息来知道属性的名称及其类型,因此我想使用一些元编程技术来 "solve" 这个。)
备注:
在理想情况下,我只能在适当的 shared_ptr
类型的 Objective-C class 上定义 iVar
,但我不能弄清楚如何使用 Objective-C 运行时 APIs 来做到这一点。
鉴于此:
std::shared_ptr<BackingStore<T>> backingStore
你如何使用它:
class_addIvar
和 object_setIvar
由于我无法弄清楚,我决定将 shared_ptr 包装到一个 Objective-C 块中,因为块是 first-class 对象并且可以传递到哪里预计 id
。
示例应用程序:
(Copy/paste 变成类似 CodeRunner
的东西来查看输出)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <memory>
typedef NSString* (^stringBlock)();
/**
* StoreBridge
*
* Objective-C class that exposes Objective-C properties
* which are "backed" by a C++ object (Store). The implementations
* for each property on this class are dynamically added.
*/
@interface StoreBridge : NSObject
@property(nonatomic, strong, readonly) NSString *storeName;
@end
@implementation StoreBridge
@dynamic storeName;
- (void)dealloc {
NSLog(@"StoreBridge DEALLOC");
}
@end
/**
* BackingStore
*
* C++ class that for this example just exposes a single,
* hard-coded getter function. In reality this class is
* much larger.
*/
class BackingStore {
public:
BackingStore() {
NSLog(@"BackingStore constructor.");
}
~BackingStore() {
NSLog(@"BackingStore destructor.");
}
NSString *name() const {
return @"Amazon";
}
// Given a shared_ptr to a BackingStore instance, this method
// will dynamically create a new Objective-C class. The new
// class will contain Objective-C properties that are backed
// by the given BackingStore.
//
// Much of this code is hard-coded for this example. In reality,
// a much larger number of properties are dynamically created
// with different return types and a new class pair is
// only created if necessary.
static id makeBridge(std::shared_ptr<BackingStore> storePtr) {
// For this example, just create a new class pair each time.
NSString *klassName = NSUUID.UUID.UUIDString;
Class klass = objc_allocateClassPair(StoreBridge.class, klassName.UTF8String, 0);
// For this example, use hard-coded values and a single iVar definition. The
// iVar will store an Objective-C block as an 'id'.
size_t ivarSize = sizeof(id);
NSString *ivarName = @"_storeNameIvar";
NSString *encoding = [NSString stringWithFormat:@"%s@", @encode(id)];
SEL selector = @selector(storeName);
// Implementation for @property.storeName on StoreBridge. This
// implementation will read the block stored in the instances
// iVar named "_storeNameIvar" and call it. Fixed casting to
// type 'stringBlock' is used for this example only.
IMP implementation = imp_implementationWithBlock((id) ^id(id _self) {
Ivar iv = class_getInstanceVariable([_self class], ivarName.UTF8String);
id obj = object_getIvar(_self, iv);
return ((stringBlock)obj)();
});
// Add iVar definition and property implementation to newly created class pair.
class_addIvar(klass, ivarName.UTF8String, ivarSize, rint(log2(ivarSize)), @encode(id));
class_addMethod(klass, selector, implementation, encoding.UTF8String);
objc_registerClassPair(klass);
// Create instance of the newly defined class.
id bridge = [[klass alloc] init];
// Capture storePtr in an Objective-C block. This is the block that
// will be stored in the instance's iVar. Each bridge instance has
// its own backingStore, therefore the storePtr must be set on the
// instance's iVar and not captured in the implementation above.
id block = ^NSString* { return storePtr->name(); };
Ivar iva = class_getInstanceVariable(klass, ivarName.UTF8String);
// Assign block to previously declared iVar. When the strongDefault
// method is used, the shared_ptr will leak and the BackingStore
// will never get deallocated. When object_setIvar() is used,
// the BackingStore will get deallocated but crashes at
// runtime as 'block' is not retained anywhere.
//
// The documentation for object_setIvar() says that if 'strong'
// or 'weak' is not used, then 'unretained' is used. It might
// "work" in this example, but in a larger program it crashes
// as 'block' goes out of scope.
#define USE_STRONG_SETTER 1
#if USE_STRONG_SETTER
object_setIvarWithStrongDefault(bridge, iva, block);
#else
object_setIvar(bridge, iva, block);
#endif
return bridge;
}
};
int main(int argc, char *argv[]) {
@autoreleasepool {
std::shared_ptr<BackingStore> storePtr = std::make_shared<BackingStore>();
StoreBridge *bridge = BackingStore::makeBridge(storePtr);
NSLog(@"bridge.storeName: %@", bridge.storeName);
// When USE_STRONG_SETTER is 1, output is:
//
// > BackingStore constructor.
// > bridge.storeName: Amazon
// > StoreBridge DEALLOC
// When USE_STRONG_SETTER is 0, output is:
//
// > BackingStore constructor.
// > bridge.storeName: Amazon
// > BackingStore destructor.
// > StoreBridge DEALLOC
}
}
让我们快点坐上时光机吧,C.A。 2010 年。这是一个更简单的时间,在处理 multi-architecture 切片、64 位和其他奇特的东西之前,比如重要的 ARC。
在这个看似遥远的世界到今天,当你有记忆的时候,你必须自己释放它gasp。这意味着,如果你的 class 上有一个 iVar,你必须在 dealloc
中明确调用 release
。
好吧,ARC 实际上并没有改变这一点。唯一发生变化的是 编译器 会在 dealloc
中为您生成所有那些不错的 release
调用,即使您没有定义该方法。真好
然而,这里的问题是编译器实际上并不知道包含该块的 iVar - 它是在运行时完全定义的。那么编译器怎么释放内存呢?
答案是否定的。您需要施展魔法才能确保在 run-time 发布这些内容。我的建议是迭代 class 的 iVars,并将它们设置为 nil
,而不是直接调用 objc_release(因为如果你使用 ARC)。
像这样:
for (ivar in class) {
if ivar_type == @encode(id) {
objc_setIvar(self, ivar, nil)
}
}
现在,如果您有意向 class 添加一个 __unsafe_unretained ivar,您可能会遇到更多问题。但是你真的不应该像这样从 classes 继承,mmkay?
总结:
在下面的示例应用程序中,shared_ptr
在 Objective-C 块中被捕获。 Objective-C 块正在分配给使用 Objective-C 运行时 API object_setIvarWithStrongDefault
动态创建的 class 的 ivar
。当 Objective-C 对象被释放时,shared_ptr
正在泄漏并且它保留的 C++ 对象没有被删除。这是为什么?
当使用 object_setIvar
时,可以防止泄漏,但是一旦块超出范围,ivar
就会指向垃圾,因为 object_setIvar
假定分配了 unsafe_unretained
.
我假设这与Objective-C如何捕获C++对象、复制块以及shared_ptr
如何处理被复制有关,但我希望有人比下面列出的文档更能说明这一点。
参考文献:
Apple's Blocks and Variables Documentation 包含一个关于 C++ 对象的简短部分,但我并不完全清楚它如何影响共享指针。
LLVM's Documentation on Blocks & C++ Support比苹果的详细一点...
objc-class.mm 包含
object_setIvarWithStrongDefault
的实现
背景故事:
此示例代码是从一个更大的项目中提取的,并且已大大减少到显示问题所需的最低限度。该项目是一个 Objective-C macOS 应用程序。该应用程序包含几个被美化 key/value 存储的整体 C++ 对象。每个对象都是相同 class 的一个实例,但以键类型为模板。我想动态创建一个 Objective-C class,其中包含由 C++ class 支持的类型化 属性 getter。
(是的,这一切都可以通过自己编写大量的 getter 来手动完成,但我不想这样做。C++ class 有足够的信息来知道属性的名称及其类型,因此我想使用一些元编程技术来 "solve" 这个。)
备注:
在理想情况下,我只能在适当的 shared_ptr
类型的 Objective-C class 上定义 iVar
,但我不能弄清楚如何使用 Objective-C 运行时 APIs 来做到这一点。
鉴于此:
std::shared_ptr<BackingStore<T>> backingStore
你如何使用它:
class_addIvar
和 object_setIvar
由于我无法弄清楚,我决定将 shared_ptr 包装到一个 Objective-C 块中,因为块是 first-class 对象并且可以传递到哪里预计 id
。
示例应用程序:
(Copy/paste 变成类似 CodeRunner
的东西来查看输出)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <memory>
typedef NSString* (^stringBlock)();
/**
* StoreBridge
*
* Objective-C class that exposes Objective-C properties
* which are "backed" by a C++ object (Store). The implementations
* for each property on this class are dynamically added.
*/
@interface StoreBridge : NSObject
@property(nonatomic, strong, readonly) NSString *storeName;
@end
@implementation StoreBridge
@dynamic storeName;
- (void)dealloc {
NSLog(@"StoreBridge DEALLOC");
}
@end
/**
* BackingStore
*
* C++ class that for this example just exposes a single,
* hard-coded getter function. In reality this class is
* much larger.
*/
class BackingStore {
public:
BackingStore() {
NSLog(@"BackingStore constructor.");
}
~BackingStore() {
NSLog(@"BackingStore destructor.");
}
NSString *name() const {
return @"Amazon";
}
// Given a shared_ptr to a BackingStore instance, this method
// will dynamically create a new Objective-C class. The new
// class will contain Objective-C properties that are backed
// by the given BackingStore.
//
// Much of this code is hard-coded for this example. In reality,
// a much larger number of properties are dynamically created
// with different return types and a new class pair is
// only created if necessary.
static id makeBridge(std::shared_ptr<BackingStore> storePtr) {
// For this example, just create a new class pair each time.
NSString *klassName = NSUUID.UUID.UUIDString;
Class klass = objc_allocateClassPair(StoreBridge.class, klassName.UTF8String, 0);
// For this example, use hard-coded values and a single iVar definition. The
// iVar will store an Objective-C block as an 'id'.
size_t ivarSize = sizeof(id);
NSString *ivarName = @"_storeNameIvar";
NSString *encoding = [NSString stringWithFormat:@"%s@", @encode(id)];
SEL selector = @selector(storeName);
// Implementation for @property.storeName on StoreBridge. This
// implementation will read the block stored in the instances
// iVar named "_storeNameIvar" and call it. Fixed casting to
// type 'stringBlock' is used for this example only.
IMP implementation = imp_implementationWithBlock((id) ^id(id _self) {
Ivar iv = class_getInstanceVariable([_self class], ivarName.UTF8String);
id obj = object_getIvar(_self, iv);
return ((stringBlock)obj)();
});
// Add iVar definition and property implementation to newly created class pair.
class_addIvar(klass, ivarName.UTF8String, ivarSize, rint(log2(ivarSize)), @encode(id));
class_addMethod(klass, selector, implementation, encoding.UTF8String);
objc_registerClassPair(klass);
// Create instance of the newly defined class.
id bridge = [[klass alloc] init];
// Capture storePtr in an Objective-C block. This is the block that
// will be stored in the instance's iVar. Each bridge instance has
// its own backingStore, therefore the storePtr must be set on the
// instance's iVar and not captured in the implementation above.
id block = ^NSString* { return storePtr->name(); };
Ivar iva = class_getInstanceVariable(klass, ivarName.UTF8String);
// Assign block to previously declared iVar. When the strongDefault
// method is used, the shared_ptr will leak and the BackingStore
// will never get deallocated. When object_setIvar() is used,
// the BackingStore will get deallocated but crashes at
// runtime as 'block' is not retained anywhere.
//
// The documentation for object_setIvar() says that if 'strong'
// or 'weak' is not used, then 'unretained' is used. It might
// "work" in this example, but in a larger program it crashes
// as 'block' goes out of scope.
#define USE_STRONG_SETTER 1
#if USE_STRONG_SETTER
object_setIvarWithStrongDefault(bridge, iva, block);
#else
object_setIvar(bridge, iva, block);
#endif
return bridge;
}
};
int main(int argc, char *argv[]) {
@autoreleasepool {
std::shared_ptr<BackingStore> storePtr = std::make_shared<BackingStore>();
StoreBridge *bridge = BackingStore::makeBridge(storePtr);
NSLog(@"bridge.storeName: %@", bridge.storeName);
// When USE_STRONG_SETTER is 1, output is:
//
// > BackingStore constructor.
// > bridge.storeName: Amazon
// > StoreBridge DEALLOC
// When USE_STRONG_SETTER is 0, output is:
//
// > BackingStore constructor.
// > bridge.storeName: Amazon
// > BackingStore destructor.
// > StoreBridge DEALLOC
}
}
让我们快点坐上时光机吧,C.A。 2010 年。这是一个更简单的时间,在处理 multi-architecture 切片、64 位和其他奇特的东西之前,比如重要的 ARC。
在这个看似遥远的世界到今天,当你有记忆的时候,你必须自己释放它gasp。这意味着,如果你的 class 上有一个 iVar,你必须在 dealloc
中明确调用 release
。
好吧,ARC 实际上并没有改变这一点。唯一发生变化的是 编译器 会在 dealloc
中为您生成所有那些不错的 release
调用,即使您没有定义该方法。真好
然而,这里的问题是编译器实际上并不知道包含该块的 iVar - 它是在运行时完全定义的。那么编译器怎么释放内存呢?
答案是否定的。您需要施展魔法才能确保在 run-time 发布这些内容。我的建议是迭代 class 的 iVars,并将它们设置为 nil
,而不是直接调用 objc_release(因为如果你使用 ARC)。
像这样:
for (ivar in class) {
if ivar_type == @encode(id) {
objc_setIvar(self, ivar, nil)
}
}
现在,如果您有意向 class 添加一个 __unsafe_unretained ivar,您可能会遇到更多问题。但是你真的不应该像这样从 classes 继承,mmkay?