diff --git a/Parse.xcodeproj/project.pbxproj b/Parse.xcodeproj/project.pbxproj index c14a5559b..99aac42a5 100644 --- a/Parse.xcodeproj/project.pbxproj +++ b/Parse.xcodeproj/project.pbxproj @@ -1473,6 +1473,8 @@ F515355A1B57573700C49F56 /* PFDefaultACLController.h in Headers */ = {isa = PBXBuildFile; fileRef = F51535571B57573700C49F56 /* PFDefaultACLController.h */; }; F515355B1B57573700C49F56 /* PFDefaultACLController.m in Sources */ = {isa = PBXBuildFile; fileRef = F51535581B57573700C49F56 /* PFDefaultACLController.m */; }; F515355C1B57573700C49F56 /* PFDefaultACLController.m in Sources */ = {isa = PBXBuildFile; fileRef = F51535581B57573700C49F56 /* PFDefaultACLController.m */; }; + F5198B001C1B74B700CC6D61 /* ParseClientConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5198AF81C1B744200CC6D61 /* ParseClientConfigurationTests.m */; }; + F5198B011C1B74B800CC6D61 /* ParseClientConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5198AF81C1B744200CC6D61 /* ParseClientConfigurationTests.m */; }; F51D06341B792CF10044539E /* PFSQLiteDatabaseController.h in Headers */ = {isa = PBXBuildFile; fileRef = F51D06321B792CF10044539E /* PFSQLiteDatabaseController.h */; }; F51D06351B792CF10044539E /* PFSQLiteDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = F51D06331B792CF10044539E /* PFSQLiteDatabaseController.m */; }; F51D06371B793A110044539E /* PFSQLiteDatabase_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = F51D06361B793A110044539E /* PFSQLiteDatabase_Private.h */; }; @@ -1551,6 +1553,14 @@ F5B0C4F51BA248F7000AB0D5 /* PFFileDataStream.h in Headers */ = {isa = PBXBuildFile; fileRef = F5B0C4F21BA248F7000AB0D5 /* PFFileDataStream.h */; }; F5B0C4F61BA248F7000AB0D5 /* PFFileDataStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B0C4F31BA248F7000AB0D5 /* PFFileDataStream.m */; }; F5B0C4F71BA248F7000AB0D5 /* PFFileDataStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B0C4F31BA248F7000AB0D5 /* PFFileDataStream.m */; }; + F5B64D8B1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F5B64D8C1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F5B64D8D1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F5B64D8E1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F5B64D8F1BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */; }; + F5B64D901BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */; }; + F5B64D911BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */; }; + F5B64D921BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */; }; F5C42CC71B34C22100C720D8 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 095ACE9913C69BF700566243 /* AudioToolbox.framework */; }; F5C42CD41B34F68C00C720D8 /* PFObjectSubclassingController.h in Headers */ = {isa = PBXBuildFile; fileRef = F5C42CD21B34F68C00C720D8 /* PFObjectSubclassingController.h */; }; F5C42CD51B34F68C00C720D8 /* PFObjectSubclassingController.h in Headers */ = {isa = PBXBuildFile; fileRef = F5C42CD21B34F68C00C720D8 /* PFObjectSubclassingController.h */; }; @@ -2191,11 +2201,13 @@ F51534FC1B571E9100C49F56 /* PFMutableACLState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMutableACLState.m; sourceTree = ""; }; F51535571B57573700C49F56 /* PFDefaultACLController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFDefaultACLController.h; sourceTree = ""; }; F51535581B57573700C49F56 /* PFDefaultACLController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFDefaultACLController.m; sourceTree = ""; }; + F5198AF81C1B744200CC6D61 /* ParseClientConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ParseClientConfigurationTests.m; sourceTree = ""; }; F51D06321B792CF10044539E /* PFSQLiteDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFSQLiteDatabaseController.h; sourceTree = ""; }; F51D06331B792CF10044539E /* PFSQLiteDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFSQLiteDatabaseController.m; sourceTree = ""; }; F51D06361B793A110044539E /* PFSQLiteDatabase_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFSQLiteDatabase_Private.h; sourceTree = ""; }; F5556A141B66F36000410837 /* URLSessionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLSessionTests.m; sourceTree = ""; }; F5556A171B66F47900410837 /* PFURLSession_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFURLSession_Private.h; sourceTree = ""; }; + F556643F1C10F37E006DEC12 /* ParseClientConfiguration_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParseClientConfiguration_Private.h; sourceTree = ""; }; F55ABB531B4F39DA00A0ECD5 /* Parse-iOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Parse-iOS.xcconfig"; sourceTree = ""; }; F55ABB541B4F39DA00A0ECD5 /* Parse-OSX.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Parse-OSX.xcconfig"; sourceTree = ""; }; F55ABB591B4F39DA00A0ECD5 /* ParseUnitTests-iOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "ParseUnitTests-iOS.xcconfig"; sourceTree = ""; }; @@ -2224,6 +2236,8 @@ F5B0B3141B44A21100F3EBC4 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.4.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; F5B0C4F21BA248F7000AB0D5 /* PFFileDataStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFileDataStream.h; sourceTree = ""; }; F5B0C4F31BA248F7000AB0D5 /* PFFileDataStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFileDataStream.m; sourceTree = ""; }; + F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParseClientConfiguration.h; sourceTree = ""; }; + F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ParseClientConfiguration.m; sourceTree = ""; }; F5C42CD21B34F68C00C720D8 /* PFObjectSubclassingController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFObjectSubclassingController.h; sourceTree = ""; }; F5C42CD31B34F68C00C720D8 /* PFObjectSubclassingController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFObjectSubclassingController.m; sourceTree = ""; }; F5C42CD81B38761B00C720D8 /* PFObjectSubclassInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFObjectSubclassInfo.h; sourceTree = ""; }; @@ -2311,6 +2325,8 @@ 09809FB81434F98C00EC3E74 /* Resources */, 09EEA12D1434FB1F00E3A3FA /* Parse.h */, 09EEA12E1434FB1F00E3A3FA /* Parse.m */, + F5B64D891BFA646C0038F3CB /* ParseClientConfiguration.h */, + F5B64D8A1BFA646C0038F3CB /* ParseClientConfiguration.m */, 64C47802147336C70092082F /* PFACL.h */, 64C47803147336C70092082F /* PFACL.m */, 9739513816B9D28E0010B884 /* PFAnalytics.h */, @@ -2398,6 +2414,7 @@ 81E2D5AF19DDAAB5009053A1 /* PFAssert.h */, 816AC9B81A3F48250031D94C /* PFApplication.h */, 816AC9B91A3F48250031D94C /* PFApplication.m */, + F556643F1C10F37E006DEC12 /* ParseClientConfiguration_Private.h */, 81068EEF1AE0845D00A34D13 /* PFEncoder.h */, 81068EF01AE0845D00A34D13 /* PFEncoder.m */, 919311D519AE5EB20008FF12 /* PFDecoder.h */, @@ -2910,6 +2927,7 @@ 814916051B66D44500EFD14F /* OfflineQueryControllerTests.m */, 814916061B66D44500EFD14F /* OfflineQueryLogicUnitTests.m */, 814916071B66D44500EFD14F /* OperationSetUnitTests.m */, + F5198AF81C1B744200CC6D61 /* ParseClientConfigurationTests.m */, 814916081B66D44500EFD14F /* ParseModuleUnitTests.m */, 814916091B66D44500EFD14F /* ParseSetupUnitTests.m */, 8149160A1B66D44500EFD14F /* PinningObjectStoreTests.m */, @@ -4015,6 +4033,7 @@ 810156501BB3832700D7C7BD /* PFSubclassing.h in Headers */, 810156511BB3832700D7C7BD /* PFObjectBatchController.h in Headers */, 810156521BB3832700D7C7BD /* PFAnalyticsUtilities.h in Headers */, + F5B64D8E1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */, 810156531BB3832700D7C7BD /* PFObject+Subclass.h in Headers */, 810156541BB3832700D7C7BD /* PFUserState_Private.h in Headers */, 810156551BB3832700D7C7BD /* PFAnonymousAuthenticationProvider.h in Headers */, @@ -4219,6 +4238,7 @@ 815F24091BD04D150054659F /* PFOfflineQueryController.h in Headers */, 815F240A1BD04D150054659F /* PFUser.h in Headers */, 815F240B1BD04D150054659F /* PFSQLiteDatabase_Private.h in Headers */, + F5B64D8D1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */, 815F240C1BD04D150054659F /* PFACLState.h in Headers */, 815F240D1BD04D150054659F /* PFCurrentConfigController.h in Headers */, ); @@ -4232,6 +4252,7 @@ 810B7D761A0291FF003C0909 /* PFMacros.h in Headers */, 815E764D1BDF168A00E1DF8E /* PFPersistenceController.h in Headers */, 81BBE1351A0062B800622646 /* PFRESTAnalyticsCommand.h in Headers */, + F5B64D8B1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */, F5B0C4F41BA248F7000AB0D5 /* PFFileDataStream.h in Headers */, 81CB7FA01B1800E400DC601D /* PFPushController.h in Headers */, 815EE93C19FA56D20076FE5D /* PFHTTPURLRequestConstructor.h in Headers */, @@ -4617,6 +4638,7 @@ F5556A191B66F47900410837 /* PFURLSession_Private.h in Headers */, 8148815A1B795CAC008763BF /* PFPropertyInfo_Private.h in Headers */, 81C9C9F819FEA89200D514C5 /* PFRESTPushCommand.h in Headers */, + F5B64D8C1BFA646C0038F3CB /* ParseClientConfiguration.h in Headers */, 81C7F49A1AF42187007B5418 /* PFFileState.h in Headers */, 81CB7F761B166FF500DC601D /* PFMutableObjectState.h in Headers */, 8166FCC51B503886003841A2 /* PFSQLiteStatement.h in Headers */, @@ -5056,6 +5078,7 @@ 810155371BB3832700D7C7BD /* PFFileStagingController.m in Sources */, 810155381BB3832700D7C7BD /* PFSQLiteDatabaseController.m in Sources */, 815E76541BDF168A00E1DF8E /* PFPersistenceController.m in Sources */, + F5B64D921BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */, 810155391BB3832700D7C7BD /* PFFileManager.m in Sources */, 8101553B1BB3832700D7C7BD /* PFPinningEventuallyQueue.m in Sources */, 8101553C1BB3832700D7C7BD /* PFRESTQueryCommand.m in Sources */, @@ -5262,6 +5285,7 @@ 815F23261BD04D150054659F /* PFHTTPURLRequestConstructor.m in Sources */, 815F23271BD04D150054659F /* PFObjectUtilities.m in Sources */, 815F23281BD04D150054659F /* PFURLSessionJSONDataTaskDelegate.m in Sources */, + F5B64D911BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */, 815F23291BD04D150054659F /* PFObjectEstimatedData.m in Sources */, 815F232A1BD04D150054659F /* PFConfig.m in Sources */, 815F232B1BD04D150054659F /* PFMultiProcessFileLockController.m in Sources */, @@ -5378,6 +5402,7 @@ 814916D31B66D44600EFD14F /* SessionUnitTests.m in Sources */, 81E0337E1B57441F00B25168 /* CLLocationManager+TestAdditions.m in Sources */, 814916471B66D44600EFD14F /* CommandUnitTests.m in Sources */, + F5198B001C1B74B700CC6D61 /* ParseClientConfigurationTests.m in Sources */, 814916611B66D44600EFD14F /* FieldOperationTests.m in Sources */, 814916A11B66D44600EFD14F /* ParseModuleUnitTests.m in Sources */, 814916951B66D44600EFD14F /* ObjectSubclassTests.m in Sources */, @@ -5439,6 +5464,7 @@ 8149168A1B66D44600EFD14F /* ObjectLocalIdStoreTests.m in Sources */, 814916A61B66D44600EFD14F /* PinningObjectStoreTests.m in Sources */, 814916501B66D44600EFD14F /* ConfigUnitTests.m in Sources */, + F5198B011C1B74B800CC6D61 /* ParseClientConfigurationTests.m in Sources */, 814916E01B66D44600EFD14F /* UserFileCodingLogicTests.m in Sources */, 814916D41B66D44600EFD14F /* SessionUnitTests.m in Sources */, 811AAF191B72D7E400B1AC1F /* ObjectFilePersistenceControllerTests.m in Sources */, @@ -5558,6 +5584,7 @@ 8166FC751B50376D003841A2 /* PFObjectController.m in Sources */, 81C3826A19CCAD7F0066284A /* PFCategoryLoader.m in Sources */, 8166FCDB1B503914003841A2 /* PFUserAuthenticationController.m in Sources */, + F5B64D8F1BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */, F5E8DE1B1B29100000EEA594 /* PFRelationState.m in Sources */, 8127148A1AE6F1270076AE8D /* ParseManager.m in Sources */, 81CB7F901B1795C000DC601D /* PFPushState.m in Sources */, @@ -5710,6 +5737,7 @@ 8166FC661B50375D003841A2 /* PFOperationSet.m in Sources */, 81C7F4B31AF42BD9007B5418 /* PFQueryState.m in Sources */, 81BF4ABF1B0BF64B00A3D75B /* PFCurrentConfigController.m in Sources */, + F5B64D901BFA646C0038F3CB /* ParseClientConfiguration.m in Sources */, 811214761B3E1CF10052741B /* PFObjectBatchController.m in Sources */, 81986CA51A412277007B8860 /* PFApplication.m in Sources */, 81A715A71B423A4100A504FC /* PFObjectUtilities.m in Sources */, diff --git a/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.h b/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.h index 7f75dfd56..56124fac0 100644 --- a/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.h +++ b/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.h @@ -16,6 +16,15 @@ NS_ASSUME_NONNULL_BEGIN @interface PFURLSessionCommandRunner : NSObject - (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDataSource:(id)dataSource + retryAttempts:(NSUInteger)retryAttempts + applicationId:(NSString *)applicationId + clientKey:(NSString *)clientKey; + ++ (instancetype)commandRunnerWithDataSource:(id)dataSource + retryAttempts:(NSUInteger)retryAttempts + applicationId:(NSString *)applicationId + clientKey:(NSString *)clientKey; @end diff --git a/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.m b/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.m index 1a304fc6d..0dee2faa7 100644 --- a/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.m +++ b/Parse/Internal/Commands/CommandRunner/URLSession/PFURLSessionCommandRunner.m @@ -33,6 +33,7 @@ @interface PFURLSessionCommandRunner () @property (nonatomic, strong) NSNotificationCenter *notificationCenter; +@property (nonatomic, assign) NSUInteger retryAttempts; @end @@ -53,8 +54,20 @@ - (instancetype)init { - (instancetype)initWithDataSource:(id)dataSource applicationId:(NSString *)applicationId clientKey:(NSString *)clientKey { + return [self initWithDataSource:dataSource + retryAttempts:PFCommandRunningDefaultMaxAttemptsCount + applicationId:applicationId + clientKey:clientKey]; + +} + +- (instancetype)initWithDataSource:(id)dataSource + retryAttempts:(NSUInteger)retryAttempts + applicationId:(NSString *)applicationId + clientKey:(NSString *)clientKey { NSURLSessionConfiguration *configuration = [[self class] _urlSessionConfigurationForApplicationId:applicationId clientKey:clientKey]; + PFURLSession *session = [PFURLSession sessionWithConfiguration:configuration delegate:self]; PFCommandURLRequestConstructor *constructor = [PFCommandURLRequestConstructor constructorWithDataSource:dataSource]; self = [self initWithDataSource:dataSource @@ -63,6 +76,7 @@ - (instancetype)initWithDataSource:(id)da notificationCenter:[NSNotificationCenter defaultCenter]]; if (!self) return nil; + _retryAttempts = retryAttempts; _applicationId = [applicationId copy]; _clientKey = [clientKey copy]; @@ -77,6 +91,7 @@ - (instancetype)initWithDataSource:(id)da if (!self) return nil; _initialRetryDelay = PFCommandRunningDefaultRetryDelay; + _retryAttempts = PFCommandRunningDefaultMaxAttemptsCount; _requestConstructor = requestConstructor; _session = session; @@ -91,6 +106,16 @@ + (instancetype)commandRunnerWithDataSource:(id)dataSource + retryAttempts:(NSUInteger)retryAttempts + applicationId:(NSString *)applicationId + clientKey:(NSString *)clientKey { + return [[self alloc] initWithDataSource:dataSource + retryAttempts:retryAttempts + applicationId:applicationId + clientKey:clientKey]; +} + ///-------------------------------------- #pragma mark - Dealloc ///-------------------------------------- @@ -192,7 +217,7 @@ - (BFTask *)_performCommandRunningBlock:(nonnull id (^)())block return [self _performCommandRunningBlock:block withCancellationToken:cancellationToken delay:delay - forAttempts:PFCommandRunningDefaultMaxAttemptsCount]; + forAttempts:_retryAttempts]; } - (BFTask *)_performCommandRunningBlock:(nonnull id (^)())block @@ -209,7 +234,7 @@ - (BFTask *)_performCommandRunningBlock:(nonnull id (^)())block if ([[task.error userInfo][@"temporary"] boolValue] && attempts > 1) { PFLogError(PFLoggingTagCommon, @"Network connection failed. Making attempt %lu after sleeping for %f seconds.", - (unsigned long)(PFCommandRunningDefaultMaxAttemptsCount - attempts + 1), (double)delay); + (unsigned long)(_retryAttempts - attempts + 1), (double)delay); return [[BFTask taskWithDelay:(int)(delay * 1000)] continueWithBlock:^id(BFTask *task) { return [self _performCommandRunningBlock:block diff --git a/Parse/Internal/ParseClientConfiguration_Private.h b/Parse/Internal/ParseClientConfiguration_Private.h new file mode 100644 index 000000000..6e075d10d --- /dev/null +++ b/Parse/Internal/ParseClientConfiguration_Private.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ParseClientConfiguration.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ParseClientConfiguration () + +@property (nullable, nonatomic, copy, readwrite) NSString *applicationId; +@property (nullable, nonatomic, copy, readwrite) NSString *clientKey; + +@property (nonatomic, assign, readwrite, getter=isLocalDatastoreEnabled) BOOL localDatastoreEnabled; + +@property (nullable, nonatomic, copy, readwrite) NSString *applicationGroupIdentifier; +@property (nullable, nonatomic, copy, readwrite) NSString *containingApplicationBundleIdentifier; + +@property (nonatomic, assign, readwrite) NSUInteger networkRetryAttempts; + ++ (instancetype)emptyConfiguration; +- (instancetype)initEmpty NS_DESIGNATED_INITIALIZER; + +- (void)_resetDataSharingIdentifiers; + +@end + +// We must implement the protocol here otherwise clang issues warnings about non-matching property declarations. +// For some reason if the property declarations are on a separate category, it doesn't care. +@interface ParseClientConfiguration (Private) +@end + +NS_ASSUME_NONNULL_END diff --git a/Parse/Internal/ParseInternal.h b/Parse/Internal/ParseInternal.h index 58e17fcf7..ca91ff3d1 100644 --- a/Parse/Internal/ParseInternal.h +++ b/Parse/Internal/ParseInternal.h @@ -9,7 +9,7 @@ #import -# import +#import #import "PFAssert.h" #import "PFCommandCache.h" diff --git a/Parse/Internal/ParseManager.h b/Parse/Internal/ParseManager.h index a708268bd..2a70e2eca 100644 --- a/Parse/Internal/ParseManager.h +++ b/Parse/Internal/ParseManager.h @@ -9,6 +9,7 @@ #import +#import #import #import "PFDataProvider.h" @@ -32,11 +33,7 @@ PFKeychainStoreProvider, PFKeyValueCacheProvider, PFInstallationIdentifierStoreProvider> -@property (nonatomic, copy, readonly) NSString *applicationId; -@property (nonatomic, copy, readonly) NSString *clientKey; - -@property (nonatomic, copy, readonly) NSString *applicationGroupIdentifier; -@property (nonatomic, copy, readonly) NSString *containingApplicationIdentifier; +@property (nonatomic, copy, readonly) ParseClientConfiguration *configuration; @property (nonatomic, strong, readonly) PFCoreManager *coreManager; @@ -59,24 +56,16 @@ PFInstallationIdentifierStoreProvider> /** Initializes an instance of ParseManager class. - @param applicationId ApplicationId of Parse app. - @param clientKey ClientKey of Parse app. + @param configuration Configuration of parse app. @return `ParseManager` instance. */ -- (instancetype)initWithApplicationId:(NSString *)applicationId - clientKey:(NSString *)clientKey NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithConfiguration:(ParseClientConfiguration *)configuration; /** - Configures ParseManager with specified properties. - - @param applicationGroupIdentifier Shared AppGroup container identifier. - @param containingApplicationIdentifier Containg application bundle identifier (for extensions). - @param localDataStoreEnabled `BOOL` flag to enable local datastore or not. + Begins all necessary operations for this manager to become active. */ -- (void)configureWithApplicationGroupIdentifier:(NSString *)applicationGroupIdentifier - containingApplicationIdentifier:(NSString *)containingApplicationIdentifier - enabledLocalDataStore:(BOOL)localDataStoreEnabled; +- (void)startManaging; ///-------------------------------------- /// @name Offline Store diff --git a/Parse/Internal/ParseManager.m b/Parse/Internal/ParseManager.m index 5af1a0fcf..7a6245b02 100644 --- a/Parse/Internal/ParseManager.m +++ b/Parse/Internal/ParseManager.m @@ -90,7 +90,7 @@ - (instancetype)init { PFNotDesignatedInitializer(); } -- (instancetype)initWithApplicationId:(NSString *)applicationId clientKey:(NSString *)clientKey { +- (instancetype)initWithConfiguration:(ParseClientConfiguration *)configuration { self = [super init]; if (!self) return nil; @@ -108,26 +108,20 @@ - (instancetype)initWithApplicationId:(NSString *)applicationId clientKey:(NSStr _controllerAccessQueue = dispatch_queue_create("com.parse.controller.access", DISPATCH_QUEUE_SERIAL); _preloadQueue = dispatch_queue_create("com.parse.preload", DISPATCH_QUEUE_SERIAL); - _applicationId = [applicationId copy]; - _clientKey = [clientKey copy]; + _configuration = [configuration copy]; return self; } -- (void)configureWithApplicationGroupIdentifier:(NSString *)applicationGroupIdentifier - containingApplicationIdentifier:(NSString *)containingApplicationIdentifier - enabledLocalDataStore:(BOOL)localDataStoreEnabled { - _applicationGroupIdentifier = [applicationGroupIdentifier copy]; - _containingApplicationIdentifier = [containingApplicationIdentifier copy]; - +- (void)startManaging { // Migrate any data if it's required. [self _migrateSandboxDataToApplicationGroupContainerIfNeeded]; // TODO: (nlutsenko) Make it not terrible! [[self.persistenceController getPersistenceGroupAsync] waitForResult:nil withMainThreadWarning:NO]; - if (localDataStoreEnabled) { - PFOfflineStoreOptions options = (_applicationGroupIdentifier ? + if (self.configuration.localDatastoreEnabled) { + PFOfflineStoreOptions options = (self.configuration.applicationGroupIdentifier ? PFOfflineStoreOptionAlwaysFetchFromSQLite : 0); [self loadOfflineStoreWithOptions:options]; } @@ -138,8 +132,8 @@ - (void)configureWithApplicationGroupIdentifier:(NSString *)applicationGroupIden ///-------------------------------------- - (void)loadOfflineStoreWithOptions:(PFOfflineStoreOptions)options { - PFConsistencyAssert(!_offlineStore, @"Can't load offline store more than once."); dispatch_barrier_sync(_offlineStoreAccessQueue, ^{ + PFConsistencyAssert(!_offlineStore, @"Can't load offline store more than once."); _offlineStore = [[PFOfflineStore alloc] initWithFileManager:self.fileManager options:options]; }); } @@ -159,7 +153,7 @@ - (PFOfflineStore *)offlineStore { } - (BOOL)isOfflineStoreLoaded { - return (self.offlineStore != nil); + return self.configuration.localDatastoreEnabled; } ///-------------------------------------- @@ -226,7 +220,7 @@ - (PFKeychainStore *)keychainStore { __block PFKeychainStore *store = nil; dispatch_sync(_keychainStoreAccessQueue, ^{ if (!_keychainStore) { - NSString *bundleIdentifier = (_containingApplicationIdentifier ?: [[NSBundle mainBundle] bundleIdentifier]); + NSString *bundleIdentifier = (self.configuration.containingApplicationBundleIdentifier ?: [[NSBundle mainBundle] bundleIdentifier]); NSString *service = [NSString stringWithFormat:@"%@.%@", bundleIdentifier, PFKeychainStoreDefaultService]; _keychainStore = [[PFKeychainStore alloc] initWithService:service]; } @@ -241,8 +235,8 @@ - (PFFileManager *)fileManager { __block PFFileManager *fileManager = nil; dispatch_sync(_fileManagerAccessQueue, ^{ if (!_fileManager) { - _fileManager = [[PFFileManager alloc] initWithApplicationIdentifier:self.applicationId - applicationGroupIdentifier:self.applicationGroupIdentifier]; + _fileManager = [[PFFileManager alloc] initWithApplicationIdentifier:self.configuration.applicationId + applicationGroupIdentifier:self.configuration.applicationGroupIdentifier]; } fileManager = _fileManager; }); @@ -278,7 +272,7 @@ - (PFPersistenceController *)_createPersistenceController { return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; }] continueWithSuccessBlock:^id(BFTask *task) { if (task.result) { - if ([self.applicationId isEqualToString:task.result]) { + if ([self.configuration.applicationId isEqualToString:task.result]) { // Everything is valid, no need to remove, set applicationId here. return nil; } @@ -287,15 +281,15 @@ - (PFPersistenceController *)_createPersistenceController { [self.keyValueCache removeAllObjects]; } return [[group removeAllDataAsync] continueWithSuccessBlock:^id(BFTask *task) { - NSData *applicationIdData = [self.applicationId dataUsingEncoding:NSUTF8StringEncoding]; + NSData *applicationIdData = [self.configuration.applicationId dataUsingEncoding:NSUTF8StringEncoding]; return [group setDataAsync:applicationIdData forKey:_ParseApplicationIdFileName]; }]; }] continueWithBlock:^id(BFTask *task) { return [group endLockedContentAccessAsyncToDataForKey:_ParseApplicationIdFileName]; }]; }; - return [[PFPersistenceController alloc] initWithApplicationIdentifier:self.applicationId - applicationGroupIdentifier:self.applicationGroupIdentifier + return [[PFPersistenceController alloc] initWithApplicationIdentifier:self.configuration.applicationId + applicationGroupIdentifier:self.configuration.applicationGroupIdentifier groupValidationHandler:validationHandler]; } @@ -319,8 +313,9 @@ - (PFInstallationIdentifierStore *)installationIdentifierStore { dispatch_sync(_commandRunnerAccessQueue, ^{ if (!_commandRunner) { _commandRunner = [PFURLSessionCommandRunner commandRunnerWithDataSource:self - applicationId:self.applicationId - clientKey:self.clientKey]; + retryAttempts:self.configuration.networkRetryAttempts + applicationId:self.configuration.applicationId + clientKey:self.configuration.clientKey]; } runner = _commandRunner; }); @@ -455,7 +450,7 @@ - (void)_migrateSandboxDataToApplicationGroupContainerIfNeeded { // There is no need to migrate anything on OSX, since we are using globally available folder. #if TARGET_OS_IOS // Do nothing if there is no application group container or containing application is specified. - if (!self.applicationGroupIdentifier || self.containingApplicationIdentifier) { + if (!self.configuration.applicationGroupIdentifier || self.configuration.containingApplicationBundleIdentifier) { return; } diff --git a/Parse/Parse.h b/Parse/Parse.h index 87835896d..0fce0f70b 100644 --- a/Parse/Parse.h +++ b/Parse/Parse.h @@ -9,6 +9,7 @@ #import +#import #import #import #import @@ -66,6 +67,22 @@ NS_ASSUME_NONNULL_BEGIN */ + (void)setApplicationId:(NSString *)applicationId clientKey:(NSString *)clientKey; +/** + Sets the configuration to be used for the Parse SDK. + + @note Re-setting the configuration after having previously sent requests through the SDK results in undefined behavior. + + @param configuration The new configuration to set for the SDK. + */ ++ (void)initializeWithConfiguration:(ParseClientConfiguration *)configuration; + +/** + Gets the current configuration in use by the Parse SDK. + + @return The current configuration in use by the SDK. Returns nil if the SDK has not been initialized yet. + */ ++ (ParseClientConfiguration *)currentConfiguration; + /** The current application id that was used to configure Parse framework. */ diff --git a/Parse/Parse.m b/Parse/Parse.m index 8e59097ab..09e5aec7b 100644 --- a/Parse/Parse.m +++ b/Parse/Parse.m @@ -11,6 +11,7 @@ #import "Parse.h" #import "ParseInternal.h" #import "ParseManager.h" +#import "ParseClientConfiguration_Private.h" #import "PFEventuallyPin.h" #import "PFObject+Subclass.h" #import "PFOfflineStore.h" @@ -34,11 +35,7 @@ @implementation Parse static ParseManager *currentParseManager_; - -static BOOL shouldEnableLocalDatastore_; - -static NSString *applicationGroupIdentifier_; -static NSString *containingApplicationBundleIdentifier_; +static ParseClientConfiguration *currentParseConfiguration_; + (void)initialize { if (self == [Parse class]) { @@ -46,6 +43,8 @@ + (void)initialize { // Without this call - private categories - will require `-ObjC` in linker flags. // By explicitly calling empty method - we can avoid that. [PFCategoryLoader loadPrivateCategories]; + + currentParseConfiguration_ = [ParseClientConfiguration emptyConfiguration]; } } @@ -54,17 +53,30 @@ + (void)initialize { ///-------------------------------------- + (void)setApplicationId:(NSString *)applicationId clientKey:(NSString *)clientKey { - PFConsistencyAssert([applicationId length], @"'applicationId' should not be nil."); - PFConsistencyAssert([clientKey length], @"'clientKey' should not be nil."); - - // Setup new manager first, so it's 100% ready whenever someone sends a request for anything. - ParseManager *manager = [[ParseManager alloc] initWithApplicationId:applicationId clientKey:clientKey]; - [manager configureWithApplicationGroupIdentifier:applicationGroupIdentifier_ - containingApplicationIdentifier:containingApplicationBundleIdentifier_ - enabledLocalDataStore:shouldEnableLocalDatastore_]; - currentParseManager_ = manager; + currentParseConfiguration_.applicationId = applicationId; + currentParseConfiguration_.clientKey = clientKey; + + [self initializeWithConfiguration:currentParseConfiguration_]; + + // This is needed to reset LDS's state in between initializations of Parse. We rely on this in the + // context of unit tests. + currentParseConfiguration_.localDatastoreEnabled = NO; +} - shouldEnableLocalDatastore_ = NO; ++ (void)initializeWithConfiguration:(ParseClientConfiguration *)configuration { + PFConsistencyAssert(configuration.applicationId.length != 0, + @"You must set your configuration's `applicationId` before calling %s!", __PRETTY_FUNCTION__); + PFConsistencyAssert(configuration.clientKey.length != 0, + @"You must set your configuration's `clientKey` before calling %s!", __PRETTY_FUNCTION__); + PFConsistencyAssert(![PFApplication currentApplication].extensionEnvironment || + configuration.applicationGroupIdentifier == nil || + configuration.containingApplicationBundleIdentifier != nil, + @"'containingApplicationBundleIdentifier' must be non-nil in extension environment"); + + ParseManager *manager = [[ParseManager alloc] initWithConfiguration:configuration]; + [manager startManaging]; + + currentParseManager_ = manager; PFObjectSubclassingController *subclassingController = [PFObjectSubclassingController defaultController]; // Register built-in subclasses of PFObject so they get used. @@ -88,19 +100,23 @@ + (void)setApplicationId:(NSString *)applicationId clientKey:(NSString *)clientK [currentParseManager_ preloadDiskObjectsToMemoryAsync]; - [[self parseModulesCollection] parseDidInitializeWithApplicationId:applicationId clientKey:clientKey]; + [[self parseModulesCollection] parseDidInitializeWithApplicationId:configuration.applicationId clientKey:configuration.clientKey]; +} + ++ (ParseClientConfiguration *)currentConfiguration { + return currentParseManager_.configuration; } + (NSString *)getApplicationId { PFConsistencyAssert(currentParseManager_, @"You have to call setApplicationId:clientKey: on Parse to configure Parse."); - return currentParseManager_.applicationId; + return currentParseManager_.configuration.applicationId; } + (NSString *)getClientKey { PFConsistencyAssert(currentParseManager_, @"You have to call setApplicationId:clientKey: on Parse to configure Parse."); - return currentParseManager_.clientKey; + return currentParseManager_.configuration.clientKey; } ///-------------------------------------- @@ -112,36 +128,35 @@ + (void)enableDataSharingWithApplicationGroupIdentifier:(NSString *)groupIdentif @"'enableDataSharingWithApplicationGroupIdentifier:' must be called before 'setApplicationId:clientKey'"); PFParameterAssert([groupIdentifier length], @"'groupIdentifier' should not be nil."); PFConsistencyAssert(![PFApplication currentApplication].extensionEnvironment, @"This method cannot be used in application extensions."); - PFConsistencyAssert([PFFileManager isApplicationGroupContainerReachableForGroupIdentifier:groupIdentifier], - @"ApplicationGroupContainer is unreachable. Please double check your Xcode project settings."); - applicationGroupIdentifier_ = [groupIdentifier copy]; + + currentParseConfiguration_.applicationGroupIdentifier = groupIdentifier; } + (void)enableDataSharingWithApplicationGroupIdentifier:(NSString *)groupIdentifier - containingApplication:(NSString *)bundleIdentifier { + containingApplication:(NSString *)bundleIdentifier { PFConsistencyAssert(!currentParseManager_, @"'enableDataSharingWithApplicationGroupIdentifier:containingApplication:' must be called before 'setApplicationId:clientKey'"); PFParameterAssert([groupIdentifier length], @"'groupIdentifier' should not be nil."); PFParameterAssert([bundleIdentifier length], @"Containing application bundle identifier should not be nil."); - PFConsistencyAssert([PFApplication currentApplication].extensionEnvironment, @"This method can only be used in application extensions."); - PFConsistencyAssert([PFFileManager isApplicationGroupContainerReachableForGroupIdentifier:groupIdentifier], - @"ApplicationGroupContainer is unreachable. Please double check your Xcode project settings."); - applicationGroupIdentifier_ = groupIdentifier; - containingApplicationBundleIdentifier_ = bundleIdentifier; + currentParseConfiguration_.applicationGroupIdentifier = groupIdentifier; + currentParseConfiguration_.containingApplicationBundleIdentifier = bundleIdentifier; } + (NSString *)applicationGroupIdentifierForDataSharing { - return applicationGroupIdentifier_; + ParseClientConfiguration *config = currentParseManager_ ? currentParseManager_.configuration + : currentParseConfiguration_; + return config.applicationGroupIdentifier; } + (NSString *)containingApplicationBundleIdentifierForDataSharing { - return containingApplicationBundleIdentifier_; + ParseClientConfiguration *config = currentParseManager_ ? currentParseManager_.configuration + : currentParseConfiguration_; + return config.containingApplicationBundleIdentifier; } + (void)_resetDataSharingIdentifiers { - applicationGroupIdentifier_ = nil; - containingApplicationBundleIdentifier_ = nil; + [currentParseConfiguration_ _resetDataSharingIdentifiers]; } ///-------------------------------------- @@ -154,12 +169,12 @@ + (void)enableLocalDatastore { // Lazily enableLocalDatastore after init. We can't use ParseModule because // ParseModule isn't processed in main thread and may cause race condition. - shouldEnableLocalDatastore_ = YES; + currentParseConfiguration_.localDatastoreEnabled = YES; } + (BOOL)isLocalDatastoreEnabled { if (!currentParseManager_) { - return shouldEnableLocalDatastore_; + return currentParseConfiguration_.localDatastoreEnabled; } return currentParseManager_.offlineStoreLoaded; } diff --git a/Parse/ParseClientConfiguration.h b/Parse/ParseClientConfiguration.h new file mode 100644 index 000000000..5dc926c27 --- /dev/null +++ b/Parse/ParseClientConfiguration.h @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The `ParseMutableClientConfiguration` represents a `ParseClientConfiguration` object that can be mutated. + + It is only usable during the execution of the block passed to `ParseClientConfiguration.+configurationWithBlock:`, + during which time you should set your properties on it, similar to the following: + + ``` + configuration.applicationId = @"<#YOUR APPLICATION ID#>" + configuration.clientKey = @"<#YOUR CLIENT KEY#>" + configuration.localDatastoreEnabled = true + ``` + */ +@protocol ParseMutableClientConfiguration + +/** + The Parse.com application id to configure the SDK with. + */ +@property (nullable, nonatomic, copy) NSString *applicationId; + +/** + The Parse.com client key to configure the SDK with. + */ +@property (nullable, nonatomic, copy) NSString *clientKey; + +/** + Whether or not to enable pinning in the SDK. + + The default value is `NO`. + */ +@property (nonatomic, assign, readwrite, getter=isLocalDatastoreEnabled) BOOL localDatastoreEnabled; + +/** + When set, enables data sharing with an application group identifier. + + After enabling - Local Datastore, `PFUser.+currentUser`, `PFInstallation.+currentInstallation` and all eventually commands + are going to be available to every application/extension in a group that have the same Parse applicationId. + */ +@property (nullable, nonatomic, copy) NSString *applicationGroupIdentifier; + +/** + When set, controls the bundle identifier of the parent bundle to connect to. + + @warning This property should only be set from inside an extension environment. + */ +@property (nullable, nonatomic, copy) NSString *containingApplicationBundleIdentifier; + +/** + The maximum number of retry attempts to make upon a failed network request. + */ +@property (nonatomic, assign) NSUInteger networkRetryAttempts; + +@end + +/*! + The `ParseClientConfiguration` represents the local configuration of the SDK to connect to the server with. + + These configurations can be stored, copied, and compared, but cannot be safely changed once the SDK is initialized. + + Use this object to construct a configuration for the SDK in your application, and pass it to + `Parse.+initializeWithConfiguration:`. + */ +@interface ParseClientConfiguration : NSObject + +/** + The Parse.com application id to configure the SDK with. + */ +@property (nullable, nonatomic, copy, readonly) NSString *applicationId; + +/** + The Parse.com client key to configure the SDK with. + */ +@property (nullable, nonatomic, copy, readonly) NSString *clientKey; + +/** + Whether or not to enable pinning in the SDK. + + The default value is `NO`. + */ +@property (nonatomic, assign, readonly, getter=isLocalDatastoreEnabled) BOOL localDatastoreEnabled; + +/** + When set, enables data sharing with an application group identifier. + + After enabling - Local Datastore, `PFUser.+currentUser`, `PFInstallation.+currentInstallation` and all eventually + commands are going to be available to every application/extension in a group that have the same Parse applicationId. + */ +@property (nullable, nonatomic, copy, readonly) NSString *applicationGroupIdentifier; + +/** + When set, controls the bundle identifier of the parent bundle to connect to. + + @warning This property should only be set from inside an extension environment. + */ +@property (nullable, nonatomic, copy, readonly) NSString *containingApplicationBundleIdentifier; + +/** + The maximum number of retry attempts to make upon a failed network request. + */ +@property (nonatomic, assign, readonly) NSUInteger networkRetryAttempts; + +/** + Create a new SDK configuration object. This will create a temporarily modifiable configuration, and pass it to a block + to be initialized. + + Example usage: + + ``` + [ParseClientConfiguration configurationWithBlock:^(id configuration) { + configuration.applicationId = ...; + configuration.clientKey = ...; + configuration.localDatastoreEnabled = ...; + }]; + ``` + + @param configurationBlock A block used to modify the created configuration. + + @return A newly created configuration. + */ ++ (instancetype)configurationWithBlock:(void (^)(id configuration))configurationBlock; + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Parse/ParseClientConfiguration.m b/Parse/ParseClientConfiguration.m new file mode 100644 index 000000000..0a219754d --- /dev/null +++ b/Parse/ParseClientConfiguration.m @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ParseClientConfiguration.h" +#import "ParseClientConfiguration_Private.h" + +#import "PFAssert.h" +#import "PFApplication.h" +#import "PFCommandRunningConstants.h" +#import "PFFileManager.h" +#import "PFHash.h" +#import "PFObjectUtilities.h" + +@implementation ParseClientConfiguration + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + ++ (instancetype)emptyConfiguration { + return [[super alloc] initEmpty]; +} + +- (instancetype)initEmpty { + self = [super init]; + if (!self) return nil; + + _networkRetryAttempts = PFCommandRunningDefaultMaxAttemptsCount; + + return self; +} + +- (instancetype)initWithBlock:(void (^)(id))configurationBlock { + self = [self initEmpty]; + if (!self) return nil; + + configurationBlock(self); + + PFConsistencyAssert(self.applicationId.length, @"`applicationId` should not be nil."); + PFConsistencyAssert(self.clientKey.length, @"`clientKey` should not be nil."); + + return self; +} + ++ (instancetype)configurationWithBlock:(void (^)(id))configurationBlock { + return [[self alloc] initWithBlock:configurationBlock]; +} + +///-------------------------------------- +#pragma mark - Properties +///-------------------------------------- + +- (void)setApplicationId:(NSString *)applicationId { + PFConsistencyAssert(applicationId.length, @"'applicationId' should not be nil."); + _applicationId = [applicationId copy]; +} + +- (void)setClientKey:(NSString *)clientKey { + PFConsistencyAssert(clientKey.length, @"'clientKey' should not be nil."); + _clientKey = [clientKey copy]; +} + +- (void)setApplicationGroupIdentifier:(NSString *)applicationGroupIdentifier { + PFConsistencyAssert(applicationGroupIdentifier == nil || + [PFFileManager isApplicationGroupContainerReachableForGroupIdentifier:applicationGroupIdentifier], + @"ApplicationGroupContainer is unreachable. Please double check your Xcode project settings."); + + _applicationGroupIdentifier = [applicationGroupIdentifier copy]; +} + +- (void)setContainingApplicationBundleIdentifier:(NSString *)containingApplicationBundleIdentifier { + PFConsistencyAssert([PFApplication currentApplication].extensionEnvironment, + @"'containingApplicationBundleIdentifier' cannot be set in non-extension environment"); + PFConsistencyAssert(containingApplicationBundleIdentifier.length, + @"'containingApplicationBundleIdentifier' should not be nil."); + + _containingApplicationBundleIdentifier = containingApplicationBundleIdentifier; +} + +- (void)_resetDataSharingIdentifiers { + _applicationGroupIdentifier = nil; + _containingApplicationBundleIdentifier = nil; +} + +///-------------------------------------- +#pragma mark - NSObject +///-------------------------------------- + +- (NSUInteger)hash { + return PFIntegerPairHash(self.applicationId.hash, self.clientKey.hash); +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ParseClientConfiguration class]]) { + return NO; + } + + ParseClientConfiguration *other = object; + return ([PFObjectUtilities isObject:self.applicationId equalToObject:other.applicationId] && + [PFObjectUtilities isObject:self.clientKey equalToObject:other.clientKey] && + self.localDatastoreEnabled == other.localDatastoreEnabled && + [PFObjectUtilities isObject:self.applicationGroupIdentifier equalToObject:other.applicationGroupIdentifier] && + [PFObjectUtilities isObject:self.containingApplicationBundleIdentifier equalToObject:other.containingApplicationBundleIdentifier] && + self.networkRetryAttempts == other.networkRetryAttempts); +} + +///-------------------------------------- +#pragma mark - NSCopying +///-------------------------------------- + +- (instancetype)copyWithZone:(NSZone *)zone { + return [ParseClientConfiguration configurationWithBlock:^(ParseClientConfiguration *configuration) { + // Use direct assignment to skip over all of the assertions that may fail if we're not fully initialized yet. + configuration->_applicationId = [self->_applicationId copy]; + configuration->_clientKey = [self->_clientKey copy]; + configuration->_localDatastoreEnabled = self->_localDatastoreEnabled; + configuration->_applicationGroupIdentifier = [self->_applicationGroupIdentifier copy]; + configuration->_containingApplicationBundleIdentifier = [self->_containingApplicationBundleIdentifier copy]; + configuration->_networkRetryAttempts = self->_networkRetryAttempts; + }]; +} + +@end diff --git a/Tests/Unit/ParseClientConfigurationTests.m b/Tests/Unit/ParseClientConfigurationTests.m new file mode 100644 index 000000000..835e7290b --- /dev/null +++ b/Tests/Unit/ParseClientConfigurationTests.m @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +@import Foundation; + +#import "PFTestCase.h" +#import "ParseClientConfiguration.h" +#import "ParseClientConfiguration_Private.h" +#import "PFExtensionDataSharingTestHelper.h" + +@interface ParseClientConfigurationTests : PFTestCase { + PFExtensionDataSharingTestHelper *_testHelper; +} +@end + +@implementation ParseClientConfigurationTests + +- (void)setUp { + [super setUp]; + + _testHelper = [[PFExtensionDataSharingTestHelper alloc] init]; +} + +- (void)tearDown { + _testHelper = nil; + + [super tearDown]; +} + +- (void)testConfigurationWithBlock { + ParseClientConfiguration *configuration = [ParseClientConfiguration configurationWithBlock:^(id configuration) { + configuration.applicationId = @"foo"; + configuration.clientKey = @"bar"; + configuration.localDatastoreEnabled = YES; + configuration.networkRetryAttempts = 1337; + }]; + + XCTAssertEqualObjects(configuration.applicationId, @"foo"); + XCTAssertEqualObjects(configuration.clientKey, @"bar"); + XCTAssertTrue(configuration.localDatastoreEnabled); + XCTAssertEqual(configuration.networkRetryAttempts, 1337); +} + +- (void)testEqual { + ParseClientConfiguration *configurationA = [(id)[ParseClientConfiguration alloc] init]; + ParseClientConfiguration *configurationB = [(id)[ParseClientConfiguration alloc] init]; + XCTAssertEqualObjects(configurationA, configurationB); + XCTAssertEqual(configurationA.hash, configurationB.hash); + + configurationA.applicationId = configurationB.applicationId = @"foo"; + XCTAssertEqualObjects(configurationA, configurationB); + XCTAssertEqual(configurationA.hash, configurationB.hash); + configurationB.applicationId = @"test"; + XCTAssertNotEqualObjects(configurationA, configurationB); + configurationB.applicationId = configurationA.applicationId; + + configurationA.clientKey = configurationB.clientKey = @"bar"; + XCTAssertEqualObjects(configurationA, configurationB); + XCTAssertEqual(configurationA.hash, configurationB.hash); + configurationB.clientKey = @"test"; + XCTAssertNotEqualObjects(configurationA, configurationB); + configurationB.clientKey = configurationA.clientKey; + + configurationA.localDatastoreEnabled = configurationB.localDatastoreEnabled = YES; + XCTAssertEqualObjects(configurationA, configurationB); + XCTAssertEqual(configurationA.hash, configurationB.hash); + configurationB.localDatastoreEnabled = NO; + XCTAssertNotEqualObjects(configurationA, configurationB); + configurationB.localDatastoreEnabled = configurationA.localDatastoreEnabled; + + configurationA.networkRetryAttempts = configurationB.networkRetryAttempts = 1337; + XCTAssertEqualObjects(configurationA, configurationB); + XCTAssertEqual(configurationA.hash, configurationB.hash); + configurationB.networkRetryAttempts = 7; + XCTAssertNotEqualObjects(configurationA, configurationB); +} + +- (void)testCopy { + ParseClientConfiguration *configurationA = [ParseClientConfiguration configurationWithBlock:^(id configuration) { + configuration.applicationId = @"foo"; + configuration.clientKey = @"bar"; + configuration.localDatastoreEnabled = YES; + configuration.networkRetryAttempts = 1337; + }]; + + ParseClientConfiguration *configurationB = [configurationA copy]; + + XCTAssertNotEqual(configurationA, configurationB); + XCTAssertEqualObjects(configurationA, configurationB); + + configurationA.localDatastoreEnabled = NO; + + XCTAssertNotEqualObjects(configurationA, configurationB); + + XCTAssertEqualObjects(configurationB.applicationId, @"foo"); + XCTAssertEqualObjects(configurationB.clientKey, @"bar"); + XCTAssertTrue(configurationB.localDatastoreEnabled); + XCTAssertEqual(configurationB.networkRetryAttempts, 1337); +} + +- (void)testExtensionDataSharing { + ParseClientConfiguration *configuration = [ParseClientConfiguration emptyConfiguration]; + +#if !PF_TARGET_OS_OSX + // Innaccessible bundle identifiers should throw + XCTAssertThrows(configuration.applicationGroupIdentifier = @"someBundleIdentifier"); +#endif + + // Accessible bundle identifiers should not throw + _testHelper.swizzledGroupContainerDirectoryPath = YES; + XCTAssertNoThrow(configuration.applicationGroupIdentifier = @"someBundleIdentifier"); + + // In non-extension environment, setting containing identifier should throw. + XCTAssertThrows(configuration.containingApplicationBundleIdentifier = @"someContainer"); + + _testHelper.runningInExtensionEnvironment = YES; + + // In extension environment this should succeed. + XCTAssertNoThrow(configuration.containingApplicationBundleIdentifier = @"someContainer"); +} + +@end