ios 如何在C++ std::map中正确存储(Objective-C)SKProduct*?

evrscar2  于 2022-11-26  发布在  iOS
关注(0)|答案(1)|浏览(115)

我有一个std::map<std::string, 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*>{};
  const auto product_id = std::string(
    [product.productIdentifier UTF8String]
  );
  for(SKProduct* product in response.products) {
    if(product != nil) {
      map[product_id] = product;
      [map[product_id] retain]; 
    }
  }
  s_map = map;
}

后来(购买时)我发现SKProduct* 如下所示:

auto make_purchase(const product_id_t& product_id) -> void {
  // Note that the whole map is copied
  const std::map<std::string, SKProduct*> map = s_map;
  const auto product_it = map.find(product_id);
  if(it == map.end()) {
    return;
  }
  // Here somewhere I get a crash objc_retain_x0
  SKProduct* product = product_it->second;
  [product retain];
  SKPayment* payment = [SKPayment paymentWithProduct: product];
  [payment retain]; 
  // Continue the purchase from here on...
}

当我从std::map中存储/检索SKProduct*时,我是否做错了什么?我不熟悉引用计数的Objective-C模型。
(Note与原始代码相比,为了清楚起见,代码稍微简化了一点)

bvjveswy

bvjveswy1#

对于标题中的问题,我会将这部分封装到一个自定义容器类中,以将内存管理 Package 在其中。

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 clear() {
        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.clear();
    }

    id_unordered_map& operator=(const id_unordered_map& rhs) {
        clear();
        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) {
        clear();
        for (const auto& element: rhs.m_underlying_map) {
            m_underlying_map[element.first] = [element.second retain];
        }
        rhs.clear();
        return *this;
    }

    void setObject(const Key& key, id object) {
        removeObject(key);

        if (object) {
            m_underlying_map[key] = [object retain];
        }
    }

    id getObject(const Key& key) {
        if (auto it = m_underlying_map.find(key); it != m_underlying_map.end()) {
            return it->second;
        } else {
            return nil;
        }
    }

    void removeObject(const 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() {
        clear();
    }
};

这里我建议使用浅拷贝方法,因为它与可可自己的集合工作方式一致。进行深拷贝被认为是一种例外情况,需要作为单独的方法(例如NSDictionaryinitWithDictionary:copyItems:构造函数)
然而,我个人并不认为提供的代码中存在导致应用崩溃的明显错误。您所观察到的错误通常发生在向未设置为nil但已释放的对象发送消息时。如果没有向函数之间的Map中的对象发送release消息,则您的SKProduct对象必须存活。
不过,以下是一些需要考虑的事项:

  • productsRequest:didReceiveResponse:调用线程是未指定的,它与UI线程有99%的不同,我假设您的make_purchase函数是从UI线程调用的。这意味着,在委托线程中派生的对象可能会从创建它们的自动释放池中出来(然而,如果您对对象进行了retain调用,并且在阅读/写Map时没有发生竞态条件,这应该不是问题)。
  • [SKPayment paymentWithProduct: product];返回一个自动释放的对象,该对象至少在当前作用域结束之前不会过期,因此不需要对它执行retain操作。
  • 如果您在应用的生命周期内多次发出产品请求,请确保在将新数据写入Map之前释放Map包含的对象并对其进行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, readonly, 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];
    [_productsSyncQueue release];
    [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];

    decltype(_products) newProducts;
    // Artificial Products
    for (NSString *identifier in response.invalidProductIdentifiers) {
        newProducts.setObject(identifier.UTF8String, [[SKProduct new] autorelease]);
    }
    self.products = newProducts;

}

这里你可以看到,Map的访问/阅读与GCD和Objective-C属性的使用是同步的,我承认,当涉及到通过值访问的C++对象时,这是非常无效的。你会想优化它,但我相信它应该不会崩溃。

**P.S.**您通常还希望同步从pendingRequests集合阅读/写入pendingRequests集合,但这与问题的上下文无关,因此我省略了这一部分。

您还可以考虑只通过引用获取products数组,而不使用C++对象,这样应该可以很好地工作,而且更直接:

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    [_pendingRequests removeObject:request];
    NSArray<SKProduct *> *products = [response.products retain];
    ...
}

相关问题