У меня есть 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...
}
Я делаю что-то неправильно при сохранении/извлечении SKProduct* из std::map? Я не знаком с моделью подсчета ссылок Objective-C.
(Обратите внимание, что код немного упрощен для ясности по сравнению с исходным кодом)
Кроме того, не могли бы вы подробнее рассказать, как происходит переход между store_map(map) и get_map()? Сохраняет в файл/восстанавливает из файла? Или он хранится в строке, а затем восстанавливается? (мне очень интересно, используются ли одни и те же экземпляры SKProduct * в функции make_purchase
@TheDreamsWind Карта просто хранится как переменная (код обновлен, чтобы быть более понятным). Я хотел бы, чтобы код работал независимо от того, включен или отключен ARC. По сути, я не знаю, когда SKProduct* на карте уничтожается.
Мой [product retain]; просто там, чтобы убедиться, что он не уничтожен. Я не знаю, что я делаю. Например, должно быть map[product_id] = product; быть map[product_id] = [product retain];
Я сомневаюсь, что вы можете сделать код ARC-агностическим, потому что ARC запрещает явные сообщения для методов подсчета ссылок (retain/release/autorelease и т. д.). Однако в MRC этот код имеет несколько чрезмерных retain, но в целом я не вижу причин для его сбоя. Какое именно сообщение об ошибке вы получаете здесь и в какой строке?
И да - вы можете поставить retain встроенный, это не должно иметь никакого значения
Я не могу воспроизвести сбой самостоятельно, я получаю только стеки вызовов сбоев (с именем objc_retain_x0 вверху) от репортера сбоев. Но если я правильно понимаю, SKProduct* не должен как минимум уничтожаться под капотом, даже из-за того, что у меня слишком много сохранений? (Обратите внимание, что ARC отключен, но слабые ссылки в ручном сохранении включены)
Для вопроса в заголовке я бы инкапсулировал эту часть в пользовательский класс-контейнер, чтобы обернуть внутри управление памятью. Вот пример минималистичной реализации:
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();
}
};
Я предлагаю здесь поверхностный подход к копированию, так как он согласуется с тем, как работают собственные коллекции Cocoa. Создание глубокой копии считается исключительным случаем и требует отдельного метода (например, initWithDictionary:copyItems: конструктор NSDictionary)
Однако лично я не чувствую, что в предоставленном коде есть очевидная ошибка, которая приводит к сбою приложения. Ошибка, которую вы наблюдаете, обычно возникает, когда сообщение отправляется объекту, для которого не установлено значение nil, но который уже выпущен. И при условии, что никакие release сообщения не отправляются объектам на карте между функциями, ваш SKProduct объект должен выжить.
Вот несколько вещей, которые следует учитывать:
make_purchase
функция. Это означает, что объекты, порожденные в потоке делегатов, могут выйти из пула автоматического освобождения, в котором они были созданы (это, однако, не должно быть проблемой, если вы retain
добавили объекты и при чтении/записи на карту не возникает состояние гонки) .[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, 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;
}
Здесь вы можете видеть, что доступ/чтение карты синхронизировано с использованием свойств GCD и Objective-C, что, я признаю, ужасно неэффективно, когда речь идет об объектах C++, доступ к которым осуществляется по значению. Вы захотите оптимизировать его, НО я считаю, что он должен работать без сбоев.
P.S. обычно вы также хотите синхронизировать чтение из/запись в набор pendingRequests, но это не имеет отношения к контексту вопроса, поэтому я пропустил эту часть.
Вы также можете подумать о том, чтобы просто взять массивы продуктов по ссылке, не используя объекты C++, что должно работать нормально и намного проще:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[_pendingRequests removeObject:request];
NSArray<SKProduct *> *products = [response.products retain];
...
}
Большое спасибо, будем реализовывать. Кроме того, хорошей идеей будет просто сохранить весь массив. Кроме того, я думаю о том, что вместо того, чтобы обертывать всю карту, я могу просто создать класс, который обертывает объект Objective-C (и сохраняет/освобождает внутри конструктора копирования и т. д.)
Вы компилируете его без включенного ARC?