![](/img/trans.png)
[英]Managing objective-C objects with c++ std::unique_ptr<> or std::shared_ptr<>
[英]Leaking C++ shared_ptr in Objective-C Block
概括:
在下面的示例應用程序中,在 Objective-C 塊中捕獲了shared_ptr
。 使用 Objective-C 運行時 API object_setIvarWithStrongDefault
將 Objective-C 塊分配給動態創建的類的ivar
。 當 Objective-C 對象被釋放時, shared_ptr
正在泄漏,並且它保留的 C++ 對象不會被刪除。 這是為什么?
當使用object_setIvar
,可以防止泄漏,但是一旦塊超出范圍, ivar
指向垃圾,因為object_setIvar
假定分配了unsafe_unretained
。
我認為這與 Objective-C 如何捕獲 C++ 對象、復制塊以及shared_ptr
如何處理被復制有關,但我希望有人能比下面列出的文檔更清楚地說明這一點。
參考:
Apple 的 Blocks and Variables Documentation包含一個關於 C++ 對象的簡短部分,但我並不完全清楚它如何影響共享指針。
LLVM 的 Blocks & C++ Support 文檔比 Apple 的文檔更詳細一些...
objc-class.mm包含object_setIvarWithStrongDefault
的實現
背景故事:
此示例代碼是從一個更大的項目中提取的,並且已顯着減少到顯示問題所需的最低限度。 該項目是一個 Objective-C macOS 應用程序。 該應用程序包含幾個單一的 C++ 對象,它們是美化的鍵/值存儲。 每個對象都是同一個類的實例,但在鍵類型上進行了模板化。 我想動態創建一個包含由 C++ 類支持的類型化屬性 getter 的 Objective-C 類。
(是的,這一切都可以通過自己編寫大量的 getter 來手動完成,但我不想這樣做。C++ 類有足夠的信息來了解屬性的名稱及其類型,因此我會喜歡使用一些元編程技術來“解決”這個問題。)
筆記:
在理想的世界中,我只能在適當shared_ptr
類型的 Objective-C 類上定義iVar
,但我無法弄清楚如何使用 Objective-C 運行時 API 來做到這一點。
鑒於這種:
std::shared_ptr<BackingStore<T>> backingStore
你如何使用這個:
class_addIvar
和object_setIvar
由於我無法弄清楚,我決定將 shared_ptr 包裝到一個 Objective-C 塊中,因為塊是一流的對象,可以在需要id
地方傳遞。
示例應用:
(復制/粘貼到類似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
}
}
讓我們快速進入時間機器,CA 2010。在必須處理多架構切片、64 位和其他花哨的東西(例如重要的 ARC)之前,這是一個更簡單的時間。
在這個看似遙遠的世界到今天,當你有記憶的時候,你不得不自己喘口氣來釋放它。 這意味着,如果你的類上有一個 iVar,你必須明確地在dealloc
內部調用release
。
嗯,這實際上並沒有隨着 ARC 改變。 唯一改變的是編譯器在dealloc
內部為您生成所有這些好的release
調用,即使您沒有定義該方法。 多好。
然而,這里的問題是編譯器實際上並不知道包含該塊的 iVar - 它是在運行時完全定義的。 那么編譯器如何釋放內存呢?
答案是不會。 你需要做一些魔法來確保你在運行時釋放這些東西。 我的建議是迭代類的 iVars,並將它們設置為nil
,而不是直接調用 objc_release (因為如果您使用 ARC,它會導致大量的哭泣和咬牙切齒)。
像這樣的東西:
for (ivar in class) {
if ivar_type == @encode(id) {
objc_setIvar(self, ivar, nil)
}
}
現在,如果您進入並有意向此類添加 __unsafe_unretained ivar,您可能會遇到更多問題。 但是你真的不應該從這樣的類繼承,嗯?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.