diff --git a/Framework/Info.plist b/Framework/Info.plist index b09c7d99..8104ce1c 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.9.0 + 5.10.0 CFBundleSignature ???? CFBundleVersion diff --git a/Podfile.lock b/Podfile.lock index 056f105b..14bf8490 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -104,4 +104,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 578415250db59f137d92c9b1ce32646ae18974ba -COCOAPODS: 1.13.0 +COCOAPODS: 1.15.2 diff --git a/Qonversion.podspec b/Qonversion.podspec index de387663..4e78588e 100644 --- a/Qonversion.podspec +++ b/Qonversion.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| idfa_exclude_files = ['Sources/Qonversion/IDFA'] s.name = 'Qonversion' s.swift_version = '5.5' - s.version = '5.9.0' + s.version = '5.10.0' s.summary = 'qonversion.io' s.description = <<-DESC Deep Analytics for iOS Subscriptions diff --git a/Sources/Qonversion/Public/QONConfiguration.m b/Sources/Qonversion/Public/QONConfiguration.m index 9f71dce4..307cdbf0 100644 --- a/Sources/Qonversion/Public/QONConfiguration.m +++ b/Sources/Qonversion/Public/QONConfiguration.m @@ -9,7 +9,7 @@ #import "QONConfiguration.h" #import "QNAPIConstants.h" -static NSString *const kSDKVersion = @"5.9.0"; +static NSString *const kSDKVersion = @"5.10.0"; @interface QONConfiguration () diff --git a/Sources/Qonversion/Public/Qonversion.h b/Sources/Qonversion/Public/Qonversion.h index 959fe78d..4f41ba26 100644 --- a/Sources/Qonversion/Public/Qonversion.h +++ b/Sources/Qonversion/Public/Qonversion.h @@ -128,7 +128,7 @@ static NSString *const QonversionApiErrorDomain = @"com.qonversion.io.api"; @param data Dictionary received by the provider @param provider Attribution provider */ -- (void)attribution:(NSDictionary *)data fromProvider:(QONAttributionProvider)provider; +- (void)attribution:(NSDictionary *)data fromProvider:(QONAttributionProvider)provider DEPRECATED_MSG_ATTRIBUTE("This function shouldn't be called anymore. All attribution logic continues to work as usual."); /** Check user entitlements diff --git a/Sources/Qonversion/Public/Qonversion.m b/Sources/Qonversion/Public/Qonversion.m index c1112d9f..4334bd68 100644 --- a/Sources/Qonversion/Public/Qonversion.m +++ b/Sources/Qonversion/Public/Qonversion.m @@ -138,7 +138,7 @@ - (void)setPromoPurchasesDelegate:(id)delegate { } - (void)attribution:(NSDictionary *)data fromProvider:(QONAttributionProvider)provider { - [self.attributionManager addAttributionData:data fromProvider:provider]; + // This function logic is deprecated and is not needed anymore } - (void)setUserProperty:(QONUserPropertyKey)key value:(NSString *)value { @@ -258,7 +258,7 @@ - (instancetype)initWithCustomUserDefaults:(NSUserDefaults *)userDefaults { _productCenterManager.remoteConfigManager = _remoteConfigManager; _remoteConfigManager.productCenterManager = _productCenterManager; - + _remoteConfigManager.userPropertiesManager = _propertiesManager; _debugMode = NO; } diff --git a/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.h b/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.h index 4d2fe199..94b018aa 100644 --- a/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.h +++ b/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.h @@ -4,7 +4,6 @@ NS_ASSUME_NONNULL_BEGIN @interface QNAttributionManager : NSObject -- (void)addAttributionData:(NSDictionary *)data fromProvider:(NSInteger)provider; - (void)addAppleSearchAttributionData; @end diff --git a/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.m b/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.m index 5f9aa2f5..a20a85d4 100644 --- a/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.m +++ b/Sources/Qonversion/Qonversion/Main/QNAttributionManager/QNAttributionManager.m @@ -23,20 +23,6 @@ - (instancetype)init return self; } -- (void)addAttributionData:(NSDictionary *)data fromProvider:(NSInteger)provider { - double delayInSeconds = 5.0; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - - dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ - - [self.client attributionRequest:provider data:data completion:^(NSDictionary * _Nullable dict, NSError * _Nullable error) { - if (dict && [dict respondsToSelector:@selector(valueForKey:)]) { - QONVERSION_LOG(@"Attribution Request Log Response:\n%@", dict); - } - }]; - }); -} - - (void)addAppleSearchAttributionData { double delayInSeconds = 5.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); diff --git a/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.h b/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.h index 5b7b5a9b..a3ba2aa7 100644 --- a/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.h +++ b/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.h @@ -5,14 +5,17 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^QONUserPropertiesEmptyCompletionHandler)(void) NS_SWIFT_NAME(Qonversion.UserPropertiesEmptyCompletionHandler); + @interface QNUserPropertiesManager : NSObject @property (nonatomic, strong) QNProductCenterManager *productCenterManager; @property (nonatomic, strong) QONUserPropertiesMapper *mapper; - (void)setUserProperty:(NSString *)property value:(NSString *)value; - - (void)getUserProperties:(QONUserPropertiesCompletionHandler)completion; +- (void)forceSendProperties:(QONUserPropertiesEmptyCompletionHandler)completion; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.m b/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.m index f6b4ab93..6a95b294 100644 --- a/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.m +++ b/Sources/Qonversion/Qonversion/Main/QNUserPropertiesManager/QNUserPropertiesManager.m @@ -26,6 +26,8 @@ @interface QNUserPropertiesManager() @property (nonatomic) QNInMemoryStorage *inMemoryStorage; +@property (nonatomic, strong) NSMutableArray *completionBlocks; + @property (nonatomic, assign, readwrite) BOOL sendingScheduled; @property (nonatomic, assign, readwrite) BOOL updatingCurrently; @property (nonatomic, assign, readwrite) NSUInteger retryDelay; @@ -46,7 +48,9 @@ - (instancetype)init { [_backgroundQueue setSuspended:NO]; _apiClient = [QNAPIClient shared]; _mapper = [QONUserPropertiesMapper new]; - + + _completionBlocks = [NSMutableArray new]; + _backgroundQueue.name = kBackgroundQueueName; _device = QNDevice.current; _retryDelay = kQPropertiesSendingPeriodInSeconds; @@ -61,10 +65,8 @@ - (instancetype)init { - (void)setUserProperty:(NSString *)property value:(NSString *)value { if ([QNProperties checkProperty:property] && [QNProperties checkValue:value]) { - [self runOnBackgroundQueue:^{ - [self->_inMemoryStorage storeObject:value forKey:property]; - [self sendPropertiesWithDelay:self.retryDelay]; - }]; + [self.inMemoryStorage storeObject:value forKey:property]; + [self sendPropertiesWithDelay:self.retryDelay]; } } @@ -86,6 +88,17 @@ - (void)enterBackground { [self sendPropertiesInBackground]; } +- (void)forceSendProperties:(QONUserPropertiesEmptyCompletionHandler)completion { + if (self.inMemoryStorage.storageDictionary.count == 0) { + completion(); + return; + } + + [self.completionBlocks addObject:completion]; + + [self sendProperties:YES]; +} + - (void)sendPropertiesWithDelay:(NSUInteger)delay { if (!_sendingScheduled) { _sendingScheduled = YES; @@ -104,13 +117,17 @@ - (void)sendPropertiesInBackground { } - (void)sendProperties { + [self sendProperties:NO]; +} + +- (void)sendProperties:(BOOL)force { if ([QNUtils isEmptyString:_apiClient.apiKey]) { QONVERSION_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with launchWithKey:"); return; } @synchronized (self) { - if (_updatingCurrently) { + if (_updatingCurrently && !force) { return; } _updatingCurrently = YES; @@ -129,12 +146,30 @@ - (void)sendProperties { return; } + self.inMemoryStorage.storageDictionary = @{}; __block __weak QNUserPropertiesManager *weakSelf = self; [self.apiClient sendProperties:properties - completion:^(NSDictionary * _Nullable dict, NSError * _Nullable error) { + completion:^(NSDictionary * _Nullable dict, NSError * _Nullable error) { weakSelf.updatingCurrently = NO; + NSArray *completions = [weakSelf.completionBlocks copy]; + for (QONUserPropertiesEmptyCompletionHandler storedCompletion in completions) { + storedCompletion(); + } + + [weakSelf.completionBlocks removeAllObjects]; + if (error) { + // copy of an existing array to prevent erasing properties set while the current request is in progress + NSMutableDictionary *allProperties = [self.inMemoryStorage.storageDictionary mutableCopy]; + for (NSString *key in properties.allKeys) { + if (!allProperties[key]) { + allProperties[key] = properties[key]; + } + } + + self.inMemoryStorage.storageDictionary = [allProperties copy]; + if ([error.domain isEqualToString:QonversionApiErrorDomain] && error.code == QONAPIErrorInvalidClientUID) { [weakSelf.productCenterManager launchWithCompletion:^(QONLaunchResult * _Nonnull result, NSError * _Nullable error) { [weakSelf retryProperties]; @@ -145,7 +180,6 @@ - (void)sendProperties { } else { weakSelf.retryDelay = kQPropertiesSendingPeriodInSeconds; weakSelf.retriesCounter = 0; - [weakSelf clearProperties:properties]; } }]; }]; @@ -153,12 +187,12 @@ - (void)sendProperties { - (void)getUserProperties:(QONUserPropertiesCompletionHandler)completion { [self.apiClient getProperties:^(NSArray * _Nullable array, NSError * _Nullable error) { - if (error) { - completion(nil, error); - } else { - QONUserProperties *properties = [self.mapper mapUserProperties:array]; - completion(properties, nil); - } + if (error) { + completion(nil, error); + } else { + QONUserProperties *properties = [self.mapper mapUserProperties:array]; + completion(properties, nil); + } }]; } @@ -170,18 +204,6 @@ - (void)retryProperties { [self sendPropertiesWithDelay:self.retryDelay]; } -- (void)clearProperties:(NSDictionary *)properties { - [self runOnBackgroundQueue:^{ - if (!properties || ![properties respondsToSelector:@selector(valueForKey:)]) { - return; - } - - for (NSString *key in properties.allKeys) { - [self->_inMemoryStorage removeObjectForKey:key]; - } - }]; -} - - (BOOL)runOnBackgroundQueue:(void (^)(void))block { if ([[NSOperationQueue currentQueue].name isEqualToString:kBackgroundQueueName]) { QONVERSION_LOG(@"Already running in the background."); diff --git a/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.h b/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.h index 610e37e6..95d64d2a 100644 --- a/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.h +++ b/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.h @@ -10,7 +10,7 @@ #import "QONLaunchResult.h" #import "QONExperiment.h" -@class QONRemoteConfigService, QNProductCenterManager; +@class QONRemoteConfigService, QNProductCenterManager, QNUserPropertiesManager; NS_ASSUME_NONNULL_BEGIN @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) QONRemoteConfigService *remoteConfigService; @property (nonatomic, strong) QNProductCenterManager *productCenterManager; +@property (nonatomic, strong) QNUserPropertiesManager *userPropertiesManager; - (void)userChangingRequestFailedWithError:(NSError *)error; - (void)handlePendingRequests; diff --git a/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.m b/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.m index 820f8ec6..220407eb 100644 --- a/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.m +++ b/Sources/Qonversion/Qonversion/Main/QONRemoteConfigManager/QONRemoteConfigManager.m @@ -14,6 +14,7 @@ #import "QNProductCenterManager.h" #import "QONRemoteConfigLoadingState.h" #import "QONRemoteConfigListRequestData.h" +#import "QNUserPropertiesManager.h" static NSString *const kEmptyContextKey = @""; @@ -91,18 +92,22 @@ - (void)obtainRemoteConfigWithContextKey:(NSString * _Nullable)contextKey comple } loadingState.isInProgress = YES; + __block __weak QONRemoteConfigManager *weakSelf = self; - [self.remoteConfigService loadRemoteConfig:contextKey completion:^(QONRemoteConfig * _Nullable remoteConfig, NSError * _Nullable error) { - loadingState.isInProgress = NO; - if (error) { - [weakSelf executeRemoteConfigCompletionsWithContextKey:contextKey remoteConfig:nil error:error]; - completion(nil, error); - return; - } - - loadingState.loadedConfig = remoteConfig; - [weakSelf executeRemoteConfigCompletionsWithContextKey:contextKey remoteConfig:remoteConfig error:nil]; - completion(remoteConfig, nil); + + [self.userPropertiesManager forceSendProperties:^{ + [weakSelf.remoteConfigService loadRemoteConfig:contextKey completion:^(QONRemoteConfig * _Nullable remoteConfig, NSError * _Nullable error) { + loadingState.isInProgress = NO; + if (error) { + [weakSelf executeRemoteConfigCompletionsWithContextKey:contextKey remoteConfig:nil error:error]; + completion(nil, error); + return; + } + + loadingState.loadedConfig = remoteConfig; + [weakSelf executeRemoteConfigCompletionsWithContextKey:contextKey remoteConfig:remoteConfig error:nil]; + completion(remoteConfig, nil); + }]; }]; } @@ -134,8 +139,12 @@ - (void)obtainRemoteConfigListWithContextKeys:(NSArray *)contextKeys return; } - QONRemoteConfigListCompletionHandler completionWrapper = [self remoteConfigListCompletionWrapper:completion]; - [self.remoteConfigService loadRemoteConfigList:contextKeys includeEmptyContextKey:includeEmptyContextKey completion:completionWrapper]; + __block __weak QONRemoteConfigManager *weakSelf = self; + + [self.userPropertiesManager forceSendProperties:^{ + QONRemoteConfigListCompletionHandler completionWrapper = [weakSelf remoteConfigListCompletionWrapper:completion]; + [weakSelf.remoteConfigService loadRemoteConfigList:contextKeys includeEmptyContextKey:includeEmptyContextKey completion:completionWrapper]; + }]; } - (void)obtainRemoteConfigList:(QONRemoteConfigListCompletionHandler)completion { @@ -147,8 +156,12 @@ - (void)obtainRemoteConfigList:(QONRemoteConfigListCompletionHandler)completion return; } - QONRemoteConfigListCompletionHandler completionWrapper = [self remoteConfigListCompletionWrapper:completion]; - [self.remoteConfigService loadRemoteConfigList:completionWrapper]; + __block __weak QONRemoteConfigManager *weakSelf = self; + + [self.userPropertiesManager forceSendProperties:^{ + QONRemoteConfigListCompletionHandler completionWrapper = [weakSelf remoteConfigListCompletionWrapper:completion]; + [weakSelf.remoteConfigService loadRemoteConfigList:completionWrapper]; + }]; } - (void)attachUserToExperiment:(NSString *)experimentId groupId:(NSString *)groupId completion:(QONExperimentAttachCompletionHandler)completion { diff --git a/fastlane/report.xml b/fastlane/report.xml index 3a8f4d99..a012561d 100644 --- a/fastlane/report.xml +++ b/fastlane/report.xml @@ -5,12 +5,12 @@ - + - +