如何在C++ std::map中正确存储Objective-C的SKProduct*?
问题分析与解答
你在使用std::map<std::string, SKProduct*>存储和检索SKProduct*时存在多处错误,包括逻辑错误、引用计数管理错误,这些问题直接导致了objc_retain_x0崩溃,以下是具体分析和修正方案:
存在的核心错误
1. 致命逻辑错误:product_id定义位置错误
在productsRequest:didReceiveResponse:方法中,你把product_id的定义放在了for循环外部,此时product变量还未在循环中声明,这不仅是语法错误,还会导致所有产品都使用无效的product_id(运行时可能取到垃圾值),最终map中只会存储一个错误的条目。
2. 引用计数管理漏洞
- 存储阶段:将新map赋值给
s_map前,没有释放旧s_map中存储的SKProduct*,会导致内存泄漏——旧对象的引用计数永远无法降到0,无法被销毁。 - 检索阶段:
make_purchase中复制整个s_map完全没必要,既浪费性能又增加复杂度;另外崩溃objc_retain_x0通常是因为尝试retain一个nil或已被释放的野指针对象,大概率是之前的逻辑错误导致取到无效对象。 - 冗余操作:
[SKPayment paymentWithProduct:]返回的是autorelease对象,额外调用retain会造成内存泄漏。 - 语法错误:
if(it == map.end())中的it未定义,应该是product_it。
修正后的代码示例
存储SKProduct的修正代码
// Assume s_map is always accessed in a thread safe way static auto s_map = std::map<std::string, SKProduct*>{}; -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { auto map = std::map<std::string, SKProduct*>{}; for(SKProduct* product in response.products) { if(product != nil) { // 将product_id移到循环内部,获取当前产品的标识符 const auto product_id = std::string([product.productIdentifier UTF8String]); map[product_id] = product; [product retain]; // 直接retain当前产品,确保map持有引用 } } // 释放旧map中的所有对象,避免内存泄漏 for (auto& entry : s_map) { [entry.second release]; } s_map.swap(map); // 使用swap避免额外拷贝,提升性能 }
购买逻辑的修正代码
auto make_purchase(const product_id_t& product_id) -> void { const auto product_it = s_map.find(product_id); if(product_it == s_map.end()) { return; } SKProduct* product = product_it->second; // s_map已经持有product的引用,无需额外retain SKPayment* payment = [SKPayment paymentWithProduct: product]; // payment是autorelease对象,无需手动retain // 继续执行购买流程... // 若后续需长时间持有product/payment,再调用retain,并在使用完成后release }
关键引用计数规则总结
- 当需要持有对象(如存入容器)时,调用
retain,引用计数+1; - 当不再需要持有对象(如容器销毁、移除条目)时,调用
release,引用计数-1; - 引用计数降到0时,对象会自动销毁;
- 禁止retain nil对象,这会触发
objc_retain_x0崩溃; - 工厂方法返回的autorelease对象,无需手动retain,除非需要延长其生命周期。
内容的提问来源于stack exchange,提问作者Viktor Sehr
相关产品推荐
相关产品推荐

