对于标题中的问题,我会封装这样一个类来简化内存管理范围。这是此类的简约实现:
template<typename Key> // Key type must not be a retain-release object
class id_unordered_map {
std::unordered_map<Key, id> m_underlying_map;
void release_objects() {
for (const auto& element: m_underlying_map) {
[element.second release];
}
m_underlying_map.clear();
}
public:
id_unordered_map() = default;
id_unordered_map(const id_unordered_map& rhs) {
for (const auto& element: rhs.m_underlying_map) {
// makes a shallow copy
m_underlying_map[element.first] = [element.second retain];
}
};
id_unordered_map(id_unordered_map&& rhs) {
for (const auto& element: rhs.m_underlying_map) {
m_underlying_map[element.first] = [element.second retain];
}
rhs.release_objects();
}
id_unordered_map& operator=(const id_unordered_map& rhs) {
release_objects();
for (const auto& element: rhs.m_underlying_map) {
// makes a shallow copy
m_underlying_map[element.first] = [element.second retain];
}
return *this;
}
id_unordered_map& operator=(id_unordered_map&& rhs) {
release_objects();
for (const auto& element: rhs.m_underlying_map) {
m_underlying_map[element.first] = [element.second retain];
}
rhs.release_objects();
return *this;
}
void addObject(const Key& key, id object) {
if (object) {
m_underlying_map[key] = [object retain];
}
}
id getObject(Key key) {
if (auto it = m_underlying_map.find(key); it != m_underlying_map.end()) {
return it->second;
} else {
return nil;
}
}
void removeObject(Key key) {
if (auto it = m_underlying_map.find(key); it != m_underlying_map.end()) {
[it->second release];
m_underlying_map.erase(it);
}
}
~id_unordered_map() {
release_objects();
}
};
您可能对我在这里建议的浅拷贝方法感到有点困惑,但它与 Cocoa 自己的集合的工作方式是一致的。进行深度复制被认为是一种例外情况,需要单独的方法(例如 initWithDictionary:copyItems:NSDictionary 的构造函数)
但是,就我个人而言,我不认为所提供的代码中存在导致崩溃发生的明显错误。当消息发送到未设置为 nil 但已发布的对象时,通常会发生您观察到的错误。如果没有 release 消息发送到函数之间映射中的对象,您的 SKProduct 对象必须存活。
不过,这里有几点需要考虑:
-
productsRequest:didReceiveResponse: 调用线程未指定,它与 UI 线程有 99% 的不同,我假设你的
make_purchase 函数是从中调用的。这意味着,在委托线程中生成的对象可能会离开它们创建时所在的自动释放池(但是,如果您 retained 对象并且在读取/写入时没有竞争条件发生,这应该不是问题地图)。
-
[SKPayment paymentWithProduct: product]; 返回一个自动释放的对象,该对象不会(至少)在当前范围结束之前过期,因此您不需要 retain 它。
- 如果您在应用程序的生命周期中多次发出产品请求,请确保在将新数据写入地图之前释放地图包含的对象并
clear()它。
总结一下,你的SKProductsRequestDelegate应该看起来像这样(这里的产品是人造的,所以我在回复中即时制作):
NS_ASSUME_NONNULL_BEGIN
@interface TDWObject ()<SKProductsRequestDelegate>
@property (strong, readonly, nonatomic) dispatch_queue_t productsSyncQueue;
@property (assign, nonatomic) id_unordered_map<std::string> products;
@property (strong, nonatomic) NSMutableSet<SKProductsRequest *> *pendingRequests;
@end
NS_ASSUME_NONNULL_END
@implementation TDWObject
@synthesize products = _products;
#pragma mark Lifecycle
- (instancetype)init {
if (self = [super init]) {
_productsSyncQueue = dispatch_queue_create("the.dreams.wind.property_access.products",
DISPATCH_QUEUE_CONCURRENT);
_pendingRequests = [[NSMutableSet set] retain];
}
return self;
}
- (void)dealloc {
[_pendingRequests release];
_pendingRequests = nil;
[_productsSyncQueue release];
_productsSyncQueue = nil;
[super dealloc];
}
#pragma mark Properties
- (id_unordered_map<std::string>)products {
__block id_unordered_map<std::string> *data;
dispatch_sync(_productsSyncQueue, ^{
// Take by pointer here, to avoid redundant copy
data = &_products;
});
return *data; // makes a copy for observers
}
- (void)setProducts:(id_unordered_map<std::string>)products {
dispatch_barrier_async(_productsSyncQueue, ^{
_products = std::move(products);
});
}
#pragma mark Actions
- (void)requestProducts {
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:@[
@"the.dreams.wind.sampleSKU1"
]]];
productRequest.delegate = self;
[productRequest start];
[_pendingRequests addObject:productRequest];
}
- (void)makePurchase {
SKProduct *product = [_products.getObject("the.dreams.wind.sampleSKU1") retain];
// Just checking that the object exists
NSLog(@"%@", product);
[product release];
}
#pragma mark SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[_pendingRequests removeObject:request];
[response.invalidProductIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
SKProduct *product = [SKProduct new];
auto products = self.products;
products.addObject([obj UTF8String], product);
self.products = products;
[product release];
}];
}
@end
您可以在这里看到属性的访问/读取与 GCD 和 Objective-C 的使用同步,我承认,当涉及到 C++ 对象(在 getter 和 setter 中复制)时,这非常低效,您会想要对其进行优化,但它应该可以在您的场景中正常工作而不会崩溃。
您也可以考虑仅通过引用获取 products 数组,而不使用 C++ 对象,这应该可以正常工作并且更直接:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[_pendingRequests removeObject:request];
NSArray<SKProduct *> *products = [response.products retain];
...
}