diff --git a/CHANGELOG.md b/CHANGELOG.md index f94631e9..5f348c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# 8.0.0 +- Remove swizzling support for `application:didReceiveRemoteNotification:`. + Use `application:didReceiveRemoteNotification:fetchCompletionHandler:` instead. (#162) +- Remove `GULHeartbeatDateStorable`, `GULHeartbeatDateStorage`, + `GULHeartbeatDateStorageUserDefaults` APIs. (#164) +- Remove '+ [GULAppEnvironmentUtil isIOS7OrHigher]' API. (#165) +- Remove '+ [GULAppEnvironmentUtil hasSwiftRuntime]' API. (#166) +- Fix an issue where ObjC associated objects were sometimes set with a + non-const key, potentially resulting in undefined behavior. (#141) +- Add nullabilty annotations to public headers. (#169) +- Remove references to deprecated CTCarrier. (#106) +- [changed] **Breaking change**: Minimum supported versions have + updated for the following platforms: + - | Platform | GoogleUtilities 8.0| + | ------------- | ------------- | + | iOS | **12.0** | + | tvOS | **13.0** | + | macOS | **10.15** | + | watchOS | 7.0 | +- Remove dependency on `FBLPromises`. The following public API have + been removed: + - `- [NSURLSession gul_dataTaskPromiseWithRequest:]` + - `GULURLSessionDataResponse` + The following promise-based public API have been replaced with + completion handler-based alternatives. + - `- [GULKeychainStorage getObjectForKey:objectClass:accessGroup:]` + - `- [GULKeychainStorage setObject:forKey:accessGroup:]` + - `- [GULKeychainStorage removeObjectForKey:accessGroup:]` + # 7.13.3 - Rename parameter placeholder in `GULSecureCoding` unarchiving API to avoid conflict with keyword. (#152) diff --git a/GoogleUtilities.podspec b/GoogleUtilities.podspec index e06b8533..e0fdd2fe 100644 --- a/GoogleUtilities.podspec +++ b/GoogleUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleUtilities' - s.version = '7.13.3' + s.version = '8.0.0' s.summary = 'Google Utilities for Apple platform SDKs' s.description = <<-DESC @@ -17,10 +17,10 @@ other Google CocoaPods. They're not intended for direct public usage. :tag => 'CocoaPods-' + s.version.to_s } - ios_deployment_target = '9.0' - osx_deployment_target = '10.12' - tvos_deployment_target = '10.0' - watchos_deployment_target = '6.0' + ios_deployment_target = '12.0' + osx_deployment_target = '10.15' + tvos_deployment_target = '13.0' + watchos_deployment_target = '7.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target @@ -47,7 +47,6 @@ other Google CocoaPods. They're not intended for direct public usage. 'third_party/IsAppEncrypted/**/*.[mh]' ] es.public_header_files = 'GoogleUtilities/Environment/Public/GoogleUtilities/*.h' - es.dependency 'PromisesObjC', '>= 1.2', '< 3.0' es.dependency 'GoogleUtilities/Privacy' es.frameworks = [ 'Security' @@ -115,12 +114,6 @@ other Google CocoaPods. They're not intended for direct public usage. adss.dependency 'GoogleUtilities/Environment' end - s.subspec 'ISASwizzler' do |iss| - iss.source_files = 'GoogleUtilities/ISASwizzler/**/*.[mh]', 'GoogleUtilities/Common/*.h' - iss.public_header_files = 'GoogleUtilities/ISASwizzler/Public/GoogleUtilities/*.h' - iss.dependency 'GoogleUtilities/Privacy' - end - s.subspec 'MethodSwizzler' do |mss| mss.source_files = 'GoogleUtilities/MethodSwizzler/**/*.[mh]', 'GoogleUtilities/Common/*.h' mss.public_header_files = 'GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/*.h' diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index bda5ead7..d10cbfde 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -106,8 +106,6 @@ typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( @"application:didRegisterForRemoteNotificationsWithDeviceToken:"; static NSString *const kGULDidFailToRegisterForRemoteNotificationsSEL = @"application:didFailToRegisterForRemoteNotificationsWithError:"; -static NSString *const kGULDidReceiveRemoteNotificationSEL = - @"application:didReceiveRemoteNotification:"; static NSString *const kGULDidReceiveRemoteNotificationWithCompletionSEL = @"application:didReceiveRemoteNotification:fetchCompletionHandler:"; @@ -499,38 +497,18 @@ + (void)proxyRemoteNotificationsMethodsWithAppDelegateSubClass:(Class)appDelegat realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; - // For application:didReceiveRemoteNotification: - SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL); - SEL didReceiveRemoteNotificationDonorSEL = @selector(application: - donor_didReceiveRemoteNotification:); - - [self proxyDestinationSelector:didReceiveRemoteNotificationSEL - implementationsFromSourceSelector:didReceiveRemoteNotificationDonorSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass - realClass:realClass - storeDestinationImplementationTo:realImplementationsBySelector]; - // For application:didReceiveRemoteNotification:fetchCompletionHandler: #if !TARGET_OS_WATCH && !TARGET_OS_OSX SEL didReceiveRemoteNotificationWithCompletionSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL); SEL didReceiveRemoteNotificationWithCompletionDonorSEL = @selector(application:donor_didReceiveRemoteNotification:fetchCompletionHandler:); - if ([appDelegate respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) { - // Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if - // the original AppDelegate implements it. - // This fixes a bug if an app only implements application:didReceiveRemoteNotification: - // (if we add the method with completion, iOS sees that one exists and does not call - // the method without the completion, which in this case is the only one the app implements). - - [self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL - implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass - realClass:realClass - storeDestinationImplementationTo:realImplementationsBySelector]; - } + [self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL + implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass + realClass:realClass + storeDestinationImplementationTo:realImplementationsBySelector]; #endif // !TARGET_OS_WATCH && !TARGET_OS_OSX } @@ -954,35 +932,6 @@ - (void)application:(GULApplication *)application } #endif // !TARGET_OS_WATCH && !TARGET_OS_OSX -- (void)application:(GULApplication *)application - donor_didReceiveRemoteNotification:(NSDictionary *)userInfo { - SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL); - NSValue *didReceiveRemoteNotificationIMPPointer = - [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; - GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = - [didReceiveRemoteNotificationIMPPointer pointerValue]; - - // Notify interceptors. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [GULAppDelegateSwizzler - notifyInterceptorsWithMethodSelector:methodSelector - callback:^(id interceptor) { - NSInvocation *invocation = [GULAppDelegateSwizzler - appDelegateInvocationForSelector:methodSelector]; - [invocation setTarget:interceptor]; - [invocation setSelector:methodSelector]; - [invocation setArgument:(void *)(&application) atIndex:2]; - [invocation setArgument:(void *)(&userInfo) atIndex:3]; - [invocation invoke]; - }]; -#pragma clang diagnostic pop - // Call the real implementation if the real App Delegate has any. - if (didReceiveRemoteNotificationIMP) { - didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo); - } -} - + (nullable NSInvocation *)appDelegateInvocationForSelector:(SEL)selector { struct objc_method_description methodDescription = protocol_getMethodDescription(@protocol(GULApplicationDelegate), selector, NO, YES); diff --git a/GoogleUtilities/Environment/GULAppEnvironmentUtil.m b/GoogleUtilities/Environment/GULAppEnvironmentUtil.m index 0ad77c7f..fb37d9ed 100644 --- a/GoogleUtilities/Environment/GULAppEnvironmentUtil.m +++ b/GoogleUtilities/Environment/GULAppEnvironmentUtil.m @@ -218,24 +218,6 @@ + (BOOL)isAppExtension { #endif } -+ (BOOL)isIOS7OrHigher { - return YES; -} - -+ (BOOL)hasSwiftRuntime { - // The class - // [Swift._SwiftObject](https://github.com/apple/swift/blob/5eac3e2818eb340b11232aff83edfbd1c307fa03/stdlib/public/runtime/SwiftObject.h#L35) - // is a part of Swift runtime, so it should be present if Swift runtime is available. - - BOOL hasSwiftRuntime = - objc_lookUpClass("Swift._SwiftObject") != nil || - // Swift object class name before - // https://github.com/apple/swift/commit/9637b4a6e11ddca72f5f6dbe528efc7c92f14d01 - objc_getClass("_TtCs12_SwiftObject") != nil; - - return hasSwiftRuntime; -} - + (NSString *)applePlatform { NSString *applePlatform = @"unknown"; diff --git a/GoogleUtilities/Environment/GULHeartbeatDateStorage.m b/GoogleUtilities/Environment/GULHeartbeatDateStorage.m deleted file mode 100644 index f1d8ddcc..00000000 --- a/GoogleUtilities/Environment/GULHeartbeatDateStorage.m +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -NSString *const kGULHeartbeatStorageDirectory = @"Google/FIRApp"; - -@interface GULHeartbeatDateStorage () - -/** The name of the file that stores heartbeat information. */ -@property(nonatomic, readonly) NSString *fileName; -@end - -@implementation GULHeartbeatDateStorage - -@synthesize fileURL = _fileURL; - -- (instancetype)initWithFileName:(NSString *)fileName { - if (fileName == nil) return nil; - - self = [super init]; - if (self) { - _fileName = fileName; - } - return self; -} - -/** Lazy getter for fileURL. - * @return fileURL where heartbeat information is stored. - */ -- (NSURL *)fileURL { - if (!_fileURL) { - NSURL *directoryURL = [self directoryPathURL]; - [self checkAndCreateDirectory:directoryURL]; - _fileURL = [directoryURL URLByAppendingPathComponent:_fileName]; - } - return _fileURL; -} - -/** Returns the URL path of the directory for heartbeat storage data. - * @return the URL path of the directory for heartbeat storage data. - */ -- (NSURL *)directoryPathURL { - NSArray *paths; -#if TARGET_OS_TV - paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); -#else - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); -#endif // TARGET_OS_TV - NSString *rootPath = [paths lastObject]; - NSURL *rootURL = [NSURL fileURLWithPath:rootPath]; - NSURL *directoryURL = [rootURL URLByAppendingPathComponent:kGULHeartbeatStorageDirectory]; - return directoryURL; -} - -/** Check for the existence of the directory specified by the URL, and create it if it does not - * exist. - * @param directoryPathURL The path to the directory that needs to exist. - */ -- (void)checkAndCreateDirectory:(NSURL *)directoryPathURL { - NSError *error; - if (![directoryPathURL checkResourceIsReachableAndReturnError:&error]) { - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtURL:directoryPathURL - withIntermediateDirectories:YES - attributes:nil - error:&error]; - } -} - -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag { - @synchronized(self.class) { - NSDictionary *heartbeatDictionary = [self heartbeatDictionaryWithFileURL:self.fileURL]; - NSDate *heartbeatDate = heartbeatDictionary[tag]; - - // Validate the value type. If the storage file was corrupted or updated with a different format - // by a newer SDK version the value type may be different. - if (![heartbeatDate isKindOfClass:[NSDate class]]) { - heartbeatDate = nil; - } - - return heartbeatDate; - } -} - -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag { - // Synchronize on the class to ensure that the different instances of the class will not access - // the same file concurrently. - // TODO: Consider a different synchronization strategy here and in `-heartbeatDateForTag:` method. - // Currently no heartbeats can be read/written concurrently even if they are in different files. - @synchronized(self.class) { - NSMutableDictionary *heartbeatDictionary = - [[self heartbeatDictionaryWithFileURL:self.fileURL] mutableCopy]; - heartbeatDictionary[tag] = date; - NSError *error; - BOOL isSuccess = [self writeDictionary:[heartbeatDictionary copy] - forWritingURL:self.fileURL - error:&error]; - return isSuccess; - } -} - -- (NSDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL { - NSDictionary *heartbeatDictionary; - - NSError *error; - NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error]; - - if (objectData.length > 0 && error == nil) { - NSSet *objectClasses = - [NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class ]]; - heartbeatDictionary = [GULSecureCoding unarchivedObjectOfClasses:objectClasses - fromData:objectData - error:&error]; - } - - if (heartbeatDictionary.count == 0 || error != nil) { - heartbeatDictionary = [NSDictionary dictionary]; - } - - return heartbeatDictionary; -} - -- (BOOL)writeDictionary:(NSDictionary *)dictionary - forWritingURL:(NSURL *)writingFileURL - error:(NSError **)outError { - // Archive a mutable copy `dictionary` for writing to disk. This is done for - // backwards compatibility. See Google Utilities issue #36 for more context. - // TODO: Remove usage of mutable copy in a future version of Google Utilities. - NSData *data = [GULSecureCoding archivedDataWithRootObject:[dictionary mutableCopy] - error:outError]; - if (data.length == 0) { - return NO; - } - - return [data writeToURL:writingFileURL atomically:YES]; -} - -@end diff --git a/GoogleUtilities/Environment/GULHeartbeatDateStorageUserDefaults.m b/GoogleUtilities/Environment/GULHeartbeatDateStorageUserDefaults.m deleted file mode 100644 index f8f11591..00000000 --- a/GoogleUtilities/Environment/GULHeartbeatDateStorageUserDefaults.m +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h" - -@interface GULHeartbeatDateStorageUserDefaults () - -/** The storage to store the date of the last sent heartbeat. */ -@property(nonatomic, readonly) NSUserDefaults *userDefaults; - -/** The key for user defaults to store heartbeat information. */ -@property(nonatomic, readonly) NSString *key; - -@end - -@implementation GULHeartbeatDateStorageUserDefaults - -- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key { - self = [super init]; - if (self) { - _userDefaults = defaults; - _key = key; - } - return self; -} - -- (NSMutableDictionary *)heartbeatDictionaryFromDefaults { - NSDictionary *heartbeatDict = [self.userDefaults objectForKey:self.key]; - if (heartbeatDict != nil) { - return [heartbeatDict mutableCopy]; - } else { - return [NSMutableDictionary dictionary]; - } -} - -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag { - NSDate *date = nil; - @synchronized(self.userDefaults) { - NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults]; - date = dict[tag]; - } - - return date; -} - -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag { - @synchronized(self.userDefaults) { - NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults]; - dict[tag] = date; - [self.userDefaults setObject:dict forKey:self.key]; - } - return true; -} - -@end diff --git a/GoogleUtilities/Environment/GULSecureCoding.m b/GoogleUtilities/Environment/GULSecureCoding.m deleted file mode 100644 index a0668255..00000000 --- a/GoogleUtilities/Environment/GULSecureCoding.m +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -NSString *const kGULSecureCodingError = @"GULSecureCodingError"; - -@implementation GULSecureCoding - -+ (nullable id)unarchivedObjectOfClasses:(NSSet *)classes - fromData:(NSData *)data - error:(NSError **)outError { - id object; -#if __has_builtin(__builtin_available) - if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { - object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:outError]; - } else -#endif // __has_builtin(__builtin_available) - { - @try { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; -#pragma clang diagnostic pop - unarchiver.requiresSecureCoding = YES; - - object = [unarchiver decodeObjectOfClasses:classes forKey:NSKeyedArchiveRootObjectKey]; - } @catch (NSException *exception) { - if (outError) { - *outError = [self archivingErrorWithException:exception]; - } - } - - if (object == nil && outError && *outError == nil) { - NSString *failureReason = @"NSKeyedUnarchiver failed to unarchive data."; - *outError = [NSError errorWithDomain:kGULSecureCodingError - code:-1 - userInfo:@{NSLocalizedFailureReasonErrorKey : failureReason}]; - } - } - - return object; -} - -+ (nullable id)unarchivedObjectOfClass:(Class)aClass - fromData:(NSData *)data - error:(NSError **)outError { - return [self unarchivedObjectOfClasses:[NSSet setWithObject:aClass] fromData:data error:outError]; -} - -+ (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError { - NSData *archiveData; -#if __has_builtin(__builtin_available) - if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { - archiveData = [NSKeyedArchiver archivedDataWithRootObject:object - requiringSecureCoding:YES - error:outError]; - } else -#endif // __has_builtin(__builtin_available) - { - @try { - NSMutableData *data = [NSMutableData data]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; -#pragma clang diagnostic pop - archiver.requiresSecureCoding = YES; - - [archiver encodeObject:object forKey:NSKeyedArchiveRootObjectKey]; - [archiver finishEncoding]; - - archiveData = [data copy]; - } @catch (NSException *exception) { - if (outError) { - *outError = [self archivingErrorWithException:exception]; - } - } - } - - return archiveData; -} - -+ (NSError *)archivingErrorWithException:(NSException *)exception { - NSString *failureReason = [NSString - stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@", - exception.name, exception.reason, exception.userInfo]; - NSDictionary *errorUserInfo = @{NSLocalizedFailureReasonErrorKey : failureReason}; - - return [NSError errorWithDomain:kGULSecureCodingError code:-1 userInfo:errorUserInfo]; -} - -@end diff --git a/GoogleUtilities/Environment/NetworkInfo/GULNetworkInfo.m b/GoogleUtilities/Environment/NetworkInfo/GULNetworkInfo.m index 622ae4a8..fa3b878d 100644 --- a/GoogleUtilities/Environment/NetworkInfo/GULNetworkInfo.m +++ b/GoogleUtilities/Environment/NetworkInfo/GULNetworkInfo.m @@ -20,7 +20,6 @@ #if __has_include("CoreTelephony/CTTelephonyNetworkInfo.h") && !TARGET_OS_MACCATALYST && \ !TARGET_OS_OSX && !TARGET_OS_TV && !TARGET_OS_WATCH #define TARGET_HAS_MOBILE_CONNECTIVITY -#import #import #import #endif @@ -38,61 +37,6 @@ + (CTTelephonyNetworkInfo *)getNetworkInfo { } #endif -+ (NSString *_Nullable)getNetworkMobileCountryCode { -#ifdef TARGET_HAS_MOBILE_CONNECTIVITY - CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CTCarrier *provider = networkInfo.subscriberCellularProvider; -#pragma clang diagnostic push - return provider.mobileCountryCode; -#endif - return nil; -} - -+ (NSString *_Nullable)getNetworkMobileNetworkCode { -#ifdef TARGET_HAS_MOBILE_CONNECTIVITY - CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CTCarrier *provider = networkInfo.subscriberCellularProvider; -#pragma clang diagnostic push - return provider.mobileNetworkCode; -#endif - return nil; -} - -/** - * Returns the formatted MccMnc if the inputs are valid, otherwise nil - * @param mcc The Mobile Country Code returned from `getNetworkMobileCountryCode` - * @param mnc The Mobile Network Code returned from `getNetworkMobileNetworkCode` - * @returns A string with the concatenated mccMnc if both inputs are valid, otherwise nil - */ -+ (NSString *_Nullable)formatMcc:(NSString *)mcc andMNC:(NSString *)mnc { - // These are both nil if the target does not support mobile connectivity - if (mcc == nil && mnc == nil) { - return nil; - } - - if (mcc.length != 3 || mnc.length < 2 || mnc.length > 3) { - return nil; - } - - // If the resulting appended mcc + mnc contains characters that are not - // decimal digits, return nil - static NSCharacterSet *notDigits; - static dispatch_once_t token; - dispatch_once(&token, ^{ - notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - }); - NSString *mccMnc = [mcc stringByAppendingString:mnc]; - if ([mccMnc rangeOfCharacterFromSet:notDigits].location != NSNotFound) { - return nil; - } - - return mccMnc; -} - + (GULNetworkType)getNetworkType { GULNetworkType networkType = GULNetworkTypeNone; @@ -126,13 +70,11 @@ + (GULNetworkType)getNetworkType { + (NSString *)getNetworkRadioType { #ifdef TARGET_HAS_MOBILE_CONNECTIVITY CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return networkInfo.currentRadioAccessTechnology; -#pragma clang diagnostic pop -#else - return @""; + if (networkInfo.serviceCurrentRadioAccessTechnology.count) { + return networkInfo.serviceCurrentRadioAccessTechnology.allValues[0] ?: @""; + } #endif + return @""; } @end diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h index e84ab9e6..dbce3631 100644 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h +++ b/GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h @@ -46,13 +46,6 @@ NS_ASSUME_NONNULL_BEGIN /// Indicates whether it is running inside an extension or an app. + (BOOL)isAppExtension; -/// @return Returns @YES when is run on iOS version greater or equal to 7.0 -+ (BOOL)isIOS7OrHigher DEPRECATED_MSG_ATTRIBUTE( - "Always `YES` because only iOS 8 and higher supported. The method will be removed."); - -/// @return YES if Swift runtime detected in the app. -+ (BOOL)hasSwiftRuntime __deprecated; - /// @return An Apple platform. Possible values "ios", "tvos", "macos", "watchos", "maccatalyst", and /// "visionos". + (NSString *)applePlatform; diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h deleted file mode 100644 index 43d3740a..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Describes an object that can store and fetch heartbeat dates for given tags. - */ -@protocol GULHeartbeatDateStorable - -/** - * Reads the date from the specified file for the given tag. - * @return Returns date if exists, otherwise `nil`. - */ -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; - -/** - * Saves the date for the specified tag in the specified file. - * @return YES on success, NO otherwise. - */ -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h deleted file mode 100644 index 245b1a25..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "GULHeartbeatDateStorable.h" - -NS_ASSUME_NONNULL_BEGIN - -/// The name of the directory where the heartbeat data is stored. -extern NSString *const kGULHeartbeatStorageDirectory; - -/// Stores either a date or a dictionary to a specified file. -@interface GULHeartbeatDateStorage : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -@property(nonatomic, readonly) NSURL *fileURL; - -/** - * Default initializer. - * @param fileName The name of the file to store the date information. - * exist, it will be created if needed. - */ -- (instancetype)initWithFileName:(NSString *)fileName; - -/** - * Reads the date from the specified file for the given tag. - * @return Returns date if exists, otherwise `nil`. - */ -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; - -/** - * Saves the date for the specified tag in the specified file. - * @return YES on success, NO otherwise. - */ -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h deleted file mode 100644 index e6c7dda7..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "GULHeartbeatDateStorable.h" - -NS_ASSUME_NONNULL_BEGIN - -/// Stores either a date or a dictionary to a specified file. -@interface GULHeartbeatDateStorageUserDefaults : NSObject - -/** - * Default initializer. tvOS can only write to the cache directory and - * there are no guarantees that the directory will persist. User defaults will - * be retained, so that should be used instead. - * @param defaults User defaults instance to store the heartbeat information. - * @param key The key to be used with the user defaults instance. - */ -- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * Reads the date from the specified file for the given tag. - * @return Returns date if exists, otherwise `nil`. - */ -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; - -/** - * Saves the date for the specified tag in the specified file. - * @return YES on success, NO otherwise. - */ -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h index af10cb4d..eb90ea34 100644 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h +++ b/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h @@ -16,8 +16,6 @@ #import -@class FBLPromise; - NS_ASSUME_NONNULL_BEGIN /// The class provides a convenient, multiplatform abstraction of the Keychain. @@ -34,42 +32,46 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithService:(NSString *)service; -/** - * Get an object by key. - * @param key The key. - * @param objectClass The expected object class required by `NSSecureCoding`. - * @param accessGroup The Keychain Access Group. - * - * @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved - * with `nil` when the object not found. It fails on a Keychain error. - */ -- (FBLPromise> *)getObjectForKey:(NSString *)key - objectClass:(Class)objectClass - accessGroup:(nullable NSString *)accessGroup; +/// Get an object by key. +/// @param key The key. +/// @param objectClass The expected object class required by `NSSecureCoding`. +/// @param accessGroup The Keychain Access Group. +/// @param completionHandler The completion handler to call when the +/// synchronized keychain read is complete. An error is passed to the +/// completion handler if the keychain read fails. Else, the object stored in +/// the keychain, or `nil` if it does not exist, is passed to the completion +/// handler. +- (void)getObjectForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup + completionHandler: + (void (^)(id _Nullable obj, NSError *_Nullable error))completionHandler; -/** - * Saves the given object by the given key. - * @param object The object to store. - * @param key The key to store the object. If there is an existing object by the key, it will be - * overridden. - * @param accessGroup The Keychain Access Group. - * - * @return Returns which is resolved with `[NSNull null]` on success. - */ -- (FBLPromise *)setObject:(id)object - forKey:(NSString *)key - accessGroup:(nullable NSString *)accessGroup; +/// Saves the given object by the given key. +/// @param object The object to store. +/// @param key The key to store the object. If there is an existing object by the key, it will be +/// overridden. +/// @param accessGroup The Keychain Access Group. +/// @param completionHandler The completion handler to call when the +/// synchronized keychain write is complete. An error is passed to the +/// completion handler if the keychain read fails. Else, the object written to +/// the keychain is passed to the completion handler. +- (void)setObject:(id)object + forKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup + completionHandler: + (void (^)(id _Nullable obj, NSError *_Nullable error))completionHandler; -/** - * Removes the object by the given key. - * @param key The key to store the object. If there is an existing object by the key, it will be - * overridden. - * @param accessGroup The Keychain Access Group. - * - * @return Returns which is resolved with `[NSNull null]` on success. - */ -- (FBLPromise *)removeObjectForKey:(NSString *)key - accessGroup:(nullable NSString *)accessGroup; +/// Removes the object by the given key. +/// @param key The key to store the object. If there is an existing object by +/// the key, it will be overridden. +/// @param accessGroup The Keychain Access Group. +/// @param completionHandler The completion handler to call when the +/// synchronized keychain removal is complete. An error is passed to the +/// completion handler if the keychain removal fails. +- (void)removeObjectForKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup + completionHandler:(void (^)(NSError *_Nullable error))completionHandler; #if TARGET_OS_OSX /// If not `nil`, then only this keychain will be used to save and read data (see diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULNetworkInfo.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULNetworkInfo.h index d3025cd9..06139413 100644 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULNetworkInfo.h +++ b/GoogleUtilities/Environment/Public/GoogleUtilities/GULNetworkInfo.h @@ -27,20 +27,6 @@ typedef NS_ENUM(NSInteger, GULNetworkType) { /// Collection of utilities to read network status information @interface GULNetworkInfo : NSObject -/// Returns the cellular mobile country code (mcc) if CoreTelephony is supported, otherwise nil -+ (NSString *_Nullable)getNetworkMobileCountryCode; - -/// Returns the cellular mobile network code (mnc) if CoreTelephony is supported, otherwise nil -+ (NSString *_Nullable)getNetworkMobileNetworkCode; - -/** - * Returns the formatted MccMnc if the inputs are valid, otherwise nil - * @param mcc The Mobile Country Code returned from `getNetworkMobileCountryCode` - * @param mnc The Mobile Network Code returned from `getNetworkMobileNetworkCode` - * @returns A string with the concatenated mccMnc if both inputs are valid, otherwise nil - */ -+ (NSString *_Nullable)formatMcc:(NSString *_Nullable)mcc andMNC:(NSString *_Nullable)mnc; - /// Returns an enum indicating the network type. The enum values should be easily transferrable to /// the NetworkType value in android/play/playlog/proto/clientanalytics.proto. Right now this always /// returns None on platforms other than iOS. This should be updated in the future to return Wi-Fi diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h deleted file mode 100644 index e9acc8eb..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** The class wraps `NSKeyedArchiver` and `NSKeyedUnarchiver` API to provide a unified secure coding - * methods for iOS versions before and after 11. - */ -@interface GULSecureCoding : NSObject - -+ (nullable id)unarchivedObjectOfClasses:(NSSet *)classes - fromData:(NSData *)data - error:(NSError **)outError; - -+ (nullable id)unarchivedObjectOfClass:(Class)aClass - fromData:(NSData *)data - error:(NSError **)outError; - -+ (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h b/GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h deleted file mode 100644 index e88eb67b..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** The class represents HTTP response received from `NSURLSession`. */ -@interface GULURLSessionDataResponse : NSObject - -@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; -@property(nonatomic, nullable, readonly) NSData *HTTPBody; - -- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h b/GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h deleted file mode 100644 index 7bed005e..00000000 --- a/GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class GULURLSessionDataResponse; - -NS_ASSUME_NONNULL_BEGIN - -/** Promise based API for `NSURLSession`. */ -@interface NSURLSession (GULPromises) - -/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method. - * @param URLRequest The request to create a data task with. - * @return A promise that is fulfilled when an HTTP response is received (with any response code), - * or is rejected with the error passed to the task completion. - */ -- (FBLPromise *)gul_dataTaskPromiseWithRequest: - (NSURLRequest *)URLRequest; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m b/GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m index 022c1bf7..e6aa69a7 100644 --- a/GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m +++ b/GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m @@ -17,14 +17,7 @@ #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h" #import -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" @interface GULKeychainStorage () @property(nonatomic, readonly) dispatch_queue_t keychainQueue; @@ -57,108 +50,113 @@ - (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache { #pragma mark - Public -- (FBLPromise> *)getObjectForKey:(NSString *)key - objectClass:(Class)objectClass - accessGroup:(nullable NSString *)accessGroup { - return [FBLPromise onQueue:self.inMemoryCacheQueue - do:^id _Nullable { - // Return cached object or fail otherwise. - id object = [self.inMemoryCache objectForKey:key]; - return object - ?: [[NSError alloc] - initWithDomain:FBLPromiseErrorDomain - code:FBLPromiseErrorCodeValidationFailure - userInfo:nil]; - }] - .recover(^id _Nullable(NSError *error) { - // Look for the object in the keychain. - return [self getObjectFromKeychainForKey:key - objectClass:objectClass - accessGroup:accessGroup]; - }); +- (void)getObjectForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup + completionHandler: + (void (^)(id _Nullable obj, NSError *_Nullable error))completionHandler { + dispatch_async(self.inMemoryCacheQueue, ^{ + // Return cached object or fail otherwise. + id object = [self.inMemoryCache objectForKey:key]; + if (object) { + completionHandler(object, nil); + } else { + // Look for the object in the keychain. + [self getObjectFromKeychainForKey:key + objectClass:objectClass + accessGroup:accessGroup + completionHandler:completionHandler]; + } + }); } -- (FBLPromise *)setObject:(id)object - forKey:(NSString *)key - accessGroup:(nullable NSString *)accessGroup { - return [FBLPromise onQueue:self.inMemoryCacheQueue - do:^id _Nullable { - // Save to the in-memory cache first. - [self.inMemoryCache setObject:object forKey:[key copy]]; - return [NSNull null]; - }] - .thenOn(self.keychainQueue, ^id(id result) { - // Then store the object to the keychain. - NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; - NSError *error; - NSData *encodedObject = [GULSecureCoding archivedDataWithRootObject:object error:&error]; - if (!encodedObject) { - return error; - } - - if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) { - return error; - } - - return [NSNull null]; - }); +- (void)setObject:(id)object + forKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup + completionHandler: + (void (^)(id _Nullable obj, NSError *_Nullable error))completionHandler { + dispatch_async(self.inMemoryCacheQueue, ^{ + // Save to the in-memory cache first. + [self.inMemoryCache setObject:object forKey:[key copy]]; + + dispatch_async(self.keychainQueue, ^{ + // Then store the object to the keychain. + NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; + NSError *error; + NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object + requiringSecureCoding:YES + error:&error]; + if (!encodedObject) { + completionHandler(nil, error); + return; + } + + if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) { + completionHandler(nil, error); + return; + } + + completionHandler(object, nil); + }); + }); } -- (FBLPromise *)removeObjectForKey:(NSString *)key - accessGroup:(nullable NSString *)accessGroup { - return [FBLPromise onQueue:self.inMemoryCacheQueue - do:^id _Nullable { - [self.inMemoryCache removeObjectForKey:key]; - return nil; - }] - .thenOn(self.keychainQueue, ^id(id result) { - NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; - - NSError *error; - if (![GULKeychainUtils removeItemWithQuery:query error:&error]) { - return error; - } - - return [NSNull null]; - }); +- (void)removeObjectForKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup + completionHandler:(void (^)(NSError *_Nullable error))completionHandler { + dispatch_async(self.inMemoryCacheQueue, ^{ + [self.inMemoryCache removeObjectForKey:key]; + dispatch_async(self.keychainQueue, ^{ + NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; + + NSError *error; + if (![GULKeychainUtils removeItemWithQuery:query error:&error]) { + completionHandler(error); + } else { + completionHandler(nil); + } + }); + }); } #pragma mark - Private -- (FBLPromise> *)getObjectFromKeychainForKey:(NSString *)key - objectClass:(Class)objectClass - accessGroup:(nullable NSString *)accessGroup { +- (void)getObjectFromKeychainForKey:(NSString *)key + objectClass:(Class)objectClass + accessGroup:(nullable NSString *)accessGroup + completionHandler:(void (^)(id _Nullable obj, + NSError *_Nullable error))completionHandler { // Look for the object in the keychain. - return [FBLPromise - onQueue:self.keychainQueue - do:^id { - NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; - NSError *error; - NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error]; - - if (error) { - return error; - } - if (!encodedObject) { - return nil; - } - id object = [GULSecureCoding unarchivedObjectOfClass:objectClass - fromData:encodedObject - error:&error]; - if (error) { - return error; - } - - return object; - }] - .thenOn(self.inMemoryCacheQueue, - ^id _Nullable(id _Nullable object) { - // Save object to the in-memory cache if exists and return the object. - if (object) { - [self.inMemoryCache setObject:object forKey:[key copy]]; - } - return object; - }); + dispatch_async(self.keychainQueue, ^{ + NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup]; + NSError *error; + NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error]; + + if (error) { + completionHandler(nil, error); + return; + } + if (!encodedObject) { + completionHandler(nil, nil); + return; + } + id object = [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass + fromData:encodedObject + error:&error]; + if (error) { + completionHandler(nil, error); + return; + } + + dispatch_async(self.inMemoryCacheQueue, ^{ + // Save object to the in-memory cache if exists and return the object. + if (object) { + [self.inMemoryCache setObject:object forKey:[key copy]]; + } + + completionHandler(object, nil); + }); + }); } - (void)resetInMemoryCache { diff --git a/GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m b/GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m deleted file mode 100644 index 559875a7..00000000 --- a/GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h" - -@implementation GULURLSessionDataResponse - -- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body { - self = [super init]; - if (self) { - _HTTPResponse = response; - _HTTPBody = body; - } - return self; -} - -@end diff --git a/GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m b/GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m deleted file mode 100644 index 6c70310f..00000000 --- a/GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h" - -@implementation NSURLSession (GULPromises) - -- (FBLPromise *)gul_dataTaskPromiseWithRequest: - (NSURLRequest *)URLRequest { - return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { - [[self dataTaskWithRequest:URLRequest - completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - reject(error); - } else { - fulfill([[GULURLSessionDataResponse alloc] - initWithResponse:(NSHTTPURLResponse *)response - HTTPBody:data]); - } - }] resume]; - }]; -} - -@end diff --git a/GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h b/GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h deleted file mode 100644 index 26d86557..00000000 --- a/GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h" - -FOUNDATION_EXPORT NSString *kGULSwizzlerAssociatedObjectKey; - -@interface GULObjectSwizzler (Internal) - -- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass; - -@end diff --git a/GoogleUtilities/ISASwizzler/GULObjectSwizzler.m b/GoogleUtilities/ISASwizzler/GULObjectSwizzler.m deleted file mode 100644 index 7d786b79..00000000 --- a/GoogleUtilities/ISASwizzler/GULObjectSwizzler.m +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h" - -#import - -#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h" -#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h" - -@implementation GULObjectSwizzler { - // The swizzled object. - __weak id _swizzledObject; - - // The original class of the object. - Class _originalClass; - - // The dynamically generated subclass of _originalClass. - Class _generatedClass; -} - -#pragma mark - Class methods - -+ (void)setAssociatedObject:(id)object - key:(NSString *)key - value:(nullable id)value - association:(GUL_ASSOCIATION)association { - objc_AssociationPolicy resolvedAssociation; - switch (association) { - case GUL_ASSOCIATION_ASSIGN: - resolvedAssociation = OBJC_ASSOCIATION_ASSIGN; - break; - - case GUL_ASSOCIATION_RETAIN_NONATOMIC: - resolvedAssociation = OBJC_ASSOCIATION_RETAIN_NONATOMIC; - break; - - case GUL_ASSOCIATION_COPY_NONATOMIC: - resolvedAssociation = OBJC_ASSOCIATION_COPY_NONATOMIC; - break; - - case GUL_ASSOCIATION_RETAIN: - resolvedAssociation = OBJC_ASSOCIATION_RETAIN; - break; - - case GUL_ASSOCIATION_COPY: - resolvedAssociation = OBJC_ASSOCIATION_COPY; - break; - - default: - break; - } - objc_setAssociatedObject(object, key.UTF8String, value, resolvedAssociation); -} - -+ (nullable id)getAssociatedObject:(id)object key:(NSString *)key { - return objc_getAssociatedObject(object, key.UTF8String); -} - -#pragma mark - Instance methods - -/** Instantiates an instance of this class. - * - * @param object The object to swizzle. - * @return An instance of this class. - */ -- (instancetype)initWithObject:(id)object { - if (object == nil) { - return nil; - } - - GULObjectSwizzler *existingSwizzler = - [[self class] getAssociatedObject:object key:kGULSwizzlerAssociatedObjectKey]; - if ([existingSwizzler isKindOfClass:[GULObjectSwizzler class]]) { - // The object has been swizzled already, no need to swizzle again. - return existingSwizzler; - } - - self = [super init]; - if (self) { - _swizzledObject = object; - _originalClass = object_getClass(object); - NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString], - NSStringFromClass(_originalClass)]; - _generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0); - NSAssert(_generatedClass, @"Wasn't able to allocate the class pair."); - } - return self; -} - -- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector { - NSAssert(_generatedClass, @"This object has already been unswizzled."); - Method method = isClassSelector ? class_getClassMethod(aClass, selector) - : class_getInstanceMethod(aClass, selector); - Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass; - IMP implementation = method_getImplementation(method); - - const char *typeEncoding = method_getTypeEncoding(method); - class_replaceMethod(targetClass, selector, implementation, typeEncoding); -} - -- (void)setAssociatedObjectWithKey:(NSString *)key - value:(id)value - association:(GUL_ASSOCIATION)association { - __strong id swizzledObject = _swizzledObject; - if (swizzledObject) { - [[self class] setAssociatedObject:swizzledObject key:key value:value association:association]; - } -} - -- (nullable id)getAssociatedObjectForKey:(NSString *)key { - __strong id swizzledObject = _swizzledObject; - if (swizzledObject) { - return [[self class] getAssociatedObject:swizzledObject key:key]; - } - return nil; -} - -- (void)swizzle { - __strong id swizzledObject = _swizzledObject; - - GULObjectSwizzler *existingSwizzler = - [[self class] getAssociatedObject:swizzledObject key:kGULSwizzlerAssociatedObjectKey]; - if (existingSwizzler != nil) { - NSAssert(existingSwizzler == self, @"The swizzled object has a different swizzler."); - // The object has been swizzled already. - return; - } - - if (swizzledObject) { - [GULObjectSwizzler setAssociatedObject:swizzledObject - key:kGULSwizzlerAssociatedObjectKey - value:self - association:GUL_ASSOCIATION_RETAIN]; - - [GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self]; - - NSAssert(_originalClass == object_getClass(swizzledObject), - @"The original class is not the reported class now."); - NSAssert(class_getInstanceSize(_originalClass) == class_getInstanceSize(_generatedClass), - @"The instance size of the generated class must be equal to the original class."); - objc_registerClassPair(_generatedClass); - Class doubleCheckOriginalClass __unused = object_setClass(_swizzledObject, _generatedClass); - NSAssert(_originalClass == doubleCheckOriginalClass, - @"The original class must be the same as the class returned by object_setClass"); - } else { - NSAssert(NO, @"You can't swizzle a nil object"); - } -} - -- (void)dealloc { - // When the Zombies instrument is enabled, a zombie is created for the swizzled object upon - // deallocation. Because this zombie subclasses the generated class, the swizzler should not - // dispose it during the swizzler's deallocation. - // - // There are other special cases where the generated class might be subclassed by a third-party - // generated classes, for example: https://github.com/firebase/firebase-ios-sdk/issues/9083 - // To avoid errors in such cases, the environment variable `GULGeneratedClassDisposeDisabled` can - // be set with `YES`. - NSDictionary *environment = [[NSProcessInfo processInfo] environment]; - if ([[environment objectForKey:@"NSZombieEnabled"] boolValue] || - [[environment objectForKey:@"GULGeneratedClassDisposeDisabled"] boolValue]) { - return; - } - - if (_generatedClass) { - if (_swizzledObject == nil) { - // The swizzled object has been deallocated already, so the generated class can be disposed - // now. - objc_disposeClassPair(_generatedClass); - return; - } - - // GULSwizzledObject is retained by the swizzled object which means that the swizzled object is - // being deallocated now. Let's see if we should schedule the generated class disposal. - - // If the swizzled object has a different class, it most likely indicates that the object was - // ISA swizzled one more time. In this case it is not safe to dispose the generated class. We - // will have to keep it to prevent a crash. - - // TODO: Consider adding a flag that can be set by the host application to dispose the class - // pair unconditionally. It may be used by apps that use ISA Swizzling themself and are - // confident in disposing their subclasses. - BOOL isSwizzledObjectInstanceOfGeneratedClass = - object_getClass(_swizzledObject) == _generatedClass; - - if (isSwizzledObjectInstanceOfGeneratedClass) { - Class generatedClass = _generatedClass; - - // Schedule the generated class disposal after the swizzled object has been deallocated. - dispatch_async(dispatch_get_main_queue(), ^{ - objc_disposeClassPair(generatedClass); - }); - } - } -} - -- (BOOL)isSwizzlingProxyObject { - return [_swizzledObject isProxy]; -} - -@end diff --git a/GoogleUtilities/ISASwizzler/GULSwizzledObject.m b/GoogleUtilities/ISASwizzler/GULSwizzledObject.m deleted file mode 100644 index bf3c7076..00000000 --- a/GoogleUtilities/ISASwizzler/GULSwizzledObject.m +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h" -#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h" - -NSString *kGULSwizzlerAssociatedObjectKey = @"gul_objectSwizzler"; - -@interface GULSwizzledObject () - -@end - -@implementation GULSwizzledObject - -+ (void)copyDonorSelectorsUsingObjectSwizzler:(GULObjectSwizzler *)objectSwizzler { - [objectSwizzler copySelector:@selector(gul_objectSwizzler) fromClass:self isClassSelector:NO]; - [objectSwizzler copySelector:@selector(gul_class) fromClass:self isClassSelector:NO]; - - // This is needed because NSProxy objects usually override -[NSObjectProtocol respondsToSelector:] - // and ask this question to the underlying object. Since we don't swizzle the underlying object - // but swizzle the proxy, when someone calls -[NSObjectProtocol respondsToSelector:] on the proxy, - // the answer ends up being NO even if we added new methods to the subclass through ISA Swizzling. - // To solve that, we override -[NSObjectProtocol respondsToSelector:] in such a way that takes - // into account the fact that we've added new methods. - if ([objectSwizzler isSwizzlingProxyObject]) { - [objectSwizzler copySelector:@selector(respondsToSelector:) fromClass:self isClassSelector:NO]; - } -} - -- (instancetype)init { - NSAssert(NO, @"Do not instantiate this class, it's only a donor class"); - return nil; -} - -- (GULObjectSwizzler *)gul_objectSwizzler { - return [GULObjectSwizzler getAssociatedObject:self key:kGULSwizzlerAssociatedObjectKey]; -} - -#pragma mark - Donor methods - -- (Class)gul_class { - return [[self gul_objectSwizzler] generatedClass]; -} - -// Only added to a class when we detect it is a proxy. -- (BOOL)respondsToSelector:(SEL)aSelector { - Class gulClass = [[self gul_objectSwizzler] generatedClass]; - return [gulClass instancesRespondToSelector:aSelector] || [super respondsToSelector:aSelector]; -} - -@end diff --git a/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h b/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h deleted file mode 100644 index b0a692a3..00000000 --- a/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** Enums that map to their OBJC-prefixed counterparts. */ -typedef OBJC_ENUM(uintptr_t, GUL_ASSOCIATION){ - - // Is a weak association. - GUL_ASSOCIATION_ASSIGN, - - // Is a nonatomic strong association. - GUL_ASSOCIATION_RETAIN_NONATOMIC, - - // Is a nonatomic copy association. - GUL_ASSOCIATION_COPY_NONATOMIC, - - // Is an atomic strong association. - GUL_ASSOCIATION_RETAIN, - - // Is an atomic copy association. - GUL_ASSOCIATION_COPY}; - -/** This class handles swizzling a specific instance of a class by generating a - * dynamic subclass and installing selectors and properties onto the dynamic - * subclass. Then, the instance's class is set to the dynamic subclass. There - * should be a 1:1 ratio of object swizzlers to swizzled instances. - */ -@interface GULObjectSwizzler : NSObject - -/** The subclass that is generated. */ -@property(nullable, nonatomic, readonly) Class generatedClass; - -/** Sets an associated object in the runtime. This mechanism can be used to - * simulate adding properties. - * - * @param object The object that will be queried for the associated object. - * @param key The key of the associated object. - * @param value The value to associate to the swizzled object. - * @param association The mechanism to use when associating the objects. - */ -+ (void)setAssociatedObject:(id)object - key:(NSString *)key - value:(nullable id)value - association:(GUL_ASSOCIATION)association; - -/** Gets an associated object in the runtime. This mechanism can be used to - * simulate adding properties. - * - * @param object The object that will be queried for the associated object. - * @param key The key of the associated object. - */ -+ (nullable id)getAssociatedObject:(id)object key:(NSString *)key; - -/** Please use the designated initializer. */ -- (instancetype)init NS_UNAVAILABLE; - -/** Instantiates an object swizzler using an object it will operate on. - * Generates a new class pair. - * - * @note There is no need to store this object. After calling -swizzle, this - * object can be found by calling -gul_objectSwizzler - * - * @param object The object to be swizzled. - * @return An instance of this class. - */ -- (instancetype)initWithObject:(id)object NS_DESIGNATED_INITIALIZER; - -/** Sets an associated object in the runtime. This mechanism can be used to - * simulate adding properties. - * - * @param key The key of the associated object. - * @param value The value to associate to the swizzled object. - * @param association The mechanism to use when associating the objects. - */ -- (void)setAssociatedObjectWithKey:(NSString *)key - value:(id)value - association:(GUL_ASSOCIATION)association; - -/** Gets an associated object in the runtime. This mechanism can be used to - * simulate adding properties. - * - * @param key The key of the associated object. - */ -- (nullable id)getAssociatedObjectForKey:(NSString *)key; - -/** Copies a selector from an existing class onto the generated dynamic subclass - * that this object will adopt. This mechanism can be used to add methods to - * specific instances of a class. - * - * @note Should not be called after calling -swizzle. - * @param selector The selector to add to the instance. - * @param aClass The class supplying an implementation of the method. - * @param isClassSelector A BOOL specifying whether the selector is a class or - * instance selector. - */ -- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector; - -/** Swizzles the object, changing its class to the generated class. Registers - * the class pair. */ -- (void)swizzle; - -/** @return The value of -[objectBeingSwizzled isProxy] */ -- (BOOL)isSwizzlingProxyObject; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h b/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h deleted file mode 100644 index fc07f0a2..00000000 --- a/GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class GULObjectSwizzler; - -/** This class exists as a method donor. These methods will be added to all objects that are - * swizzled by the object swizzler. This class should not be instantiated. - */ -@interface GULSwizzledObject : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/** Copies the methods below to the swizzled object. - * - * @param objectSwizzler The swizzler to use when adding the methods below. - */ -+ (void)copyDonorSelectorsUsingObjectSwizzler:(GULObjectSwizzler *)objectSwizzler; - -#pragma mark - Donor methods. - -/** @return The generated subclass. Used in respondsToSelector: calls. */ -- (Class)gul_class; - -/** @return The object swizzler that manages this object. */ -- (GULObjectSwizzler *)gul_objectSwizzler; - -@end diff --git a/GoogleUtilities/ISASwizzler/Resources/PrivacyInfo.xcprivacy b/GoogleUtilities/ISASwizzler/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index c89c88f6..00000000 --- a/GoogleUtilities/ISASwizzler/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,18 +0,0 @@ - - - - - NSPrivacyTracking - - NSPrivacyTrackingDomains - - - NSPrivacyCollectedDataTypes - - - NSPrivacyAccessedAPITypes - - - - - diff --git a/GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h b/GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h index f0ee435b..f7b28e59 100644 --- a/GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h +++ b/GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h @@ -16,6 +16,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + /** * The log levels used by internal logging. */ @@ -35,3 +37,5 @@ typedef NS_ENUM(NSInteger, GULLoggerLevel) { /** Maximum log level. */ GULLoggerLevelMax = GULLoggerLevelDebug } NS_SWIFT_NAME(GoogleLoggerLevel); + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h b/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h index a33262af..c340f85c 100644 --- a/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h +++ b/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h @@ -14,6 +14,10 @@ * limitations under the License. */ +#import + +NS_ASSUME_NONNULL_BEGIN + /** * GULOriginalIMPConvenienceMacros.h * @@ -205,3 +209,5 @@ __typeof__(__arg7), __typeof__(__arg8), __typeof__(__arg9)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \ __arg8, __arg9) + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/NSData+zlib/GULNSData+zlib.m b/GoogleUtilities/NSData+zlib/GULNSData+zlib.m index e441e36b..cec621e5 100644 --- a/GoogleUtilities/NSData+zlib/GULNSData+zlib.m +++ b/GoogleUtilities/NSData+zlib/GULNSData+zlib.m @@ -25,7 +25,7 @@ @implementation NSData (GULGzip) -+ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error { ++ (nullable NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error { const void *bytes = [data bytes]; NSUInteger length = [data length]; if (!bytes || !length) { @@ -119,7 +119,7 @@ + (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)erro return result; } -+ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error { ++ (nullable NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error { const void *bytes = [data bytes]; NSUInteger length = [data length]; diff --git a/GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h b/GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h index 36f94a70..f195d572 100644 --- a/GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h +++ b/GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h @@ -14,6 +14,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + /// This is a copy of Google Toolbox for Mac library to avoid creating an extra framework. // NOTE: For 64bit, none of these apis handle input sizes >32bits, they will return nil when given @@ -24,11 +26,11 @@ /// Returns an data as the result of decompressing the payload of |data|.The data to decompress must /// be a gzipped payloads. -+ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error; ++ (nullable NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error; /// Returns an compressed data with the result of gzipping the payload of |data|. Uses the default /// compression level. -+ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error; ++ (nullable NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber @@ -47,3 +49,5 @@ typedef NS_ENUM(NSInteger, GULNSDataZlibError) { }; @end + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Network/GULNetwork.m b/GoogleUtilities/Network/GULNetwork.m index 327a3a06..4f66260f 100644 --- a/GoogleUtilities/Network/GULNetwork.m +++ b/GoogleUtilities/Network/GULNetwork.m @@ -90,11 +90,11 @@ + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:completionHandler]; } -- (NSString *)postURL:(NSURL *)url - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler { +- (nullable NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { return [self postURL:url headers:nil payload:payload @@ -103,12 +103,12 @@ - (NSString *)postURL:(NSURL *)url completionHandler:handler]; } -- (NSString *)postURL:(NSURL *)url - headers:(NSDictionary *)headers - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler { +- (nullable NSString *)postURL:(NSURL *)url + headers:(nullable NSDictionary *)headers + payload:(NSData *)payload + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { if (!url.absoluteString.length) { [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; return nil; @@ -189,11 +189,11 @@ - (NSString *)postURL:(NSURL *)url return requestID; } -- (NSString *)getURL:(NSURL *)url - headers:(NSDictionary *)headers - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler { +- (nullable NSString *)getURL:(NSURL *)url + headers:(nullable NSDictionary *)headers + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { if (!url.absoluteString.length) { [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; return nil; diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index 6880fa5a..1dcfd436 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -387,28 +387,20 @@ - (void)URLSession:(NSURLSession *)session dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(evaluateBackgroundQueue, ^{ - SecTrustResultType trustEval = kSecTrustResultInvalid; BOOL shouldAllow; - OSStatus trustError; + CFErrorRef errorRef = NULL; @synchronized([GULNetworkURLSession class]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - trustError = SecTrustEvaluate(serverTrust, &trustEval); -#pragma clang diagnostic pop + shouldAllow = SecTrustEvaluateWithError(serverTrust, &errorRef); } - if (trustError != errSecSuccess) { - [self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError - messageCode:kGULNetworkMessageCodeURLSession008 - message:@"Cannot evaluate server trust. Error, host" - contexts:@[ @(trustError), self->_request.URL ]]; - shouldAllow = NO; - } else { - // Having a trust level "unspecified" by the user is the usual result, described at - // https://developer.apple.com/library/mac/qa/qa1360 - shouldAllow = - (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed); + if (errorRef) { + [self->_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession008 + message:@"Cannot evaluate server trust. Error, host" + contexts:@[ @((int)CFErrorGetCode(errorRef)), self->_request.URL ]]; + CFRelease(errorRef); } // Call the call back with the permission. diff --git a/GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h b/GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h index a8cc45b4..02f25db8 100644 --- a/GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h +++ b/GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h @@ -16,6 +16,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + /// A mutable dictionary that provides atomic accessor and mutators. @interface GULMutableDictionary : NSObject @@ -44,3 +46,5 @@ - (NSDictionary *)dictionary; @end + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h index 8631b8bf..4c5b5f56 100644 --- a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h +++ b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h @@ -20,6 +20,8 @@ #import "GULNetworkLoggerProtocol.h" #import "GULNetworkURLSession.h" +NS_ASSUME_NONNULL_BEGIN + /// Delegate protocol for GULNetwork events. @protocol GULNetworkReachabilityDelegate @@ -69,29 +71,31 @@ /// Compresses and sends a POST request with the provided data to the URL. The session will be /// background session if usingBackgroundSession is YES. Otherwise, the POST session is default /// session. Returns a session ID or nil if an error occurs. -- (NSString *)postURL:(NSURL *)url - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler; +- (nullable NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; /// Compresses and sends a POST request with the provided headers and data to the URL. The session /// will be background session if usingBackgroundSession is YES. Otherwise, the POST session is /// default session. Returns a session ID or nil if an error occurs. -- (NSString *)postURL:(NSURL *)url - headers:(NSDictionary *)headers - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler; +- (nullable NSString *)postURL:(NSURL *)url + headers:(nullable NSDictionary *)headers + payload:(NSData *)payload + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; /// Sends a GET request with the provided data to the URL. The session will be background session /// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a /// session ID or nil if an error occurs. -- (NSString *)getURL:(NSURL *)url - headers:(NSDictionary *)headers - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(GULNetworkCompletionHandler)handler; +- (nullable NSString *)getURL:(NSURL *)url + headers:(nullable NSDictionary *)headers + queue:(nullable dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; @end + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h index 1cbedd1b..341b9745 100644 --- a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h +++ b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h @@ -16,6 +16,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + /// Error codes in Firebase Network error domain. /// Note: these error codes should never change. It would make it harder to decode the errors if /// we inadvertently altered any of these codes in a future SDK version. @@ -69,3 +71,5 @@ extern const int kGULNetworkHTTPStatusCodeMovedTemporarily; extern const int kGULNetworkHTTPStatusCodeNotFound; extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic; extern const int kGULNetworkHTTPStatusCodeUnavailable; + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h index 425c0731..b9e93ec6 100644 --- a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h +++ b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h @@ -18,6 +18,8 @@ #import "GULNetworkMessageCode.h" +NS_ASSUME_NONNULL_BEGIN + /// The log levels used by GULNetworkLogger. typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { kGULNetworkLogLevelError = 3, @@ -47,3 +49,5 @@ typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { message:(NSString *)message; @end + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h index 507bc5a5..2d45ec6e 100644 --- a/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h +++ b/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h @@ -16,6 +16,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + // Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { // GULNetwork.m @@ -45,3 +47,5 @@ typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018 kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019 }; + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h b/GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h index 0c70c055..cac5ca3d 100644 --- a/GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h +++ b/GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h @@ -19,6 +19,8 @@ #import #endif +NS_ASSUME_NONNULL_BEGIN + /// Reachability Status typedef enum { kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. @@ -77,3 +79,5 @@ const NSString *GULReachabilityStatusString(GULReachabilityStatus status); - (void)stop; @end + +NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/SwizzlerTestHelpers/GULProxy.h b/GoogleUtilities/SwizzlerTestHelpers/GULProxy.h deleted file mode 100644 index 1d36f6b9..00000000 --- a/GoogleUtilities/SwizzlerTestHelpers/GULProxy.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -/** An example NSProxy that could be used to wrap an object that we have to ISA Swizzle. */ -@interface GULProxy : NSProxy - -+ (instancetype)proxyWithDelegate:(id)delegate; - -@end diff --git a/GoogleUtilities/SwizzlerTestHelpers/GULProxy.m b/GoogleUtilities/SwizzlerTestHelpers/GULProxy.m deleted file mode 100644 index e542643c..00000000 --- a/GoogleUtilities/SwizzlerTestHelpers/GULProxy.m +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GoogleUtilities/SwizzlerTestHelpers/GULProxy.h" - -@interface GULProxy () - -@property(nonatomic, strong) id delegateObject; - -@end - -@implementation GULProxy - -- (instancetype)initWithDelegate:(id)delegate { - _delegateObject = delegate; - return self; -} - -+ (instancetype)proxyWithDelegate:(id)delegate { - return [[GULProxy alloc] initWithDelegate:delegate]; -} - -- (id)forwardingTargetForSelector:(SEL)selector { - return _delegateObject; -} - -- (void)forwardInvocation:(NSInvocation *)invocation { - if (_delegateObject != nil) { - [invocation setTarget:_delegateObject]; - [invocation invoke]; - } -} - -- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - return [_delegateObject instanceMethodSignatureForSelector:selector]; -} - -- (BOOL)respondsToSelector:(SEL)aSelector { - return [_delegateObject respondsToSelector:aSelector]; -} - -- (BOOL)isEqual:(id)object { - return [_delegateObject isEqual:object]; -} - -- (NSUInteger)hash { - return [_delegateObject hash]; -} - -- (Class)superclass { - return [_delegateObject superclass]; -} - -- (Class)class { - return [_delegateObject class]; -} - -- (BOOL)isKindOfClass:(Class)aClass { - return [_delegateObject isKindOfClass:aClass]; -} - -- (BOOL)isMemberOfClass:(Class)aClass { - return [_delegateObject isMemberOfClass:aClass]; -} - -- (BOOL)conformsToProtocol:(Protocol *)aProtocol { - return [_delegateObject conformsToProtocol:aProtocol]; -} - -- (BOOL)isProxy { - return YES; -} - -- (NSString *)description { - return [_delegateObject description]; -} - -- (NSString *)debugDescription { - return [_delegateObject debugDescription]; -} - -@end diff --git a/GoogleUtilities/Tests/SwiftUnit/GULNetworkInfoTests.swift b/GoogleUtilities/Tests/SwiftUnit/GULNetworkInfoTests.swift deleted file mode 100644 index 1673bc32..00000000 --- a/GoogleUtilities/Tests/SwiftUnit/GULNetworkInfoTests.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import XCTest - -@testable import GoogleUtilities - -class GULNetworkInfoTests: XCTestCase { - func test_mccMNC_validatesCorrectly() { - let expectations: [(mobileCountryCode: String, mobileNetworkCode: String, expected: String)] = [ - ("310", "004", "310004"), - ("310", "01", "31001"), - ("001", "50", "00150"), - ] - - expectations - .forEach { (mobileCountryCode: String, mobileNetworkCode: String, expected: String) in - let format = GULNetworkInfo.formatMcc(mobileCountryCode, andMNC: mobileNetworkCode) - XCTAssertEqual(format, expected) - } - } - - func test_mccMNC_isEmptyWhenInvalid() { - let expectations: [(mobileCountryCode: String?, mobileNetworkCode: String?)] = [ - ("3100", "004"), // MCC too long - ("31", "01"), // MCC too short - ("310", "0512"), // MNC too long - ("L00", "003"), // MCC contains non-decimal characters - ("300", "00T"), // MNC contains non-decimal characters - (nil, nil), // Handle nils gracefully - (nil, "001"), - ("310", nil), - ] - - expectations.forEach { (mobileCountryCode: String?, mobileNetworkCode: String?) in - let format = GULNetworkInfo.formatMcc(mobileCountryCode, andMNC: mobileNetworkCode) - XCTAssertEqual(format, nil) - } - } -} diff --git a/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageTest.m b/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageTest.m deleted file mode 100644 index f5e383eb..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageTest.m +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -// Import specific version implementations for compatibility testing. -#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h" -#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h" - -@interface GULHeartbeatDateStorageTest : XCTestCase -@property(nonatomic) GULHeartbeatDateStorage *storage; -@end - -static NSString *const kTestFileName = @"GULStorageHeartbeatTestFile"; - -@implementation GULHeartbeatDateStorageTest { - BOOL _rootDirectoryCreated; -} - -- (BOOL)setUpWithError:(NSError *__autoreleasing _Nullable *)error { - [super setUpWithError:error]; - - NSError *directoryError; - if (!_rootDirectoryCreated) { - _rootDirectoryCreated = [self createRootDirectoryWithError:&directoryError]; - } - - BOOL success = _rootDirectoryCreated; - if (!success && error) { - *error = [NSError errorWithDomain:@"com.GULHeartbeatDateStorageTest.ErrorDomain" - code:1 - userInfo:@{NSUnderlyingErrorKey : directoryError}]; - } - - return success; -} - -- (void)setUp { - [super setUp]; - - // Clean up before the test in case the cleanup was not completed in previous tests for some - // reason (e.g. a crash). - [self cleanupStorageDir]; - - self.storage = [[GULHeartbeatDateStorage alloc] initWithFileName:kTestFileName]; - [self assertInitializationDoesNotAccessFileSystem]; -} - -- (void)tearDown { - [super tearDown]; - - [self cleanupStorageDir]; - - self.storage = nil; -} - -#pragma mark - Public API Tests - -- (void)testHeartbeatDateForTag { - // 1. Tags and saves heartbeat info, which creates the storage directory & file as side effects. - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 2. Retrieve saved heartbeat info. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - - // 3. Assert that requested heartbeat info matches what was stored. - // This implies the storage directory & file were created, written to, and read from. - XCTAssertEqualObjects(retrievedDate, date); -} - -/// Heartbeat info is requested when the storage directory already exists (i.e. it was created in a -/// previous app launch). -- (void)testHeartbeatDateForTagWhenHeartbeatStorageDirectoryExists { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with heartbeat info. - // 2.1 Create a dictionary with heartbeat info. - NSDate *storedDate = [NSDate distantPast]; - NSString *storedTag = @"stored-tag"; - NSDictionary *storedHeartbeatDictionary = @{storedTag : storedDate}; - // 2.2 Encode the dictionary. - NSError *archiveError; - NSData *data = [GULSecureCoding archivedDataWithRootObject:storedHeartbeatDictionary - error:&archiveError]; - XCTAssertNotNil(data); - XCTAssertNil(archiveError); - - // 2.3 Write the encoded dictionary to file. - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [data writeToURL:heartbeatStorageFileURL atomically:YES]; - XCTAssertNotNil(data); - - // 3. Retrieve the stored heartbeat info and validate that it matches the info that was stored. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:storedTag]; - - XCTAssertEqualObjects(retrievedDate, storedDate); -} - -- (void)testHeartbeatDateForTagWhenReturnedDateIsNil { - NSString *nonexistentTag = @"missing-tag"; - NSDate *nilDate = [self.storage heartbeatDateForTag:nonexistentTag]; - XCTAssertNil(nilDate); -} - -- (void)testSetHeartbeatDateForTagWhenExpectingFailure { - // The `setHearbeatDate: forTag:` API is expected to return NO if it is unable to write the - // heartbeat info to file. To verify the API successfully fails, an invalid string is used to - // create an invalid instance of `GULHeartbeatDateStorage` by supplying an invalid filename. - // This replicates a file system error that prohibits `setHearbeatDate: forTag:` from successfully - // storing the heartbeat info. - NSString *invalidFileName = @""; - GULHeartbeatDateStorage *invalidStorage = - [[GULHeartbeatDateStorage alloc] initWithFileName:invalidFileName]; - - BOOL success = [invalidStorage setHearbeatDate:NSDate.date forTag:@"tag"]; - XCTAssertFalse(success); -} - -- (void)testSetHeartbeatDateForTag { - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - - // 1. Retrieve heartbeat info that is not stored and assert the retrieved info is nil. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNil(retrievedDate); - - // 2. Save the heartbeat info and assert the save was successful. - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 3. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate. - retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, date); -} - -- (void)testHeartbeatDateForTagWhenHeartbeatFileReturnsInvalidData { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with invalid data. - NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; - XCTAssertNotNil(corruptedData); - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [corruptedData writeToURL:heartbeatStorageFileURL atomically:YES]; - - // 3. Retrieve saved heartbeat info and assert that it is nil. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:@"tag"]; - XCTAssertNil(retrievedDate); -} - -- (void)testHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedContent { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with unexpected content. - NSArray *array = @[ [NSDate distantPast], [NSDate date], [NSDate distantFuture] ]; - NSError *archiveError; - NSData *data = [GULSecureCoding archivedDataWithRootObject:array error:&archiveError]; - XCTAssertNotNil(data); - XCTAssert(data.length > 0); - XCTAssertNil(archiveError); - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [data writeToURL:heartbeatStorageFileURL atomically:YES]; - - // 3. Retrieve saved heartbeat info and assert that it is nil. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:@"tag"]; - XCTAssertNil(retrievedDate); -} - -- (void)testSetHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedDictionaryContent { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with unexpected content. - NSDictionary *heartbeatDict = @{@"tag" : [NSDate distantPast]}; - NSDictionary *nestedHeartbeatDict = @{@"tag" : heartbeatDict}; - NSError *archiveError; - NSData *data = [GULSecureCoding archivedDataWithRootObject:nestedHeartbeatDict - error:&archiveError]; - XCTAssertNotNil(data); - XCTAssert(data.length > 0); - XCTAssertNil(archiveError); - - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [data writeToURL:heartbeatStorageFileURL atomically:YES]; - - // 3. Create heartbeat info. - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - - // 4. Retrieve heartbeat info that is not stored and assert the retrieved info is nil. - // This assertion implies type validation in `heartbeatDateForTag:` has worked correctly. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNil(retrievedDate); - - // 5. Save the heartbeat info and assert the save was successful. - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 6. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate. - retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, date); -} - -- (void)testSetHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedContent { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with unexpected content. - NSArray *array = @[ [NSDate distantPast], [NSDate date], [NSDate distantFuture] ]; - NSError *archiveError; - NSData *data = [GULSecureCoding archivedDataWithRootObject:array error:&archiveError]; - XCTAssertNotNil(data); - XCTAssert(data.length > 0); - XCTAssertNil(archiveError); - - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [data writeToURL:heartbeatStorageFileURL atomically:YES]; - - // 3. Create heartbeat info. - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - - // 4. Retrieve heartbeat info that is not stored and assert the retrieved info is nil. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNil(retrievedDate); - - // 5. Save the heartbeat info and assert the save was successful. - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 6. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate. - retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, date); -} - -- (void)testSetHeartbeatDateForTagWhenHeartbeatFileReturnsInvalidData { - // 1. Manually create the heartbeat directory. - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSError *error; - BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL - withError:&error]; - XCTAssert(directoryCreated); - - // 2. Populate the storage file with invalid data. - NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; - XCTAssertNotNil(corruptedData); - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - [corruptedData writeToURL:heartbeatStorageFileURL atomically:YES]; - - // 3. Tag and save heartbeat info. This should overwrite the invalid data with a - // correctly encoded heartbeat dictionary. - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 4. Retrieve saved heartbeat info. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - - // 5. Assert that requested heartbeat info matches what was stored. - // This implies the storage directory & file were created, written to, and read from. - XCTAssertEqualObjects(retrievedDate, date); -} - -- (void)testConformsToHeartbeatStorableProtocol { - XCTAssertTrue([self.storage conformsToProtocol:@protocol(GULHeartbeatDateStorable)]); -} - -#pragma mark - Concurrency tests - -- (void)testConcurrentReadWriteWithSingleInstance { - dispatch_queue_t concurrentQueue = dispatch_queue_create( - "testConcurrentReadWriteToTheSameFileFromDifferentInstances", DISPATCH_QUEUE_CONCURRENT); - - NSUInteger attemptsCount = 50; - - for (NSUInteger i = 0; i < attemptsCount; i++) { - dispatch_async(concurrentQueue, ^{ - [self assertWriteAndReadNoFileCorruption:self.storage]; - }); - } - - // Wait until all storage operations completed. - dispatch_barrier_sync(concurrentQueue, ^{ - }); -} - -- (void)testConcurrentReadWritesToTheSameFileFromDifferentInstances { - dispatch_queue_t concurrentQueue = dispatch_queue_create( - "testConcurrentReadWriteToTheSameFileFromDifferentInstances", DISPATCH_QUEUE_CONCURRENT); - - GULHeartbeatDateStorage *storage1 = - [[GULHeartbeatDateStorage alloc] initWithFileName:kTestFileName]; - GULHeartbeatDateStorage *storage2 = - [[GULHeartbeatDateStorage alloc] initWithFileName:kTestFileName]; - - NSUInteger attemptsCount = 50; - - for (NSUInteger i = 0; i < attemptsCount; i++) { - dispatch_async(concurrentQueue, ^{ - [self assertWriteAndReadNoFileCorruption:storage1]; - }); - } - - for (NSUInteger i = 0; i < attemptsCount; i++) { - dispatch_async(concurrentQueue, ^{ - [self assertWriteAndReadNoFileCorruption:storage2]; - }); - } - - // Wait until all storage operations completed. - dispatch_barrier_sync(concurrentQueue, ^{ - }); -} - -#pragma mark - Version Compatibility (#36) - -- (void)testCompatibility_pre7_4_0 { - NSString *tag = @"tag"; - - // 1. Store heartbeat using current heartbeat API. - NSDate *distantPast = [NSDate distantPast]; - BOOL successfulSave = [self.storage setHearbeatDate:distantPast forTag:tag]; - XCTAssert(successfulSave); - [self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]]; - - // 2. Developer downgrades to below 7.4.0. - - // 3. Store heartbeat from pre-7.4.0 API and verify success. - NSDate *storedDate = [NSDate date]; - GULHeartbeatDateStorage7_3_1 *storage7_3_1 = - [[GULHeartbeatDateStorage7_3_1 alloc] initWithFileName:kTestFileName]; - // The following line caused crashes after persistent storage modifications by 7.4.0. - successfulSave = [storage7_3_1 setHearbeatDate:storedDate forTag:tag]; - XCTAssert(successfulSave); - [self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]]; - - NSDate *retrievedDate = [storage7_3_1 heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, storedDate); -} - -- (void)testForwardCompatibility7_4_0 { - NSString *tag = @"tag"; - - // 1. Store heartbeat using heartbeat API from 7.4.0. (Immutable info is written to disk.) - NSDate *storedDate = [NSDate distantPast]; - GULHeartbeatDateStorage7_4_0 *storage7_4_0 = - [[GULHeartbeatDateStorage7_4_0 alloc] initWithFileName:kTestFileName]; - // The following line caused crashes after persistent storage modifications by 7.4.0. - BOOL successfulSave = [storage7_4_0 setHearbeatDate:storedDate forTag:tag]; - XCTAssert(successfulSave); - [self assertStoredHeartbeatDictionaryIsKindOf:[NSDictionary class]]; - - // 2. Developer upgrades to current version. - - // 3. Retrieve and then store heartbeat from current API and verify success. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNotNil(retrievedDate); - XCTAssertEqualObjects(retrievedDate, storedDate); - - NSDate *date = [NSDate date]; - successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - [self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]]; - - retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNotNil(retrievedDate); - XCTAssertEqualObjects(retrievedDate, date); -} - -#pragma mark - Testing Utilities - -- (BOOL)createRootDirectoryWithError:(NSError *__autoreleasing _Nullable *)outError { - NSArray *paths; -#if TARGET_OS_TV - paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); -#else - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); -#endif // TARGET_OS_TV - NSString *rootPath = [paths lastObject]; - NSURL *rootURL = [NSURL fileURLWithPath:rootPath]; - - BOOL rootDirectoryExists = [rootURL checkResourceIsReachableAndReturnError:nil]; - if (!rootDirectoryExists) { - rootDirectoryExists = [[NSFileManager defaultManager] createDirectoryAtURL:rootURL - withIntermediateDirectories:YES - attributes:nil - error:outError]; - } - return rootDirectoryExists; -} - -- (BOOL)explicitlyCreateDirectoryForURL:(NSURL *)directoryPathURL withError:(NSError **)outError { - BOOL success = false; - if (![directoryPathURL checkResourceIsReachableAndReturnError:outError]) { - success = [[NSFileManager defaultManager] createDirectoryAtURL:directoryPathURL - withIntermediateDirectories:YES - attributes:nil - error:outError]; - } - return success; -} - -- (void)assertInitializationDoesNotAccessFileSystem { - NSString *directoryURL = [[self pathURLForDirectory:kGULHeartbeatStorageDirectory] path]; - BOOL isDir; - BOOL directoryIsReachable = - [[NSFileManager defaultManager] fileExistsAtPath:directoryURL isDirectory:&isDir] && isDir; - XCTAssertFalse(directoryIsReachable, - @"The Heartbeat Storage Directory already exists. " - @"GULHeartbeatDateStorage initialization should not access the file system."); -} - -- (NSURL *)pathURLForDirectory:(NSString *)directory { - NSArray *paths; -#if TARGET_OS_TV - paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); -#else - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); -#endif // TARGET_OS_TV - NSString *rootPath = [paths lastObject]; - NSURL *rootURL = [NSURL fileURLWithPath:rootPath]; - NSURL *directoryURL = [rootURL URLByAppendingPathComponent:directory]; - return directoryURL; -} - -- (NSURL *)fileURLForDirectory:(NSURL *)directoryURL { - NSURL *fileURL = [directoryURL URLByAppendingPathComponent:kTestFileName]; - return fileURL; -} - -- (void)assertStoredHeartbeatDictionaryIsKindOf:(Class)class { - NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL]; - NSData *objectData = [NSData dataWithContentsOfURL:heartbeatStorageFileURL options:0 error:nil]; - __auto_type objectClasses = - [NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class ]]; - NSMutableDictionary *heartbeatDict = [GULSecureCoding unarchivedObjectOfClasses:objectClasses - fromData:objectData - error:nil]; - if (class == [NSDictionary class]) { - XCTAssertFalse([heartbeatDict isKindOfClass:[NSMutableDictionary class]]); - } - XCTAssertTrue([heartbeatDict isKindOfClass:class]); -} - -- (void)assertWriteAndReadNoFileCorruption:(GULHeartbeatDateStorage *)storage { - NSString *tag = self.name; - NSDate *date = [NSDate date]; - [storage setHearbeatDate:date forTag:tag]; - - // Assert that the file was not corrupted by concurrent access. - // NOTE: With the current synchronization model we cannot expect the read date to be equal the - // date just set because another date may be set from another thread before read is performed. - // Prevent the read/modify/write data race is currently the storage clients responsibility. - XCTAssertNotNil([storage heartbeatDateForTag:tag]); -} - -- (void)cleanupStorageDir { - // Removes the Heartbeat Storage Directory if it exists. - NSURL *directoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory]; - [[NSFileManager defaultManager] removeItemAtURL:directoryURL error:nil]; -} - -@end diff --git a/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageUserDefaultsTest.m b/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageUserDefaultsTest.m deleted file mode 100644 index 18c13b2f..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/GULHeartbeatDateStorageUserDefaultsTest.m +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h" - -@interface GULHeartbeatDateStorageUserDefaultsTest : XCTestCase -@property(nonatomic) GULHeartbeatDateStorageUserDefaults *storage; -@property(nonatomic) NSUserDefaults *defaults; -@end - -@implementation GULHeartbeatDateStorageUserDefaultsTest - -- (void)setUp { - NSString *suiteName = [self userDefaultsSuiteName]; - self.defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; - self.storage = [[GULHeartbeatDateStorageUserDefaults alloc] initWithDefaults:self.defaults - key:@"test_root"]; -} - -- (void)tearDown { - [self.defaults removePersistentDomainForName:[self userDefaultsSuiteName]]; - self.defaults = nil; - - self.storage = nil; -} - -#pragma mark - Public API Tests - -- (void)testHeartbeatDateForTag { - // 1. Tag and save some heartbeat info. - NSDate *storedDate = [NSDate date]; - NSString *tag = @"fire-iid"; - [self.storage setHearbeatDate:storedDate forTag:tag]; - - // 2. Retrieve the stored heartbeat info and assert the retrieved info is accurate. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, storedDate); -} - -- (void)testHeartbeatDateForTagWhenReturnedDateIsNil { - NSString *nonexistentTag = @"missing-tag"; - NSDate *nilDate = [self.storage heartbeatDateForTag:nonexistentTag]; - XCTAssertNil(nilDate); -} - -- (void)testSetHeartbeatDateForTag { - NSDate *date = [NSDate date]; - NSString *tag = @"tag"; - - // 1. Retrieve heartbeat info that is not stored and assert the retrieved info is nil. - NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertNil(retrievedDate); - - // 2. Save the heartbeat info and assert the save was successful. - BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag]; - XCTAssert(successfulSave); - - // 3. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate. - retrievedDate = [self.storage heartbeatDateForTag:tag]; - XCTAssertEqualObjects(retrievedDate, date); -} - -- (void)testConformsToHeartbeatStorableProtocol { - XCTAssertTrue([self.storage conformsToProtocol:@protocol(GULHeartbeatDateStorable)]); -} - -#pragma mark - Testing Utilities - -- (NSString *)userDefaultsSuiteName { - NSCharacterSet *lettersToTrim = [[NSCharacterSet letterCharacterSet] invertedSet]; - NSString *nameWithSpaces = [self.name stringByTrimmingCharactersInSet:lettersToTrim]; - return [nameWithSpaces stringByReplacingOccurrencesOfString:@" " withString:@"_"]; -} - -@end diff --git a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m index d2512974..12160c6b 100644 --- a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m +++ b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m @@ -29,7 +29,6 @@ #import #import -#import "FBLPromise+Testing.h" #import "GoogleUtilities/Tests/Unit/Utils/GULTestKeychain.h" #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h" @@ -120,16 +119,21 @@ - (void)testGetExistingObjectClassMismatch { // Skip in-memory cache because the error is relevant only for Keychain. OCMExpect([self.mockCache objectForKey:key]).andReturn(nil); - FBLPromise> *getPromise = [self.storage getObjectForKey:key - objectClass:[NSString class] - accessGroup:nil]; - - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssertNil(getPromise.value); - XCTAssertNotNil(getPromise.error); - // TODO: Test for particular error. - - OCMVerifyAll(self.mockCache); + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self.storage getObjectForKey:key + objectClass:[NSString class] + accessGroup:nil + completionHandler:^(id _Nullable obj, NSError *_Nullable error) { + XCTAssertNil(obj); + // Assert class mismatch error. + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, NSCocoaErrorDomain); + XCTAssertEqual(error.code, 4864); + + OCMVerifyAll(self.mockCache); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; } - (void)testRemoveExistingObject { @@ -155,15 +159,23 @@ - (void)testRemoveNonExistingObject { - (void)assertSuccessWriteObject:(id)object forKey:(NSString *)key { OCMExpect([self.mockCache setObject:object forKey:key]).andForwardToRealObject(); - FBLPromise *setPromise = [self.storage setObject:object forKey:key accessGroup:nil]; - - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssertNil(setPromise.error, @"%@", self.name); - + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + __weak __auto_type weakSelf = self; + [self.storage setObject:object + forKey:key + accessGroup:nil + completionHandler:^(id _Nullable obj, NSError *_Nullable error) { + if (!weakSelf) { + return; + } + XCTAssertNil(error, @"%@", weakSelf.name); + // Check in-memory cache. + XCTAssertEqualObjects([weakSelf.cache objectForKey:key], object); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:1.0]; OCMVerifyAll(self.mockCache); - - // Check in-memory cache. - XCTAssertEqualObjects([self.cache objectForKey:key], object); } - (void)assertSuccessReadObject:(id)object @@ -176,39 +188,57 @@ - (void)assertSuccessReadObject:(id)object OCMExpect([self.mockCache setObject:object forKey:key]).andForwardToRealObject(); } - FBLPromise> *getPromise = - [self.storage getObjectForKey:key objectClass:class accessGroup:nil]; - - XCTAssert(FBLWaitForPromisesWithTimeout(1), @"%@", self.name); - XCTAssertEqualObjects(getPromise.value, object, @"%@", self.name); - XCTAssertNil(getPromise.error, @"%@", self.name); - + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + __weak __auto_type weakSelf = self; + [self.storage + getObjectForKey:key + objectClass:class + accessGroup:nil + completionHandler:^(id _Nullable obj, NSError *_Nullable error) { + if (!weakSelf) { + return; + } + XCTAssertEqualObjects(obj, object, @"%@", weakSelf.name); + XCTAssertNil(error, @"%@", weakSelf.name); + // Check in-memory cache. + XCTAssertEqualObjects([weakSelf.cache objectForKey:key], object, @"%@", weakSelf.name); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; OCMVerifyAll(self.mockCache); - - // Check in-memory cache. - XCTAssertEqualObjects([self.cache objectForKey:key], object, @"%@", self.name); } - (void)assertNonExistingObjectForKey:(NSString *)key class:(Class)class { OCMExpect([self.mockCache objectForKey:key]).andForwardToRealObject(); - FBLPromise> *promise = - [self.storage getObjectForKey:key objectClass:class accessGroup:nil]; - - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssertNil(promise.error, @"%@", self.name); - XCTAssertNil(promise.value, @"%@", self.name); - + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + __weak __auto_type weakSelf = self; + [self.storage getObjectForKey:key + objectClass:class + accessGroup:nil + completionHandler:^(id _Nullable obj, NSError *_Nullable error) { + if (!weakSelf) { + return; + } + XCTAssertNil(error, @"%@", weakSelf.name); + XCTAssertNil(obj, @"%@", weakSelf.name); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; OCMVerifyAll(self.mockCache); } - (void)assertRemoveObjectForKey:(NSString *)key { OCMExpect([self.mockCache removeObjectForKey:key]).andForwardToRealObject(); - FBLPromise *removePromise = [self.storage removeObjectForKey:key accessGroup:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssertNil(removePromise.error); - + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self.storage removeObjectForKey:key + accessGroup:nil + completionHandler:^(NSError *_Nullable error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; OCMVerifyAll(self.mockCache); } diff --git a/GoogleUtilities/Tests/Unit/Environment/NSURLSession+GULPromisesTests.m b/GoogleUtilities/Tests/Unit/Environment/NSURLSession+GULPromisesTests.m deleted file mode 100644 index 5a4994bc..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/NSURLSession+GULPromisesTests.m +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import -#import "FBLPromise+Testing.h" -#import "GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h" - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h" - -#if !TARGET_OS_MACCATALYST - -@interface NSURLSession_GULPromisesTests : XCTestCase -@property(nonatomic) NSURLSession *URLSession; -@property(nonatomic) id URLSessionMock; -@end - -@implementation NSURLSession_GULPromisesTests - -- (void)setUp { - self.URLSession = [NSURLSession - sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - self.URLSessionMock = OCMPartialMock(self.URLSession); -} - -- (void)tearDown { - [self.URLSessionMock stopMocking]; - self.URLSessionMock = nil; - self.URLSession = nil; -} - -- (void)testDataTaskPromiseWithRequestSuccess { - NSURL *url = [NSURL URLWithString:@"https://localhost"]; - NSURLRequest *request = [NSURLRequest requestWithURL:url]; - - NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:url - statusCode:200 - HTTPVersion:@"1.1" - headerFields:nil]; - NSData *expectedBody = [@"body" dataUsingEncoding:NSUTF8StringEncoding]; - - [FIRURLSessionOCMockStub - stubURLSessionDataTaskWithResponse:expectedResponse - body:expectedBody - error:nil - URLSessionMock:self.URLSessionMock - requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) { - return [sentRequest isEqual:request]; - }]; - - __auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request]; - - XCTAssert(FBLWaitForPromisesWithTimeout(1.0)); - - XCTAssertTrue(taskPromise.isFulfilled); - XCTAssertNil(taskPromise.error); - XCTAssertEqualObjects(taskPromise.value.HTTPResponse, expectedResponse); - XCTAssertEqualObjects(taskPromise.value.HTTPBody, expectedBody); -} - -- (void)testDataTaskPromiseWithRequestError { - NSURL *url = [NSURL URLWithString:@"https://localhost"]; - NSURLRequest *request = [NSURLRequest requestWithURL:url]; - - NSError *expectedError = [NSError errorWithDomain:@"testDataTaskPromiseWithRequestError" - code:-1 - userInfo:nil]; - - [FIRURLSessionOCMockStub - stubURLSessionDataTaskWithResponse:nil - body:nil - error:expectedError - URLSessionMock:self.URLSessionMock - requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) { - return [sentRequest isEqual:request]; - }]; - - __auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request]; - - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - XCTAssertTrue(taskPromise.isRejected); - XCTAssertEqualObjects(taskPromise.error, expectedError); - XCTAssertNil(taskPromise.value); -} - -@end - -#endif // !TARGET_OS_MACCATALYST diff --git a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h b/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h deleted file mode 100644 index ef983da4..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// MARK: This file is strictly for version compatibility testing. - -#import - -NS_ASSUME_NONNULL_BEGIN - -/// Stores either a date or a dictionary to a specified file. -@interface GULHeartbeatDateStorage7_3_1 : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -@property(nonatomic, readonly) NSURL *fileURL; - -/** - * Default initializer. - * @param fileName The name of the file to store the date information. - * exist, it will be created if needed. - */ -- (instancetype)initWithFileName:(NSString *)fileName; - -/** - * Reads the date from the specified file for the given tag. - * @return Returns date if exists, otherwise `nil`. - */ -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; - -/** - * Saves the date for the specified tag in the specified file. - * @return YES on success, NO otherwise. - */ -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.m b/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.m deleted file mode 100644 index 136169cd..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.m +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// MARK: This file is strictly for version compatibility testing. - -#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -@interface GULHeartbeatDateStorage7_3_1 () -/** The storage to store the date of the last sent heartbeat. */ -@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator; -/** The name of the file that stores heartbeat information. */ -@property(nonatomic, readonly) NSString *fileName; -@end - -@implementation GULHeartbeatDateStorage7_3_1 - -@synthesize fileURL = _fileURL; - -- (instancetype)initWithFileName:(NSString *)fileName { - if (fileName == nil) { - return nil; - } - - self = [super init]; - if (self) { - _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; - _fileName = fileName; - } - return self; -} - -/** Lazy getter for fileURL - * @return fileURL where heartbeat information is stored. - */ -- (NSURL *)fileURL { - if (!_fileURL) { - NSURL *directoryURL = [[self class] directoryPathURL]; - [[self class] checkAndCreateDirectory:directoryURL fileCoordinator:_fileCoordinator]; - _fileURL = [directoryURL URLByAppendingPathComponent:_fileName]; - } - return _fileURL; -} - -/** Returns the URL path of the directory for heartbeat storage data. - * @return the URL path of the directory for heartbeat storage data. - */ -+ (NSURL *)directoryPathURL { -#if TARGET_OS_TV - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); -#else - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); -#endif - NSArray *components = @[ paths.lastObject, @"Google/FIRApp" ]; - NSString *directoryString = [NSString pathWithComponents:components]; - NSURL *directoryURL = [NSURL fileURLWithPath:directoryString]; - return directoryURL; -} - -/** Checks and creates a directory for the directory specified by the - * directory url - * @param directoryPathURL The path to the directory which needs to be created. - * @param fileCoordinator The fileCoordinator object to coordinate writes to the directory. - */ -+ (void)checkAndCreateDirectory:(NSURL *)directoryPathURL - fileCoordinator:(NSFileCoordinator *)fileCoordinator { - NSError *fileCoordinatorError = nil; - [fileCoordinator - coordinateWritingItemAtURL:directoryPathURL - options:0 - error:&fileCoordinatorError - byAccessor:^(NSURL *writingDirectoryURL) { - NSError *error; - if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) { - // If fail creating the Application Support directory, log warning. - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:&error]; - } - }]; -} - -- (nullable NSMutableDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL { - NSError *error; - NSMutableDictionary *dict; - NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error]; - if (objectData == nil || error != nil) { - dict = [NSMutableDictionary dictionary]; - } else { - dict = [GULSecureCoding - unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]] - fromData:objectData - error:&error]; - if (dict == nil || error != nil) { - dict = [NSMutableDictionary dictionary]; - } - } - return dict; -} - -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag { - __block NSMutableDictionary *dict; - NSError *error; - [self.fileCoordinator coordinateReadingItemAtURL:self.fileURL - options:0 - error:&error - byAccessor:^(NSURL *readingURL) { - dict = [self heartbeatDictionaryWithFileURL:readingURL]; - }]; - return dict[tag]; -} - -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag { - NSError *error; - __block BOOL isSuccess = false; - [self.fileCoordinator coordinateReadingItemAtURL:self.fileURL - options:0 - writingItemAtURL:self.fileURL - options:0 - error:&error - byAccessor:^(NSURL *readingURL, NSURL *writingURL) { - NSMutableDictionary *dictionary = - [self heartbeatDictionaryWithFileURL:readingURL]; - dictionary[tag] = date; - NSError *error; - isSuccess = [self writeDictionary:dictionary - forWritingURL:writingURL - error:&error]; - }]; - return isSuccess; -} - -- (BOOL)writeDictionary:(NSMutableDictionary *)dictionary - forWritingURL:(NSURL *)writingFileURL - error:(NSError **)outError { - NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError]; - if (*outError != nil) { - return false; - } else { - return [data writeToURL:writingFileURL atomically:YES]; - } -} - -@end diff --git a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h b/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h deleted file mode 100644 index ce7da7c7..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// MARK: This file is strictly for version compatibility testing. - -#import - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const kGULHeartbeatStorageDirectory7_4_0; // Avoids duplicate linker error. - -/// Stores either a date or a dictionary to a specified file. -@interface GULHeartbeatDateStorage7_4_0 : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -@property(nonatomic, readonly) NSURL *fileURL; - -/** - * Default initializer. - * @param fileName The name of the file to store the date information. - * exist, it will be created if needed. - */ -- (instancetype)initWithFileName:(NSString *)fileName; - -/** - * Reads the date from the specified file for the given tag. - * @return Returns date if exists, otherwise `nil`. - */ -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag; - -/** - * Saves the date for the specified tag in the specified file. - * @return YES on success, NO otherwise. - */ -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.m b/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.m deleted file mode 100644 index 2ec621eb..00000000 --- a/GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.m +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// MARK: This file is strictly for version compatibility testing. - -#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h" -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -// Add static to avoid linker error. -NSString *const kGULHeartbeatStorageDirectory7_4_0 = @"Google/FIRApp"; - -@interface GULHeartbeatDateStorage7_4_0 () -/** The storage to store the date of the last sent heartbeat. */ -@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator; -/** The name of the file that stores heartbeat information. */ -@property(nonatomic, readonly) NSString *fileName; -@end - -@implementation GULHeartbeatDateStorage7_4_0 - -@synthesize fileURL = _fileURL; - -- (instancetype)initWithFileName:(NSString *)fileName { - if (fileName == nil) return nil; - - self = [super init]; - if (self) { - _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; - _fileName = fileName; - } - return self; -} - -/** Lazy getter for fileURL. - * @return fileURL where heartbeat information is stored. - */ -- (NSURL *)fileURL { - if (!_fileURL) { - NSURL *directoryURL = [self directoryPathURL]; - [self checkAndCreateDirectory:directoryURL]; - _fileURL = [directoryURL URLByAppendingPathComponent:_fileName]; - } - return _fileURL; -} - -/** Returns the URL path of the directory for heartbeat storage data. - * @return the URL path of the directory for heartbeat storage data. - */ -- (NSURL *)directoryPathURL { - NSArray *paths; -#if TARGET_OS_TV - paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); -#else - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); -#endif // TARGET_OS_TV - NSString *rootPath = [paths lastObject]; - NSURL *rootURL = [NSURL fileURLWithPath:rootPath]; - NSURL *directoryURL = [rootURL URLByAppendingPathComponent:kGULHeartbeatStorageDirectory7_4_0]; - return directoryURL; -} - -/** Check for the existence of the directory specified by the URL, and create it if it does not - * exist. - * @param directoryPathURL The path to the directory that needs to exist. - */ -- (void)checkAndCreateDirectory:(NSURL *)directoryPathURL { - NSError *fileCoordinatorError = nil; - [self.fileCoordinator - coordinateWritingItemAtURL:directoryPathURL - options:0 - error:&fileCoordinatorError - byAccessor:^(NSURL *writingDirectoryURL) { - NSError *error; - if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) { - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:&error]; - } - }]; -} - -- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag { - __block NSDictionary *heartbeatDictionary; - NSError *error; - [self.fileCoordinator coordinateReadingItemAtURL:self.fileURL - options:0 - error:&error - byAccessor:^(NSURL *readingURL) { - heartbeatDictionary = - [self heartbeatDictionaryWithFileURL:readingURL]; - }]; - return heartbeatDictionary[tag]; -} - -- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag { - NSError *error; - __block BOOL isSuccess = false; - [self.fileCoordinator - coordinateReadingItemAtURL:self.fileURL - options:0 - writingItemAtURL:self.fileURL - options:0 - error:&error - byAccessor:^(NSURL *readingURL, NSURL *writingURL) { - NSMutableDictionary *heartbeatDictionary = - [[self heartbeatDictionaryWithFileURL:readingURL] mutableCopy]; - heartbeatDictionary[tag] = date; - NSError *error; - isSuccess = [self writeDictionary:heartbeatDictionary.copy - forWritingURL:writingURL - error:&error]; - }]; - return isSuccess; -} - -- (NSDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL { - NSDictionary *heartbeatDictionary = nil; - - NSError *error; - NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error]; - - if (objectData.length > 0 && error == nil) { - NSSet *objectClasses = [NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]]; - heartbeatDictionary = [GULSecureCoding unarchivedObjectOfClasses:objectClasses - fromData:objectData - error:&error]; - } - - if (heartbeatDictionary.count == 0 || error != nil) { - heartbeatDictionary = [NSDictionary dictionary]; - } - - return heartbeatDictionary; -} - -- (BOOL)writeDictionary:(NSDictionary *)dictionary - forWritingURL:(NSURL *)writingFileURL - error:(NSError **)outError { - NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError]; - if (data.length == 0) { - return NO; - } - - return [data writeToURL:writingFileURL atomically:YES]; -} - -@end diff --git a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m index 7e7edfb6..57df4d95 100644 --- a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m +++ b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m @@ -185,7 +185,8 @@ - (void)testNilURLNSURLSession_POST_foreground { NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding]; _statusCode = 200; - [_network postURL:nil + NSURL *nilURL = nil; + [_network postURL:nilURL payload:uncompressedData queue:_backgroundQueue usingBackgroundSession:NO @@ -415,7 +416,8 @@ - (void)testNilURLNSURLSession_POST_background { NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding]; _statusCode = 200; - [_network postURL:nil + NSURL *nilURL = nil; + [_network postURL:nilURL payload:uncompressedData queue:_backgroundQueue usingBackgroundSession:YES @@ -638,7 +640,8 @@ - (void)testNilURLNSURLSession_GET_foreground { XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"]; _statusCode = 200; - [_network getURL:nil + NSURL *nilURL = nil; + [_network getURL:nilURL headers:nil queue:_backgroundQueue usingBackgroundSession:NO @@ -834,7 +837,8 @@ - (void)testNilURLNSURLSession_GET_background { XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"]; _statusCode = 200; - [_network getURL:nil + NSURL *nilURL = nil; + [_network getURL:nilURL headers:nil queue:_backgroundQueue usingBackgroundSession:YES diff --git a/GoogleUtilities/Tests/Unit/Reachability/GULReachabilityCheckerTest.m b/GoogleUtilities/Tests/Unit/Reachability/GULReachabilityCheckerTest.m index e5162149..d657caa8 100644 --- a/GoogleUtilities/Tests/Unit/Reachability/GULReachabilityCheckerTest.m +++ b/GoogleUtilities/Tests/Unit/Reachability/GULReachabilityCheckerTest.m @@ -349,7 +349,8 @@ - (void)testApiScheduleFail { } - (void)testBadHost { - XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:nil], + NSString *nilHost = nil; + XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:nilHost], @"Creating a checker with nil hostname must fail."); XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:@""], @"Creating a checker with empty hostname must fail."); diff --git a/GoogleUtilities/Tests/Unit/SecureCoding/GULSecureCodingTests.m b/GoogleUtilities/Tests/Unit/SecureCoding/GULSecureCodingTests.m deleted file mode 100644 index d936469d..00000000 --- a/GoogleUtilities/Tests/Unit/SecureCoding/GULSecureCodingTests.m +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h" - -@interface SecureCodingIncompatibleObject : NSObject -@end - -@implementation SecureCodingIncompatibleObject - -- (void)encodeWithCoder:(nonnull NSCoder *)coder { -} - -- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - return [self init]; -} - -@end - -@interface GULSecureCodingTests : XCTestCase - -@end - -@implementation GULSecureCodingTests - -- (void)testArchiveUnarchiveSingleClass { - NSDictionary *objectToArchive = @{}; - - NSError *error; - NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:&error]; - XCTAssertNil(error); - XCTAssertNotNil(archiveData); - - NSDictionary *unarchivedObject = [GULSecureCoding unarchivedObjectOfClass:[NSDictionary class] - fromData:archiveData - error:&error]; - XCTAssertNil(error); - XCTAssert([objectToArchive isEqualToDictionary:unarchivedObject]); -} - -- (void)testArchiveUnarchiveMultipleClasses { - NSDictionary *objectToArchive = @{@"key1" : [NSDate date], @"key2" : @(2)}; - - NSError *error; - NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:&error]; - XCTAssertNil(error); - XCTAssertNotNil(archiveData); - - NSDictionary *unarchivedObject = [GULSecureCoding - unarchivedObjectOfClasses: - [NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class, NSNumber.class ]] - fromData:archiveData - error:&error]; - XCTAssertNil(error); - XCTAssert([objectToArchive isEqualToDictionary:unarchivedObject]); -} - -- (void)testArchivingIncompatibleObjectError { - SecureCodingIncompatibleObject *objectToArchive = [[SecureCodingIncompatibleObject alloc] init]; - - NSError *error; - NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:&error]; - XCTAssertNotNil(error); - XCTAssertNil(archiveData); -} - -- (void)testUnarchivingClassMismatchError { - NSDictionary *objectToArchive = @{@"key1" : @"value1", @"key2" : @(2)}; - NSError *error; - NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:&error]; - XCTAssertNil(error); - XCTAssertNotNil(archiveData); - - NSArray *unarchivedObject = [GULSecureCoding unarchivedObjectOfClass:[NSArray class] - fromData:archiveData - error:&error]; - XCTAssertNotNil(error); - XCTAssertNil(unarchivedObject); -} - -- (void)testUnarchivingCorruptedDataError { - NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSString *unarchivedObject = [GULSecureCoding unarchivedObjectOfClass:[NSString class] - fromData:corruptedData - error:&error]; - XCTAssertNotNil(error); - XCTAssertNil(unarchivedObject); -} - -- (void)testArchiveUnarchiveWithNULLError { - SecureCodingIncompatibleObject *objectToArchive = [[SecureCodingIncompatibleObject alloc] init]; - - NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:NULL]; - XCTAssertNil(archiveData); - - NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *unarchivedObject = [GULSecureCoding unarchivedObjectOfClass:[NSDictionary class] - fromData:corruptedData - error:NULL]; - XCTAssertNil(unarchivedObject); -} - -@end diff --git a/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m index e0a34515..7ed61b7e 100644 --- a/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m @@ -399,10 +399,8 @@ - (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate { respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); - XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: - didReceiveRemoteNotification:)]); -#if TARGET_OS_IOS || TARGET_OS_TV +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION // The implementation should not be added if there is no original implementation XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); @@ -410,12 +408,11 @@ - (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate { respondsToSelector:@selector(application: handleEventsForBackgroundURLSession:completionHandler:)]); - // The implementation should not be added if there is no original implementation - XCTAssertFalse([realAppDelegate + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: didReceiveRemoteNotification:fetchCompletionHandler:)]); -#endif // TARGET_OS_IOS || TARGET_OS_TV +#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION // Make sure that the class has changed. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); @@ -452,20 +449,18 @@ - (void)testProxyRemoteNotificationsMethodsEmptyAppDelegateAfterInitialProxy { respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); - XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: - didReceiveRemoteNotification:)]); -#if TARGET_OS_IOS || TARGET_OS_TV + +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION // The implementation should not be added if there is no original implementation XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: handleEventsForBackgroundURLSession:completionHandler:)]); - // The implementation should not be added if there is no original implementation - XCTAssertFalse([realAppDelegate + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: didReceiveRemoteNotification:fetchCompletionHandler:)]); -#endif // TARGET_OS_IOS || TARGET_OS_TV +#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION // Make sure that the class has changed. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); @@ -620,76 +615,72 @@ - (void)testAppDelegateInstance { #if TARGET_OS_IOS || TARGET_OS_TV /** Tests that application:openURL:options: is invoked on the interceptor if it exists. */ - (void)testApplicationOpenURLOptionsIsInvokedOnInterceptors { - if (@available(iOS 10, *)) { - id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) - .andReturn(NO); - - id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) - .andReturn(NO); - - NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; - NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; - - GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; - OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); - - [GULAppDelegateSwizzler proxyOriginalDelegate]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; - - [testAppDelegate application:[GULApplication sharedApplication] - openURL:testURL - options:testOpenURLOptions]; - OCMVerifyAll(interceptor); - OCMVerifyAll(interceptor2); - - // Check that original implementation was called with proper parameters - XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]); - XCTAssertEqual(testAppDelegate.url, testURL); - } + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + + NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; + NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + // Check that original implementation was called with proper parameters + XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]); + XCTAssertEqual(testAppDelegate.url, testURL); } /** Tests that the result of application:openURL:options: from all interceptors is ORed. */ - (void)testResultOfApplicationOpenURLOptionsIsORed { - if (@available(iOS 10, *)) { - NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; - NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; - - GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; - OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); - [GULAppDelegateSwizzler proxyOriginalDelegate]; - - BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] - openURL:testURL - options:testOpenURLOptions]; - // Verify that the original app delegate returns NO. - XCTAssertFalse(shouldOpen); - - id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) - .andReturn(NO); - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; - shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] - openURL:testURL - options:testOpenURLOptions]; - // Verify that if the only interceptor returns NO, the value is still NO. - XCTAssertFalse(shouldOpen); - - id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) - .andReturn(YES); - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; - - OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) - .andReturn(NO); - shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] - openURL:testURL - options:testOpenURLOptions]; - // Verify that if one of the two interceptors returns YES, the value is YES. - XCTAssertTrue(shouldOpen); - } + NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; + NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that the original app delegate returns NO. + XCTAssertFalse(shouldOpen); + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that if the only interceptor returns NO, the value is still NO. + XCTAssertFalse(shouldOpen); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(YES); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that if one of the two interceptors returns YES, the value is YES. + XCTAssertTrue(shouldOpen); } #endif // TARGET_OS_IOS || TARGET_OS_TV @@ -871,38 +862,6 @@ - (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterce XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error); } -// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version, -// it will be unnecessary to check if `TARGET_OS_VISION` is defined. -#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { - NSDictionary *notification = @{}; - GULApplication *application = [GULApplication sharedApplication]; - - id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]); - - id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); - OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]); - - GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; - OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); - [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; - - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; - - [testAppDelegate application:application didReceiveRemoteNotification:notification]; - OCMVerifyAll(interceptor); - OCMVerifyAll(interceptor2); - - XCTAssertEqual(testAppDelegate.application, application); - XCTAssertEqual(testAppDelegate.remoteNotification, notification); -} -#pragma clang diagnostic pop -#endif // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) - #if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { NSDictionary *notification = @{}; @@ -1039,21 +998,19 @@ - (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCal expectedResult:UIBackgroundFetchResultNewData]; } -- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded { - // The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler: +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsAdded { + // The delegate must have an application:didReceiveRemoteNotification:fetchCompletionHandler: // implementation - GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init]; - OCMStub([self.mockSharedApplication delegate]).andReturn(legacyDelegate); + GULTestInterceptorAppDelegate *delegate = [[GULTestInterceptorAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(delegate); - XCTAssertFalse([legacyDelegate - respondsToSelector:@selector(application: - didReceiveRemoteNotification:fetchCompletionHandler:)]); + XCTAssertFalse([delegate respondsToSelector:@selector + (application:didReceiveRemoteNotification:fetchCompletionHandler:)]); [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; - XCTAssertFalse([legacyDelegate - respondsToSelector:@selector(application: - didReceiveRemoteNotification:fetchCompletionHandler:)]); + XCTAssertTrue([delegate respondsToSelector:@selector + (application:didReceiveRemoteNotification:fetchCompletionHandler:)]); } #endif // TARGET_OS_IOS || TARGET_OS_TV diff --git a/GoogleUtilities/Tests/Unit/Swizzler/GULObjectSwizzlerTest.m b/GoogleUtilities/Tests/Unit/Swizzler/GULObjectSwizzlerTest.m deleted file mode 100644 index 244fe81b..00000000 --- a/GoogleUtilities/Tests/Unit/Swizzler/GULObjectSwizzlerTest.m +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import -#import - -#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h" -#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h" -#import "GoogleUtilities/SwizzlerTestHelpers/GULProxy.h" - -@interface GULObjectSwizzlerTest : XCTestCase - -@end - -@implementation GULObjectSwizzlerTest - -/** Used as a donor method to add a method that doesn't exist on the superclass. */ -- (NSString *)donorDescription { - return @"SwizzledDonorDescription"; -} - -/** Used as a donor method to add a method that exists on the superclass. */ -- (NSString *)description { - return @"SwizzledDescription"; -} - -/** Exists just as a donor method. */ -- (void)donorMethod { -} - -- (void)testRetainedAssociatedObjects { - NSObject *object = [[NSObject alloc] init]; - NSObject *associatedObject = [[NSObject alloc] init]; - size_t addressOfAssociatedObject = (size_t)&associatedObject; - [GULObjectSwizzler setAssociatedObject:object - key:@"test" - value:associatedObject - association:GUL_ASSOCIATION_RETAIN]; - associatedObject = nil; - associatedObject = [GULObjectSwizzler getAssociatedObject:object key:@"test"]; - XCTAssertEqual((size_t)&associatedObject, addressOfAssociatedObject); - XCTAssertNotNil(associatedObject); -} - -/** Tests that creating an object swizzler works. */ -- (void)testObjectSwizzlerInit { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - XCTAssertNotNil(objectSwizzler); -} - -/** Tests that you're able to swizzle an object. */ -- (void)testSwizzle { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - XCTAssertEqual([object class], [NSObject class]); - [objectSwizzler swizzle]; - XCTAssertNotEqual([object class], [NSObject class]); - XCTAssertTrue([[object class] isSubclassOfClass:[NSObject class]]); - XCTAssertTrue([object respondsToSelector:@selector(gul_class)]); -} - -/** Tests that swizzling a nil object fails. */ -- (void)testSwizzleNil { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - XCTAssertEqual([object class], [NSObject class]); - object = nil; - XCTAssertThrows([objectSwizzler swizzle]); -} - -/** Tests the ability to copy a selector from one class to the swizzled object's generated class. */ -- (void)testCopySelectorFromClassIsClassSelectorAndSwizzle { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [objectSwizzler copySelector:@selector(donorMethod) fromClass:[self class] isClassSelector:NO]; - XCTAssertFalse([object respondsToSelector:@selector(donorMethod)]); - XCTAssertFalse([[object class] instancesRespondToSelector:@selector(donorMethod)]); - [objectSwizzler swizzle]; - XCTAssertTrue([object respondsToSelector:@selector(donorMethod)]); - // [object class] should return the original class, not the swizzled class. - XCTAssertTrue( - [[(GULSwizzledObject *)object gul_class] instancesRespondToSelector:@selector(donorMethod)]); -} - -/** Tests that some helper methods are always added to swizzled objects. */ -- (void)testCommonSelectorsAddedUponSwizzling { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - XCTAssertFalse([object respondsToSelector:@selector(gul_class)]); - [objectSwizzler swizzle]; - XCTAssertTrue([object respondsToSelector:@selector(gul_class)]); -} - -/** Tests that there's no retain cycle and that -dealloc causes unswizzling. */ -- (void)testRetainCycleDoesntExistAndDeallocCausesUnswizzling { - NSObject *object = [[NSObject alloc] init]; - GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [objectSwizzler copySelector:@selector(donorMethod) fromClass:[self class] isClassSelector:NO]; - [objectSwizzler swizzle]; - // If objectSwizzler were used, the strong reference would make it live to the end of this test. - // We want to make sure it dies when the object dies, hence the weak reference. - __weak GULObjectSwizzler *weakObjectSwizzler = objectSwizzler; - objectSwizzler = nil; - XCTAssertNotNil(weakObjectSwizzler); - object = nil; - XCTAssertNil(weakObjectSwizzler); -} - -/** Tests the class get/set associated object methods. */ -- (void)testClassSetAssociatedObjectCopy { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init]; - [GULObjectSwizzler setAssociatedObject:object - key:@"fir_key" - value:objectToBeAssociated - association:GUL_ASSOCIATION_COPY]; - NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"]; - XCTAssertEqualObjects(returnedObject, objectToBeAssociated); -} - -/** Tests the class get/set associated object methods. */ -- (void)testClassSetAssociatedObjectAssign { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init]; - [GULObjectSwizzler setAssociatedObject:object - key:@"fir_key" - value:objectToBeAssociated - association:GUL_ASSOCIATION_ASSIGN]; - NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"]; - XCTAssertEqualObjects(returnedObject, objectToBeAssociated); -} - -/** Tests the class get/set associated object methods. */ -- (void)testClassSetAssociatedObjectRetain { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init]; - [GULObjectSwizzler setAssociatedObject:object - key:@"fir_key" - value:objectToBeAssociated - association:GUL_ASSOCIATION_RETAIN]; - NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"]; - XCTAssertEqualObjects(returnedObject, objectToBeAssociated); -} - -/** Tests the class get/set associated object methods. */ -- (void)testClassSetAssociatedObjectCopyNonatomic { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init]; - [GULObjectSwizzler setAssociatedObject:object - key:@"fir_key" - value:objectToBeAssociated - association:GUL_ASSOCIATION_COPY_NONATOMIC]; - NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"]; - XCTAssertEqualObjects(returnedObject, objectToBeAssociated); -} - -/** Tests the class get/set associated object methods. */ -- (void)testClassSetAssociatedObjectRetainNonatomic { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init]; - [GULObjectSwizzler setAssociatedObject:object - key:@"fir_key" - value:objectToBeAssociated - association:GUL_ASSOCIATION_RETAIN_NONATOMIC]; - NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"]; - XCTAssertEqualObjects(returnedObject, objectToBeAssociated); -} - -/** Tests the swizzler get/set associated object methods. */ -- (void)testSetGetAssociatedObjectCopy { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" - value:associatedObject - association:GUL_ASSOCIATION_COPY]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests the swizzler get/set associated object methods. */ -- (void)testSetGetAssociatedObjectAssign { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" - value:associatedObject - association:GUL_ASSOCIATION_ASSIGN]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests the swizzler get/set associated object methods. */ -- (void)testSetGetAssociatedObjectRetain { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" - value:associatedObject - association:GUL_ASSOCIATION_RETAIN]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests the swizzler get/set associated object methods. */ -- (void)testSetGetAssociatedObjectCopyNonatomic { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" - value:associatedObject - association:GUL_ASSOCIATION_COPY_NONATOMIC]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests the swizzler get/set associated object methods. */ -- (void)testSetGetAssociatedObjectRetainNonatomic { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" - value:associatedObject - association:GUL_ASSOCIATION_RETAIN_NONATOMIC]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests getting and setting an associated object with an invalid association type. */ -- (void)testSetGetAssociatedObjectWithoutProperAssociation { - NSObject *object = [[NSObject alloc] init]; - NSDictionary *associatedObject = [[NSDictionary alloc] init]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - [swizzler setAssociatedObjectWithKey:@"key" value:associatedObject association:1337]; - NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"]; - XCTAssertEqualObjects(returnedObject, associatedObject); -} - -/** Tests using the GULObjectSwizzler to swizzle an object wrapped in an NSProxy. */ -- (void)testSwizzleProxiedObject { - NSObject *object = [[NSObject alloc] init]; - GULProxy *proxyObject = [GULProxy proxyWithDelegate:object]; - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject]; - - XCTAssertNoThrow([swizzler swizzle]); - - XCTAssertNotEqual(object_getClass(proxyObject), [GULProxy class]); - XCTAssertTrue([object_getClass(proxyObject) isSubclassOfClass:[GULProxy class]]); - - XCTAssertTrue([proxyObject respondsToSelector:@selector(gul_objectSwizzler)]); - XCTAssertNoThrow([proxyObject performSelector:@selector(gul_objectSwizzler)]); - - XCTAssertTrue([proxyObject respondsToSelector:@selector(gul_class)]); - XCTAssertNoThrow([proxyObject performSelector:@selector(gul_class)]); -} - -/** Tests overriding a method that already exists on a proxied object works as expected. */ -- (void)testSwizzleProxiedObjectInvokesInjectedMethodWhenOverridingMethod { - NSObject *object = [[NSObject alloc] init]; - GULProxy *proxyObject = [GULProxy proxyWithDelegate:object]; - - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject]; - [swizzler copySelector:@selector(description) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - - XCTAssertEqual([proxyObject performSelector:@selector(description)], @"SwizzledDescription"); -} - -/** Tests adding a method that doesn't exist on a proxied object works as expected. */ -- (void)testSwizzleProxiedObjectInvokesInjectedMethodWhenAddingMethod { - NSObject *object = [[NSObject alloc] init]; - GULProxy *proxyObject = [GULProxy proxyWithDelegate:object]; - - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject]; - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - - XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)], - @"SwizzledDonorDescription"); -} - -/** Tests KVOing a proxy object that we've ISA Swizzled works as expected. */ -- (void)testRespondsToSelectorWorksEvenIfSwizzledProxyIsKVOd { - NSObject *object = [[NSObject alloc] init]; - GULProxy *proxyObject = [GULProxy proxyWithDelegate:object]; - - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject]; - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - - [(NSObject *)proxyObject addObserver:self - forKeyPath:NSStringFromSelector(@selector(description)) - options:0 - context:NULL]; - - XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]); - XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)], - @"SwizzledDonorDescription"); - - [(NSObject *)proxyObject removeObserver:self - forKeyPath:NSStringFromSelector(@selector(description))]; -} - -// TODO: Investigate why this test fails in Swift PM build. - -/** Tests that -[NSObjectProtocol respondsToSelector:] works as expected after someone else ISA - * swizzles a proxy object that we've also ISA Swizzled. - */ -- (void)testRespondsToSelectorWorksEvenIfSwizzledProxyISASwizzledBySomeoneElse { - Class generatedClass = nil; - __weak GULObjectSwizzler *weakSwizzler; - - @autoreleasepool { - NSObject *object = [[NSObject alloc] init]; - GULProxy *proxyObject = [GULProxy proxyWithDelegate:object]; - - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject]; - weakSwizzler = swizzler; - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - - // Someone else ISA Swizzles the same object after GULObjectSwizzler. - Class originalClass = object_getClass(proxyObject); - NSString *newClassName = [NSString - stringWithFormat:@"gul_test_%p_%@", proxyObject, NSStringFromClass(originalClass)]; - generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0); - objc_registerClassPair(generatedClass); - object_setClass(proxyObject, generatedClass); - - XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]); - XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)], - @"SwizzledDonorDescription"); - - // Release GULObjectSwizzler - [GULObjectSwizzler setAssociatedObject:proxyObject - key:kGULSwizzlerAssociatedObjectKey - value:nil - association:GUL_ASSOCIATION_RETAIN]; - } - XCTAssertNil(weakSwizzler); - - // Clean up. - objc_disposeClassPair(generatedClass); -} - -#if !TARGET_OS_MACCATALYST -// Test fails on Catalyst due to an interaction with GULSceneDelegateSwizzlerTests. - -- (void)testSwizzlerDoesntDisposeGeneratedClassWhenObjectIsISASwizzledBySomeoneElse { - Class generatedClass = nil; - __weak GULObjectSwizzler *weakSwizzler; - - XCTestExpectation *swizzlerDeallocatedExpectation = - [self expectationWithDescription:@"swizzlerDeallocatedExpectation"]; - - @autoreleasepool { - NSObject *object = [[NSObject alloc] init]; - - @autoreleasepool { - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - weakSwizzler = swizzler; - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - } - - // Someone else ISA Swizzles the same object after GULObjectSwizzler. - Class originalClass = object_getClass(object); - NSString *newClassName = - [NSString stringWithFormat:@"gul_test_%p_%@", object, NSStringFromClass(originalClass)]; - generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0); - objc_registerClassPair(generatedClass); - object_setClass(object, generatedClass); - - // Release GULObjectSwizzler - [GULObjectSwizzler setAssociatedObject:object - key:kGULSwizzlerAssociatedObjectKey - value:nil - association:GUL_ASSOCIATION_RETAIN]; - - // Wait for a while - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - [swizzlerDeallocatedExpectation fulfill]; - }); - - [self waitForExpectations:@[ swizzlerDeallocatedExpectation ] timeout:2]; - - XCTAssertNil(weakSwizzler); - // A class generated by GULObjectSwizzler must not be disposed if there is its subclass. - XCTAssertNoThrow([generatedClass description]); - } - - // Clean up. - objc_disposeClassPair(generatedClass); -} -#endif - -// The test is disabled because in the case of success it should crash with SIGABRT, so it is not -// suitable for CI. -- (void)disabledForCI_testSwizzlerDisposesGeneratedClass { - __weak GULObjectSwizzler *weakSwizzler; - - XCTestExpectation *swizzlerDeallocatedExpectation = - [self expectationWithDescription:@"swizzlerDeallocatedExpectation"]; - - @autoreleasepool { - NSObject *object = [[NSObject alloc] init]; - - @autoreleasepool { - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - weakSwizzler = swizzler; - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - } - - // Release GULObjectSwizzler - [GULObjectSwizzler setAssociatedObject:object - key:kGULSwizzlerAssociatedObjectKey - value:nil - association:GUL_ASSOCIATION_RETAIN]; - - // Wait for a while until GULObjectSwizzler has disposed the generated class. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - [swizzlerDeallocatedExpectation fulfill]; - }); - - [self waitForExpectations:@[ swizzlerDeallocatedExpectation ] timeout:2]; - - XCTAssertNil(weakSwizzler); - - // Must crash here with SIGABRT. - XCTAssertThrows([object description]); - XCTFail(@"The test must have crashed on the previous line."); - } -} - -- (void)testMultiSwizzling { - NSObject *object = [[NSObject alloc] init]; - - __weak GULObjectSwizzler *existingSwizzler; - - // Use @autoreleasepool to make the memory management in the test more deterministic. - @autoreleasepool { - NSInteger swizzleCount = 10; - for (NSInteger i = 0; i < swizzleCount; i++) { - GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object]; - - if (i > 0) { - XCTAssertEqualObjects(swizzler, existingSwizzler, - @"There must be a single swizzler per object."); - } else { - existingSwizzler = swizzler; - } - - [swizzler copySelector:@selector(donorDescription) - fromClass:[GULObjectSwizzlerTest class] - isClassSelector:NO]; - [swizzler swizzle]; - } - - XCTAssertNoThrow([object performSelector:@selector(donorDescription)]); - object = nil; - } - - XCTAssertNil(existingSwizzler, - @"GULObjectSwizzler must be deallocated after the object deallocation."); -} - -@end diff --git a/Package.swift b/Package.swift index 0afdba44..797efd91 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. // Copyright 2021 Google LLC @@ -19,7 +19,7 @@ import PackageDescription let package = Package( name: "GoogleUtilities", - platforms: [.iOS(.v9), .macOS(.v10_12), .tvOS(.v10), .watchOS(.v6)], + platforms: [.iOS(.v12), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v7)], products: [ .library( name: "GULAppDelegateSwizzler", @@ -33,10 +33,6 @@ let package = Package( name: "GULLogger", targets: ["GoogleUtilities-Logger"] ), - .library( - name: "GULISASwizzler", - targets: ["GoogleUtilities-ISASwizzler"] - ), .library( name: "GULMethodSwizzler", targets: ["GoogleUtilities-MethodSwizzler"] @@ -63,11 +59,12 @@ let package = Package( ), ], dependencies: [ - .package(name: "Promises", url: "https://github.com/google/promises.git", "1.2.8" ..< "3.0.0"), + .package(url: "https://github.com/google/promises.git", "1.2.8" ..< "3.0.0"), + // TODO: restore OCMock when https://github.com/erikdoe/ocmock/pull/537 + // gets merged to fix Xcode 15.3 builds. .package( - name: "OCMock", - url: "https://github.com/erikdoe/ocmock.git", - .revision("c5eeaa6dde7c308a5ce48ae4d4530462dd3a1110") + url: "https://github.com/paulb777/ocmock.git", + revision: "173955e93e6ee6999a10729ab67e4b4efdd1db6d" ), ], targets: [ @@ -87,7 +84,6 @@ let package = Package( .target( name: "GoogleUtilities-Environment", dependencies: [ - .product(name: "FBLPromises", package: "Promises"), "third-party-IsAppEncrypted", ], path: "GoogleUtilities/Environment", @@ -119,17 +115,6 @@ let package = Package( ] ), - .target( - name: "GoogleUtilities-ISASwizzler", - dependencies: ["GoogleUtilities-Logger"], - path: "GoogleUtilities/ISASwizzler", - resources: [.process("Resources/PrivacyInfo.xcprivacy")], - publicHeadersPath: "Public", - cSettings: [ - .headerSearchPath("../../"), - ] - ), - .target( name: "GoogleUtilities-MethodSwizzler", dependencies: ["GoogleUtilities-Logger"], @@ -199,7 +184,6 @@ let package = Package( dependencies: [ "GoogleUtilities-AppDelegateSwizzler", "GoogleUtilities-Environment", - "GoogleUtilities-ISASwizzler", "GoogleUtilities-Logger", "GoogleUtilities-MethodSwizzler", "GoogleUtilities-Network", @@ -214,7 +198,6 @@ let package = Package( dependencies: [ "GoogleUtilities-AppDelegateSwizzler", "GoogleUtilities-Environment", - "GoogleUtilities-ISASwizzler", "GoogleUtilities-Logger", "GoogleUtilities-MethodSwizzler", "GoogleUtilities-Network", @@ -228,10 +211,9 @@ let package = Package( .testTarget( name: "UtilitiesUnit", dependencies: [ - "OCMock", + .product(name: "OCMock", package: "OCMock"), "GoogleUtilities-AppDelegateSwizzler", "GoogleUtilities-Environment", - "GoogleUtilities-ISASwizzler", "GoogleUtilities-Logger", "GoogleUtilities-MethodSwizzler", "GoogleUtilities-Network", diff --git a/SwiftPMTests/objc-import-test/objc-header.m b/SwiftPMTests/objc-import-test/objc-header.m index a3eb18ea..3fc08703 100644 --- a/SwiftPMTests/objc-import-test/objc-header.m +++ b/SwiftPMTests/objc-import-test/objc-header.m @@ -17,7 +17,6 @@ #import "GoogleUtilities/GULLogger.h" #import "GoogleUtilities/GULNSData+zlib.h" #import "GoogleUtilities/GULNetwork.h" -#import "GoogleUtilities/GULObjectSwizzler.h" #import "GoogleUtilities/GULReachabilityChecker.h" #import "GoogleUtilities/GULSwizzler.h" #import "GoogleUtilities/GULUserDefaults.h" @@ -27,7 +26,6 @@ #import #import #import -#import #import #import #import diff --git a/SwiftPMTests/objc-import-test/objc-module.m b/SwiftPMTests/objc-import-test/objc-module.m index 110bc2cd..b0d0d507 100644 --- a/SwiftPMTests/objc-import-test/objc-module.m +++ b/SwiftPMTests/objc-import-test/objc-module.m @@ -14,7 +14,6 @@ @import GoogleUtilities_AppDelegateSwizzler; @import GoogleUtilities_Environment; -@import GoogleUtilities_ISASwizzler; @import GoogleUtilities_Logger; @import GoogleUtilities_MethodSwizzler; @import GoogleUtilities_Network;