Repository: AfrazCodes/Spotify-iOS Branch: master Commit: 03895df1d28c Files: 661 Total size: 34.4 MB Directory structure: gitextract_bk3gpcyp/ ├── Podfile ├── Pods/ │ ├── Appirater/ │ │ ├── Appirater.h │ │ ├── Appirater.m │ │ ├── AppiraterDelegate.h │ │ ├── README.md │ │ ├── ar.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ca.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── cs.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── da.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── de.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── el.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── en.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── es.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── fa.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── fi.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── fr.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── he.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── hu.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── hy.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── id.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── it.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ja.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ko.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ms.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── nb.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── nl.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── pl.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── pt-BR.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── pt.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ro.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── ru.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── sk.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── sv.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── th.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── tr.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── uk.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── vi.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ ├── zh-Hans.lproj/ │ │ │ └── AppiraterLocalizable.strings │ │ └── zh-Hant.lproj/ │ │ └── AppiraterLocalizable.strings │ ├── Firebase/ │ │ ├── CoreOnly/ │ │ │ └── Sources/ │ │ │ ├── Firebase.h │ │ │ └── module.modulemap │ │ ├── LICENSE │ │ └── README.md │ ├── FirebaseAnalytics/ │ │ └── Frameworks/ │ │ └── FirebaseAnalytics.xcframework/ │ │ ├── Info.plist │ │ ├── ios-arm64_armv7/ │ │ │ └── FirebaseAnalytics.framework/ │ │ │ ├── FirebaseAnalytics │ │ │ ├── Headers/ │ │ │ │ ├── FIRAnalytics+AppDelegate.h │ │ │ │ ├── FIRAnalytics+Consent.h │ │ │ │ ├── FIRAnalytics.h │ │ │ │ ├── FIREventNames.h │ │ │ │ ├── FIRParameterNames.h │ │ │ │ ├── FIRUserPropertyNames.h │ │ │ │ └── FirebaseAnalytics.h │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ └── ios-arm64_i386_x86_64-simulator/ │ │ └── FirebaseAnalytics.framework/ │ │ ├── FirebaseAnalytics │ │ ├── Headers/ │ │ │ ├── FIRAnalytics+AppDelegate.h │ │ │ ├── FIRAnalytics+Consent.h │ │ │ ├── FIRAnalytics.h │ │ │ ├── FIREventNames.h │ │ │ ├── FIRParameterNames.h │ │ │ ├── FIRUserPropertyNames.h │ │ │ └── FirebaseAnalytics.h │ │ ├── Info.plist │ │ └── Modules/ │ │ └── module.modulemap │ ├── FirebaseCore/ │ │ ├── FirebaseCore/ │ │ │ └── Sources/ │ │ │ ├── FIRAnalyticsConfiguration.h │ │ │ ├── FIRAnalyticsConfiguration.m │ │ │ ├── FIRApp.m │ │ │ ├── FIRAppAssociationRegistration.h │ │ │ ├── FIRAppAssociationRegistration.m │ │ │ ├── FIRBundleUtil.h │ │ │ ├── FIRBundleUtil.m │ │ │ ├── FIRComponent.m │ │ │ ├── FIRComponentContainer.m │ │ │ ├── FIRComponentContainerInternal.h │ │ │ ├── FIRComponentType.m │ │ │ ├── FIRConfiguration.m │ │ │ ├── FIRConfigurationInternal.h │ │ │ ├── FIRCoreDiagnosticsConnector.m │ │ │ ├── FIRDependency.m │ │ │ ├── FIRDiagnosticsData.h │ │ │ ├── FIRDiagnosticsData.m │ │ │ ├── FIRFirebaseUserAgent.h │ │ │ ├── FIRFirebaseUserAgent.m │ │ │ ├── FIRHeartbeatInfo.m │ │ │ ├── FIRLogger.m │ │ │ ├── FIROptions.m │ │ │ ├── FIRVersion.m │ │ │ ├── Private/ │ │ │ │ ├── FIRAppInternal.h │ │ │ │ ├── FIRComponent.h │ │ │ │ ├── FIRComponentContainer.h │ │ │ │ ├── FIRComponentType.h │ │ │ │ ├── FIRCoreDiagnosticsConnector.h │ │ │ │ ├── FIRDependency.h │ │ │ │ ├── FIRHeartbeatInfo.h │ │ │ │ ├── FIRLibrary.h │ │ │ │ ├── FIRLogger.h │ │ │ │ ├── FIROptionsInternal.h │ │ │ │ └── FirebaseCoreInternal.h │ │ │ └── Public/ │ │ │ └── FirebaseCore/ │ │ │ ├── FIRApp.h │ │ │ ├── FIRConfiguration.h │ │ │ ├── FIRLoggerLevel.h │ │ │ ├── FIROptions.h │ │ │ ├── FIRVersion.h │ │ │ └── FirebaseCore.h │ │ ├── Interop/ │ │ │ └── CoreDiagnostics/ │ │ │ └── Public/ │ │ │ ├── FIRCoreDiagnosticsData.h │ │ │ └── FIRCoreDiagnosticsInterop.h │ │ ├── LICENSE │ │ └── README.md │ ├── FirebaseCoreDiagnostics/ │ │ ├── Firebase/ │ │ │ └── CoreDiagnostics/ │ │ │ └── FIRCDLibrary/ │ │ │ ├── FIRCoreDiagnostics.m │ │ │ ├── Protogen/ │ │ │ │ └── nanopb/ │ │ │ │ ├── firebasecore.nanopb.c │ │ │ │ └── firebasecore.nanopb.h │ │ │ └── Public/ │ │ │ └── FIRCoreDiagnostics.h │ │ ├── Interop/ │ │ │ └── CoreDiagnostics/ │ │ │ └── Public/ │ │ │ ├── FIRCoreDiagnosticsData.h │ │ │ └── FIRCoreDiagnosticsInterop.h │ │ ├── LICENSE │ │ └── README.md │ ├── FirebaseInstallations/ │ │ ├── FirebaseCore/ │ │ │ └── Sources/ │ │ │ └── Private/ │ │ │ ├── FIRAppInternal.h │ │ │ ├── FIRComponent.h │ │ │ ├── FIRComponentContainer.h │ │ │ ├── FIRComponentType.h │ │ │ ├── FIRCoreDiagnosticsConnector.h │ │ │ ├── FIRDependency.h │ │ │ ├── FIRHeartbeatInfo.h │ │ │ ├── FIRLibrary.h │ │ │ ├── FIRLogger.h │ │ │ ├── FIROptionsInternal.h │ │ │ └── FirebaseCoreInternal.h │ │ ├── FirebaseInstallations/ │ │ │ └── Source/ │ │ │ └── Library/ │ │ │ ├── Errors/ │ │ │ │ ├── FIRInstallationsErrorUtil.h │ │ │ │ ├── FIRInstallationsErrorUtil.m │ │ │ │ ├── FIRInstallationsHTTPError.h │ │ │ │ └── FIRInstallationsHTTPError.m │ │ │ ├── FIRInstallations.m │ │ │ ├── FIRInstallationsAuthTokenResult.m │ │ │ ├── FIRInstallationsAuthTokenResultInternal.h │ │ │ ├── FIRInstallationsItem.h │ │ │ ├── FIRInstallationsItem.m │ │ │ ├── FIRInstallationsLogger.h │ │ │ ├── FIRInstallationsLogger.m │ │ │ ├── IIDMigration/ │ │ │ │ ├── FIRInstallationsIIDStore.h │ │ │ │ ├── FIRInstallationsIIDStore.m │ │ │ │ ├── FIRInstallationsIIDTokenStore.h │ │ │ │ └── FIRInstallationsIIDTokenStore.m │ │ │ ├── InstallationsAPI/ │ │ │ │ ├── FIRInstallationsAPIService.h │ │ │ │ ├── FIRInstallationsAPIService.m │ │ │ │ ├── FIRInstallationsItem+RegisterInstallationAPI.h │ │ │ │ └── FIRInstallationsItem+RegisterInstallationAPI.m │ │ │ ├── InstallationsIDController/ │ │ │ │ ├── FIRCurrentDateProvider.h │ │ │ │ ├── FIRCurrentDateProvider.m │ │ │ │ ├── FIRInstallationsBackoffController.h │ │ │ │ ├── FIRInstallationsBackoffController.m │ │ │ │ ├── FIRInstallationsIDController.h │ │ │ │ ├── FIRInstallationsIDController.m │ │ │ │ ├── FIRInstallationsSingleOperationPromiseCache.h │ │ │ │ ├── FIRInstallationsSingleOperationPromiseCache.m │ │ │ │ └── FIRInstallationsStatus.h │ │ │ ├── InstallationsStore/ │ │ │ │ ├── FIRInstallationsStore.h │ │ │ │ ├── FIRInstallationsStore.m │ │ │ │ ├── FIRInstallationsStoredAuthToken.h │ │ │ │ ├── FIRInstallationsStoredAuthToken.m │ │ │ │ ├── FIRInstallationsStoredItem.h │ │ │ │ └── FIRInstallationsStoredItem.m │ │ │ ├── Private/ │ │ │ │ └── FirebaseInstallationsInternal.h │ │ │ └── Public/ │ │ │ └── FirebaseInstallations/ │ │ │ ├── FIRInstallations.h │ │ │ ├── FIRInstallationsAuthTokenResult.h │ │ │ ├── FIRInstallationsErrors.h │ │ │ └── FirebaseInstallations.h │ │ ├── LICENSE │ │ └── README.md │ ├── GoogleAppMeasurement/ │ │ └── Frameworks/ │ │ └── GoogleAppMeasurement.xcframework/ │ │ ├── Info.plist │ │ ├── ios-arm64_armv7/ │ │ │ └── GoogleAppMeasurement.framework/ │ │ │ ├── GoogleAppMeasurement │ │ │ ├── Info.plist │ │ │ └── Modules/ │ │ │ └── module.modulemap │ │ └── ios-arm64_i386_x86_64-simulator/ │ │ └── GoogleAppMeasurement.framework/ │ │ ├── GoogleAppMeasurement │ │ ├── Info.plist │ │ └── Modules/ │ │ └── module.modulemap │ ├── GoogleDataTransport/ │ │ ├── GoogleDataTransport/ │ │ │ ├── GDTCCTLibrary/ │ │ │ │ ├── GDTCCTCompressionHelper.m │ │ │ │ ├── GDTCCTNanopbHelpers.m │ │ │ │ ├── GDTCCTUploadOperation.m │ │ │ │ ├── GDTCCTUploader.m │ │ │ │ ├── GDTCOREvent+GDTCCTSupport.m │ │ │ │ ├── Private/ │ │ │ │ │ ├── GDTCCTCompressionHelper.h │ │ │ │ │ ├── GDTCCTNanopbHelpers.h │ │ │ │ │ ├── GDTCCTUploadOperation.h │ │ │ │ │ └── GDTCCTUploader.h │ │ │ │ ├── Protogen/ │ │ │ │ │ └── nanopb/ │ │ │ │ │ ├── cct.nanopb.c │ │ │ │ │ └── cct.nanopb.h │ │ │ │ └── Public/ │ │ │ │ └── GDTCOREvent+GDTCCTSupport.h │ │ │ └── GDTCORLibrary/ │ │ │ ├── GDTCORAssert.m │ │ │ ├── GDTCORClock.m │ │ │ ├── GDTCORConsoleLogger.m │ │ │ ├── GDTCORDirectorySizeTracker.m │ │ │ ├── GDTCOREndpoints.m │ │ │ ├── GDTCOREvent.m │ │ │ ├── GDTCORFlatFileStorage+Promises.m │ │ │ ├── GDTCORFlatFileStorage.m │ │ │ ├── GDTCORLifecycle.m │ │ │ ├── GDTCORPlatform.m │ │ │ ├── GDTCORReachability.m │ │ │ ├── GDTCORRegistrar.m │ │ │ ├── GDTCORStorageEventSelector.m │ │ │ ├── GDTCORTransformer.m │ │ │ ├── GDTCORTransport.m │ │ │ ├── GDTCORUploadBatch.m │ │ │ ├── GDTCORUploadCoordinator.m │ │ │ ├── Internal/ │ │ │ │ ├── GDTCORAssert.h │ │ │ │ ├── GDTCORDirectorySizeTracker.h │ │ │ │ ├── GDTCORLifecycle.h │ │ │ │ ├── GDTCORPlatform.h │ │ │ │ ├── GDTCORReachability.h │ │ │ │ ├── GDTCORRegistrar.h │ │ │ │ ├── GDTCORStorageEventSelector.h │ │ │ │ ├── GDTCORStorageProtocol.h │ │ │ │ └── GDTCORUploader.h │ │ │ ├── Private/ │ │ │ │ ├── GDTCOREndpoints_Private.h │ │ │ │ ├── GDTCOREvent_Private.h │ │ │ │ ├── GDTCORFlatFileStorage+Promises.h │ │ │ │ ├── GDTCORFlatFileStorage.h │ │ │ │ ├── GDTCORReachability_Private.h │ │ │ │ ├── GDTCORRegistrar_Private.h │ │ │ │ ├── GDTCORTransformer.h │ │ │ │ ├── GDTCORTransformer_Private.h │ │ │ │ ├── GDTCORTransport_Private.h │ │ │ │ ├── GDTCORUploadBatch.h │ │ │ │ └── GDTCORUploadCoordinator.h │ │ │ └── Public/ │ │ │ └── GoogleDataTransport/ │ │ │ ├── GDTCORClock.h │ │ │ ├── GDTCORConsoleLogger.h │ │ │ ├── GDTCOREndpoints.h │ │ │ ├── GDTCOREvent.h │ │ │ ├── GDTCOREventDataObject.h │ │ │ ├── GDTCOREventTransformer.h │ │ │ ├── GDTCORTargets.h │ │ │ ├── GDTCORTransport.h │ │ │ └── GoogleDataTransport.h │ │ ├── LICENSE │ │ └── README.md │ ├── GoogleUtilities/ │ │ ├── GoogleUtilities/ │ │ │ ├── AppDelegateSwizzler/ │ │ │ │ ├── GULAppDelegateSwizzler.m │ │ │ │ ├── GULSceneDelegateSwizzler.m │ │ │ │ ├── Internal/ │ │ │ │ │ ├── GULAppDelegateSwizzler_Private.h │ │ │ │ │ └── GULSceneDelegateSwizzler_Private.h │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ ├── GULAppDelegateSwizzler.h │ │ │ │ ├── GULApplication.h │ │ │ │ └── GULSceneDelegateSwizzler.h │ │ │ ├── Common/ │ │ │ │ └── GULLoggerCodes.h │ │ │ ├── Environment/ │ │ │ │ ├── GULHeartbeatDateStorage.m │ │ │ │ ├── GULHeartbeatDateStorageUserDefaults.m │ │ │ │ ├── GULSecureCoding.m │ │ │ │ ├── Public/ │ │ │ │ │ └── GoogleUtilities/ │ │ │ │ │ ├── GULAppEnvironmentUtil.h │ │ │ │ │ ├── GULHeartbeatDateStorable.h │ │ │ │ │ ├── GULHeartbeatDateStorage.h │ │ │ │ │ ├── GULHeartbeatDateStorageUserDefaults.h │ │ │ │ │ ├── GULKeychainStorage.h │ │ │ │ │ ├── GULKeychainUtils.h │ │ │ │ │ ├── GULSecureCoding.h │ │ │ │ │ ├── GULURLSessionDataResponse.h │ │ │ │ │ └── NSURLSession+GULPromises.h │ │ │ │ ├── SecureStorage/ │ │ │ │ │ ├── GULKeychainStorage.m │ │ │ │ │ └── GULKeychainUtils.m │ │ │ │ ├── URLSessionPromiseWrapper/ │ │ │ │ │ ├── GULURLSessionDataResponse.m │ │ │ │ │ └── NSURLSession+GULPromises.m │ │ │ │ └── third_party/ │ │ │ │ └── GULAppEnvironmentUtil.m │ │ │ ├── Logger/ │ │ │ │ ├── GULLogger.m │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ ├── GULLogger.h │ │ │ │ └── GULLoggerLevel.h │ │ │ ├── MethodSwizzler/ │ │ │ │ ├── GULSwizzler.m │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ ├── GULOriginalIMPConvenienceMacros.h │ │ │ │ └── GULSwizzler.h │ │ │ ├── NSData+zlib/ │ │ │ │ ├── GULNSData+zlib.m │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ └── GULNSData+zlib.h │ │ │ ├── Network/ │ │ │ │ ├── GULMutableDictionary.m │ │ │ │ ├── GULNetwork.m │ │ │ │ ├── GULNetworkConstants.m │ │ │ │ ├── GULNetworkInternal.h │ │ │ │ ├── GULNetworkURLSession.m │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ ├── GULMutableDictionary.h │ │ │ │ ├── GULNetwork.h │ │ │ │ ├── GULNetworkConstants.h │ │ │ │ ├── GULNetworkLoggerProtocol.h │ │ │ │ ├── GULNetworkMessageCode.h │ │ │ │ └── GULNetworkURLSession.h │ │ │ ├── Reachability/ │ │ │ │ ├── GULReachabilityChecker+Internal.h │ │ │ │ ├── GULReachabilityChecker.m │ │ │ │ ├── GULReachabilityMessageCode.h │ │ │ │ └── Public/ │ │ │ │ └── GoogleUtilities/ │ │ │ │ └── GULReachabilityChecker.h │ │ │ └── UserDefaults/ │ │ │ ├── GULUserDefaults.m │ │ │ └── Public/ │ │ │ └── GoogleUtilities/ │ │ │ └── GULUserDefaults.h │ │ ├── LICENSE │ │ └── README.md │ ├── Pods.xcodeproj/ │ │ ├── project.pbxproj │ │ └── xcuserdata/ │ │ └── afrazsiddiqui.xcuserdatad/ │ │ └── xcschemes/ │ │ ├── Appirater-Appirater.xcscheme │ │ ├── Appirater.xcscheme │ │ ├── Firebase.xcscheme │ │ ├── FirebaseAnalytics.xcscheme │ │ ├── FirebaseCore.xcscheme │ │ ├── FirebaseCoreDiagnostics.xcscheme │ │ ├── FirebaseInstallations.xcscheme │ │ ├── GoogleAppMeasurement.xcscheme │ │ ├── GoogleDataTransport.xcscheme │ │ ├── GoogleUtilities.xcscheme │ │ ├── Pods-Spotify.xcscheme │ │ ├── PromisesObjC.xcscheme │ │ ├── SDWebImage.xcscheme │ │ ├── nanopb.xcscheme │ │ └── xcschememanagement.plist │ ├── PromisesObjC/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ └── FBLPromises/ │ │ ├── FBLPromise+All.m │ │ ├── FBLPromise+Always.m │ │ ├── FBLPromise+Any.m │ │ ├── FBLPromise+Async.m │ │ ├── FBLPromise+Await.m │ │ ├── FBLPromise+Catch.m │ │ ├── FBLPromise+Delay.m │ │ ├── FBLPromise+Do.m │ │ ├── FBLPromise+Race.m │ │ ├── FBLPromise+Recover.m │ │ ├── FBLPromise+Reduce.m │ │ ├── FBLPromise+Retry.m │ │ ├── FBLPromise+Testing.m │ │ ├── FBLPromise+Then.m │ │ ├── FBLPromise+Timeout.m │ │ ├── FBLPromise+Validate.m │ │ ├── FBLPromise+Wrap.m │ │ ├── FBLPromise.m │ │ ├── FBLPromiseError.m │ │ └── include/ │ │ ├── FBLPromise+All.h │ │ ├── FBLPromise+Always.h │ │ ├── FBLPromise+Any.h │ │ ├── FBLPromise+Async.h │ │ ├── FBLPromise+Await.h │ │ ├── FBLPromise+Catch.h │ │ ├── FBLPromise+Delay.h │ │ ├── FBLPromise+Do.h │ │ ├── FBLPromise+Race.h │ │ ├── FBLPromise+Recover.h │ │ ├── FBLPromise+Reduce.h │ │ ├── FBLPromise+Retry.h │ │ ├── FBLPromise+Testing.h │ │ ├── FBLPromise+Then.h │ │ ├── FBLPromise+Timeout.h │ │ ├── FBLPromise+Validate.h │ │ ├── FBLPromise+Wrap.h │ │ ├── FBLPromise.h │ │ ├── FBLPromiseError.h │ │ ├── FBLPromisePrivate.h │ │ └── FBLPromises.h │ ├── SDWebImage/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SDWebImage/ │ │ │ ├── Core/ │ │ │ │ ├── NSButton+WebCache.h │ │ │ │ ├── NSButton+WebCache.m │ │ │ │ ├── NSData+ImageContentType.h │ │ │ │ ├── NSData+ImageContentType.m │ │ │ │ ├── NSImage+Compatibility.h │ │ │ │ ├── NSImage+Compatibility.m │ │ │ │ ├── SDAnimatedImage.h │ │ │ │ ├── SDAnimatedImage.m │ │ │ │ ├── SDAnimatedImagePlayer.h │ │ │ │ ├── SDAnimatedImagePlayer.m │ │ │ │ ├── SDAnimatedImageRep.h │ │ │ │ ├── SDAnimatedImageRep.m │ │ │ │ ├── SDAnimatedImageView+WebCache.h │ │ │ │ ├── SDAnimatedImageView+WebCache.m │ │ │ │ ├── SDAnimatedImageView.h │ │ │ │ ├── SDAnimatedImageView.m │ │ │ │ ├── SDDiskCache.h │ │ │ │ ├── SDDiskCache.m │ │ │ │ ├── SDGraphicsImageRenderer.h │ │ │ │ ├── SDGraphicsImageRenderer.m │ │ │ │ ├── SDImageAPNGCoder.h │ │ │ │ ├── SDImageAPNGCoder.m │ │ │ │ ├── SDImageAWebPCoder.h │ │ │ │ ├── SDImageAWebPCoder.m │ │ │ │ ├── SDImageCache.h │ │ │ │ ├── SDImageCache.m │ │ │ │ ├── SDImageCacheConfig.h │ │ │ │ ├── SDImageCacheConfig.m │ │ │ │ ├── SDImageCacheDefine.h │ │ │ │ ├── SDImageCacheDefine.m │ │ │ │ ├── SDImageCachesManager.h │ │ │ │ ├── SDImageCachesManager.m │ │ │ │ ├── SDImageCoder.h │ │ │ │ ├── SDImageCoder.m │ │ │ │ ├── SDImageCoderHelper.h │ │ │ │ ├── SDImageCoderHelper.m │ │ │ │ ├── SDImageCodersManager.h │ │ │ │ ├── SDImageCodersManager.m │ │ │ │ ├── SDImageFrame.h │ │ │ │ ├── SDImageFrame.m │ │ │ │ ├── SDImageGIFCoder.h │ │ │ │ ├── SDImageGIFCoder.m │ │ │ │ ├── SDImageGraphics.h │ │ │ │ ├── SDImageGraphics.m │ │ │ │ ├── SDImageHEICCoder.h │ │ │ │ ├── SDImageHEICCoder.m │ │ │ │ ├── SDImageIOAnimatedCoder.h │ │ │ │ ├── SDImageIOAnimatedCoder.m │ │ │ │ ├── SDImageIOCoder.h │ │ │ │ ├── SDImageIOCoder.m │ │ │ │ ├── SDImageLoader.h │ │ │ │ ├── SDImageLoader.m │ │ │ │ ├── SDImageLoadersManager.h │ │ │ │ ├── SDImageLoadersManager.m │ │ │ │ ├── SDImageTransformer.h │ │ │ │ ├── SDImageTransformer.m │ │ │ │ ├── SDMemoryCache.h │ │ │ │ ├── SDMemoryCache.m │ │ │ │ ├── SDWebImageCacheKeyFilter.h │ │ │ │ ├── SDWebImageCacheKeyFilter.m │ │ │ │ ├── SDWebImageCacheSerializer.h │ │ │ │ ├── SDWebImageCacheSerializer.m │ │ │ │ ├── SDWebImageCompat.h │ │ │ │ ├── SDWebImageCompat.m │ │ │ │ ├── SDWebImageDefine.h │ │ │ │ ├── SDWebImageDefine.m │ │ │ │ ├── SDWebImageDownloader.h │ │ │ │ ├── SDWebImageDownloader.m │ │ │ │ ├── SDWebImageDownloaderConfig.h │ │ │ │ ├── SDWebImageDownloaderConfig.m │ │ │ │ ├── SDWebImageDownloaderDecryptor.h │ │ │ │ ├── SDWebImageDownloaderDecryptor.m │ │ │ │ ├── SDWebImageDownloaderOperation.h │ │ │ │ ├── SDWebImageDownloaderOperation.m │ │ │ │ ├── SDWebImageDownloaderRequestModifier.h │ │ │ │ ├── SDWebImageDownloaderRequestModifier.m │ │ │ │ ├── SDWebImageDownloaderResponseModifier.h │ │ │ │ ├── SDWebImageDownloaderResponseModifier.m │ │ │ │ ├── SDWebImageError.h │ │ │ │ ├── SDWebImageError.m │ │ │ │ ├── SDWebImageIndicator.h │ │ │ │ ├── SDWebImageIndicator.m │ │ │ │ ├── SDWebImageManager.h │ │ │ │ ├── SDWebImageManager.m │ │ │ │ ├── SDWebImageOperation.h │ │ │ │ ├── SDWebImageOperation.m │ │ │ │ ├── SDWebImageOptionsProcessor.h │ │ │ │ ├── SDWebImageOptionsProcessor.m │ │ │ │ ├── SDWebImagePrefetcher.h │ │ │ │ ├── SDWebImagePrefetcher.m │ │ │ │ ├── SDWebImageTransition.h │ │ │ │ ├── SDWebImageTransition.m │ │ │ │ ├── UIButton+WebCache.h │ │ │ │ ├── UIButton+WebCache.m │ │ │ │ ├── UIImage+ExtendedCacheData.h │ │ │ │ ├── UIImage+ExtendedCacheData.m │ │ │ │ ├── UIImage+ForceDecode.h │ │ │ │ ├── UIImage+ForceDecode.m │ │ │ │ ├── UIImage+GIF.h │ │ │ │ ├── UIImage+GIF.m │ │ │ │ ├── UIImage+MemoryCacheCost.h │ │ │ │ ├── UIImage+MemoryCacheCost.m │ │ │ │ ├── UIImage+Metadata.h │ │ │ │ ├── UIImage+Metadata.m │ │ │ │ ├── UIImage+MultiFormat.h │ │ │ │ ├── UIImage+MultiFormat.m │ │ │ │ ├── UIImage+Transform.h │ │ │ │ ├── UIImage+Transform.m │ │ │ │ ├── UIImageView+HighlightedWebCache.h │ │ │ │ ├── UIImageView+HighlightedWebCache.m │ │ │ │ ├── UIImageView+WebCache.h │ │ │ │ ├── UIImageView+WebCache.m │ │ │ │ ├── UIView+WebCache.h │ │ │ │ ├── UIView+WebCache.m │ │ │ │ ├── UIView+WebCacheOperation.h │ │ │ │ └── UIView+WebCacheOperation.m │ │ │ └── Private/ │ │ │ ├── NSBezierPath+SDRoundedCorners.h │ │ │ ├── NSBezierPath+SDRoundedCorners.m │ │ │ ├── SDAssociatedObject.h │ │ │ ├── SDAssociatedObject.m │ │ │ ├── SDAsyncBlockOperation.h │ │ │ ├── SDAsyncBlockOperation.m │ │ │ ├── SDDeviceHelper.h │ │ │ ├── SDDeviceHelper.m │ │ │ ├── SDDisplayLink.h │ │ │ ├── SDDisplayLink.m │ │ │ ├── SDFileAttributeHelper.h │ │ │ ├── SDFileAttributeHelper.m │ │ │ ├── SDImageAssetManager.h │ │ │ ├── SDImageAssetManager.m │ │ │ ├── SDImageCachesManagerOperation.h │ │ │ ├── SDImageCachesManagerOperation.m │ │ │ ├── SDImageIOAnimatedCoderInternal.h │ │ │ ├── SDInternalMacros.h │ │ │ ├── SDInternalMacros.m │ │ │ ├── SDWeakProxy.h │ │ │ ├── SDWeakProxy.m │ │ │ ├── SDWebImageTransitionInternal.h │ │ │ ├── SDmetamacros.h │ │ │ ├── UIColor+SDHexString.h │ │ │ └── UIColor+SDHexString.m │ │ └── WebImage/ │ │ └── SDWebImage.h │ ├── Target Support Files/ │ │ ├── Appirater/ │ │ │ ├── Appirater-Info.plist │ │ │ ├── Appirater-dummy.m │ │ │ ├── Appirater-prefix.pch │ │ │ ├── Appirater-umbrella.h │ │ │ ├── Appirater.debug.xcconfig │ │ │ ├── Appirater.modulemap │ │ │ ├── Appirater.release.xcconfig │ │ │ └── ResourceBundle-Appirater-Appirater-Info.plist │ │ ├── Firebase/ │ │ │ ├── Firebase.debug.xcconfig │ │ │ └── Firebase.release.xcconfig │ │ ├── FirebaseAnalytics/ │ │ │ ├── FirebaseAnalytics-xcframeworks-input-files.xcfilelist │ │ │ ├── FirebaseAnalytics-xcframeworks-output-files.xcfilelist │ │ │ ├── FirebaseAnalytics-xcframeworks.sh │ │ │ ├── FirebaseAnalytics.debug.xcconfig │ │ │ └── FirebaseAnalytics.release.xcconfig │ │ ├── FirebaseCore/ │ │ │ ├── FirebaseCore-Info.plist │ │ │ ├── FirebaseCore-dummy.m │ │ │ ├── FirebaseCore-umbrella.h │ │ │ ├── FirebaseCore.debug.xcconfig │ │ │ ├── FirebaseCore.modulemap │ │ │ └── FirebaseCore.release.xcconfig │ │ ├── FirebaseCoreDiagnostics/ │ │ │ ├── FirebaseCoreDiagnostics-Info.plist │ │ │ ├── FirebaseCoreDiagnostics-dummy.m │ │ │ ├── FirebaseCoreDiagnostics-umbrella.h │ │ │ ├── FirebaseCoreDiagnostics.debug.xcconfig │ │ │ ├── FirebaseCoreDiagnostics.modulemap │ │ │ └── FirebaseCoreDiagnostics.release.xcconfig │ │ ├── FirebaseInstallations/ │ │ │ ├── FirebaseInstallations-Info.plist │ │ │ ├── FirebaseInstallations-dummy.m │ │ │ ├── FirebaseInstallations-umbrella.h │ │ │ ├── FirebaseInstallations.debug.xcconfig │ │ │ ├── FirebaseInstallations.modulemap │ │ │ └── FirebaseInstallations.release.xcconfig │ │ ├── GoogleAppMeasurement/ │ │ │ ├── GoogleAppMeasurement-xcframeworks-input-files.xcfilelist │ │ │ ├── GoogleAppMeasurement-xcframeworks-output-files.xcfilelist │ │ │ ├── GoogleAppMeasurement-xcframeworks.sh │ │ │ ├── GoogleAppMeasurement.debug.xcconfig │ │ │ └── GoogleAppMeasurement.release.xcconfig │ │ ├── GoogleDataTransport/ │ │ │ ├── GoogleDataTransport-Info.plist │ │ │ ├── GoogleDataTransport-dummy.m │ │ │ ├── GoogleDataTransport-umbrella.h │ │ │ ├── GoogleDataTransport.debug.xcconfig │ │ │ ├── GoogleDataTransport.modulemap │ │ │ └── GoogleDataTransport.release.xcconfig │ │ ├── GoogleUtilities/ │ │ │ ├── GoogleUtilities-Info.plist │ │ │ ├── GoogleUtilities-dummy.m │ │ │ ├── GoogleUtilities-umbrella.h │ │ │ ├── GoogleUtilities.debug.xcconfig │ │ │ ├── GoogleUtilities.modulemap │ │ │ └── GoogleUtilities.release.xcconfig │ │ ├── Pods-Spotify/ │ │ │ ├── Pods-Spotify-Info.plist │ │ │ ├── Pods-Spotify-acknowledgements.markdown │ │ │ ├── Pods-Spotify-acknowledgements.plist │ │ │ ├── Pods-Spotify-dummy.m │ │ │ ├── Pods-Spotify-frameworks-Debug-input-files.xcfilelist │ │ │ ├── Pods-Spotify-frameworks-Debug-output-files.xcfilelist │ │ │ ├── Pods-Spotify-frameworks-Release-input-files.xcfilelist │ │ │ ├── Pods-Spotify-frameworks-Release-output-files.xcfilelist │ │ │ ├── Pods-Spotify-frameworks.sh │ │ │ ├── Pods-Spotify-umbrella.h │ │ │ ├── Pods-Spotify.debug.xcconfig │ │ │ ├── Pods-Spotify.modulemap │ │ │ └── Pods-Spotify.release.xcconfig │ │ ├── PromisesObjC/ │ │ │ ├── PromisesObjC-Info.plist │ │ │ ├── PromisesObjC-dummy.m │ │ │ ├── PromisesObjC-umbrella.h │ │ │ ├── PromisesObjC.debug.xcconfig │ │ │ ├── PromisesObjC.modulemap │ │ │ └── PromisesObjC.release.xcconfig │ │ ├── SDWebImage/ │ │ │ ├── SDWebImage-Info.plist │ │ │ ├── SDWebImage-dummy.m │ │ │ ├── SDWebImage-prefix.pch │ │ │ ├── SDWebImage-umbrella.h │ │ │ ├── SDWebImage.debug.xcconfig │ │ │ ├── SDWebImage.modulemap │ │ │ └── SDWebImage.release.xcconfig │ │ └── nanopb/ │ │ ├── nanopb-Info.plist │ │ ├── nanopb-dummy.m │ │ ├── nanopb-prefix.pch │ │ ├── nanopb-umbrella.h │ │ ├── nanopb.debug.xcconfig │ │ ├── nanopb.modulemap │ │ └── nanopb.release.xcconfig │ └── nanopb/ │ ├── LICENSE.txt │ ├── README.md │ ├── pb.h │ ├── pb_common.c │ ├── pb_common.h │ ├── pb_decode.c │ ├── pb_decode.h │ ├── pb_encode.c │ └── pb_encode.h ├── README.md ├── Spotify/ │ ├── Controllers/ │ │ ├── Core/ │ │ │ ├── HomeViewController.swift │ │ │ ├── LibraryViewController.swift │ │ │ ├── SearchViewController.swift │ │ │ └── TabBarViewController.swift │ │ └── Other/ │ │ ├── AlbumViewController.swift │ │ ├── AuthViewController.swift │ │ ├── CategoryViewController.swift │ │ ├── Library/ │ │ │ ├── LibraryAlbumsViewController.swift │ │ │ └── LibraryPlaylistsViewController.swift │ │ ├── PlayerViewController.swift │ │ ├── PlaylistViewController.swift │ │ ├── ProfileViewController.swift │ │ ├── SearchResultsViewController.swift │ │ ├── SettingsViewController.swift │ │ └── WelcomeViewController.swift │ ├── Info.plist │ ├── Managers/ │ │ ├── APICaller.swift │ │ ├── AuthManager.swift │ │ └── HapticsManager.swift │ ├── Models/ │ │ ├── APIImage.swift │ │ ├── AlbumDetailsResponse.swift │ │ ├── AllCategoriesResponse.swift │ │ ├── Artist.swift │ │ ├── AudioTrack.swift │ │ ├── AuthResponse.swift │ │ ├── FeaturedPlaylistsResponse.swift │ │ ├── LibraryAlbumsResponse.swift │ │ ├── LibraryPlaylistsResponse.swift │ │ ├── NewReleasesResponse.swift │ │ ├── Playlist.swift │ │ ├── PlaylistDetailsResponse.swift │ │ ├── RecommendationsResponse.swift │ │ ├── RecommendedGenresResponse.swift │ │ ├── SearchResult.swift │ │ ├── SearchResultResponse.swift │ │ ├── SettingsModels.swift │ │ └── UserProfile.swift │ ├── Presenter/ │ │ └── PlaybackPresenter.swift │ ├── Resources/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── albums_background.imageset/ │ │ │ │ └── Contents.json │ │ │ └── logo.imageset/ │ │ │ └── Contents.json │ │ ├── Extensions.swift │ │ └── SceneDelegate.swift │ ├── ViewModels/ │ │ ├── AlbumCollectionViewCellViewModel.swift │ │ ├── CategoryCollectionViewCellViewModel.swift │ │ ├── FeaturedPlaylistCellViewModel.swift │ │ ├── NewReleasesCellViewModel.swift │ │ ├── PlaylistHeaderViewViewModel.swift │ │ ├── RecommendedTrackCellViewModel.swift │ │ ├── SearchResultDefaultTableViewCellViewModel.swift │ │ └── SearchResultSubtitleTableViewCellViewModel.swift │ └── Views/ │ ├── ActionLabelView.swift │ ├── AlbumTrackCollectionViewCell.swift │ ├── Base.lproj/ │ │ └── LaunchScreen.storyboard │ ├── Browse/ │ │ ├── FeaturedPlaylistCollectionViewCell.swift │ │ ├── NewReleaseCollectionViewCell.swift │ │ └── RecommendedTrackCollectionViewCell.swift │ ├── GenreCollectionViewCell.swift │ ├── LibraryToggleView.swift │ ├── PlayerControlsView.swift │ ├── PlaylistHeaderCollectionReusableView.swift │ ├── SearchResultCells/ │ │ ├── SearchResultDefaultTableViewCell.swift │ │ └── SearchResultSubtitleTableViewCell.swift │ └── TitleHeaderCollectionReusableView.swift ├── Spotify.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata/ │ └── afrazsiddiqui.xcuserdatad/ │ ├── xcdebugger/ │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes/ │ └── xcschememanagement.plist ├── Spotify.xcworkspace/ │ ├── contents.xcworkspacedata │ ├── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata/ │ └── afrazsiddiqui.xcuserdatad/ │ ├── UserInterfaceState.xcuserstate │ └── xcdebugger/ │ └── Breakpoints_v2.xcbkptlist ├── SpotifyTests/ │ └── SpotifyTests.swift ├── codemagic.yaml └── codemagic.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: Podfile ================================================ # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'Spotify' do use_frameworks! pod 'SDWebImage' pod 'Appirater' pod 'Firebase/Analytics' end ================================================ FILE: Pods/Appirater/Appirater.h ================================================ /* This file is part of Appirater. Copyright (c) 2012, Arash Payan All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Appirater.h * appirater * * Created by Arash Payan on 9/5/09. * http://arashpayan.com * Copyright 2012 Arash Payan. All rights reserved. */ #import #import #import "AppiraterDelegate.h" extern NSString *const kAppiraterFirstUseDate; extern NSString *const kAppiraterUseCount; extern NSString *const kAppiraterSignificantEventCount; extern NSString *const kAppiraterCurrentVersion; extern NSString *const kAppiraterRatedCurrentVersion; extern NSString *const kAppiraterDeclinedToRate; extern NSString *const kAppiraterReminderRequestDate; /*! Your localized app's name. */ #define APPIRATER_LOCALIZED_APP_NAME [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"] /*! Your app's name. */ #define APPIRATER_APP_NAME APPIRATER_LOCALIZED_APP_NAME ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"] /*! This is the message your users will see once they've passed the day+launches threshold. */ #define APPIRATER_LOCALIZED_MESSAGE NSLocalizedStringFromTableInBundle(@"If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!", @"AppiraterLocalizable", [Appirater bundle], nil) #define APPIRATER_MESSAGE [NSString stringWithFormat:APPIRATER_LOCALIZED_MESSAGE, APPIRATER_APP_NAME] /*! This is the title of the message alert that users will see. */ #define APPIRATER_LOCALIZED_MESSAGE_TITLE NSLocalizedStringFromTableInBundle(@"Rate %@", @"AppiraterLocalizable", [Appirater bundle], nil) #define APPIRATER_MESSAGE_TITLE [NSString stringWithFormat:APPIRATER_LOCALIZED_MESSAGE_TITLE, APPIRATER_APP_NAME] /*! The text of the button that rejects reviewing the app. */ #define APPIRATER_CANCEL_BUTTON NSLocalizedStringFromTableInBundle(@"No, Thanks", @"AppiraterLocalizable", [Appirater bundle], nil) /*! Text of button that will send user to app review page. */ #define APPIRATER_LOCALIZED_RATE_BUTTON NSLocalizedStringFromTableInBundle(@"Rate %@", @"AppiraterLocalizable", [Appirater bundle], nil) #define APPIRATER_RATE_BUTTON [NSString stringWithFormat:APPIRATER_LOCALIZED_RATE_BUTTON, APPIRATER_APP_NAME] /*! Text for button to remind the user to review later. */ #define APPIRATER_RATE_LATER NSLocalizedStringFromTableInBundle(@"Remind me later", @"AppiraterLocalizable", [Appirater bundle], nil) @interface Appirater : NSObject /*! UIAlertController for iOS 8 and later, otherwise UIAlertView */ @property(nonatomic, strong) id ratingAlert; @property(nonatomic) BOOL openInAppStore; #if __has_feature(objc_arc_weak) @property(nonatomic, weak) NSObject *delegate; #else @property(nonatomic, unsafe_unretained) NSObject *delegate; #endif /*! Tells Appirater that the app has launched, and on devices that do NOT support multitasking, the 'uses' count will be incremented. You should call this method at the end of your application delegate's application:didFinishLaunchingWithOptions: method. If the app has been used enough to be rated (and enough significant events), you can suppress the rating alert by passing NO for canPromptForRating. The rating alert will simply be postponed until it is called again with YES for canPromptForRating. The rating alert can also be triggered by appEnteredForeground: and userDidSignificantEvent: (as long as you pass YES for canPromptForRating in those methods). */ + (void)appLaunched:(BOOL)canPromptForRating; /*! Tells Appirater that the app was brought to the foreground on multitasking devices. You should call this method from the application delegate's applicationWillEnterForeground: method. If the app has been used enough to be rated (and enough significant events), you can suppress the rating alert by passing NO for canPromptForRating. The rating alert will simply be postponed until it is called again with YES for canPromptForRating. The rating alert can also be triggered by appLaunched: and userDidSignificantEvent: (as long as you pass YES for canPromptForRating in those methods). */ + (void)appEnteredForeground:(BOOL)canPromptForRating; /*! Tells Appirater that the user performed a significant event. A significant event is whatever you want it to be. If you're app is used to make VoIP calls, then you might want to call this method whenever the user places a call. If it's a game, you might want to call this whenever the user beats a level boss. If the user has performed enough significant events and used the app enough, you can suppress the rating alert by passing NO for canPromptForRating. The rating alert will simply be postponed until it is called again with YES for canPromptForRating. The rating alert can also be triggered by appLaunched: and appEnteredForeground: (as long as you pass YES for canPromptForRating in those methods). */ + (void)userDidSignificantEvent:(BOOL)canPromptForRating; /*! Tells Appirater to try and show the prompt (a rating alert). The prompt will be showed if there is connection available, the user hasn't declined to rate or hasn't rated current version. You could call to show the prompt regardless Appirater settings, e.g., in case of some special event in your app. */ + (void)tryToShowPrompt; /*! Tells Appirater to show the prompt (a rating alert). Similar to tryToShowPrompt, but without checks (the prompt is always displayed). Passing false will hide the rate later button on the prompt. The only case where you should call this is if your app has an explicit "Rate this app" command somewhere. This is similar to rateApp, but instead of jumping to the review directly, an intermediary prompt is displayed. */ + (void)forceShowPrompt:(BOOL)displayRateLaterButton; /*! Tells Appirater to open the App Store page where the user can specify a rating for the app. Also records the fact that this has happened, so the user won't be prompted again to rate the app. The only case where you should call this directly is if your app has an explicit "Rate this app" command somewhere. In all other cases, don't worry about calling this -- instead, just call the other functions listed above, and let Appirater handle the bookkeeping of deciding when to ask the user whether to rate the app. */ + (void)rateApp; /*! Tells Appirater to immediately close any open rating modals (e.g. StoreKit rating VCs). */ + (void)closeModal; /*! Asks Appirater if the user has declined to rate; */ - (BOOL)userHasDeclinedToRate; /*! Asks Appirater if the user has rated the current version. Note that this is not a guarantee that the user has actually rated the app in the app store, but they've just clicked the rate button on the Appirater dialog. */ - (BOOL)userHasRatedCurrentVersion; @end @interface Appirater(Configuration) /*! Set your Apple generated software id here. */ + (void) setAppId:(NSString*)appId; /*! Users will need to have the same version of your app installed for this many days before they will be prompted to rate it. */ + (void) setDaysUntilPrompt:(double)value; /*! An example of a 'use' would be if the user launched the app. Bringing the app into the foreground (on devices that support it) would also be considered a 'use'. You tell Appirater about these events using the two methods: [Appirater appLaunched:] [Appirater appEnteredForeground:] Users need to 'use' the same version of the app this many times before before they will be prompted to rate it. */ + (void) setUsesUntilPrompt:(NSInteger)value; /*! A significant event can be anything you want to be in your app. In a telephone app, a significant event might be placing or receiving a call. In a game, it might be beating a level or a boss. This is just another layer of filtering that can be used to make sure that only the most loyal of your users are being prompted to rate you on the app store. If you leave this at a value of -1, then this won't be a criterion used for rating. To tell Appirater that the user has performed a significant event, call the method: [Appirater userDidSignificantEvent:]; */ + (void) setSignificantEventsUntilPrompt:(NSInteger)value; /*! Once the rating alert is presented to the user, they might select 'Remind me later'. This value specifies how long (in days) Appirater will wait before reminding them. */ + (void) setTimeBeforeReminding:(double)value; /*! Set customized title for alert view. */ + (void) setCustomAlertTitle:(NSString *)title; /*! Set customized message for alert view. */ + (void) setCustomAlertMessage:(NSString *)message; /*! Set customized cancel button title for alert view. */ + (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle; /*! Set customized rate button title for alert view. */ + (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle; /*! Set customized rate later button title for alert view. */ + (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle; /*! 'YES' will show the Appirater alert everytime. Useful for testing how your message looks and making sure the link to your app's review page works. */ + (void) setDebug:(BOOL)debug; /*! Set the delegate if you want to know when Appirater does something */ + (void)setDelegate:(id)delegate; /*! Set whether or not Appirater uses animation (currently respected when pushing modal StoreKit rating VCs). */ + (void)setUsesAnimation:(BOOL)animation; /*! If set to YES, Appirater will open App Store link (instead of SKStoreProductViewController on iOS 6). Default YES. */ + (void)setOpenInAppStore:(BOOL)openInAppStore; /*! If set to YES, the main bundle will always be used to load localized strings. Set this to YES if you have provided your own custom localizations in AppiraterLocalizable.strings in your main bundle. Default is NO. */ + (void)setAlwaysUseMainBundle:(BOOL)useMainBundle; @end /*! Methods in this interface are public out of necessity, but may change without notice */ @interface Appirater(Unsafe) /*! The bundle localized strings will be loaded from. */ +(NSBundle *)bundle; @end @interface Appirater(Deprecated) /*! DEPRECATED: While still functional, it's better to use appLaunched:(BOOL)canPromptForRating instead. Calls [Appirater appLaunched:YES]. See appLaunched: for details of functionality. */ + (void)appLaunched __attribute__((deprecated)); /*! DEPRECATED: While still functional, it's better to use tryToShowPrompt instead. Calls [Appirater tryToShowPrompt]. See tryToShowPrompt for details of functionality. */ + (void)showPrompt __attribute__((deprecated)); @end ================================================ FILE: Pods/Appirater/Appirater.m ================================================ /* This file is part of Appirater. Copyright (c) 2012, Arash Payan All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Appirater.m * appirater * * Created by Arash Payan on 9/5/09. * http://arashpayan.com * Copyright 2012 Arash Payan. All rights reserved. */ #import #import #import "Appirater.h" #include #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif NSString *const kAppiraterFirstUseDate = @"kAppiraterFirstUseDate"; NSString *const kAppiraterUseCount = @"kAppiraterUseCount"; NSString *const kAppiraterSignificantEventCount = @"kAppiraterSignificantEventCount"; NSString *const kAppiraterCurrentVersion = @"kAppiraterCurrentVersion"; NSString *const kAppiraterRatedCurrentVersion = @"kAppiraterRatedCurrentVersion"; NSString *const kAppiraterDeclinedToRate = @"kAppiraterDeclinedToRate"; NSString *const kAppiraterReminderRequestDate = @"kAppiraterReminderRequestDate"; NSString *templateReviewURL = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID"; NSString *templateReviewURLiOS8 = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"; static NSString *_appId; static double _daysUntilPrompt = 30; static NSInteger _usesUntilPrompt = 20; static NSInteger _significantEventsUntilPrompt = -1; static double _timeBeforeReminding = 1; static BOOL _debug = NO; static BOOL _usesAnimation = TRUE; static UIStatusBarStyle _statusBarStyle; static BOOL _modalOpen = false; static BOOL _alwaysUseMainBundle = NO; @interface Appirater () @property (nonatomic, copy) NSString *alertTitle; @property (nonatomic, copy) NSString *alertMessage; @property (nonatomic, copy) NSString *alertCancelTitle; @property (nonatomic, copy) NSString *alertRateTitle; @property (nonatomic, copy) NSString *alertRateLaterTitle; @property (nonatomic, strong) NSOperationQueue *eventQueue; - (BOOL)connectedToNetwork; + (Appirater*)sharedInstance; - (void)showPromptWithChecks:(BOOL)withChecks displayRateLaterButton:(BOOL)displayRateLaterButton; - (void)showRatingAlert:(BOOL)displayRateLaterButton; - (void)showRatingAlert; - (BOOL)ratingAlertIsAppropriate; - (BOOL)ratingConditionsHaveBeenMet; - (void)incrementUseCount; - (void)hideRatingAlert; @end @implementation Appirater + (void) setAppId:(NSString *)appId { _appId = appId; } + (void) setDaysUntilPrompt:(double)value { _daysUntilPrompt = value; } + (void) setUsesUntilPrompt:(NSInteger)value { _usesUntilPrompt = value; } + (void) setSignificantEventsUntilPrompt:(NSInteger)value { _significantEventsUntilPrompt = value; } + (void) setTimeBeforeReminding:(double)value { _timeBeforeReminding = value; } + (void) setCustomAlertTitle:(NSString *)title { [self sharedInstance].alertTitle = title; } + (void) setCustomAlertMessage:(NSString *)message { [self sharedInstance].alertMessage = message; } + (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle { [self sharedInstance].alertCancelTitle = cancelTitle; } + (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle { [self sharedInstance].alertRateTitle = rateTitle; } + (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle { [self sharedInstance].alertRateLaterTitle = rateLaterTitle; } + (void) setDebug:(BOOL)debug { _debug = debug; } + (void)setDelegate:(id)delegate{ Appirater.sharedInstance.delegate = delegate; } + (void)setUsesAnimation:(BOOL)animation { _usesAnimation = animation; } + (void)setOpenInAppStore:(BOOL)openInAppStore { [Appirater sharedInstance].openInAppStore = openInAppStore; } + (void)setStatusBarStyle:(UIStatusBarStyle)style { _statusBarStyle = style; } + (void)setModalOpen:(BOOL)open { _modalOpen = open; } + (void)setAlwaysUseMainBundle:(BOOL)alwaysUseMainBundle { _alwaysUseMainBundle = alwaysUseMainBundle; } + (NSBundle *)bundle { NSBundle *bundle; if (_alwaysUseMainBundle) { bundle = [NSBundle mainBundle]; } else { NSURL *appiraterBundleURL = [[NSBundle mainBundle] URLForResource:@"Appirater" withExtension:@"bundle"]; if (appiraterBundleURL) { // Appirater.bundle will likely only exist when used via CocoaPods bundle = [NSBundle bundleWithURL:appiraterBundleURL]; } else { bundle = [NSBundle mainBundle]; } } return bundle; } - (NSString *)alertTitle { return _alertTitle ? _alertTitle : APPIRATER_MESSAGE_TITLE; } - (NSString *)alertMessage { return _alertMessage ? _alertMessage : APPIRATER_MESSAGE; } - (NSString *)alertCancelTitle { return _alertCancelTitle ? _alertCancelTitle : APPIRATER_CANCEL_BUTTON; } - (NSString *)alertRateTitle { return _alertRateTitle ? _alertRateTitle : APPIRATER_RATE_BUTTON; } - (NSString *)alertRateLaterTitle { return _alertRateLaterTitle ? _alertRateLaterTitle : APPIRATER_RATE_LATER; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (id)init { self = [super init]; if (self) { if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0) { self.openInAppStore = YES; } else { self.openInAppStore = NO; } } return self; } - (BOOL)connectedToNetwork { // Create zero addy struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; // Recover reachability flags SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); SCNetworkReachabilityFlags flags; Boolean didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); CFRelease(defaultRouteReachability); if (!didRetrieveFlags) { NSLog(@"Error. Could not recover network reachability flags"); return NO; } BOOL isReachable = flags & kSCNetworkFlagsReachable; BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection; NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"]; NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; sessionConfiguration.timeoutIntervalForRequest = 20.0; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; NSURLSessionTask *task = [session dataTaskWithURL:testURL]; [task resume]; return ((isReachable && !needsConnection) || nonWiFi) ? ( (task.state != NSURLSessionTaskStateSuspended) ? YES : NO ) : NO; } + (Appirater*)sharedInstance { static Appirater *appirater = nil; if (appirater == nil) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ appirater = [[Appirater alloc] init]; appirater.eventQueue = [[NSOperationQueue alloc] init]; appirater.eventQueue.maxConcurrentOperationCount = 1; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name: UIApplicationWillResignActiveNotification object:nil]; }); } return appirater; } - (void)showRatingAlert:(BOOL)displayRateLaterButton { id delegate = _delegate; if(delegate && [delegate respondsToSelector:@selector(appiraterShouldDisplayAlert:)] && ![delegate appiraterShouldDisplayAlert:self]) { return; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" if (NSStringFromClass([SKStoreReviewController class]) != nil) { #pragma clang diagnostic pop [Appirater rateApp]; } else { // Otherwise show a custom Alert NSMutableArray *buttons = [[NSMutableArray alloc] initWithObjects:self.alertRateTitle, nil]; if (displayRateLaterButton) { [buttons addObject:self.alertRateLaterTitle]; } if (NSStringFromClass([UIAlertController class]) != nil) { [buttons addObject:self.alertCancelTitle]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.alertTitle message:self.alertMessage preferredStyle:UIAlertControllerStyleAlert]; for (NSInteger i = 0; i < buttons.count; i++) { UIAlertActionStyle style = i == buttons.count - 1 ? UIAlertActionStyleCancel : UIAlertActionStyleDefault; [alert addAction:[UIAlertAction actionWithTitle:buttons[i] style:style handler:^(UIAlertAction * _Nonnull action) { NSString *title = action.title; NSInteger buttonIndex = -1; if ([title isEqual:self.alertCancelTitle]) { buttonIndex = 0; } else if ([title isEqual:self.alertRateTitle]) { buttonIndex = 1; } else if ([title isEqual:self.alertRateLaterTitle]) { buttonIndex = 2; } [self alertViewDidDismissWithButtonIndex:buttonIndex]; }]]; } [[Appirater getRootViewController] presentViewController:alert animated:YES completion:nil]; self.ratingAlert = alert; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle message:self.alertMessage delegate:self cancelButtonTitle:self.alertCancelTitle otherButtonTitles:nil]; for (NSString *button in buttons) { [alertView addButtonWithTitle:button]; } self.ratingAlert = alertView; [alertView show]; #pragma clang diagnostic pop } } if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) { [delegate appiraterDidDisplayAlert:self]; } } - (void)showRatingAlert { [self showRatingAlert:true]; } // is this an ok time to show the alert? (regardless of whether the rating conditions have been met) // // things checked here: // * connectivity with network // * whether user has rated before // * whether user has declined to rate // * whether rating alert is currently showing visibly // things NOT checked here: // * time since first launch // * number of uses of app // * number of significant events // * time since last reminder - (BOOL)ratingAlertIsAppropriate { return ([self connectedToNetwork] && ![self userHasDeclinedToRate] && ![self isRatingAlertVisible] && ![self userHasRatedCurrentVersion]); } // have the rating conditions been met/earned? (regardless of whether this would be a moment when it's appropriate to show a new rating alert) // // things checked here: // * time since first launch // * number of uses of app // * number of significant events // * time since last reminder // things NOT checked here: // * connectivity with network // * whether user has rated before // * whether user has declined to rate // * whether rating alert is currently showing visibly - (BOOL)ratingConditionsHaveBeenMet { if (_debug) return YES; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSDate *dateOfFirstLaunch = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterFirstUseDate]]; NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch]; NSTimeInterval timeUntilRate = 60 * 60 * 24 * _daysUntilPrompt; if (timeSinceFirstLaunch < timeUntilRate) return NO; // check if the app has been used enough NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount]; if (useCount < _usesUntilPrompt) return NO; // check if the user has done enough significant events NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount]; if (sigEventCount < _significantEventsUntilPrompt) return NO; // if the user wanted to be reminded later, has enough time passed? NSDate *reminderRequestDate = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterReminderRequestDate]]; NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate]; NSTimeInterval timeUntilReminder = 60 * 60 * 24 * _timeBeforeReminding; if (timeSinceReminderRequest < timeUntilReminder) return NO; return YES; } - (void)incrementUseCount { // get the app's version NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; // get the version number that we've been tracking NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; if (trackingVersion == nil) { trackingVersion = version; [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; } if (_debug) NSLog(@"APPIRATER Tracking version: %@", trackingVersion); if ([trackingVersion isEqualToString:version]) { // check if the first use date has been set. if not, set it. NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; if (timeInterval == 0) { timeInterval = [[NSDate date] timeIntervalSince1970]; [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; } // increment the use count NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount]; useCount++; [userDefaults setInteger:useCount forKey:kAppiraterUseCount]; if (_debug) NSLog(@"APPIRATER Use count: %@", @(useCount)); } else { // it's a new version of the app, so restart tracking [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterFirstUseDate]; [userDefaults setInteger:1 forKey:kAppiraterUseCount]; [userDefaults setInteger:0 forKey:kAppiraterSignificantEventCount]; [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; } [userDefaults synchronize]; } - (void)incrementSignificantEventCount { // get the app's version NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; // get the version number that we've been tracking NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; if (trackingVersion == nil) { trackingVersion = version; [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; } if (_debug) NSLog(@"APPIRATER Tracking version: %@", trackingVersion); if ([trackingVersion isEqualToString:version]) { // check if the first use date has been set. if not, set it. NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; if (timeInterval == 0) { timeInterval = [[NSDate date] timeIntervalSince1970]; [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; } // increment the significant event count NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount]; sigEventCount++; [userDefaults setInteger:sigEventCount forKey:kAppiraterSignificantEventCount]; if (_debug) NSLog(@"APPIRATER Significant event count: %@", @(sigEventCount)); } else { // it's a new version of the app, so restart tracking [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; [userDefaults setDouble:0 forKey:kAppiraterFirstUseDate]; [userDefaults setInteger:0 forKey:kAppiraterUseCount]; [userDefaults setInteger:1 forKey:kAppiraterSignificantEventCount]; [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; } [userDefaults synchronize]; } - (void)incrementAndRate:(BOOL)canPromptForRating { [self incrementUseCount]; if (canPromptForRating && [self ratingConditionsHaveBeenMet] && [self ratingAlertIsAppropriate]) { dispatch_async(dispatch_get_main_queue(), ^{ [self showRatingAlert]; }); } } - (void)incrementSignificantEventAndRate:(BOOL)canPromptForRating { [self incrementSignificantEventCount]; if (canPromptForRating && [self ratingConditionsHaveBeenMet] && [self ratingAlertIsAppropriate]) { dispatch_async(dispatch_get_main_queue(), ^{ [self showRatingAlert]; }); } } - (BOOL)userHasDeclinedToRate { return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterDeclinedToRate]; } - (BOOL)userHasRatedCurrentVersion { return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterRatedCurrentVersion]; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-implementations" + (void)appLaunched { [Appirater appLaunched:YES]; } #pragma GCC diagnostic pop + (void)appLaunched:(BOOL)canPromptForRating { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ Appirater *a = [Appirater sharedInstance]; if (_debug) { dispatch_async(dispatch_get_main_queue(), ^{ [a showRatingAlert]; }); } else { [a incrementAndRate:canPromptForRating]; } }); } - (BOOL)isRatingAlertVisible { if (NSStringFromClass([UIAlertController class]) != nil) { return ((UIAlertController *)self.ratingAlert).view.superview != nil; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return ((UIAlertView *)self.ratingAlert).visible; #pragma clang diagnostic pop } } - (void)hideRatingAlert { if ([self isRatingAlertVisible]) { if (_debug) { NSLog(@"APPIRATER Hiding Alert"); } if ([self.ratingAlert respondsToSelector:@selector(dismissWithClickedButtonIndex:animated:)]) { [self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO]; } else { [self.ratingAlert dismissViewControllerAnimated:NO completion:nil]; } } } + (void)appWillResignActive { if (_debug) NSLog(@"APPIRATER appWillResignActive"); [[Appirater sharedInstance] hideRatingAlert]; } + (void)appEnteredForeground:(BOOL)canPromptForRating { Appirater *a = [Appirater sharedInstance]; [a.eventQueue addOperationWithBlock:^{ [[Appirater sharedInstance] incrementAndRate:canPromptForRating]; }]; } + (void)userDidSignificantEvent:(BOOL)canPromptForRating { Appirater *a = [Appirater sharedInstance]; [a.eventQueue addOperationWithBlock:^{ [[Appirater sharedInstance] incrementSignificantEventAndRate:canPromptForRating]; }]; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-implementations" + (void)showPrompt { [Appirater tryToShowPrompt]; } #pragma GCC diagnostic pop + (void)tryToShowPrompt { [[Appirater sharedInstance] showPromptWithChecks:true displayRateLaterButton:true]; } + (void)forceShowPrompt:(BOOL)displayRateLaterButton { [[Appirater sharedInstance] showPromptWithChecks:false displayRateLaterButton:displayRateLaterButton]; } - (void)showPromptWithChecks:(BOOL)withChecks displayRateLaterButton:(BOOL)displayRateLaterButton { if (withChecks == NO || [self ratingAlertIsAppropriate]) { [self showRatingAlert:displayRateLaterButton]; } } + (id)getRootViewController { UIWindow *window = [[UIApplication sharedApplication] keyWindow]; if (window.windowLevel != UIWindowLevelNormal) { NSArray *windows = [[UIApplication sharedApplication] windows]; for(window in windows) { if (window.windowLevel == UIWindowLevelNormal) { break; } } } return [Appirater iterateSubViewsForViewController:window]; // iOS 8+ deep traverse } + (id)iterateSubViewsForViewController:(UIView *) parentView { for (UIView *subView in [parentView subviews]) { UIResponder *responder = [subView nextResponder]; if([responder isKindOfClass:[UIViewController class]]) { return [self topMostViewController: (UIViewController *) responder]; } id found = [Appirater iterateSubViewsForViewController:subView]; if( nil != found) { return found; } } return nil; } + (UIViewController *) topMostViewController: (UIViewController *) controller { BOOL isPresenting = NO; do { // this path is called only on iOS 6+, so -presentedViewController is fine here. UIViewController *presented = [controller presentedViewController]; isPresenting = presented != nil; if(presented != nil) { controller = presented; } } while (isPresenting); return controller; } + (void)rateApp { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; [userDefaults synchronize]; // Use the built SKStoreReviewController if available (available from iOS 10.3 upwards) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" if (NSStringFromClass([SKStoreReviewController class]) != nil) { [SKStoreReviewController requestReview]; #pragma clang diagnostic pop return; } //Use the in-app StoreKit view if available (iOS 6) and imported. This works in the simulator. if (![Appirater sharedInstance].openInAppStore && NSStringFromClass([SKStoreProductViewController class]) != nil) { SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init]; NSNumber *appId = [NSNumber numberWithInteger:_appId.integerValue]; [storeViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:nil]; storeViewController.delegate = self.sharedInstance; id delegate = self.sharedInstance.delegate; if ([delegate respondsToSelector:@selector(appiraterWillPresentModalView:animated:)]) { [delegate appiraterWillPresentModalView:self.sharedInstance animated:_usesAnimation]; } [[self getRootViewController] presentViewController:storeViewController animated:_usesAnimation completion:^{ [self setModalOpen:YES]; }]; //Use the standard openUrl method if StoreKit is unavailable. } else { #if TARGET_IPHONE_SIMULATOR NSLog(@"APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page."); #else NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131 // Fixes condition @see https://github.com/arashpayan/appirater/issues/205 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) { reviewURL = [templateReviewURLiOS7 stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; } // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182 else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { reviewURL = [templateReviewURLiOS8 stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; } [[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]]; #endif } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { [self alertViewDidDismissWithButtonIndex:buttonIndex]; } #pragma clang diagnostic pop - (void)alertViewDidDismissWithButtonIndex:(NSInteger)buttonIndex { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; id delegate = _delegate; switch (buttonIndex) { case 0: { // they don't want to rate it [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; [userDefaults synchronize]; if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){ [delegate appiraterDidDeclineToRate:self]; } break; } case 1: { // they want to rate it [Appirater rateApp]; if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){ [delegate appiraterDidOptToRate:self]; } break; } case 2: // remind them later [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate]; [userDefaults synchronize]; if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){ [delegate appiraterDidOptToRemindLater:self]; } break; default: break; } self.ratingAlert = nil; } //Delegate call from the StoreKit view. - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController { [Appirater closeModal]; } //Close the in-app rating (StoreKit) view and restore the previous status bar style. + (void)closeModal { if (_modalOpen) { BOOL usedAnimation = _usesAnimation; [self setModalOpen:NO]; // get the top most controller (= the StoreKit Controller) and dismiss it UIViewController *presentingController = [UIApplication sharedApplication].keyWindow.rootViewController; presentingController = [self topMostViewController: presentingController]; [presentingController dismissViewControllerAnimated:_usesAnimation completion:^{ id delegate = self.sharedInstance.delegate; if ([delegate respondsToSelector:@selector(appiraterDidDismissModalView:animated:)]) { [delegate appiraterDidDismissModalView:(Appirater *)self animated:usedAnimation]; } }]; [self.class setStatusBarStyle:(UIStatusBarStyle)nil]; } } @end ================================================ FILE: Pods/Appirater/AppiraterDelegate.h ================================================ // // AppiraterDelegate.h // Banana Stand // // Created by Robert Haining on 9/25/12. // Copyright (c) 2012 News.me. All rights reserved. // #import @class Appirater; @protocol AppiraterDelegate @optional -(BOOL)appiraterShouldDisplayAlert:(Appirater *)appirater; -(void)appiraterDidDisplayAlert:(Appirater *)appirater; -(void)appiraterDidDeclineToRate:(Appirater *)appirater; -(void)appiraterDidOptToRate:(Appirater *)appirater; -(void)appiraterDidOptToRemindLater:(Appirater *)appirater; -(void)appiraterWillPresentModalView:(Appirater *)appirater animated:(BOOL)animated; -(void)appiraterDidDismissModalView:(Appirater *)appirater animated:(BOOL)animated; @end ================================================ FILE: Pods/Appirater/README.md ================================================ Introduction --------------- Appirater is a class that you can drop into any iPhone app (iOS 4.0 or later) that will help remind your users to review your app on the App Store. The code is released under the MIT/X11, so feel free to modify and share your changes with the world. Read on below for how to get started. If you need any help using, the library, post your questions on [Stack Overflow] [stackoverflow] under the `appirater` tag. Getting Started --------------- ### CocoaPods To add Appirater to your app, add `pod "Appirater"` to your Podfile. Configuration ------------- 1. Appirater provides class methods to configure its behavior. See [`Appirater.h`] [Appirater.h] for more information. ```objc [Appirater setAppId:@"552035781"]; [Appirater setDaysUntilPrompt:1]; [Appirater setUsesUntilPrompt:10]; [Appirater setSignificantEventsUntilPrompt:-1]; [Appirater setTimeBeforeReminding:2]; [Appirater setDebug:YES]; ``` 2. Call `[Appirater setAppId:@"yourAppId"]` with the app id provided by Apple. A good place to do this is at the beginning of your app delegate's `application:didFinishLaunchingWithOptions:` method. 3. Call `[Appirater appLaunched:YES]` at the end of your app delegate's `application:didFinishLaunchingWithOptions:` method. 4. Call `[Appirater appEnteredForeground:YES]` in your app delegate's `applicationWillEnterForeground:` method. 5. (OPTIONAL) Call `[Appirater userDidSignificantEvent:YES]` when the user does something 'significant' in the app. ### Development Setting `[Appirater setDebug:YES]` will ensure that the rating request is shown each time the app is launched. ### Production Make sure you set `[Appirater setDebug:NO]` to ensure the request is not shown every time the app is launched. Also make sure that each of these components are set in the `application:didFinishLaunchingWithOptions:` method. This example states that the rating request is only shown when the app has been launched 5 times **and** after 7 days. ```objc [Appirater setAppId:@"770699556"]; [Appirater setDaysUntilPrompt:7]; [Appirater setUsesUntilPrompt:5]; [Appirater setSignificantEventsUntilPrompt:-1]; [Appirater setTimeBeforeReminding:2]; [Appirater setDebug:NO]; [Appirater appLaunched:YES]; ``` If you wanted to show the request after 5 days only you can set the following: ```objc [Appirater setAppId:@"770699556"]; [Appirater setDaysUntilPrompt:5]; [Appirater setUsesUntilPrompt:0]; [Appirater setSignificantEventsUntilPrompt:-1]; [Appirater setTimeBeforeReminding:2]; [Appirater setDebug:NO]; [Appirater appLaunched:YES]; ``` SKStoreReviewController ---------------------- In iOS 10.3, [SKStoreReviewController](https://developer.apple.com/library/content/releasenotes/General/WhatsNewIniOS/Articles/iOS10_3.html) was introduced which allows rating directly within the app without any additional setup. Appirater automatically uses `SKStoreReviewController` if available. You'll need to manually link `StoreKit` in your App however. If `SKStoreReviewController` is used, Appirater is used only to decide when to show the rating dialog to the user. Keep in mind, that `SKStoreReviewController` automatically limits the number of impressions, so the dialog might be displayed less frequently than your configured conditions might suggest. License ------- Copyright 2017. [Arash Payan] [arash]. This library is distributed under the terms of the MIT/X11. While not required, I greatly encourage and appreciate any improvements that you make to this library be contributed back for the benefit of all who use Appirater. Ports for other SDKs -------------- A few people have ported Appirater to other SDKs. The ports are listed here in hopes that they may assist developers of those SDKs. I don't know how closesly (if at all) they track the Objective-C version of Appirater. If you need support for any of the libraries, please contact the maintainer of the port. + MonoTouch Binding (using native Appirater). [Github] [monotouchbinding] [stackoverflow]: http://stackoverflow.com/ [homepage]: https://arashpayan.com/blog/2009/09/07/presenting-appirater/ [arash]: https://arashpayan.com [Appirater.h]: https://github.com/arashpayan/appirater/blob/master/Appirater.h [monotouchbinding]: https://github.com/theonlylawislove/MonoTouch.Appirater ================================================ FILE: Pods/Appirater/ar.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "إذا كنت تستمع باستخدام %@، فهل تمانع بأن تأخذ دقيقة من وقتك لتقيمه؟ لن يستغرق الأمر أكثر من دقيقة. شكرا لدعمك!"; "Rate %@" = "قيم %@"; "No, Thanks" = "لا شكرا"; "Remind me later" = "ذكرني لاحقا"; ================================================ FILE: Pods/Appirater/ca.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si li agrada utilitzar %@, li importaria prendre’s un moment per a valorar-lo? No trigarà més d’un minut. Gràcies por la seva col·laboració!"; "Rate %@" = "Valorar %@"; "No, Thanks" = "No, gràcies"; "Remind me later" = "Recordar-m’ho més tard"; ================================================ FILE: Pods/Appirater/cs.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Pokud se Vám aplikace %@ líbí, mohli byste ji prosím ohodnotit v App Store? Zabere to jen chvilku. Díky za Vaši podporu!"; "Rate %@" = "Ohodnotit %@"; "No, Thanks" = "Ne, díky"; "Remind me later" = "Možná později"; ================================================ FILE: Pods/Appirater/da.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Hvis du synes om at bruge %@, vil du have noget imod at bruge et kort øjeblik på at bedømme det? Det tager kun et minut. Tak for din støtte!"; "Rate %@" = "Bedøm %@"; "No, Thanks" = "Nej tak"; "Remind me later" = "Påmind mig senere"; ================================================ FILE: Pods/Appirater/de.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Sie nutzen %@ gerne? Dann nehmen Sie sich bitte für eine Bewertung einen Moment Zeit! Es dauert nicht länger als eine Minute. Vielen Dank!"; "Rate %@" = "Bewerte %@"; "No, Thanks" = "Nein, danke"; "Remind me later" = "Später erinnern"; ================================================ FILE: Pods/Appirater/el.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Αν σου αρέσει το %@, θα μπορούσες να αφιερώσεις μια στιγμή για να το βαθμολογήσεις; Η διαδικασία είναι πολύ σύντομη. Ευχαριστούμε για τη στήριξη!"; "Rate %@" = "Βαθμολόγηση του %@"; "No, Thanks" = "Όχι, ευχαριστώ"; "Remind me later" = "Υπενθύμιση αργότερα"; ================================================ FILE: Pods/Appirater/en.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!"; "Rate %@" = "Rate %@"; "No, Thanks" = "No, thanks"; "Remind me later" = "Remind me later"; ================================================ FILE: Pods/Appirater/es.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si te ha gustado %@, ¿te gustaría calificarnos? No te tomará más de un minuto. ¡Gracias por tu colaboración!"; "Rate %@" = "Calificar %@"; "No, Thanks" = "No, gracias"; "Remind me later" = "Recuérdame más tarde"; ================================================ FILE: Pods/Appirater/fa.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "اگر از استفاده برنامه %@ لذت می‌برید، زمان دارید بهش امتیاز دهید؟ بیشتر از یک دقیقه زمان نخواهد گرفت. با تشکر از اینکه ما را همایت می‌کنید."; "Rate %@" = "ارزیابی کردن %@"; "No, Thanks" = "نه، مرسی"; "Remind me later" = "بعدا"; ================================================ FILE: Pods/Appirater/fi.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jos käytät mielelläsi %@, voisitko käyttää hetken ja arvostella sen? Se ei kestä minuuttia kauempaa. Kiitos tuestasi!"; "Rate %@" = "Arvioi %@"; "No, Thanks" = "Ei kiitos"; "Remind me later" = "Muistuta minua myöhemmin"; ================================================ FILE: Pods/Appirater/fr.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si vous aimez %@, voulez-vous prendre un moment pour l'évaluer ? Cela ne vous prendra pas plus d'une minute. Merci de votre soutien !"; "Rate %@" = "Évaluer %@"; "No, Thanks" = "Non, merci"; "Remind me later" = "Me rappeler plus tard"; ================================================ FILE: Pods/Appirater/he.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "אם נהנת להשתמש ב %@, האם תסכים לדרג אותה? זה לא יקח יותר מדקה. תודה על התמיכה!"; "Rate %@" = "דרג את %@"; "No, Thanks" = "לא תודה"; "Remind me later" = "מאוחר יותר"; ================================================ FILE: Pods/Appirater/hu.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Ha tetszik a %@, ne felejtsd el értékelni az App Store-ban! Csak egy perc az egész. Köszönet a támogatásért!"; "Rate %@" = "%@ értékelése"; "No, Thanks" = "Most inkább nem"; "Remind me later" = "Emlékeztess később"; ================================================ FILE: Pods/Appirater/hy.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Եթե Դուք հաճույքով եք օգտագործում %@-ը, դեմ չե՞ք լինի տրամադրել մեկ րոպե այն գնահատելու համար: Այն չի պահանջի ձեզանից ավելի քան մեկ րոպե: Շնորհակալություն աջակցության համար:"; "Rate %@" = "Գնահատել %@-ը"; "No, Thanks" = "Ոչ, շնորհակալություն"; "Remind me later" = "Հիշեցնել ավելի ուշ"; ================================================ FILE: Pods/Appirater/id.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jika anda menyukai %@, maukah anda memberikan rating kepada aplikasi ini? Rating hanya memakan waktu kurang dari 1 menit. Terimakasih untuk dukungan anda!"; "Rate %@" = "Rating %@"; "No, Thanks" = "Tidak, terimakasih"; "Remind me later" = "Silakan ingatkan saya lagi"; ================================================ FILE: Pods/Appirater/it.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se ti piace %@, perché non dedichi qualche istante a darne una valutazione sull'App Store? Non richiederà più di un minuto. Grazie per il supporto!"; "Rate %@" = "Valuta %@"; "No, Thanks" = "No, grazie"; "Remind me later" = "Ricordamelo più tardi"; ================================================ FILE: Pods/Appirater/ja.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "%@をお使いいただきありがとうございます。もしよろしければ、ほんの少しだけお時間をいただき評価をお願いできませんか?ご協力感謝いたします!"; "Rate %@" = "%@を評価する"; "No, Thanks" ="結構です"; "Remind me later" = "あとで"; ================================================ FILE: Pods/Appirater/ko.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "%@ 사용이 맘에 드셨나요? 잠시만 시간을 내서 평가를 부탁드리겠습니다. 감사합니다!"; "Rate %@" = "%@ 평가하기"; "No, Thanks" = "평가하지 않겠습니다"; "Remind me later" = "다음에 평가하겠습니다"; ================================================ FILE: Pods/Appirater/ms.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jika anda suka %@, bolehkah luangkan sedikit masa untuk beri penarafan? Tak sampai seminit pun. Terima kasih atas sokongan anda!"; "Rate %@" = "Tarafkan %@"; "No, Thanks" = "Terima kasih saja"; "Remind me later" = "Ingatkan saya lain kali"; ================================================ FILE: Pods/Appirater/nb.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Hvis du liker å bruke %@, kan du ta deg et øyeblikk for å vurdere den? Det vil ikke ta mer enn ett minutt. Takk for din støtte!"; "Rate %@" = "Vurder %@"; "No, Thanks" = "Nei, takk"; "Remind me later" = "Påminn meg senere"; ================================================ FILE: Pods/Appirater/nl.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Als het gebruik van %@ je bevalt, zou je dan een momentje de tijd willen nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!"; "Rate %@" = "%@ beoordelen"; "No, Thanks" = "Nee, bedankt"; "Remind me later" = "Herinner me er later aan"; ================================================ FILE: Pods/Appirater/pl.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jeżeli podoba Ci się korzystanie z %@, może zechciałbyś poświęcić chwilę czasu, aby ocenić aplikację? Nie zajmie Ci to więcej niż minutę. Dziękujemy za pomoc!"; "Rate %@" = "Oceń %@"; "No, Thanks" = "Nie, dziękuję"; "Remind me later" = "Przypomnij później"; ================================================ FILE: Pods/Appirater/pt-BR.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se você gosta de usar o %@, que tal avaliá-lo? Não levará mais de um minuto. Agradecemos o seu apoio!"; "Rate %@" = "Avaliar o %@"; "No, Thanks" = "Não, obrigado"; "Remind me later" = "Mais tarde"; ================================================ FILE: Pods/Appirater/pt.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se você gosta de usar o %@, que tal avaliá-lo? Não levará mais de um minuto. Agradecemos o seu apoio!"; "Rate %@" = "Avaliar o %@"; "No, Thanks" = "Não, obrigado"; "Remind me later" = "Mais tarde"; ================================================ FILE: Pods/Appirater/ro.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Dacă îți place %@, acordă-i o notă te rog, nu durează mult. Mulțumim pentru susținere!"; "Rate %@" = "Acordă notă pentru %@"; "No, Thanks" = "Nu, mulțumesc"; "Remind me later" = "Adu-mi aminte mai târziu"; ================================================ FILE: Pods/Appirater/ru.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Если Вам нравится %@, пожалуйста, поставьте свою оценку. Это займет у Вас не больше одной минуты.\n Спасибо за поддержку!"; "Rate %@" = "Оценить %@"; "No, Thanks" = "Нет, спасибо"; "Remind me later" = "Напомнить позже"; ================================================ FILE: Pods/Appirater/sk.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Pokiaľ sa Vám páči aplikácia %@, mohli by ste ju prosím ohodnotiť v App Store? Zaberie to len chvíľu. Vďaka za Vašu podporu!"; "Rate %@" = "Ohodnotiť %@"; "No, Thanks" = "Nie, ďakujem"; "Remind me later" = "Pripomenúť neskôr"; ================================================ FILE: Pods/Appirater/sv.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Om du gillar att använda %@, kan du tänka dig att betygsätta det åt oss? Det tar bara en minut. Tack för hjälpen!"; "Rate %@" = "Betygsätt %@"; "No, Thanks" = "Nej tack"; "Remind me later" = "Påminn mig senare"; ================================================ FILE: Pods/Appirater/th.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "ถ้าคุณกำลังใช้ %@ โปรดสละเวลาสักครู่ในการให้อันดับแก่เรา คุณจะเสียเวลาไม่เกินหนึ่งนาที ขอบคุณสำหรับการสนับสนุน!"; "Rate %@" = "ให้อันดับ %@"; "No, Thanks" = "ไม่ ขอบคุณ"; "Remind me later" = "เตือนฉันภายหลัง"; ================================================ FILE: Pods/Appirater/tr.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Eğer %@ uygulamasını kullanmaktan keyif alıyorsanız, onu değerlendirmek için zaman ayırabilir misiniz? Desteğiniz için teşekkür ederiz!"; "Rate %@" = "%@ uygulamasını değerlendir"; "No, Thanks" = "Hayır, teşekkürler"; "Remind me later" = "Daha sonra hatırlat"; ================================================ FILE: Pods/Appirater/uk.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Якщо вам сподобалося %@, будь ласка, поставте свою оцінку. Це займає не більше однієї хвилини.\n Дякуємо за підтримку!"; "Rate %@" = "Оцінити %@"; "No, Thanks" = "Ні, дякую"; "Remind me later" = "Нагадати пізніше"; ================================================ FILE: Pods/Appirater/vi.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Cảm ơn bạn đã sử dụng ứng dụng %@ trong thời gian qua, bạn có thể dành chút thời gian để đánh giá ứng dụng trong AppStore không?"; "Rate %@" = "Đánh giá %@"; "No, Thanks" = "Không, xin cảm ơn"; "Remind me later" = "Hãy nhắc nhở tôi sau"; ================================================ FILE: Pods/Appirater/zh-Hans.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "如果你喜欢使用%@,你介意花一点时间给它评分吗?不会超过一分钟。感谢您的支持!"; "Rate %@" = "给%@评分"; "No, Thanks" = "不,谢谢"; "Remind me later" = "稍后提醒我"; ================================================ FILE: Pods/Appirater/zh-Hant.lproj/AppiraterLocalizable.strings ================================================ "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "如果你喜歡使用%@,你介意花一點時間給它評分嗎?不會超過一分鐘。感謝您的支持!"; "Rate %@" = "給%@評分"; "No, Thanks" = "不,謝謝"; "Remind me later" = "稍後提醒我"; ================================================ FILE: Pods/Firebase/CoreOnly/Sources/Firebase.h ================================================ // 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 #if !defined(__has_include) #error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \ import the headers individually." #else #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif #endif // defined(__has_include) ================================================ FILE: Pods/Firebase/CoreOnly/Sources/module.modulemap ================================================ module Firebase { export * header "Firebase.h" } ================================================ FILE: Pods/Firebase/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/Firebase/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![License](https://img.shields.io/cocoapods/l/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Platform](https://img.shields.io/cocoapods/p/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Actions Status][gh-abtesting-badge]][gh-actions] [![Actions Status][gh-appcheck-badge]][gh-actions] [![Actions Status][gh-appdistribution-badge]][gh-actions] [![Actions Status][gh-auth-badge]][gh-actions] [![Actions Status][gh-cocoapods-integration-badge]][gh-actions] [![Actions Status][gh-core-badge]][gh-actions] [![Actions Status][gh-core-diagnostics-badge]][gh-actions] [![Actions Status][gh-crashlytics-badge]][gh-actions] [![Actions Status][gh-database-badge]][gh-actions] [![Actions Status][gh-datatransport-badge]][gh-actions] [![Actions Status][gh-dynamiclinks-badge]][gh-actions] [![Actions Status][gh-firebasepod-badge]][gh-actions] [![Actions Status][gh-firestore-badge]][gh-actions] [![Actions Status][gh-functions-badge]][gh-actions] [![Actions Status][gh-google-utilities-badge]][gh-actions] [![Actions Status][gh-google-utilities-components-badge]][gh-actions] [![Actions Status][gh-inappmessaging-badge]][gh-actions] [![Actions Status][gh-interop-badge]][gh-actions] [![Actions Status][gh-messaging-badge]][gh-actions] [![Actions Status][gh-mlmodeldownloader-badge]][gh-actions] [![Actions Status][gh-performance-badge]][gh-actions] [![Actions Status][gh-remoteconfig-badge]][gh-actions] [![Actions Status][gh-storage-badge]][gh-actions] [![Actions Status][gh-symbolcollision-badge]][gh-actions] [![Actions Status][gh-zip-badge]][gh-actions] [![Travis](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase Apple Open Source Development This repository contains all Apple platform Firebase SDK source except FirebaseAnalytics and FirebaseML. Firebase is an app development platform with tools to help you build, grow and monetize your app. More information about Firebase can be found on the [official Firebase website](https://firebase.google.com). **Note** _FirebaseCombineSwift_ contains support for Apple's Combine framework. This module is currently under development, and not yet supported for use in production environments. Fore more details, please refer to the [docs](FirebaseCombineSwift/README.md). ## Installation See the subsections below for details about the different installation methods. 1. [Standard pod install](README.md#standard-pod-install) 1. [Swift Package Manager](SwiftPackageManager.md) 1. [Installing from the GitHub repo](README.md#installing-from-github) 1. [Experimental Carthage](README.md#carthage-ios-only) ### Standard pod install Go to [https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). ### Swift Package Manager Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be found at [SwiftPackageManager.md](SwiftPackageManager.md). ### Installing from GitHub These instructions can be used to access the Firebase repo at other branches, tags, or commits. #### Background See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) for instructions and options about overriding pod source locations. #### Accessing Firebase Source Snapshots All of the official releases are tagged in this repo and available via CocoaPods. To access a local source snapshot or unreleased branch, use Podfile directives like the following: To access FirebaseFirestore via a branch: ```ruby pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' ``` To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: ```ruby pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' ``` ### Carthage (iOS only) Instructions for the experimental Carthage distribution are at [Carthage](Carthage.md). ### Using Firebase from a Framework or a library [Using Firebase from a Framework or a library](docs/firebase_in_libraries.md) ## Development To develop Firebase software in this repository, ensure that you have at least the following software: * Xcode 12.2 (or later) CocoaPods is still the canonical way to develop, but much of the repo now supports development with Swift Package Manager. ### CocoaPods Install * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: ```ruby pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios ``` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. Firestore has a self contained Xcode project. See [Firestore/README.md](Firestore/README.md). #### Development for Catalyst * `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Swift Package Manager * To enable test schemes: `./scripts/setup_spm_tests.sh` * `open Package.swift` or double click `Package.swift` in Finder. * Xcode will open the project * Choose a scheme for a library to build or test suite to run * Choose a target platform by selecting the run destination along with the scheme ### Adding a New Firebase Pod See [AddNewPod.md](AddNewPod.md). ### Managing Headers and Imports See [HeadersImports.md](HeadersImports.md). ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ### Running Sample Apps In order to run the sample apps and integration tests, you'll need a valid `GoogleService-Info.plist` file. The Firebase Xcode project contains dummy plist files without real values, but can be replaced with real plist files. To get your own `GoogleService-Info.plist` files: 1. Go to the [Firebase Console](https://console.firebase.google.com/) 2. Create a new Firebase project, if you don't already have one 3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.Database-Example`) 4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project. ### Coverage Report Generation See [scripts/code_coverage_report/README.md](scripts/code_coverage_report/README.md). ## Specific Component Instructions See the sections below for any special instructions for those components. ### Firebase Auth If you're doing specific Firebase Auth development, see [the Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about building and running the FirebaseAuth pod along with various samples and tests. ### Firebase Database The Firebase Database Integration tests can be run against a locally running Database Emulator or against a production instance. To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before running the integration test. To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to `FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to [public](https://firebase.google.com/docs/database/security/quickstart) while your tests are running. ### Firebase Performance Monitoring If you're doing specific Firebase Performance Monitoring development, see [the Performance README](FirebasePerformance/README.md) for instructions about building the SDK and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about integrating Performance with the dev test App. ### Firebase Storage To run the Storage Integration tests, follow the instructions in [FIRStorageIntegrationTests.m](FirebaseStorage/Tests/Integration/FIRStorageIntegrationTests.m). #### Push Notifications Push notifications can only be delivered to specially provisioned App IDs in the developer portal. In order to actually test receiving push notifications, you will need to: 1. Change the bundle identifier of the sample app to something you own in your Apple Developer account, and enable that App ID for push notifications. 2. You'll also need to [upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) at **Project Settings > Cloud Messaging > [Your Firebase App]**. 3. Ensure your iOS device is added to your Apple Developer portal as a test device. #### iOS Simulator The iOS Simulator cannot register for remote notifications, and will not receive push notifications. In order to receive push notifications, you'll have to follow the steps above and run the app on a physical device. ## Building with Firebase on Apple platforms At this time, not all of Firebase's products are available across all Apple platforms. However, Firebase is constantly evolving and community supported efforts have helped expand Firebase's support. To keep up with the latest info regarding Firebase's support across Apple platforms, refer to [this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform) in Firebase's documentation. ### Community Supported Efforts We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are very grateful! We'd like to empower as many developers as we can to be able to use Firebase and participate in the Firebase community. #### tvOS, macOS, watchOS and Catalyst Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on tvOS, macOS, watchOS and Catalyst. For tvOS, see the [Sample](Example/tvOSSample). For watchOS, currently only Messaging, Storage and Crashlytics (and their dependencies) have limited support. See the [Independent Watch App Sample](Example/watchOSSample). Keep in mind that macOS, tvOS, watchOS and Catalyst are not officially supported by Firebase, and this repository is actively developed primarily for iOS. While we can catch basic unit test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected on macOS, tvOS or watchOS. If you encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). During app setup in the console, you may get to a step that mentions something like "Checking if the app has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/watchOS/Catalyst. **It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. #### Additional MacOS and Catalyst Notes * FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` to Build Settings. * For Catalyst, FirebaseFirestore requires signing the [gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). #### Additional Crashlytics Notes * watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded) ## Roadmap See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source plans and directions. ## Contributing See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase iOS SDK. ## License The contents of this repository are licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Your use of Firebase is governed by the [Terms of Service for Firebase Services](https://firebase.google.com/terms/). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-abtesting-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/abtesting/badge.svg [gh-appcheck-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/app_check/badge.svg [gh-appdistribution-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/appdistribution/badge.svg [gh-auth-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/auth/badge.svg [gh-cocoapods-integration-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/cocoapods-integration/badge.svg [gh-core-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core/badge.svg [gh-core-diagnostics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core-diagnostics/badge.svg [gh-crashlytics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/crashlytics/badge.svg [gh-database-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/database/badge.svg [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg [gh-dynamiclinks-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/dynamiclinks/badge.svg [gh-firebasepod-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firebasepod/badge.svg [gh-firestore-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firestore/badge.svg [gh-functions-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/functions/badge.svg [gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg [gh-google-utilities-components-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities-components/badge.svg [gh-inappmessaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/inappmessaging/badge.svg [gh-interop-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/interop/badge.svg [gh-messaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/messaging/badge.svg [gh-mlmodeldownloader-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/mlmodeldownloader/badge.svg [gh-performance-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/performance/badge.svg [gh-remoteconfig-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/remoteconfig/badge.svg [gh-storage-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/storage/badge.svg [gh-symbolcollision-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/symbolcollision/badge.svg [gh-zip-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/zip/badge.svg ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/Info.plist ================================================ AvailableLibraries LibraryIdentifier ios-arm64_armv7 LibraryPath FirebaseAnalytics.framework SupportedArchitectures arm64 armv7 SupportedPlatform ios LibraryIdentifier ios-arm64_i386_x86_64-simulator LibraryPath FirebaseAnalytics.framework SupportedArchitectures arm64 i386 x86_64 SupportedPlatform ios SupportedPlatformVariant simulator CFBundlePackageType XFWK XCFrameworkFormatVersion 1.0 ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIRAnalytics+AppDelegate.h ================================================ #import #import "FIRAnalytics.h" NS_ASSUME_NONNULL_BEGIN /** * Provides App Delegate handlers to be used in your App Delegate. * * To save time integrating Firebase Analytics in an application, Firebase Analytics does not * require delegation implementation from the AppDelegate. Instead this is automatically done by * Firebase Analytics. Should you choose instead to delegate manually, you can turn off the App * Delegate Proxy by adding FirebaseAppDelegateProxyEnabled into your app's Info.plist and setting * it to NO, and adding the methods in this category to corresponding delegation handlers. * * To handle Universal Links, you must return YES in * [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. */ @interface FIRAnalytics (AppDelegate) /** * Handles events related to a URL session that are waiting to be processed. * * For optimal use of Firebase Analytics, call this method from the * [UIApplicationDelegate application:handleEventsForBackgroundURLSession:completionHandler] * method of the app delegate in your app. * * @param identifier The identifier of the URL session requiring attention. * @param completionHandler The completion handler to call when you finish processing the events. * Calling this completion handler lets the system know that your app's user interface is * updated and a new snapshot can be taken. */ + (void)handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(nullable void (^)(void))completionHandler; /** * Handles the event when the app is launched by a URL. * * Call this method from [UIApplicationDelegate application:openURL:options:] (on iOS 9.0 and * above), or [UIApplicationDelegate application:openURL:sourceApplication:annotation:] (on * iOS 8.x and below) in your app. * * @param url The URL resource to open. This resource can be a network resource or a file. */ + (void)handleOpenURL:(NSURL *)url; /** * Handles the event when the app receives data associated with user activity that includes a * Universal Link (on iOS 9.0 and above). * * Call this method from [UIApplication continueUserActivity:restorationHandler:] in your app * delegate (on iOS 9.0 and above). * * @param userActivity The activity object containing the data associated with the task the user * was performing. */ + (void)handleUserActivity:(id)userActivity; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIRAnalytics+Consent.h ================================================ #import #import "FIRAnalytics.h" NS_ASSUME_NONNULL_BEGIN /// The type of consent to set. Supported consent types are `ConsentType.adStorage` and /// `ConsentType.analyticsStorage`. Omitting a type retains its previous status. typedef NSString *FIRConsentType NS_TYPED_ENUM NS_SWIFT_NAME(ConsentType); extern FIRConsentType const FIRConsentTypeAdStorage; extern FIRConsentType const FIRConsentTypeAnalyticsStorage; /// The status value of the consent type. Supported statuses are `ConsentStatus.granted` and /// `ConsentStatus.denied`. typedef NSString *FIRConsentStatus NS_TYPED_ENUM NS_SWIFT_NAME(ConsentStatus); extern FIRConsentStatus const FIRConsentStatusDenied; extern FIRConsentStatus const FIRConsentStatusGranted; /// Sets the applicable end user consent state. @interface FIRAnalytics (Consent) /// Sets the applicable end user consent state (e.g. for device identifiers) for this app on this /// device. Use the consent settings to specify individual consent type values. Settings are /// persisted across app sessions. By default consent types are set to `ConsentStatus.granted`. /// /// @param consentSettings An NSDictionary of consent types. Supported consent type keys are /// `ConsentType.adStorage` and `ConsentType.analyticsStorage`. Valid values are /// `ConsentStatus.granted` and `ConsentStatus.denied`. + (void)setConsent:(NSDictionary *)consentSettings; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIRAnalytics.h ================================================ #import #import "FIREventNames.h" #import "FIRParameterNames.h" #import "FIRUserPropertyNames.h" NS_ASSUME_NONNULL_BEGIN /// The top level Firebase Analytics singleton that provides methods for logging events and setting /// user properties. See the developer guides for general /// information on using Firebase Analytics in your apps. /// /// @note The Analytics SDK uses SQLite to persist events and other app-specific data. Calling /// certain thread-unsafe global SQLite methods like `sqlite3_shutdown()` can result in /// unexpected crashes at runtime. NS_SWIFT_NAME(Analytics) @interface FIRAnalytics : NSObject /// Logs an app event. The event can have up to 25 parameters. Events with the same name must have /// the same parameters. Up to 500 event names are supported. Using predefined events and/or /// parameters is recommended for optimal reporting. /// /// The following event names are reserved and cannot be used: ///
    ///
  • ad_activeview
  • ///
  • ad_click
  • ///
  • ad_exposure
  • ///
  • ad_query
  • ///
  • ad_reward
  • ///
  • adunit_exposure
  • ///
  • app_background
  • ///
  • app_clear_data
  • ///
  • app_exception
  • ///
  • app_remove
  • ///
  • app_store_refund
  • ///
  • app_store_subscription_cancel
  • ///
  • app_store_subscription_convert
  • ///
  • app_store_subscription_renew
  • ///
  • app_update
  • ///
  • app_upgrade
  • ///
  • dynamic_link_app_open
  • ///
  • dynamic_link_app_update
  • ///
  • dynamic_link_first_open
  • ///
  • error
  • ///
  • firebase_campaign
  • ///
  • first_open
  • ///
  • first_visit
  • ///
  • in_app_purchase
  • ///
  • notification_dismiss
  • ///
  • notification_foreground
  • ///
  • notification_open
  • ///
  • notification_receive
  • ///
  • os_update
  • ///
  • session_start
  • ///
  • session_start_with_rollout
  • ///
  • user_engagement
  • ///
/// /// @param name The name of the event. Should contain 1 to 40 alphanumeric characters or /// underscores. The name must start with an alphabetic character. Some event names are /// reserved. See FIREventNames.h for the list of reserved event names. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used. Note that event names are /// case-sensitive and that logging two events whose names differ only in case will result in /// two distinct events. To manually log screen view events, use the `screen_view` event name. /// @param parameters The dictionary of event parameters. Passing nil indicates that the event has /// no parameters. Parameter names can be up to 40 characters long and must start with an /// alphabetic character and contain only alphanumeric characters and underscores. Only NSString /// and NSNumber (signed 64-bit integer and 64-bit floating-point number) parameter types are /// supported. NSString parameter values can be up to 100 characters long. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used for parameter names. + (void)logEventWithName:(NSString *)name parameters:(nullable NSDictionary *)parameters NS_SWIFT_NAME(logEvent(_:parameters:)); /// Sets a user property to a given value. Up to 25 user property names are supported. Once set, /// user property values persist throughout the app lifecycle and across sessions. /// /// The following user property names are reserved and cannot be used: ///
    ///
  • first_open_time
  • ///
  • last_deep_link_referrer
  • ///
  • user_id
  • ///
/// /// @param value The value of the user property. Values can be up to 36 characters long. Setting the /// value to nil removes the user property. /// @param name The name of the user property to set. Should contain 1 to 24 alphanumeric characters /// or underscores and must start with an alphabetic character. The "firebase_", "google_", and /// "ga_" prefixes are reserved and should not be used for user property names. + (void)setUserPropertyString:(nullable NSString *)value forName:(NSString *)name NS_SWIFT_NAME(setUserProperty(_:forName:)); /// Sets the user ID property. This feature must be used in accordance with /// Google's Privacy Policy /// /// @param userID The user ID to ascribe to the user of this app on this device, which must be /// non-empty and no more than 256 characters long. Setting userID to nil removes the user ID. + (void)setUserID:(nullable NSString *)userID; /// Sets whether analytics collection is enabled for this app on this device. This setting is /// persisted across app sessions. By default it is enabled. /// /// @param analyticsCollectionEnabled A flag that enables or disables Analytics collection. + (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; /// Sets the interval of inactivity in seconds that terminates the current session. The default /// value is 1800 seconds (30 minutes). /// /// @param sessionTimeoutInterval The custom time of inactivity in seconds before the current /// session terminates. + (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval; /// Returns the unique ID for this instance of the application or nil if /// `ConsentType.analyticsStorage` has been set to `ConsentStatus.denied`. /// /// @see `FIRAnalytics+Consent.h` + (nullable NSString *)appInstanceID; /// Clears all analytics data for this instance from the device and resets the app instance ID. /// FIRAnalyticsConfiguration values will be reset to the default values. + (void)resetAnalyticsData; /// Adds parameters that will be set on every event logged from the SDK, including automatic ones. /// The values passed in the parameters dictionary will be added to the dictionary of default event /// parameters. These parameters persist across app runs. They are of lower precedence than event /// parameters, so if an event parameter and a parameter set using this API have the same name, the /// value of the event parameter will be used. The same limitations on event parameters apply to /// default event parameters. /// /// @param parameters Parameters to be added to the dictionary of parameters added to every event. /// They will be added to the dictionary of default event parameters, replacing any existing /// parameter with the same name. Valid parameters are NSString and NSNumber (signed 64-bit /// integer and 64-bit floating-point number). Setting a key's value to [NSNull null] will clear /// that parameter. Passing in a nil dictionary will clear all parameters. + (void)setDefaultEventParameters:(nullable NSDictionary *)parameters; /// Unavailable. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIREventNames.h ================================================ /// @file FIREventNames.h /// /// Predefined event names. /// /// An Event is an important occurrence in your app that you want to measure. You can report up to /// 500 different types of Events per app and you can associate up to 25 unique parameters with each /// Event type. Some common events are suggested below, but you may also choose to specify custom /// Event types that are associated with your specific app. Each event type is identified by a /// unique name. Event names can be up to 40 characters long, may only contain alphanumeric /// characters and underscores ("_"), and must start with an alphabetic character. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used. #import /// Add Payment Info event. This event signifies that a user has submitted their payment /// information. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterPaymentType (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddPaymentInfo NS_SWIFT_NAME(AnalyticsEventAddPaymentInfo) = @"add_payment_info"; /// E-Commerce Add To Cart event. This event signifies that an item(s) was added to a cart for /// purchase. Add this event to a funnel with @c kFIREventPurchase to gauge the effectiveness of /// your checkout process. Note: If you supply the @c kFIRParameterValue parameter, you must also /// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddToCart NS_SWIFT_NAME(AnalyticsEventAddToCart) = @"add_to_cart"; /// E-Commerce Add To Wishlist event. This event signifies that an item was added to a wishlist. Use /// this event to identify popular gift items. Note: If you supply the @c kFIRParameterValue /// parameter, you must also supply the @c kFIRParameterCurrency parameter so that revenue metrics /// can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddToWishlist NS_SWIFT_NAME(AnalyticsEventAddToWishlist) = @"add_to_wishlist"; /// Ad Impression event. This event signifies when a user sees an ad impression. Note: If you supply /// the @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency parameter /// so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterAdPlatform (NSString) (optional)
  • ///
  • @c kFIRParameterAdFormat (NSString) (optional)
  • ///
  • @c kFIRParameterAdSource (NSString) (optional)
  • ///
  • @c kFIRParameterAdUnitName (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAdImpression NS_SWIFT_NAME(AnalyticsEventAdImpression) = @"ad_impression"; /// App Open event. By logging this event when an App becomes active, developers can understand how /// often users leave and return during the course of a Session. Although Sessions are automatically /// reported, this event can provide further clarification around the continuous engagement of /// app-users. static NSString *const kFIREventAppOpen NS_SWIFT_NAME(AnalyticsEventAppOpen) = @"app_open"; /// E-Commerce Begin Checkout event. This event signifies that a user has begun the process of /// checking out. Add this event to a funnel with your @c kFIREventPurchase event to gauge the /// effectiveness of your checkout process. Note: If you supply the @c kFIRParameterValue parameter, /// you must also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be /// computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventBeginCheckout NS_SWIFT_NAME(AnalyticsEventBeginCheckout) = @"begin_checkout"; /// Campaign Detail event. Log this event to supply the referral details of a re-engagement /// campaign. Note: you must supply at least one of the required parameters kFIRParameterSource, /// kFIRParameterMedium or kFIRParameterCampaign. Params: /// ///
    ///
  • @c kFIRParameterSource (NSString)
  • ///
  • @c kFIRParameterMedium (NSString)
  • ///
  • @c kFIRParameterCampaign (NSString)
  • ///
  • @c kFIRParameterTerm (NSString) (optional)
  • ///
  • @c kFIRParameterContent (NSString) (optional)
  • ///
  • @c kFIRParameterAdNetworkClickID (NSString) (optional)
  • ///
  • @c kFIRParameterCP1 (NSString) (optional)
  • ///
static NSString *const kFIREventCampaignDetails NS_SWIFT_NAME(AnalyticsEventCampaignDetails) = @"campaign_details"; /// Checkout progress. Params: /// ///
    ///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCheckoutOption (NSString) (optional)
  • ///
/// This constant has been deprecated. static NSString *const kFIREventCheckoutProgress NS_SWIFT_NAME(AnalyticsEventCheckoutProgress) = @"checkout_progress"; /// Earn Virtual Currency event. This event tracks the awarding of virtual currency in your app. Log /// this along with @c kFIREventSpendVirtualCurrency to better understand your virtual economy. /// Params: /// ///
    ///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • ///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • ///
static NSString *const kFIREventEarnVirtualCurrency NS_SWIFT_NAME(AnalyticsEventEarnVirtualCurrency) = @"earn_virtual_currency"; /// E-Commerce Purchase event. This event signifies that an item was purchased by a user. Note: /// This is different from the in-app purchase event, which is reported automatically for App /// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also /// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterLocation (NSString) (optional)
  • ///
  • @c kFIRParameterStartDate (NSString) (optional)
  • ///
  • @c kFIRParameterEndDate (NSString) (optional)
  • ///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) /// for travel bookings
  • ///
  • @c kFIRParameterOrigin (NSString) (optional)
  • ///
  • @c kFIRParameterDestination (NSString) (optional)
  • ///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • ///
/// This constant has been deprecated. Use @c kFIREventPurchase constant instead. static NSString *const kFIREventEcommercePurchase NS_SWIFT_NAME(AnalyticsEventEcommercePurchase) = @"ecommerce_purchase"; /// Generate Lead event. Log this event when a lead has been generated in the app to understand the /// efficacy of your install and re-engagement campaigns. Note: If you supply the /// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency /// parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventGenerateLead NS_SWIFT_NAME(AnalyticsEventGenerateLead) = @"generate_lead"; /// Join Group event. Log this event when a user joins a group such as a guild, team or family. Use /// this event to analyze how popular certain groups or social features are in your app. Params: /// ///
    ///
  • @c kFIRParameterGroupID (NSString)
  • ///
static NSString *const kFIREventJoinGroup NS_SWIFT_NAME(AnalyticsEventJoinGroup) = @"join_group"; /// Level End event. Log this event when the user finishes a level. Params: /// ///
    ///
  • @c kFIRParameterLevelName (NSString)
  • ///
  • @c kFIRParameterSuccess (NSString)
  • ///
static NSString *const kFIREventLevelEnd NS_SWIFT_NAME(AnalyticsEventLevelEnd) = @"level_end"; /// Level Start event. Log this event when the user starts a new level. Params: /// ///
    ///
  • @c kFIRParameterLevelName (NSString)
  • ///
static NSString *const kFIREventLevelStart NS_SWIFT_NAME(AnalyticsEventLevelStart) = @"level_start"; /// Level Up event. This event signifies that a player has leveled up in your gaming app. It can /// help you gauge the level distribution of your userbase and help you identify certain levels that /// are difficult to pass. Params: /// ///
    ///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCharacter (NSString) (optional)
  • ///
static NSString *const kFIREventLevelUp NS_SWIFT_NAME(AnalyticsEventLevelUp) = @"level_up"; /// Login event. Apps with a login feature can report this event to signify that a user has logged /// in. static NSString *const kFIREventLogin NS_SWIFT_NAME(AnalyticsEventLogin) = @"login"; /// Post Score event. Log this event when the user posts a score in your gaming app. This event can /// help you understand how users are actually performing in your game and it can help you correlate /// high scores with certain audiences or behaviors. Params: /// ///
    ///
  • @c kFIRParameterScore (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber) (optional)
  • ///
  • @c kFIRParameterCharacter (NSString) (optional)
  • ///
static NSString *const kFIREventPostScore NS_SWIFT_NAME(AnalyticsEventPostScore) = @"post_score"; /// Present Offer event. This event signifies that the app has presented a purchase offer to a user. /// Add this event to a funnel with the kFIREventAddToCart and kFIREventEcommercePurchase to gauge /// your conversion process. Note: If you supply the @c kFIRParameterValue parameter, you must /// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
  • @c kFIRParameterItemName (NSString)
  • ///
  • @c kFIRParameterItemCategory (NSString)
  • ///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
/// This constant has been deprecated. Use @c kFIREventViewPromotion constant instead. static NSString *const kFIREventPresentOffer NS_SWIFT_NAME(AnalyticsEventPresentOffer) = @"present_offer"; /// E-Commerce Purchase Refund event. This event signifies that an item purchase was refunded. /// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. /// Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
/// This constant has been deprecated. Use @c kFIREventRefund constant instead. static NSString *const kFIREventPurchaseRefund NS_SWIFT_NAME(AnalyticsEventPurchaseRefund) = @"purchase_refund"; /// E-Commerce Remove from Cart event. This event signifies that an item(s) was removed from a cart. /// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the @c /// kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventRemoveFromCart NS_SWIFT_NAME(AnalyticsEventRemoveFromCart) = @"remove_from_cart"; /// Screen View event. This event signifies a screen view. Use this when a screen transition occurs. /// This event can be logged irrespective of whether automatic screen tracking is enabled. Params: /// ///
    ///
  • @c kFIRParameterScreenClass (NSString) (optional)
  • ///
  • @c kFIRParameterScreenName (NSString) (optional)
  • ///
static NSString *const kFIREventScreenView NS_SWIFT_NAME(AnalyticsEventScreenView) = @"screen_view"; /// Search event. Apps that support search features can use this event to contextualize search /// operations by supplying the appropriate, corresponding parameters. This event can help you /// identify the most popular content in your app. Params: /// ///
    ///
  • @c kFIRParameterSearchTerm (NSString)
  • ///
  • @c kFIRParameterStartDate (NSString) (optional)
  • ///
  • @c kFIRParameterEndDate (NSString) (optional)
  • ///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) /// for travel bookings
  • ///
  • @c kFIRParameterOrigin (NSString) (optional)
  • ///
  • @c kFIRParameterDestination (NSString) (optional)
  • ///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • ///
static NSString *const kFIREventSearch NS_SWIFT_NAME(AnalyticsEventSearch) = @"search"; /// Select Content event. This general purpose event signifies that a user has selected some content /// of a certain type in an app. The content can be any object in your app. This event can help you /// identify popular content and categories of content in your app. Params: /// ///
    ///
  • @c kFIRParameterContentType (NSString)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
static NSString *const kFIREventSelectContent NS_SWIFT_NAME(AnalyticsEventSelectContent) = @"select_content"; /// Set checkout option. Params: /// ///
    ///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCheckoutOption (NSString)
  • ///
/// This constant has been deprecated. static NSString *const kFIREventSetCheckoutOption NS_SWIFT_NAME(AnalyticsEventSetCheckoutOption) = @"set_checkout_option"; /// Share event. Apps with social features can log the Share event to identify the most viral /// content. Params: /// ///
    ///
  • @c kFIRParameterContentType (NSString)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
static NSString *const kFIREventShare NS_SWIFT_NAME(AnalyticsEventShare) = @"share"; /// Sign Up event. This event indicates that a user has signed up for an account in your app. The /// parameter signifies the method by which the user signed up. Use this event to understand the /// different behaviors between logged in and logged out users. Params: /// ///
    ///
  • @c kFIRParameterSignUpMethod (NSString)
  • ///
static NSString *const kFIREventSignUp NS_SWIFT_NAME(AnalyticsEventSignUp) = @"sign_up"; /// Spend Virtual Currency event. This event tracks the sale of virtual goods in your app and can /// help you identify which virtual goods are the most popular objects of purchase. Params: /// ///
    ///
  • @c kFIRParameterItemName (NSString)
  • ///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • ///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • ///
static NSString *const kFIREventSpendVirtualCurrency NS_SWIFT_NAME(AnalyticsEventSpendVirtualCurrency) = @"spend_virtual_currency"; /// Tutorial Begin event. This event signifies the start of the on-boarding process in your app. Use /// this in a funnel with kFIREventTutorialComplete to understand how many users complete this /// process and move on to the full app experience. static NSString *const kFIREventTutorialBegin NS_SWIFT_NAME(AnalyticsEventTutorialBegin) = @"tutorial_begin"; /// Tutorial End event. Use this event to signify the user's completion of your app's on-boarding /// process. Add this to a funnel with kFIREventTutorialBegin to gauge the completion rate of your /// on-boarding process. static NSString *const kFIREventTutorialComplete NS_SWIFT_NAME(AnalyticsEventTutorialComplete) = @"tutorial_complete"; /// Unlock Achievement event. Log this event when the user has unlocked an achievement in your /// game. Since achievements generally represent the breadth of a gaming experience, this event can /// help you understand how many users are experiencing all that your game has to offer. Params: /// ///
    ///
  • @c kFIRParameterAchievementID (NSString)
  • ///
static NSString *const kFIREventUnlockAchievement NS_SWIFT_NAME(AnalyticsEventUnlockAchievement) = @"unlock_achievement"; /// View Item event. This event signifies that a user has viewed an item. Use the appropriate /// parameters to contextualize the event. Use this event to discover the most popular items viewed /// in your app. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventViewItem NS_SWIFT_NAME(AnalyticsEventViewItem) = @"view_item"; /// View Item List event. Log this event when a user sees a list of items or offerings. Params: /// ///
    ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterItemListID (NSString) (optional)
  • ///
  • @c kFIRParameterItemListName (NSString) (optional)
  • ///
static NSString *const kFIREventViewItemList NS_SWIFT_NAME(AnalyticsEventViewItemList) = @"view_item_list"; /// View Search Results event. Log this event when the user has been presented with the results of a /// search. Params: /// ///
    ///
  • @c kFIRParameterSearchTerm (NSString)
  • ///
static NSString *const kFIREventViewSearchResults NS_SWIFT_NAME(AnalyticsEventViewSearchResults) = @"view_search_results"; /// Add Shipping Info event. This event signifies that a user has submitted their shipping /// information. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShippingTier (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddShippingInfo NS_SWIFT_NAME(AnalyticsEventAddShippingInfo) = @"add_shipping_info"; /// E-Commerce Purchase event. This event signifies that an item(s) was purchased by a user. Note: /// This is different from the in-app purchase event, which is reported automatically for App /// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also supply /// the @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. /// Params: /// ///
    ///
  • @c kFIRParameterAffiliation (NSString) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventPurchase NS_SWIFT_NAME(AnalyticsEventPurchase) = @"purchase"; /// E-Commerce Refund event. This event signifies that a refund was issued. Note: If you supply the /// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency parameter so /// that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterAffiliation (NSString) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventRefund NS_SWIFT_NAME(AnalyticsEventRefund) = @"refund"; /// Select Item event. This event signifies that an item was selected by a user from a list. Use the /// appropriate parameters to contextualize the event. Use this event to discover the most popular /// items selected. Params: /// ///
    ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterItemListID (NSString) (optional)
  • ///
  • @c kFIRParameterItemListName (NSString) (optional)
  • ///
static NSString *const kFIREventSelectItem NS_SWIFT_NAME(AnalyticsEventSelectItem) = @"select_item"; /// Select promotion event. This event signifies that a user has selected a promotion offer. Use the /// appropriate parameters to contextualize the event, such as the item(s) for which the promotion /// applies. Params: /// ///
    ///
  • @c kFIRParameterCreativeName (NSString) (optional)
  • ///
  • @c kFIRParameterCreativeSlot (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionName (NSString) (optional)
  • ///
static NSString *const kFIREventSelectPromotion NS_SWIFT_NAME(AnalyticsEventSelectPromotion) = @"select_promotion"; /// E-commerce View Cart event. This event signifies that a user has viewed their cart. Use this to /// analyze your purchase funnel. Note: If you supply the @c kFIRParameterValue parameter, you must /// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventViewCart NS_SWIFT_NAME(AnalyticsEventViewCart) = @"view_cart"; /// View Promotion event. This event signifies that a promotion was shown to a user. Add this event /// to a funnel with the @c kFIREventAddToCart and @c kFIREventPurchase to gauge your conversion /// process. Params: /// ///
    ///
  • @c kFIRParameterCreativeName (NSString) (optional)
  • ///
  • @c kFIRParameterCreativeSlot (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionName (NSString) (optional)
  • ///
static NSString *const kFIREventViewPromotion NS_SWIFT_NAME(AnalyticsEventViewPromotion) = @"view_promotion"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIRParameterNames.h ================================================ /// @file FIRParameterNames.h /// /// Predefined event parameter names. /// /// Params supply information that contextualize Events. You can associate up to 25 unique Params /// with each Event type. Some Params are suggested below for certain common Events, but you are /// not limited to these. You may supply extra Params for suggested Events or custom Params for /// Custom events. Param names can be up to 40 characters long, may only contain alphanumeric /// characters and underscores ("_"), and must start with an alphabetic character. Param values can /// be up to 100 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and /// should not be used. #import /// Game achievement ID (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAchievementID : @"10_matches_won",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAchievementID NS_SWIFT_NAME(AnalyticsParameterAchievementID) = @"achievement_id"; /// The ad format (e.g. Banner, Interstitial, Rewarded, Native, Rewarded Interstitial, Instream). /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdFormat : @"Banner",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdFormat NS_SWIFT_NAME(AnalyticsParameterAdFormat) = @"ad_format"; /// Ad Network Click ID (NSString). Used for network-specific click IDs which vary in format. ///
///     NSDictionary *params = @{
///       kFIRParameterAdNetworkClickID : @"1234567",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdNetworkClickID NS_SWIFT_NAME(AnalyticsParameterAdNetworkClickID) = @"aclid"; /// The ad platform (e.g. MoPub, IronSource) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdPlatform : @"MoPub",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdPlatform NS_SWIFT_NAME(AnalyticsParameterAdPlatform) = @"ad_platform"; /// The ad source (e.g. AdColony) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdSource : @"AdColony",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdSource NS_SWIFT_NAME(AnalyticsParameterAdSource) = @"ad_source"; /// The ad unit name (e.g. Banner_03) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdUnitName : @"Banner_03",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdUnitName NS_SWIFT_NAME(AnalyticsParameterAdUnitName) = @"ad_unit_name"; /// A product affiliation to designate a supplying company or brick and mortar store location /// (NSString).
///     NSDictionary *params = @{
///       kFIRParameterAffiliation : @"Google Store",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAffiliation NS_SWIFT_NAME(AnalyticsParameterAffiliation) = @"affiliation"; /// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to /// capture campaign information, otherwise can be populated by developer. Highly Recommended /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCampaign : @"winter_promotion",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) = @"campaign"; /// Character used in game (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCharacter : @"beat_boss",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCharacter NS_SWIFT_NAME(AnalyticsParameterCharacter) = @"character"; /// The checkout step (1..N) (unsigned 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterCheckoutStep : @"1",
///       // ...
///     };
/// 
/// This constant has been deprecated. static NSString *const kFIRParameterCheckoutStep NS_SWIFT_NAME(AnalyticsParameterCheckoutStep) = @"checkout_step"; /// Some option on a step in an ecommerce flow (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCheckoutOption : @"Visa",
///       // ...
///     };
/// 
/// This constant has been deprecated. static NSString *const kFIRParameterCheckoutOption NS_SWIFT_NAME(AnalyticsParameterCheckoutOption) = @"checkout_option"; /// Campaign content (NSString). static NSString *const kFIRParameterContent NS_SWIFT_NAME(AnalyticsParameterContent) = @"content"; /// Type of content selected (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterContentType : @"news article",
///       // ...
///     };
/// 
static NSString *const kFIRParameterContentType NS_SWIFT_NAME(AnalyticsParameterContentType) = @"content_type"; /// Coupon code used for a purchase (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCoupon : @"SUMMER_FUN",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCoupon NS_SWIFT_NAME(AnalyticsParameterCoupon) = @"coupon"; /// Campaign custom parameter (NSString). Used as a method of capturing custom data in a campaign. /// Use varies by network. ///
///     NSDictionary *params = @{
///       kFIRParameterCP1 : @"custom_data",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCP1 NS_SWIFT_NAME(AnalyticsParameterCP1) = @"cp1"; /// The name of a creative used in a promotional spot (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCreativeName : @"Summer Sale",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCreativeName NS_SWIFT_NAME(AnalyticsParameterCreativeName) = @"creative_name"; /// The name of a creative slot (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCreativeSlot : @"summer_banner2",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCreativeSlot NS_SWIFT_NAME(AnalyticsParameterCreativeSlot) = @"creative_slot"; /// Currency of the purchase or items associated with the event, in 3-letter /// ISO_4217 format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCurrency : @"USD",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCurrency NS_SWIFT_NAME(AnalyticsParameterCurrency) = @"currency"; /// Flight or Travel destination (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterDestination : @"Mountain View, CA",
///       // ...
///     };
/// 
static NSString *const kFIRParameterDestination NS_SWIFT_NAME(AnalyticsParameterDestination) = @"destination"; /// The arrival date, check-out date or rental end date for the item. This should be in /// YYYY-MM-DD format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterEndDate : @"2015-09-14",
///       // ...
///     };
/// 
static NSString *const kFIRParameterEndDate NS_SWIFT_NAME(AnalyticsParameterEndDate) = @"end_date"; /// Flight number for travel events (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterFlightNumber : @"ZZ800",
///       // ...
///     };
/// 
static NSString *const kFIRParameterFlightNumber NS_SWIFT_NAME(AnalyticsParameterFlightNumber) = @"flight_number"; /// Group/clan/guild ID (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterGroupID : @"g1",
///       // ...
///     };
/// 
static NSString *const kFIRParameterGroupID NS_SWIFT_NAME(AnalyticsParameterGroupID) = @"group_id"; /// The index of the item in a list (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterIndex : @(5),
///       // ...
///     };
/// 
static NSString *const kFIRParameterIndex NS_SWIFT_NAME(AnalyticsParameterIndex) = @"index"; /// Item brand (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemBrand : @"Google",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemBrand NS_SWIFT_NAME(AnalyticsParameterItemBrand) = @"item_brand"; /// Item category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory NS_SWIFT_NAME(AnalyticsParameterItemCategory) = @"item_category"; /// Item ID (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemID : @"SKU_12345",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemID NS_SWIFT_NAME(AnalyticsParameterItemID) = @"item_id"; /// The Google Place ID (NSString) that /// corresponds to the associated item. Alternatively, you can supply your own custom Location ID. ///
///     NSDictionary *params = @{
///       kFIRParameterItemLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
/// This constant has been deprecated. Use @c kFIRParameterLocationID constant instead. static NSString *const kFIRParameterItemLocationID NS_SWIFT_NAME(AnalyticsParameterItemLocationID) = @"item_location_id"; /// Item Name (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemName : @"jeggings",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemName NS_SWIFT_NAME(AnalyticsParameterItemName) = @"item_name"; /// The list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemList : @"Search Results",
///       // ...
///     };
/// 
/// This constant has been deprecated. Use @c kFIRParameterItemListName constant instead. static NSString *const kFIRParameterItemList NS_SWIFT_NAME(AnalyticsParameterItemList) = @"item_list"; /// Item variant (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemVariant : @"Black",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemVariant NS_SWIFT_NAME(AnalyticsParameterItemVariant) = @"item_variant"; /// Level in game (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterLevel : @(42),
///       // ...
///     };
/// 
static NSString *const kFIRParameterLevel NS_SWIFT_NAME(AnalyticsParameterLevel) = @"level"; /// Location (NSString). The Google Place ID /// that corresponds to the associated event. Alternatively, you can supply your own custom /// Location ID. ///
///     NSDictionary *params = @{
///       kFIRParameterLocation : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLocation NS_SWIFT_NAME(AnalyticsParameterLocation) = @"location"; /// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterMedium : @"email",
///       // ...
///     };
/// 
static NSString *const kFIRParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium"; /// Number of nights staying at hotel (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfNights : @(3),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfNights NS_SWIFT_NAME(AnalyticsParameterNumberOfNights) = @"number_of_nights"; /// Number of passengers traveling (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfPassengers : @(11),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfPassengers NS_SWIFT_NAME(AnalyticsParameterNumberOfPassengers) = @"number_of_passengers"; /// Number of rooms for travel events (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfRooms : @(2),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfRooms NS_SWIFT_NAME(AnalyticsParameterNumberOfRooms) = @"number_of_rooms"; /// Flight or Travel origin (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterOrigin : @"Mountain View, CA",
///       // ...
///     };
/// 
static NSString *const kFIRParameterOrigin NS_SWIFT_NAME(AnalyticsParameterOrigin) = @"origin"; /// Purchase price (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterPrice : @(1.0),
///       kFIRParameterCurrency : @"USD",  // e.g. $1.00 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterPrice NS_SWIFT_NAME(AnalyticsParameterPrice) = @"price"; /// Purchase quantity (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterQuantity : @(1),
///       // ...
///     };
/// 
static NSString *const kFIRParameterQuantity NS_SWIFT_NAME(AnalyticsParameterQuantity) = @"quantity"; /// Score in game (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterScore : @(4200),
///       // ...
///     };
/// 
static NSString *const kFIRParameterScore NS_SWIFT_NAME(AnalyticsParameterScore) = @"score"; /// Current screen class, such as the class name of the UIViewController, logged with screen_view /// event and added to every event (NSString).
///     NSDictionary *params = @{
///       kFIRParameterScreenClass : @"LoginViewController",
///       // ...
///     };
/// 
static NSString *const kFIRParameterScreenClass NS_SWIFT_NAME(AnalyticsParameterScreenClass) = @"screen_class"; /// Current screen name, such as the name of the UIViewController, logged with screen_view event and /// added to every event (NSString).
///     NSDictionary *params = @{
///       kFIRParameterScreenName : @"LoginView",
///       // ...
///     };
/// 
static NSString *const kFIRParameterScreenName NS_SWIFT_NAME(AnalyticsParameterScreenName) = @"screen_name"; /// The search string/keywords used (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSearchTerm : @"periodic table",
///       // ...
///     };
/// 
static NSString *const kFIRParameterSearchTerm NS_SWIFT_NAME(AnalyticsParameterSearchTerm) = @"search_term"; /// Shipping cost associated with a transaction (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterShipping : @(5.99),
///       kFIRParameterCurrency : @"USD",  // e.g. $5.99 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterShipping NS_SWIFT_NAME(AnalyticsParameterShipping) = @"shipping"; /// Sign up method (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSignUpMethod : @"google",
///       // ...
///     };
/// 
/// /// This constant has been deprecated. Use Method constant instead. static NSString *const kFIRParameterSignUpMethod NS_SWIFT_NAME(AnalyticsParameterSignUpMethod) = @"sign_up_method"; /// A particular approach used in an operation; for example, "facebook" or "email" in the context /// of a sign_up or login event. (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterMethod : @"google",
///       // ...
///     };
/// 
static NSString *const kFIRParameterMethod NS_SWIFT_NAME(AnalyticsParameterMethod) = @"method"; /// The origin of your traffic, such as an Ad network (for example, google) or partner (urban /// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your /// property. Highly recommended (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSource : @"InMobi",
///       // ...
///     };
/// 
static NSString *const kFIRParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source"; /// The departure date, check-in date or rental start date for the item. This should be in /// YYYY-MM-DD format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterStartDate : @"2015-09-14",
///       // ...
///     };
/// 
static NSString *const kFIRParameterStartDate NS_SWIFT_NAME(AnalyticsParameterStartDate) = @"start_date"; /// Tax cost associated with a transaction (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterTax : @(2.43),
///       kFIRParameterCurrency : @"USD",  // e.g. $2.43 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterTax NS_SWIFT_NAME(AnalyticsParameterTax) = @"tax"; /// If you're manually tagging keyword campaigns, you should use utm_term to specify the keyword /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTerm : @"game",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTerm NS_SWIFT_NAME(AnalyticsParameterTerm) = @"term"; /// The unique identifier of a transaction (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTransactionID : @"T12345",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTransactionID NS_SWIFT_NAME(AnalyticsParameterTransactionID) = @"transaction_id"; /// Travel class (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTravelClass : @"business",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTravelClass NS_SWIFT_NAME(AnalyticsParameterTravelClass) = @"travel_class"; /// A context-specific numeric value which is accumulated automatically for each event type. This is /// a general purpose parameter that is useful for accumulating a key metric that pertains to an /// event. Examples include revenue, distance, time and points. Value should be specified as signed /// 64-bit integer or double as NSNumber. Notes: Values for pre-defined currency-related events /// (such as @c kFIREventAddToCart) should be supplied using double as NSNumber and must be /// accompanied by a @c kFIRParameterCurrency parameter. The valid range of accumulated values is /// [-9,223,372,036,854.77, 9,223,372,036,854.77]. Supplying a non-numeric value, omitting the /// corresponding @c kFIRParameterCurrency parameter, or supplying an invalid /// currency code for conversion events will cause that /// conversion to be omitted from reporting. ///
///     NSDictionary *params = @{
///       kFIRParameterValue : @(3.99),
///       kFIRParameterCurrency : @"USD",  // e.g. $3.99 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterValue NS_SWIFT_NAME(AnalyticsParameterValue) = @"value"; /// Name of virtual currency type (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterVirtualCurrencyName : @"virtual_currency_name",
///       // ...
///     };
/// 
static NSString *const kFIRParameterVirtualCurrencyName NS_SWIFT_NAME(AnalyticsParameterVirtualCurrencyName) = @"virtual_currency_name"; /// The name of a level in a game (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterLevelName : @"room_1",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLevelName NS_SWIFT_NAME(AnalyticsParameterLevelName) = @"level_name"; /// The result of an operation. Specify 1 to indicate success and 0 to indicate failure (unsigned /// integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterSuccess : @(1),
///       // ...
///     };
/// 
static NSString *const kFIRParameterSuccess NS_SWIFT_NAME(AnalyticsParameterSuccess) = @"success"; /// Indicates that the associated event should either extend the current session /// or start a new session if no session was active when the event was logged. /// Specify YES to extend the current session or to start a new session; any /// other value will not extend or start a session. ///
///     NSDictionary *params = @{
///       kFIRParameterExtendSession : @YES,
///       // ...
///     };
/// 
static NSString *const kFIRParameterExtendSession NS_SWIFT_NAME(AnalyticsParameterExtendSession) = @"extend_session"; /// Monetary value of discount associated with a purchase (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterDiscount : @(2.0),
///       kFIRParameterCurrency : @"USD",  // e.g. $2.00 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterDiscount NS_SWIFT_NAME(AnalyticsParameterDiscount) = @"discount"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory2 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory2 NS_SWIFT_NAME(AnalyticsParameterItemCategory2) = @"item_category2"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory3 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory3 NS_SWIFT_NAME(AnalyticsParameterItemCategory3) = @"item_category3"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory4 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory4 NS_SWIFT_NAME(AnalyticsParameterItemCategory4) = @"item_category4"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory5 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory5 NS_SWIFT_NAME(AnalyticsParameterItemCategory5) = @"item_category5"; /// The ID of the list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemListID : @"ABC123",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemListID NS_SWIFT_NAME(AnalyticsParameterItemListID) = @"item_list_id"; /// The name of the list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemListName : @"Related products",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemListName NS_SWIFT_NAME(AnalyticsParameterItemListName) = @"item_list_name"; /// The list of items involved in the transaction. (NSArray). ///
///     NSDictionary *params = @{
///       kFIRParameterItems : @[
///         @{kFIRParameterItemName : @"jeggings", kFIRParameterItemCategory : @"pants"},
///         @{kFIRParameterItemName : @"boots", kFIRParameterItemCategory : @"shoes"},
///       ],
///     };
/// 
static NSString *const kFIRParameterItems NS_SWIFT_NAME(AnalyticsParameterItems) = @"items"; /// The location associated with the event. Preferred to be the Google /// Place ID that corresponds to the /// associated item but could be overridden to a custom location ID string.(NSString).
///     NSDictionary *params = @{
///       kFIRParameterLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLocationID NS_SWIFT_NAME(AnalyticsParameterLocationID) = @"location_id"; /// The chosen method of payment (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPaymentType : @"Visa",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPaymentType NS_SWIFT_NAME(AnalyticsParameterPaymentType) = @"payment_type"; /// The ID of a product promotion (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPromotionID : @"ABC123",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPromotionID NS_SWIFT_NAME(AnalyticsParameterPromotionID) = @"promotion_id"; /// The name of a product promotion (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPromotionName : @"Summer Sale",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPromotionName NS_SWIFT_NAME(AnalyticsParameterPromotionName) = @"promotion_name"; /// The shipping tier (e.g. Ground, Air, Next-day) selected for delivery of the purchased item /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterShippingTier : @"Ground",
///       // ...
///     };
/// 
static NSString *const kFIRParameterShippingTier NS_SWIFT_NAME(AnalyticsParameterShippingTier) = @"shipping_tier"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FIRUserPropertyNames.h ================================================ /// @file FIRUserPropertyNames.h /// /// Predefined user property names. /// /// A UserProperty is an attribute that describes the app-user. By supplying UserProperties, you can /// later analyze different behaviors of various segments of your userbase. You may supply up to 25 /// unique UserProperties per app, and you can use the name and value of your choosing for each one. /// UserProperty names can be up to 24 characters long, may only contain alphanumeric characters and /// underscores ("_"), and must start with an alphabetic character. UserProperty values can be up to /// 36 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and should not /// be used. #import /// The method used to sign in. For example, "google", "facebook" or "twitter". static NSString *const kFIRUserPropertySignUpMethod NS_SWIFT_NAME(AnalyticsUserPropertySignUpMethod) = @"sign_up_method"; /// Indicates whether events logged by Google Analytics can be used to personalize ads for the user. /// Set to "YES" to enable, or "NO" to disable. Default is enabled. See the /// documentation for /// more details and information about related settings. /// ///
///     [FIRAnalytics setUserPropertyString:@"NO"
///                                 forName:kFIRUserPropertyAllowAdPersonalizationSignals];
/// 
static NSString *const kFIRUserPropertyAllowAdPersonalizationSignals NS_SWIFT_NAME(AnalyticsUserPropertyAllowAdPersonalizationSignals) = @"allow_personalized_ads"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h ================================================ #import "FIRAnalytics+AppDelegate.h" #import "FIRAnalytics+Consent.h" #import "FIRAnalytics.h" #import "FIREventNames.h" #import "FIRParameterNames.h" #import "FIRUserPropertyNames.h" ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Info.plist ================================================ CFBundleExecutable FirebaseAnalytics CFBundleIdentifier com.firebase.Firebase-FirebaseAnalytics CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseAnalytics CFBundlePackageType FMWK CFBundleVersion 8.3.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_armv7/FirebaseAnalytics.framework/Modules/module.modulemap ================================================ framework module FirebaseAnalytics { umbrella header "FirebaseAnalytics.h" export * module * { export * } link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link framework "UIKit" link "c++" link "sqlite3" link "z" } ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIRAnalytics+AppDelegate.h ================================================ #import #import "FIRAnalytics.h" NS_ASSUME_NONNULL_BEGIN /** * Provides App Delegate handlers to be used in your App Delegate. * * To save time integrating Firebase Analytics in an application, Firebase Analytics does not * require delegation implementation from the AppDelegate. Instead this is automatically done by * Firebase Analytics. Should you choose instead to delegate manually, you can turn off the App * Delegate Proxy by adding FirebaseAppDelegateProxyEnabled into your app's Info.plist and setting * it to NO, and adding the methods in this category to corresponding delegation handlers. * * To handle Universal Links, you must return YES in * [UIApplicationDelegate application:didFinishLaunchingWithOptions:]. */ @interface FIRAnalytics (AppDelegate) /** * Handles events related to a URL session that are waiting to be processed. * * For optimal use of Firebase Analytics, call this method from the * [UIApplicationDelegate application:handleEventsForBackgroundURLSession:completionHandler] * method of the app delegate in your app. * * @param identifier The identifier of the URL session requiring attention. * @param completionHandler The completion handler to call when you finish processing the events. * Calling this completion handler lets the system know that your app's user interface is * updated and a new snapshot can be taken. */ + (void)handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(nullable void (^)(void))completionHandler; /** * Handles the event when the app is launched by a URL. * * Call this method from [UIApplicationDelegate application:openURL:options:] (on iOS 9.0 and * above), or [UIApplicationDelegate application:openURL:sourceApplication:annotation:] (on * iOS 8.x and below) in your app. * * @param url The URL resource to open. This resource can be a network resource or a file. */ + (void)handleOpenURL:(NSURL *)url; /** * Handles the event when the app receives data associated with user activity that includes a * Universal Link (on iOS 9.0 and above). * * Call this method from [UIApplication continueUserActivity:restorationHandler:] in your app * delegate (on iOS 9.0 and above). * * @param userActivity The activity object containing the data associated with the task the user * was performing. */ + (void)handleUserActivity:(id)userActivity; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIRAnalytics+Consent.h ================================================ #import #import "FIRAnalytics.h" NS_ASSUME_NONNULL_BEGIN /// The type of consent to set. Supported consent types are `ConsentType.adStorage` and /// `ConsentType.analyticsStorage`. Omitting a type retains its previous status. typedef NSString *FIRConsentType NS_TYPED_ENUM NS_SWIFT_NAME(ConsentType); extern FIRConsentType const FIRConsentTypeAdStorage; extern FIRConsentType const FIRConsentTypeAnalyticsStorage; /// The status value of the consent type. Supported statuses are `ConsentStatus.granted` and /// `ConsentStatus.denied`. typedef NSString *FIRConsentStatus NS_TYPED_ENUM NS_SWIFT_NAME(ConsentStatus); extern FIRConsentStatus const FIRConsentStatusDenied; extern FIRConsentStatus const FIRConsentStatusGranted; /// Sets the applicable end user consent state. @interface FIRAnalytics (Consent) /// Sets the applicable end user consent state (e.g. for device identifiers) for this app on this /// device. Use the consent settings to specify individual consent type values. Settings are /// persisted across app sessions. By default consent types are set to `ConsentStatus.granted`. /// /// @param consentSettings An NSDictionary of consent types. Supported consent type keys are /// `ConsentType.adStorage` and `ConsentType.analyticsStorage`. Valid values are /// `ConsentStatus.granted` and `ConsentStatus.denied`. + (void)setConsent:(NSDictionary *)consentSettings; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIRAnalytics.h ================================================ #import #import "FIREventNames.h" #import "FIRParameterNames.h" #import "FIRUserPropertyNames.h" NS_ASSUME_NONNULL_BEGIN /// The top level Firebase Analytics singleton that provides methods for logging events and setting /// user properties. See the developer guides for general /// information on using Firebase Analytics in your apps. /// /// @note The Analytics SDK uses SQLite to persist events and other app-specific data. Calling /// certain thread-unsafe global SQLite methods like `sqlite3_shutdown()` can result in /// unexpected crashes at runtime. NS_SWIFT_NAME(Analytics) @interface FIRAnalytics : NSObject /// Logs an app event. The event can have up to 25 parameters. Events with the same name must have /// the same parameters. Up to 500 event names are supported. Using predefined events and/or /// parameters is recommended for optimal reporting. /// /// The following event names are reserved and cannot be used: ///
    ///
  • ad_activeview
  • ///
  • ad_click
  • ///
  • ad_exposure
  • ///
  • ad_query
  • ///
  • ad_reward
  • ///
  • adunit_exposure
  • ///
  • app_background
  • ///
  • app_clear_data
  • ///
  • app_exception
  • ///
  • app_remove
  • ///
  • app_store_refund
  • ///
  • app_store_subscription_cancel
  • ///
  • app_store_subscription_convert
  • ///
  • app_store_subscription_renew
  • ///
  • app_update
  • ///
  • app_upgrade
  • ///
  • dynamic_link_app_open
  • ///
  • dynamic_link_app_update
  • ///
  • dynamic_link_first_open
  • ///
  • error
  • ///
  • firebase_campaign
  • ///
  • first_open
  • ///
  • first_visit
  • ///
  • in_app_purchase
  • ///
  • notification_dismiss
  • ///
  • notification_foreground
  • ///
  • notification_open
  • ///
  • notification_receive
  • ///
  • os_update
  • ///
  • session_start
  • ///
  • session_start_with_rollout
  • ///
  • user_engagement
  • ///
/// /// @param name The name of the event. Should contain 1 to 40 alphanumeric characters or /// underscores. The name must start with an alphabetic character. Some event names are /// reserved. See FIREventNames.h for the list of reserved event names. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used. Note that event names are /// case-sensitive and that logging two events whose names differ only in case will result in /// two distinct events. To manually log screen view events, use the `screen_view` event name. /// @param parameters The dictionary of event parameters. Passing nil indicates that the event has /// no parameters. Parameter names can be up to 40 characters long and must start with an /// alphabetic character and contain only alphanumeric characters and underscores. Only NSString /// and NSNumber (signed 64-bit integer and 64-bit floating-point number) parameter types are /// supported. NSString parameter values can be up to 100 characters long. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used for parameter names. + (void)logEventWithName:(NSString *)name parameters:(nullable NSDictionary *)parameters NS_SWIFT_NAME(logEvent(_:parameters:)); /// Sets a user property to a given value. Up to 25 user property names are supported. Once set, /// user property values persist throughout the app lifecycle and across sessions. /// /// The following user property names are reserved and cannot be used: ///
    ///
  • first_open_time
  • ///
  • last_deep_link_referrer
  • ///
  • user_id
  • ///
/// /// @param value The value of the user property. Values can be up to 36 characters long. Setting the /// value to nil removes the user property. /// @param name The name of the user property to set. Should contain 1 to 24 alphanumeric characters /// or underscores and must start with an alphabetic character. The "firebase_", "google_", and /// "ga_" prefixes are reserved and should not be used for user property names. + (void)setUserPropertyString:(nullable NSString *)value forName:(NSString *)name NS_SWIFT_NAME(setUserProperty(_:forName:)); /// Sets the user ID property. This feature must be used in accordance with /// Google's Privacy Policy /// /// @param userID The user ID to ascribe to the user of this app on this device, which must be /// non-empty and no more than 256 characters long. Setting userID to nil removes the user ID. + (void)setUserID:(nullable NSString *)userID; /// Sets whether analytics collection is enabled for this app on this device. This setting is /// persisted across app sessions. By default it is enabled. /// /// @param analyticsCollectionEnabled A flag that enables or disables Analytics collection. + (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; /// Sets the interval of inactivity in seconds that terminates the current session. The default /// value is 1800 seconds (30 minutes). /// /// @param sessionTimeoutInterval The custom time of inactivity in seconds before the current /// session terminates. + (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval; /// Returns the unique ID for this instance of the application or nil if /// `ConsentType.analyticsStorage` has been set to `ConsentStatus.denied`. /// /// @see `FIRAnalytics+Consent.h` + (nullable NSString *)appInstanceID; /// Clears all analytics data for this instance from the device and resets the app instance ID. /// FIRAnalyticsConfiguration values will be reset to the default values. + (void)resetAnalyticsData; /// Adds parameters that will be set on every event logged from the SDK, including automatic ones. /// The values passed in the parameters dictionary will be added to the dictionary of default event /// parameters. These parameters persist across app runs. They are of lower precedence than event /// parameters, so if an event parameter and a parameter set using this API have the same name, the /// value of the event parameter will be used. The same limitations on event parameters apply to /// default event parameters. /// /// @param parameters Parameters to be added to the dictionary of parameters added to every event. /// They will be added to the dictionary of default event parameters, replacing any existing /// parameter with the same name. Valid parameters are NSString and NSNumber (signed 64-bit /// integer and 64-bit floating-point number). Setting a key's value to [NSNull null] will clear /// that parameter. Passing in a nil dictionary will clear all parameters. + (void)setDefaultEventParameters:(nullable NSDictionary *)parameters; /// Unavailable. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIREventNames.h ================================================ /// @file FIREventNames.h /// /// Predefined event names. /// /// An Event is an important occurrence in your app that you want to measure. You can report up to /// 500 different types of Events per app and you can associate up to 25 unique parameters with each /// Event type. Some common events are suggested below, but you may also choose to specify custom /// Event types that are associated with your specific app. Each event type is identified by a /// unique name. Event names can be up to 40 characters long, may only contain alphanumeric /// characters and underscores ("_"), and must start with an alphabetic character. The "firebase_", /// "google_", and "ga_" prefixes are reserved and should not be used. #import /// Add Payment Info event. This event signifies that a user has submitted their payment /// information. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterPaymentType (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddPaymentInfo NS_SWIFT_NAME(AnalyticsEventAddPaymentInfo) = @"add_payment_info"; /// E-Commerce Add To Cart event. This event signifies that an item(s) was added to a cart for /// purchase. Add this event to a funnel with @c kFIREventPurchase to gauge the effectiveness of /// your checkout process. Note: If you supply the @c kFIRParameterValue parameter, you must also /// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddToCart NS_SWIFT_NAME(AnalyticsEventAddToCart) = @"add_to_cart"; /// E-Commerce Add To Wishlist event. This event signifies that an item was added to a wishlist. Use /// this event to identify popular gift items. Note: If you supply the @c kFIRParameterValue /// parameter, you must also supply the @c kFIRParameterCurrency parameter so that revenue metrics /// can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddToWishlist NS_SWIFT_NAME(AnalyticsEventAddToWishlist) = @"add_to_wishlist"; /// Ad Impression event. This event signifies when a user sees an ad impression. Note: If you supply /// the @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency parameter /// so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterAdPlatform (NSString) (optional)
  • ///
  • @c kFIRParameterAdFormat (NSString) (optional)
  • ///
  • @c kFIRParameterAdSource (NSString) (optional)
  • ///
  • @c kFIRParameterAdUnitName (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAdImpression NS_SWIFT_NAME(AnalyticsEventAdImpression) = @"ad_impression"; /// App Open event. By logging this event when an App becomes active, developers can understand how /// often users leave and return during the course of a Session. Although Sessions are automatically /// reported, this event can provide further clarification around the continuous engagement of /// app-users. static NSString *const kFIREventAppOpen NS_SWIFT_NAME(AnalyticsEventAppOpen) = @"app_open"; /// E-Commerce Begin Checkout event. This event signifies that a user has begun the process of /// checking out. Add this event to a funnel with your @c kFIREventPurchase event to gauge the /// effectiveness of your checkout process. Note: If you supply the @c kFIRParameterValue parameter, /// you must also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be /// computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventBeginCheckout NS_SWIFT_NAME(AnalyticsEventBeginCheckout) = @"begin_checkout"; /// Campaign Detail event. Log this event to supply the referral details of a re-engagement /// campaign. Note: you must supply at least one of the required parameters kFIRParameterSource, /// kFIRParameterMedium or kFIRParameterCampaign. Params: /// ///
    ///
  • @c kFIRParameterSource (NSString)
  • ///
  • @c kFIRParameterMedium (NSString)
  • ///
  • @c kFIRParameterCampaign (NSString)
  • ///
  • @c kFIRParameterTerm (NSString) (optional)
  • ///
  • @c kFIRParameterContent (NSString) (optional)
  • ///
  • @c kFIRParameterAdNetworkClickID (NSString) (optional)
  • ///
  • @c kFIRParameterCP1 (NSString) (optional)
  • ///
static NSString *const kFIREventCampaignDetails NS_SWIFT_NAME(AnalyticsEventCampaignDetails) = @"campaign_details"; /// Checkout progress. Params: /// ///
    ///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCheckoutOption (NSString) (optional)
  • ///
/// This constant has been deprecated. static NSString *const kFIREventCheckoutProgress NS_SWIFT_NAME(AnalyticsEventCheckoutProgress) = @"checkout_progress"; /// Earn Virtual Currency event. This event tracks the awarding of virtual currency in your app. Log /// this along with @c kFIREventSpendVirtualCurrency to better understand your virtual economy. /// Params: /// ///
    ///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • ///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • ///
static NSString *const kFIREventEarnVirtualCurrency NS_SWIFT_NAME(AnalyticsEventEarnVirtualCurrency) = @"earn_virtual_currency"; /// E-Commerce Purchase event. This event signifies that an item was purchased by a user. Note: /// This is different from the in-app purchase event, which is reported automatically for App /// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also /// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterLocation (NSString) (optional)
  • ///
  • @c kFIRParameterStartDate (NSString) (optional)
  • ///
  • @c kFIRParameterEndDate (NSString) (optional)
  • ///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) /// for travel bookings
  • ///
  • @c kFIRParameterOrigin (NSString) (optional)
  • ///
  • @c kFIRParameterDestination (NSString) (optional)
  • ///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • ///
/// This constant has been deprecated. Use @c kFIREventPurchase constant instead. static NSString *const kFIREventEcommercePurchase NS_SWIFT_NAME(AnalyticsEventEcommercePurchase) = @"ecommerce_purchase"; /// Generate Lead event. Log this event when a lead has been generated in the app to understand the /// efficacy of your install and re-engagement campaigns. Note: If you supply the /// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency /// parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventGenerateLead NS_SWIFT_NAME(AnalyticsEventGenerateLead) = @"generate_lead"; /// Join Group event. Log this event when a user joins a group such as a guild, team or family. Use /// this event to analyze how popular certain groups or social features are in your app. Params: /// ///
    ///
  • @c kFIRParameterGroupID (NSString)
  • ///
static NSString *const kFIREventJoinGroup NS_SWIFT_NAME(AnalyticsEventJoinGroup) = @"join_group"; /// Level End event. Log this event when the user finishes a level. Params: /// ///
    ///
  • @c kFIRParameterLevelName (NSString)
  • ///
  • @c kFIRParameterSuccess (NSString)
  • ///
static NSString *const kFIREventLevelEnd NS_SWIFT_NAME(AnalyticsEventLevelEnd) = @"level_end"; /// Level Start event. Log this event when the user starts a new level. Params: /// ///
    ///
  • @c kFIRParameterLevelName (NSString)
  • ///
static NSString *const kFIREventLevelStart NS_SWIFT_NAME(AnalyticsEventLevelStart) = @"level_start"; /// Level Up event. This event signifies that a player has leveled up in your gaming app. It can /// help you gauge the level distribution of your userbase and help you identify certain levels that /// are difficult to pass. Params: /// ///
    ///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCharacter (NSString) (optional)
  • ///
static NSString *const kFIREventLevelUp NS_SWIFT_NAME(AnalyticsEventLevelUp) = @"level_up"; /// Login event. Apps with a login feature can report this event to signify that a user has logged /// in. static NSString *const kFIREventLogin NS_SWIFT_NAME(AnalyticsEventLogin) = @"login"; /// Post Score event. Log this event when the user posts a score in your gaming app. This event can /// help you understand how users are actually performing in your game and it can help you correlate /// high scores with certain audiences or behaviors. Params: /// ///
    ///
  • @c kFIRParameterScore (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterLevel (signed 64-bit integer as NSNumber) (optional)
  • ///
  • @c kFIRParameterCharacter (NSString) (optional)
  • ///
static NSString *const kFIREventPostScore NS_SWIFT_NAME(AnalyticsEventPostScore) = @"post_score"; /// Present Offer event. This event signifies that the app has presented a purchase offer to a user. /// Add this event to a funnel with the kFIREventAddToCart and kFIREventEcommercePurchase to gauge /// your conversion process. Note: If you supply the @c kFIRParameterValue parameter, you must /// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterQuantity (signed 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
  • @c kFIRParameterItemName (NSString)
  • ///
  • @c kFIRParameterItemCategory (NSString)
  • ///
  • @c kFIRParameterItemLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPrice (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
/// This constant has been deprecated. Use @c kFIREventViewPromotion constant instead. static NSString *const kFIREventPresentOffer NS_SWIFT_NAME(AnalyticsEventPresentOffer) = @"present_offer"; /// E-Commerce Purchase Refund event. This event signifies that an item purchase was refunded. /// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. /// Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
/// This constant has been deprecated. Use @c kFIREventRefund constant instead. static NSString *const kFIREventPurchaseRefund NS_SWIFT_NAME(AnalyticsEventPurchaseRefund) = @"purchase_refund"; /// E-Commerce Remove from Cart event. This event signifies that an item(s) was removed from a cart. /// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the @c /// kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventRemoveFromCart NS_SWIFT_NAME(AnalyticsEventRemoveFromCart) = @"remove_from_cart"; /// Screen View event. This event signifies a screen view. Use this when a screen transition occurs. /// This event can be logged irrespective of whether automatic screen tracking is enabled. Params: /// ///
    ///
  • @c kFIRParameterScreenClass (NSString) (optional)
  • ///
  • @c kFIRParameterScreenName (NSString) (optional)
  • ///
static NSString *const kFIREventScreenView NS_SWIFT_NAME(AnalyticsEventScreenView) = @"screen_view"; /// Search event. Apps that support search features can use this event to contextualize search /// operations by supplying the appropriate, corresponding parameters. This event can help you /// identify the most popular content in your app. Params: /// ///
    ///
  • @c kFIRParameterSearchTerm (NSString)
  • ///
  • @c kFIRParameterStartDate (NSString) (optional)
  • ///
  • @c kFIRParameterEndDate (NSString) (optional)
  • ///
  • @c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for /// hotel bookings
  • ///
  • @c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional) /// for travel bookings
  • ///
  • @c kFIRParameterOrigin (NSString) (optional)
  • ///
  • @c kFIRParameterDestination (NSString) (optional)
  • ///
  • @c kFIRParameterTravelClass (NSString) (optional) for travel bookings
  • ///
static NSString *const kFIREventSearch NS_SWIFT_NAME(AnalyticsEventSearch) = @"search"; /// Select Content event. This general purpose event signifies that a user has selected some content /// of a certain type in an app. The content can be any object in your app. This event can help you /// identify popular content and categories of content in your app. Params: /// ///
    ///
  • @c kFIRParameterContentType (NSString)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
static NSString *const kFIREventSelectContent NS_SWIFT_NAME(AnalyticsEventSelectContent) = @"select_content"; /// Set checkout option. Params: /// ///
    ///
  • @c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)
  • ///
  • @c kFIRParameterCheckoutOption (NSString)
  • ///
/// This constant has been deprecated. static NSString *const kFIREventSetCheckoutOption NS_SWIFT_NAME(AnalyticsEventSetCheckoutOption) = @"set_checkout_option"; /// Share event. Apps with social features can log the Share event to identify the most viral /// content. Params: /// ///
    ///
  • @c kFIRParameterContentType (NSString)
  • ///
  • @c kFIRParameterItemID (NSString)
  • ///
static NSString *const kFIREventShare NS_SWIFT_NAME(AnalyticsEventShare) = @"share"; /// Sign Up event. This event indicates that a user has signed up for an account in your app. The /// parameter signifies the method by which the user signed up. Use this event to understand the /// different behaviors between logged in and logged out users. Params: /// ///
    ///
  • @c kFIRParameterSignUpMethod (NSString)
  • ///
static NSString *const kFIREventSignUp NS_SWIFT_NAME(AnalyticsEventSignUp) = @"sign_up"; /// Spend Virtual Currency event. This event tracks the sale of virtual goods in your app and can /// help you identify which virtual goods are the most popular objects of purchase. Params: /// ///
    ///
  • @c kFIRParameterItemName (NSString)
  • ///
  • @c kFIRParameterVirtualCurrencyName (NSString)
  • ///
  • @c kFIRParameterValue (signed 64-bit integer or double as NSNumber)
  • ///
static NSString *const kFIREventSpendVirtualCurrency NS_SWIFT_NAME(AnalyticsEventSpendVirtualCurrency) = @"spend_virtual_currency"; /// Tutorial Begin event. This event signifies the start of the on-boarding process in your app. Use /// this in a funnel with kFIREventTutorialComplete to understand how many users complete this /// process and move on to the full app experience. static NSString *const kFIREventTutorialBegin NS_SWIFT_NAME(AnalyticsEventTutorialBegin) = @"tutorial_begin"; /// Tutorial End event. Use this event to signify the user's completion of your app's on-boarding /// process. Add this to a funnel with kFIREventTutorialBegin to gauge the completion rate of your /// on-boarding process. static NSString *const kFIREventTutorialComplete NS_SWIFT_NAME(AnalyticsEventTutorialComplete) = @"tutorial_complete"; /// Unlock Achievement event. Log this event when the user has unlocked an achievement in your /// game. Since achievements generally represent the breadth of a gaming experience, this event can /// help you understand how many users are experiencing all that your game has to offer. Params: /// ///
    ///
  • @c kFIRParameterAchievementID (NSString)
  • ///
static NSString *const kFIREventUnlockAchievement NS_SWIFT_NAME(AnalyticsEventUnlockAchievement) = @"unlock_achievement"; /// View Item event. This event signifies that a user has viewed an item. Use the appropriate /// parameters to contextualize the event. Use this event to discover the most popular items viewed /// in your app. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventViewItem NS_SWIFT_NAME(AnalyticsEventViewItem) = @"view_item"; /// View Item List event. Log this event when a user sees a list of items or offerings. Params: /// ///
    ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterItemListID (NSString) (optional)
  • ///
  • @c kFIRParameterItemListName (NSString) (optional)
  • ///
static NSString *const kFIREventViewItemList NS_SWIFT_NAME(AnalyticsEventViewItemList) = @"view_item_list"; /// View Search Results event. Log this event when the user has been presented with the results of a /// search. Params: /// ///
    ///
  • @c kFIRParameterSearchTerm (NSString)
  • ///
static NSString *const kFIREventViewSearchResults NS_SWIFT_NAME(AnalyticsEventViewSearchResults) = @"view_search_results"; /// Add Shipping Info event. This event signifies that a user has submitted their shipping /// information. Note: If you supply the @c kFIRParameterValue parameter, you must also supply the /// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShippingTier (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventAddShippingInfo NS_SWIFT_NAME(AnalyticsEventAddShippingInfo) = @"add_shipping_info"; /// E-Commerce Purchase event. This event signifies that an item(s) was purchased by a user. Note: /// This is different from the in-app purchase event, which is reported automatically for App /// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also supply /// the @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately. /// Params: /// ///
    ///
  • @c kFIRParameterAffiliation (NSString) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventPurchase NS_SWIFT_NAME(AnalyticsEventPurchase) = @"purchase"; /// E-Commerce Refund event. This event signifies that a refund was issued. Note: If you supply the /// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency parameter so /// that revenue metrics can be computed accurately. Params: /// ///
    ///
  • @c kFIRParameterAffiliation (NSString) (optional)
  • ///
  • @c kFIRParameterCoupon (NSString) (optional)
  • ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterShipping (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTax (double as NSNumber) (optional)
  • ///
  • @c kFIRParameterTransactionID (NSString) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventRefund NS_SWIFT_NAME(AnalyticsEventRefund) = @"refund"; /// Select Item event. This event signifies that an item was selected by a user from a list. Use the /// appropriate parameters to contextualize the event. Use this event to discover the most popular /// items selected. Params: /// ///
    ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterItemListID (NSString) (optional)
  • ///
  • @c kFIRParameterItemListName (NSString) (optional)
  • ///
static NSString *const kFIREventSelectItem NS_SWIFT_NAME(AnalyticsEventSelectItem) = @"select_item"; /// Select promotion event. This event signifies that a user has selected a promotion offer. Use the /// appropriate parameters to contextualize the event, such as the item(s) for which the promotion /// applies. Params: /// ///
    ///
  • @c kFIRParameterCreativeName (NSString) (optional)
  • ///
  • @c kFIRParameterCreativeSlot (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionName (NSString) (optional)
  • ///
static NSString *const kFIREventSelectPromotion NS_SWIFT_NAME(AnalyticsEventSelectPromotion) = @"select_promotion"; /// E-commerce View Cart event. This event signifies that a user has viewed their cart. Use this to /// analyze your purchase funnel. Note: If you supply the @c kFIRParameterValue parameter, you must /// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed /// accurately. Params: /// ///
    ///
  • @c kFIRParameterCurrency (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterValue (double as NSNumber) (optional)
  • ///
static NSString *const kFIREventViewCart NS_SWIFT_NAME(AnalyticsEventViewCart) = @"view_cart"; /// View Promotion event. This event signifies that a promotion was shown to a user. Add this event /// to a funnel with the @c kFIREventAddToCart and @c kFIREventPurchase to gauge your conversion /// process. Params: /// ///
    ///
  • @c kFIRParameterCreativeName (NSString) (optional)
  • ///
  • @c kFIRParameterCreativeSlot (NSString) (optional)
  • ///
  • @c kFIRParameterItems (NSArray) (optional)
  • ///
  • @c kFIRParameterLocationID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionID (NSString) (optional)
  • ///
  • @c kFIRParameterPromotionName (NSString) (optional)
  • ///
static NSString *const kFIREventViewPromotion NS_SWIFT_NAME(AnalyticsEventViewPromotion) = @"view_promotion"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIRParameterNames.h ================================================ /// @file FIRParameterNames.h /// /// Predefined event parameter names. /// /// Params supply information that contextualize Events. You can associate up to 25 unique Params /// with each Event type. Some Params are suggested below for certain common Events, but you are /// not limited to these. You may supply extra Params for suggested Events or custom Params for /// Custom events. Param names can be up to 40 characters long, may only contain alphanumeric /// characters and underscores ("_"), and must start with an alphabetic character. Param values can /// be up to 100 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and /// should not be used. #import /// Game achievement ID (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAchievementID : @"10_matches_won",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAchievementID NS_SWIFT_NAME(AnalyticsParameterAchievementID) = @"achievement_id"; /// The ad format (e.g. Banner, Interstitial, Rewarded, Native, Rewarded Interstitial, Instream). /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdFormat : @"Banner",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdFormat NS_SWIFT_NAME(AnalyticsParameterAdFormat) = @"ad_format"; /// Ad Network Click ID (NSString). Used for network-specific click IDs which vary in format. ///
///     NSDictionary *params = @{
///       kFIRParameterAdNetworkClickID : @"1234567",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdNetworkClickID NS_SWIFT_NAME(AnalyticsParameterAdNetworkClickID) = @"aclid"; /// The ad platform (e.g. MoPub, IronSource) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdPlatform : @"MoPub",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdPlatform NS_SWIFT_NAME(AnalyticsParameterAdPlatform) = @"ad_platform"; /// The ad source (e.g. AdColony) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdSource : @"AdColony",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdSource NS_SWIFT_NAME(AnalyticsParameterAdSource) = @"ad_source"; /// The ad unit name (e.g. Banner_03) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterAdUnitName : @"Banner_03",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAdUnitName NS_SWIFT_NAME(AnalyticsParameterAdUnitName) = @"ad_unit_name"; /// A product affiliation to designate a supplying company or brick and mortar store location /// (NSString).
///     NSDictionary *params = @{
///       kFIRParameterAffiliation : @"Google Store",
///       // ...
///     };
/// 
static NSString *const kFIRParameterAffiliation NS_SWIFT_NAME(AnalyticsParameterAffiliation) = @"affiliation"; /// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to /// capture campaign information, otherwise can be populated by developer. Highly Recommended /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCampaign : @"winter_promotion",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) = @"campaign"; /// Character used in game (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCharacter : @"beat_boss",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCharacter NS_SWIFT_NAME(AnalyticsParameterCharacter) = @"character"; /// The checkout step (1..N) (unsigned 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterCheckoutStep : @"1",
///       // ...
///     };
/// 
/// This constant has been deprecated. static NSString *const kFIRParameterCheckoutStep NS_SWIFT_NAME(AnalyticsParameterCheckoutStep) = @"checkout_step"; /// Some option on a step in an ecommerce flow (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCheckoutOption : @"Visa",
///       // ...
///     };
/// 
/// This constant has been deprecated. static NSString *const kFIRParameterCheckoutOption NS_SWIFT_NAME(AnalyticsParameterCheckoutOption) = @"checkout_option"; /// Campaign content (NSString). static NSString *const kFIRParameterContent NS_SWIFT_NAME(AnalyticsParameterContent) = @"content"; /// Type of content selected (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterContentType : @"news article",
///       // ...
///     };
/// 
static NSString *const kFIRParameterContentType NS_SWIFT_NAME(AnalyticsParameterContentType) = @"content_type"; /// Coupon code used for a purchase (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCoupon : @"SUMMER_FUN",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCoupon NS_SWIFT_NAME(AnalyticsParameterCoupon) = @"coupon"; /// Campaign custom parameter (NSString). Used as a method of capturing custom data in a campaign. /// Use varies by network. ///
///     NSDictionary *params = @{
///       kFIRParameterCP1 : @"custom_data",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCP1 NS_SWIFT_NAME(AnalyticsParameterCP1) = @"cp1"; /// The name of a creative used in a promotional spot (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCreativeName : @"Summer Sale",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCreativeName NS_SWIFT_NAME(AnalyticsParameterCreativeName) = @"creative_name"; /// The name of a creative slot (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCreativeSlot : @"summer_banner2",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCreativeSlot NS_SWIFT_NAME(AnalyticsParameterCreativeSlot) = @"creative_slot"; /// Currency of the purchase or items associated with the event, in 3-letter /// ISO_4217 format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterCurrency : @"USD",
///       // ...
///     };
/// 
static NSString *const kFIRParameterCurrency NS_SWIFT_NAME(AnalyticsParameterCurrency) = @"currency"; /// Flight or Travel destination (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterDestination : @"Mountain View, CA",
///       // ...
///     };
/// 
static NSString *const kFIRParameterDestination NS_SWIFT_NAME(AnalyticsParameterDestination) = @"destination"; /// The arrival date, check-out date or rental end date for the item. This should be in /// YYYY-MM-DD format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterEndDate : @"2015-09-14",
///       // ...
///     };
/// 
static NSString *const kFIRParameterEndDate NS_SWIFT_NAME(AnalyticsParameterEndDate) = @"end_date"; /// Flight number for travel events (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterFlightNumber : @"ZZ800",
///       // ...
///     };
/// 
static NSString *const kFIRParameterFlightNumber NS_SWIFT_NAME(AnalyticsParameterFlightNumber) = @"flight_number"; /// Group/clan/guild ID (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterGroupID : @"g1",
///       // ...
///     };
/// 
static NSString *const kFIRParameterGroupID NS_SWIFT_NAME(AnalyticsParameterGroupID) = @"group_id"; /// The index of the item in a list (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterIndex : @(5),
///       // ...
///     };
/// 
static NSString *const kFIRParameterIndex NS_SWIFT_NAME(AnalyticsParameterIndex) = @"index"; /// Item brand (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemBrand : @"Google",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemBrand NS_SWIFT_NAME(AnalyticsParameterItemBrand) = @"item_brand"; /// Item category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory NS_SWIFT_NAME(AnalyticsParameterItemCategory) = @"item_category"; /// Item ID (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemID : @"SKU_12345",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemID NS_SWIFT_NAME(AnalyticsParameterItemID) = @"item_id"; /// The Google Place ID (NSString) that /// corresponds to the associated item. Alternatively, you can supply your own custom Location ID. ///
///     NSDictionary *params = @{
///       kFIRParameterItemLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
/// This constant has been deprecated. Use @c kFIRParameterLocationID constant instead. static NSString *const kFIRParameterItemLocationID NS_SWIFT_NAME(AnalyticsParameterItemLocationID) = @"item_location_id"; /// Item Name (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemName : @"jeggings",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemName NS_SWIFT_NAME(AnalyticsParameterItemName) = @"item_name"; /// The list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemList : @"Search Results",
///       // ...
///     };
/// 
/// This constant has been deprecated. Use @c kFIRParameterItemListName constant instead. static NSString *const kFIRParameterItemList NS_SWIFT_NAME(AnalyticsParameterItemList) = @"item_list"; /// Item variant (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemVariant : @"Black",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemVariant NS_SWIFT_NAME(AnalyticsParameterItemVariant) = @"item_variant"; /// Level in game (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterLevel : @(42),
///       // ...
///     };
/// 
static NSString *const kFIRParameterLevel NS_SWIFT_NAME(AnalyticsParameterLevel) = @"level"; /// Location (NSString). The Google Place ID /// that corresponds to the associated event. Alternatively, you can supply your own custom /// Location ID. ///
///     NSDictionary *params = @{
///       kFIRParameterLocation : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLocation NS_SWIFT_NAME(AnalyticsParameterLocation) = @"location"; /// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterMedium : @"email",
///       // ...
///     };
/// 
static NSString *const kFIRParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium"; /// Number of nights staying at hotel (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfNights : @(3),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfNights NS_SWIFT_NAME(AnalyticsParameterNumberOfNights) = @"number_of_nights"; /// Number of passengers traveling (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfPassengers : @(11),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfPassengers NS_SWIFT_NAME(AnalyticsParameterNumberOfPassengers) = @"number_of_passengers"; /// Number of rooms for travel events (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterNumberOfRooms : @(2),
///       // ...
///     };
/// 
static NSString *const kFIRParameterNumberOfRooms NS_SWIFT_NAME(AnalyticsParameterNumberOfRooms) = @"number_of_rooms"; /// Flight or Travel origin (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterOrigin : @"Mountain View, CA",
///       // ...
///     };
/// 
static NSString *const kFIRParameterOrigin NS_SWIFT_NAME(AnalyticsParameterOrigin) = @"origin"; /// Purchase price (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterPrice : @(1.0),
///       kFIRParameterCurrency : @"USD",  // e.g. $1.00 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterPrice NS_SWIFT_NAME(AnalyticsParameterPrice) = @"price"; /// Purchase quantity (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterQuantity : @(1),
///       // ...
///     };
/// 
static NSString *const kFIRParameterQuantity NS_SWIFT_NAME(AnalyticsParameterQuantity) = @"quantity"; /// Score in game (signed 64-bit integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterScore : @(4200),
///       // ...
///     };
/// 
static NSString *const kFIRParameterScore NS_SWIFT_NAME(AnalyticsParameterScore) = @"score"; /// Current screen class, such as the class name of the UIViewController, logged with screen_view /// event and added to every event (NSString).
///     NSDictionary *params = @{
///       kFIRParameterScreenClass : @"LoginViewController",
///       // ...
///     };
/// 
static NSString *const kFIRParameterScreenClass NS_SWIFT_NAME(AnalyticsParameterScreenClass) = @"screen_class"; /// Current screen name, such as the name of the UIViewController, logged with screen_view event and /// added to every event (NSString).
///     NSDictionary *params = @{
///       kFIRParameterScreenName : @"LoginView",
///       // ...
///     };
/// 
static NSString *const kFIRParameterScreenName NS_SWIFT_NAME(AnalyticsParameterScreenName) = @"screen_name"; /// The search string/keywords used (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSearchTerm : @"periodic table",
///       // ...
///     };
/// 
static NSString *const kFIRParameterSearchTerm NS_SWIFT_NAME(AnalyticsParameterSearchTerm) = @"search_term"; /// Shipping cost associated with a transaction (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterShipping : @(5.99),
///       kFIRParameterCurrency : @"USD",  // e.g. $5.99 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterShipping NS_SWIFT_NAME(AnalyticsParameterShipping) = @"shipping"; /// Sign up method (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSignUpMethod : @"google",
///       // ...
///     };
/// 
/// /// This constant has been deprecated. Use Method constant instead. static NSString *const kFIRParameterSignUpMethod NS_SWIFT_NAME(AnalyticsParameterSignUpMethod) = @"sign_up_method"; /// A particular approach used in an operation; for example, "facebook" or "email" in the context /// of a sign_up or login event. (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterMethod : @"google",
///       // ...
///     };
/// 
static NSString *const kFIRParameterMethod NS_SWIFT_NAME(AnalyticsParameterMethod) = @"method"; /// The origin of your traffic, such as an Ad network (for example, google) or partner (urban /// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your /// property. Highly recommended (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterSource : @"InMobi",
///       // ...
///     };
/// 
static NSString *const kFIRParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source"; /// The departure date, check-in date or rental start date for the item. This should be in /// YYYY-MM-DD format (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterStartDate : @"2015-09-14",
///       // ...
///     };
/// 
static NSString *const kFIRParameterStartDate NS_SWIFT_NAME(AnalyticsParameterStartDate) = @"start_date"; /// Tax cost associated with a transaction (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterTax : @(2.43),
///       kFIRParameterCurrency : @"USD",  // e.g. $2.43 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterTax NS_SWIFT_NAME(AnalyticsParameterTax) = @"tax"; /// If you're manually tagging keyword campaigns, you should use utm_term to specify the keyword /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTerm : @"game",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTerm NS_SWIFT_NAME(AnalyticsParameterTerm) = @"term"; /// The unique identifier of a transaction (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTransactionID : @"T12345",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTransactionID NS_SWIFT_NAME(AnalyticsParameterTransactionID) = @"transaction_id"; /// Travel class (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterTravelClass : @"business",
///       // ...
///     };
/// 
static NSString *const kFIRParameterTravelClass NS_SWIFT_NAME(AnalyticsParameterTravelClass) = @"travel_class"; /// A context-specific numeric value which is accumulated automatically for each event type. This is /// a general purpose parameter that is useful for accumulating a key metric that pertains to an /// event. Examples include revenue, distance, time and points. Value should be specified as signed /// 64-bit integer or double as NSNumber. Notes: Values for pre-defined currency-related events /// (such as @c kFIREventAddToCart) should be supplied using double as NSNumber and must be /// accompanied by a @c kFIRParameterCurrency parameter. The valid range of accumulated values is /// [-9,223,372,036,854.77, 9,223,372,036,854.77]. Supplying a non-numeric value, omitting the /// corresponding @c kFIRParameterCurrency parameter, or supplying an invalid /// currency code for conversion events will cause that /// conversion to be omitted from reporting. ///
///     NSDictionary *params = @{
///       kFIRParameterValue : @(3.99),
///       kFIRParameterCurrency : @"USD",  // e.g. $3.99 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterValue NS_SWIFT_NAME(AnalyticsParameterValue) = @"value"; /// Name of virtual currency type (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterVirtualCurrencyName : @"virtual_currency_name",
///       // ...
///     };
/// 
static NSString *const kFIRParameterVirtualCurrencyName NS_SWIFT_NAME(AnalyticsParameterVirtualCurrencyName) = @"virtual_currency_name"; /// The name of a level in a game (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterLevelName : @"room_1",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLevelName NS_SWIFT_NAME(AnalyticsParameterLevelName) = @"level_name"; /// The result of an operation. Specify 1 to indicate success and 0 to indicate failure (unsigned /// integer as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterSuccess : @(1),
///       // ...
///     };
/// 
static NSString *const kFIRParameterSuccess NS_SWIFT_NAME(AnalyticsParameterSuccess) = @"success"; /// Indicates that the associated event should either extend the current session /// or start a new session if no session was active when the event was logged. /// Specify YES to extend the current session or to start a new session; any /// other value will not extend or start a session. ///
///     NSDictionary *params = @{
///       kFIRParameterExtendSession : @YES,
///       // ...
///     };
/// 
static NSString *const kFIRParameterExtendSession NS_SWIFT_NAME(AnalyticsParameterExtendSession) = @"extend_session"; /// Monetary value of discount associated with a purchase (double as NSNumber). ///
///     NSDictionary *params = @{
///       kFIRParameterDiscount : @(2.0),
///       kFIRParameterCurrency : @"USD",  // e.g. $2.00 USD
///       // ...
///     };
/// 
static NSString *const kFIRParameterDiscount NS_SWIFT_NAME(AnalyticsParameterDiscount) = @"discount"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory2 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory2 NS_SWIFT_NAME(AnalyticsParameterItemCategory2) = @"item_category2"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory3 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory3 NS_SWIFT_NAME(AnalyticsParameterItemCategory3) = @"item_category3"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory4 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory4 NS_SWIFT_NAME(AnalyticsParameterItemCategory4) = @"item_category4"; /// Item Category (context-specific) (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemCategory5 : @"pants",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemCategory5 NS_SWIFT_NAME(AnalyticsParameterItemCategory5) = @"item_category5"; /// The ID of the list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemListID : @"ABC123",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemListID NS_SWIFT_NAME(AnalyticsParameterItemListID) = @"item_list_id"; /// The name of the list in which the item was presented to the user (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterItemListName : @"Related products",
///       // ...
///     };
/// 
static NSString *const kFIRParameterItemListName NS_SWIFT_NAME(AnalyticsParameterItemListName) = @"item_list_name"; /// The list of items involved in the transaction. (NSArray). ///
///     NSDictionary *params = @{
///       kFIRParameterItems : @[
///         @{kFIRParameterItemName : @"jeggings", kFIRParameterItemCategory : @"pants"},
///         @{kFIRParameterItemName : @"boots", kFIRParameterItemCategory : @"shoes"},
///       ],
///     };
/// 
static NSString *const kFIRParameterItems NS_SWIFT_NAME(AnalyticsParameterItems) = @"items"; /// The location associated with the event. Preferred to be the Google /// Place ID that corresponds to the /// associated item but could be overridden to a custom location ID string.(NSString).
///     NSDictionary *params = @{
///       kFIRParameterLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
///       // ...
///     };
/// 
static NSString *const kFIRParameterLocationID NS_SWIFT_NAME(AnalyticsParameterLocationID) = @"location_id"; /// The chosen method of payment (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPaymentType : @"Visa",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPaymentType NS_SWIFT_NAME(AnalyticsParameterPaymentType) = @"payment_type"; /// The ID of a product promotion (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPromotionID : @"ABC123",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPromotionID NS_SWIFT_NAME(AnalyticsParameterPromotionID) = @"promotion_id"; /// The name of a product promotion (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterPromotionName : @"Summer Sale",
///       // ...
///     };
/// 
static NSString *const kFIRParameterPromotionName NS_SWIFT_NAME(AnalyticsParameterPromotionName) = @"promotion_name"; /// The shipping tier (e.g. Ground, Air, Next-day) selected for delivery of the purchased item /// (NSString). ///
///     NSDictionary *params = @{
///       kFIRParameterShippingTier : @"Ground",
///       // ...
///     };
/// 
static NSString *const kFIRParameterShippingTier NS_SWIFT_NAME(AnalyticsParameterShippingTier) = @"shipping_tier"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FIRUserPropertyNames.h ================================================ /// @file FIRUserPropertyNames.h /// /// Predefined user property names. /// /// A UserProperty is an attribute that describes the app-user. By supplying UserProperties, you can /// later analyze different behaviors of various segments of your userbase. You may supply up to 25 /// unique UserProperties per app, and you can use the name and value of your choosing for each one. /// UserProperty names can be up to 24 characters long, may only contain alphanumeric characters and /// underscores ("_"), and must start with an alphabetic character. UserProperty values can be up to /// 36 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and should not /// be used. #import /// The method used to sign in. For example, "google", "facebook" or "twitter". static NSString *const kFIRUserPropertySignUpMethod NS_SWIFT_NAME(AnalyticsUserPropertySignUpMethod) = @"sign_up_method"; /// Indicates whether events logged by Google Analytics can be used to personalize ads for the user. /// Set to "YES" to enable, or "NO" to disable. Default is enabled. See the /// documentation for /// more details and information about related settings. /// ///
///     [FIRAnalytics setUserPropertyString:@"NO"
///                                 forName:kFIRUserPropertyAllowAdPersonalizationSignals];
/// 
static NSString *const kFIRUserPropertyAllowAdPersonalizationSignals NS_SWIFT_NAME(AnalyticsUserPropertyAllowAdPersonalizationSignals) = @"allow_personalized_ads"; ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Headers/FirebaseAnalytics.h ================================================ #import "FIRAnalytics+AppDelegate.h" #import "FIRAnalytics+Consent.h" #import "FIRAnalytics.h" #import "FIREventNames.h" #import "FIRParameterNames.h" #import "FIRUserPropertyNames.h" ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Info.plist ================================================ CFBundleExecutable FirebaseAnalytics CFBundleIdentifier com.firebase.Firebase-FirebaseAnalytics CFBundleInfoDictionaryVersion 6.0 CFBundleName FirebaseAnalytics CFBundlePackageType FMWK CFBundleVersion 8.3.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/ios-arm64_i386_x86_64-simulator/FirebaseAnalytics.framework/Modules/module.modulemap ================================================ framework module FirebaseAnalytics { umbrella header "FirebaseAnalytics.h" export * module * { export * } link framework "CoreTelephony" link framework "Foundation" link framework "Security" link framework "SystemConfiguration" link framework "UIKit" link "c++" link "sqlite3" link "z" } ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.h ================================================ /* * Copyright 2017 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 /// Values stored in analyticsEnabledState. Never alter these constants since they must match with /// values persisted to disk. typedef NS_ENUM(int64_t, FIRAnalyticsEnabledState) { // 0 is the default value for keys not found stored in persisted config, so it cannot represent // kFIRAnalyticsEnabledStateSetNo. It must represent kFIRAnalyticsEnabledStateNotSet. kFIRAnalyticsEnabledStateNotSet = 0, kFIRAnalyticsEnabledStateSetYes = 1, kFIRAnalyticsEnabledStateSetNo = 2, }; /// The user defaults key for the persisted measurementEnabledState value. FIRAPersistedConfig reads /// measurementEnabledState using this same key. static NSString *const kFIRAPersistedConfigMeasurementEnabledStateKey = @"/google/measurement/measurement_enabled_state"; static NSString *const kFIRAnalyticsConfigurationSetEnabledNotification = @"FIRAnalyticsConfigurationSetEnabledNotification"; static NSString *const kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification = @"FIRAnalyticsConfigurationSetMinimumSessionIntervalNotification"; static NSString *const kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification = @"FIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification"; @interface FIRAnalyticsConfiguration : NSObject /// Returns the shared instance of FIRAnalyticsConfiguration. + (FIRAnalyticsConfiguration *)sharedInstance; // Sets whether analytics collection is enabled for this app on this device. This setting is // persisted across app sessions. By default it is enabled. - (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; /// Sets whether analytics collection is enabled for this app on this device, and a flag to persist /// the value or not. The setting should not be persisted if being set by the global data collection /// flag. - (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled persistSetting:(BOOL)shouldPersist; @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/FIRAnalyticsConfiguration.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" @implementation FIRAnalyticsConfiguration #pragma clang diagnostic pop + (FIRAnalyticsConfiguration *)sharedInstance { static FIRAnalyticsConfiguration *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[FIRAnalyticsConfiguration alloc] init]; }); return sharedInstance; } - (void)postNotificationName:(NSString *)name value:(id)value { if (!name.length || !value) { return; } [[NSNotificationCenter defaultCenter] postNotificationName:name object:self userInfo:@{name : value}]; } - (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled { [self setAnalyticsCollectionEnabled:analyticsCollectionEnabled persistSetting:YES]; } - (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled persistSetting:(BOOL)shouldPersist { // Persist the measurementEnabledState. Use FIRAnalyticsEnabledState values instead of YES/NO. FIRAnalyticsEnabledState analyticsEnabledState = analyticsCollectionEnabled ? kFIRAnalyticsEnabledStateSetYes : kFIRAnalyticsEnabledStateSetNo; if (shouldPersist) { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setObject:@(analyticsEnabledState) forKey:kFIRAPersistedConfigMeasurementEnabledStateKey]; [userDefaults synchronize]; } [self postNotificationName:kFIRAnalyticsConfigurationSetEnabledNotification value:@(analyticsCollectionEnabled)]; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRApp.m ================================================ // Copyright 2017 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. #include #if __has_include() #import #endif #if __has_include() #import #endif #import "FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h" #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h" #import "FirebaseCore/Sources/FIRBundleUtil.h" #import "FirebaseCore/Sources/FIRComponentContainerInternal.h" #import "FirebaseCore/Sources/FIRConfigurationInternal.h" #import "FirebaseCore/Sources/FIRFirebaseUserAgent.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" #import #import // The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics // pod to a Core dependency. These symbols are not used and should be deleted after the transition. NSString *const kFIRServiceAdMob; NSString *const kFIRServiceAuth; NSString *const kFIRServiceAuthUI; NSString *const kFIRServiceCrash; NSString *const kFIRServiceDatabase; NSString *const kFIRServiceDynamicLinks; NSString *const kFIRServiceFirestore; NSString *const kFIRServiceFunctions; NSString *const kFIRServiceInstanceID; NSString *const kFIRServiceInvites; NSString *const kFIRServiceMessaging; NSString *const kFIRServiceMeasurement; NSString *const kFIRServicePerformance; NSString *const kFIRServiceRemoteConfig; NSString *const kFIRServiceStorage; NSString *const kGGLServiceAnalytics; NSString *const kGGLServiceSignIn; NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT"; NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification"; NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification"; NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey"; NSString *const kFIRAppNameKey = @"FIRAppNameKey"; NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey"; NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat = @"/google/firebase/global_data_collection_enabled:%@"; NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey = @"FirebaseDataCollectionDefaultEnabled"; NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType"; NSString *const kFIRAppDiagnosticsErrorKey = @"Error"; NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp"; NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName"; NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion"; NSString *const kFIRAppDiagnosticsApplePlatformPrefix = @"apple-platform"; // Auth internal notification notification and key. NSString *const FIRAuthStateDidChangeInternalNotification = @"FIRAuthStateDidChangeInternalNotification"; NSString *const FIRAuthStateDidChangeInternalNotificationAppKey = @"FIRAuthStateDidChangeInternalNotificationAppKey"; NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey = @"FIRAuthStateDidChangeInternalNotificationTokenKey"; NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey = @"FIRAuthStateDidChangeInternalNotificationUIDKey"; /** * Error domain for exceptions and NSError construction. */ NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core"; /** The NSUserDefaults suite name for FirebaseCore, for those storage locations that use it. */ NSString *const kFirebaseCoreDefaultsSuiteName = @"com.firebase.core"; /** * The URL to download plist files. */ static NSString *const kPlistURL = @"https://console.firebase.google.com/"; /** * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle * events from Core. */ static NSMutableArray> *sRegisteredAsConfigurable; @interface FIRApp () #ifdef DEBUG @property(nonatomic) BOOL alreadyOutputDataCollectionFlag; #endif // DEBUG @end @implementation FIRApp // This is necessary since our custom getter prevents `_options` from being created. @synthesize options = _options; static NSMutableDictionary *sAllApps; static FIRApp *sDefaultApp; + (void)configure { FIROptions *options = [FIROptions defaultOptions]; if (!options) { [NSException raise:kFirebaseCoreErrorDomain format:@"`FirebaseApp.configure()` could not find " @"a valid GoogleService-Info.plist in your project. Please download one " @"from %@.", kPlistURL]; } [FIRApp configureWithOptions:options]; #if TARGET_OS_OSX || TARGET_OS_TV FIRLogNotice(kFIRLoggerCore, @"I-COR000028", @"tvOS and macOS SDK support is not part of the official Firebase product. " @"Instead they are community supported. Details at " @"https://github.com/firebase/firebase-ios-sdk/blob/master/README.md."); #endif } + (void)configureWithOptions:(FIROptions *)options { if (!options) { [NSException raise:kFirebaseCoreErrorDomain format:@"Options is nil. Please pass a valid options."]; } [FIRApp configureWithName:kFIRDefaultAppName options:options]; } + (NSCharacterSet *)applicationNameAllowedCharacters { static NSCharacterSet *applicationNameAllowedCharacters; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSMutableCharacterSet *allowedNameCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; [allowedNameCharacters addCharactersInString:@"-_"]; applicationNameAllowedCharacters = [allowedNameCharacters copy]; }); return applicationNameAllowedCharacters; } + (void)configureWithName:(NSString *)name options:(FIROptions *)options { if (!name || !options) { [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."]; } if (name.length == 0) { [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."]; } if ([name isEqualToString:kFIRDefaultAppName]) { if (sDefaultApp) { // The default app already exists. Handle duplicate `configure` calls and return. [self appWasConfiguredTwice:sDefaultApp usingOptions:options]; return; } FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app."); } else { // Validate the app name and ensure it hasn't been configured already. NSCharacterSet *nameCharacters = [NSCharacterSet characterSetWithCharactersInString:name]; if (![[self applicationNameAllowedCharacters] isSupersetOfSet:nameCharacters]) { [NSException raise:kFirebaseCoreErrorDomain format:@"App name can only contain alphanumeric, " @"hyphen (-), and underscore (_) characters"]; } @synchronized(self) { if (sAllApps && sAllApps[name]) { // The app already exists. Handle a duplicate `configure` call and return. [self appWasConfiguredTwice:sAllApps[name] usingOptions:options]; return; } } FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name); } @synchronized(self) { FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options]; if (app.isDefaultApp) { sDefaultApp = app; } [FIRApp addAppToAppDictionary:app]; // The FIRApp instance is ready to go, `sDefaultApp` is assigned, other SDKs are now ready to be // instantiated. [app.container instantiateEagerComponents]; [FIRApp sendNotificationsToSDKs:app]; } } /// Called when `configure` has been called multiple times for the same app. This can either throw /// an exception (most cases) or ignore the duplicate configuration in situations where it's allowed /// like an extension. + (void)appWasConfiguredTwice:(FIRApp *)app usingOptions:(FIROptions *)options { // Only extensions should potentially be able to call `configure` more than once. if (![GULAppEnvironmentUtil isAppExtension]) { // Throw an exception since this is now an invalid state. if (app.isDefaultApp) { [NSException raise:kFirebaseCoreErrorDomain format:@"Default app has already been configured."]; } else { [NSException raise:kFirebaseCoreErrorDomain format:@"App named %@ has already been configured.", app.name]; } } // In an extension, the entry point could be called multiple times. As long as the options are // identical we should allow multiple `configure` calls. if ([options isEqual:app.options]) { // Everything is identical but the extension's lifecycle triggered `configure` twice. // Ignore duplicate calls and return since everything should still be in a valid state. FIRLogDebug(kFIRLoggerCore, @"I-COR000035", @"Ignoring second `configure` call in an extension."); return; } else { [NSException raise:kFirebaseCoreErrorDomain format:@"App named %@ has already been configured.", app.name]; } } + (FIRApp *)defaultApp { if (sDefaultApp) { return sDefaultApp; } FIRLogError(kFIRLoggerCore, @"I-COR000003", @"The default Firebase app has not yet been " @"configured. Add `FirebaseApp.configure()` to your " @"application initialization. This can be done in " @"in the App Delegate's application(_:didFinishLaunchingWithOptions:)` " @"(or the `@main` struct's initializer in SwiftUI). " @"Read more: https://goo.gl/ctyzm8."); return nil; } + (FIRApp *)appNamed:(NSString *)name { @synchronized(self) { if (sAllApps) { FIRApp *app = sAllApps[name]; if (app) { return app; } } FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name); return nil; } } + (NSDictionary *)allApps { @synchronized(self) { if (!sAllApps) { FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet."); } return [sAllApps copy]; } } // Public only for tests + (void)resetApps { @synchronized(self) { sDefaultApp = nil; [sAllApps removeAllObjects]; sAllApps = nil; [[self userAgent] reset]; } } - (void)deleteApp:(FIRAppVoidBoolCallback)completion { @synchronized([self class]) { if (sAllApps && sAllApps[self.name]) { FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name); // Remove all registered libraries from the container to avoid creating new instances. [self.container removeAllComponents]; // Remove all cached instances from the container before deleting the app. [self.container removeAllCachedInstances]; [sAllApps removeObjectForKey:self.name]; [self clearDataCollectionSwitchFromUserDefaults]; if ([self.name isEqualToString:kFIRDefaultAppName]) { sDefaultApp = nil; } NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name}; [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification object:[self class] userInfo:appInfoDict]; completion(YES); } else { FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist."); completion(NO); } } } + (void)addAppToAppDictionary:(FIRApp *)app { if (!sAllApps) { sAllApps = [NSMutableDictionary dictionary]; } if ([app configureCore]) { sAllApps[app.name] = app; } else { [NSException raise:kFirebaseCoreErrorDomain format:@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in " @"GoogleService-Info.plist or set in the customized options."]; } } - (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options { self = [super init]; if (self) { _name = [name copy]; _options = [options copy]; _options.editingLocked = YES; _isDefaultApp = [name isEqualToString:kFIRDefaultAppName]; _container = [[FIRComponentContainer alloc] initWithApp:self]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (BOOL)configureCore { [self checkExpectedBundleID]; if (![self isAppIDValid]) { return NO; } // Initialize the Analytics once there is a valid options under default app. Analytics should // always initialize first by itself before the other SDKs. if ([self.name isEqualToString:kFIRDefaultAppName]) { Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics"); if (firAnalyticsClass) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:); #pragma clang diagnostic pop if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #pragma clang diagnostic ignored "-Wdeprecated-declarations" [firAnalyticsClass performSelector:startWithConfigurationSelector withObject:[FIRConfiguration sharedInstance].analyticsConfiguration withObject:_options]; #pragma clang diagnostic pop } } } [self subscribeForAppDidBecomeActiveNotifications]; return YES; } - (FIROptions *)options { return [_options copy]; } - (void)setDataCollectionDefaultEnabled:(BOOL)dataCollectionDefaultEnabled { #ifdef DEBUG FIRLogDebug(kFIRLoggerCore, @"I-COR000034", @"Explicitly %@ data collection flag.", dataCollectionDefaultEnabled ? @"enabled" : @"disabled"); self.alreadyOutputDataCollectionFlag = YES; #endif // DEBUG NSString *key = [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name]; [[NSUserDefaults standardUserDefaults] setBool:dataCollectionDefaultEnabled forKey:key]; // Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set // within FIROptions and change the Analytics value if necessary. Analytics only works with the // default app, so return if this isn't the default app. if (!self.isDefaultApp) { return; } // Check if the Analytics flag is explicitly set. If so, no further actions are necessary. if ([self.options isAnalyticsCollectionExplicitlySet]) { return; } // The Analytics flag has not been explicitly set, so update with the value being set. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[FIRAnalyticsConfiguration sharedInstance] setAnalyticsCollectionEnabled:dataCollectionDefaultEnabled persistSetting:NO]; #pragma clang diagnostic pop } - (BOOL)isDataCollectionDefaultEnabled { // Check if it's been manually set before in code, and use that as the higher priority value. NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self]; if (defaultsObject != nil) { #ifdef DEBUG if (!self.alreadyOutputDataCollectionFlag) { FIRLogDebug(kFIRLoggerCore, @"I-COR000031", @"Data Collection flag is %@ in user defaults.", [defaultsObject boolValue] ? @"enabled" : @"disabled"); self.alreadyOutputDataCollectionFlag = YES; } #endif // DEBUG return [defaultsObject boolValue]; } // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`. // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has // no performance impact calling multiple times. NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist]; if (collectionEnabledPlistValue != nil) { #ifdef DEBUG if (!self.alreadyOutputDataCollectionFlag) { FIRLogDebug(kFIRLoggerCore, @"I-COR000032", @"Data Collection flag is %@ in plist.", [collectionEnabledPlistValue boolValue] ? @"enabled" : @"disabled"); self.alreadyOutputDataCollectionFlag = YES; } #endif // DEBUG return [collectionEnabledPlistValue boolValue]; } #ifdef DEBUG if (!self.alreadyOutputDataCollectionFlag) { FIRLogDebug(kFIRLoggerCore, @"I-COR000033", @"Data Collection flag is not set."); self.alreadyOutputDataCollectionFlag = YES; } #endif // DEBUG return YES; } #pragma mark - private + (void)sendNotificationsToSDKs:(FIRApp *)app { // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`. NSNumber *isDefaultApp = [NSNumber numberWithBool:app.isDefaultApp]; NSDictionary *appInfoDict = @{ kFIRAppNameKey : app.name, kFIRAppIsDefaultAppKey : isDefaultApp, kFIRGoogleAppIDKey : app.options.googleAppID }; [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification object:self userInfo:appInfoDict]; // This is the new way of sending information to SDKs. // TODO: Do we want this on a background thread, maybe? @synchronized(self) { for (Class library in sRegisteredAsConfigurable) { [library configureWithApp:app]; } } } + (NSError *)errorForMissingOptions { NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : @"Unable to parse GoogleService-Info.plist in order to configure services.", NSLocalizedRecoverySuggestionErrorKey : @"Check formatting and location of GoogleService-Info.plist." }; return [NSError errorWithDomain:kFirebaseCoreErrorDomain code:-100 userInfo:errorDict]; } + (NSError *)errorForInvalidAppID { NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : @"Unable to validate Google App ID", NSLocalizedRecoverySuggestionErrorKey : @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the " @"customized options." }; return [NSError errorWithDomain:kFirebaseCoreErrorDomain code:-101 userInfo:errorDict]; } + (BOOL)isDefaultAppConfigured { return (sDefaultApp != nil); } + (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version { // Create the set of characters which aren't allowed, only if this feature is used. NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet]; [allowedSet addCharactersInString:@"-_."]; NSCharacterSet *disallowedSet = [allowedSet invertedSet]; // Make sure the library name and version strings do not contain unexpected characters, and // add the name/version pair to the dictionary. if ([name rangeOfCharacterFromSet:disallowedSet].location == NSNotFound && [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) { [[self userAgent] setValue:version forComponent:name]; } else { FIRLogError(kFIRLoggerCore, @"I-COR000027", @"The library name (%@) or version number (%@) contain invalid characters. " @"Only alphanumeric, dash, underscore and period characters are allowed.", name, version); } } + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name { [self registerInternalLibrary:library withName:name withVersion:FIRFirebaseVersion()]; } + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name withVersion:(nonnull NSString *)version { // This is called at +load time, keep the work to a minimum. // Ensure the class given conforms to the proper protocol. if (![(Class)library conformsToProtocol:@protocol(FIRLibrary)] || ![(Class)library respondsToSelector:@selector(componentsToRegister)]) { [NSException raise:NSInvalidArgumentException format:@"Class %@ attempted to register components, but it does not conform to " @"`FIRLibrary or provide a `componentsToRegister:` method.", library]; } [FIRComponentContainer registerAsComponentRegistrant:library]; if ([(Class)library respondsToSelector:@selector(configureWithApp:)]) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sRegisteredAsConfigurable = [[NSMutableArray alloc] init]; }); @synchronized(self) { [sRegisteredAsConfigurable addObject:library]; } } [self registerLibrary:name withVersion:version]; } + (FIRFirebaseUserAgent *)userAgent { static dispatch_once_t onceToken; static FIRFirebaseUserAgent *_userAgent; dispatch_once(&onceToken, ^{ _userAgent = [[FIRFirebaseUserAgent alloc] init]; [_userAgent setValue:FIRFirebaseVersion() forComponent:@"fire-ios"]; }); return _userAgent; } + (NSString *)firebaseUserAgent { return [[self userAgent] firebaseUserAgent]; } - (void)checkExpectedBundleID { NSArray *bundles = [FIRBundleUtil relevantBundles]; NSString *expectedBundleID = [self expectedBundleID]; // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for // backward compatibility. if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifierPrefix:expectedBundleID inBundles:bundles]) { FIRLogError(kFIRLoggerCore, @"I-COR000008", @"The project's Bundle ID is inconsistent with " @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are " @"using a customized options. To ensure that everything can be configured " @"correctly, you may need to make the Bundle IDs consistent. To continue with this " @"plist file, you may change your app's bundle identifier to '%@'. Or you can " @"download a new configuration file that matches your bundle identifier from %@ " @"and replace the current one.", kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL); } } #pragma mark - private - App ID Validation /** * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file. * This is the main method for validating app ID. * * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise. */ - (BOOL)isAppIDValid { NSString *appID = _options.googleAppID; BOOL isValid = [FIRApp validateAppID:appID]; if (!isValid) { NSString *expectedBundleID = [self expectedBundleID]; FIRLogError(kFIRLoggerCore, @"I-COR000009", @"The GOOGLE_APP_ID either in the plist file " @"'%@.%@' or the one set in the customized options is invalid. If you are using " @"the plist file, use the iOS version of bundle identifier to download the file, " @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle " @"identifier to '%@'. Or you can download a new configuration file that matches " @"your bundle identifier from %@ and replace the current one.", kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL); }; return isValid; } + (BOOL)validateAppID:(NSString *)appID { // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not // have a valid fingerprint, otherwise we just warn about the potential issue. if (!appID.length) { return NO; } NSScanner *stringScanner = [NSScanner scannerWithString:appID]; stringScanner.charactersToBeSkipped = nil; NSString *appIDVersion; if (![stringScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&appIDVersion]) { return NO; } if (![stringScanner scanString:@":" intoString:NULL]) { // appIDVersion must be separated by ":" return NO; } NSArray *knownVersions = @[ @"1" ]; if (![knownVersions containsObject:appIDVersion]) { // Permit unknown yet properly formatted app ID versions. FIRLogInfo(kFIRLoggerCore, @"I-COR000010", @"Unknown GOOGLE_APP_ID version: %@", appIDVersion); return YES; } if (![self validateAppIDFormat:appID withVersion:appIDVersion]) { return NO; } if (![self validateAppIDFingerprint:appID withVersion:appIDVersion]) { return NO; } return YES; } + (NSString *)actualBundleID { return [[NSBundle mainBundle] bundleIdentifier]; } /** * Validates that the format of the app ID string is what is expected based on the supplied version. * The version must end in ":". * * For v1 app ids the format is expected to be * '::ios:'. * * This method does not verify that the contents of the app id are correct, just that they fulfill * the expected format. * * @param appID Contents of GOOGLE_APP_ID from the plist file. * @param version Indicates what version of the app id format this string should be. * @return YES if provided string fufills the expected format, NO otherwise. */ + (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version { if (!appID.length || !version.length) { return NO; } NSScanner *stringScanner = [NSScanner scannerWithString:appID]; stringScanner.charactersToBeSkipped = nil; // Skip version part // '**::ios:' if (![stringScanner scanString:version intoString:NULL]) { // The version part is missing or mismatched return NO; } // Validate version part (see part between '*' symbols below) // '*:*:ios:' if (![stringScanner scanString:@":" intoString:NULL]) { // appIDVersion must be separated by ":" return NO; } // Validate version part (see part between '*' symbols below) // ':**:ios:'. NSInteger projectNumber = NSNotFound; if (![stringScanner scanInteger:&projectNumber]) { // NO project number found. return NO; } // Validate version part (see part between '*' symbols below) // ':*:*ios:'. if (![stringScanner scanString:@":" intoString:NULL]) { // The project number must be separated by ":" return NO; } // Validate version part (see part between '*' symbols below) // '::*ios*:'. NSString *platform; if (![stringScanner scanUpToString:@":" intoString:&platform]) { return NO; } if (![platform isEqualToString:@"ios"]) { // The platform must be @"ios" return NO; } // Validate version part (see part between '*' symbols below) // '::ios*:*'. if (![stringScanner scanString:@":" intoString:NULL]) { // The platform must be separated by ":" return NO; } // Validate version part (see part between '*' symbols below) // '::ios:**'. unsigned long long fingerprint = NSNotFound; if (![stringScanner scanHexLongLong:&fingerprint]) { // Fingerprint part is missing return NO; } if (!stringScanner.isAtEnd) { // There are not allowed characters in the fingerprint part return NO; } return YES; } /** * Validates that the fingerprint of the app ID string is what is expected based on the supplied * version. * * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated. * * @param appID Contents of GOOGLE_APP_ID from the plist file. * @param version Indicates what version of the app id format this string should be. * @return YES if provided string fufills the expected fingerprint and the version is known, NO * otherwise. */ + (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version { // Extract the supplied fingerprint from the supplied app ID. // This assumes the app ID format is the same for all known versions below. If the app ID format // changes in future versions, the tokenizing of the app ID format will need to take into account // the version of the app ID. NSArray *components = [appID componentsSeparatedByString:@":"]; if (components.count != 4) { return NO; } NSString *suppliedFingerprintString = components[3]; if (!suppliedFingerprintString.length) { return NO; } uint64_t suppliedFingerprint; NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString]; if (![scanner scanHexLongLong:&suppliedFingerprint]) { return NO; } if ([version isEqual:@"1"]) { // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated. return YES; } // Unknown version. return NO; } - (NSString *)expectedBundleID { return _options.bundleID; } // end App ID validation #pragma mark - Reading From Plist & User Defaults /** * Clears the data collection switch from the standard NSUserDefaults for easier testing and * readability. */ - (void)clearDataCollectionSwitchFromUserDefaults { NSString *key = [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; } /** * Reads the data collection switch from the standard NSUserDefaults for easier testing and * readability. */ + (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app { // Read the object in user defaults, and only return if it's an NSNumber. NSString *key = [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name]; id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key]; if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) { return collectionEnabledDefaultsObject; } return nil; } /** * Reads the data collection switch from the Info.plist for easier testing and readability. Will * only read once from the plist and return the cached value. */ + (nullable NSNumber *)readDataCollectionSwitchFromPlist { static NSNumber *collectionEnabledPlistObject; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber. id plistValue = [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey]; if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) { collectionEnabledPlistObject = (NSNumber *)plistValue; } }); return collectionEnabledPlistObject; } #pragma mark - App Life Cycle - (void)subscribeForAppDidBecomeActiveNotifications { #if TARGET_OS_IOS || TARGET_OS_TV NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification; #elif TARGET_OS_OSX NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification; #endif #if !TARGET_OS_WATCH [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive:) name:notificationName object:nil]; #endif } - (void)appDidBecomeActive:(NSNotification *)notification { [self logCoreTelemetryIfEnabled]; } - (void)logCoreTelemetryIfEnabled { if ([self isDataCollectionDefaultEnabled]) { dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ [FIRCoreDiagnosticsConnector logCoreTelemetryWithOptions:[self options]]; }); } } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRAppAssociationRegistration.h ================================================ /* * Copyright 2017 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 // TODO: Remove this once Auth moves over to Core's instance registration system. /** @class FIRAppAssociationRegistration @brief Manages object associations as a singleton-dependent: At most one object is registered for any given host/key pair, and the object shall be created on-the-fly when asked for. */ @interface FIRAppAssociationRegistration : NSObject /** @fn registeredObjectWithHost:key:creationBlock: @brief Retrieves the registered object with a particular host and key. @param host The host object. @param key The key to specify the registered object on the host. @param creationBlock The block to return the object to be registered if not already. The block is executed immediately before this method returns if it is executed at all. It can also be executed multiple times across different method invocations if previous execution of the block returns @c nil. @return The registered object for the host/key pair, or @c nil if no object is registered and @c creationBlock returns @c nil. @remarks The method is thread-safe but non-reentrant in the sense that attempting to call this method again within the @c creationBlock with the same host/key pair raises an exception. The registered object is retained by the host. */ + (nullable ObjectType)registeredObjectWithHost:(id)host key:(NSString *)key creationBlock:(ObjectType _Nullable (^)(void))creationBlock; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRAppAssociationRegistration.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/FIRAppAssociationRegistration.h" #import @implementation FIRAppAssociationRegistration + (nullable id)registeredObjectWithHost:(id)host key:(NSString *)key creationBlock:(id _Nullable (^)(void))creationBlock { @synchronized(self) { SEL dictKey = @selector(registeredObjectWithHost:key:creationBlock:); NSMutableDictionary *objectsByKey = objc_getAssociatedObject(host, dictKey); if (!objectsByKey) { objectsByKey = [[NSMutableDictionary alloc] init]; objc_setAssociatedObject(host, dictKey, objectsByKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } id obj = objectsByKey[key]; NSValue *creationBlockBeingCalled = [NSValue valueWithPointer:dictKey]; if (obj) { if ([creationBlockBeingCalled isEqual:obj]) { [NSException raise:@"Reentering registeredObjectWithHost:key:creationBlock: not allowed" format:@"host: %@ key: %@", host, key]; } return obj; } objectsByKey[key] = creationBlockBeingCalled; obj = creationBlock(); objectsByKey[key] = obj; return obj; } } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.h ================================================ /* * Copyright 2017 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 /** * This class provides utilities for accessing resources in bundles. */ @interface FIRBundleUtil : NSObject /** * Finds all relevant bundles, starting with [NSBundle mainBundle]. */ + (NSArray *)relevantBundles; /** * Reads the options dictionary from one of the provided bundles. * * @param resourceName The resource name, e.g. @"GoogleService-Info". * @param fileType The file type (extension), e.g. @"plist". * @param bundles The bundles to expect, in priority order. See also * +[FIRBundleUtil relevantBundles]. */ + (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName andFileType:(NSString *)fileType inBundles:(NSArray *)bundles; /** * Finds URL schemes defined in all relevant bundles, starting with those from * [NSBundle mainBundle]. */ + (NSArray *)relevantURLSchemes; /** * Checks if any of the given bundles have a matching bundle identifier prefix (removing extension * suffixes). */ + (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles; @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRBundleUtil.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/FIRBundleUtil.h" #import @implementation FIRBundleUtil + (NSArray *)relevantBundles { return @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ]; } + (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName andFileType:(NSString *)fileType inBundles:(NSArray *)bundles { // Loop through all bundles to find the config dict. for (NSBundle *bundle in bundles) { NSString *path = [bundle pathForResource:resourceName ofType:fileType]; // Use the first one we find. if (path) { return path; } } return nil; } + (NSArray *)relevantURLSchemes { NSMutableArray *result = [[NSMutableArray alloc] init]; for (NSBundle *bundle in [[self class] relevantBundles]) { NSArray *urlTypes = [bundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]; for (NSDictionary *urlType in urlTypes) { [result addObjectsFromArray:urlType[@"CFBundleURLSchemes"]]; } } return result; } + (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles { for (NSBundle *bundle in bundles) { if ([bundle.bundleIdentifier isEqualToString:bundleIdentifier]) { return YES; } if ([GULAppEnvironmentUtil isAppExtension]) { // A developer could be using the same `FIROptions` for both their app and extension. Since // extensions have a suffix added to the bundleID, we consider a matching prefix as valid. NSString *appBundleIDFromExtension = [self bundleIdentifierByRemovingLastPartFrom:bundle.bundleIdentifier]; if ([appBundleIDFromExtension isEqualToString:bundleIdentifier]) { return YES; } } } return NO; } + (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier { NSString *bundleIDComponentsSeparator = @"."; NSMutableArray *bundleIDComponents = [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy]; [bundleIDComponents removeLastObject]; return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator]; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRComponent.m ================================================ /* * Copyright 2018 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 "FirebaseCore/Sources/Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRDependency.h" @interface FIRComponent () - (instancetype)initWithProtocol:(Protocol *)protocol instantiationTiming:(FIRInstantiationTiming)instantiationTiming dependencies:(NSArray *)dependencies creationBlock:(FIRComponentCreationBlock)creationBlock; @end @implementation FIRComponent + (instancetype)componentWithProtocol:(Protocol *)protocol creationBlock:(FIRComponentCreationBlock)creationBlock { return [[FIRComponent alloc] initWithProtocol:protocol instantiationTiming:FIRInstantiationTimingLazy dependencies:@[] creationBlock:creationBlock]; } + (instancetype)componentWithProtocol:(Protocol *)protocol instantiationTiming:(FIRInstantiationTiming)instantiationTiming dependencies:(NSArray *)dependencies creationBlock:(FIRComponentCreationBlock)creationBlock { return [[FIRComponent alloc] initWithProtocol:protocol instantiationTiming:instantiationTiming dependencies:dependencies creationBlock:creationBlock]; } - (instancetype)initWithProtocol:(Protocol *)protocol instantiationTiming:(FIRInstantiationTiming)instantiationTiming dependencies:(NSArray *)dependencies creationBlock:(FIRComponentCreationBlock)creationBlock { self = [super init]; if (self) { _protocol = protocol; _instantiationTiming = instantiationTiming; _dependencies = [dependencies copy]; _creationBlock = creationBlock; } return self; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentContainer.m ================================================ /* * Copyright 2018 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 "FirebaseCore/Sources/Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRLogger.h" NS_ASSUME_NONNULL_BEGIN @interface FIRComponentContainer () /// The dictionary of components that are registered for a particular app. The key is an `NSString` /// of the protocol. @property(nonatomic, strong) NSMutableDictionary *components; /// Cached instances of components that requested to be cached. @property(nonatomic, strong) NSMutableDictionary *cachedInstances; /// Protocols of components that have requested to be eagerly instantiated. @property(nonatomic, strong, nullable) NSMutableArray *eagerProtocolsToInstantiate; @end @implementation FIRComponentContainer // Collection of all classes that register to provide components. static NSMutableSet *sFIRComponentRegistrants; #pragma mark - Public Registration + (void)registerAsComponentRegistrant:(Class)klass { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sFIRComponentRegistrants = [[NSMutableSet alloc] init]; }); [self registerAsComponentRegistrant:klass inSet:sFIRComponentRegistrants]; } + (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet *)allRegistrants { [allRegistrants addObject:klass]; } #pragma mark - Internal Initialization - (instancetype)initWithApp:(FIRApp *)app { return [self initWithApp:app registrants:sFIRComponentRegistrants]; } - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet *)allRegistrants { self = [super init]; if (self) { _app = app; _cachedInstances = [NSMutableDictionary dictionary]; _components = [NSMutableDictionary dictionary]; [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app]; } return self; } - (void)populateComponentsFromRegisteredClasses:(NSSet *)classes forApp:(FIRApp *)app { // Keep track of any components that need to eagerly instantiate after all components are added. self.eagerProtocolsToInstantiate = [[NSMutableArray alloc] init]; // Loop through the verified component registrants and populate the components array. for (Class klass in classes) { // Loop through all the components being registered and store them as appropriate. // Classes which do not provide functionality should use a dummy FIRComponentRegistrant // protocol. for (FIRComponent *component in [klass componentsToRegister]) { // Check if the component has been registered before, and error out if so. NSString *protocolName = NSStringFromProtocol(component.protocol); if (self.components[protocolName]) { FIRLogError(kFIRLoggerCore, @"I-COR000029", @"Attempted to register protocol %@, but it already has an implementation.", protocolName); continue; } // Store the creation block for later usage. self.components[protocolName] = component.creationBlock; // Queue any protocols that should be eagerly instantiated. Don't instantiate them yet // because they could depend on other components that haven't been added to the components // array yet. BOOL shouldInstantiateEager = (component.instantiationTiming == FIRInstantiationTimingAlwaysEager); BOOL shouldInstantiateDefaultEager = (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp && [app isDefaultApp]); if (shouldInstantiateEager || shouldInstantiateDefaultEager) { [self.eagerProtocolsToInstantiate addObject:component.protocol]; } } } } #pragma mark - Instance Creation - (void)instantiateEagerComponents { // After all components are registered, instantiate the ones that are requesting eager // instantiation. @synchronized(self) { for (Protocol *protocol in self.eagerProtocolsToInstantiate) { // Get an instance for the protocol, which will instantiate it since it couldn't have been // cached yet. Ignore the instance coming back since we don't need it. __unused id unusedInstance = [self instanceForProtocol:protocol]; } // All eager instantiation is complete, clear the stored property now. self.eagerProtocolsToInstantiate = nil; } } /// Instantiate an instance of a class that conforms to the specified protocol. /// This will: /// - Call the block to create an instance if possible, /// - Validate that the instance returned conforms to the protocol it claims to, /// - Cache the instance if the block requests it /// /// Note that this method assumes the caller already has @sychronized on self. - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol withBlock:(FIRComponentCreationBlock)creationBlock { if (!creationBlock) { return nil; } // Create an instance using the creation block. BOOL shouldCache = NO; id instance = creationBlock(self, &shouldCache); if (!instance) { return nil; } // An instance was created, validate that it conforms to the protocol it claims to. NSString *protocolName = NSStringFromProtocol(protocol); if (![instance conformsToProtocol:protocol]) { FIRLogError(kFIRLoggerCore, @"I-COR000030", @"An instance conforming to %@ was requested, but the instance provided does not " @"conform to the protocol", protocolName); } // The instance is ready to be returned, but check if it should be cached first before returning. if (shouldCache) { self.cachedInstances[protocolName] = instance; } return instance; } #pragma mark - Internal Retrieval - (nullable id)instanceForProtocol:(Protocol *)protocol { // Check if there is a cached instance, and return it if so. NSString *protocolName = NSStringFromProtocol(protocol); id cachedInstance; @synchronized(self) { cachedInstance = self.cachedInstances[protocolName]; if (!cachedInstance) { // Use the creation block to instantiate an instance and return it. FIRComponentCreationBlock creationBlock = self.components[protocolName]; cachedInstance = [self instantiateInstanceForProtocol:protocol withBlock:creationBlock]; } } return cachedInstance; } #pragma mark - Lifecycle - (void)removeAllCachedInstances { @synchronized(self) { // Loop through the cache and notify each instance that is a maintainer to clean up after // itself. for (id instance in self.cachedInstances.allValues) { if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] && [instance respondsToSelector:@selector(appWillBeDeleted:)]) { [instance appWillBeDeleted:self.app]; } } // Empty the cache. [self.cachedInstances removeAllObjects]; } } - (void)removeAllComponents { @synchronized(self) { [self.components removeAllObjects]; } } @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentContainerInternal.h ================================================ /* * Copyright 2018 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 "FirebaseCore/Sources/Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h" @class FIRApp; NS_ASSUME_NONNULL_BEGIN @interface FIRComponentContainer (Private) /// Initializes a container for a given app. This should only be called by the app itself. - (instancetype)initWithApp:(FIRApp *)app; /// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the /// protocol wasn't registered, or if the instance couldn't be instantiated for the provided app. - (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:)); /// Instantiates all the components that have registered as "eager" after initialization. - (void)instantiateEagerComponents; /// Remove all of the cached instances stored and allow them to clean up after themselves. - (void)removeAllCachedInstances; /// Removes all the components. After calling this method no new instances will be created. - (void)removeAllComponents; /// Register a class to provide components for the interoperability system. The class should conform /// to `FIRComponentRegistrant` and provide an array of `FIRComponent` objects. + (void)registerAsComponentRegistrant:(Class)klass; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRComponentType.m ================================================ /* * Copyright 2018 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 "FirebaseCore/Sources/Private/FIRComponentType.h" #import "FirebaseCore/Sources/FIRComponentContainerInternal.h" @implementation FIRComponentType + (id)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container { // Forward the call to the container. return [container instanceForProtocol:protocol]; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRConfiguration.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/FIRConfigurationInternal.h" #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h" extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); @implementation FIRConfiguration + (instancetype)sharedInstance { static FIRConfiguration *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[FIRConfiguration alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _analyticsConfiguration = [FIRAnalyticsConfiguration sharedInstance]; } return self; } - (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel { NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin, @"Invalid logger level, %ld", (long)loggerLevel); FIRSetLoggerLevel(loggerLevel); } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRConfigurationInternal.h ================================================ /* * 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 "FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h" @class FIRAnalyticsConfiguration; @interface FIRConfiguration () /** * The configuration class for Firebase Analytics. This should be removed once the logic for * enabling and disabling Analytics is moved to Analytics. */ @property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration; @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m ================================================ /* * 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 "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h" #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "FirebaseCore/Sources/FIRDiagnosticsData.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" // Define the interop class symbol declared as an extern in FIRCoreDiagnosticsInterop. Class FIRCoreDiagnosticsImplementation; @implementation FIRCoreDiagnosticsConnector + (void)initialize { if (!FIRCoreDiagnosticsImplementation) { FIRCoreDiagnosticsImplementation = NSClassFromString(@"FIRCoreDiagnostics"); if (FIRCoreDiagnosticsImplementation) { NSAssert([FIRCoreDiagnosticsImplementation conformsToProtocol:@protocol(FIRCoreDiagnosticsInterop)], @"If FIRCoreDiagnostics is implemented, it must conform to the interop protocol."); NSAssert( [FIRCoreDiagnosticsImplementation respondsToSelector:@selector(sendDiagnosticsData:)], @"If FIRCoreDiagnostics is implemented, it must implement +sendDiagnosticsData."); } } } + (void)logCoreTelemetryWithOptions:(FIROptions *)options { if (FIRCoreDiagnosticsImplementation) { FIRDiagnosticsData *diagnosticsData = [[FIRDiagnosticsData alloc] init]; [diagnosticsData insertValue:@(YES) forKey:kFIRCDIsDataCollectionDefaultEnabledKey]; [diagnosticsData insertValue:[FIRApp firebaseUserAgent] forKey:kFIRCDFirebaseUserAgentKey]; [diagnosticsData insertValue:@(FIRConfigTypeCore) forKey:kFIRCDConfigurationTypeKey]; [diagnosticsData insertValue:options.googleAppID forKey:kFIRCDGoogleAppIDKey]; [diagnosticsData insertValue:options.bundleID forKey:kFIRCDBundleIDKey]; [diagnosticsData insertValue:@(options.usingOptionsFromDefaultPlist) forKey:kFIRCDUsingOptionsFromDefaultPlistKey]; [diagnosticsData insertValue:options.libraryVersionID forKey:kFIRCDLibraryVersionIDKey]; [FIRCoreDiagnosticsImplementation sendDiagnosticsData:diagnosticsData]; } } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRDependency.m ================================================ /* * Copyright 2018 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 "FirebaseCore/Sources/Private/FIRDependency.h" @interface FIRDependency () - (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; @end @implementation FIRDependency + (instancetype)dependencyWithProtocol:(Protocol *)protocol { return [[self alloc] initWithProtocol:protocol isRequired:YES]; } + (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { return [[self alloc] initWithProtocol:protocol isRequired:required]; } - (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { self = [super init]; if (self) { _protocol = protocol; _isRequired = required; } return self; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRDiagnosticsData.h ================================================ /* * 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 "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h" NS_ASSUME_NONNULL_BEGIN /** Implements the FIRCoreDiagnosticsData protocol to log diagnostics data. */ @interface FIRDiagnosticsData : NSObject /** Inserts values into the diagnosticObjects dictionary if the value isn't nil. * * @param value The value to insert if it's not nil. * @param key The key to associate it with. */ - (void)insertValue:(nullable id)value forKey:(NSString *)key; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRDiagnosticsData.m ================================================ /* * 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 "FirebaseCore/Sources/FIRDiagnosticsData.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" @implementation FIRDiagnosticsData { /** Backing ivar for the diagnosticObjects property. */ NSMutableDictionary *_diagnosticObjects; } - (instancetype)init { self = [super init]; if (self) { _diagnosticObjects = [[NSMutableDictionary alloc] init]; } return self; } - (void)insertValue:(nullable id)value forKey:(NSString *)key { if (key) { _diagnosticObjects[key] = value; } } #pragma mark - FIRCoreDiagnosticsData - (NSDictionary *)diagnosticObjects { if (!_diagnosticObjects[kFIRCDllAppsCountKey]) { _diagnosticObjects[kFIRCDllAppsCountKey] = @([FIRApp allApps].count); } if (!_diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey]) { _diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey] = @([[FIRApp defaultApp] isDataCollectionDefaultEnabled]); } if (!_diagnosticObjects[kFIRCDFirebaseUserAgentKey]) { _diagnosticObjects[kFIRCDFirebaseUserAgentKey] = [FIRApp firebaseUserAgent]; } return _diagnosticObjects; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" - (void)setDiagnosticObjects:(NSDictionary *)diagnosticObjects { NSAssert(NO, @"Please use -insertValue:forKey:"); } #pragma clang diagnostic pop @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRFirebaseUserAgent.h ================================================ /* * 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 @interface FIRFirebaseUserAgent : NSObject /** Returns the firebase user agent which consists of environment part and the components added via * `setValue:forComponent` method. */ - (NSString *)firebaseUserAgent; /** Sets value associated with the specified component. If value is `nil` then the component is * removed. */ - (void)setValue:(nullable NSString *)value forComponent:(NSString *)componentName; /** Resets manually added components. */ - (void)reset; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRFirebaseUserAgent.m ================================================ /* * 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 "FirebaseCore/Sources/FIRFirebaseUserAgent.h" #import @interface FIRFirebaseUserAgent () @property(nonatomic, readonly) NSMutableDictionary *valuesByComponent; @property(nonatomic, readonly) NSDictionary *environmentComponents; @property(nonatomic, readonly) NSString *firebaseUserAgent; @end @implementation FIRFirebaseUserAgent @synthesize firebaseUserAgent = _firebaseUserAgent; @synthesize environmentComponents = _environmentComponents; - (instancetype)init { self = [super init]; if (self) { _valuesByComponent = [[NSMutableDictionary alloc] init]; } return self; } - (NSString *)firebaseUserAgent { @synchronized(self) { if (_firebaseUserAgent == nil) { NSMutableDictionary *allComponents = [self.valuesByComponent mutableCopy]; [allComponents setValuesForKeysWithDictionary:self.environmentComponents]; __block NSMutableArray *components = [[NSMutableArray alloc] initWithCapacity:self.valuesByComponent.count]; [allComponents enumerateKeysAndObjectsUsingBlock:^( NSString *_Nonnull name, NSString *_Nonnull value, BOOL *_Nonnull stop) { [components addObject:[NSString stringWithFormat:@"%@/%@", name, value]]; }]; [components sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; _firebaseUserAgent = [components componentsJoinedByString:@" "]; } return _firebaseUserAgent; } } - (void)setValue:(nullable NSString *)value forComponent:(NSString *)componentName { @synchronized(self) { self.valuesByComponent[componentName] = value; // Reset cached user agent string. _firebaseUserAgent = nil; } } - (void)reset { @synchronized(self) { // Reset components. _valuesByComponent = [[[self class] environmentComponents] mutableCopy]; // Reset cached user agent string. _firebaseUserAgent = nil; } } #pragma mark - Environment components - (NSDictionary *)environmentComponents { if (_environmentComponents == nil) { _environmentComponents = [[self class] environmentComponents]; } return _environmentComponents; } + (NSDictionary *)environmentComponents { NSMutableDictionary *components = [NSMutableDictionary dictionary]; NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; NSString *xcodeVersion = info[@"DTXcodeBuild"]; NSString *appleSdkVersion = info[@"DTSDKBuild"]; NSString *isFromAppstoreFlagValue = [GULAppEnvironmentUtil isFromAppStore] ? @"true" : @"false"; components[@"apple-platform"] = [GULAppEnvironmentUtil applePlatform]; components[@"apple-sdk"] = appleSdkVersion; components[@"appstore"] = isFromAppstoreFlagValue; components[@"deploy"] = [GULAppEnvironmentUtil deploymentType]; components[@"device"] = [GULAppEnvironmentUtil deviceModel]; components[@"os-version"] = [GULAppEnvironmentUtil systemVersion]; components[@"xcode"] = xcodeVersion; return [components copy]; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRHeartbeatInfo.m ================================================ // 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 "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h" #import #import #import #import #import "FirebaseCore/Sources/Private/FIRAppInternal.h" const static long secondsInDay = 86400; @implementation FIRHeartbeatInfo : NSObject /** Updates the storage with the heartbeat information corresponding to this tag. * @param heartbeatTag Tag which could either be sdk specific tag or the global tag. * @return Boolean representing whether the heartbeat needs to be sent for this tag or not. */ + (BOOL)updateIfNeededHeartbeatDateForTag:(NSString *)heartbeatTag { @synchronized(self) { NSString *const kHeartbeatStorageName = @"HEARTBEAT_INFO_STORAGE"; id dataStorage; #if TARGET_OS_TV NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kFirebaseCoreDefaultsSuiteName]; dataStorage = [[GULHeartbeatDateStorageUserDefaults alloc] initWithDefaults:defaults key:kHeartbeatStorageName]; #else dataStorage = [[GULHeartbeatDateStorage alloc] initWithFileName:kHeartbeatStorageName]; #endif NSDate *heartbeatTime = [dataStorage heartbeatDateForTag:heartbeatTag]; NSDate *currentDate = [NSDate date]; if (heartbeatTime != nil) { NSTimeInterval secondsBetween = [currentDate timeIntervalSinceDate:heartbeatTime]; if (secondsBetween < secondsInDay) { return false; } } return [dataStorage setHearbeatDate:currentDate forTag:heartbeatTag]; } } + (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag { NSString *globalTag = @"GLOBAL"; BOOL isSdkHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:heartbeatTag]; BOOL isGlobalHeartbeatNeeded = [FIRHeartbeatInfo updateIfNeededHeartbeatDateForTag:globalTag]; if (!isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) { // Both sdk and global heartbeat not needed. return FIRHeartbeatInfoCodeNone; } else if (isSdkHeartbeatNeeded && !isGlobalHeartbeatNeeded) { // Only SDK heartbeat needed. return FIRHeartbeatInfoCodeSDK; } else if (!isSdkHeartbeatNeeded && isGlobalHeartbeatNeeded) { // Only global heartbeat needed. return FIRHeartbeatInfoCodeGlobal; } else { // Both sdk and global heartbeat are needed. return FIRHeartbeatInfoCodeCombined; } } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRLogger.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/Private/FIRLogger.h" #import #import #import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]"; // All the FIRLoggerService definitions should be migrated to clients. Do not add new ones! FIRLoggerService kFIRLoggerAnalytics = @"[Firebase/Analytics]"; FIRLoggerService kFIRLoggerCrash = @"[Firebase/Crash]"; FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]"; /// Arguments passed on launch. NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled"; NSString *const kFIREnableDebugModeApplicationArgument = @"-FIRDebugEnabled"; NSString *const kFIRLoggerForceSDTERRApplicationArgument = @"-FIRLoggerForceSTDERR"; /// Key for the debug mode bit in NSUserDefaults. NSString *const kFIRPersistedDebugModeKey = @"/google/firebase/debug_mode"; /// NSUserDefaults that should be used to store and read variables. If nil, `standardUserDefaults` /// will be used. static NSUserDefaults *sFIRLoggerUserDefaults; static dispatch_once_t sFIRLoggerOnceToken; // The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled // flags used by Analytics. Users who use those flags expect Analytics to log verbosely, // while the rest of Firebase logs at the default level. This flag is introduced to support // that behavior. static BOOL sFIRAnalyticsDebugMode; #ifdef DEBUG /// The regex pattern for the message code. static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; static NSRegularExpression *sMessageCodeRegex; #endif void FIRLoggerInitializeASL(void) { dispatch_once(&sFIRLoggerOnceToken, ^{ // Register Firebase Version with GULLogger. GULLoggerRegisterVersion(FIRFirebaseVersion()); // Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in. NSArray *arguments = [NSProcessInfo processInfo].arguments; BOOL overrideSTDERR = [arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]; // Use the standard NSUserDefaults if it hasn't been explicitly set. if (sFIRLoggerUserDefaults == nil) { sFIRLoggerUserDefaults = [NSUserDefaults standardUserDefaults]; } BOOL forceDebugMode = NO; BOOL debugMode = [sFIRLoggerUserDefaults boolForKey:kFIRPersistedDebugModeKey]; if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; } else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument] || debugMode) { // Debug mode [sFIRLoggerUserDefaults setBool:YES forKey:kFIRPersistedDebugModeKey]; forceDebugMode = YES; } GULLoggerInitializeASL(); if (overrideSTDERR) { GULLoggerEnableSTDERR(); } if (forceDebugMode) { GULLoggerForceDebug(); } }); } __attribute__((no_sanitize("thread"))) void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) { sFIRAnalyticsDebugMode = analyticsDebugMode; } void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { FIRLoggerInitializeASL(); GULSetLoggerLevel((GULLoggerLevel)loggerLevel); } #ifdef DEBUG void FIRResetLogger(void) { extern void GULResetLogger(void); sFIRLoggerOnceToken = 0; [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; sFIRLoggerUserDefaults = nil; GULResetLogger(); } void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { sFIRLoggerUserDefaults = defaults; } #endif /** * Check if the level is high enough to be loggable. * * Analytics can override the log level with an intentional race condition. * Add the attribute to get a clean thread sanitizer run. */ __attribute__((no_sanitize("thread"))) BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent) { FIRLoggerInitializeASL(); if (sFIRAnalyticsDebugMode && analyticsComponent) { return YES; } return GULIsLoggableLevel((GULLoggerLevel)loggerLevel); } void FIRLogBasic(FIRLoggerLevel level, FIRLoggerService service, NSString *messageCode, NSString *message, va_list args_ptr) { FIRLoggerInitializeASL(); GULLogBasic((GULLoggerLevel)level, service, sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service], messageCode, message, args_ptr); } /** * Generates the logging functions using macros. * * Calling FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows: * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [Firebase/Core][I-COR000001] Configure blah failed. * Calling FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configure succeed.") shows: * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [Firebase/Core][I-COR000001] Configure succeed. */ #define FIR_LOGGING_FUNCTION(level) \ void FIRLog##level(FIRLoggerService service, NSString *messageCode, NSString *message, ...) { \ va_list args_ptr; \ va_start(args_ptr, message); \ FIRLogBasic(FIRLoggerLevel##level, service, messageCode, message, args_ptr); \ va_end(args_ptr); \ } FIR_LOGGING_FUNCTION(Error) FIR_LOGGING_FUNCTION(Warning) FIR_LOGGING_FUNCTION(Notice) FIR_LOGGING_FUNCTION(Info) FIR_LOGGING_FUNCTION(Debug) #undef FIR_MAKE_LOGGER #pragma mark - FIRLoggerWrapper @implementation FIRLoggerWrapper + (void)logWithLevel:(FIRLoggerLevel)level withService:(FIRLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args { FIRLogBasic(level, service, messageCode, message, args); } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIROptions.m ================================================ // Copyright 2017 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 "FirebaseCore/Sources/FIRBundleUtil.h" #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" // Keys for the strings in the plist file. NSString *const kFIRAPIKey = @"API_KEY"; NSString *const kFIRTrackingID = @"TRACKING_ID"; NSString *const kFIRGoogleAppID = @"GOOGLE_APP_ID"; NSString *const kFIRClientID = @"CLIENT_ID"; NSString *const kFIRGCMSenderID = @"GCM_SENDER_ID"; NSString *const kFIRAndroidClientID = @"ANDROID_CLIENT_ID"; NSString *const kFIRDatabaseURL = @"DATABASE_URL"; NSString *const kFIRStorageBucket = @"STORAGE_BUCKET"; // The key to locate the expected bundle identifier in the plist file. NSString *const kFIRBundleID = @"BUNDLE_ID"; // The key to locate the project identifier in the plist file. NSString *const kFIRProjectID = @"PROJECT_ID"; NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED"; NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED"; NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED"; NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED"; NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED"; // Library version ID formatted like: // @"5" // Major version (one or more digits) // @"04" // Minor version (exactly 2 digits) // @"01" // Build number (exactly 2 digits) // @"000"; // Fixed "000" NSString *kFIRLibraryVersionID; // Plist file name. NSString *const kServiceInfoFileName = @"GoogleService-Info"; // Plist file type. NSString *const kServiceInfoFileType = @"plist"; // Exception raised from attempting to modify a FIROptions after it's been copied to a FIRApp. NSString *const kFIRExceptionBadModification = @"Attempted to modify options after it's set on FIRApp. Please modify all properties before " @"initializing FIRApp."; @interface FIROptions () /** * This property maintains the actual configuration key-value pairs. */ @property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary; /** * Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary. * It combines analytics options from both the infoDictionary and the GoogleService-Info.plist. * Values which are present in the main plist override values from the GoogleService-Info.plist. */ @property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary; /** * Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist. * Values which are present in the infoDictionary override values from the GoogleService-Info.plist. */ - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary; /** * Throw exception if editing is locked when attempting to modify an option. */ - (void)checkEditingLocked; @end @implementation FIROptions { /// Backing variable for self.analyticsOptionsDictionary. NSDictionary *_analyticsOptionsDictionary; } static FIROptions *sDefaultOptions = nil; static NSDictionary *sDefaultOptionsDictionary = nil; static dispatch_once_t sDefaultOptionsOnceToken; static dispatch_once_t sDefaultOptionsDictionaryOnceToken; #pragma mark - Public only for internal class methods + (FIROptions *)defaultOptions { dispatch_once(&sDefaultOptionsOnceToken, ^{ NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary]; if (defaultOptionsDictionary != nil) { sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary]; } }); return sDefaultOptions; } #pragma mark - Private class methods + (NSDictionary *)defaultOptionsDictionary { dispatch_once(&sDefaultOptionsDictionaryOnceToken, ^{ NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName]; if (plistFilePath == nil) { return; } sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath]; if (sDefaultOptionsDictionary == nil) { FIRLogError(kFIRLoggerCore, @"I-COR000011", @"The configuration file is not a dictionary: " @"'%@.%@'.", kServiceInfoFileName, kServiceInfoFileType); } }); return sDefaultOptionsDictionary; } // Returns the path of the plist file with a given file name. + (NSString *)plistFilePathWithName:(NSString *)fileName { NSArray *bundles = [FIRBundleUtil relevantBundles]; NSString *plistFilePath = [FIRBundleUtil optionsDictionaryPathWithResourceName:fileName andFileType:kServiceInfoFileType inBundles:bundles]; if (plistFilePath == nil) { FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.", fileName, kServiceInfoFileType); } return plistFilePath; } + (void)resetDefaultOptions { sDefaultOptions = nil; sDefaultOptionsDictionary = nil; sDefaultOptionsOnceToken = 0; sDefaultOptionsDictionaryOnceToken = 0; } #pragma mark - Private instance methods - (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary { self = [super init]; if (self) { _optionsDictionary = [optionsDictionary mutableCopy]; _usingOptionsFromDefaultPlist = YES; } return self; } - (id)copyWithZone:(NSZone *)zone { FIROptions *newOptions = [(FIROptions *)[[self class] allocWithZone:zone] initInternalWithOptionsDictionary:self.optionsDictionary]; if (newOptions) { newOptions.deepLinkURLScheme = self.deepLinkURLScheme; newOptions.appGroupID = self.appGroupID; newOptions.editingLocked = self.isEditingLocked; newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist; } return newOptions; } #pragma mark - Public instance methods - (instancetype)init { // Unavailable. [self doesNotRecognizeSelector:_cmd]; return nil; } - (instancetype)initWithContentsOfFile:(NSString *)plistPath { self = [super init]; if (self) { if (plistPath == nil) { FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil."); return nil; } _optionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:plistPath] mutableCopy]; if (_optionsDictionary == nil) { FIRLogError(kFIRLoggerCore, @"I-COR000014", @"The configuration file at %@ does not exist or " @"is not a well-formed plist file.", plistPath); return nil; } // TODO: Do we want to validate the dictionary here? It says we do that already in // the public header. } return self; } - (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID { self = [super init]; if (self) { NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary]; [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID]; [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID]; [mutableOptionsDict setValue:[[NSBundle mainBundle] bundleIdentifier] forKey:kFIRBundleID]; self.optionsDictionary = mutableOptionsDict; } return self; } - (NSString *)APIKey { return self.optionsDictionary[kFIRAPIKey]; } - (void)checkEditingLocked { if (self.isEditingLocked) { [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification]; } } - (void)setAPIKey:(NSString *)APIKey { [self checkEditingLocked]; _optionsDictionary[kFIRAPIKey] = [APIKey copy]; } - (NSString *)clientID { return self.optionsDictionary[kFIRClientID]; } - (void)setClientID:(NSString *)clientID { [self checkEditingLocked]; _optionsDictionary[kFIRClientID] = [clientID copy]; } - (NSString *)trackingID { return self.optionsDictionary[kFIRTrackingID]; } - (void)setTrackingID:(NSString *)trackingID { [self checkEditingLocked]; _optionsDictionary[kFIRTrackingID] = [trackingID copy]; } - (NSString *)GCMSenderID { return self.optionsDictionary[kFIRGCMSenderID]; } - (void)setGCMSenderID:(NSString *)GCMSenderID { [self checkEditingLocked]; _optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy]; } - (NSString *)projectID { return self.optionsDictionary[kFIRProjectID]; } - (void)setProjectID:(NSString *)projectID { [self checkEditingLocked]; _optionsDictionary[kFIRProjectID] = [projectID copy]; } - (NSString *)androidClientID { return self.optionsDictionary[kFIRAndroidClientID]; } - (void)setAndroidClientID:(NSString *)androidClientID { [self checkEditingLocked]; _optionsDictionary[kFIRAndroidClientID] = [androidClientID copy]; } - (NSString *)googleAppID { return self.optionsDictionary[kFIRGoogleAppID]; } - (void)setGoogleAppID:(NSString *)googleAppID { [self checkEditingLocked]; _optionsDictionary[kFIRGoogleAppID] = [googleAppID copy]; } - (NSString *)libraryVersionID { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // The unit tests are set up to catch anything that does not properly convert. NSString *version = FIRFirebaseVersion(); NSArray *components = [version componentsSeparatedByString:@"."]; NSString *major = [components objectAtIndex:0]; NSString *minor = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:1] intValue]]; NSString *patch = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:2] intValue]]; kFIRLibraryVersionID = [NSString stringWithFormat:@"%@%@%@000", major, minor, patch]; }); return kFIRLibraryVersionID; } - (void)setLibraryVersionID:(NSString *)libraryVersionID { _optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy]; } - (NSString *)databaseURL { return self.optionsDictionary[kFIRDatabaseURL]; } - (void)setDatabaseURL:(NSString *)databaseURL { [self checkEditingLocked]; _optionsDictionary[kFIRDatabaseURL] = [databaseURL copy]; } - (NSString *)storageBucket { return self.optionsDictionary[kFIRStorageBucket]; } - (void)setStorageBucket:(NSString *)storageBucket { [self checkEditingLocked]; _optionsDictionary[kFIRStorageBucket] = [storageBucket copy]; } - (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme { [self checkEditingLocked]; _deepLinkURLScheme = [deepLinkURLScheme copy]; } - (NSString *)bundleID { return self.optionsDictionary[kFIRBundleID]; } - (void)setBundleID:(NSString *)bundleID { [self checkEditingLocked]; _optionsDictionary[kFIRBundleID] = [bundleID copy]; } - (void)setAppGroupID:(NSString *)appGroupID { [self checkEditingLocked]; _appGroupID = [appGroupID copy]; } #pragma mark - Equality - (BOOL)isEqual:(id)object { if (!object || ![object isKindOfClass:[FIROptions class]]) { return NO; } return [self isEqualToOptions:(FIROptions *)object]; } - (BOOL)isEqualToOptions:(FIROptions *)options { // Skip any non-FIROptions classes. if (![options isKindOfClass:[FIROptions class]]) { return NO; } // Check the internal dictionary and custom properties for differences. if (![options.optionsDictionary isEqualToDictionary:self.optionsDictionary]) { return NO; } // Validate extra properties not contained in the dictionary. Only validate it if one of the // objects has the property set. if ((options.deepLinkURLScheme != nil || self.deepLinkURLScheme != nil) && ![options.deepLinkURLScheme isEqualToString:self.deepLinkURLScheme]) { return NO; } if ((options.appGroupID != nil || self.appGroupID != nil) && ![options.appGroupID isEqualToString:self.appGroupID]) { return NO; } // Validate the Analytics options haven't changed with the Info.plist. if (![options.analyticsOptionsDictionary isEqualToDictionary:self.analyticsOptionsDictionary]) { return NO; } // We don't care about the `editingLocked` or `usingOptionsFromDefaultPlist` properties since // those relate to lifecycle and construction, we only care if the contents of the options // themselves are equal. return YES; } - (NSUInteger)hash { // This is strongly recommended for any object that implements a custom `isEqual:` method to // ensure that dictionary and set behavior matches other `isEqual:` checks. // Note: `self.analyticsOptionsDictionary` was left out here since it solely relies on the // contents of the main bundle's `Info.plist`. We should avoid reading that file and the contents // should be identical. return self.optionsDictionary.hash ^ self.deepLinkURLScheme.hash ^ self.appGroupID.hash; } #pragma mark - Internal instance methods - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary { if (_analyticsOptionsDictionary == nil) { NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init]; NSArray *measurementKeys = @[ kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled, kFIRIsAnalyticsCollectionDeactivated ]; for (NSString *key in measurementKeys) { id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil; if (!value) { continue; } tempAnalyticsOptions[key] = value; } _analyticsOptionsDictionary = tempAnalyticsOptions; } return _analyticsOptionsDictionary; } - (NSDictionary *)analyticsOptionsDictionary { return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary]; } /** * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in * GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still * be supported. */ - (BOOL)isMeasurementEnabled { if (self.isAnalyticsCollectionDeactivated) { return NO; } NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled]; if (value == nil) { // TODO: This could probably be cleaned up since FIROptions shouldn't know about FIRApp or have // to check if it's the default app. The FIROptions instance can't be modified after // `+configure` is called, so it's not a good place to copy it either in case the flag is // changed at runtime. // If no values are set for Analytics, fall back to the global collection switch in FIRApp. // Analytics only supports the default FIRApp, so check that first. if (![FIRApp isDefaultAppConfigured]) { return NO; } // Fall back to the default app's collection switch when the key is not in the dictionary. return [FIRApp defaultApp].isDataCollectionDefaultEnabled; } return [value boolValue]; } - (BOOL)isAnalyticsCollectionExplicitlySet { // If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication // that the developer wants FirebaseAnalytics enabled so continue checking. if (self.isAnalyticsCollectionDeactivated) { return YES; } // Check if the current Analytics flag is set. id collectionEnabledObject = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled]; if (collectionEnabledObject && [collectionEnabledObject isKindOfClass:[NSNumber class]]) { // It doesn't matter what the value is, it's explicitly set. return YES; } // Check if the old measurement flag is set. id measurementEnabledObject = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled]; if (measurementEnabledObject && [measurementEnabledObject isKindOfClass:[NSNumber class]]) { // It doesn't matter what the value is, it's explicitly set. return YES; } // No flags are set to explicitly enable or disable FirebaseAnalytics. return NO; } - (BOOL)isAnalyticsCollectionEnabled { if (self.isAnalyticsCollectionDeactivated) { return NO; } NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled]; if (value == nil) { return self.isMeasurementEnabled; // Fall back to older plist flag. } return [value boolValue]; } - (BOOL)isAnalyticsCollectionDeactivated { NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated]; if (value == nil) { return NO; // Analytics Collection is not deactivated when the key is not in the dictionary. } return [value boolValue]; } - (BOOL)isAnalyticsEnabled { return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue]; } - (BOOL)isSignInEnabled { return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue]; } @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/FIRVersion.m ================================================ /* * Copyright 2017 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 "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" #ifndef Firebase_VERSION #error "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation" #endif // The following two macros supply the incantation so that the C // preprocessor does not try to parse the version as a floating // point number. See // https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/ #define STR(x) STR_EXPAND(x) #define STR_EXPAND(x) #x NSString* FIRFirebaseVersion(void) { return @STR(Firebase_VERSION); } ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRAppInternal.h ================================================ /* * Copyright 2017 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 @class FIRComponentContainer; @protocol FIRLibrary; /** * The internal interface to FIRApp. This is meant for first-party integrators, who need to receive * FIRApp notifications, log info about the success or failure of their configuration, and access * other internal functionality of FIRApp. * * TODO(b/28296561): Restructure this header. */ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, FIRConfigType) { FIRConfigTypeCore = 1, FIRConfigTypeSDK = 2, }; extern NSString *const kFIRDefaultAppName; extern NSString *const kFIRAppReadyToConfigureSDKNotification; extern NSString *const kFIRAppDeleteNotification; extern NSString *const kFIRAppIsDefaultAppKey; extern NSString *const kFIRAppNameKey; extern NSString *const kFIRGoogleAppIDKey; extern NSString *const kFirebaseCoreErrorDomain; /** The NSUserDefaults suite name for FirebaseCore, for those storage locations that use it. */ extern NSString *const kFirebaseCoreDefaultsSuiteName; /** * The format string for the User Defaults key used for storing the data collection enabled flag. * This includes formatting to append the Firebase App's name. */ extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat; /** * The plist key used for storing the data collection enabled flag. */ extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey; /** @var FIRAuthStateDidChangeInternalNotification @brief The name of the @c NSNotificationCenter notification which is posted when the auth state changes (e.g. a new token has been produced, a user logs in or out). The object parameter of the notification is a dictionary possibly containing the key: @c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not contain this key it indicates a sign-out event took place. */ extern NSString *const FIRAuthStateDidChangeInternalNotification; /** @var FIRAuthStateDidChangeInternalNotificationTokenKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the new access token. */ extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey; /** @var FIRAuthStateDidChangeInternalNotificationAppKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the FIRApp associated with the auth instance. */ extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey; /** @var FIRAuthStateDidChangeInternalNotificationUIDKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the new user's UID (or nil if there is no longer a user signed in). */ extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey; @interface FIRApp () /** * A flag indicating if this is the default app (has the default app name). */ @property(nonatomic, readonly) BOOL isDefaultApp; /* * The container of interop SDKs for this app. */ @property(nonatomic) FIRComponentContainer *container; /** * Checks if the default app is configured without trying to configure it. */ + (BOOL)isDefaultAppConfigured; /** * Registers a given third-party library with the given version number to be reported for * analytics. * * @param name Name of the library. * @param version Version of the library. */ + (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version; /** * Registers a given internal library to be reported for analytics. * * @param library Optional parameter for component registration. * @param name Name of the library. */ + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name; /** * Registers a given internal library with the given version number to be reported for * analytics. This should only be used for non-Firebase libraries that have their own versioning * scheme. * * @param library Optional parameter for component registration. * @param name Name of the library. * @param version Version of the library. */ + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name withVersion:(nonnull NSString *)version; /** * A concatenated string representing all the third-party libraries and version numbers. */ + (NSString *)firebaseUserAgent; /** * Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe. */ + (void)resetApps; /** * Can be used by the unit tests in each SDK to set customized options. */ - (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponent.h ================================================ /* * Copyright 2018 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 @class FIRApp; @class FIRComponentContainer; NS_ASSUME_NONNULL_BEGIN /// Provides a system to clean up cached instances returned from the component system. NS_SWIFT_NAME(ComponentLifecycleMaintainer) @protocol FIRComponentLifecycleMaintainer /// The associated app will be deleted, clean up any resources as they are about to be deallocated. - (void)appWillBeDeleted:(FIRApp *)app; @end typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container, BOOL *isCacheable) NS_SWIFT_NAME(ComponentCreationBlock); @class FIRDependency; /// Describes the timing of instantiation. Note: new components should default to lazy unless there /// is a strong reason to be eager. typedef NS_ENUM(NSInteger, FIRInstantiationTiming) { FIRInstantiationTimingLazy, FIRInstantiationTimingAlwaysEager, FIRInstantiationTimingEagerInDefaultApp } NS_SWIFT_NAME(InstantiationTiming); /// A component that can be used from other Firebase SDKs. NS_SWIFT_NAME(Component) @interface FIRComponent : NSObject /// The protocol describing functionality provided from the Component. @property(nonatomic, strong, readonly) Protocol *protocol; /// The timing of instantiation. @property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming; /// An array of dependencies for the component. @property(nonatomic, copy, readonly) NSArray *dependencies; /// A block to instantiate an instance of the component with the appropriate dependencies. @property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock; // There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format // for the next two methods. // clang-format off /// Creates a component with no dependencies that will be lazily initialized. + (instancetype)componentWithProtocol:(Protocol *)protocol creationBlock:(FIRComponentCreationBlock)creationBlock NS_SWIFT_NAME(init(_:creationBlock:)); /// Creates a component to be registered with the component container. /// /// @param protocol - The protocol describing functionality provided by the component. /// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's /// a good reason to be instantiated earlier. /// @param dependencies - Any dependencies the `implementingClass` has, optional or required. /// @param creationBlock - A block to instantiate the component with a container, and if /// @return A component that can be registered with the component container. + (instancetype)componentWithProtocol:(Protocol *)protocol instantiationTiming:(FIRInstantiationTiming)instantiationTiming dependencies:(NSArray *)dependencies creationBlock:(FIRComponentCreationBlock)creationBlock NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:)); // clang-format on /// Unavailable. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentContainer.h ================================================ /* * Copyright 2018 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 /// A type-safe macro to retrieve a component from a container. This should be used to retrieve /// components instead of using the container directly. #define FIR_COMPONENT(type, container) \ [FIRComponentType> instanceForProtocol:@protocol(type) inContainer:container] @class FIRApp; /// A container that holds different components that are registered via the /// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant` /// in order to properly register components for Core. NS_SWIFT_NAME(FirebaseComponentContainer) @interface FIRComponentContainer : NSObject /// A weak reference to the app that an instance of the container belongs to. @property(nonatomic, weak, readonly) FIRApp *app; /// Unavailable. Use the `container` property on `FIRApp`. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRComponentType.h ================================================ /* * Copyright 2018 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 @class FIRComponentContainer; NS_ASSUME_NONNULL_BEGIN /// Do not use directly. A placeholder type in order to provide a macro that will warn users of /// mis-matched protocols. NS_SWIFT_NAME(ComponentType) @interface FIRComponentType<__covariant T> : NSObject /// Do not use directly. A factory method to retrieve an instance that provides a specific /// functionality. + (T)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h ================================================ /* * 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 @class FIRDiagnosticsData; @class FIROptions; NS_ASSUME_NONNULL_BEGIN /** Connects FIRCore with the CoreDiagnostics library. */ @interface FIRCoreDiagnosticsConnector : NSObject /** Logs FirebaseCore related data. * * @param options The options object containing data to log. */ + (void)logCoreTelemetryWithOptions:(FIROptions *)options; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRDependency.h ================================================ /* * Copyright 2018 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 /// A dependency on a specific protocol's functionality. NS_SWIFT_NAME(Dependency) @interface FIRDependency : NSObject /// The protocol describing functionality being depended on. @property(nonatomic, strong, readonly) Protocol *protocol; /// A flag to specify if the dependency is required or not. @property(nonatomic, readonly) BOOL isRequired; /// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for /// the required parameter. /// Creates a required dependency on the specified protocol's functionality. + (instancetype)dependencyWithProtocol:(Protocol *)protocol; /// Creates a dependency on the specified protocol's functionality and specify if it's required for /// the class's functionality. + (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; /// Use `dependencyWithProtocol:isRequired:` instead. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRHeartbeatInfo.h ================================================ // 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 @interface FIRHeartbeatInfo : NSObject // Enum representing the different heartbeat codes. typedef NS_ENUM(NSInteger, FIRHeartbeatInfoCode) { FIRHeartbeatInfoCodeNone = 0, FIRHeartbeatInfoCodeSDK = 1, FIRHeartbeatInfoCodeGlobal = 2, FIRHeartbeatInfoCodeCombined = 3, }; /** * Get heartbeat code required for the sdk. * @param heartbeatTag String representing the sdk heartbeat tag. * @return Heartbeat code indicating whether or not an sdk/global heartbeat * needs to be sent */ + (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLibrary.h ================================================ /* * Copyright 2018 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. */ #ifndef FIRLibrary_h #define FIRLibrary_h #import @class FIRApp; @class FIRComponent; NS_ASSUME_NONNULL_BEGIN /// Provide an interface to register a library for userAgent logging and availability to others. NS_SWIFT_NAME(Library) @protocol FIRLibrary /// Returns one or more FIRComponents that will be registered in /// FIRApp and participate in dependency resolution and injection. + (NSArray *)componentsToRegister; @optional /// Implement this method if the library needs notifications for lifecycle events. This method is /// called when the developer calls `FirebaseApp.configure()`. + (void)configureWithApp:(FIRApp *)app; @end NS_ASSUME_NONNULL_END #endif /* FIRLibrary_h */ ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIRLogger.h ================================================ /* * Copyright 2017 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 NS_ASSUME_NONNULL_BEGIN /** * The Firebase services used in Firebase logger. */ typedef NSString *const FIRLoggerService; extern FIRLoggerService kFIRLoggerAnalytics; extern FIRLoggerService kFIRLoggerCrash; extern FIRLoggerService kFIRLoggerCore; extern FIRLoggerService kFIRLoggerRemoteConfig; /** * The key used to store the logger's error count. */ extern NSString *const kFIRLoggerErrorCountKey; /** * The key used to store the logger's warning count. */ extern NSString *const kFIRLoggerWarningCountKey; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Enables or disables Analytics debug mode. * If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug. * Enabling the debug mode has no effect if the app is running from App Store. * (required) analytics debug mode flag. */ void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode); /** * Changes the default logging level of FIRLoggerLevelNotice to a user-specified level. * The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store. * (required) log level (one of the FIRLoggerLevel enum values). */ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); /** * Checks if the specified logger level is loggable given the current settings. * (required) log level (one of the FIRLoggerLevel enum values). * (required) whether or not this function is called from the Analytics component. */ BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent); /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming. * (required) log level (one of the FIRLoggerLevel enum values). * (required) service name of type FIRLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ extern void FIRLogBasic(FIRLoggerLevel level, FIRLoggerService service, NSString *messageCode, NSString *message, // On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable // See: http://stackoverflow.com/q/29095469 #if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX va_list args_ptr #else va_list _Nullable args_ptr #endif ); /** * The following functions accept the following parameters in order: * (required) service name of type FIRLoggerService. * (required) message code starting from "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * See go/firebase-log-proposal for details. * (required) message string which can be a format string. * (optional) the list of arguments to substitute into the format string. * Example usage: * FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); */ extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); #ifdef __cplusplus } // extern "C" #endif // __cplusplus @interface FIRLoggerWrapper : NSObject /** * Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger * (required) log level (one of the FIRLoggerLevel enum values). * (required) service name of type FIRLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ + (void)logWithLevel:(FIRLoggerLevel)level withService:(FIRLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FIROptionsInternal.h ================================================ /* * Copyright 2017 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 /** * Keys for the strings in the plist file. */ extern NSString *const kFIRAPIKey; extern NSString *const kFIRTrackingID; extern NSString *const kFIRGoogleAppID; extern NSString *const kFIRClientID; extern NSString *const kFIRGCMSenderID; extern NSString *const kFIRAndroidClientID; extern NSString *const kFIRDatabaseURL; extern NSString *const kFIRStorageBucket; extern NSString *const kFIRBundleID; extern NSString *const kFIRProjectID; /** * Keys for the plist file name */ extern NSString *const kServiceInfoFileName; extern NSString *const kServiceInfoFileType; /** * This header file exposes the initialization of FIROptions to internal use. */ @interface FIROptions () /** * resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests. */ + (void)resetDefaultOptions; /** * Initializes the options with dictionary. The above strings are the keys of the dictionary. * This is the designated initializer. */ - (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary NS_DESIGNATED_INITIALIZER; /** * defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and * other first party services. */ + (FIROptions *)defaultOptions; + (NSDictionary *)defaultOptionsDictionary; /** * Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at * runtime. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet; /** * Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless * explicitly disabled in GoogleService-Info.plist. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled; /** * Whether or not Analytics Collection was completely disabled. If YES, then * isAnalyticsCollectionEnabled will be NO. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated; /** * The version ID of the client library, e.g. @"1100000". */ @property(nonatomic, readonly, copy) NSString *libraryVersionID; /** * The flag indicating whether this object was constructed with the values in the default plist * file. */ @property(nonatomic) BOOL usingOptionsFromDefaultPlist; /** * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in * GoogleService-Info.plist. */ @property(nonatomic, readonly) BOOL isMeasurementEnabled; /** * Whether or not Analytics was enabled in the developer console. */ @property(nonatomic, readonly) BOOL isAnalyticsEnabled; /** * Whether or not SignIn was enabled in the developer console. */ @property(nonatomic, readonly) BOOL isSignInEnabled; /** * Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp. */ @property(nonatomic, getter=isEditingLocked) BOOL editingLocked; @end ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Private/FirebaseCoreInternal.h ================================================ // 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. // An umbrella header, for any other libraries in this repo to access Firebase Public and Private // headers. Any package manager complexity should be handled here. #import #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRComponentType.h" #import "FirebaseCore/Sources/Private/FIRDependency.h" #import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h ================================================ /* * Copyright 2017 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 @class FIROptions; NS_ASSUME_NONNULL_BEGIN /** A block that takes a BOOL and has no return value. */ typedef void (^FIRAppVoidBoolCallback)(BOOL success) NS_SWIFT_NAME(FirebaseAppVoidBoolCallback); /** * The entry point of Firebase SDKs. * * Initialize and configure FIRApp using +[FIRApp configure] * or other customized ways as shown below. * * The logging system has two modes: default mode and debug mode. In default mode, only logs with * log level Notice, Warning and Error will be sent to device. In debug mode, all logs will be sent * to device. The log levels that Firebase uses are consistent with the ASL log levels. * * Enable debug mode by passing the -FIRDebugEnabled argument to the application. You can add this * argument in the application's Xcode scheme. When debug mode is enabled via -FIRDebugEnabled, * further executions of the application will also be in debug mode. In order to return to default * mode, you must explicitly disable the debug mode with the application argument -FIRDebugDisabled. * * It is also possible to change the default logging level in code by calling setLoggerLevel: on * the FIRConfiguration interface. */ NS_SWIFT_NAME(FirebaseApp) @interface FIRApp : NSObject /** * Configures a default Firebase app. Raises an exception if any configuration step fails. The * default app is named "__FIRAPP_DEFAULT". This method should be called after the app is launched * and before using Firebase services. This method should be called from the main thread and * contains synchronous file I/O (reading GoogleService-Info.plist from disk). */ + (void)configure; /** * Configures the default Firebase app with the provided options. The default app is named * "__FIRAPP_DEFAULT". Raises an exception if any configuration step fails. This method should be * called from the main thread. * * @param options The Firebase application options used to configure the service. */ + (void)configureWithOptions:(FIROptions *)options NS_SWIFT_NAME(configure(options:)); /** * Configures a Firebase app with the given name and options. Raises an exception if any * configuration step fails. This method should be called from the main thread. * * @param name The application's name given by the developer. The name should should only contain Letters, Numbers and Underscore. * @param options The Firebase application options used to configure the services. */ // clang-format off + (void)configureWithName:(NSString *)name options:(FIROptions *)options NS_SWIFT_NAME(configure(name:options:)); // clang-format on /** * Returns the default app, or nil if the default app does not exist. */ + (nullable FIRApp *)defaultApp NS_SWIFT_NAME(app()); /** * Returns a previously created FIRApp instance with the given name, or nil if no such app exists. * This method is thread safe. */ + (nullable FIRApp *)appNamed:(NSString *)name NS_SWIFT_NAME(app(name:)); /** * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This * method is thread safe. */ @property(class, readonly, nullable) NSDictionary *allApps; /** * Cleans up the current FIRApp, freeing associated data and returning its name to the pool for * future use. This method is thread safe. */ - (void)deleteApp:(FIRAppVoidBoolCallback)completion; /** * FIRApp instances should not be initialized directly. Call +[FIRApp configure], * +[FIRApp configureWithOptions:], or +[FIRApp configureWithNames:options:] directly. */ - (instancetype)init NS_UNAVAILABLE; /** * Gets the name of this app. */ @property(nonatomic, copy, readonly) NSString *name; /** * Gets a copy of the options for this app. These are non-modifiable. */ @property(nonatomic, copy, readonly) FIROptions *options; /** * Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES` * unless `FirebaseDataCollectionDefaultEnabled` is set to `NO` in your app's Info.plist. This value * is persisted across runs of the app so that it can be set once when users have consented to * collection. */ @property(nonatomic, readwrite, getter=isDataCollectionDefaultEnabled) BOOL dataCollectionDefaultEnabled; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h ================================================ /* * Copyright 2017 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 "FIRLoggerLevel.h" NS_ASSUME_NONNULL_BEGIN /** * This interface provides global level properties that the developer can tweak. */ NS_SWIFT_NAME(FirebaseConfiguration) @interface FIRConfiguration : NSObject /** Returns the shared configuration object. */ @property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared); /** * Sets the logging level for internal Firebase logging. Firebase will only log messages * that are logged at or below loggerLevel. The messages are logged both to the Xcode * console and to the device's log. Note that if an app is running from AppStore, it will * never log above FIRLoggerLevelNotice even if loggerLevel is set to a higher (more verbose) * setting. * * @param loggerLevel The maximum logging level. The default level is set to FIRLoggerLevelNotice. */ - (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h ================================================ /* * Copyright 2017 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. */ // Note that importing GULLoggerLevel.h will lead to a non-modular header // import error. /** * The log levels used by internal logging. */ typedef NS_ENUM(NSInteger, FIRLoggerLevel) { /** Error level, matches ASL_LEVEL_ERR. */ FIRLoggerLevelError = 3, /** Warning level, matches ASL_LEVEL_WARNING. */ FIRLoggerLevelWarning = 4, /** Notice level, matches ASL_LEVEL_NOTICE. */ FIRLoggerLevelNotice = 5, /** Info level, matches ASL_LEVEL_INFO. */ FIRLoggerLevelInfo = 6, /** Debug level, matches ASL_LEVEL_DEBUG. */ FIRLoggerLevelDebug = 7, /** Minimum log level. */ FIRLoggerLevelMin = FIRLoggerLevelError, /** Maximum log level. */ FIRLoggerLevelMax = FIRLoggerLevelDebug } NS_SWIFT_NAME(FirebaseLoggerLevel); ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h ================================================ /* * Copyright 2017 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 /** * This class provides constant fields of Google APIs. */ NS_SWIFT_NAME(FirebaseOptions) @interface FIROptions : NSObject /** * Returns the default options. The first time this is called it synchronously reads * GoogleService-Info.plist from disk. */ + (nullable FIROptions *)defaultOptions NS_SWIFT_NAME(defaultOptions()); /** * An iOS API key used for authenticating requests from your app, e.g. * @"AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers. */ @property(nonatomic, copy, nullable) NSString *APIKey NS_SWIFT_NAME(apiKey); /** * The bundle ID for the application. Defaults to `[[NSBundle mainBundle] bundleID]` when not set * manually or in a plist. */ @property(nonatomic, copy) NSString *bundleID; /** * The OAuth2 client ID for iOS application used to authenticate Google users, for example * @"12345.apps.googleusercontent.com", used for signing in with Google. */ @property(nonatomic, copy, nullable) NSString *clientID; /** * The tracking ID for Google Analytics, e.g. @"UA-12345678-1", used to configure Google Analytics. */ @property(nonatomic, copy, nullable) NSString *trackingID; /** * The Project Number from the Google Developer's console, for example @"012345678901", used to * configure Google Cloud Messaging. */ @property(nonatomic, copy) NSString *GCMSenderID NS_SWIFT_NAME(gcmSenderID); /** * The Project ID from the Firebase console, for example @"abc-xyz-123". */ @property(nonatomic, copy, nullable) NSString *projectID; /** * The Android client ID used in Google AppInvite when an iOS app has its Android version, for * example @"12345.apps.googleusercontent.com". */ @property(nonatomic, copy, nullable) NSString *androidClientID; /** * The Google App ID that is used to uniquely identify an instance of an app. */ @property(nonatomic, copy) NSString *googleAppID; /** * The database root URL, e.g. @"http://abc-xyz-123.firebaseio.com". */ @property(nonatomic, copy, nullable) NSString *databaseURL; /** * The URL scheme used to set up Durable Deep Link service. */ @property(nonatomic, copy, nullable) NSString *deepLinkURLScheme; /** * The Google Cloud Storage bucket name, e.g. @"abc-xyz-123.storage.firebase.com". */ @property(nonatomic, copy, nullable) NSString *storageBucket; /** * The App Group identifier to share data between the application and the application extensions. * The App Group must be configured in the application and on the Apple Developer Portal. Default * value `nil`. */ @property(nonatomic, copy, nullable) NSString *appGroupID; /** * Initializes a customized instance of FIROptions from the file at the given plist file path. This * will read the file synchronously from disk. * For example, * NSString *filePath = * [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; * FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; * Returns nil if the plist file does not exist or is invalid. */ - (nullable instancetype)initWithContentsOfFile:(NSString *)plistPath NS_DESIGNATED_INITIALIZER; /** * Initializes a customized instance of FIROptions with required fields. Use the mutable properties * to modify fields for configuring specific services. */ // clang-format off - (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID NS_SWIFT_NAME(init(googleAppID:gcmSenderID:)) NS_DESIGNATED_INITIALIZER; // clang-format on /** Unavailable. Please use `init(contentsOfFile:)` or `init(googleAppID:gcmSenderID:)` instead. */ - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h ================================================ /* * 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 /** Returns the current version of Firebase. */ NS_SWIFT_NAME(FirebaseVersion()) NSString* FIRFirebaseVersion(void); NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore/FirebaseCore.h ================================================ /* * Copyright 2017 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 "FIRApp.h" #import "FIRConfiguration.h" #import "FIRLoggerLevel.h" #import "FIROptions.h" #import "FIRVersion.h" ================================================ FILE: Pods/FirebaseCore/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h ================================================ /* * 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 /** If present, is a BOOL wrapped in an NSNumber. */ #define kFIRCDIsDataCollectionDefaultEnabledKey @"FIRCDIsDataCollectionDefaultEnabledKey" /** If present, is an int32_t wrapped in an NSNumber. */ #define kFIRCDConfigurationTypeKey @"FIRCDConfigurationTypeKey" /** If present, is an NSString. */ #define kFIRCDSdkNameKey @"FIRCDSdkNameKey" /** If present, is an NSString. */ #define kFIRCDSdkVersionKey @"FIRCDSdkVersionKey" /** If present, is an int32_t wrapped in an NSNumber. */ #define kFIRCDllAppsCountKey @"FIRCDllAppsCountKey" /** If present, is an NSString. */ #define kFIRCDGoogleAppIDKey @"FIRCDGoogleAppIDKey" /** If present, is an NSString. */ #define kFIRCDBundleIDKey @"FIRCDBundleID" /** If present, is a BOOL wrapped in an NSNumber. */ #define kFIRCDUsingOptionsFromDefaultPlistKey @"FIRCDUsingOptionsFromDefaultPlistKey" /** If present, is an NSString. */ #define kFIRCDLibraryVersionIDKey @"FIRCDLibraryVersionIDKey" /** If present, is an NSString. */ #define kFIRCDFirebaseUserAgentKey @"FIRCDFirebaseUserAgentKey" /** Defines the interface of a data object needed to log diagnostics data. */ @protocol FIRCoreDiagnosticsData @required /** A dictionary containing data (non-exhaustive) to be logged in diagnostics. */ @property(nonatomic) NSDictionary *diagnosticObjects; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h ================================================ /* * 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 "FIRCoreDiagnosticsData.h" NS_ASSUME_NONNULL_BEGIN /** Allows the interoperation of FirebaseCore and FirebaseCoreDiagnostics. */ @protocol FIRCoreDiagnosticsInterop /** Sends the given diagnostics data. * * @param diagnosticsData The diagnostics data object to send. */ + (void)sendDiagnosticsData:(id)diagnosticsData; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCore/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/FirebaseCore/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![License](https://img.shields.io/cocoapods/l/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Platform](https://img.shields.io/cocoapods/p/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Actions Status][gh-abtesting-badge]][gh-actions] [![Actions Status][gh-appcheck-badge]][gh-actions] [![Actions Status][gh-appdistribution-badge]][gh-actions] [![Actions Status][gh-auth-badge]][gh-actions] [![Actions Status][gh-cocoapods-integration-badge]][gh-actions] [![Actions Status][gh-core-badge]][gh-actions] [![Actions Status][gh-core-diagnostics-badge]][gh-actions] [![Actions Status][gh-crashlytics-badge]][gh-actions] [![Actions Status][gh-database-badge]][gh-actions] [![Actions Status][gh-datatransport-badge]][gh-actions] [![Actions Status][gh-dynamiclinks-badge]][gh-actions] [![Actions Status][gh-firebasepod-badge]][gh-actions] [![Actions Status][gh-firestore-badge]][gh-actions] [![Actions Status][gh-functions-badge]][gh-actions] [![Actions Status][gh-google-utilities-badge]][gh-actions] [![Actions Status][gh-google-utilities-components-badge]][gh-actions] [![Actions Status][gh-inappmessaging-badge]][gh-actions] [![Actions Status][gh-interop-badge]][gh-actions] [![Actions Status][gh-messaging-badge]][gh-actions] [![Actions Status][gh-mlmodeldownloader-badge]][gh-actions] [![Actions Status][gh-performance-badge]][gh-actions] [![Actions Status][gh-remoteconfig-badge]][gh-actions] [![Actions Status][gh-storage-badge]][gh-actions] [![Actions Status][gh-symbolcollision-badge]][gh-actions] [![Actions Status][gh-zip-badge]][gh-actions] [![Travis](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase Apple Open Source Development This repository contains all Apple platform Firebase SDK source except FirebaseAnalytics and FirebaseML. Firebase is an app development platform with tools to help you build, grow and monetize your app. More information about Firebase can be found on the [official Firebase website](https://firebase.google.com). **Note** _FirebaseCombineSwift_ contains support for Apple's Combine framework. This module is currently under development, and not yet supported for use in production environments. Fore more details, please refer to the [docs](FirebaseCombineSwift/README.md). ## Installation See the subsections below for details about the different installation methods. 1. [Standard pod install](README.md#standard-pod-install) 1. [Swift Package Manager](SwiftPackageManager.md) 1. [Installing from the GitHub repo](README.md#installing-from-github) 1. [Experimental Carthage](README.md#carthage-ios-only) ### Standard pod install Go to [https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). ### Swift Package Manager Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be found at [SwiftPackageManager.md](SwiftPackageManager.md). ### Installing from GitHub These instructions can be used to access the Firebase repo at other branches, tags, or commits. #### Background See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) for instructions and options about overriding pod source locations. #### Accessing Firebase Source Snapshots All of the official releases are tagged in this repo and available via CocoaPods. To access a local source snapshot or unreleased branch, use Podfile directives like the following: To access FirebaseFirestore via a branch: ```ruby pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' ``` To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: ```ruby pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' ``` ### Carthage (iOS only) Instructions for the experimental Carthage distribution are at [Carthage](Carthage.md). ### Using Firebase from a Framework or a library [Using Firebase from a Framework or a library](docs/firebase_in_libraries.md) ## Development To develop Firebase software in this repository, ensure that you have at least the following software: * Xcode 12.2 (or later) CocoaPods is still the canonical way to develop, but much of the repo now supports development with Swift Package Manager. ### CocoaPods Install * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: ```ruby pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios ``` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. Firestore has a self contained Xcode project. See [Firestore/README.md](Firestore/README.md). #### Development for Catalyst * `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Swift Package Manager * To enable test schemes: `./scripts/setup_spm_tests.sh` * `open Package.swift` or double click `Package.swift` in Finder. * Xcode will open the project * Choose a scheme for a library to build or test suite to run * Choose a target platform by selecting the run destination along with the scheme ### Adding a New Firebase Pod See [AddNewPod.md](AddNewPod.md). ### Managing Headers and Imports See [HeadersImports.md](HeadersImports.md). ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ### Running Sample Apps In order to run the sample apps and integration tests, you'll need a valid `GoogleService-Info.plist` file. The Firebase Xcode project contains dummy plist files without real values, but can be replaced with real plist files. To get your own `GoogleService-Info.plist` files: 1. Go to the [Firebase Console](https://console.firebase.google.com/) 2. Create a new Firebase project, if you don't already have one 3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.Database-Example`) 4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project. ### Coverage Report Generation See [scripts/code_coverage_report/README.md](scripts/code_coverage_report/README.md). ## Specific Component Instructions See the sections below for any special instructions for those components. ### Firebase Auth If you're doing specific Firebase Auth development, see [the Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about building and running the FirebaseAuth pod along with various samples and tests. ### Firebase Database The Firebase Database Integration tests can be run against a locally running Database Emulator or against a production instance. To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before running the integration test. To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to `FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to [public](https://firebase.google.com/docs/database/security/quickstart) while your tests are running. ### Firebase Performance Monitoring If you're doing specific Firebase Performance Monitoring development, see [the Performance README](FirebasePerformance/README.md) for instructions about building the SDK and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about integrating Performance with the dev test App. ### Firebase Storage To run the Storage Integration tests, follow the instructions in [FIRStorageIntegrationTests.m](FirebaseStorage/Tests/Integration/FIRStorageIntegrationTests.m). #### Push Notifications Push notifications can only be delivered to specially provisioned App IDs in the developer portal. In order to actually test receiving push notifications, you will need to: 1. Change the bundle identifier of the sample app to something you own in your Apple Developer account, and enable that App ID for push notifications. 2. You'll also need to [upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) at **Project Settings > Cloud Messaging > [Your Firebase App]**. 3. Ensure your iOS device is added to your Apple Developer portal as a test device. #### iOS Simulator The iOS Simulator cannot register for remote notifications, and will not receive push notifications. In order to receive push notifications, you'll have to follow the steps above and run the app on a physical device. ## Building with Firebase on Apple platforms At this time, not all of Firebase's products are available across all Apple platforms. However, Firebase is constantly evolving and community supported efforts have helped expand Firebase's support. To keep up with the latest info regarding Firebase's support across Apple platforms, refer to [this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform) in Firebase's documentation. ### Community Supported Efforts We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are very grateful! We'd like to empower as many developers as we can to be able to use Firebase and participate in the Firebase community. #### tvOS, macOS, watchOS and Catalyst Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on tvOS, macOS, watchOS and Catalyst. For tvOS, see the [Sample](Example/tvOSSample). For watchOS, currently only Messaging, Storage and Crashlytics (and their dependencies) have limited support. See the [Independent Watch App Sample](Example/watchOSSample). Keep in mind that macOS, tvOS, watchOS and Catalyst are not officially supported by Firebase, and this repository is actively developed primarily for iOS. While we can catch basic unit test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected on macOS, tvOS or watchOS. If you encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). During app setup in the console, you may get to a step that mentions something like "Checking if the app has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/watchOS/Catalyst. **It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. #### Additional MacOS and Catalyst Notes * FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` to Build Settings. * For Catalyst, FirebaseFirestore requires signing the [gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). #### Additional Crashlytics Notes * watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded) ## Roadmap See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source plans and directions. ## Contributing See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase iOS SDK. ## License The contents of this repository are licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Your use of Firebase is governed by the [Terms of Service for Firebase Services](https://firebase.google.com/terms/). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-abtesting-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/abtesting/badge.svg [gh-appcheck-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/app_check/badge.svg [gh-appdistribution-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/appdistribution/badge.svg [gh-auth-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/auth/badge.svg [gh-cocoapods-integration-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/cocoapods-integration/badge.svg [gh-core-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core/badge.svg [gh-core-diagnostics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core-diagnostics/badge.svg [gh-crashlytics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/crashlytics/badge.svg [gh-database-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/database/badge.svg [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg [gh-dynamiclinks-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/dynamiclinks/badge.svg [gh-firebasepod-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firebasepod/badge.svg [gh-firestore-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firestore/badge.svg [gh-functions-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/functions/badge.svg [gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg [gh-google-utilities-components-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities-components/badge.svg [gh-inappmessaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/inappmessaging/badge.svg [gh-interop-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/interop/badge.svg [gh-messaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/messaging/badge.svg [gh-mlmodeldownloader-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/mlmodeldownloader/badge.svg [gh-performance-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/performance/badge.svg [gh-remoteconfig-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/remoteconfig/badge.svg [gh-storage-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/storage/badge.svg [gh-symbolcollision-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/symbolcollision/badge.svg [gh-zip-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/zip/badge.svg ================================================ FILE: Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m ================================================ /* * 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 #include #import #import #import #import #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h" #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h" #import #import #import #import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h" /** The logger service string to use when printing to the console. */ static GULLoggerService kFIRCoreDiagnostics = @"[FirebaseCoreDiagnostics/FIRCoreDiagnostics]"; #ifdef FIREBASE_BUILD_ZIP_FILE static BOOL kUsingZipFile = YES; #else // FIREBASE_BUILD_ZIP_FILE static BOOL kUsingZipFile = NO; #endif // FIREBASE_BUILD_ZIP_FILE #if SWIFT_PACKAGE #define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM #elif FIREBASE_BUILD_CARTHAGE #define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_CARTHAGE #elif FIREBASE_BUILD_ZIP_FILE #define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ZIP_FILE #else #define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS #endif static NSString *const kFIRServiceMLModelInterpreter = @"MLModelInterpreter"; static NSString *const kFIRServiceAdMob = @"AdMob"; static NSString *const kFIRServiceAuth = @"Auth"; static NSString *const kFIRServiceAuthUI = @"AuthUI"; static NSString *const kFIRServiceCrash = @"Crash"; static NSString *const kFIRServiceDatabase = @"Database"; static NSString *const kFIRServiceDynamicLinks = @"DynamicLinks"; static NSString *const kFIRServiceFirestore = @"Firestore"; static NSString *const kFIRServiceFunctions = @"Functions"; static NSString *const kFIRServiceIAM = @"InAppMessaging"; static NSString *const kFIRServiceInstanceID = @"InstanceID"; static NSString *const kFIRServiceInvites = @"Invites"; static NSString *const kFIRServiceMessaging = @"Messaging"; static NSString *const kFIRServiceMeasurement = @"Measurement"; static NSString *const kFIRServicePerformance = @"Performance"; static NSString *const kFIRServiceRemoteConfig = @"RemoteConfig"; static NSString *const kFIRServiceStorage = @"Storage"; static NSString *const kGGLServiceAnalytics = @"Analytics"; static NSString *const kGGLServiceSignIn = @"SignIn"; static NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"FIRAppDiagnosticsConfigurationTypeKey"; static NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRAppDiagnosticsFIRAppKey"; static NSString *const kFIRAppDiagnosticsSDKNameKey = @"FIRAppDiagnosticsSDKNameKey"; static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKVersionKey"; static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics"; /** * The file name to the recent heartbeat date. */ NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTICS_HEARTBEAT_DATE"; /** * @note This should implement the GDTCOREventDataObject protocol, but can't because of * weak-linking. */ @interface FIRCoreDiagnosticsLog : NSObject /** The config that will be converted to proto bytes. */ @property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config; @end @implementation FIRCoreDiagnosticsLog - (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config { self = [super init]; if (self) { _config = config; } return self; } // Provided and required by the GDTCOREventDataObject protocol. - (NSData *)transportBytes { pb_ostream_t sizestream = PB_OSTREAM_SIZING; // Encode 1 time to determine the size. if (!pb_encode(&sizestream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s", PB_GET_ERROR(&sizestream)); } // Encode a 2nd time to actually get the bytes from it. size_t bufferSize = sizestream.bytes_written; CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); CFDataSetLength(dataRef, bufferSize); pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); if (!pb_encode(&ostream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) { GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for bytes: %s", PB_GET_ERROR(&ostream)); } CFDataSetLength(dataRef, ostream.bytes_written); return CFBridgingRelease(dataRef); } - (void)dealloc { pb_release(logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config); } @end NS_ASSUME_NONNULL_BEGIN /** This class produces a protobuf containing diagnostics and usage data to be logged. */ @interface FIRCoreDiagnostics : NSObject /** The queue on which all diagnostics collection will occur. */ @property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue; /** The transport object used to send data. */ @property(nonatomic, readonly) GDTCORTransport *transport; /** The storage to store the date of the last sent heartbeat. */ @property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage; @end NS_ASSUME_NONNULL_END @implementation FIRCoreDiagnostics + (instancetype)sharedInstance { static FIRCoreDiagnostics *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[FIRCoreDiagnostics alloc] init]; }); return sharedInstance; } - (instancetype)init { GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"137" transformers:nil target:kGDTCORTargetFLL]; GULHeartbeatDateStorage *dateStorage = [[GULHeartbeatDateStorage alloc] initWithFileName:kFIRCoreDiagnosticsHeartbeatDateFileName]; return [self initWithTransport:transport heartbeatDateStorage:dateStorage]; } /** Initializer for unit tests. * * @param transport A `GDTCORTransport` instance which that be used to send event. * @param heartbeatDateStorage An instanse of date storage to track heartbeat sending. * @return Returns the initialized `FIRCoreDiagnostics` instance. */ - (instancetype)initWithTransport:(GDTCORTransport *)transport heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage { self = [super init]; if (self) { _diagnosticsQueue = dispatch_queue_create("com.google.FIRCoreDiagnostics", DISPATCH_QUEUE_SERIAL); _transport = transport; _heartbeatDateStorage = heartbeatDateStorage; } return self; } #pragma mark - nanopb helper functions /** Callocs a pb_bytes_array and copies the given NSString's bytes into the bytes array. * * @note Memory needs to be free manually, through pb_free or pb_release. * @param string The string to encode as pb_bytes. */ pb_bytes_array_t *FIREncodeString(NSString *string) { NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; return FIREncodeData(stringBytes); } /** Callocs a pb_bytes_array and copies the given NSData bytes into the bytes array. * * @note Memory needs to be free manually, through pb_free or pb_release. * @param data The data to copy into the new bytes array. */ pb_bytes_array_t *FIREncodeData(NSData *data) { pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length)); if (pbBytesArray != NULL) { [data getBytes:pbBytesArray->bytes length:data.length]; pbBytesArray->size = (pb_size_t)data.length; } return pbBytesArray; } /** Maps a service string to the representative nanopb enum. * * @param serviceString The SDK service string to convert. * @return The representative nanopb enum. */ logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum( NSString *serviceString) { static NSDictionary *serviceStringToTypeEnum; if (serviceStringToTypeEnum == nil) { serviceStringToTypeEnum = @{ kFIRServiceAdMob : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ADMOB), kFIRServiceMessaging : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MESSAGING), kFIRServiceMeasurement : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MEASUREMENT), kFIRServiceRemoteConfig : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_REMOTE_CONFIG), kFIRServiceDatabase : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DATABASE), kFIRServiceDynamicLinks : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DYNAMIC_LINKS), kFIRServiceAuth : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH), kFIRServiceAuthUI : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH_UI), kFIRServiceFirestore : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FIRESTORE), kFIRServiceFunctions : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FUNCTIONS), kFIRServicePerformance : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_PERFORMANCE), kFIRServiceStorage : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_STORAGE), kFIRServiceMLModelInterpreter : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_MODEL_INTERPRETER), kGGLServiceAnalytics : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ANALYTICS), kGGLServiceSignIn : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SIGN_IN), kFIRServiceIAM : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_IN_APP_MESSAGING), }; } if (serviceStringToTypeEnum[serviceString] != nil) { return (int32_t)serviceStringToTypeEnum[serviceString].longLongValue; } return logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE; } #pragma mark - Proto population functions /** Populates the given proto with data related to an SDK logDiagnostics call from the * diagnosticObjects dictionary. * * @param config The proto to populate * @param diagnosticObjects The dictionary of diagnostics objects. */ void FIRPopulateProtoWithInfoFromUserInfoParams(logs_proto_mobilesdk_ios_ICoreConfiguration *config, NSDictionary *diagnosticObjects) { NSNumber *configurationType = diagnosticObjects[kFIRCDConfigurationTypeKey]; if (configurationType != nil) { switch (configurationType.integerValue) { case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE: config->configuration_type = logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE; config->has_configuration_type = 1; break; case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK: config->configuration_type = logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK; config->has_configuration_type = 1; break; default: break; } } NSString *sdkName = diagnosticObjects[kFIRCDSdkNameKey]; if (sdkName) { config->sdk_name = FIRMapFromServiceStringToTypeEnum(sdkName); config->has_sdk_name = 1; } NSString *version = diagnosticObjects[kFIRCDSdkVersionKey]; if (version) { config->sdk_version = FIREncodeString(version); } } /** Populates the given proto with data from the calling FIRApp using the given * diagnosticObjects dictionary. * * @param config The proto to populate * @param diagnosticObjects The dictionary of diagnostics objects. */ void FIRPopulateProtoWithCommonInfoFromApp(logs_proto_mobilesdk_ios_ICoreConfiguration *config, NSDictionary *diagnosticObjects) { config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE; config->has_pod_name = 1; if (!diagnosticObjects[kFIRCDllAppsCountKey]) { GDTCORLogError(GDTCORMCEGeneralError, @"%@", @"App count is a required value in the data dict."); } config->app_count = (int32_t)[diagnosticObjects[kFIRCDllAppsCountKey] integerValue]; config->has_app_count = 1; NSString *googleAppID = diagnosticObjects[kFIRCDGoogleAppIDKey]; if (googleAppID.length) { config->app_id = FIREncodeString(googleAppID); } NSString *bundleID = diagnosticObjects[kFIRCDBundleIDKey]; if (bundleID.length) { config->bundle_id = FIREncodeString(bundleID); } NSString *firebaseUserAgent = diagnosticObjects[kFIRCDFirebaseUserAgentKey]; if (firebaseUserAgent.length) { config->platform_info = FIREncodeString(firebaseUserAgent); } NSNumber *usingOptionsFromDefaultPlist = diagnosticObjects[kFIRCDUsingOptionsFromDefaultPlistKey]; if (usingOptionsFromDefaultPlist != nil) { config->use_default_app = [usingOptionsFromDefaultPlist boolValue]; config->has_use_default_app = 1; } NSString *libraryVersionID = diagnosticObjects[kFIRCDLibraryVersionIDKey]; if (libraryVersionID) { config->icore_version = FIREncodeString(libraryVersionID); } NSString *deviceModel = [GULAppEnvironmentUtil deviceModel]; if (deviceModel.length) { config->device_model = FIREncodeString(deviceModel); } NSString *osVersion = [GULAppEnvironmentUtil systemVersion]; if (osVersion.length) { config->os_version = FIREncodeString(osVersion); } config->using_zip_file = kUsingZipFile; config->has_using_zip_file = 1; config->deployment_type = kDeploymentType; config->has_deployment_type = 1; config->deployed_in_app_store = [GULAppEnvironmentUtil isFromAppStore]; config->has_deployed_in_app_store = 1; } /** Populates the given proto with installed services data. * * @param config The proto to populate */ void FIRPopulateProtoWithInstalledServices(logs_proto_mobilesdk_ios_ICoreConfiguration *config) { NSMutableArray *sdkServiceInstalledArray = [NSMutableArray array]; // AdMob if (NSClassFromString(@"GADBannerView") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAdMob))]; } // CloudMessaging if (NSClassFromString(@"FIRMessaging") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMessaging))]; } // RemoteConfig if (NSClassFromString(@"FIRRemoteConfig") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceRemoteConfig))]; } // Measurement/Analtyics if (NSClassFromString(@"FIRAnalytics") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMeasurement))]; } // ML Model Interpreter if (NSClassFromString(@"FIRCustomModelInterpreter") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLModelInterpreter))]; } // Database if (NSClassFromString(@"FIRDatabase") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDatabase))]; } // DynamicDeepLink if (NSClassFromString(@"FIRDynamicLinks") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDynamicLinks))]; } // Auth if (NSClassFromString(@"FIRAuth") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuth))]; } // AuthUI if (NSClassFromString(@"FUIAuth") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuthUI))]; } // Firestore if (NSClassFromString(@"FIRFirestore") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFirestore))]; } // Functions if (NSClassFromString(@"FIRFunctions") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFunctions))]; } // Performance if (NSClassFromString(@"FIRPerformance") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServicePerformance))]; } // Storage if (NSClassFromString(@"FIRStorage") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceStorage))]; } // SignIn via Google pod if (NSClassFromString(@"GIDSignIn") != nil && NSClassFromString(@"GGLContext") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceSignIn))]; } // Analytics via Google pod if (NSClassFromString(@"GAI") != nil && NSClassFromString(@"GGLContext") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceAnalytics))]; } // In-App Messaging if (NSClassFromString(@"FIRInAppMessaging") != nil) { [sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceIAM))]; } logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *servicesInstalled = calloc(sdkServiceInstalledArray.count, sizeof(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType)); if (servicesInstalled == NULL) { return; } for (NSUInteger i = 0; i < sdkServiceInstalledArray.count; i++) { NSNumber *typeEnum = sdkServiceInstalledArray[i]; logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType serviceType = (int32_t)typeEnum.integerValue; servicesInstalled[i] = serviceType; } config->sdk_service_installed = servicesInstalled; config->sdk_service_installed_count = (int32_t)sdkServiceInstalledArray.count; } /** Populates the proto with Info.plist values. * * @param config The proto to populate. */ void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfiguration *config) { NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @""; NSString *sdkVersion = info[@"DTSDKBuild"] ?: @""; NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion]; config->apple_framework_version = FIREncodeString(combinedVersions); NSString *minVersion = info[@"MinimumOSVersion"]; if (minVersion) { config->min_supported_ios_version = FIREncodeString(minVersion); } // Apps can turn off swizzling in the Info.plist, check if they've explicitly set the value and // report it. It's enabled by default. NSNumber *appDelegateSwizzledNum = info[@"FirebaseAppDelegateProxyEnabled"]; BOOL appDelegateSwizzled = YES; if ([appDelegateSwizzledNum isKindOfClass:[NSNumber class]]) { appDelegateSwizzled = [appDelegateSwizzledNum boolValue]; } config->swizzling_enabled = appDelegateSwizzled; config->has_swizzling_enabled = 1; } #pragma mark - FIRCoreDiagnosticsInterop + (void)sendDiagnosticsData:(nonnull id)diagnosticsData { FIRCoreDiagnostics *diagnostics = [FIRCoreDiagnostics sharedInstance]; [diagnostics sendDiagnosticsData:diagnosticsData]; } - (void)sendDiagnosticsData:(nonnull id)diagnosticsData { dispatch_async(self.diagnosticsQueue, ^{ NSDictionary *diagnosticObjects = diagnosticsData.diagnosticObjects; NSNumber *isDataCollectionDefaultEnabled = diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey]; if (isDataCollectionDefaultEnabled && ![isDataCollectionDefaultEnabled boolValue]) { return; } // Create the proto. logs_proto_mobilesdk_ios_ICoreConfiguration icore_config = logs_proto_mobilesdk_ios_ICoreConfiguration_init_default; icore_config.using_gdt = 1; icore_config.has_using_gdt = 1; // Populate the proto with information. FIRPopulateProtoWithInfoFromUserInfoParams(&icore_config, diagnosticObjects); FIRPopulateProtoWithCommonInfoFromApp(&icore_config, diagnosticObjects); FIRPopulateProtoWithInstalledServices(&icore_config); FIRPopulateProtoWithInfoPlistValues(&icore_config); [self setHeartbeatFlagIfNeededToConfig:&icore_config]; // This log object is capable of converting the proto to bytes. FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icore_config]; // Send the log as a telemetry event. GDTCOREvent *event = [self.transport eventForTransport]; event.dataObject = (id)log; [self.transport sendTelemetryEvent:event]; }); } #pragma mark - Heartbeat - (void)setHeartbeatFlagIfNeededToConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config { // Check if need to send a heartbeat. NSDate *currentDate = [NSDate date]; NSDate *lastCheckin = [self.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag]; if (lastCheckin) { // Ensure the previous checkin was on a different date in the past. if ([self isDate:currentDate inSameDayOrBeforeThan:lastCheckin]) { return; } } // Update heartbeat sent date. [self.heartbeatDateStorage setHearbeatDate:currentDate forTag:kFIRCoreDiagnosticsHeartbeatTag]; // Set the flag. config->sdk_name = logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE; config->has_sdk_name = 1; } - (BOOL)isDate:(NSDate *)date1 inSameDayOrBeforeThan:(NSDate *)date2 { return [[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2] || [date1 compare:date2] == NSOrderedAscending; } @end ================================================ FILE: Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c ================================================ /* * 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. */ /* Automatically generated nanopb constant definitions */ /* Generated by nanopb-0.3.9.7 */ #include "firebasecore.nanopb.h" /* @@protoc_insertion_point(includes) */ #if PB_PROTO_HEADER_VERSION != 30 #error Regenerate this file with the current version of nanopb generator. #endif const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22] = { PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, logs_proto_mobilesdk_ios_ICoreConfiguration, configuration_type, configuration_type, 0), PB_FIELD( 7, UENUM , REPEATED, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_service_installed, configuration_type, 0), PB_FIELD( 9, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, device_model, sdk_service_installed, 0), PB_FIELD( 10, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_id, device_model, 0), PB_FIELD( 12, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, bundle_id, app_id, 0), PB_FIELD( 16, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, pod_name, bundle_id, 0), PB_FIELD( 18, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, icore_version, pod_name, 0), PB_FIELD( 19, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_version, icore_version, 0), PB_FIELD( 20, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, sdk_name, sdk_version, 0), PB_FIELD( 21, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, app_count, sdk_name, 0), PB_FIELD( 22, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, os_version, app_count, 0), PB_FIELD( 24, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, min_supported_ios_version, os_version, 0), PB_FIELD( 25, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, use_default_app, min_supported_ios_version, 0), PB_FIELD( 26, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployed_in_app_store, use_default_app, 0), PB_FIELD( 27, INT32 , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, dynamic_framework_count, deployed_in_app_store, 0), PB_FIELD( 28, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, apple_framework_version, dynamic_framework_count, 0), PB_FIELD( 29, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_zip_file, apple_framework_version, 0), PB_FIELD( 30, UENUM , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, deployment_type, using_zip_file, 0), PB_FIELD( 31, BYTES , OPTIONAL, POINTER , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, platform_info, deployment_type, 0), PB_FIELD( 33, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, swizzling_enabled, platform_info, 0), PB_FIELD( 36, BOOL , OPTIONAL, STATIC , OTHER, logs_proto_mobilesdk_ios_ICoreConfiguration, using_gdt, swizzling_enabled, 0), PB_LAST_FIELD }; /* @@protoc_insertion_point(eof) */ ================================================ FILE: Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h ================================================ /* * 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. */ /* Automatically generated nanopb header */ /* Generated by nanopb-0.3.9.7 */ #ifndef PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED #define PB_LOGS_PROTO_MOBILESDK_IOS_FIREBASECORE_NANOPB_H_INCLUDED #include /* @@protoc_insertion_point(includes) */ #if PB_PROTO_HEADER_VERSION != 30 #error Regenerate this file with the current version of nanopb generator. #endif /* Enum definitions */ typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType { logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_UNKNOWN_CONFIGURATION_TYPE = 0, logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE = 1, logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK = 2 } logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType; #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_UNKNOWN_CONFIGURATION_TYPE #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType)(logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK+1)) typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType { logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_UNKNOWN_BUILD_TYPE = 0, logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_INTERNAL = 1, logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_EAP = 2, logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD = 3 } logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType; #define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_UNKNOWN_BUILD_TYPE #define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD #define _logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType)(logs_proto_mobilesdk_ios_ICoreConfiguration_BuildType_PROD+1)) typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType { logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE = 0, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE = 1, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ADMOB = 2, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_APP_INVITE = 3, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SIGN_IN = 5, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_GCM = 6, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MAPS = 7, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SCION = 8, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ANALYTICS = 9, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_APP_INDEXING = 10, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_CONFIG = 11, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DURABLE_DEEP_LINKS = 12, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_CRASH = 13, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH = 14, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DATABASE = 15, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_STORAGE = 16, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MESSAGING = 17, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MEASUREMENT = 18, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_REMOTE_CONFIG = 19, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DYNAMIC_LINKS = 20, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_INVITES = 21, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH_UI = 22, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FIRESTORE = 23, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_PERFORMANCE = 24, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_FACE = 26, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_BARCODE = 27, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_TEXT = 28, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_LABEL = 29, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_MODEL_INTERPRETER = 30, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_IN_APP_MESSAGING = 31, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FUNCTIONS = 32, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_NATURAL_LANGUAGE = 33, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_AUTOML = 34, logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION = 35 } logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType; #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION #define _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType)(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_VISION_ON_DEVICE_OBJECT_DETECTION+1)) typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName { logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_UNKNOWN_POD_NAME = 0, logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_GOOGLE = 1, logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE = 2 } logs_proto_mobilesdk_ios_ICoreConfiguration_PodName; #define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_UNKNOWN_POD_NAME #define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE #define _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_PodName)(logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE+1)) typedef enum _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType { logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_UNKNOWN = 0, logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS = 1, logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ZIP_FILE = 2, logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_CARTHAGE = 3, logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM = 4 } logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType; #define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_UNKNOWN #define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MAX logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM #define _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ARRAYSIZE ((logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType)(logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM+1)) /* Struct definitions */ typedef struct _logs_proto_mobilesdk_ios_ICoreConfiguration { bool has_configuration_type; logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType configuration_type; pb_size_t sdk_service_installed_count; logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *sdk_service_installed; pb_bytes_array_t *device_model; pb_bytes_array_t *app_id; pb_bytes_array_t *bundle_id; bool has_pod_name; logs_proto_mobilesdk_ios_ICoreConfiguration_PodName pod_name; pb_bytes_array_t *icore_version; pb_bytes_array_t *sdk_version; bool has_sdk_name; logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType sdk_name; bool has_app_count; int32_t app_count; pb_bytes_array_t *os_version; pb_bytes_array_t *min_supported_ios_version; bool has_use_default_app; bool use_default_app; bool has_deployed_in_app_store; bool deployed_in_app_store; bool has_dynamic_framework_count; int32_t dynamic_framework_count; pb_bytes_array_t *apple_framework_version; bool has_using_zip_file; bool using_zip_file; bool has_deployment_type; logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType deployment_type; pb_bytes_array_t *platform_info; bool has_swizzling_enabled; bool swizzling_enabled; bool has_using_gdt; bool using_gdt; /* @@protoc_insertion_point(struct:logs_proto_mobilesdk_ios_ICoreConfiguration) */ } logs_proto_mobilesdk_ios_ICoreConfiguration; /* Default values for struct fields */ /* Initializer values for message structs */ #define logs_proto_mobilesdk_ios_ICoreConfiguration_init_default {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0} #define logs_proto_mobilesdk_ios_ICoreConfiguration_init_zero {false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_MIN, 0, NULL, NULL, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_MIN, NULL, NULL, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MIN, false, 0, NULL, NULL, false, 0, false, 0, false, 0, NULL, false, 0, false, _logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_MIN, NULL, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define logs_proto_mobilesdk_ios_ICoreConfiguration_pod_name_tag 16 #define logs_proto_mobilesdk_ios_ICoreConfiguration_configuration_type_tag 1 #define logs_proto_mobilesdk_ios_ICoreConfiguration_icore_version_tag 18 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_version_tag 19 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_service_installed_tag 7 #define logs_proto_mobilesdk_ios_ICoreConfiguration_sdk_name_tag 20 #define logs_proto_mobilesdk_ios_ICoreConfiguration_device_model_tag 9 #define logs_proto_mobilesdk_ios_ICoreConfiguration_os_version_tag 22 #define logs_proto_mobilesdk_ios_ICoreConfiguration_app_id_tag 10 #define logs_proto_mobilesdk_ios_ICoreConfiguration_bundle_id_tag 12 #define logs_proto_mobilesdk_ios_ICoreConfiguration_min_supported_ios_version_tag 24 #define logs_proto_mobilesdk_ios_ICoreConfiguration_use_default_app_tag 25 #define logs_proto_mobilesdk_ios_ICoreConfiguration_app_count_tag 21 #define logs_proto_mobilesdk_ios_ICoreConfiguration_deployed_in_app_store_tag 26 #define logs_proto_mobilesdk_ios_ICoreConfiguration_dynamic_framework_count_tag 27 #define logs_proto_mobilesdk_ios_ICoreConfiguration_apple_framework_version_tag 28 #define logs_proto_mobilesdk_ios_ICoreConfiguration_using_zip_file_tag 29 #define logs_proto_mobilesdk_ios_ICoreConfiguration_deployment_type_tag 30 #define logs_proto_mobilesdk_ios_ICoreConfiguration_platform_info_tag 31 #define logs_proto_mobilesdk_ios_ICoreConfiguration_swizzling_enabled_tag 33 #define logs_proto_mobilesdk_ios_ICoreConfiguration_using_gdt_tag 36 /* Struct field encoding specification for nanopb */ extern const pb_field_t logs_proto_mobilesdk_ios_ICoreConfiguration_fields[22]; /* Maximum encoded size of messages (where known) */ /* logs_proto_mobilesdk_ios_ICoreConfiguration_size depends on runtime parameters */ /* Message IDs (where set with "msgid" option) */ #ifdef PB_MSGID #define FIREBASECORE_MESSAGES \ #endif /* @@protoc_insertion_point(eof) */ #endif ================================================ FILE: Pods/FirebaseCoreDiagnostics/Firebase/CoreDiagnostics/FIRCDLibrary/Public/FIRCoreDiagnostics.h ================================================ /* * 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. */ // There are no actual public headers in the lib. This is a dummy public header to prevent Cocoapods // from adding all internal headers as public. ================================================ FILE: Pods/FirebaseCoreDiagnostics/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h ================================================ /* * 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 /** If present, is a BOOL wrapped in an NSNumber. */ #define kFIRCDIsDataCollectionDefaultEnabledKey @"FIRCDIsDataCollectionDefaultEnabledKey" /** If present, is an int32_t wrapped in an NSNumber. */ #define kFIRCDConfigurationTypeKey @"FIRCDConfigurationTypeKey" /** If present, is an NSString. */ #define kFIRCDSdkNameKey @"FIRCDSdkNameKey" /** If present, is an NSString. */ #define kFIRCDSdkVersionKey @"FIRCDSdkVersionKey" /** If present, is an int32_t wrapped in an NSNumber. */ #define kFIRCDllAppsCountKey @"FIRCDllAppsCountKey" /** If present, is an NSString. */ #define kFIRCDGoogleAppIDKey @"FIRCDGoogleAppIDKey" /** If present, is an NSString. */ #define kFIRCDBundleIDKey @"FIRCDBundleID" /** If present, is a BOOL wrapped in an NSNumber. */ #define kFIRCDUsingOptionsFromDefaultPlistKey @"FIRCDUsingOptionsFromDefaultPlistKey" /** If present, is an NSString. */ #define kFIRCDLibraryVersionIDKey @"FIRCDLibraryVersionIDKey" /** If present, is an NSString. */ #define kFIRCDFirebaseUserAgentKey @"FIRCDFirebaseUserAgentKey" /** Defines the interface of a data object needed to log diagnostics data. */ @protocol FIRCoreDiagnosticsData @required /** A dictionary containing data (non-exhaustive) to be logged in diagnostics. */ @property(nonatomic) NSDictionary *diagnosticObjects; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCoreDiagnostics/Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h ================================================ /* * 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 "FIRCoreDiagnosticsData.h" NS_ASSUME_NONNULL_BEGIN /** Allows the interoperation of FirebaseCore and FirebaseCoreDiagnostics. */ @protocol FIRCoreDiagnosticsInterop /** Sends the given diagnostics data. * * @param diagnosticsData The diagnostics data object to send. */ + (void)sendDiagnosticsData:(id)diagnosticsData; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseCoreDiagnostics/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/FirebaseCoreDiagnostics/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![License](https://img.shields.io/cocoapods/l/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Platform](https://img.shields.io/cocoapods/p/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Actions Status][gh-abtesting-badge]][gh-actions] [![Actions Status][gh-appcheck-badge]][gh-actions] [![Actions Status][gh-appdistribution-badge]][gh-actions] [![Actions Status][gh-auth-badge]][gh-actions] [![Actions Status][gh-cocoapods-integration-badge]][gh-actions] [![Actions Status][gh-core-badge]][gh-actions] [![Actions Status][gh-core-diagnostics-badge]][gh-actions] [![Actions Status][gh-crashlytics-badge]][gh-actions] [![Actions Status][gh-database-badge]][gh-actions] [![Actions Status][gh-datatransport-badge]][gh-actions] [![Actions Status][gh-dynamiclinks-badge]][gh-actions] [![Actions Status][gh-firebasepod-badge]][gh-actions] [![Actions Status][gh-firestore-badge]][gh-actions] [![Actions Status][gh-functions-badge]][gh-actions] [![Actions Status][gh-google-utilities-badge]][gh-actions] [![Actions Status][gh-google-utilities-components-badge]][gh-actions] [![Actions Status][gh-inappmessaging-badge]][gh-actions] [![Actions Status][gh-interop-badge]][gh-actions] [![Actions Status][gh-messaging-badge]][gh-actions] [![Actions Status][gh-mlmodeldownloader-badge]][gh-actions] [![Actions Status][gh-performance-badge]][gh-actions] [![Actions Status][gh-remoteconfig-badge]][gh-actions] [![Actions Status][gh-storage-badge]][gh-actions] [![Actions Status][gh-symbolcollision-badge]][gh-actions] [![Actions Status][gh-zip-badge]][gh-actions] [![Travis](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase Apple Open Source Development This repository contains all Apple platform Firebase SDK source except FirebaseAnalytics and FirebaseML. Firebase is an app development platform with tools to help you build, grow and monetize your app. More information about Firebase can be found on the [official Firebase website](https://firebase.google.com). **Note** _FirebaseCombineSwift_ contains support for Apple's Combine framework. This module is currently under development, and not yet supported for use in production environments. Fore more details, please refer to the [docs](FirebaseCombineSwift/README.md). ## Installation See the subsections below for details about the different installation methods. 1. [Standard pod install](README.md#standard-pod-install) 1. [Swift Package Manager](SwiftPackageManager.md) 1. [Installing from the GitHub repo](README.md#installing-from-github) 1. [Experimental Carthage](README.md#carthage-ios-only) ### Standard pod install Go to [https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). ### Swift Package Manager Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be found at [SwiftPackageManager.md](SwiftPackageManager.md). ### Installing from GitHub These instructions can be used to access the Firebase repo at other branches, tags, or commits. #### Background See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) for instructions and options about overriding pod source locations. #### Accessing Firebase Source Snapshots All of the official releases are tagged in this repo and available via CocoaPods. To access a local source snapshot or unreleased branch, use Podfile directives like the following: To access FirebaseFirestore via a branch: ```ruby pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' ``` To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: ```ruby pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' ``` ### Carthage (iOS only) Instructions for the experimental Carthage distribution are at [Carthage](Carthage.md). ### Using Firebase from a Framework or a library [Using Firebase from a Framework or a library](docs/firebase_in_libraries.md) ## Development To develop Firebase software in this repository, ensure that you have at least the following software: * Xcode 12.2 (or later) CocoaPods is still the canonical way to develop, but much of the repo now supports development with Swift Package Manager. ### CocoaPods Install * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: ```ruby pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios ``` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. Firestore has a self contained Xcode project. See [Firestore/README.md](Firestore/README.md). #### Development for Catalyst * `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Swift Package Manager * To enable test schemes: `./scripts/setup_spm_tests.sh` * `open Package.swift` or double click `Package.swift` in Finder. * Xcode will open the project * Choose a scheme for a library to build or test suite to run * Choose a target platform by selecting the run destination along with the scheme ### Adding a New Firebase Pod See [AddNewPod.md](AddNewPod.md). ### Managing Headers and Imports See [HeadersImports.md](HeadersImports.md). ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ### Running Sample Apps In order to run the sample apps and integration tests, you'll need a valid `GoogleService-Info.plist` file. The Firebase Xcode project contains dummy plist files without real values, but can be replaced with real plist files. To get your own `GoogleService-Info.plist` files: 1. Go to the [Firebase Console](https://console.firebase.google.com/) 2. Create a new Firebase project, if you don't already have one 3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.Database-Example`) 4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project. ### Coverage Report Generation See [scripts/code_coverage_report/README.md](scripts/code_coverage_report/README.md). ## Specific Component Instructions See the sections below for any special instructions for those components. ### Firebase Auth If you're doing specific Firebase Auth development, see [the Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about building and running the FirebaseAuth pod along with various samples and tests. ### Firebase Database The Firebase Database Integration tests can be run against a locally running Database Emulator or against a production instance. To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before running the integration test. To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to `FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to [public](https://firebase.google.com/docs/database/security/quickstart) while your tests are running. ### Firebase Performance Monitoring If you're doing specific Firebase Performance Monitoring development, see [the Performance README](FirebasePerformance/README.md) for instructions about building the SDK and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about integrating Performance with the dev test App. ### Firebase Storage To run the Storage Integration tests, follow the instructions in [FIRStorageIntegrationTests.m](FirebaseStorage/Tests/Integration/FIRStorageIntegrationTests.m). #### Push Notifications Push notifications can only be delivered to specially provisioned App IDs in the developer portal. In order to actually test receiving push notifications, you will need to: 1. Change the bundle identifier of the sample app to something you own in your Apple Developer account, and enable that App ID for push notifications. 2. You'll also need to [upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) at **Project Settings > Cloud Messaging > [Your Firebase App]**. 3. Ensure your iOS device is added to your Apple Developer portal as a test device. #### iOS Simulator The iOS Simulator cannot register for remote notifications, and will not receive push notifications. In order to receive push notifications, you'll have to follow the steps above and run the app on a physical device. ## Building with Firebase on Apple platforms At this time, not all of Firebase's products are available across all Apple platforms. However, Firebase is constantly evolving and community supported efforts have helped expand Firebase's support. To keep up with the latest info regarding Firebase's support across Apple platforms, refer to [this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform) in Firebase's documentation. ### Community Supported Efforts We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are very grateful! We'd like to empower as many developers as we can to be able to use Firebase and participate in the Firebase community. #### tvOS, macOS, watchOS and Catalyst Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on tvOS, macOS, watchOS and Catalyst. For tvOS, see the [Sample](Example/tvOSSample). For watchOS, currently only Messaging, Storage and Crashlytics (and their dependencies) have limited support. See the [Independent Watch App Sample](Example/watchOSSample). Keep in mind that macOS, tvOS, watchOS and Catalyst are not officially supported by Firebase, and this repository is actively developed primarily for iOS. While we can catch basic unit test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected on macOS, tvOS or watchOS. If you encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). During app setup in the console, you may get to a step that mentions something like "Checking if the app has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/watchOS/Catalyst. **It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. #### Additional MacOS and Catalyst Notes * FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` to Build Settings. * For Catalyst, FirebaseFirestore requires signing the [gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). #### Additional Crashlytics Notes * watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded) ## Roadmap See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source plans and directions. ## Contributing See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase iOS SDK. ## License The contents of this repository are licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Your use of Firebase is governed by the [Terms of Service for Firebase Services](https://firebase.google.com/terms/). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-abtesting-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/abtesting/badge.svg [gh-appcheck-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/app_check/badge.svg [gh-appdistribution-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/appdistribution/badge.svg [gh-auth-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/auth/badge.svg [gh-cocoapods-integration-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/cocoapods-integration/badge.svg [gh-core-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core/badge.svg [gh-core-diagnostics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core-diagnostics/badge.svg [gh-crashlytics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/crashlytics/badge.svg [gh-database-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/database/badge.svg [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg [gh-dynamiclinks-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/dynamiclinks/badge.svg [gh-firebasepod-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firebasepod/badge.svg [gh-firestore-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firestore/badge.svg [gh-functions-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/functions/badge.svg [gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg [gh-google-utilities-components-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities-components/badge.svg [gh-inappmessaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/inappmessaging/badge.svg [gh-interop-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/interop/badge.svg [gh-messaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/messaging/badge.svg [gh-mlmodeldownloader-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/mlmodeldownloader/badge.svg [gh-performance-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/performance/badge.svg [gh-remoteconfig-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/remoteconfig/badge.svg [gh-storage-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/storage/badge.svg [gh-symbolcollision-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/symbolcollision/badge.svg [gh-zip-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/zip/badge.svg ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRAppInternal.h ================================================ /* * Copyright 2017 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 @class FIRComponentContainer; @protocol FIRLibrary; /** * The internal interface to FIRApp. This is meant for first-party integrators, who need to receive * FIRApp notifications, log info about the success or failure of their configuration, and access * other internal functionality of FIRApp. * * TODO(b/28296561): Restructure this header. */ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, FIRConfigType) { FIRConfigTypeCore = 1, FIRConfigTypeSDK = 2, }; extern NSString *const kFIRDefaultAppName; extern NSString *const kFIRAppReadyToConfigureSDKNotification; extern NSString *const kFIRAppDeleteNotification; extern NSString *const kFIRAppIsDefaultAppKey; extern NSString *const kFIRAppNameKey; extern NSString *const kFIRGoogleAppIDKey; extern NSString *const kFirebaseCoreErrorDomain; /** The NSUserDefaults suite name for FirebaseCore, for those storage locations that use it. */ extern NSString *const kFirebaseCoreDefaultsSuiteName; /** * The format string for the User Defaults key used for storing the data collection enabled flag. * This includes formatting to append the Firebase App's name. */ extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat; /** * The plist key used for storing the data collection enabled flag. */ extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey; /** @var FIRAuthStateDidChangeInternalNotification @brief The name of the @c NSNotificationCenter notification which is posted when the auth state changes (e.g. a new token has been produced, a user logs in or out). The object parameter of the notification is a dictionary possibly containing the key: @c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not contain this key it indicates a sign-out event took place. */ extern NSString *const FIRAuthStateDidChangeInternalNotification; /** @var FIRAuthStateDidChangeInternalNotificationTokenKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the new access token. */ extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey; /** @var FIRAuthStateDidChangeInternalNotificationAppKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the FIRApp associated with the auth instance. */ extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey; /** @var FIRAuthStateDidChangeInternalNotificationUIDKey @brief A key present in the dictionary object parameter of the @c FIRAuthStateDidChangeInternalNotification notification. The value associated with this key will contain the new user's UID (or nil if there is no longer a user signed in). */ extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey; @interface FIRApp () /** * A flag indicating if this is the default app (has the default app name). */ @property(nonatomic, readonly) BOOL isDefaultApp; /* * The container of interop SDKs for this app. */ @property(nonatomic) FIRComponentContainer *container; /** * Checks if the default app is configured without trying to configure it. */ + (BOOL)isDefaultAppConfigured; /** * Registers a given third-party library with the given version number to be reported for * analytics. * * @param name Name of the library. * @param version Version of the library. */ + (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version; /** * Registers a given internal library to be reported for analytics. * * @param library Optional parameter for component registration. * @param name Name of the library. */ + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name; /** * Registers a given internal library with the given version number to be reported for * analytics. This should only be used for non-Firebase libraries that have their own versioning * scheme. * * @param library Optional parameter for component registration. * @param name Name of the library. * @param version Version of the library. */ + (void)registerInternalLibrary:(nonnull Class)library withName:(nonnull NSString *)name withVersion:(nonnull NSString *)version; /** * A concatenated string representing all the third-party libraries and version numbers. */ + (NSString *)firebaseUserAgent; /** * Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe. */ + (void)resetApps; /** * Can be used by the unit tests in each SDK to set customized options. */ - (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRComponent.h ================================================ /* * Copyright 2018 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 @class FIRApp; @class FIRComponentContainer; NS_ASSUME_NONNULL_BEGIN /// Provides a system to clean up cached instances returned from the component system. NS_SWIFT_NAME(ComponentLifecycleMaintainer) @protocol FIRComponentLifecycleMaintainer /// The associated app will be deleted, clean up any resources as they are about to be deallocated. - (void)appWillBeDeleted:(FIRApp *)app; @end typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container, BOOL *isCacheable) NS_SWIFT_NAME(ComponentCreationBlock); @class FIRDependency; /// Describes the timing of instantiation. Note: new components should default to lazy unless there /// is a strong reason to be eager. typedef NS_ENUM(NSInteger, FIRInstantiationTiming) { FIRInstantiationTimingLazy, FIRInstantiationTimingAlwaysEager, FIRInstantiationTimingEagerInDefaultApp } NS_SWIFT_NAME(InstantiationTiming); /// A component that can be used from other Firebase SDKs. NS_SWIFT_NAME(Component) @interface FIRComponent : NSObject /// The protocol describing functionality provided from the Component. @property(nonatomic, strong, readonly) Protocol *protocol; /// The timing of instantiation. @property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming; /// An array of dependencies for the component. @property(nonatomic, copy, readonly) NSArray *dependencies; /// A block to instantiate an instance of the component with the appropriate dependencies. @property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock; // There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format // for the next two methods. // clang-format off /// Creates a component with no dependencies that will be lazily initialized. + (instancetype)componentWithProtocol:(Protocol *)protocol creationBlock:(FIRComponentCreationBlock)creationBlock NS_SWIFT_NAME(init(_:creationBlock:)); /// Creates a component to be registered with the component container. /// /// @param protocol - The protocol describing functionality provided by the component. /// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's /// a good reason to be instantiated earlier. /// @param dependencies - Any dependencies the `implementingClass` has, optional or required. /// @param creationBlock - A block to instantiate the component with a container, and if /// @return A component that can be registered with the component container. + (instancetype)componentWithProtocol:(Protocol *)protocol instantiationTiming:(FIRInstantiationTiming)instantiationTiming dependencies:(NSArray *)dependencies creationBlock:(FIRComponentCreationBlock)creationBlock NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:)); // clang-format on /// Unavailable. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRComponentContainer.h ================================================ /* * Copyright 2018 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 /// A type-safe macro to retrieve a component from a container. This should be used to retrieve /// components instead of using the container directly. #define FIR_COMPONENT(type, container) \ [FIRComponentType> instanceForProtocol:@protocol(type) inContainer:container] @class FIRApp; /// A container that holds different components that are registered via the /// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant` /// in order to properly register components for Core. NS_SWIFT_NAME(FirebaseComponentContainer) @interface FIRComponentContainer : NSObject /// A weak reference to the app that an instance of the container belongs to. @property(nonatomic, weak, readonly) FIRApp *app; /// Unavailable. Use the `container` property on `FIRApp`. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRComponentType.h ================================================ /* * Copyright 2018 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 @class FIRComponentContainer; NS_ASSUME_NONNULL_BEGIN /// Do not use directly. A placeholder type in order to provide a macro that will warn users of /// mis-matched protocols. NS_SWIFT_NAME(ComponentType) @interface FIRComponentType<__covariant T> : NSObject /// Do not use directly. A factory method to retrieve an instance that provides a specific /// functionality. + (T)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h ================================================ /* * 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 @class FIRDiagnosticsData; @class FIROptions; NS_ASSUME_NONNULL_BEGIN /** Connects FIRCore with the CoreDiagnostics library. */ @interface FIRCoreDiagnosticsConnector : NSObject /** Logs FirebaseCore related data. * * @param options The options object containing data to log. */ + (void)logCoreTelemetryWithOptions:(FIROptions *)options; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRDependency.h ================================================ /* * Copyright 2018 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 /// A dependency on a specific protocol's functionality. NS_SWIFT_NAME(Dependency) @interface FIRDependency : NSObject /// The protocol describing functionality being depended on. @property(nonatomic, strong, readonly) Protocol *protocol; /// A flag to specify if the dependency is required or not. @property(nonatomic, readonly) BOOL isRequired; /// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for /// the required parameter. /// Creates a required dependency on the specified protocol's functionality. + (instancetype)dependencyWithProtocol:(Protocol *)protocol; /// Creates a dependency on the specified protocol's functionality and specify if it's required for /// the class's functionality. + (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; /// Use `dependencyWithProtocol:isRequired:` instead. - (instancetype)init NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRHeartbeatInfo.h ================================================ // 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 @interface FIRHeartbeatInfo : NSObject // Enum representing the different heartbeat codes. typedef NS_ENUM(NSInteger, FIRHeartbeatInfoCode) { FIRHeartbeatInfoCodeNone = 0, FIRHeartbeatInfoCodeSDK = 1, FIRHeartbeatInfoCodeGlobal = 2, FIRHeartbeatInfoCodeCombined = 3, }; /** * Get heartbeat code required for the sdk. * @param heartbeatTag String representing the sdk heartbeat tag. * @return Heartbeat code indicating whether or not an sdk/global heartbeat * needs to be sent */ + (FIRHeartbeatInfoCode)heartbeatCodeForTag:(NSString *)heartbeatTag; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRLibrary.h ================================================ /* * Copyright 2018 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. */ #ifndef FIRLibrary_h #define FIRLibrary_h #import @class FIRApp; @class FIRComponent; NS_ASSUME_NONNULL_BEGIN /// Provide an interface to register a library for userAgent logging and availability to others. NS_SWIFT_NAME(Library) @protocol FIRLibrary /// Returns one or more FIRComponents that will be registered in /// FIRApp and participate in dependency resolution and injection. + (NSArray *)componentsToRegister; @optional /// Implement this method if the library needs notifications for lifecycle events. This method is /// called when the developer calls `FirebaseApp.configure()`. + (void)configureWithApp:(FIRApp *)app; @end NS_ASSUME_NONNULL_END #endif /* FIRLibrary_h */ ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIRLogger.h ================================================ /* * Copyright 2017 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 NS_ASSUME_NONNULL_BEGIN /** * The Firebase services used in Firebase logger. */ typedef NSString *const FIRLoggerService; extern FIRLoggerService kFIRLoggerAnalytics; extern FIRLoggerService kFIRLoggerCrash; extern FIRLoggerService kFIRLoggerCore; extern FIRLoggerService kFIRLoggerRemoteConfig; /** * The key used to store the logger's error count. */ extern NSString *const kFIRLoggerErrorCountKey; /** * The key used to store the logger's warning count. */ extern NSString *const kFIRLoggerWarningCountKey; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Enables or disables Analytics debug mode. * If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug. * Enabling the debug mode has no effect if the app is running from App Store. * (required) analytics debug mode flag. */ void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode); /** * Changes the default logging level of FIRLoggerLevelNotice to a user-specified level. * The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store. * (required) log level (one of the FIRLoggerLevel enum values). */ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); /** * Checks if the specified logger level is loggable given the current settings. * (required) log level (one of the FIRLoggerLevel enum values). * (required) whether or not this function is called from the Analytics component. */ BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent); /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming. * (required) log level (one of the FIRLoggerLevel enum values). * (required) service name of type FIRLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ extern void FIRLogBasic(FIRLoggerLevel level, FIRLoggerService service, NSString *messageCode, NSString *message, // On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable // See: http://stackoverflow.com/q/29095469 #if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX va_list args_ptr #else va_list _Nullable args_ptr #endif ); /** * The following functions accept the following parameters in order: * (required) service name of type FIRLoggerService. * (required) message code starting from "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * See go/firebase-log-proposal for details. * (required) message string which can be a format string. * (optional) the list of arguments to substitute into the format string. * Example usage: * FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); */ extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); #ifdef __cplusplus } // extern "C" #endif // __cplusplus @interface FIRLoggerWrapper : NSObject /** * Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger * (required) log level (one of the FIRLoggerLevel enum values). * (required) service name of type FIRLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ + (void)logWithLevel:(FIRLoggerLevel)level withService:(FIRLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FIROptionsInternal.h ================================================ /* * Copyright 2017 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 /** * Keys for the strings in the plist file. */ extern NSString *const kFIRAPIKey; extern NSString *const kFIRTrackingID; extern NSString *const kFIRGoogleAppID; extern NSString *const kFIRClientID; extern NSString *const kFIRGCMSenderID; extern NSString *const kFIRAndroidClientID; extern NSString *const kFIRDatabaseURL; extern NSString *const kFIRStorageBucket; extern NSString *const kFIRBundleID; extern NSString *const kFIRProjectID; /** * Keys for the plist file name */ extern NSString *const kServiceInfoFileName; extern NSString *const kServiceInfoFileType; /** * This header file exposes the initialization of FIROptions to internal use. */ @interface FIROptions () /** * resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests. */ + (void)resetDefaultOptions; /** * Initializes the options with dictionary. The above strings are the keys of the dictionary. * This is the designated initializer. */ - (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary NS_DESIGNATED_INITIALIZER; /** * defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and * other first party services. */ + (FIROptions *)defaultOptions; + (NSDictionary *)defaultOptionsDictionary; /** * Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at * runtime. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet; /** * Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless * explicitly disabled in GoogleService-Info.plist. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled; /** * Whether or not Analytics Collection was completely disabled. If YES, then * isAnalyticsCollectionEnabled will be NO. */ @property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated; /** * The version ID of the client library, e.g. @"1100000". */ @property(nonatomic, readonly, copy) NSString *libraryVersionID; /** * The flag indicating whether this object was constructed with the values in the default plist * file. */ @property(nonatomic) BOOL usingOptionsFromDefaultPlist; /** * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in * GoogleService-Info.plist. */ @property(nonatomic, readonly) BOOL isMeasurementEnabled; /** * Whether or not Analytics was enabled in the developer console. */ @property(nonatomic, readonly) BOOL isAnalyticsEnabled; /** * Whether or not SignIn was enabled in the developer console. */ @property(nonatomic, readonly) BOOL isSignInEnabled; /** * Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp. */ @property(nonatomic, getter=isEditingLocked) BOOL editingLocked; @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseCore/Sources/Private/FirebaseCoreInternal.h ================================================ // 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. // An umbrella header, for any other libraries in this repo to access Firebase Public and Private // headers. Any package manager complexity should be handled here. #import #import "FirebaseCore/Sources/Private/FIRAppInternal.h" #import "FirebaseCore/Sources/Private/FIRComponent.h" #import "FirebaseCore/Sources/Private/FIRComponentContainer.h" #import "FirebaseCore/Sources/Private/FIRComponentType.h" #import "FirebaseCore/Sources/Private/FIRDependency.h" #import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h" #import "FirebaseCore/Sources/Private/FIRLibrary.h" #import "FirebaseCore/Sources/Private/FIRLogger.h" #import "FirebaseCore/Sources/Private/FIROptionsInternal.h" ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsErrors.h" @class FIRInstallationsHTTPError; @class FBLPromise; NS_ASSUME_NONNULL_BEGIN void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer); @interface FIRInstallationsErrorUtil : NSObject + (NSError *)keyedArchiverErrorWithException:(NSException *)exception; + (NSError *)keyedArchiverErrorWithError:(NSError *)error; + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status; + (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName; + (NSError *)JSONSerializationError:(NSError *)error; + (NSError *)networkErrorWithError:(NSError *)error; + (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName; + (NSError *)corruptedIIDTokenData; + (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data; + (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode; + (NSError *)backoffIntervalWaitError; /** * Returns the passed error if it is already in the public domain or a new error with the passed * error at `NSUnderlyingErrorKey`. */ + (NSError *)publicDomainErrorWithError:(NSError *)error; + (FBLPromise *)rejectedPromiseWithError:(NSError *)error; + (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code failureReason:(nullable NSString *)failureReason underlyingError:(nullable NSError *)underlyingError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h" #if __has_include() #import #else #import "FBLPromises.h" #endif NSString *const kFirebaseInstallationsErrorDomain = @"com.firebase.installations"; void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer) { if (pointer != NULL) { *pointer = error; } } @implementation FIRInstallationsErrorUtil + (NSError *)keyedArchiverErrorWithException:(NSException *)exception { NSString *failureReason = [NSString stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@", exception.name, exception.reason, exception.userInfo]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } + (NSError *)keyedArchiverErrorWithError:(NSError *)error { NSString *failureReason = [NSString stringWithFormat:@"NSKeyedArchiver error."]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:error]; } + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeKeychain failureReason:failureReason underlyingError:nil]; } + (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSString *)appName { NSString *failureReason = [NSString stringWithFormat:@"Installation for appID %@ appName %@ not found", appID, appName]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } + (NSError *)corruptedIIDTokenData { NSString *failureReason = @"IID token data stored in Keychain is corrupted or in an incompatible format."; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } + (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data { return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:HTTPResponse data:data]; } + (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode { if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) { return NO; } return [(FIRInstallationsHTTPError *)error HTTPResponse].statusCode == HTTPCode; } + (NSError *)JSONSerializationError:(NSError *)error { NSString *failureReason = [NSString stringWithFormat:@"Failed to serialize JSON data."]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } + (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName { NSString *failureReason = [NSString stringWithFormat:@"A required response field with name %@ is missing", missingFieldName]; return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } + (NSError *)networkErrorWithError:(NSError *)error { return [self installationsErrorWithCode:FIRInstallationsErrorCodeServerUnreachable failureReason:@"Network connection error." underlyingError:error]; } + (NSError *)backoffIntervalWaitError { return [self installationsErrorWithCode:FIRInstallationsErrorCodeServerUnreachable failureReason:@"Too many server requests." underlyingError:nil]; } + (NSError *)publicDomainErrorWithError:(NSError *)error { if ([error.domain isEqualToString:kFirebaseInstallationsErrorDomain]) { return error; } return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:nil underlyingError:error]; } + (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code failureReason:(nullable NSString *)failureReason underlyingError:(nullable NSError *)underlyingError { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[NSUnderlyingErrorKey] = underlyingError; userInfo[NSLocalizedFailureReasonErrorKey] = failureReason ?: [NSString stringWithFormat:@"Underlying error: %@", underlyingError.localizedDescription]; return [NSError errorWithDomain:kFirebaseInstallationsErrorDomain code:code userInfo:userInfo]; } + (FBLPromise *)rejectedPromiseWithError:(NSError *)error { FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; [rejectedPromise reject:error]; return rejectedPromise; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h ================================================ /* * 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 /** Represents an error caused by an unexpected API response. */ @interface FIRInstallationsHTTPError : NSError @property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; @property(nonatomic, readonly, nonnull) NSData *data; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data; @end NS_ASSUME_NONNULL_END typedef NS_ENUM(NSInteger, FIRInstallationsHTTPCodes) { FIRInstallationsHTTPCodesTooManyRequests = 429, FIRInstallationsHTTPCodesServerInternalError = 500, }; /** Possible response HTTP codes for `CreateInstallation` API request. */ typedef NS_ENUM(NSInteger, FIRInstallationsRegistrationHTTPCode) { FIRInstallationsRegistrationHTTPCodeSuccess = 201, FIRInstallationsRegistrationHTTPCodeInvalidArgument = 400, FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch = 403, FIRInstallationsRegistrationHTTPCodeProjectNotFound = 404, FIRInstallationsRegistrationHTTPCodeTooManyRequests = 429, FIRInstallationsRegistrationHTTPCodeServerInternalError = 500 }; typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenHTTPCode) { FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication = 401, FIRInstallationsAuthTokenHTTPCodeFIDNotFound = 404, }; ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" @implementation FIRInstallationsHTTPError - (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data { NSDictionary *userInfo = [FIRInstallationsHTTPError userInfoWithHTTPResponse:HTTPResponse data:data]; self = [super initWithDomain:kFirebaseInstallationsErrorDomain code:[FIRInstallationsHTTPError errorCodeWithHTTPCode:HTTPResponse.statusCode] userInfo:userInfo]; if (self) { _HTTPResponse = HTTPResponse; _data = data; } return self; } + (FIRInstallationsErrorCode)errorCodeWithHTTPCode:(NSInteger)HTTPCode { return FIRInstallationsErrorCodeUnknown; } + (NSDictionary *)userInfoWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data { NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *failureReason = [NSString stringWithFormat:@"The server responded with an error: \n - URL: %@ \n - HTTP " @"status code: %ld \n - Response body: %@", HTTPResponse.URL, (long)HTTPResponse.statusCode, responseString]; return @{NSLocalizedFailureReasonErrorKey : failureReason}; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:self.HTTPResponse data:self.data]; } #pragma mark - NSSecureCoding - (nullable instancetype)initWithCoder:(NSCoder *)coder { NSHTTPURLResponse *HTTPResponse = [coder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:@"HTTPResponse"]; if (!HTTPResponse) { return nil; } NSData *data = [coder decodeObjectOfClass:[NSData class] forKey:@"data"]; return [self initWithHTTPResponse:HTTPResponse data:data]; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.HTTPResponse forKey:@"HTTPResponse"]; [coder encodeObject:self.data forKey:@"data"]; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallations.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallations.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsItem.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" #import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" NS_ASSUME_NONNULL_BEGIN static const NSUInteger kExpectedAPIKeyLength = 39; @protocol FIRInstallationsInstanceProvider @end @interface FIRInstallations () @property(nonatomic, readonly) FIROptions *appOptions; @property(nonatomic, readonly) NSString *appName; @property(nonatomic, readonly) FIRInstallationsIDController *installationsIDController; @end @implementation FIRInstallations #pragma mark - Firebase component + (void)load { [FIRApp registerInternalLibrary:(Class)self withName:@"fire-install"]; } + (nonnull NSArray *)componentsToRegister { FIRComponentCreationBlock creationBlock = ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { *isCacheable = YES; FIRInstallations *installations = [[FIRInstallations alloc] initWithApp:container.app]; return installations; }; FIRComponent *installationsProvider = [FIRComponent componentWithProtocol:@protocol(FIRInstallationsInstanceProvider) instantiationTiming:FIRInstantiationTimingAlwaysEager dependencies:@[] creationBlock:creationBlock]; return @[ installationsProvider ]; } - (instancetype)initWithApp:(FIRApp *)app { return [self initWitAppOptions:app.options appName:app.name]; } - (instancetype)initWitAppOptions:(FIROptions *)appOptions appName:(NSString *)appName { FIRInstallationsIDController *IDController = [[FIRInstallationsIDController alloc] initWithGoogleAppID:appOptions.googleAppID appName:appName APIKey:appOptions.APIKey projectID:appOptions.projectID GCMSenderID:appOptions.GCMSenderID accessGroup:appOptions.appGroupID]; // `prefetchAuthToken` is disabled due to b/156746574. return [self initWithAppOptions:appOptions appName:appName installationsIDController:IDController prefetchAuthToken:NO]; } /// The initializer is supposed to be used by tests to inject `installationsStore`. - (instancetype)initWithAppOptions:(FIROptions *)appOptions appName:(NSString *)appName installationsIDController:(FIRInstallationsIDController *)installationsIDController prefetchAuthToken:(BOOL)prefetchAuthToken { self = [super init]; if (self) { [[self class] validateAppOptions:appOptions appName:appName]; [[self class] assertCompatibleIIDVersion]; _appOptions = [appOptions copy]; _appName = [appName copy]; _installationsIDController = installationsIDController; // Pre-fetch auth token. if (prefetchAuthToken) { [self authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult, NSError *_Nullable error){ }]; } } return self; } + (void)validateAppOptions:(FIROptions *)appOptions appName:(NSString *)appName { NSMutableArray *missingFields = [NSMutableArray array]; if (appName.length < 1) { [missingFields addObject:@"`FirebaseApp.name`"]; } if (appOptions.APIKey.length < 1) { [missingFields addObject:@"`FirebaseOptions.APIKey`"]; } if (appOptions.googleAppID.length < 1) { [missingFields addObject:@"`FirebaseOptions.googleAppID`"]; } if (appOptions.projectID.length < 1) { [missingFields addObject:@"`FirebaseOptions.projectID`"]; } if (missingFields.count > 0) { [NSException raise:kFirebaseInstallationsErrorDomain format: @"%@[%@] Could not configure Firebase Installations due to invalid FirebaseApp " @"options. The following parameters are nil or empty: %@. If you use " @"GoogleServices-Info.plist please download the most recent version from the Firebase " @"Console. If you configure Firebase in code, please make sure you specify all " @"required parameters.", kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseAppOptions, [missingFields componentsJoinedByString:@", "]]; } [self validateAPIKey:appOptions.APIKey]; } + (void)validateAPIKey:(nullable NSString *)APIKey { NSMutableArray *validationIssues = [[NSMutableArray alloc] init]; if (APIKey.length != kExpectedAPIKeyLength) { [validationIssues addObject:[NSString stringWithFormat:@"API Key length must be %lu characters", (unsigned long)kExpectedAPIKeyLength]]; } if (![[APIKey substringToIndex:1] isEqualToString:@"A"]) { [validationIssues addObject:@"API Key must start with `A`"]; } NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; [allowedCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@"-_"]]; NSCharacterSet *characters = [NSCharacterSet characterSetWithCharactersInString:APIKey]; if (![allowedCharacters isSupersetOfSet:characters]) { [validationIssues addObject:@"API Key must contain only base64 url-safe characters characters"]; } if (validationIssues.count > 0) { [NSException raise:kFirebaseInstallationsErrorDomain format: @"%@[%@] Could not configure Firebase Installations due to invalid FirebaseApp " @"options. `FirebaseOptions.APIKey` doesn't match the expected format: %@. If you use " @"GoogleServices-Info.plist please download the most recent version from the Firebase " @"Console. If you configure Firebase in code, please make sure you specify all " @"required parameters.", kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseAppOptions, [validationIssues componentsJoinedByString:@", "]]; } } #pragma mark - Public + (FIRInstallations *)installations { FIRApp *defaultApp = [FIRApp defaultApp]; if (!defaultApp) { [NSException raise:kFirebaseInstallationsErrorDomain format:@"The default FirebaseApp instance must be configured before the default" @"FirebaseApp instance can be initialized. One way to ensure this is to " @"call `FirebaseApp.configure()` in the App Delegate's " @"`application(_:didFinishLaunchingWithOptions:)` " @"(or the `@main` struct's initializer in SwiftUI)."]; } return [self installationsWithApp:defaultApp]; } + (FIRInstallations *)installationsWithApp:(FIRApp *)app { id installations = FIR_COMPONENT(FIRInstallationsInstanceProvider, app.container); return (FIRInstallations *)installations; } - (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion { [self.installationsIDController getInstallationItem] .then(^id(FIRInstallationsItem *installation) { completion(installation.firebaseInstallationID, nil); return nil; }) .catch(^(NSError *error) { completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]); }); } - (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion { [self authTokenForcingRefresh:NO completion:completion]; } - (void)authTokenForcingRefresh:(BOOL)forceRefresh completion:(FIRInstallationsTokenHandler)completion { [self.installationsIDController getAuthTokenForcingRefresh:forceRefresh] .then(^FIRInstallationsAuthTokenResult *(FIRInstallationsItem *installation) { FIRInstallationsAuthTokenResult *result = [[FIRInstallationsAuthTokenResult alloc] initWithToken:installation.authToken.token expirationDate:installation.authToken.expirationDate]; return result; }) .then(^id(FIRInstallationsAuthTokenResult *token) { completion(token, nil); return nil; }) .catch(^void(NSError *error) { completion(nil, [FIRInstallationsErrorUtil publicDomainErrorWithError:error]); }); } - (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion { [self.installationsIDController deleteInstallation] .then(^id(id result) { completion(nil); return nil; }) .catch(^void(NSError *error) { completion([FIRInstallationsErrorUtil publicDomainErrorWithError:error]); }); } #pragma mark - IID version compatibility + (void)assertCompatibleIIDVersion { // We use this flag to disable IID compatibility exception for unit tests. #ifdef FIR_INSTALLATIONS_ALLOWS_INCOMPATIBLE_IID_VERSION return; #else if (![self isIIDVersionCompatible]) { [NSException raise:kFirebaseInstallationsErrorDomain format:@"Firebase Instance ID is not compatible with Firebase 8.x+. Please remove the " @"dependency from the app. See the documentation at " @"https://firebase.google.com/docs/cloud-messaging/ios/" @"client#fetching-the-current-registration-token."]; } #endif } + (BOOL)isIIDVersionCompatible { Class IIDClass = NSClassFromString(@"FIRInstanceID"); if (IIDClass == nil) { // It is OK if there is no IID at all. return YES; } // We expect a compatible version having the method `+[FIRInstanceID usesFIS]` defined. BOOL isCompatibleVersion = [IIDClass respondsToSelector:NSSelectorFromString(@"usesFIS")]; return isCompatibleVersion; } @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h" @implementation FIRInstallationsAuthTokenResult - (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate { self = [super init]; if (self) { _authToken = [token copy]; _expirationDate = expirationDate; } return self; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsAuthTokenResult.h" NS_ASSUME_NONNULL_BEGIN @interface FIRInstallationsAuthTokenResult (Internal) - (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationTime; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h" @class FIRInstallationsStoredItem; @class FIRInstallationsStoredAuthToken; @class FIRInstallationsStoredIIDCheckin; NS_ASSUME_NONNULL_BEGIN /** * The class represents the required installation ID and auth token data including possible states. * The data is stored to Keychain via `FIRInstallationsStoredItem` which has only the storage * relevant data and does not contain any logic. `FIRInstallationsItem` must be used on the logic * level (not `FIRInstallationsStoredItem`). */ @interface FIRInstallationsItem : NSObject /// A `FirebaseApp` identifier. @property(nonatomic, readonly) NSString *appID; /// A `FirebaseApp` name. @property(nonatomic, readonly) NSString *firebaseAppName; /// A stable identifier that uniquely identifies the app instance. @property(nonatomic, copy, nullable) NSString *firebaseInstallationID; /// The `refreshToken` is used to authorize the auth token requests. @property(nonatomic, copy, nullable) NSString *refreshToken; @property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; @property(nonatomic, assign) FIRInstallationsStatus registrationStatus; /// Instance ID default token imported from IID store as a part of IID migration. @property(nonatomic, nullable) NSString *IIDDefaultToken; - (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName; /** * Populates `FIRInstallationsItem` properties with data from `FIRInstallationsStoredItem`. * @param item An instance of `FIRInstallationsStoredItem` to get data from. */ - (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item; /** * Creates a stored item with data from the object. * @return Returns a `FIRInstallationsStoredItem` instance with the data from the object. */ - (FIRInstallationsStoredItem *)storedItem; /** * The installation identifier. * @return Returns a string uniquely identifying the installation. */ - (NSString *)identifier; /** Validates if all the required item fields are populated and values don't explicitly conflict * with each other. * @param outError A reference to be populated with an error containing validation failure details. * @return `YES` if the item it valid, `NO` otherwise. */ - (BOOL)isValid:(NSError *_Nullable *)outError; /** * The installation identifier. * @param appID A `FirebaseApp` identifier. * @param appName A `FirebaseApp` name. * @return Returns a string uniquely identifying the installation. */ + (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName; /** * Generate a new Firebase Installation Identifier. * @return Returns a 22 characters long globally unique string created based on UUID. */ + (NSString *)generateFID; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsItem.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/FIRInstallationsItem.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" @implementation FIRInstallationsItem - (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName { self = [super init]; if (self) { _appID = [appID copy]; _firebaseAppName = [firebaseAppName copy]; } return self; } - (nonnull id)copyWithZone:(nullable NSZone *)zone { FIRInstallationsItem *clone = [[FIRInstallationsItem alloc] initWithAppID:self.appID firebaseAppName:self.firebaseAppName]; clone.firebaseInstallationID = [self.firebaseInstallationID copy]; clone.refreshToken = [self.refreshToken copy]; clone.authToken = [self.authToken copy]; clone.registrationStatus = self.registrationStatus; clone.IIDDefaultToken = [self.IIDDefaultToken copy]; return clone; } - (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item { self.firebaseInstallationID = item.firebaseInstallationID; self.refreshToken = item.refreshToken; self.authToken = item.authToken; self.registrationStatus = item.registrationStatus; self.IIDDefaultToken = item.IIDDefaultToken; } - (FIRInstallationsStoredItem *)storedItem { FIRInstallationsStoredItem *storedItem = [[FIRInstallationsStoredItem alloc] init]; storedItem.firebaseInstallationID = self.firebaseInstallationID; storedItem.refreshToken = self.refreshToken; storedItem.authToken = self.authToken; storedItem.registrationStatus = self.registrationStatus; storedItem.IIDDefaultToken = self.IIDDefaultToken; return storedItem; } - (nonnull NSString *)identifier { return [[self class] identifierWithAppID:self.appID appName:self.firebaseAppName]; } - (BOOL)isValid:(NSError *_Nullable *)outError { NSMutableArray *validationIssues = [NSMutableArray array]; if (self.appID.length == 0) { [validationIssues addObject:@"`appID` must not be empty"]; } if (self.firebaseAppName.length == 0) { [validationIssues addObject:@"`firebaseAppName` must not be empty"]; } if (self.firebaseInstallationID.length == 0) { [validationIssues addObject:@"`firebaseInstallationID` must not be empty"]; } switch (self.registrationStatus) { case FIRInstallationStatusUnknown: [validationIssues addObject:@"invalid `registrationStatus`"]; break; case FIRInstallationStatusRegistered: if (self.refreshToken == 0) { [validationIssues addObject:@"registered installation must have non-empty `refreshToken`"]; } if (self.authToken.token == 0) { [validationIssues addObject:@"registered installation must have non-empty `authToken.token`"]; } if (self.authToken.expirationDate == nil) { [validationIssues addObject:@"registered installation must have non-empty `authToken.expirationDate`"]; } case FIRInstallationStatusUnregistered: break; } BOOL isValid = validationIssues.count == 0; if (!isValid && outError) { NSString *failureReason = [NSString stringWithFormat:@"FIRInstallationsItem validation errors: %@", [validationIssues componentsJoinedByString:@", "]]; *outError = [FIRInstallationsErrorUtil installationsErrorWithCode:FIRInstallationsErrorCodeUnknown failureReason:failureReason underlyingError:nil]; } return isValid; } + (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName { return [appID stringByAppendingString:appName]; } + (NSString *)generateFID { NSUUID *UUID = [NSUUID UUID]; uuid_t UUIDBytes; [UUID getUUIDBytes:UUIDBytes]; NSUInteger UUIDLength = sizeof(uuid_t); NSData *UUIDData = [NSData dataWithBytes:UUIDBytes length:UUIDLength]; uint8_t UUIDLast4Bits = UUIDBytes[UUIDLength - 1] & 0b00001111; // FID first 4 bits must be `0111`. The last 4 UUID bits will be cut later to form a proper FID. // To keep 16 random bytes we copy these last 4 UUID to the FID 1st byte after `0111` prefix. uint8_t FIDPrefix = 0b01110000 | UUIDLast4Bits; NSMutableData *FIDData = [NSMutableData dataWithBytes:&FIDPrefix length:1]; [FIDData appendData:UUIDData]; NSString *FIDString = [self base64URLEncodedStringWithData:FIDData]; // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes. // Our generated ID has 16 bytes UUID + 1 byte prefix which after encoding with base64 will become // 23 characters plus 1 character for "=" padding. // Remove the 23rd character that was added because of the extra 4 bits at the // end of our 17 byte data and the '=' padding. return [FIDString substringWithRange:NSMakeRange(0, 22)]; } + (NSString *)base64URLEncodedStringWithData:(NSData *)data { NSString *string = [data base64EncodedStringWithOptions:0]; string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; return string; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h ================================================ /* * 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 "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" extern FIRLoggerService kFIRLoggerInstallations; // FIRInstallationsAPIService.m extern NSString *const kFIRInstallationsMessageCodeSendAPIRequest; extern NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError; extern NSString *const kFIRInstallationsMessageCodeAPIRequestResponse; extern NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse; extern NSString *const kFIRInstallationsMessageCodeParsingAPIResponse; extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed; extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed; extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed; extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed; // FIRInstallationsIDController.m extern NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated; extern NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated; extern NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated; extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration; extern NSString *const kFIRInstallationsMessageCodeCorruptedStoredInstallation; // FIRInstallationsStoredItem.m extern NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch; // FIRInstallationsStoredAuthToken.m extern NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch; // FIRInstallationsStoredIIDCheckin.m extern NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch; extern NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode; // FIRInstallations.m extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions; ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" FIRLoggerService kFIRLoggerInstallations = @"[Firebase/Installations]"; // FIRInstallationsAPIService.m NSString *const kFIRInstallationsMessageCodeSendAPIRequest = @"I-FIS001001"; NSString *const kFIRInstallationsMessageCodeAPIRequestNetworkError = @"I-FIS001002"; NSString *const kFIRInstallationsMessageCodeAPIRequestResponse = @"I-FIS001003"; NSString *const kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse = @"I-FIS001004"; NSString *const kFIRInstallationsMessageCodeParsingAPIResponse = @"I-FIS001005"; NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed = @"I-FIS001006"; NSString *const kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed = @"I-FIS001007"; NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed = @"I-FIS001008"; NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed = @"I-FIS001009"; // FIRInstallationsIDController.m NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated = @"I-FIS002000"; NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated = @"I-FIS002001"; NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated = @"I-FIS002002"; NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration = @"I-FIS002003"; NSString *const kFIRInstallationsMessageCodeCorruptedStoredInstallation = @"I-FIS002004"; // FIRInstallationsStoredItem.m NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch = @"I-FIS003000"; // FIRInstallationsStoredAuthToken.m NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch = @"I-FIS004000"; // FIRInstallationsStoredIIDCheckin.m NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch = @"I-FIS007000"; NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode = @"I-FIS007001"; // FIRInstallations.m NSString *const kFIRInstallationsMessageCodeInvalidFirebaseAppOptions = @"I-FIS008000"; ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h ================================================ /* * 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 @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /** The class encapsulates a port of a piece FirebaseInstanceID logic required to migrate IID. */ @interface FIRInstallationsIIDStore : NSObject /** * Retrieves existing IID if present. * @return Returns a promise that is resolved with IID string if IID has been found or rejected with * an error otherwise. */ - (FBLPromise *)existingIID; /** * Deletes existing IID if present. * @return Returns a promise that is resolved with `[NSNull null]` if the IID was successfully. * deleted or was not found. The promise is rejected otherwise. */ - (FBLPromise *)deleteExistingIID; #if TARGET_OS_OSX /// If not `nil`, then only this keychain will be used to save and read data (see /// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests. @property(nonatomic, nullable) SecKeychainRef keychainRef; #endif // TARGET_OSX @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" static NSString *const kFIRInstallationsIIDKeyPairPublicTagPrefix = @"com.google.iid.keypair.public-"; static NSString *const kFIRInstallationsIIDKeyPairPrivateTagPrefix = @"com.google.iid.keypair.private-"; static NSString *const kFIRInstallationsIIDCreationTimePlistKey = @"|S|cre"; @implementation FIRInstallationsIIDStore - (FBLPromise *)existingIID { return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) do:^id _Nullable { if (![self hasPlistIIDFlag]) { return nil; } NSData *IIDPublicKeyData = [self IIDPublicKeyData]; return [self IIDWithPublicKeyData:IIDPublicKeyData]; }] .validate(^BOOL(NSString *_Nullable IID) { return IID.length > 0; }); } - (FBLPromise *)deleteExistingIID { return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) do:^id _Nullable { NSError *error; if (![self deleteIIDFlagFromPlist:&error]) { return error; } if (![self deleteIID:&error]) { return error; } return [NSNull null]; }]; } #pragma mark - IID decoding - (NSString *)IIDWithPublicKeyData:(NSData *)publicKeyData { NSData *publicKeySHA1 = [self sha1WithData:publicKeyData]; const uint8_t *bytes = publicKeySHA1.bytes; NSMutableData *identityData = [NSMutableData dataWithData:publicKeySHA1]; uint8_t b0 = bytes[0]; // Take the first byte and make the initial four 7 by initially making the initial 4 bits 0 // and then adding 0x70 to it. b0 = 0x70 + (0xF & b0); // failsafe should give you back b0 itself b0 = (b0 & 0xFF); [identityData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&b0]; NSData *data = [identityData subdataWithRange:NSMakeRange(0, 8 * sizeof(Byte))]; return [self base64URLEncodedStringWithData:data]; } /** FirebaseInstallations SDK uses the SHA1 hash for backwards compatibility with the legacy * FirebaseInstanceID SDK. The SHA1 hash is used to access Instance IDs stored on the device and not * for any security-relevant process. This is a one-time step that allows migration of old client * identifiers. Cryptographic security is not needed here, so potential hash collisions are not a * problem. */ - (NSData *)sha1WithData:(NSData *)data { unsigned char output[CC_SHA1_DIGEST_LENGTH]; unsigned int length = (unsigned int)[data length]; CC_SHA1(data.bytes, length, output); return [NSData dataWithBytes:output length:CC_SHA1_DIGEST_LENGTH]; } - (NSString *)base64URLEncodedStringWithData:(NSData *)data { NSString *string = [data base64EncodedStringWithOptions:0]; string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; string = [string stringByReplacingOccurrencesOfString:@"=" withString:@""]; return string; } #pragma mark - Keychain - (NSData *)IIDPublicKeyData { NSString *tag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix]; NSDictionary *query = [self keyPairQueryWithTag:tag returnData:YES]; CFTypeRef keyRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&keyRef); if (status != noErr) { if (keyRef) { CFRelease(keyRef); } return nil; } return (__bridge NSData *)keyRef; } - (BOOL)deleteIID:(NSError **)outError { if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix error:outError]) { return NO; } if (![self deleteKeychainKeyWithTagPrefix:kFIRInstallationsIIDKeyPairPrivateTagPrefix error:outError]) { return NO; } return YES; } - (BOOL)deleteKeychainKeyWithTagPrefix:(NSString *)tagPrefix error:(NSError **)outError { NSString *keyTag = [self keychainKeyTagWithPrefix:kFIRInstallationsIIDKeyPairPublicTagPrefix]; NSDictionary *keyQuery = [self keyPairQueryWithTag:keyTag returnData:NO]; OSStatus status = SecItemDelete((__bridge CFDictionaryRef)keyQuery); // When item is not found, it should NOT be considered as an error. The operation should // continue. if (status != noErr && status != errSecItemNotFound) { FIRInstallationsItemSetErrorToPointer( [FIRInstallationsErrorUtil keychainErrorWithFunction:@"SecItemDelete" status:status], outError); return NO; } return YES; } - (NSDictionary *)keyPairQueryWithTag:(NSString *)tag returnData:(BOOL)shouldReturnData { NSMutableDictionary *query = [NSMutableDictionary dictionary]; NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding]; query[(__bridge id)kSecClass] = (__bridge id)kSecClassKey; query[(__bridge id)kSecAttrApplicationTag] = tagData; query[(__bridge id)kSecAttrKeyType] = (__bridge id)kSecAttrKeyTypeRSA; if (shouldReturnData) { query[(__bridge id)kSecReturnData] = @(YES); } #if TARGET_OS_OSX if (self.keychainRef) { query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ]; } #endif // TARGET_OSX return query; } - (NSString *)keychainKeyTagWithPrefix:(NSString *)prefix { NSString *mainAppBundleID = [[NSBundle mainBundle] bundleIdentifier]; if (mainAppBundleID.length == 0) { return nil; } return [NSString stringWithFormat:@"%@%@", prefix, mainAppBundleID]; } - (NSString *)mainbundleIdentifier { NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; if (!bundleIdentifier.length) { return nil; } return bundleIdentifier; } #pragma mark - Plist - (BOOL)deleteIIDFlagFromPlist:(NSError **)outError { NSString *path = [self plistPath]; if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { return YES; } NSMutableDictionary *plistContent = [[NSMutableDictionary alloc] initWithContentsOfFile:path]; plistContent[kFIRInstallationsIIDCreationTimePlistKey] = nil; if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { return [plistContent writeToURL:[NSURL fileURLWithPath:path] error:outError]; } return [plistContent writeToFile:path atomically:YES]; } - (BOOL)hasPlistIIDFlag { NSString *path = [self plistPath]; if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { return NO; } NSDictionary *plistContent = [[NSDictionary alloc] initWithContentsOfFile:path]; return plistContent[kFIRInstallationsIIDCreationTimePlistKey] != nil; } - (NSString *)plistPath { NSString *plistNameWithExtension = @"com.google.iid-keypair.plist"; NSString *_subDirectoryName = @"Google/FirebaseInstanceID"; NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); NSArray *components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ]; return [NSString pathWithComponents:components]; } - (NSSearchPathDirectory)supportedDirectory { #if TARGET_OS_TV return NSCachesDirectory; #else return NSApplicationSupportDirectory; #endif } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h ================================================ /* * 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 @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /** * The class reads a default IID token from IID store if available. */ @interface FIRInstallationsIIDTokenStore : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID; - (FBLPromise *)existingIIDDefaultToken; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" static NSString *const kFIRInstallationsIIDTokenKeychainId = @"com.google.iid-tokens"; @interface FIRInstallationsIIDTokenInfo : NSObject @property(nonatomic, nullable, copy) NSString *token; @end @implementation FIRInstallationsIIDTokenInfo + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(nonnull NSCoder *)coder { } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { self = [super init]; if (self) { _token = [coder decodeObjectOfClass:[NSString class] forKey:@"token"]; } return self; } @end @interface FIRInstallationsIIDTokenStore () @property(nonatomic, readonly) NSString *GCMSenderID; @end @implementation FIRInstallationsIIDTokenStore - (instancetype)initWithGCMSenderID:(NSString *)GCMSenderID { self = [super init]; if (self) { _GCMSenderID = GCMSenderID; } return self; } - (FBLPromise *)existingIIDDefaultToken { return [[FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) do:^id _Nullable { return [self IIDDefaultTokenData]; }] onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) then:^id _Nullable(NSData *_Nullable keychainData) { return [self IIDCheckinWithData:keychainData]; }]; } - (FBLPromise *)IIDCheckinWithData:(NSData *)data { FBLPromise *resultPromise = [FBLPromise pendingPromise]; NSError *archiverError; NSKeyedUnarchiver *unarchiver; if (@available(iOS 11.0, tvOS 11.0, macOS 10.13, *)) { unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&archiverError]; } else { @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; #pragma clang diagnostic pop } @catch (NSException *exception) { archiverError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception]; } } if (!unarchiver) { NSError *error = archiverError ?: [FIRInstallationsErrorUtil corruptedIIDTokenData]; [resultPromise reject:error]; return resultPromise; } [unarchiver setClass:[FIRInstallationsIIDTokenInfo class] forClassName:@"FIRInstanceIDTokenInfo"]; FIRInstallationsIIDTokenInfo *IIDTokenInfo = [unarchiver decodeObjectOfClass:[FIRInstallationsIIDTokenInfo class] forKey:NSKeyedArchiveRootObjectKey]; if (IIDTokenInfo.token.length < 1) { [resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDTokenData]]; return resultPromise; } [resultPromise fulfill:IIDTokenInfo.token]; return resultPromise; } - (FBLPromise *)IIDDefaultTokenData { FBLPromise *resultPromise = [FBLPromise pendingPromise]; NSMutableDictionary *keychainQuery = [self IIDDefaultTokenDataKeychainQuery]; NSError *error; NSData *data = [GULKeychainUtils getItemWithQuery:keychainQuery error:&error]; if (data) { [resultPromise fulfill:data]; return resultPromise; } else { NSError *outError = error ?: [FIRInstallationsErrorUtil corruptedIIDTokenData]; [resultPromise reject:outError]; return resultPromise; } } - (NSMutableDictionary *)IIDDefaultTokenDataKeychainQuery { NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword}; NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query]; finalQuery[(__bridge NSString *)kSecAttrGeneric] = kFIRInstallationsIIDTokenKeychainId; NSString *account = [self IIDAppIdentifier]; if ([account length]) { finalQuery[(__bridge NSString *)kSecAttrAccount] = account; } finalQuery[(__bridge NSString *)kSecAttrService] = [self serviceKeyForAuthorizedEntity:self.GCMSenderID scope:@"*"]; return finalQuery; } - (NSString *)IIDAppIdentifier { return [[NSBundle mainBundle] bundleIdentifier] ?: @""; } - (NSString *)serviceKeyForAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { return [NSString stringWithFormat:@"%@:%@", authorizedEntity, scope]; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h ================================================ /* * 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 @class FBLPromise; @class FIRInstallationsItem; NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const kFIRInstallationsUserAgentKey; FOUNDATION_EXPORT NSString *const kFIRInstallationsHeartbeatKey; /** * The class is responsible for interacting with HTTP REST API for Installations. */ @interface FIRInstallationsAPIService : NSObject /** * The default initializer. * @param APIKey The Firebase project API key (see `FIROptions.APIKey`). * @param projectID The Firebase project ID (see `FIROptions.projectID`). */ - (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID; /** * Sends a request to register a new FID to get auth and refresh tokens. * @param installation The `FIRInstallationsItem` instance with the FID to register. * @return A promise that is resolved with a new `FIRInstallationsItem` instance with valid tokens. * It is rejected with an error in case of a failure. */ - (FBLPromise *)registerInstallation:(FIRInstallationsItem *)installation; - (FBLPromise *)refreshAuthTokenForInstallation: (FIRInstallationsItem *)installation; /** * Sends a request to delete the installation, related auth tokens and all related data from the * server. * @param installation The installation to delete. * @return Returns a promise that is resolved with the passed installation on successful deletion or * is rejected with an error otherwise. */ - (FBLPromise *)deleteInstallation:(FIRInstallationsItem *)installation; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" #import "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h" NSString *const kFIRInstallationsAPIBaseURL = @"https://firebaseinstallations.googleapis.com"; NSString *const kFIRInstallationsAPIKey = @"X-Goog-Api-Key"; NSString *const kFIRInstallationsBundleId = @"X-Ios-Bundle-Identifier"; NSString *const kFIRInstallationsIIDMigrationAuthHeader = @"x-goog-fis-ios-iid-migration-auth"; NSString *const kFIRInstallationsHeartbeatKey = @"X-firebase-client-log-type"; NSString *const kFIRInstallationsHeartbeatTag = @"fire-installations"; NSString *const kFIRInstallationsUserAgentKey = @"X-firebase-client"; NS_ASSUME_NONNULL_BEGIN @interface FIRInstallationsURLSessionResponse : NSObject @property(nonatomic) NSHTTPURLResponse *HTTPResponse; @property(nonatomic) NSData *data; - (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data; @end @implementation FIRInstallationsURLSessionResponse - (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NSData *)data { self = [super init]; if (self) { _HTTPResponse = response; _data = data ?: [NSData data]; } return self; } @end @interface FIRInstallationsAPIService () @property(nonatomic, readonly) NSURLSession *URLSession; @property(nonatomic, readonly) NSString *APIKey; @property(nonatomic, readonly) NSString *projectID; @end NS_ASSUME_NONNULL_END @implementation FIRInstallationsAPIService - (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID { NSURLSession *URLSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; return [self initWithURLSession:URLSession APIKey:APIKey projectID:projectID]; } /// The initializer for tests. - (instancetype)initWithURLSession:(NSURLSession *)URLSession APIKey:(NSString *)APIKey projectID:(NSString *)projectID { self = [super init]; if (self) { _URLSession = URLSession; _APIKey = [APIKey copy]; _projectID = [projectID copy]; } return self; } #pragma mark - Public - (FBLPromise *)registerInstallation:(FIRInstallationsItem *)installation { return [self validateInstallation:installation] .then(^id _Nullable(FIRInstallationsItem *_Nullable validInstallation) { return [self registerRequestWithInstallation:validInstallation]; }) .then(^id _Nullable(NSURLRequest *_Nullable request) { return [self sendURLRequest:request]; }) .then(^id _Nullable(FIRInstallationsURLSessionResponse *response) { return [self registeredInstallationWithInstallation:installation serverResponse:response]; }); } - (FBLPromise *)refreshAuthTokenForInstallation: (FIRInstallationsItem *)installation { return [self authTokenRequestWithInstallation:installation] .then(^id _Nullable(NSURLRequest *_Nullable request) { return [self sendURLRequest:request]; }) .then(^FBLPromise *( FIRInstallationsURLSessionResponse *response) { return [self authTokenWithServerResponse:response]; }) .then(^FIRInstallationsItem *(FIRInstallationsStoredAuthToken *authToken) { FIRInstallationsItem *updatedInstallation = [installation copy]; updatedInstallation.authToken = authToken; return updatedInstallation; }); } - (FBLPromise *)deleteInstallation:(FIRInstallationsItem *)installation { return [self deleteInstallationRequestWithInstallation:installation] .then(^id _Nullable(NSURLRequest *_Nullable request) { return [self sendURLRequest:request]; }) .then(^id _Nullable(FIRInstallationsURLSessionResponse *_Nullable value) { // Return the original installation on success. return installation; }); } #pragma mark - Register Installation - (FBLPromise *)registerRequestWithInstallation: (FIRInstallationsItem *)installation { NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/", kFIRInstallationsAPIBaseURL, self.projectID]; NSURL *URL = [NSURL URLWithString:URLString]; NSDictionary *bodyDict = @{ // `firebaseInstallationID` is validated before but let's make sure it is not `nil` one more // time to prevent a crash. @"fid" : installation.firebaseInstallationID ?: @"", @"authVersion" : @"FIS_v2", @"appId" : installation.appID, @"sdkVersion" : [self SDKVersion] }; NSDictionary *headers; if (installation.IIDDefaultToken) { headers = @{kFIRInstallationsIIDMigrationAuthHeader : installation.IIDDefaultToken}; } return [self requestWithURL:URL HTTPMethod:@"POST" bodyDict:bodyDict refreshToken:nil additionalHeaders:headers]; } - (FBLPromise *) registeredInstallationWithInstallation:(FIRInstallationsItem *)installation serverResponse:(FIRInstallationsURLSessionResponse *)response { return [FBLPromise do:^id { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse, @"Parsing server response for %@.", response.HTTPResponse.URL); NSError *error; FIRInstallationsItem *registeredInstallation = [installation registeredInstallationWithJSONData:response.data date:[NSDate date] error:&error]; if (registeredInstallation == nil) { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIResponseParsingInstallationFailed, @"Failed to parse FIRInstallationsItem: %@.", error); return error; } FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIResponseParsingInstallationSucceed, @"FIRInstallationsItem parsed successfully."); return registeredInstallation; }]; } #pragma mark - Auth token - (FBLPromise *)authTokenRequestWithInstallation: (FIRInstallationsItem *)installation { NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/authTokens:generate", kFIRInstallationsAPIBaseURL, self.projectID, installation.firebaseInstallationID]; NSURL *URL = [NSURL URLWithString:URLString]; NSDictionary *bodyDict = @{@"installation" : @{@"sdkVersion" : [self SDKVersion]}}; return [self requestWithURL:URL HTTPMethod:@"POST" bodyDict:bodyDict refreshToken:installation.refreshToken]; } - (FBLPromise *)authTokenWithServerResponse: (FIRInstallationsURLSessionResponse *)response { return [FBLPromise do:^id { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeParsingAPIResponse, @"Parsing server response for %@.", response.HTTPResponse.URL); NSError *error; FIRInstallationsStoredAuthToken *token = [FIRInstallationsItem authTokenWithGenerateTokenAPIJSONData:response.data date:[NSDate date] error:&error]; if (token == nil) { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenFailed, @"Failed to parse FIRInstallationsStoredAuthToken: %@.", error); return error; } FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSucceed, @"FIRInstallationsStoredAuthToken parsed successfully."); return token; }]; } #pragma mark - Delete Installation - (FBLPromise *)deleteInstallationRequestWithInstallation: (FIRInstallationsItem *)installation { NSString *URLString = [NSString stringWithFormat:@"%@/v1/projects/%@/installations/%@/", kFIRInstallationsAPIBaseURL, self.projectID, installation.firebaseInstallationID]; NSURL *URL = [NSURL URLWithString:URLString]; return [self requestWithURL:URL HTTPMethod:@"DELETE" bodyDict:@{} refreshToken:installation.refreshToken]; } #pragma mark - URL Request - (FBLPromise *)requestWithURL:(NSURL *)requestURL HTTPMethod:(NSString *)HTTPMethod bodyDict:(NSDictionary *)bodyDict refreshToken:(nullable NSString *)refreshToken { return [self requestWithURL:requestURL HTTPMethod:HTTPMethod bodyDict:bodyDict refreshToken:refreshToken additionalHeaders:nil]; } - (FBLPromise *)requestWithURL:(NSURL *)requestURL HTTPMethod:(NSString *)HTTPMethod bodyDict:(NSDictionary *)bodyDict refreshToken:(nullable NSString *)refreshToken additionalHeaders:(nullable NSDictionary *) additionalHeaders { return [FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0) do:^id _Nullable { __block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL]; request.HTTPMethod = HTTPMethod; NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; [request addValue:self.APIKey forHTTPHeaderField:kFIRInstallationsAPIKey]; [request addValue:bundleIdentifier forHTTPHeaderField:kFIRInstallationsBundleId]; [self setJSONHTTPBody:bodyDict forRequest:request]; if (refreshToken) { NSString *authHeader = [NSString stringWithFormat:@"FIS_v2 %@", refreshToken]; [request setValue:authHeader forHTTPHeaderField:@"Authorization"]; } // User agent Header. [request setValue:[FIRApp firebaseUserAgent] forHTTPHeaderField:kFIRInstallationsUserAgentKey]; // Heartbeat Header. [request setValue:@([FIRHeartbeatInfo heartbeatCodeForTag:kFIRInstallationsHeartbeatTag]) .stringValue forHTTPHeaderField:kFIRInstallationsHeartbeatKey]; [additionalHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { [request setValue:obj forHTTPHeaderField:key]; }]; return [request copy]; }]; } - (FBLPromise *)URLRequestPromise:(NSURLRequest *)request { return [[FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeSendAPIRequest, @"Sending request: %@, body:%@, headers: %@.", request, [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding], request.allHTTPHeaderFields); [[self.URLSession dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { if (error) { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIRequestNetworkError, @"Request failed: %@, error: %@.", request, error); reject(error); } else { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAPIRequestResponse, @"Request response received: %@, error: %@, body: %@.", request, error, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); fulfill([[FIRInstallationsURLSessionResponse alloc] initWithResponse:(NSHTTPURLResponse *)response data:data]); } }] resume]; }] then:^id _Nullable(FIRInstallationsURLSessionResponse *response) { return [self validateHTTPResponseStatusCode:response]; }]; } - (FBLPromise *)validateHTTPResponseStatusCode: (FIRInstallationsURLSessionResponse *)response { NSInteger statusCode = response.HTTPResponse.statusCode; return [FBLPromise do:^id _Nullable { if (statusCode < 200 || statusCode >= 300) { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeUnexpectedAPIRequestResponse, @"Unexpected API response: %@, body: %@.", response.HTTPResponse, [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]); return [FIRInstallationsErrorUtil APIErrorWithHTTPResponse:response.HTTPResponse data:response.data]; } return response; }]; } - (FBLPromise *)sendURLRequest:(NSURLRequest *)request { return [FBLPromise attempts:1 delay:1 condition:^BOOL(NSInteger remainingAttempts, NSError *_Nonnull error) { return [FIRInstallationsErrorUtil isAPIError:error withHTTPCode:FIRInstallationsHTTPCodesServerInternalError]; } retry:^id _Nullable { return [self URLRequestPromise:request]; }]; } - (NSString *)SDKVersion { return [NSString stringWithFormat:@"i:%@", FIRFirebaseVersion()]; } #pragma mark - Validation - (FBLPromise *)validateInstallation:(FIRInstallationsItem *)installation { FBLPromise *result = [FBLPromise pendingPromise]; NSError *validationError; if ([installation isValid:&validationError]) { [result fulfill:installation]; } else { [result reject:validationError]; } return result; } #pragma mark - JSON - (void)setJSONHTTPBody:(NSDictionary *)body forRequest:(NSMutableURLRequest *)request { [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; NSError *error; NSData *JSONData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&error]; if (JSONData == nil) { // TODO: Log or return an error. } request.HTTPBody = JSONData; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/FIRInstallationsItem.h" @class FIRInstallationsStoredAuthToken; NS_ASSUME_NONNULL_BEGIN @interface FIRInstallationsItem (RegisterInstallationAPI) /** * Parses and validates the Register Installation API response and returns a corresponding * `FIRInstallationsItem` instance on success. * @param JSONData The data with JSON encoded API response. * @param date The installation auth token expiration date will be calculated as `date` + * `response.authToken.expiresIn`. For most of the cases `[NSDate date]` should be passed there. A * different value may be passed e.g. for unit tests. * @param outError A pointer to assign a specific `NSError` instance in case of failure. No error is * assigned in case of success. * @return Returns a new `FIRInstallationsItem` instance in the success case or `nil` otherwise. */ - (nullable FIRInstallationsItem *)registeredInstallationWithJSONData:(NSData *)JSONData date:(NSDate *)date error: (NSError *_Nullable *)outError; + (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data date:(NSDate *)date error:(NSError **) outError; + (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict: (NSDictionary *)dict date:(NSDate *)date error:(NSError **)outError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" @implementation FIRInstallationsItem (RegisterInstallationAPI) - (nullable FIRInstallationsItem *) registeredInstallationWithJSONData:(NSData *)data date:(NSDate *)date error:(NSError *__autoreleasing _Nullable *_Nullable)outError { NSDictionary *responseJSON = [FIRInstallationsItem dictionaryFromJSONData:data error:outError]; if (!responseJSON) { return nil; } NSString *refreshToken = [FIRInstallationsItem validStringOrNilForKey:@"refreshToken" fromDict:responseJSON]; if (refreshToken == nil) { FIRInstallationsItemSetErrorToPointer( [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"refreshToken"], outError); return nil; } NSDictionary *authTokenDict = responseJSON[@"authToken"]; if (![authTokenDict isKindOfClass:[NSDictionary class]]) { FIRInstallationsItemSetErrorToPointer( [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken"], outError); return nil; } FIRInstallationsStoredAuthToken *authToken = [FIRInstallationsItem authTokenWithJSONDict:authTokenDict date:date error:outError]; if (authToken == nil) { return nil; } FIRInstallationsItem *installation = [[FIRInstallationsItem alloc] initWithAppID:self.appID firebaseAppName:self.firebaseAppName]; NSString *installationID = [FIRInstallationsItem validStringOrNilForKey:@"fid" fromDict:responseJSON]; installation.firebaseInstallationID = installationID ?: self.firebaseInstallationID; installation.refreshToken = refreshToken; installation.authToken = authToken; installation.registrationStatus = FIRInstallationStatusRegistered; return installation; } #pragma mark - Auth token + (nullable FIRInstallationsStoredAuthToken *)authTokenWithGenerateTokenAPIJSONData:(NSData *)data date:(NSDate *)date error:(NSError **) outError { NSDictionary *dict = [self dictionaryFromJSONData:data error:outError]; if (!dict) { return nil; } return [self authTokenWithJSONDict:dict date:date error:outError]; } + (nullable FIRInstallationsStoredAuthToken *)authTokenWithJSONDict: (NSDictionary *)dict date:(NSDate *)date error:(NSError **)outError { NSString *token = [self validStringOrNilForKey:@"token" fromDict:dict]; if (token == nil) { FIRInstallationsItemSetErrorToPointer( [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken.token"], outError); return nil; } NSString *expiresInString = [self validStringOrNilForKey:@"expiresIn" fromDict:dict]; if (expiresInString == nil) { FIRInstallationsItemSetErrorToPointer( [FIRInstallationsErrorUtil FIDRegistrationErrorWithResponseMissingField:@"authToken.expiresIn"], outError); return nil; } // The response should contain the string in format like "604800s". // The server should never response with anything else except seconds. // Just drop the last character and parse a number from string. NSString *expiresInSeconds = [expiresInString substringToIndex:expiresInString.length - 1]; NSTimeInterval expiresIn = [expiresInSeconds doubleValue]; NSDate *expirationDate = [date dateByAddingTimeInterval:expiresIn]; FIRInstallationsStoredAuthToken *authToken = [[FIRInstallationsStoredAuthToken alloc] init]; authToken.status = FIRInstallationsAuthTokenStatusTokenReceived; authToken.token = token; authToken.expirationDate = expirationDate; return authToken; } #pragma mark - JSON + (nullable NSDictionary *)dictionaryFromJSONData:(NSData *)data error:(NSError **)outError { NSError *error; NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (![responseJSON isKindOfClass:[NSDictionary class]]) { FIRInstallationsItemSetErrorToPointer([FIRInstallationsErrorUtil JSONSerializationError:error], outError); return nil; } return responseJSON; } + (NSString *)validStringOrNilForKey:(NSString *)key fromDict:(NSDictionary *)dict { NSString *string = dict[key]; if ([string isKindOfClass:[NSString class]] && string.length > 0) { return string; } return nil; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h ================================================ /* * 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 /** A block returning current date. */ typedef NSDate *_Nonnull (^FIRCurrentDateProvider)(void); /** The function returns a `FIRCurrentDateProvider` block that returns a real current date. */ FIRCurrentDateProvider FIRRealCurrentDateProvider(void); NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h" FIRCurrentDateProvider FIRRealCurrentDateProvider(void) { return ^NSDate *(void) { return [NSDate date]; }; } ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h" NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, FIRInstallationsBackoffEvent) { FIRInstallationsBackoffEventSuccess, FIRInstallationsBackoffEventRecoverableFailure, FIRInstallationsBackoffEventUnrecoverableFailure }; /** The protocol defines API for a class that encapsulates backoff logic that prevents the SDK from * sending unnecessary server requests. See API docs for the methods for more details. */ @protocol FIRInstallationsBackoffControllerProtocol /** The client must call the method each time a protected server request succeeds of fails. It will * affect the `isNextRequestAllowed` method result for the current time, e.g. when 3 recoverable * errors were logged in a row, then `isNextRequestAllowed` will return `YES` only in `pow(2, 3)` * seconds. */ - (void)registerEvent:(FIRInstallationsBackoffEvent)event; /** Returns if sending a next protected is recommended based on the time and the sequence of logged * events and the current time. See also `registerEvent:`. */ - (BOOL)isNextRequestAllowed; @end /** An implementation of `FIRInstallationsBackoffControllerProtocol` with exponential backoff for * recoverable errors and constant backoff for recoverable errors. */ @interface FIRInstallationsBackoffController : NSObject - (instancetype)initWithCurrentDateProvider:(FIRCurrentDateProvider)currentDateProvider; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h" static const NSTimeInterval k24Hours = 24 * 60 * 60; static const NSTimeInterval k30Minutes = 30 * 60; /** The class represents `FIRInstallationsBackoffController` sate required to calculate next allowed request time. The properties of the class are intentionally immutable because changing them separately leads to an inconsistent state. */ @interface FIRInstallationsBackoffEventData : NSObject @property(nonatomic, readonly) FIRInstallationsBackoffEvent eventType; @property(nonatomic, readonly) NSDate *lastEventDate; @property(nonatomic, readonly) NSInteger eventCount; @property(nonatomic, readonly) NSTimeInterval backoffTimeInterval; @end @implementation FIRInstallationsBackoffEventData - (instancetype)initWithEvent:(FIRInstallationsBackoffEvent)eventType lastEventDate:(NSDate *)lastEventDate eventCount:(NSInteger)eventCount { self = [super init]; if (self) { _eventType = eventType; _lastEventDate = lastEventDate; _eventCount = eventCount; _backoffTimeInterval = [[self class] backoffTimeIntervalWithEvent:eventType eventCount:eventCount]; } return self; } + (NSTimeInterval)backoffTimeIntervalWithEvent:(FIRInstallationsBackoffEvent)eventType eventCount:(NSInteger)eventCount { switch (eventType) { case FIRInstallationsBackoffEventSuccess: return 0; break; case FIRInstallationsBackoffEventRecoverableFailure: return [self recoverableErrorBackoffTimeForAttemptNumber:eventCount]; break; case FIRInstallationsBackoffEventUnrecoverableFailure: return k24Hours; break; } } + (NSTimeInterval)recoverableErrorBackoffTimeForAttemptNumber:(NSInteger)attemptNumber { NSTimeInterval exponentialInterval = pow(2, attemptNumber) + [self randomMilliseconds]; return MIN(exponentialInterval, k30Minutes); } + (NSTimeInterval)randomMilliseconds { int32_t random_millis = ABS(arc4random() % 1000); return (double)random_millis * 0.001; } @end @interface FIRInstallationsBackoffController () @property(nonatomic, readonly) FIRCurrentDateProvider currentDateProvider; @property(nonatomic, nullable) FIRInstallationsBackoffEventData *lastEventData; @end @implementation FIRInstallationsBackoffController - (instancetype)init { return [self initWithCurrentDateProvider:FIRRealCurrentDateProvider()]; } - (instancetype)initWithCurrentDateProvider:(FIRCurrentDateProvider)currentDateProvider { self = [super init]; if (self) { _currentDateProvider = [currentDateProvider copy]; } return self; } - (BOOL)isNextRequestAllowed { @synchronized(self) { if (self.lastEventData == nil) { return YES; } NSTimeInterval timeSinceLastEvent = [self.currentDateProvider() timeIntervalSinceDate:self.lastEventData.lastEventDate]; return timeSinceLastEvent >= self.lastEventData.backoffTimeInterval; } } - (void)registerEvent:(FIRInstallationsBackoffEvent)event { @synchronized(self) { // Event of the same type as was registered before. if (self.lastEventData && self.lastEventData.eventType == event) { self.lastEventData = [[FIRInstallationsBackoffEventData alloc] initWithEvent:event lastEventDate:self.currentDateProvider() eventCount:self.lastEventData.eventCount + 1]; } else { // A different event. self.lastEventData = [[FIRInstallationsBackoffEventData alloc] initWithEvent:event lastEventDate:self.currentDateProvider() eventCount:1]; } } } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h ================================================ /* * 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 @class FBLPromise; @class FIRInstallationsItem; /** * The class is responsible for managing FID for a given `FIRApp`. */ @interface FIRInstallationsIDController : NSObject - (instancetype)initWithGoogleAppID:(NSString *)appID appName:(NSString *)appName APIKey:(NSString *)APIKey projectID:(NSString *)projectID GCMSenderID:(NSString *)GCMSenderID accessGroup:(nullable NSString *)accessGroup; - (FBLPromise *)getInstallationItem; - (FBLPromise *)getAuthTokenForcingRefresh:(BOOL)forceRefresh; - (FBLPromise *)deleteInstallation; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsItem.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" #import "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h" #import "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h" #import "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h" #import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h" #import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h" #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" const NSNotificationName FIRInstallationIDDidChangeNotification = @"FIRInstallationIDDidChangeNotification"; NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey = @"FIRInstallationIDDidChangeNotification"; NSTimeInterval const kFIRInstallationsTokenExpirationThreshold = 60 * 60; // 1 hour. static NSString *const kKeychainService = @"com.firebase.FIRInstallations.installations"; @interface FIRInstallationsIDController () @property(nonatomic, readonly) NSString *appID; @property(nonatomic, readonly) NSString *appName; @property(nonatomic, readonly) FIRInstallationsStore *installationsStore; @property(nonatomic, readonly) FIRInstallationsIIDStore *IIDStore; @property(nonatomic, readonly) FIRInstallationsIIDTokenStore *IIDTokenStore; @property(nonatomic, readonly) FIRInstallationsAPIService *APIService; @property(nonatomic, readonly) id backoffController; @property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache *getInstallationPromiseCache; @property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache *authTokenPromiseCache; @property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache *authTokenForcingRefreshPromiseCache; @property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache *deleteInstallationPromiseCache; @end @implementation FIRInstallationsIDController - (instancetype)initWithGoogleAppID:(NSString *)appID appName:(NSString *)appName APIKey:(NSString *)APIKey projectID:(NSString *)projectID GCMSenderID:(NSString *)GCMSenderID accessGroup:(nullable NSString *)accessGroup { NSString *serviceName = [FIRInstallationsIDController keychainServiceWithAppID:appID]; GULKeychainStorage *secureStorage = [[GULKeychainStorage alloc] initWithService:serviceName]; FIRInstallationsStore *installationsStore = [[FIRInstallationsStore alloc] initWithSecureStorage:secureStorage accessGroup:accessGroup]; FIRInstallationsAPIService *apiService = [[FIRInstallationsAPIService alloc] initWithAPIKey:APIKey projectID:projectID]; FIRInstallationsIIDStore *IIDStore = [[FIRInstallationsIIDStore alloc] init]; FIRInstallationsIIDTokenStore *IIDCheckingStore = [[FIRInstallationsIIDTokenStore alloc] initWithGCMSenderID:GCMSenderID]; FIRInstallationsBackoffController *backoffController = [[FIRInstallationsBackoffController alloc] init]; return [self initWithGoogleAppID:appID appName:appName installationsStore:installationsStore APIService:apiService IIDStore:IIDStore IIDTokenStore:IIDCheckingStore backoffController:backoffController]; } /// The initializer is supposed to be used by tests to inject `installationsStore`. - (instancetype)initWithGoogleAppID:(NSString *)appID appName:(NSString *)appName installationsStore:(FIRInstallationsStore *)installationsStore APIService:(FIRInstallationsAPIService *)APIService IIDStore:(FIRInstallationsIIDStore *)IIDStore IIDTokenStore:(FIRInstallationsIIDTokenStore *)IIDTokenStore backoffController: (id)backoffController { self = [super init]; if (self) { _appID = appID; _appName = appName; _installationsStore = installationsStore; _APIService = APIService; _IIDStore = IIDStore; _IIDTokenStore = IIDTokenStore; _backoffController = backoffController; __weak FIRInstallationsIDController *weakSelf = self; _getInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] initWithNewOperationHandler:^FBLPromise *_Nonnull { FIRInstallationsIDController *strongSelf = weakSelf; return [strongSelf createGetInstallationItemPromise]; }]; _authTokenPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] initWithNewOperationHandler:^FBLPromise *_Nonnull { FIRInstallationsIDController *strongSelf = weakSelf; return [strongSelf installationWithValidAuthTokenForcingRefresh:NO]; }]; _authTokenForcingRefreshPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] initWithNewOperationHandler:^FBLPromise *_Nonnull { FIRInstallationsIDController *strongSelf = weakSelf; return [strongSelf installationWithValidAuthTokenForcingRefresh:YES]; }]; _deleteInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc] initWithNewOperationHandler:^FBLPromise *_Nonnull { FIRInstallationsIDController *strongSelf = weakSelf; return [strongSelf createDeleteInstallationPromise]; }]; } return self; } #pragma mark - Get Installation. - (FBLPromise *)getInstallationItem { return [self.getInstallationPromiseCache getExistingPendingOrCreateNewPromise]; } - (FBLPromise *)createGetInstallationItemPromise { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewGetInstallationOperationCreated, @"%s, appName: %@", __PRETTY_FUNCTION__, self.appName); FBLPromise *installationItemPromise = [self getStoredInstallation].recover(^id(NSError *error) { return [self createAndSaveFID]; }); // Initiate registration process on success if needed, but return the installation without waiting // for it. installationItemPromise.then(^id(FIRInstallationsItem *installation) { [self getAuthTokenForcingRefresh:NO]; return nil; }); return installationItemPromise; } - (FBLPromise *)getStoredInstallation { return [self.installationsStore installationForAppID:self.appID appName:self.appName].validate( ^BOOL(FIRInstallationsItem *installation) { NSError *validationError; BOOL isValid = [installation isValid:&validationError]; if (!isValid) { FIRLogWarning( kFIRLoggerInstallations, kFIRInstallationsMessageCodeCorruptedStoredInstallation, @"Stored installation validation error: %@", validationError.localizedDescription); } return isValid; }); } - (FBLPromise *)createAndSaveFID { return [self migrateOrGenerateInstallation] .then(^FBLPromise *(FIRInstallationsItem *installation) { return [self saveInstallation:installation]; }) .then(^FIRInstallationsItem *(FIRInstallationsItem *installation) { [self postFIDDidChangeNotification]; return installation; }); } - (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installation { return [self.installationsStore saveInstallation:installation].then( ^FIRInstallationsItem *(NSNull *result) { return installation; }); } /** * Tries to migrate IID data stored by FirebaseInstanceID SDK or generates a new Installation ID if * not found. */ - (FBLPromise *)migrateOrGenerateInstallation { if (![self isDefaultApp]) { // Existing IID should be used only for default FirebaseApp. FIRInstallationsItem *installation = [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil]; return [FBLPromise resolvedWith:installation]; } return [[[FBLPromise all:@[ [self.IIDStore existingIID], [self.IIDTokenStore existingIIDDefaultToken] ]] then:^id _Nullable(NSArray *_Nullable results) { NSString *existingIID = results[0]; NSString *IIDDefaultToken = results[1]; return [self createInstallationWithFID:existingIID IIDDefaultToken:IIDDefaultToken]; }] recover:^id _Nullable(NSError *_Nonnull error) { return [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil]; }]; } - (FIRInstallationsItem *)createInstallationWithFID:(NSString *)FID IIDDefaultToken:(nullable NSString *)IIDDefaultToken { FIRInstallationsItem *installation = [[FIRInstallationsItem alloc] initWithAppID:self.appID firebaseAppName:self.appName]; installation.firebaseInstallationID = FID; installation.IIDDefaultToken = IIDDefaultToken; installation.registrationStatus = FIRInstallationStatusUnregistered; return installation; } #pragma mark - FID registration - (FBLPromise *)registerInstallationIfNeeded: (FIRInstallationsItem *)installation { switch (installation.registrationStatus) { case FIRInstallationStatusRegistered: // Already registered. Do nothing. return [FBLPromise resolvedWith:installation]; case FIRInstallationStatusUnknown: case FIRInstallationStatusUnregistered: // Registration required. Proceed. break; } // Check for backoff. if (![self.backoffController isNextRequestAllowed]) { return [FIRInstallationsErrorUtil rejectedPromiseWithError:[FIRInstallationsErrorUtil backoffIntervalWaitError]]; } return [self.APIService registerInstallation:installation] .catch(^(NSError *_Nonnull error) { [self updateBackoffWithSuccess:NO APIError:error]; if ([self doesRegistrationErrorRequireConfigChange:error]) { FIRLogError(kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseConfiguration, @"Firebase Installation registration failed for app with name: %@, error:\n" @"%@\nPlease make sure you use valid GoogleService-Info.plist", self.appName, error.userInfo[NSLocalizedFailureReasonErrorKey]); } }) .then(^id(FIRInstallationsItem *registeredInstallation) { [self updateBackoffWithSuccess:YES APIError:nil]; return [self saveInstallation:registeredInstallation]; }) .then(^FIRInstallationsItem *(FIRInstallationsItem *registeredInstallation) { // Server may respond with a different FID if the sent one cannot be accepted. if (![registeredInstallation.firebaseInstallationID isEqualToString:installation.firebaseInstallationID]) { [self postFIDDidChangeNotification]; } return registeredInstallation; }); } - (BOOL)doesRegistrationErrorRequireConfigChange:(NSError *)error { FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error; if (![HTTPError isKindOfClass:[FIRInstallationsHTTPError class]]) { return NO; } switch (HTTPError.HTTPResponse.statusCode) { // These are the errors that require Firebase configuration change. case FIRInstallationsRegistrationHTTPCodeInvalidArgument: case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch: case FIRInstallationsRegistrationHTTPCodeProjectNotFound: return YES; default: return NO; } } #pragma mark - Auth Token - (FBLPromise *)getAuthTokenForcingRefresh:(BOOL)forceRefresh { if (forceRefresh || [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] != nil) { return [self.authTokenForcingRefreshPromiseCache getExistingPendingOrCreateNewPromise]; } else { return [self.authTokenPromiseCache getExistingPendingOrCreateNewPromise]; } } - (FBLPromise *)installationWithValidAuthTokenForcingRefresh: (BOOL)forceRefresh { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated, @"-[FIRInstallationsIDController installationWithValidAuthTokenForcingRefresh:%@], " @"appName: %@", @(forceRefresh), self.appName); return [self getInstallationItem] .then(^FBLPromise *(FIRInstallationsItem *installation) { return [self registerInstallationIfNeeded:installation]; }) .then(^id(FIRInstallationsItem *registeredInstallation) { BOOL isTokenExpiredOrExpiresSoon = [registeredInstallation.authToken.expirationDate timeIntervalSinceDate:[NSDate date]] < kFIRInstallationsTokenExpirationThreshold; if (forceRefresh || isTokenExpiredOrExpiresSoon) { return [self refreshAuthTokenForInstallation:registeredInstallation]; } else { return registeredInstallation; } }) .recover(^id(NSError *error) { return [self regenerateFIDOnRefreshTokenErrorIfNeeded:error]; }); } - (FBLPromise *)refreshAuthTokenForInstallation: (FIRInstallationsItem *)installation { // Check for backoff. if (![self.backoffController isNextRequestAllowed]) { return [FIRInstallationsErrorUtil rejectedPromiseWithError:[FIRInstallationsErrorUtil backoffIntervalWaitError]]; } return [[[self.APIService refreshAuthTokenForInstallation:installation] then:^id _Nullable(FIRInstallationsItem *_Nullable refreshedInstallation) { [self updateBackoffWithSuccess:YES APIError:nil]; return [self saveInstallation:refreshedInstallation]; }] recover:^id _Nullable(NSError *_Nonnull error) { // Pass the error to the backoff controller. [self updateBackoffWithSuccess:NO APIError:error]; return error; }]; } - (id)regenerateFIDOnRefreshTokenErrorIfNeeded:(NSError *)error { if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) { // No recovery possible. Return the same error. return error; } FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error; switch (HTTPError.HTTPResponse.statusCode) { case FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication: case FIRInstallationsAuthTokenHTTPCodeFIDNotFound: // The stored installation was damaged or blocked by the server. // Delete the stored installation then generate and register a new one. return [self getInstallationItem] .then(^FBLPromise *(FIRInstallationsItem *installation) { return [self deleteInstallationLocally:installation]; }) .then(^FBLPromise *(id result) { return [self installationWithValidAuthTokenForcingRefresh:NO]; }); default: // No recovery possible. Return the same error. return error; } } #pragma mark - Delete FID - (FBLPromise *)deleteInstallation { return [self.deleteInstallationPromiseCache getExistingPendingOrCreateNewPromise]; } - (FBLPromise *)createDeleteInstallationPromise { FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated, @"%s, appName: %@", __PRETTY_FUNCTION__, self.appName); // Check for ongoing requests first, if there is no a request, then check local storage for // existing installation. FBLPromise *currentInstallationPromise = [self mostRecentInstallationOperation] ?: [self getStoredInstallation]; return currentInstallationPromise .then(^id(FIRInstallationsItem *installation) { return [self sendDeleteInstallationRequestIfNeeded:installation]; }) .then(^id(FIRInstallationsItem *installation) { // Remove the installation from the local storage. return [self deleteInstallationLocally:installation]; }); } - (FBLPromise *)deleteInstallationLocally:(FIRInstallationsItem *)installation { return [self.installationsStore removeInstallationForAppID:installation.appID appName:installation.firebaseAppName] .then(^FBLPromise *(NSNull *result) { return [self deleteExistingIIDIfNeeded]; }) .then(^NSNull *(NSNull *result) { [self postFIDDidChangeNotification]; return result; }); } - (FBLPromise *)sendDeleteInstallationRequestIfNeeded: (FIRInstallationsItem *)installation { switch (installation.registrationStatus) { case FIRInstallationStatusUnknown: case FIRInstallationStatusUnregistered: // The installation is not registered, so it is safe to be deleted as is, so return early. return [FBLPromise resolvedWith:installation]; break; case FIRInstallationStatusRegistered: // Proceed to de-register the installation on the server. break; } return [self.APIService deleteInstallation:installation].recover(^id(NSError *APIError) { if ([FIRInstallationsErrorUtil isAPIError:APIError withHTTPCode:404]) { // The installation was not found on the server. // Return success. return installation; } else { // Re-throw the error otherwise. return APIError; } }); } - (FBLPromise *)deleteExistingIIDIfNeeded { if ([self isDefaultApp]) { return [self.IIDStore deleteExistingIID]; } else { return [FBLPromise resolvedWith:[NSNull null]]; } } - (nullable FBLPromise *)mostRecentInstallationOperation { return [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] ?: [self.authTokenPromiseCache getExistingPendingPromise] ?: [self.getInstallationPromiseCache getExistingPendingPromise]; } #pragma mark - Backoff - (void)updateBackoffWithSuccess:(BOOL)success APIError:(nullable NSError *)APIError { if (success) { [self.backoffController registerEvent:FIRInstallationsBackoffEventSuccess]; } else if ([APIError isKindOfClass:[FIRInstallationsHTTPError class]]) { FIRInstallationsHTTPError *HTTPResponseError = (FIRInstallationsHTTPError *)APIError; NSInteger statusCode = HTTPResponseError.HTTPResponse.statusCode; if (statusCode == FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication || statusCode == FIRInstallationsAuthTokenHTTPCodeFIDNotFound) { // These errors are explicitly excluded because they are handled by FIS SDK itself so don't // require backoff. } else if (statusCode == 400 || statusCode == 403) { // Explicitly unrecoverable errors. [self.backoffController registerEvent:FIRInstallationsBackoffEventUnrecoverableFailure]; } else if (statusCode == 429 || (statusCode >= 500 && statusCode < 600)) { // Explicitly recoverable errors. [self.backoffController registerEvent:FIRInstallationsBackoffEventRecoverableFailure]; } else { // Treat all unknown errors as recoverable. [self.backoffController registerEvent:FIRInstallationsBackoffEventRecoverableFailure]; } } // If the error class is not `FIRInstallationsHTTPError` it indicates a connection error. Such // errors should not change backoff interval. } #pragma mark - Notifications - (void)postFIDDidChangeNotification { [[NSNotificationCenter defaultCenter] postNotificationName:FIRInstallationIDDidChangeNotification object:nil userInfo:@{kFIRInstallationIDDidChangeNotificationAppNameKey : self.appName}]; } #pragma mark - Default App - (BOOL)isDefaultApp { return [self.appName isEqualToString:kFIRDefaultAppName]; } #pragma mark - Keychain + (NSString *)keychainServiceWithAppID:(NSString *)appID { #if TARGET_OS_MACCATALYST || TARGET_OS_OSX // We need to keep service name unique per application on macOS. // Applications on macOS may request access to Keychain items stored by other applications. It // means that when the app looks up for a relevant Keychain item in the service scope it will // request user password to grant access to the Keychain if there are other Keychain items from // other applications stored under the same Keychain Service. return [kKeychainService stringByAppendingFormat:@".%@", appID]; #else // Use a constant Keychain service for non-macOS because: // 1. Keychain items cannot be shared between apps until configured specifically so the service // name collisions are not a concern // 2. We don't want to change the service name to avoid doing a migration. return kKeychainService; #endif } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h ================================================ /* * 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 @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /** * The class makes sure the a single operation (represented by a promise) is performed at a time. If * there is an ongoing operation, then its existing corresponding promise will be returned instead * of starting a new operation. */ @interface FIRInstallationsSingleOperationPromiseCache<__covariant ResultType> : NSObject - (instancetype)init NS_UNAVAILABLE; /** * The designated initializer. * @param newOperationHandler The block that must return a new promise representing the * single-at-a-time operation. The promise should be fulfilled when the operation is completed. The * factory block will be used to create a new promise when needed. */ - (instancetype)initWithNewOperationHandler: (FBLPromise *_Nonnull (^)(void))newOperationHandler NS_DESIGNATED_INITIALIZER; /** * Creates a new promise or returns an existing pending one. * @return Returns and existing pending promise if exists. If the pending promise does not exist * then a new one will be created using the `factory` block passed in the initializer. Once the * pending promise gets resolved, it is removed, so calling the method again will lead to creating * and caching another promise. */ - (FBLPromise *)getExistingPendingOrCreateNewPromise; /** * Returns an existing pending promise or `nil`. * @return Returns an existing pending promise if there is one or `nil` otherwise. */ - (nullable FBLPromise *)getExistingPendingPromise; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h" #if __has_include() #import #else #import "FBLPromises.h" #endif @interface FIRInstallationsSingleOperationPromiseCache () @property(nonatomic, readonly) FBLPromise *_Nonnull (^newOperationHandler)(void); @property(nonatomic, nullable) FBLPromise *pendingPromise; @end @implementation FIRInstallationsSingleOperationPromiseCache - (instancetype)initWithNewOperationHandler: (FBLPromise *_Nonnull (^)(void))newOperationHandler { if (newOperationHandler == nil) { [NSException raise:NSInvalidArgumentException format:@"`newOperationHandler` must not be `nil`."]; } self = [super init]; if (self) { _newOperationHandler = [newOperationHandler copy]; } return self; } - (FBLPromise *)getExistingPendingOrCreateNewPromise { @synchronized(self) { if (!self.pendingPromise) { self.pendingPromise = self.newOperationHandler(); self.pendingPromise .then(^id(id result) { @synchronized(self) { self.pendingPromise = nil; return nil; } }) .catch(^void(NSError *error) { @synchronized(self) { self.pendingPromise = nil; } }); } return self.pendingPromise; } } - (nullable FBLPromise *)getExistingPendingPromise { @synchronized(self) { return self.pendingPromise; } } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h ================================================ /* * 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 /** * The enum represent possible states of the installation ID. * * WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredItem`. Modification * of it can lead to incompatibility with previous version. Any modification must be evaluated and, * if it is really needed, the `storageVersion` must be bumped and proper migration code added. */ typedef NS_ENUM(NSInteger, FIRInstallationsStatus) { /** Represents either an initial status when a FIRInstallationsItem instance was created but not * stored to Keychain or an undefined status (e.g. when the status failed to deserialize). */ FIRInstallationStatusUnknown, /// The Firebase Installation has not yet been registered with FIS. FIRInstallationStatusUnregistered, /// The Firebase Installation has successfully been registered with FIS. FIRInstallationStatusRegistered, }; ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h ================================================ /* * 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 @class FBLPromise; @class FIRInstallationsItem; @class GULKeychainStorage; NS_ASSUME_NONNULL_BEGIN /// The user defaults suite name used to store data. extern NSString *const kFIRInstallationsStoreUserDefaultsID; /// The class is responsible for storing and accessing the installations data. @interface FIRInstallationsStore : NSObject /** * The default initializer. * @param storage The secure storage to save installations data. * @param accessGroup The Keychain Access Group to store and request the installations data. */ - (instancetype)initWithSecureStorage:(GULKeychainStorage *)storage accessGroup:(nullable NSString *)accessGroup; /** * Retrieves existing installation ID if there is. * @param appID The Firebase(Google) Application ID. * @param appName The Firebase Application Name. * * @return Returns a `FBLPromise` instance. The promise is resolved with a FIRInstallationsItem * instance if there is a valid installation stored for `appID` and `appName`. The promise is * rejected with a specific error when the installation has not been found or with another possible * error. */ - (FBLPromise *)installationForAppID:(NSString *)appID appName:(NSString *)appName; /** * Saves the given installation. * * @param installationItem The installation data. * @return Returns a promise that is resolved with `[NSNull null]` on success. */ - (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installationItem; /** * Removes installation data for the given app parameters. * @param appID The Firebase(Google) Application ID. * @param appName The Firebase Application Name. * * @return Returns a promise that is resolved with `[NSNull null]` on success. */ - (FBLPromise *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h" #import #if __has_include() #import #else #import "FBLPromises.h" #endif #import #import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsItem.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h" NSString *const kFIRInstallationsStoreUserDefaultsID = @"com.firebase.FIRInstallations"; @interface FIRInstallationsStore () @property(nonatomic, readonly) GULKeychainStorage *secureStorage; @property(nonatomic, readonly, nullable) NSString *accessGroup; @property(nonatomic, readonly) dispatch_queue_t queue; @property(nonatomic, readonly) GULUserDefaults *userDefaults; @end @implementation FIRInstallationsStore - (instancetype)initWithSecureStorage:(GULKeychainStorage *)storage accessGroup:(NSString *)accessGroup { self = [super init]; if (self) { _secureStorage = storage; _accessGroup = [accessGroup copy]; _queue = dispatch_queue_create("com.firebase.FIRInstallationsStore", DISPATCH_QUEUE_SERIAL); NSString *userDefaultsSuiteName = _accessGroup ?: kFIRInstallationsStoreUserDefaultsID; _userDefaults = [[GULUserDefaults alloc] initWithSuiteName:userDefaultsSuiteName]; } return self; } - (FBLPromise *)installationForAppID:(NSString *)appID appName:(NSString *)appName { NSString *itemID = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; return [self installationExistsForAppID:appID appName:appName] .then(^id(id result) { return [self.secureStorage getObjectForKey:itemID objectClass:[FIRInstallationsStoredItem class] accessGroup:self.accessGroup]; }) .then(^id(FIRInstallationsStoredItem *_Nullable storedItem) { if (storedItem == nil) { return [FIRInstallationsErrorUtil installationItemNotFoundForAppID:appID appName:appName]; } FIRInstallationsItem *item = [[FIRInstallationsItem alloc] initWithAppID:appID firebaseAppName:appName]; [item updateWithStoredItem:storedItem]; return item; }); } - (FBLPromise *)saveInstallation:(FIRInstallationsItem *)installationItem { FIRInstallationsStoredItem *storedItem = [installationItem storedItem]; NSString *identifier = [installationItem identifier]; return [self.secureStorage setObject:storedItem forKey:identifier accessGroup:self.accessGroup].then( ^id(id result) { return [self setInstallationExists:YES forItemWithIdentifier:identifier]; }); } - (FBLPromise *)removeInstallationForAppID:(NSString *)appID appName:(NSString *)appName { NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; return [self.secureStorage removeObjectForKey:identifier accessGroup:self.accessGroup].then( ^id(id result) { return [self setInstallationExists:NO forItemWithIdentifier:identifier]; }); } #pragma mark - User defaults - (FBLPromise *)installationExistsForAppID:(NSString *)appID appName:(NSString *)appName { NSString *identifier = [FIRInstallationsItem identifierWithAppID:appID appName:appName]; return [FBLPromise onQueue:self.queue do:^id _Nullable { return [[self userDefaults] objectForKey:identifier] != nil ? [NSNull null] : [FIRInstallationsErrorUtil installationItemNotFoundForAppID:appID appName:appName]; }]; } - (FBLPromise *)setInstallationExists:(BOOL)exists forItemWithIdentifier:(NSString *)identifier { return [FBLPromise onQueue:self.queue do:^id _Nullable { if (exists) { [[self userDefaults] setBool:YES forKey:identifier]; } else { [[self userDefaults] removeObjectForKey:identifier]; } return [NSNull null]; }]; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h ================================================ /* * 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 enum represent possible states of the installation auth token. * * WARNING: The enum is stored to Keychain as a part of `FIRInstallationsStoredAuthToken`. * Modification of it can lead to incompatibility with previous version. Any modification must be * evaluated and, if it is really needed, the `storageVersion` must be bumped and proper migration * code added. */ typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenStatus) { /// An initial status or an undefined value. FIRInstallationsAuthTokenStatusUnknown, /// The auth token has been received from the server. FIRInstallationsAuthTokenStatusTokenReceived }; /** * This class serializes and deserializes the installation data into/from `NSData` to be stored in * Keychain. This class is primarily used by `FIRInstallationsStore`. It is also used on the logic * level as a data object (see `FIRInstallationsItem.authToken`). * * WARNING: Modification of the class properties can lead to incompatibility with the stored data * encoded by the previous class versions. Any modification must be evaluated and, if it is really * needed, the `storageVersion` must be bumped and proper migration code added. */ @interface FIRInstallationsStoredAuthToken : NSObject @property FIRInstallationsAuthTokenStatus status; /// The installation auth token string that can be used to authorize requests to Firebase backend. @property(nullable, copy) NSString *token; /// The installation auth token expiration date. @property(nullable, copy) NSDate *expirationDate; /// The version of local storage. @property(nonatomic, readonly) NSInteger storageVersion; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" NSString *const kFIRInstallationsStoredAuthTokenStatusKey = @"status"; NSString *const kFIRInstallationsStoredAuthTokenTokenKey = @"token"; NSString *const kFIRInstallationsStoredAuthTokenExpirationDateKey = @"expirationDate"; NSString *const kFIRInstallationsStoredAuthTokenStorageVersionKey = @"storageVersion"; NSInteger const kFIRInstallationsStoredAuthTokenStorageVersion = 1; @implementation FIRInstallationsStoredAuthToken - (NSInteger)storageVersion { return kFIRInstallationsStoredAuthTokenStorageVersion; } - (nonnull id)copyWithZone:(nullable NSZone *)zone { FIRInstallationsStoredAuthToken *clone = [[FIRInstallationsStoredAuthToken alloc] init]; clone.status = self.status; clone.token = [self.token copy]; clone.expirationDate = self.expirationDate; return clone; } - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeInteger:self.status forKey:kFIRInstallationsStoredAuthTokenStatusKey]; [aCoder encodeObject:self.token forKey:kFIRInstallationsStoredAuthTokenTokenKey]; [aCoder encodeObject:self.expirationDate forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey]; [aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredAuthTokenStorageVersionKey]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { NSInteger storageVersion = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStorageVersionKey]; if (storageVersion > kFIRInstallationsStoredAuthTokenStorageVersion) { FIRLogWarning(kFIRLoggerInstallations, kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch, @"FIRInstallationsStoredAuthToken was encoded by a newer coder version %ld. " @"Current coder version is %ld. Some auth token data may be lost.", (long)storageVersion, (long)kFIRInstallationsStoredAuthTokenStorageVersion); } FIRInstallationsStoredAuthToken *object = [[FIRInstallationsStoredAuthToken alloc] init]; object.status = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredAuthTokenStatusKey]; object.token = [aDecoder decodeObjectOfClass:[NSString class] forKey:kFIRInstallationsStoredAuthTokenTokenKey]; object.expirationDate = [aDecoder decodeObjectOfClass:[NSDate class] forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey]; return object; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h" @class FIRInstallationsStoredAuthToken; @class FIRInstallationsStoredIIDCheckin; NS_ASSUME_NONNULL_BEGIN /** * The class is supposed to be used by `FIRInstallationsStore` only. It is required to * serialize/deserialize the installation data into/from `NSData` to be stored in Keychain. * * WARNING: Modification of the class properties can lead to incompatibility with the stored data * encoded by the previous class versions. Any modification must be evaluated and, if it is really * needed, the `storageVersion` must be bumped and proper migration code added. */ @interface FIRInstallationsStoredItem : NSObject /// A stable identifier that uniquely identifies the app instance. @property(nonatomic, copy, nullable) NSString *firebaseInstallationID; /// The `refreshToken` is used to authorize the installation auth token requests. @property(nonatomic, copy, nullable) NSString *refreshToken; @property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; @property(nonatomic) FIRInstallationsStatus registrationStatus; /// Instance ID default auth token imported from IID store as a part of IID migration. @property(nonatomic, nullable) NSString *IIDDefaultToken; /// The version of local storage. @property(nonatomic, readonly) NSInteger storageVersion; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m ================================================ /* * 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 "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h" #import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h" #import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h" NSString *const kFIRInstallationsStoredItemFirebaseInstallationIDKey = @"firebaseInstallationID"; NSString *const kFIRInstallationsStoredItemRefreshTokenKey = @"refreshToken"; NSString *const kFIRInstallationsStoredItemAuthTokenKey = @"authToken"; NSString *const kFIRInstallationsStoredItemRegistrationStatusKey = @"registrationStatus"; NSString *const kFIRInstallationsStoredItemIIDDefaultTokenKey = @"IIDDefaultToken"; NSString *const kFIRInstallationsStoredItemStorageVersionKey = @"storageVersion"; NSInteger const kFIRInstallationsStoredItemStorageVersion = 1; @implementation FIRInstallationsStoredItem - (NSInteger)storageVersion { return kFIRInstallationsStoredItemStorageVersion; } - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:self.firebaseInstallationID forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey]; [aCoder encodeObject:self.refreshToken forKey:kFIRInstallationsStoredItemRefreshTokenKey]; [aCoder encodeObject:self.authToken forKey:kFIRInstallationsStoredItemAuthTokenKey]; [aCoder encodeInteger:self.registrationStatus forKey:kFIRInstallationsStoredItemRegistrationStatusKey]; [aCoder encodeObject:self.IIDDefaultToken forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey]; [aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredItemStorageVersionKey]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { NSInteger storageVersion = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemStorageVersionKey]; if (storageVersion > self.storageVersion) { FIRLogWarning(kFIRLoggerInstallations, kFIRInstallationsMessageCodeInstallationCoderVersionMismatch, @"FIRInstallationsStoredItem was encoded by a newer coder version %ld. Current " @"coder version is %ld. Some installation data may be lost.", (long)storageVersion, (long)kFIRInstallationsStoredItemStorageVersion); } FIRInstallationsStoredItem *item = [[FIRInstallationsStoredItem alloc] init]; item.firebaseInstallationID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey]; item.refreshToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kFIRInstallationsStoredItemRefreshTokenKey]; item.authToken = [aDecoder decodeObjectOfClass:[FIRInstallationsStoredAuthToken class] forKey:kFIRInstallationsStoredItemAuthTokenKey]; item.registrationStatus = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemRegistrationStatusKey]; item.IIDDefaultToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kFIRInstallationsStoredItemIIDDefaultTokenKey]; return item; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h ================================================ // 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. // An umbrella header, for any other libraries in this repo to access Firebase // Installations Public headers. Any package manager complexity should be // handled here. #import ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallations.h ================================================ /* * 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 @class FIRApp; @class FIRInstallationsAuthTokenResult; NS_ASSUME_NONNULL_BEGIN /** A notification with this name is sent each time an installation is created or deleted. */ // clang-format off // clang-format12 merges the next two lines. FOUNDATION_EXPORT const NSNotificationName FIRInstallationIDDidChangeNotification NS_SWIFT_NAME(InstallationIDDidChange); /** `userInfo` key for the `FirebaseApp.name` in `FIRInstallationIDDidChangeNotification`. */ FOUNDATION_EXPORT NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey NS_SWIFT_NAME(InstallationIDDidChangeAppNameKey); // clang-format on /** * An installation ID handler block. * @param identifier The installation ID string if exists or `nil` otherwise. * @param error The error when `identifier == nil` or `nil` otherwise. */ typedef void (^FIRInstallationsIDHandler)(NSString *__nullable identifier, NSError *__nullable error) NS_SWIFT_NAME(InstallationsIDHandler); /** * An authorization token handler block. * @param tokenResult An instance of `InstallationsAuthTokenResult` in case of success or `nil` * otherwise. * @param error The error when `tokenResult == nil` or `nil` otherwise. */ typedef void (^FIRInstallationsTokenHandler)( FIRInstallationsAuthTokenResult *__nullable tokenResult, NSError *__nullable error) NS_SWIFT_NAME(InstallationsTokenHandler); /** * The class provides API for Firebase Installations. * Each configured `FirebaseApp` has a corresponding single instance of `Installations`. * An instance of the class provides access to the installation info for the `FirebaseApp` as well * as the ability to delete it. A Firebase Installation is unique by `FirebaseApp.name` and * `FirebaseApp.options.googleAppID` . */ NS_SWIFT_NAME(Installations) @interface FIRInstallations : NSObject - (instancetype)init NS_UNAVAILABLE; /** * Returns a default instance of `Installations`. * @returns An instance of `Installations` for `FirebaseApp.defaultApp(). * @throw Throws an exception if the default app is not configured yet or required `FirebaseApp` * options are missing. */ + (FIRInstallations *)installations NS_SWIFT_NAME(installations()); /** * Returns an instance of `Installations` for an application. * @param application A configured `FirebaseApp` instance. * @returns An instance of `Installations` corresponding to the passed application. * @throw Throws an exception if required `FirebaseApp` options are missing. */ + (FIRInstallations *)installationsWithApp:(FIRApp *)application NS_SWIFT_NAME(installations(app:)); /** * The method creates or retrieves an installation ID. The installation ID is a stable identifier * that uniquely identifies the app instance. NOTE: If the application already has an existing * FirebaseInstanceID then the InstanceID identifier will be used. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsIDHandler` for additional details. */ - (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion; /** * Retrieves (locally if it exists or from the server) a valid installation auth token. An existing * token may be invalidated or expired, so it is recommended to fetch the installation auth token * before each server request. The method does the same as `Installations.authTokenForcingRefresh(:, * completion:)` with forcing refresh `NO`. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsTokenHandler` for additional details. */ - (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion; /** * Retrieves (locally or from the server depending on `forceRefresh` value) a valid installation * auth token. An existing token may be invalidated or expire, so it is recommended to fetch the * installation auth token before each server request. This method should be used with `forceRefresh * == YES` when e.g. a request with the previously fetched installation auth token failed with "Not * Authorized" error. * @param forceRefresh If `YES` then the locally cached installation auth token will be ignored and * a new one will be requested from the server. If `NO`, then the locally cached installation auth * token will be returned if exists and has not expired yet. * @param completion A completion handler which is invoked when the operation completes. See * `InstallationsTokenHandler` for additional details. */ - (void)authTokenForcingRefresh:(BOOL)forceRefresh completion:(FIRInstallationsTokenHandler)completion; /** * Deletes all the installation data including the unique identifier, auth tokens and * all related data on the server side. A network connection is required for the method to * succeed. If fails, the existing installation data remains untouched. * @param completion A completion handler which is invoked when the operation completes. `error == * nil` indicates success. */ - (void)deleteWithCompletion:(void (^)(NSError *__nullable error))completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsAuthTokenResult.h ================================================ /* * 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 represents a result of the installation auth token request. */ NS_SWIFT_NAME(InstallationsAuthTokenResult) @interface FIRInstallationsAuthTokenResult : NSObject /** The installation auth token string. */ @property(nonatomic, readonly) NSString *authToken; /** The installation auth token expiration date. */ @property(nonatomic, readonly) NSDate *expirationDate; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsErrors.h ================================================ /* * 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 extern NSString *const kFirebaseInstallationsErrorDomain; typedef NS_ENUM(NSUInteger, FIRInstallationsErrorCode) { /** Unknown error. See `userInfo` for details. */ FIRInstallationsErrorCodeUnknown = 0, /** Keychain error. See `userInfo` for details. */ FIRInstallationsErrorCodeKeychain = 1, /** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */ FIRInstallationsErrorCodeServerUnreachable = 2, /** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details. */ FIRInstallationsErrorCodeInvalidConfiguration = 3, } NS_SWIFT_NAME(InstallationsErrorCode); ================================================ FILE: Pods/FirebaseInstallations/FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FirebaseInstallations.h ================================================ /* * 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 "FIRInstallations.h" #import "FIRInstallationsAuthTokenResult.h" #import "FIRInstallationsErrors.h" ================================================ FILE: Pods/FirebaseInstallations/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/FirebaseInstallations/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![License](https://img.shields.io/cocoapods/l/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Platform](https://img.shields.io/cocoapods/p/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase) [![Actions Status][gh-abtesting-badge]][gh-actions] [![Actions Status][gh-appcheck-badge]][gh-actions] [![Actions Status][gh-appdistribution-badge]][gh-actions] [![Actions Status][gh-auth-badge]][gh-actions] [![Actions Status][gh-cocoapods-integration-badge]][gh-actions] [![Actions Status][gh-core-badge]][gh-actions] [![Actions Status][gh-core-diagnostics-badge]][gh-actions] [![Actions Status][gh-crashlytics-badge]][gh-actions] [![Actions Status][gh-database-badge]][gh-actions] [![Actions Status][gh-datatransport-badge]][gh-actions] [![Actions Status][gh-dynamiclinks-badge]][gh-actions] [![Actions Status][gh-firebasepod-badge]][gh-actions] [![Actions Status][gh-firestore-badge]][gh-actions] [![Actions Status][gh-functions-badge]][gh-actions] [![Actions Status][gh-google-utilities-badge]][gh-actions] [![Actions Status][gh-google-utilities-components-badge]][gh-actions] [![Actions Status][gh-inappmessaging-badge]][gh-actions] [![Actions Status][gh-interop-badge]][gh-actions] [![Actions Status][gh-messaging-badge]][gh-actions] [![Actions Status][gh-mlmodeldownloader-badge]][gh-actions] [![Actions Status][gh-performance-badge]][gh-actions] [![Actions Status][gh-remoteconfig-badge]][gh-actions] [![Actions Status][gh-storage-badge]][gh-actions] [![Actions Status][gh-symbolcollision-badge]][gh-actions] [![Actions Status][gh-zip-badge]][gh-actions] [![Travis](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk) # Firebase Apple Open Source Development This repository contains all Apple platform Firebase SDK source except FirebaseAnalytics and FirebaseML. Firebase is an app development platform with tools to help you build, grow and monetize your app. More information about Firebase can be found on the [official Firebase website](https://firebase.google.com). **Note** _FirebaseCombineSwift_ contains support for Apple's Combine framework. This module is currently under development, and not yet supported for use in production environments. Fore more details, please refer to the [docs](FirebaseCombineSwift/README.md). ## Installation See the subsections below for details about the different installation methods. 1. [Standard pod install](README.md#standard-pod-install) 1. [Swift Package Manager](SwiftPackageManager.md) 1. [Installing from the GitHub repo](README.md#installing-from-github) 1. [Experimental Carthage](README.md#carthage-ios-only) ### Standard pod install Go to [https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup). ### Swift Package Manager Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be found at [SwiftPackageManager.md](SwiftPackageManager.md). ### Installing from GitHub These instructions can be used to access the Firebase repo at other branches, tags, or commits. #### Background See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod) for instructions and options about overriding pod source locations. #### Accessing Firebase Source Snapshots All of the official releases are tagged in this repo and available via CocoaPods. To access a local source snapshot or unreleased branch, use Podfile directives like the following: To access FirebaseFirestore via a branch: ```ruby pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master' ``` To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do: ```ruby pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk' pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk' ``` ### Carthage (iOS only) Instructions for the experimental Carthage distribution are at [Carthage](Carthage.md). ### Using Firebase from a Framework or a library [Using Firebase from a Framework or a library](docs/firebase_in_libraries.md) ## Development To develop Firebase software in this repository, ensure that you have at least the following software: * Xcode 12.2 (or later) CocoaPods is still the canonical way to develop, but much of the repo now supports development with Swift Package Manager. ### CocoaPods Install * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: ```ruby pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios ``` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. Firestore has a self contained Xcode project. See [Firestore/README.md](Firestore/README.md). #### Development for Catalyst * `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Swift Package Manager * To enable test schemes: `./scripts/setup_spm_tests.sh` * `open Package.swift` or double click `Package.swift` in Finder. * Xcode will open the project * Choose a scheme for a library to build or test suite to run * Choose a target platform by selecting the run destination along with the scheme ### Adding a New Firebase Pod See [AddNewPod.md](AddNewPod.md). ### Managing Headers and Imports See [HeadersImports.md](HeadersImports.md). ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ### Running Sample Apps In order to run the sample apps and integration tests, you'll need a valid `GoogleService-Info.plist` file. The Firebase Xcode project contains dummy plist files without real values, but can be replaced with real plist files. To get your own `GoogleService-Info.plist` files: 1. Go to the [Firebase Console](https://console.firebase.google.com/) 2. Create a new Firebase project, if you don't already have one 3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.Database-Example`) 4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project. ### Coverage Report Generation See [scripts/code_coverage_report/README.md](scripts/code_coverage_report/README.md). ## Specific Component Instructions See the sections below for any special instructions for those components. ### Firebase Auth If you're doing specific Firebase Auth development, see [the Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about building and running the FirebaseAuth pod along with various samples and tests. ### Firebase Database The Firebase Database Integration tests can be run against a locally running Database Emulator or against a production instance. To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before running the integration test. To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to `FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to [public](https://firebase.google.com/docs/database/security/quickstart) while your tests are running. ### Firebase Performance Monitoring If you're doing specific Firebase Performance Monitoring development, see [the Performance README](FirebasePerformance/README.md) for instructions about building the SDK and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about integrating Performance with the dev test App. ### Firebase Storage To run the Storage Integration tests, follow the instructions in [FIRStorageIntegrationTests.m](FirebaseStorage/Tests/Integration/FIRStorageIntegrationTests.m). #### Push Notifications Push notifications can only be delivered to specially provisioned App IDs in the developer portal. In order to actually test receiving push notifications, you will need to: 1. Change the bundle identifier of the sample app to something you own in your Apple Developer account, and enable that App ID for push notifications. 2. You'll also need to [upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs) at **Project Settings > Cloud Messaging > [Your Firebase App]**. 3. Ensure your iOS device is added to your Apple Developer portal as a test device. #### iOS Simulator The iOS Simulator cannot register for remote notifications, and will not receive push notifications. In order to receive push notifications, you'll have to follow the steps above and run the app on a physical device. ## Building with Firebase on Apple platforms At this time, not all of Firebase's products are available across all Apple platforms. However, Firebase is constantly evolving and community supported efforts have helped expand Firebase's support. To keep up with the latest info regarding Firebase's support across Apple platforms, refer to [this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform) in Firebase's documentation. ### Community Supported Efforts We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are very grateful! We'd like to empower as many developers as we can to be able to use Firebase and participate in the Firebase community. #### tvOS, macOS, watchOS and Catalyst Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and work on tvOS, macOS, watchOS and Catalyst. For tvOS, see the [Sample](Example/tvOSSample). For watchOS, currently only Messaging, Storage and Crashlytics (and their dependencies) have limited support. See the [Independent Watch App Sample](Example/watchOSSample). Keep in mind that macOS, tvOS, watchOS and Catalyst are not officially supported by Firebase, and this repository is actively developed primarily for iOS. While we can catch basic unit test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected on macOS, tvOS or watchOS. If you encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). During app setup in the console, you may get to a step that mentions something like "Checking if the app has communicated with our servers". This relies on Analytics and will not work on macOS/tvOS/watchOS/Catalyst. **It's safe to ignore the message and continue**, the rest of the SDKs will work as expected. #### Additional MacOS and Catalyst Notes * FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability` to Build Settings. * For Catalyst, FirebaseFirestore requires signing the [gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681). #### Additional Crashlytics Notes * watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded) ## Roadmap See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source plans and directions. ## Contributing See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase iOS SDK. ## License The contents of this repository are licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Your use of Firebase is governed by the [Terms of Service for Firebase Services](https://firebase.google.com/terms/). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-abtesting-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/abtesting/badge.svg [gh-appcheck-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/app_check/badge.svg [gh-appdistribution-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/appdistribution/badge.svg [gh-auth-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/auth/badge.svg [gh-cocoapods-integration-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/cocoapods-integration/badge.svg [gh-core-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core/badge.svg [gh-core-diagnostics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core-diagnostics/badge.svg [gh-crashlytics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/crashlytics/badge.svg [gh-database-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/database/badge.svg [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg [gh-dynamiclinks-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/dynamiclinks/badge.svg [gh-firebasepod-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firebasepod/badge.svg [gh-firestore-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firestore/badge.svg [gh-functions-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/functions/badge.svg [gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg [gh-google-utilities-components-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities-components/badge.svg [gh-inappmessaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/inappmessaging/badge.svg [gh-interop-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/interop/badge.svg [gh-messaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/messaging/badge.svg [gh-mlmodeldownloader-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/mlmodeldownloader/badge.svg [gh-performance-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/performance/badge.svg [gh-remoteconfig-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/remoteconfig/badge.svg [gh-storage-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/storage/badge.svg [gh-symbolcollision-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/symbolcollision/badge.svg [gh-zip-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/zip/badge.svg ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/Info.plist ================================================ AvailableLibraries LibraryIdentifier ios-arm64_armv7 LibraryPath GoogleAppMeasurement.framework SupportedArchitectures arm64 armv7 SupportedPlatform ios LibraryIdentifier ios-arm64_i386_x86_64-simulator LibraryPath GoogleAppMeasurement.framework SupportedArchitectures arm64 i386 x86_64 SupportedPlatform ios SupportedPlatformVariant simulator CFBundlePackageType XFWK XCFrameworkFormatVersion 1.0 ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/GoogleAppMeasurement ================================================ [File too large to display: 20.7 MB] ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/Info.plist ================================================ CFBundleExecutable GoogleAppMeasurement CFBundleIdentifier com.firebase.Firebase-GoogleAppMeasurement CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleAppMeasurement CFBundlePackageType FMWK CFBundleVersion 8.3.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework/Modules/module.modulemap ================================================ framework module GoogleAppMeasurement { umbrella header "GoogleAppMeasurement-umbrella.h" export * module * { export * } link framework "Security" link framework "SystemConfiguration" link "c++" link "sqlite3" link "z" } ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_i386_x86_64-simulator/GoogleAppMeasurement.framework/GoogleAppMeasurement ================================================ [File too large to display: 10.5 MB] ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_i386_x86_64-simulator/GoogleAppMeasurement.framework/Info.plist ================================================ CFBundleExecutable GoogleAppMeasurement CFBundleIdentifier com.firebase.Firebase-GoogleAppMeasurement CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleAppMeasurement CFBundlePackageType FMWK CFBundleVersion 8.3.0 DTSDKName iphonesimulator11.2 ================================================ FILE: Pods/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/ios-arm64_i386_x86_64-simulator/GoogleAppMeasurement.framework/Modules/module.modulemap ================================================ framework module GoogleAppMeasurement { umbrella header "GoogleAppMeasurement-umbrella.h" export * module * { export * } link framework "Security" link framework "SystemConfiguration" link "c++" link "sqlite3" link "z" } ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/GDTCCTCompressionHelper.m ================================================ /* * 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" #import @implementation GDTCCTCompressionHelper + (nullable NSData *)gzippedData:(NSData *)data { #if defined(__LP64__) && __LP64__ // Don't support > 32bit length for 64 bit, see note in header. if (data.length > UINT_MAX) { return nil; } #endif enum { kChunkSize = 1024 }; const void *bytes = [data bytes]; NSUInteger length = [data length]; int level = Z_DEFAULT_COMPRESSION; if (!bytes || !length) { return nil; } z_stream strm; bzero(&strm, sizeof(z_stream)); int memLevel = 8; // Default. int windowBits = 15 + 16; // Enable gzip header instead of zlib header. int retCode; if (deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY) != Z_OK) { return nil; } // Hint the size at 1/4 the input size. NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)]; unsigned char output[kChunkSize]; // Setup the input. strm.avail_in = (unsigned int)length; strm.next_in = (unsigned char *)bytes; // Collect the data. do { // update what we're passing in strm.avail_out = kChunkSize; strm.next_out = output; retCode = deflate(&strm, Z_FINISH); if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { deflateEnd(&strm); return nil; } // Collect what we got. unsigned gotBack = kChunkSize - strm.avail_out; if (gotBack > 0) { [result appendBytes:output length:gotBack]; } } while (retCode == Z_OK); // If the loop exits, it used all input and the stream ended. NSAssert(strm.avail_in == 0, @"Should have finished deflating without using all input, %u bytes left", strm.avail_in); NSAssert(retCode == Z_STREAM_END, @"thought we finished deflate w/o getting a result of stream end, code %d", retCode); // Clean up. deflateEnd(&strm); return result; } + (BOOL)isGzipped:(NSData *)data { const UInt8 *bytes = (const UInt8 *)data.bytes; return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m ================================================ /* * 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" #if TARGET_OS_IOS || TARGET_OS_TV #import #elif TARGET_OS_OSX #import #endif // TARGET_OS_IOS || TARGET_OS_TV #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import #import #import #import "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h" #pragma mark - General purpose encoders pb_bytes_array_t *GDTCCTEncodeString(NSString *string) { NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; return GDTCCTEncodeData(stringBytes); } pb_bytes_array_t *GDTCCTEncodeData(NSData *data) { pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length)); if (pbBytesArray != NULL) { [data getBytes:pbBytesArray->bytes length:data.length]; pbBytesArray->size = (pb_size_t)data.length; } return pbBytesArray; } #pragma mark - CCT object constructors NSData *_Nullable GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest) { pb_ostream_t sizestream = PB_OSTREAM_SIZING; // Encode 1 time to determine the size. if (!pb_encode(&sizestream, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) { GDTCORLogError(GDTCORMCEGeneralError, @"Error in nanopb encoding for size: %s", PB_GET_ERROR(&sizestream)); } // Encode a 2nd time to actually get the bytes from it. size_t bufferSize = sizestream.bytes_written; CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); CFDataSetLength(dataRef, bufferSize); pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); if (!pb_encode(&ostream, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) { GDTCORLogError(GDTCORMCEGeneralError, @"Error in nanopb encoding for bytes: %s", PB_GET_ERROR(&ostream)); } return CFBridgingRelease(dataRef); } gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest( NSDictionary *> *logMappingIDToLogSet) { gdt_cct_BatchedLogRequest batchedLogRequest = gdt_cct_BatchedLogRequest_init_default; NSUInteger numberOfLogRequests = logMappingIDToLogSet.count; gdt_cct_LogRequest *logRequests = calloc(numberOfLogRequests, sizeof(gdt_cct_LogRequest)); if (logRequests == NULL) { return batchedLogRequest; } __block int i = 0; [logMappingIDToLogSet enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull logMappingID, NSSet *_Nonnull logSet, BOOL *_Nonnull stop) { int32_t logSource = [logMappingID intValue]; gdt_cct_LogRequest logRequest = GDTCCTConstructLogRequest(logSource, logSet); logRequests[i] = logRequest; i++; }]; batchedLogRequest.log_request = logRequests; batchedLogRequest.log_request_count = (pb_size_t)numberOfLogRequests; return batchedLogRequest; } gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, NSSet *_Nonnull logSet) { if (logSet.count == 0) { GDTCORLogError(GDTCORMCEGeneralError, @"%@", @"An empty event set can't be serialized to proto."); gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default; return logRequest; } gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default; logRequest.log_source = logSource; logRequest.has_log_source = 1; logRequest.client_info = GDTCCTConstructClientInfo(); logRequest.has_client_info = 1; logRequest.log_event = calloc(logSet.count, sizeof(gdt_cct_LogEvent)); if (logRequest.log_event == NULL) { return logRequest; } int i = 0; for (GDTCOREvent *log in logSet) { gdt_cct_LogEvent logEvent = GDTCCTConstructLogEvent(log); logRequest.log_event[i] = logEvent; i++; } logRequest.log_event_count = (pb_size_t)logSet.count; GDTCORClock *currentTime = [GDTCORClock snapshot]; logRequest.request_time_ms = currentTime.timeMillis; logRequest.has_request_time_ms = 1; logRequest.request_uptime_ms = [currentTime uptimeMilliseconds]; logRequest.has_request_uptime_ms = 1; return logRequest; } gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event) { gdt_cct_LogEvent logEvent = gdt_cct_LogEvent_init_default; logEvent.event_time_ms = event.clockSnapshot.timeMillis; logEvent.has_event_time_ms = 1; logEvent.event_uptime_ms = [event.clockSnapshot uptimeMilliseconds]; logEvent.has_event_uptime_ms = 1; logEvent.timezone_offset_seconds = event.clockSnapshot.timezoneOffsetSeconds; logEvent.has_timezone_offset_seconds = 1; if (event.customBytes) { NSData *networkConnectionInfoData = event.networkConnectionInfoData; if (networkConnectionInfoData) { [networkConnectionInfoData getBytes:&logEvent.network_connection_info length:networkConnectionInfoData.length]; logEvent.has_network_connection_info = 1; } NSNumber *eventCode = event.eventCode; if (eventCode != nil) { logEvent.has_event_code = 1; logEvent.event_code = [eventCode intValue]; } } NSError *error; NSData *extensionBytes; extensionBytes = event.serializedDataObjectBytes; if (error) { GDTCORLogWarning(GDTCORMCWFileReadError, @"There was an error reading extension bytes from disk: %@", error); return logEvent; } logEvent.source_extension = GDTCCTEncodeData(extensionBytes); // read bytes from the file. return logEvent; } gdt_cct_ClientInfo GDTCCTConstructClientInfo() { gdt_cct_ClientInfo clientInfo = gdt_cct_ClientInfo_init_default; clientInfo.client_type = gdt_cct_ClientInfo_ClientType_IOS_FIREBASE; clientInfo.has_client_type = 1; #if TARGET_OS_IOS || TARGET_OS_TV clientInfo.ios_client_info = GDTCCTConstructiOSClientInfo(); clientInfo.has_ios_client_info = 1; #elif TARGET_OS_OSX // TODO(mikehaney24): Expand the proto to include macOS client info. #endif return clientInfo; } gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo() { gdt_cct_IosClientInfo iOSClientInfo = gdt_cct_IosClientInfo_init_default; #if TARGET_OS_IOS || TARGET_OS_TV UIDevice *device = [UIDevice currentDevice]; NSBundle *bundle = [NSBundle mainBundle]; NSLocale *locale = [NSLocale currentLocale]; iOSClientInfo.os_full_version = GDTCCTEncodeString(device.systemVersion); NSArray *versionComponents = [device.systemVersion componentsSeparatedByString:@"."]; iOSClientInfo.os_major_version = GDTCCTEncodeString(versionComponents[0]); NSString *version = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; if (version) { iOSClientInfo.application_build = GDTCCTEncodeString(version); } NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; if (countryCode) { iOSClientInfo.country = GDTCCTEncodeString([locale objectForKey:NSLocaleCountryCode]); } iOSClientInfo.model = GDTCCTEncodeString(GDTCORDeviceModel()); NSString *languageCode = bundle.preferredLocalizations.firstObject; iOSClientInfo.language_code = languageCode ? GDTCCTEncodeString(languageCode) : GDTCCTEncodeString(@"en"); iOSClientInfo.application_bundle_id = GDTCCTEncodeString(bundle.bundleIdentifier); #endif return iOSClientInfo; } NSData *GDTCCTConstructNetworkConnectionInfoData() { gdt_cct_NetworkConnectionInfo networkConnectionInfo = gdt_cct_NetworkConnectionInfo_init_default; NSInteger currentNetworkType = GDTCORNetworkTypeMessage(); if (currentNetworkType) { networkConnectionInfo.has_network_type = 1; if (currentNetworkType == GDTCORNetworkTypeMobile) { networkConnectionInfo.network_type = gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE; networkConnectionInfo.mobile_subtype = GDTCCTNetworkConnectionInfoNetworkMobileSubtype(); if (networkConnectionInfo.mobile_subtype != gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE) { networkConnectionInfo.has_mobile_subtype = 1; } } else { networkConnectionInfo.network_type = gdt_cct_NetworkConnectionInfo_NetworkType_WIFI; } } NSData *networkConnectionInfoData = [NSData dataWithBytes:&networkConnectionInfo length:sizeof(networkConnectionInfo)]; return networkConnectionInfoData; } gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype() { NSNumber *networkMobileSubtypeMessage = @(GDTCORNetworkMobileSubTypeMessage()); if (!networkMobileSubtypeMessage.intValue) { return gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE; } static NSDictionary *MessageToNetworkSubTypeMessage; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ MessageToNetworkSubTypeMessage = @{ @(GDTCORNetworkMobileSubtypeGPRS) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_GPRS), @(GDTCORNetworkMobileSubtypeEdge) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EDGE), @(GDTCORNetworkMobileSubtypeWCDMA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE), @(GDTCORNetworkMobileSubtypeHSDPA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_HSDPA), @(GDTCORNetworkMobileSubtypeHSUPA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_HSUPA), @(GDTCORNetworkMobileSubtypeCDMA1x) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_CDMA), @(GDTCORNetworkMobileSubtypeCDMAEVDORev0) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_0), @(GDTCORNetworkMobileSubtypeCDMAEVDORevA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_A), @(GDTCORNetworkMobileSubtypeCDMAEVDORevB) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_B), @(GDTCORNetworkMobileSubtypeHRPD) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EHRPD), @(GDTCORNetworkMobileSubtypeLTE) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE), }; }); NSNumber *networkMobileSubtype = MessageToNetworkSubTypeMessage[networkMobileSubtypeMessage]; return networkMobileSubtype.intValue; } #pragma mark - CCT Object decoders gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error) { gdt_cct_LogResponse response = gdt_cct_LogResponse_init_default; pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]); if (!pb_decode(&istream, gdt_cct_LogResponse_fields, &response)) { NSString *nanopb_error = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)]; NSDictionary *userInfo = @{@"nanopb error:" : nanopb_error}; if (error != NULL) { *error = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:userInfo]; } response = (gdt_cct_LogResponse)gdt_cct_LogResponse_init_default; } return response; } ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m ================================================ /* * 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import #import #import #import #import #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" NS_ASSUME_NONNULL_BEGIN #ifdef GDTCOR_VERSION #define STR(x) STR_EXPAND(x) #define STR_EXPAND(x) #x static NSString *const kGDTCCTSupportSDKVersion = @STR(GDTCOR_VERSION); #else static NSString *const kGDTCCTSupportSDKVersion = @"UNKNOWN"; #endif // GDTCOR_VERSION typedef void (^GDTCCTUploaderURLTaskCompletion)(NSNumber *batchID, NSSet *_Nullable events, NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error); typedef void (^GDTCCTUploaderEventBatchBlock)(NSNumber *_Nullable batchID, NSSet *_Nullable events); @interface GDTCCTUploadOperation () /// The properties to store parameters passed in the initializer. See the initialized docs for /// details. @property(nonatomic, readonly) GDTCORTarget target; @property(nonatomic, readonly) GDTCORUploadConditions conditions; @property(nonatomic, readonly) NSURL *uploadURL; @property(nonatomic, readonly) id storage; @property(nonatomic, readonly) id metadataProvider; /** The URL session that will attempt upload. */ @property(nonatomic) NSURLSession *uploaderSession; /// NSOperation state properties implementation. @property(nonatomic, readwrite, getter=isExecuting) BOOL executing; @property(nonatomic, readwrite, getter=isFinished) BOOL finished; @property(nonatomic, readwrite) BOOL uploadAttempted; @end @implementation GDTCCTUploadOperation - (instancetype)initWithTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions uploadURL:(NSURL *)uploadURL queue:(dispatch_queue_t)queue storage:(id)storage metadataProvider:(id)metadataProvider { self = [super init]; if (self) { _uploaderQueue = queue; _target = target; _conditions = conditions; _uploadURL = uploadURL; _storage = storage; _metadataProvider = metadataProvider; } return self; } - (NSURLSession *)uploaderSessionCreateIfNeeded { if (_uploaderSession == nil) { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; _uploaderSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; } return _uploaderSession; } - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions { __block GDTCORBackgroundIdentifier backgroundTaskID = GDTCORBackgroundIdentifierInvalid; dispatch_block_t backgroundTaskCompletion = ^{ // End the background task if there was one. if (backgroundTaskID != GDTCORBackgroundIdentifierInvalid) { [[GDTCORApplication sharedApplication] endBackgroundTask:backgroundTaskID]; backgroundTaskID = GDTCORBackgroundIdentifierInvalid; } }; backgroundTaskID = [[GDTCORApplication sharedApplication] beginBackgroundTaskWithName:@"GDTCCTUploader-upload" expirationHandler:^{ if (backgroundTaskID != GDTCORBackgroundIdentifierInvalid) { // Cancel the upload and complete delivery. [self.currentTask cancel]; // End the background task. backgroundTaskCompletion(); } }]; id storage = self.storage; // 1. Check if the conditions for the target are suitable. [self isReadyToUploadTarget:target conditions:conditions] .thenOn(self.uploaderQueue, ^id(id result) { // 2. Remove previously attempted batches return [storage removeAllBatchesForTarget:target deleteEvents:NO]; }) .thenOn(self.uploaderQueue, ^FBLPromise *(id result) { // There may be a big amount of events stored, so creating a batch may be an // expensive operation. // 3. Do a lightweight check if there are any events for the target first to // finish early if there are no. return [storage hasEventsForTarget:target]; }) .validateOn(self.uploaderQueue, ^BOOL(NSNumber *hasEvents) { // Stop operation if there are no events to upload. return hasEvents.boolValue; }) .thenOn(self.uploaderQueue, ^FBLPromise *(id result) { if (self.isCancelled) { return nil; } // 4. Fetch events to upload. GDTCORStorageEventSelector *eventSelector = [self eventSelectorTarget:target withConditions:conditions]; return [storage batchWithEventSelector:eventSelector batchExpiration:[NSDate dateWithTimeIntervalSinceNow:600]]; }) .validateOn(self.uploaderQueue, ^BOOL(GDTCORUploadBatch *batch) { // 5. Validate batch. return batch.batchID != nil && batch.events.count > 0; }) .thenOn(self.uploaderQueue, ^FBLPromise *(GDTCORUploadBatch *batch) { // A non-empty batch has been created, consider it as an upload attempt. self.uploadAttempted = YES; // 6. Perform upload URL request. return [self sendURLRequestWithBatch:batch target:target storage:storage]; }) .thenOn(self.uploaderQueue, ^id(id result) { // 7. Finish operation. [self finishOperation]; backgroundTaskCompletion(); return nil; }) .catchOn(self.uploaderQueue, ^(NSError *error) { // TODO: Maybe report the error to the client. [self finishOperation]; backgroundTaskCompletion(); }); } #pragma mark - Upload implementation details /** Sends URL request to upload the provided batch and handle the response. */ - (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch target:(GDTCORTarget)target storage:(id)storage { NSNumber *batchID = batch.batchID; // 1. Send URL request. return [self sendURLRequestWithBatch:batch target:target] .thenOn( self.uploaderQueue, ^FBLPromise *(GULURLSessionDataResponse *response) { // 2. Parse response and update the next upload time if can. [self updateNextUploadTimeWithResponse:response forTarget:target]; // 3. Cleanup batch. // Only retry if one of these codes is returned: // 429 - Too many requests; // 5xx - Server errors. NSInteger statusCode = response.HTTPResponse.statusCode; if (statusCode == 429 || (statusCode >= 500 && statusCode < 600)) { // Move the events back to the main storage to be uploaded on the next attempt. return [storage removeBatchWithID:batchID deleteEvents:NO]; } else { if (statusCode >= 200 && statusCode <= 300) { GDTCORLogDebug(@"CCT: batch %@ delivered", batchID); } else { GDTCORLogDebug( @"CCT: batch %@ was rejected by the server and will be deleted with all events", batchID); } // The events are either delivered or unrecoverable broken, so remove the batch with // events. return [storage removeBatchWithID:batch.batchID deleteEvents:YES]; } }) .recoverOn(self.uploaderQueue, ^id(NSError *error) { // In the case of a network error move the events back to the main storage to be uploaded on // the next attempt. return [storage removeBatchWithID:batchID deleteEvents:NO]; }); } /** Composes and sends URL request. */ - (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch target:(GDTCORTarget)target { return [FBLPromise onQueue:self.uploaderQueue do:^NSURLRequest * { // 1. Prepare URL request. NSData *requestProtoData = [self constructRequestProtoWithEvents:batch.events]; NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData]; BOOL usingGzipData = gzippedData != nil && gzippedData.length < requestProtoData.length; NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData; NSURLRequest *request = [self constructRequestWithURL:self.uploadURL forTarget:target data:dataToSend]; GDTCORLogDebug(@"CTT: request containing %lu events for batch: %@ for target: " @"%ld created: %@", (unsigned long)batch.events.count, batch.batchID, (long)target, request); return request; }] .thenOn(self.uploaderQueue, ^FBLPromise *(NSURLRequest *request) { // 2. Send URL request. return [[self uploaderSessionCreateIfNeeded] gul_dataTaskPromiseWithRequest:request]; }) .thenOn(self.uploaderQueue, ^GULURLSessionDataResponse *(GULURLSessionDataResponse *response) { // Invalidate session to release the delegate (which is `self`) to break the retain // cycle. [self.uploaderSession finishTasksAndInvalidate]; return response; }) .recoverOn(self.uploaderQueue, ^id(NSError *error) { // Invalidate session to release the delegate (which is `self`) to break the retain cycle. [self.uploaderSession finishTasksAndInvalidate]; // Re-throw the error. return error; }); } /** Parses server response and update next upload time for the specified target based on it. */ - (void)updateNextUploadTimeWithResponse:(GULURLSessionDataResponse *)response forTarget:(GDTCORTarget)target { GDTCORClock *futureUploadTime; if (response.HTTPBody) { NSError *decodingError; gdt_cct_LogResponse logResponse = GDTCCTDecodeLogResponse(response.HTTPBody, &decodingError); if (!decodingError && logResponse.has_next_request_wait_millis) { GDTCORLogDebug(@"CCT: The backend responded asking to not upload for %lld millis from now.", logResponse.next_request_wait_millis); futureUploadTime = [GDTCORClock clockSnapshotInTheFuture:logResponse.next_request_wait_millis]; } else if (decodingError) { GDTCORLogDebug(@"There was a response decoding error: %@", decodingError); } pb_release(gdt_cct_LogResponse_fields, &logResponse); } // If no futureUploadTime was parsed from the response body, then check // [Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header. if (!futureUploadTime) { NSString *retryAfterHeader = response.HTTPResponse.allHeaderFields[@"Retry-After"]; if (retryAfterHeader.length > 0) { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; formatter.numberStyle = NSNumberFormatterDecimalStyle; NSNumber *retryAfterSeconds = [formatter numberFromString:retryAfterHeader]; if (retryAfterSeconds != nil) { uint64_t retryAfterMillis = retryAfterSeconds.unsignedIntegerValue * 1000u; futureUploadTime = [GDTCORClock clockSnapshotInTheFuture:retryAfterMillis]; } } } if (!futureUploadTime) { GDTCORLogDebug(@"%@", @"CCT: The backend response failed to parse, so the next request " @"won't occur until 15 minutes from now"); // 15 minutes from now. futureUploadTime = [GDTCORClock clockSnapshotInTheFuture:15 * 60 * 1000]; } [self.metadataProvider setNextUploadTime:futureUploadTime forTarget:target]; } #pragma mark - Private helper methods /** @return A resolved promise if is ready and a rejected promise if not. */ - (FBLPromise *)isReadyToUploadTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions { FBLPromise *promise = [FBLPromise pendingPromise]; if ([self readyToUploadTarget:target conditions:conditions]) { [promise fulfill:[NSNull null]]; } else { NSString *reason = [NSString stringWithFormat:@"Target %ld is not ready to upload with condition: %ld", (long)target, (long)conditions]; [promise reject:[self genericRejectedPromiseErrorWithReason:reason]]; } return promise; } // TODO: Move to a separate class/extension/file when needed in other files. /** Returns an error object with the specified failure reason. */ - (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { return [NSError errorWithDomain:@"GDTCCTUploader" code:-1 userInfo:@{NSLocalizedFailureReasonErrorKey : reason}]; } /** Returns if the specified target is ready to be uploaded based on the specified conditions. */ - (BOOL)readyToUploadTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions { // Not ready to upload with no network connection. // TODO: Reconsider using reachability to prevent an upload attempt. // See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details. if (conditions & GDTCORUploadConditionNoNetwork) { GDTCORLogDebug(@"%@", @"CCT: Not ready to upload without a network connection."); return NO; } // Upload events with no additional conditions if high priority. if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { GDTCORLogDebug(@"%@", @"CCT: a high priority event is allowing an upload"); return YES; } // Check next upload time for the target. BOOL isAfterNextUploadTime = YES; GDTCORClock *nextUploadTime = [self.metadataProvider nextUploadTimeForTarget:target]; if (nextUploadTime) { isAfterNextUploadTime = [[GDTCORClock snapshot] isAfter:nextUploadTime]; } if (isAfterNextUploadTime) { GDTCORLogDebug(@"CCT: can upload to target %ld because the request wait time has transpired", (long)target); } else { GDTCORLogDebug(@"CCT: can't upload to target %ld because the backend asked to wait", (long)target); } return isAfterNextUploadTime; } /** Constructs data given an upload package. * * @param events The events used to construct the request proto bytes. * @return Proto bytes representing a gdt_cct_LogRequest object. */ - (nonnull NSData *)constructRequestProtoWithEvents:(NSSet *)events { // Segment the log events by log type. NSMutableDictionary *> *logMappingIDToLogSet = [[NSMutableDictionary alloc] init]; [events enumerateObjectsUsingBlock:^(GDTCOREvent *_Nonnull event, BOOL *_Nonnull stop) { NSMutableSet *logSet = logMappingIDToLogSet[event.mappingID]; logSet = logSet ? logSet : [[NSMutableSet alloc] init]; [logSet addObject:event]; logMappingIDToLogSet[event.mappingID] = logSet; }]; gdt_cct_BatchedLogRequest batchedLogRequest = GDTCCTConstructBatchedLogRequest(logMappingIDToLogSet); NSData *data = GDTCCTEncodeBatchedLogRequest(&batchedLogRequest); pb_release(gdt_cct_BatchedLogRequest_fields, &batchedLogRequest); return data ? data : [[NSData alloc] init]; } /** Constructs a request to the given URL and target with the specified request body data. * * @param target The target backend to send the request to. * @param data The request body data. * @return A new NSURLRequest ready to be sent to FLL. */ - (nullable NSURLRequest *)constructRequestWithURL:(NSURL *)URL forTarget:(GDTCORTarget)target data:(NSData *)data { if (data == nil || data.length == 0) { GDTCORLogDebug(@"There was no data to construct a request for target %ld.", (long)target); return nil; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; NSString *targetString; switch (target) { case kGDTCORTargetCCT: targetString = @"cct"; break; case kGDTCORTargetFLL: targetString = @"fll"; break; case kGDTCORTargetCSH: targetString = @"csh"; break; case kGDTCORTargetINT: targetString = @"int"; break; default: targetString = @"unknown"; break; } NSString *userAgent = [NSString stringWithFormat:@"datatransport/%@ %@support/%@ apple/", kGDTCORVersion, targetString, kGDTCCTSupportSDKVersion]; [request setValue:[self.metadataProvider APIKeyForTarget:target] forHTTPHeaderField:@"X-Goog-Api-Key"]; if ([GDTCCTCompressionHelper isGzipped:data]) { [request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"]; } [request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"]; [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; request.HTTPMethod = @"POST"; [request setHTTPBody:data]; return request; } /** Creates and returns a storage event selector for the specified target and conditions. */ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions { if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) { return [GDTCORStorageEventSelector eventSelectorForTarget:target]; } NSMutableSet *qosTiers = [[NSMutableSet alloc] init]; if (conditions & GDTCORUploadConditionWifiData) { [qosTiers addObjectsFromArray:@[ @(GDTCOREventQoSFast), @(GDTCOREventQoSWifiOnly), @(GDTCOREventQosDefault), @(GDTCOREventQoSTelemetry), @(GDTCOREventQoSUnknown) ]]; } if (conditions & GDTCORUploadConditionMobileData) { [qosTiers addObjectsFromArray:@[ @(GDTCOREventQoSFast), @(GDTCOREventQosDefault) ]]; } return [[GDTCORStorageEventSelector alloc] initWithTarget:target eventIDs:nil mappingIDs:nil qosTiers:qosTiers]; } #pragma mark - NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler { if (!completionHandler) { return; } if (response.statusCode == 302 || response.statusCode == 301) { NSURLRequest *newRequest = [self constructRequestWithURL:request.URL forTarget:kGDTCORTargetCCT data:task.originalRequest.HTTPBody]; completionHandler(newRequest); } else { completionHandler(request); } } #pragma mark - NSOperation methods @synthesize executing = _executing; @synthesize finished = _finished; - (BOOL)isFinished { @synchronized(self) { return _finished; } } - (BOOL)isExecuting { @synchronized(self) { return _executing; } } - (BOOL)isAsynchronous { return YES; } - (void)startOperation { @synchronized(self) { [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; self->_executing = YES; self->_finished = NO; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } } - (void)finishOperation { @synchronized(self) { [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; self->_executing = NO; self->_finished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } } - (void)main { [self startOperation]; GDTCORLogDebug(@"Upload operation started: %@", self); [self uploadTarget:self.target withConditions:self.conditions]; } - (void)cancel { @synchronized(self) { [super cancel]; // If the operation hasn't been started we can set `isFinished = YES` straight away. if (!_executing) { _executing = NO; _finished = YES; } } } @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m ================================================ /* * 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h" NS_ASSUME_NONNULL_BEGIN @interface GDTCCTUploader () @property(nonatomic, readonly) NSOperationQueue *uploadOperationQueue; @property(nonatomic, readonly) dispatch_queue_t uploadQueue; @property(nonatomic, readonly) NSMutableDictionary *nextUploadTimeByTarget; @end @implementation GDTCCTUploader static NSURL *_testServerURL = nil; + (void)load { GDTCCTUploader *uploader = [GDTCCTUploader sharedInstance]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCCT]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetFLL]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCSH]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetINT]; } + (instancetype)sharedInstance { static GDTCCTUploader *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[GDTCCTUploader alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _uploadQueue = dispatch_queue_create("com.google.GDTCCTUploader", DISPATCH_QUEUE_SERIAL); _uploadOperationQueue = [[NSOperationQueue alloc] init]; _uploadOperationQueue.maxConcurrentOperationCount = 1; _nextUploadTimeByTarget = [[NSMutableDictionary alloc] init]; } return self; } - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions { // Current GDTCCTUploader expected behaviour: // 1. Accept multiple upload request // 2. Verify if there are events eligible for upload and start upload for the first suitable // target // 3. Ignore other requests while an upload is in-progress. // TODO: Revisit expected behaviour. // Potentially better option: // 1. Accept and enqueue all upload requests // 2. Notify the client of upload stages // 3. Allow the client cancelling upload requests as needed. id storage = GDTCORStoragePromiseInstanceForTarget(target); if (storage == nil) { GDTCORLogError(GDTCORMCEGeneralError, @"Failed to upload target: %ld - could not find corresponding storage instance.", (long)target); return; } GDTCCTUploadOperation *uploadOperation = [[GDTCCTUploadOperation alloc] initWithTarget:target conditions:conditions uploadURL:[[self class] serverURLForTarget:target] queue:self.uploadQueue storage:storage metadataProvider:self]; GDTCORLogDebug(@"Upload operation created: %@, target: %@", uploadOperation, @(target)); __weak __auto_type weakSelf = self; __weak GDTCCTUploadOperation *weakOperation = uploadOperation; uploadOperation.completionBlock = ^{ __auto_type strongSelf = weakSelf; GDTCCTUploadOperation *strongOperation = weakOperation; if (strongSelf == nil || strongOperation == nil) { GDTCORLogDebug(@"Internal inconsistency: GDTCCTUploader was deallocated during upload.", nil); return; } GDTCORLogDebug(@"Upload operation finished: %@, uploadAttempted: %@", strongOperation, @(strongOperation.uploadAttempted)); if (strongOperation.uploadAttempted) { // Ignore all upload requests received when the upload was in progress. [strongSelf.uploadOperationQueue cancelAllOperations]; } }; [self.uploadOperationQueue addOperation:uploadOperation]; GDTCORLogDebug(@"Upload operation scheduled: %@, operation count: %@", uploadOperation, @(self.uploadOperationQueue.operationCount)); } #pragma mark - URLs + (void)setTestServerURL:(NSURL *_Nullable)serverURL { _testServerURL = serverURL; } + (NSURL *_Nullable)testServerURL { return _testServerURL; } + (nullable NSURL *)serverURLForTarget:(GDTCORTarget)target { #if !NDEBUG if (_testServerURL) { return _testServerURL; } #endif // !NDEBUG return [GDTCOREndpoints uploadURLForTarget:target]; } - (NSString *)FLLAndCSHAndINTAPIKey { static NSString *defaultServerKey; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // These strings should be interleaved to construct the real key. const char *p1 = "AzSBG0honD6A-PxV5nBc"; const char *p2 = "Iay44Iwtu2vV0AOrz1C"; const char defaultKey[40] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], '\0'}; defaultServerKey = [NSString stringWithUTF8String:defaultKey]; }); return defaultServerKey; } #pragma mark - GDTCCTUploadMetadataProvider - (nullable GDTCORClock *)nextUploadTimeForTarget:(GDTCORTarget)target { @synchronized(self.nextUploadTimeByTarget) { return self.nextUploadTimeByTarget[@(target)]; } } - (void)setNextUploadTime:(nullable GDTCORClock *)time forTarget:(GDTCORTarget)target { @synchronized(self.nextUploadTimeByTarget) { self.nextUploadTimeByTarget[@(target)] = time; } } - (nullable NSString *)APIKeyForTarget:(GDTCORTarget)target { if (target == kGDTCORTargetFLL || target == kGDTCORTargetCSH) { return [self FLLAndCSHAndINTAPIKey]; } if (target == kGDTCORTargetINT) { return [self FLLAndCSHAndINTAPIKey]; } return nil; } #if !NDEBUG - (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout { NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; while ([expirationDate compare:[NSDate date]] == NSOrderedDescending) { if (self.uploadOperationQueue.operationCount == 0) { return YES; } else { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } } GDTCORLogDebug(@"Uploader wait for finish timeout exceeded. Operations still in queue: %@", self.uploadOperationQueue.operations); return NO; } #endif // !NDEBUG @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m ================================================ /* * 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 "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" NSString *const GDTCCTNeedsNetworkConnectionInfo = @"needs_network_connection_info"; NSString *const GDTCCTNetworkConnectionInfo = @"network_connection_info"; NSString *const GDTCCTEventCodeInfo = @"event_code_info"; @implementation GDTCOREvent (GDTCCTSupport) - (void)setNeedsNetworkConnectionInfoPopulated:(BOOL)needsNetworkConnectionInfoPopulated { if (!needsNetworkConnectionInfoPopulated) { if (!self.customBytes) { return; } // Make sure we don't destroy the eventCode data, if any is present. @try { NSError *error; NSMutableDictionary *bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error] mutableCopy]; if (error) { GDTCORLogDebug(@"Error when setting an event's event_code: %@", error); return; } NSNumber *eventCode = bytesDict[GDTCCTEventCodeInfo]; if (eventCode != nil) { self.customBytes = [NSJSONSerialization dataWithJSONObject:@{GDTCCTEventCodeInfo : eventCode} options:0 error:&error]; } } @catch (NSException *exception) { GDTCORLogDebug(@"Error when setting the event for needs_network_connection_info: %@", exception); } } else { @try { NSError *error; NSMutableDictionary *bytesDict; if (self.customBytes) { bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error] mutableCopy]; if (error) { GDTCORLogDebug(@"Error when setting an even'ts event_code: %@", error); return; } } else { bytesDict = [[NSMutableDictionary alloc] init]; } [bytesDict setObject:@YES forKey:GDTCCTNeedsNetworkConnectionInfo]; self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error]; } @catch (NSException *exception) { GDTCORLogDebug(@"Error when setting the event for needs_network_connection_info: %@", exception); } } } - (BOOL)needsNetworkConnectionInfoPopulated { if (self.customBytes) { @try { NSError *error; NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error]; return bytesDict && !error && [bytesDict[GDTCCTNeedsNetworkConnectionInfo] boolValue]; } @catch (NSException *exception) { GDTCORLogDebug(@"Error when checking the event for needs_network_connection_info: %@", exception); } } return NO; } - (void)setNetworkConnectionInfoData:(NSData *)networkConnectionInfoData { @try { NSError *error; NSString *dataString = [networkConnectionInfoData base64EncodedStringWithOptions:0]; if (dataString != nil) { NSMutableDictionary *bytesDict; if (self.customBytes) { bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error] mutableCopy]; if (error) { GDTCORLogDebug(@"Error when setting an even'ts event_code: %@", error); return; } } else { bytesDict = [[NSMutableDictionary alloc] init]; } [bytesDict setObject:dataString forKey:GDTCCTNetworkConnectionInfo]; self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error]; if (error) { self.customBytes = nil; GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", error); } } } @catch (NSException *exception) { GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", exception); } } - (nullable NSData *)networkConnectionInfoData { if (self.customBytes) { @try { NSError *error; NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error]; NSString *base64Data = bytesDict[GDTCCTNetworkConnectionInfo]; if (base64Data == nil) { return nil; } NSData *networkConnectionInfoData = [[NSData alloc] initWithBase64EncodedString:base64Data options:0]; if (error) { GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", error); return nil; } else { return networkConnectionInfoData; } } @catch (NSException *exception) { GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", exception); } } return nil; } - (NSNumber *)eventCode { if (self.customBytes) { @try { NSError *error; NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error]; NSString *eventCodeString = bytesDict[GDTCCTEventCodeInfo]; if (!eventCodeString) { return nil; } NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; formatter.numberStyle = NSNumberFormatterDecimalStyle; NSNumber *eventCode = [formatter numberFromString:eventCodeString]; if (error) { GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", error); return nil; } else { return eventCode; } } @catch (NSException *exception) { GDTCORLogDebug(@"Error when getting an event's event_code: %@", exception); } } return nil; } - (void)setEventCode:(NSNumber *)eventCode { if (eventCode == nil) { if (!self.customBytes) { return; } NSError *error; NSMutableDictionary *bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error] mutableCopy]; if (error) { GDTCORLogDebug(@"Error when setting an event's event_code: %@", error); return; } [bytesDict removeObjectForKey:GDTCCTEventCodeInfo]; self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error]; if (error) { self.customBytes = nil; GDTCORLogDebug(@"Error when setting an event's event_code: %@", error); return; } return; } @try { NSMutableDictionary *bytesDict; NSError *error; if (self.customBytes) { bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0 error:&error] mutableCopy]; if (error) { GDTCORLogDebug(@"Error when setting an event's event_code: %@", error); return; } } else { bytesDict = [[NSMutableDictionary alloc] init]; } NSString *eventCodeString = [eventCode stringValue]; if (eventCodeString == nil) { return; } [bytesDict setObject:eventCodeString forKey:GDTCCTEventCodeInfo]; self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error]; if (error) { self.customBytes = nil; GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", error); return; } } @catch (NSException *exception) { GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", exception); } } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h ================================================ /* * 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 /** A class with methods to help with gzipped data. */ @interface GDTCCTCompressionHelper : NSObject /** Compresses the given data and returns a new data object. * * @note Reduced version from GULNSData+zlib.m of GoogleUtilities. * @return Compressed data, or nil if there was an error. */ + (nullable NSData *)gzippedData:(NSData *)data; /** Returns YES if the data looks like it was gzip compressed by checking for the gzip magic number. * * @note: From https://en.wikipedia.org/wiki/Gzip, gzip's magic number is 1f 8b. * @return YES if the data appears gzipped, NO otherwise. */ + (BOOL)isGzipped:(NSData *)data; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" NS_ASSUME_NONNULL_BEGIN #pragma mark - General purpose encoders /** Converts an NSString* to a pb_bytes_array_t*. * * @note calloc is called in this method. Ensure that pb_release is called on this or the parent. * * @param string The string to convert. * @return A newly allocated array of bytes representing the UTF8 encoding of the string. */ pb_bytes_array_t *GDTCCTEncodeString(NSString *string); /** Converts an NSData to a pb_bytes_array_t*. * * @note calloc is called in this method. Ensure that pb_release is called on this or the parent. * * @param data The data to convert. * @return A newly allocated array of bytes with [data bytes] copied into it. */ pb_bytes_array_t *GDTCCTEncodeData(NSData *data); #pragma mark - CCT object constructors /** Encodes a batched log request. * * @note Ensure that pb_release is called on the batchedLogRequest param. * * @param batchedLogRequest A pointer to the log batch to encode to bytes. * @return An NSData object representing the bytes of the log request batch. */ FOUNDATION_EXPORT NSData *GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest); /** Constructs a gdt_cct_BatchedLogRequest given sets of events segemented by mapping ID. * * @note calloc is called in this method. Ensure that pb_release is called on this or the parent. * * @param logMappingIDToLogSet A map of mapping IDs to sets of events to convert into a batch. * @return A newly created gdt_cct_BatchedLogRequest. */ FOUNDATION_EXPORT gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest( NSDictionary *> *logMappingIDToLogSet); /** Constructs a log request given a log source and a set of events. * * @note calloc is called in this method. Ensure that pb_release is called on this or the parent. * @param logSource The CCT log source to put into the log request. * @param logSet The set of events to send in this log request. */ FOUNDATION_EXPORT gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, NSSet *logSet); /** Constructs a gdt_cct_LogEvent given a GDTCOREvent*. * * @param event The GDTCOREvent to convert. * @return The new gdt_cct_LogEvent object. */ FOUNDATION_EXPORT gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event); /** Constructs a gdt_cct_ClientInfo representing the client device. * * @return The new gdt_cct_ClientInfo object. */ FOUNDATION_EXPORT gdt_cct_ClientInfo GDTCCTConstructClientInfo(void); /** Constructs a gdt_cct_IosClientInfo representing the client device. * * @return The new gdt_cct_IosClientInfo object. */ FOUNDATION_EXPORT gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void); /** Constructs the data of a gdt_cct_NetworkConnectionInfo representing the client nework connection * information. * * @return The data of a gdt_cct_NetworkConnectionInfo object. */ FOUNDATION_EXPORT NSData *GDTCCTConstructNetworkConnectionInfoData(void); /** Return a gdt_cct_NetworkConnectionInfo_MobileSubtype representing the client * * @return The gdt_cct_NetworkConnectionInfo_MobileSubtype. */ FOUNDATION_EXPORT gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype(void); #pragma mark - CCT object decoders /** Decodes a gdt_cct_LogResponse given proto bytes. * * @note calloc is called in this method. Ensure that pb_release is called on the return value. * * @param data The proto bytes of the gdt_cct_LogResponse. * @param error An error that will be populated if something went wrong during decoding. * @return A newly allocated gdt_cct_LogResponse from the data, if the bytes decoded properly. */ FOUNDATION_EXPORT gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error); NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h" @protocol GDTCORStoragePromiseProtocol; NS_ASSUME_NONNULL_BEGIN /// The protocol defines methods to retrieve/update data shared between different upload operations. @protocol GDTCCTUploadMetadataProvider /** Returns a GDTCORClock object representing time after which a next upload attempt is allowed for * the specified target. Upload is allowed now if `nil`. */ - (nullable GDTCORClock *)nextUploadTimeForTarget:(GDTCORTarget)target; /** Stores or resets time after which a next upload attempt is allowed for the specified target. */ - (void)setNextUploadTime:(nullable GDTCORClock *)time forTarget:(GDTCORTarget)target; /** Returns an API key for the specified target. */ - (nullable NSString *)APIKeyForTarget:(GDTCORTarget)target; @end /** Class capable of uploading events to the CCT backend. */ @interface GDTCCTUploadOperation : NSOperation - (instancetype)init NS_UNAVAILABLE; /** The designated initializer. * @param target The events target to upload. * @param conditions A set of upload conditions. The conditions affect the set of events to be * uploaded, e.g. events with some QoS are not uploaded on a cellular network, etc. * @param uploadURL The backend URL to upload the events. * @param queue A queue to dispatch async upload steps. * @param storage A storage object to fetch events for upload. * @param metadataProvider An object to retrieve/update data shared between different upload * operations. * @return An instance of GDTCCTUploadOperation ready to be added to an NSOperationQueue. */ - (instancetype)initWithTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions uploadURL:(NSURL *)uploadURL queue:(dispatch_queue_t)queue storage:(id)storage metadataProvider:(id)metadataProvider NS_DESIGNATED_INITIALIZER; /** YES if a batch upload attempt was performed. NO otherwise. If NO for the finished operation, * then there were no events suitable for upload. */ @property(nonatomic, readonly) BOOL uploadAttempted; /** The queue on which all CCT uploading will occur. */ @property(nonatomic, readonly) dispatch_queue_t uploaderQueue; /** The current upload task. */ @property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h" NS_ASSUME_NONNULL_BEGIN /** Class capable of uploading events to the CCT backend. */ @interface GDTCCTUploader : NSObject /** Creates and/or returns the singleton instance of this class. * * @return The singleton instance of this class. */ + (instancetype)sharedInstance; #if !NDEBUG /** An upload URL used across all targets. For testing only. */ @property(class, nullable, nonatomic) NSURL *testServerURL; /** Spins runloop until upload finishes or timeout. * @return YES if upload finishes, NO in the case of timeout. */ - (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout; #endif // !NDEBUG @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c ================================================ /* * 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. */ /* Automatically generated nanopb constant definitions */ /* Generated by nanopb-0.3.9.7 */ #include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" /* @@protoc_insertion_point(includes) */ #if PB_PROTO_HEADER_VERSION != 30 #error Regenerate this file with the current version of nanopb generator. #endif const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default = gdt_cct_NetworkConnectionInfo_NetworkType_NONE; const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default = gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE; const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default = gdt_cct_QosTierConfiguration_QosTier_DEFAULT; const int32_t gdt_cct_QosTierConfiguration_log_source_default = 0; const pb_field_t gdt_cct_LogEvent_fields[7] = { PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogEvent, event_time_ms, event_time_ms, 0), PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_LogEvent, source_extension, event_time_ms, 0), PB_FIELD( 11, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_code, source_extension, 0), PB_FIELD( 15, SINT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, timezone_offset_seconds, event_code, 0), PB_FIELD( 17, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_uptime_ms, timezone_offset_seconds, 0), PB_FIELD( 23, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, network_connection_info, event_uptime_ms, &gdt_cct_NetworkConnectionInfo_fields), PB_LAST_FIELD }; const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3] = { PB_FIELD( 1, ENUM , OPTIONAL, STATIC , FIRST, gdt_cct_NetworkConnectionInfo, network_type, network_type, &gdt_cct_NetworkConnectionInfo_network_type_default), PB_FIELD( 2, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_NetworkConnectionInfo, mobile_subtype, network_type, &gdt_cct_NetworkConnectionInfo_mobile_subtype_default), PB_LAST_FIELD }; const pb_field_t gdt_cct_IosClientInfo_fields[8] = { PB_FIELD( 3, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_IosClientInfo, os_major_version, os_major_version, 0), PB_FIELD( 4, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, os_full_version, os_major_version, 0), PB_FIELD( 5, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_build, os_full_version, 0), PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, country, application_build, 0), PB_FIELD( 7, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, model, country, 0), PB_FIELD( 8, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, language_code, model, 0), PB_FIELD( 11, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_bundle_id, language_code, 0), PB_LAST_FIELD }; const pb_field_t gdt_cct_ClientInfo_fields[3] = { PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_ClientInfo, client_type, client_type, 0), PB_FIELD( 4, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, ios_client_info, client_type, &gdt_cct_IosClientInfo_fields), PB_LAST_FIELD }; const pb_field_t gdt_cct_BatchedLogRequest_fields[2] = { PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_BatchedLogRequest, log_request, log_request, &gdt_cct_LogRequest_fields), PB_LAST_FIELD }; const pb_field_t gdt_cct_LogRequest_fields[7] = { PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, gdt_cct_LogRequest, client_info, client_info, &gdt_cct_ClientInfo_fields), PB_FIELD( 2, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, log_source, client_info, 0), PB_FIELD( 3, MESSAGE , REPEATED, POINTER , OTHER, gdt_cct_LogRequest, log_event, log_source, &gdt_cct_LogEvent_fields), PB_FIELD( 4, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_time_ms, log_event, 0), PB_FIELD( 8, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_uptime_ms, request_time_ms, 0), PB_FIELD( 9, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, qos_tier, request_uptime_ms, &gdt_cct_LogRequest_qos_tier_default), PB_LAST_FIELD }; const pb_field_t gdt_cct_QosTierConfiguration_fields[3] = { PB_FIELD( 2, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_QosTierConfiguration, qos_tier, qos_tier, 0), PB_FIELD( 3, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTierConfiguration, log_source, qos_tier, &gdt_cct_QosTierConfiguration_log_source_default), PB_LAST_FIELD }; const pb_field_t gdt_cct_QosTiersOverride_fields[3] = { PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_QosTiersOverride, qos_tier_configuration, qos_tier_configuration, &gdt_cct_QosTierConfiguration_fields), PB_FIELD( 2, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTiersOverride, qos_tier_fingerprint, qos_tier_configuration, 0), PB_LAST_FIELD }; const pb_field_t gdt_cct_LogResponse_fields[3] = { PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogResponse, next_request_wait_millis, next_request_wait_millis, 0), PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogResponse, qos_tier, next_request_wait_millis, &gdt_cct_QosTiersOverride_fields), PB_LAST_FIELD }; /* Check that field information fits in pb_field_t */ #if !defined(PB_FIELD_32BIT) /* If you get an error here, it means that you need to define PB_FIELD_32BIT * compile-time option. You can do that in pb.h or on compiler command line. * * The reason you need to do this is that some of your messages contain tag * numbers or field sizes that are larger than what can fit in 8 or 16 bit * field descriptors. */ PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 65536 && pb_membersize(gdt_cct_LogRequest, client_info) < 65536 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) #endif #if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) /* If you get an error here, it means that you need to define PB_FIELD_16BIT * compile-time option. You can do that in pb.h or on compiler command line. * * The reason you need to do this is that some of your messages contain tag * numbers or field sizes that are larger than what can fit in the default * 8 bit descriptors. */ PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 256 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 256 && pb_membersize(gdt_cct_LogRequest, client_info) < 256 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) #endif /* @@protoc_insertion_point(eof) */ ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h ================================================ /* * 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. */ /* Automatically generated nanopb header */ /* Generated by nanopb-0.3.9.7 */ #ifndef PB_GDT_CCT_CCT_NANOPB_H_INCLUDED #define PB_GDT_CCT_CCT_NANOPB_H_INCLUDED #include /* @@protoc_insertion_point(includes) */ #if PB_PROTO_HEADER_VERSION != 30 #error Regenerate this file with the current version of nanopb generator. #endif /* Enum definitions */ typedef enum _gdt_cct_NetworkConnectionInfo_NetworkType { gdt_cct_NetworkConnectionInfo_NetworkType_NONE = -1, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE = 0, gdt_cct_NetworkConnectionInfo_NetworkType_WIFI = 1, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_MMS = 2, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_SUPL = 3, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_DUN = 4, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_HIPRI = 5, gdt_cct_NetworkConnectionInfo_NetworkType_WIMAX = 6, gdt_cct_NetworkConnectionInfo_NetworkType_BLUETOOTH = 7, gdt_cct_NetworkConnectionInfo_NetworkType_DUMMY = 8, gdt_cct_NetworkConnectionInfo_NetworkType_ETHERNET = 9, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_FOTA = 10, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IMS = 11, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_CBS = 12, gdt_cct_NetworkConnectionInfo_NetworkType_WIFI_P2P = 13, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IA = 14, gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_EMERGENCY = 15, gdt_cct_NetworkConnectionInfo_NetworkType_PROXY = 16, gdt_cct_NetworkConnectionInfo_NetworkType_VPN = 17 } gdt_cct_NetworkConnectionInfo_NetworkType; #define _gdt_cct_NetworkConnectionInfo_NetworkType_MIN gdt_cct_NetworkConnectionInfo_NetworkType_NONE #define _gdt_cct_NetworkConnectionInfo_NetworkType_MAX gdt_cct_NetworkConnectionInfo_NetworkType_VPN #define _gdt_cct_NetworkConnectionInfo_NetworkType_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_NetworkType)(gdt_cct_NetworkConnectionInfo_NetworkType_VPN+1)) typedef enum _gdt_cct_NetworkConnectionInfo_MobileSubtype { gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE = 0, gdt_cct_NetworkConnectionInfo_MobileSubtype_GPRS = 1, gdt_cct_NetworkConnectionInfo_MobileSubtype_EDGE = 2, gdt_cct_NetworkConnectionInfo_MobileSubtype_UMTS = 3, gdt_cct_NetworkConnectionInfo_MobileSubtype_CDMA = 4, gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_0 = 5, gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_A = 6, gdt_cct_NetworkConnectionInfo_MobileSubtype_RTT = 7, gdt_cct_NetworkConnectionInfo_MobileSubtype_HSDPA = 8, gdt_cct_NetworkConnectionInfo_MobileSubtype_HSUPA = 9, gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPA = 10, gdt_cct_NetworkConnectionInfo_MobileSubtype_IDEN = 11, gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_B = 12, gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE = 13, gdt_cct_NetworkConnectionInfo_MobileSubtype_EHRPD = 14, gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPAP = 15, gdt_cct_NetworkConnectionInfo_MobileSubtype_GSM = 16, gdt_cct_NetworkConnectionInfo_MobileSubtype_TD_SCDMA = 17, gdt_cct_NetworkConnectionInfo_MobileSubtype_IWLAN = 18, gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE_CA = 19, gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED = 100 } gdt_cct_NetworkConnectionInfo_MobileSubtype; #define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE #define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MAX gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED #define _gdt_cct_NetworkConnectionInfo_MobileSubtype_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_MobileSubtype)(gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED+1)) typedef enum _gdt_cct_ClientInfo_ClientType { gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN = 0, gdt_cct_ClientInfo_ClientType_IOS_FIREBASE = 15 } gdt_cct_ClientInfo_ClientType; #define _gdt_cct_ClientInfo_ClientType_MIN gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN #define _gdt_cct_ClientInfo_ClientType_MAX gdt_cct_ClientInfo_ClientType_IOS_FIREBASE #define _gdt_cct_ClientInfo_ClientType_ARRAYSIZE ((gdt_cct_ClientInfo_ClientType)(gdt_cct_ClientInfo_ClientType_IOS_FIREBASE+1)) typedef enum _gdt_cct_QosTierConfiguration_QosTier { gdt_cct_QosTierConfiguration_QosTier_DEFAULT = 0, gdt_cct_QosTierConfiguration_QosTier_UNMETERED_ONLY = 1, gdt_cct_QosTierConfiguration_QosTier_UNMETERED_OR_DAILY = 2, gdt_cct_QosTierConfiguration_QosTier_FAST_IF_RADIO_AWAKE = 3, gdt_cct_QosTierConfiguration_QosTier_NEVER = 4 } gdt_cct_QosTierConfiguration_QosTier; #define _gdt_cct_QosTierConfiguration_QosTier_MIN gdt_cct_QosTierConfiguration_QosTier_DEFAULT #define _gdt_cct_QosTierConfiguration_QosTier_MAX gdt_cct_QosTierConfiguration_QosTier_NEVER #define _gdt_cct_QosTierConfiguration_QosTier_ARRAYSIZE ((gdt_cct_QosTierConfiguration_QosTier)(gdt_cct_QosTierConfiguration_QosTier_NEVER+1)) /* Struct definitions */ typedef struct _gdt_cct_BatchedLogRequest { pb_size_t log_request_count; struct _gdt_cct_LogRequest *log_request; /* @@protoc_insertion_point(struct:gdt_cct_BatchedLogRequest) */ } gdt_cct_BatchedLogRequest; typedef struct _gdt_cct_IosClientInfo { pb_bytes_array_t *os_major_version; pb_bytes_array_t *os_full_version; pb_bytes_array_t *application_build; pb_bytes_array_t *country; pb_bytes_array_t *model; pb_bytes_array_t *language_code; pb_bytes_array_t *application_bundle_id; /* @@protoc_insertion_point(struct:gdt_cct_IosClientInfo) */ } gdt_cct_IosClientInfo; typedef struct _gdt_cct_ClientInfo { bool has_client_type; gdt_cct_ClientInfo_ClientType client_type; bool has_ios_client_info; gdt_cct_IosClientInfo ios_client_info; /* @@protoc_insertion_point(struct:gdt_cct_ClientInfo) */ } gdt_cct_ClientInfo; typedef struct _gdt_cct_NetworkConnectionInfo { bool has_network_type; gdt_cct_NetworkConnectionInfo_NetworkType network_type; bool has_mobile_subtype; gdt_cct_NetworkConnectionInfo_MobileSubtype mobile_subtype; /* @@protoc_insertion_point(struct:gdt_cct_NetworkConnectionInfo) */ } gdt_cct_NetworkConnectionInfo; typedef struct _gdt_cct_QosTierConfiguration { bool has_qos_tier; gdt_cct_QosTierConfiguration_QosTier qos_tier; bool has_log_source; int32_t log_source; /* @@protoc_insertion_point(struct:gdt_cct_QosTierConfiguration) */ } gdt_cct_QosTierConfiguration; typedef struct _gdt_cct_QosTiersOverride { pb_size_t qos_tier_configuration_count; struct _gdt_cct_QosTierConfiguration *qos_tier_configuration; bool has_qos_tier_fingerprint; int64_t qos_tier_fingerprint; /* @@protoc_insertion_point(struct:gdt_cct_QosTiersOverride) */ } gdt_cct_QosTiersOverride; typedef struct _gdt_cct_LogEvent { bool has_event_time_ms; int64_t event_time_ms; pb_bytes_array_t *source_extension; bool has_event_code; int32_t event_code; bool has_timezone_offset_seconds; int64_t timezone_offset_seconds; bool has_event_uptime_ms; int64_t event_uptime_ms; bool has_network_connection_info; gdt_cct_NetworkConnectionInfo network_connection_info; /* @@protoc_insertion_point(struct:gdt_cct_LogEvent) */ } gdt_cct_LogEvent; typedef struct _gdt_cct_LogRequest { bool has_client_info; gdt_cct_ClientInfo client_info; bool has_log_source; int32_t log_source; pb_size_t log_event_count; struct _gdt_cct_LogEvent *log_event; bool has_request_time_ms; int64_t request_time_ms; bool has_request_uptime_ms; int64_t request_uptime_ms; bool has_qos_tier; gdt_cct_QosTierConfiguration_QosTier qos_tier; /* @@protoc_insertion_point(struct:gdt_cct_LogRequest) */ } gdt_cct_LogRequest; typedef struct _gdt_cct_LogResponse { bool has_next_request_wait_millis; int64_t next_request_wait_millis; bool has_qos_tier; gdt_cct_QosTiersOverride qos_tier; /* @@protoc_insertion_point(struct:gdt_cct_LogResponse) */ } gdt_cct_LogResponse; /* Default values for struct fields */ extern const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default; extern const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default; extern const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default; extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; /* Initializer values for message structs */ #define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default} #define gdt_cct_NetworkConnectionInfo_init_default {false, gdt_cct_NetworkConnectionInfo_NetworkType_NONE, false, gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE} #define gdt_cct_IosClientInfo_init_default {NULL, NULL, NULL, NULL, NULL, NULL, NULL} #define gdt_cct_ClientInfo_init_default {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_default} #define gdt_cct_BatchedLogRequest_init_default {0, NULL} #define gdt_cct_LogRequest_init_default {false, gdt_cct_ClientInfo_init_default, false, 0, 0, NULL, false, 0, false, 0, false, gdt_cct_QosTierConfiguration_QosTier_DEFAULT} #define gdt_cct_QosTierConfiguration_init_default {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} #define gdt_cct_QosTiersOverride_init_default {0, NULL, false, 0} #define gdt_cct_LogResponse_init_default {false, 0, false, gdt_cct_QosTiersOverride_init_default} #define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero} #define gdt_cct_NetworkConnectionInfo_init_zero {false, _gdt_cct_NetworkConnectionInfo_NetworkType_MIN, false, _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN} #define gdt_cct_IosClientInfo_init_zero {NULL, NULL, NULL, NULL, NULL, NULL, NULL} #define gdt_cct_ClientInfo_init_zero {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_zero} #define gdt_cct_BatchedLogRequest_init_zero {0, NULL} #define gdt_cct_LogRequest_init_zero {false, gdt_cct_ClientInfo_init_zero, false, 0, 0, NULL, false, 0, false, 0, false, _gdt_cct_QosTierConfiguration_QosTier_MIN} #define gdt_cct_QosTierConfiguration_init_zero {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} #define gdt_cct_QosTiersOverride_init_zero {0, NULL, false, 0} #define gdt_cct_LogResponse_init_zero {false, 0, false, gdt_cct_QosTiersOverride_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define gdt_cct_BatchedLogRequest_log_request_tag 1 #define gdt_cct_IosClientInfo_os_major_version_tag 3 #define gdt_cct_IosClientInfo_os_full_version_tag 4 #define gdt_cct_IosClientInfo_application_build_tag 5 #define gdt_cct_IosClientInfo_country_tag 6 #define gdt_cct_IosClientInfo_model_tag 7 #define gdt_cct_IosClientInfo_language_code_tag 8 #define gdt_cct_IosClientInfo_application_bundle_id_tag 11 #define gdt_cct_ClientInfo_client_type_tag 1 #define gdt_cct_ClientInfo_ios_client_info_tag 4 #define gdt_cct_NetworkConnectionInfo_network_type_tag 1 #define gdt_cct_NetworkConnectionInfo_mobile_subtype_tag 2 #define gdt_cct_QosTierConfiguration_qos_tier_tag 2 #define gdt_cct_QosTierConfiguration_log_source_tag 3 #define gdt_cct_QosTiersOverride_qos_tier_configuration_tag 1 #define gdt_cct_QosTiersOverride_qos_tier_fingerprint_tag 2 #define gdt_cct_LogEvent_event_time_ms_tag 1 #define gdt_cct_LogEvent_event_code_tag 11 #define gdt_cct_LogEvent_event_uptime_ms_tag 17 #define gdt_cct_LogEvent_source_extension_tag 6 #define gdt_cct_LogEvent_timezone_offset_seconds_tag 15 #define gdt_cct_LogEvent_network_connection_info_tag 23 #define gdt_cct_LogRequest_request_time_ms_tag 4 #define gdt_cct_LogRequest_request_uptime_ms_tag 8 #define gdt_cct_LogRequest_client_info_tag 1 #define gdt_cct_LogRequest_log_source_tag 2 #define gdt_cct_LogRequest_log_event_tag 3 #define gdt_cct_LogRequest_qos_tier_tag 9 #define gdt_cct_LogResponse_next_request_wait_millis_tag 1 #define gdt_cct_LogResponse_qos_tier_tag 3 /* Struct field encoding specification for nanopb */ extern const pb_field_t gdt_cct_LogEvent_fields[7]; extern const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3]; extern const pb_field_t gdt_cct_IosClientInfo_fields[8]; extern const pb_field_t gdt_cct_ClientInfo_fields[3]; extern const pb_field_t gdt_cct_BatchedLogRequest_fields[2]; extern const pb_field_t gdt_cct_LogRequest_fields[7]; extern const pb_field_t gdt_cct_QosTierConfiguration_fields[3]; extern const pb_field_t gdt_cct_QosTiersOverride_fields[3]; extern const pb_field_t gdt_cct_LogResponse_fields[3]; /* Maximum encoded size of messages (where known) */ /* gdt_cct_LogEvent_size depends on runtime parameters */ #define gdt_cct_NetworkConnectionInfo_size 13 /* gdt_cct_IosClientInfo_size depends on runtime parameters */ /* gdt_cct_ClientInfo_size depends on runtime parameters */ /* gdt_cct_BatchedLogRequest_size depends on runtime parameters */ /* gdt_cct_LogRequest_size depends on runtime parameters */ #define gdt_cct_QosTierConfiguration_size 13 /* gdt_cct_QosTiersOverride_size depends on runtime parameters */ /* gdt_cct_LogResponse_size depends on runtime parameters */ /* Message IDs (where set with "msgid" option) */ #ifdef PB_MSGID #define CCT_MESSAGES \ #endif /* @@protoc_insertion_point(eof) */ #endif ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" NS_ASSUME_NONNULL_BEGIN /** A string sets in customBytes as a key paired to @YES if current event needs to * populate network connection info data, @NO otherwise. */ FOUNDATION_EXPORT NSString *const GDTCCTNeedsNetworkConnectionInfo; /** A string sets in customBytes as a key paired to the network connection info data * of current event. */ FOUNDATION_EXPORT NSString *const GDTCCTNetworkConnectionInfo; /** A category that uses the customBytes property of a GDTCOREvent to store network connection info. */ @interface GDTCOREvent (GDTCCTSupport) /** If YES, needs the network connection info field set during prioritization. * @note Uses the GDTCOREvent customBytes property. */ @property(nonatomic) BOOL needsNetworkConnectionInfoPopulated; /** The network connection info as collected at the time of the event. * @note Uses the GDTCOREvent customBytes property. */ @property(nullable, nonatomic) NSData *networkConnectionInfoData; /** Code that identifies the event to be sent to the CCT backend. */ @property(nullable, nonatomic) NSNumber *eventCode; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) { // This class is only compiled in by unit tests, and this should fail quickly in optimized builds. Class GDTCORAssertClass = NSClassFromString(@"GDTCORAssertHelper"); if (__builtin_expect(!!GDTCORAssertClass, 0)) { SEL assertionBlockSEL = NSSelectorFromString(@"assertionBlock"); if (assertionBlockSEL) { IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL]; if (assertionBlockIMP) { GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDTCORAssertClass, assertionBlockSEL); if (assertionBlock) { return assertionBlock; } } } } return NULL; } ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a // time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external // factors. This class attempts to determine the wall-clock time at the time of the event by // capturing the kernel start and time since boot to determine a wallclock time in UTC. // // Timezone offsets at the time of a snapshot are also captured in order to provide local-time // details. Other classes in this library depend on comparing times at some time in the future to // a time captured in the past, and this class needs to provide a mechanism to do that. // // TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide // a monotonic clock mechanism to accurately check if some clock snapshot was before or after // by using a shared reference point (kernel boot time). // // Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be // difficult to unit test. /** Returns the kernel boottime property from sysctl. * * Inspired by https://stackoverflow.com/a/40497811 * * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. */ static int64_t KernelBootTimeInNanoseconds() { // Caching the result is not possible because clock drift would not be accounted for. struct timeval boottime; int mib[2] = {CTL_KERN, KERN_BOOTTIME}; size_t size = sizeof(boottime); int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); if (rc != 0) { return 0; } return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; } /** Returns value of gettimeofday, in nanoseconds. * * Inspired by https://stackoverflow.com/a/40497811 * * @return The value of gettimeofday, in nanoseconds. */ static int64_t UptimeInNanoseconds() { int64_t before_now_nsec; int64_t after_now_nsec; struct timeval now; before_now_nsec = KernelBootTimeInNanoseconds(); // Addresses a race condition in which the system time has updated, but the boottime has not. do { gettimeofday(&now, NULL); after_now_nsec = KernelBootTimeInNanoseconds(); } while (after_now_nsec != before_now_nsec); return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - before_now_nsec; } // TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. @implementation GDTCORClock - (instancetype)init { self = [super init]; if (self) { _kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds(); _uptimeNanoseconds = UptimeInNanoseconds(); _timeMillis = (int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC); _timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT]; } return self; } + (GDTCORClock *)snapshot { return [[GDTCORClock alloc] init]; } + (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture { GDTCORClock *snapshot = [self snapshot]; snapshot->_timeMillis += millisInTheFuture; return snapshot; } - (BOOL)isAfter:(GDTCORClock *)otherClock { // These clocks are trivially comparable when they share a kernel boot time. if (_kernelBootTimeNanoseconds == otherClock->_kernelBootTimeNanoseconds) { int64_t timeDiff = (_timeMillis + _timezoneOffsetSeconds) - (otherClock->_timeMillis + otherClock->_timezoneOffsetSeconds); return timeDiff > 0; } else { int64_t kernelBootTimeDiff = otherClock->_kernelBootTimeNanoseconds - _kernelBootTimeNanoseconds; // This isn't a great solution, but essentially, if the other clock's boot time is 'later', NO // is returned. This can be altered by changing the system time and rebooting. return kernelBootTimeDiff < 0 ? YES : NO; } } - (int64_t)uptimeMilliseconds { return self.uptimeNanoseconds / NSEC_PER_MSEC; } - (NSUInteger)hash { return [@(_kernelBootTimeNanoseconds) hash] ^ [@(_uptimeNanoseconds) hash] ^ [@(_timeMillis) hash] ^ [@(_timezoneOffsetSeconds) hash]; } - (BOOL)isEqual:(id)object { return [self hash] == [object hash]; } #pragma mark - NSSecureCoding /** NSKeyedCoder key for timeMillis property. */ static NSString *const kGDTCORClockTimeMillisKey = @"GDTCORClockTimeMillis"; /** NSKeyedCoder key for timezoneOffsetMillis property. */ static NSString *const kGDTCORClockTimezoneOffsetSeconds = @"GDTCORClockTimezoneOffsetSeconds"; /** NSKeyedCoder key for _kernelBootTime ivar. */ static NSString *const kGDTCORClockKernelBootTime = @"GDTCORClockKernelBootTime"; /** NSKeyedCoder key for _uptimeNanoseconds ivar. */ static NSString *const kGDTCORClockUptime = @"GDTCORClockUptime"; + (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { // TODO: If the kernelBootTimeNanoseconds is more recent, we need to change the kernel boot time // and uptimeMillis ivars _timeMillis = [aDecoder decodeInt64ForKey:kGDTCORClockTimeMillisKey]; _timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTCORClockTimezoneOffsetSeconds]; _kernelBootTimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockKernelBootTime]; _uptimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockUptime]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeInt64:_timeMillis forKey:kGDTCORClockTimeMillisKey]; [aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTCORClockTimezoneOffsetSeconds]; [aCoder encodeInt64:_kernelBootTimeNanoseconds forKey:kGDTCORClockKernelBootTime]; [aCoder encodeInt64:_uptimeNanoseconds forKey:kGDTCORClockUptime]; } #pragma mark - Deprecated properties - (int64_t)kernelBootTime { return self.kernelBootTimeNanoseconds; } - (int64_t)uptime { return self.uptimeNanoseconds; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" volatile NSInteger GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelErrors; /** The console logger prefix. */ static NSString *kGDTCORConsoleLogger = @"[GoogleDataTransport]"; NSString *GDTCORMessageCodeEnumToString(GDTCORMessageCode code) { return [[NSString alloc] initWithFormat:@"I-GDTCOR%06ld", (long)code]; } void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *format, ...) { // Don't log anything in not debug builds. #if !NDEBUG if (logLevel >= GDTCORConsoleLoggerLoggingLevel) { NSString *logFormat = [NSString stringWithFormat:@"%@[%@] %@", kGDTCORConsoleLogger, GDTCORMessageCodeEnumToString(code), format]; va_list args; va_start(args, format); NSLogv(logFormat, args); va_end(args); } #endif // !NDEBUG } void GDTCORLogAssert( BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) { // Don't log anything in not debug builds. #if !NDEBUG GDTCORMessageCode code = wasFatal ? GDTCORMCEFatalAssertion : GDTCORMCEGeneralError; NSString *logFormat = [NSString stringWithFormat:@"%@[%@] (%@:%ld) : %@", kGDTCORConsoleLogger, GDTCORMessageCodeEnumToString(code), file, (long)line, format]; va_list args; va_start(args, format); NSLogv(logFormat, args); va_end(args); #endif // !NDEBUG } ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h" @interface GDTCORDirectorySizeTracker () /** The observed directory path. */ @property(nonatomic, readonly) NSString *directoryPath; /** The cached content size of the observed directory. */ @property(nonatomic, nullable) NSNumber *cachedSizeBytes; @end @implementation GDTCORDirectorySizeTracker - (instancetype)initWithDirectoryPath:(NSString *)path { self = [super init]; if (self) { _directoryPath = path; } return self; } - (GDTCORStorageSizeBytes)directoryContentSize { if (self.cachedSizeBytes == nil) { self.cachedSizeBytes = @([self calculateDirectoryContentSize]); } return self.cachedSizeBytes.unsignedLongLongValue; } - (void)fileWasAddedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize { if (![path hasPrefix:self.directoryPath]) { // Ignore because the file is not inside the directory. return; } self.cachedSizeBytes = @([self directoryContentSize] + fileSize); } - (void)fileWasRemovedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize { if (![path hasPrefix:self.directoryPath]) { // Ignore because the file is not inside the directory. return; } self.cachedSizeBytes = @([self directoryContentSize] - fileSize); } - (void)resetCachedSize { self.cachedSizeBytes = nil; } - (GDTCORStorageSizeBytes)calculateDirectoryContentSize { NSArray *prefetchedProperties = @[ NSURLIsRegularFileKey, NSURLFileSizeKey ]; uint64_t totalBytes = 0; NSURL *directoryURL = [NSURL fileURLWithPath:self.directoryPath]; NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL includingPropertiesForKeys:prefetchedProperties options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:^BOOL(NSURL *_Nonnull url, NSError *_Nonnull error) { return YES; }]; for (NSURL *fileURL in enumerator) { @autoreleasepool { NSNumber *isRegularFile; [fileURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:nil]; if (isRegularFile.boolValue) { totalBytes += [self fileSizeAtURL:fileURL]; } } } return totalBytes; } - (GDTCORStorageSizeBytes)fileSizeAtURL:(NSURL *)fileURL { NSNumber *fileSize; [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil]; return fileSize.unsignedLongLongValue; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h" static NSString *const kINTServerURL = @"https://dummyapiverylong-dummy.dummy.com/dummy/api/very/long"; @implementation GDTCOREndpoints + (NSDictionary *)uploadURLs { // These strings should be interleaved to construct the real URL. This is just to (hopefully) // fool github URL scanning bots. static NSURL *CCTServerURL; static dispatch_once_t CCTOnceToken; dispatch_once(&CCTOnceToken, ^{ const char *p1 = "hts/frbslgiggolai.o/0clgbth"; const char *p2 = "tp:/ieaeogn.ogepscmvc/o/ac"; const char URL[54] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22], p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], '\0'}; CCTServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]]; }); static NSURL *FLLServerURL; static dispatch_once_t FLLOnceToken; dispatch_once(&FLLOnceToken, ^{ const char *p1 = "hts/frbslgigp.ogepscmv/ieo/eaybtho"; const char *p2 = "tp:/ieaeogn-agolai.o/1frlglgc/aclg"; const char URL[69] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22], p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], p2[26], p1[27], p2[27], p1[28], p2[28], p1[29], p2[29], p1[30], p2[30], p1[31], p2[31], p1[32], p2[32], p1[33], p2[33], '\0'}; FLLServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]]; }); static NSURL *CSHServerURL; static dispatch_once_t CSHOnceToken; dispatch_once(&CSHOnceToken, ^{ // These strings should be interleaved to construct the real URL. This is just to (hopefully) // fool github URL scanning bots. const char *p1 = "hts/cahyiseot-agolai.o/1frlglgc/aclg"; const char *p2 = "tp:/rsltcrprsp.ogepscmv/ieo/eaybtho"; const char URL[72] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22], p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], p2[26], p1[27], p2[27], p1[28], p2[28], p1[29], p2[29], p1[30], p2[30], p1[31], p2[31], p1[32], p2[32], p1[33], p2[33], p1[34], p2[34], p1[35], '\0'}; CSHServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]]; }); static NSDictionary *uploadURLs; static dispatch_once_t URLOnceToken; dispatch_once(&URLOnceToken, ^{ uploadURLs = @{ @(kGDTCORTargetCCT) : CCTServerURL, @(kGDTCORTargetFLL) : FLLServerURL, @(kGDTCORTargetCSH) : CSHServerURL, @(kGDTCORTargetINT) : [NSURL URLWithString:kINTServerURL] }; }); return uploadURLs; } + (nullable NSURL *)uploadURLForTarget:(GDTCORTarget)target { NSDictionary *URLs = [self uploadURLs]; return [URLs objectForKey:@(target)]; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h" @implementation GDTCOREvent + (NSString *)nextEventID { // Replace special non-alphanumeric characters to avoid potential conflicts with storage logic. return [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""]; } - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target { GDTCORAssert(mappingID.length > 0, @"Please give a valid mapping ID"); GDTCORAssert(target > 0, @"A target cannot be negative or 0"); if (mappingID.length == 0 || target <= 0) { return nil; } self = [super init]; if (self) { _eventID = [GDTCOREvent nextEventID]; _mappingID = mappingID; _target = target; _qosTier = GDTCOREventQosDefault; _expirationDate = [NSDate dateWithTimeIntervalSinceNow:604800]; // 7 days. GDTCORLogDebug(@"Event %@ created. ID:%@ mappingID: %@ target:%ld", self, _eventID, mappingID, (long)target); } return self; } - (instancetype)copy { GDTCOREvent *copy = [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; copy->_eventID = _eventID; copy.dataObject = _dataObject; copy.qosTier = _qosTier; copy.clockSnapshot = _clockSnapshot; copy.customBytes = _customBytes; GDTCORLogDebug(@"Copying event %@ to event %@", self, copy); return copy; } - (NSUInteger)hash { // This loses some precision, but it's probably fine. NSUInteger eventIDHash = [_eventID hash]; NSUInteger mappingIDHash = [_mappingID hash]; NSUInteger timeHash = [_clockSnapshot hash]; NSInteger serializedBytesHash = [_serializedDataObjectBytes hash]; return eventIDHash ^ mappingIDHash ^ _target ^ _qosTier ^ timeHash ^ serializedBytesHash; } - (BOOL)isEqual:(id)object { return [self hash] == [object hash]; } #pragma mark - Property overrides - (void)setDataObject:(id)dataObject { // If you're looking here because of a performance issue in -transportBytes slowing the assignment // of -dataObject, one way to address this is to add a queue to this class, // dispatch_(barrier_ if concurrent)async here, and implement the getter with a dispatch_sync. if (dataObject != _dataObject) { _dataObject = dataObject; } self->_serializedDataObjectBytes = [dataObject transportBytes]; } #pragma mark - NSSecureCoding and NSCoding Protocols /** NSCoding key for eventID property. */ static NSString *kEventIDKey = @"GDTCOREventEventIDKey"; /** NSCoding key for mappingID property. */ static NSString *kMappingIDKey = @"GDTCOREventMappingIDKey"; /** NSCoding key for target property. */ static NSString *kTargetKey = @"GDTCOREventTargetKey"; /** NSCoding key for qosTier property. */ static NSString *kQoSTierKey = @"GDTCOREventQoSTierKey"; /** NSCoding key for clockSnapshot property. */ static NSString *kClockSnapshotKey = @"GDTCOREventClockSnapshotKey"; /** NSCoding key for expirationDate property. */ static NSString *kExpirationDateKey = @"GDTCOREventExpirationDateKey"; /** NSCoding key for serializedDataObjectBytes property. */ static NSString *kSerializedDataObjectBytes = @"GDTCOREventSerializedDataObjectBytesKey"; /** NSCoding key for customData property. */ static NSString *kCustomDataKey = @"GDTCOREventCustomDataKey"; + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [self init]; if (self) { _mappingID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kMappingIDKey]; _target = [aDecoder decodeIntegerForKey:kTargetKey]; _eventID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kEventIDKey] ?: [GDTCOREvent nextEventID]; _qosTier = [aDecoder decodeIntegerForKey:kQoSTierKey]; _clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:kClockSnapshotKey]; _customBytes = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCustomDataKey]; _expirationDate = [aDecoder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey]; _serializedDataObjectBytes = [aDecoder decodeObjectOfClass:[NSData class] forKey:kSerializedDataObjectBytes]; if (!_serializedDataObjectBytes) { return nil; } } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_eventID forKey:kEventIDKey]; [aCoder encodeObject:_mappingID forKey:kMappingIDKey]; [aCoder encodeInteger:_target forKey:kTargetKey]; [aCoder encodeInteger:_qosTier forKey:kQoSTierKey]; [aCoder encodeObject:_clockSnapshot forKey:kClockSnapshotKey]; [aCoder encodeObject:_customBytes forKey:kCustomDataKey]; [aCoder encodeObject:_expirationDate forKey:kExpirationDateKey]; [aCoder encodeObject:self.serializedDataObjectBytes forKey:kSerializedDataObjectBytes]; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" #if __has_include() #import #else #import "FBLPromises.h" #endif #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" @implementation GDTCORFlatFileStorage (Promises) - (FBLPromise *> *)batchIDsForTarget:(GDTCORTarget)target { return [FBLPromise onQueue:self.storageQueue wrapObjectCompletion:^(FBLPromiseObjectCompletion _Nonnull handler) { [self batchIDsForTarget:target onComplete:handler]; }]; } - (FBLPromise *)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents { return [FBLPromise onQueue:self.storageQueue wrapCompletion:^(FBLPromiseCompletion _Nonnull handler) { [self removeBatchWithID:batchID deleteEvents:deleteEvents onComplete:handler]; }]; } - (FBLPromise *)removeBatchesWithIDs:(NSSet *)batchIDs deleteEvents:(BOOL)deleteEvents { NSMutableArray *removeBatchPromises = [NSMutableArray arrayWithCapacity:batchIDs.count]; for (NSNumber *batchID in batchIDs) { [removeBatchPromises addObject:[self removeBatchWithID:batchID deleteEvents:deleteEvents]]; } return [FBLPromise onQueue:self.storageQueue all:[removeBatchPromises copy]].thenOn( self.storageQueue, ^id(id result) { return [FBLPromise resolvedWith:[NSNull null]]; }); } - (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target deleteEvents:(BOOL)deleteEvents { return [self batchIDsForTarget:target].thenOn(self.storageQueue, ^id(NSSet *batchIDs) { if (batchIDs.count == 0) { return [FBLPromise resolvedWith:[NSNull null]]; } else { return [self removeBatchesWithIDs:batchIDs deleteEvents:NO]; } }); } - (FBLPromise *)hasEventsForTarget:(GDTCORTarget)target { return [FBLPromise onQueue:self.storageQueue wrapBoolCompletion:^(FBLPromiseBoolCompletion _Nonnull handler) { [self hasEventsForTarget:target onComplete:handler]; }]; } - (FBLPromise *)batchWithEventSelector: (GDTCORStorageEventSelector *)eventSelector batchExpiration:(NSDate *)expiration { return [FBLPromise onQueue:self.storageQueue async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) { [self batchWithEventSelector:eventSelector batchExpiration:expiration onComplete:^(NSNumber *_Nullable newBatchID, NSSet *_Nullable batchEvents) { if (newBatchID == nil || batchEvents == nil) { reject([self genericRejectedPromiseErrorWithReason: @"There are no events for the selector."]); } else { fulfill([[GDTCORUploadBatch alloc] initWithBatchID:newBatchID events:batchEvents]); } }]; }]; } // TODO: Move to a separate class/extension when needed in more places. - (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { return [NSError errorWithDomain:@"GDTCORFlatFileStorage" code:-1 userInfo:@{NSLocalizedFailureReasonErrorKey : reason}]; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h" NS_ASSUME_NONNULL_BEGIN /** A library data key this class uses to track batchIDs. */ static NSString *const gBatchIDCounterKey = @"GDTCORFlatFileStorageBatchIDCounter"; /** The separator used between metadata elements in filenames. */ static NSString *const kMetadataSeparator = @"-"; NSString *const kGDTCOREventComponentsEventIDKey = @"GDTCOREventComponentsEventIDKey"; NSString *const kGDTCOREventComponentsQoSTierKey = @"GDTCOREventComponentsQoSTierKey"; NSString *const kGDTCOREventComponentsMappingIDKey = @"GDTCOREventComponentsMappingIDKey"; NSString *const kGDTCOREventComponentsExpirationKey = @"GDTCOREventComponentsExpirationKey"; NSString *const kGDTCORBatchComponentsTargetKey = @"GDTCORBatchComponentsTargetKey"; NSString *const kGDTCORBatchComponentsBatchIDKey = @"GDTCORBatchComponentsBatchIDKey"; NSString *const kGDTCORBatchComponentsExpirationKey = @"GDTCORBatchComponentsExpirationKey"; NSString *const GDTCORFlatFileStorageErrorDomain = @"GDTCORFlatFileStorage"; const uint64_t kGDTCORFlatFileStorageSizeLimit = 20 * 1000 * 1000; // 20 MB. @interface GDTCORFlatFileStorage () /** An instance of the size tracker to keep track of the disk space consumed by the storage. */ @property(nonatomic, readonly) GDTCORDirectorySizeTracker *sizeTracker; @end @implementation GDTCORFlatFileStorage @synthesize sizeTracker = _sizeTracker; + (void)load { #if !NDEBUG [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetTest]; #endif // !NDEBUG [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCCT]; [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetFLL]; [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCSH]; [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetINT]; } + (instancetype)sharedInstance { static GDTCORFlatFileStorage *sharedStorage; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedStorage = [[GDTCORFlatFileStorage alloc] init]; }); return sharedStorage; } - (instancetype)init { self = [super init]; if (self) { _storageQueue = dispatch_queue_create("com.google.GDTCORFlatFileStorage", DISPATCH_QUEUE_SERIAL); _uploadCoordinator = [GDTCORUploadCoordinator sharedInstance]; } return self; } - (GDTCORDirectorySizeTracker *)sizeTracker { if (_sizeTracker == nil) { _sizeTracker = [[GDTCORDirectorySizeTracker alloc] initWithDirectoryPath:GDTCORRootDirectory().path]; } return _sizeTracker; } #pragma mark - GDTCORStorageProtocol - (void)storeEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion { GDTCORLogDebug(@"Saving event: %@", event); if (event == nil || event.serializedDataObjectBytes == nil) { GDTCORLogDebug(@"%@", @"The event was nil, so it was not saved."); if (completion) { completion(NO, [NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]); } return; } if (!completion) { completion = ^(BOOL wasWritten, NSError *_Nullable error) { GDTCORLogDebug(@"event %@ stored. success:%@ error:%@", event, wasWritten ? @"YES" : @"NO", error); }; } __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; bgID = [[GDTCORApplication sharedApplication] beginBackgroundTaskWithName:@"GDTStorage" expirationHandler:^{ // End the background task if it's still valid. [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }]; dispatch_async(_storageQueue, ^{ // Check that a backend implementation is available for this target. GDTCORTarget target = event.target; NSString *filePath = [GDTCORFlatFileStorage pathForTarget:target eventID:event.eventID qosTier:@(event.qosTier) expirationDate:event.expirationDate mappingID:event.mappingID]; NSError *error; NSData *encodedEvent = GDTCOREncodeArchive(event, nil, &error); if (error) { completion(NO, error); return; } // Check storage size limit before storing the event. uint64_t resultingStorageSize = self.sizeTracker.directoryContentSize + encodedEvent.length; if (resultingStorageSize > kGDTCORFlatFileStorageSizeLimit) { NSError *error = [NSError errorWithDomain:GDTCORFlatFileStorageErrorDomain code:GDTCORFlatFileStorageErrorSizeLimitReached userInfo:@{ NSLocalizedFailureReasonErrorKey : @"Storage size limit has been reached." }]; completion(NO, error); return; } // Write the encoded event to the file. BOOL writeResult = GDTCORWriteDataToFile(encodedEvent, filePath, &error); if (writeResult == NO || error) { GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, error); completion(NO, error); return; } else { GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); completion(YES, nil); } // Notify size tracker. [self.sizeTracker fileWasAddedAtPath:filePath withSize:encodedEvent.length]; // Check the QoS, if it's high priority, notify the target that it has a high priority event. if (event.qosTier == GDTCOREventQoSFast) { // TODO: Remove a direct dependency on the upload coordinator. [self.uploadCoordinator forceUploadForTarget:target]; } // Cancel or end the associated background task if it's still valid. [[GDTCORApplication sharedApplication] endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }); } - (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector batchExpiration:(nonnull NSDate *)expiration onComplete: (nonnull void (^)(NSNumber *_Nullable batchID, NSSet *_Nullable events))onComplete { dispatch_queue_t queue = _storageQueue; void (^onPathsForTargetComplete)(NSNumber *, NSSet *_Nonnull) = ^( NSNumber *batchID, NSSet *_Nonnull paths) { dispatch_async(queue, ^{ NSMutableSet *events = [[NSMutableSet alloc] init]; for (NSString *eventPath in paths) { NSError *error; GDTCOREvent *event = (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], eventPath, nil, &error); if (event == nil || error) { GDTCORLogDebug(@"Error deserializing event: %@", error); [[NSFileManager defaultManager] removeItemAtPath:eventPath error:nil]; continue; } else { NSString *fileName = [eventPath lastPathComponent]; NSString *batchPath = [GDTCORFlatFileStorage batchPathForTarget:eventSelector.selectedTarget batchID:batchID expirationDate:expiration]; [[NSFileManager defaultManager] createDirectoryAtPath:batchPath withIntermediateDirectories:YES attributes:nil error:nil]; NSString *destinationPath = [batchPath stringByAppendingPathComponent:fileName]; error = nil; [[NSFileManager defaultManager] moveItemAtPath:eventPath toPath:destinationPath error:&error]; if (error) { GDTCORLogDebug(@"An event file wasn't moveable into the batch directory: %@", error); } [events addObject:event]; } } if (onComplete) { if (events.count == 0) { onComplete(nil, nil); } else { onComplete(batchID, events); } } }); }; void (^onBatchIDFetchComplete)(NSNumber *) = ^(NSNumber *batchID) { dispatch_async(queue, ^{ if (batchID == nil) { if (onComplete) { onComplete(nil, nil); return; } } [self pathsForTarget:eventSelector.selectedTarget eventIDs:eventSelector.selectedEventIDs qosTiers:eventSelector.selectedQosTiers mappingIDs:eventSelector.selectedMappingIDs onComplete:^(NSSet *_Nonnull paths) { onPathsForTargetComplete(batchID, paths); }]; }); }; [self nextBatchID:^(NSNumber *_Nullable batchID) { if (batchID == nil) { if (onComplete) { onComplete(nil, nil); } } else { onBatchIDFetchComplete(batchID); } }]; } - (void)removeBatchWithID:(nonnull NSNumber *)batchID deleteEvents:(BOOL)deleteEvents onComplete:(void (^_Nullable)(void))onComplete { dispatch_async(_storageQueue, ^{ [self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:deleteEvents]; if (onComplete) { onComplete(); } }); } - (void)batchIDsForTarget:(GDTCORTarget)target onComplete:(nonnull void (^)(NSSet *_Nullable))onComplete { dispatch_async(_storageQueue, ^{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *batchPaths = [fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath] error:&error]; if (error || batchPaths.count == 0) { if (onComplete) { onComplete(nil); } return; } NSMutableSet *batchIDs = [[NSMutableSet alloc] init]; for (NSString *path in batchPaths) { NSDictionary *components = [self batchComponentsFromFilename:path]; NSNumber *targetNumber = components[kGDTCORBatchComponentsTargetKey]; NSNumber *batchID = components[kGDTCORBatchComponentsBatchIDKey]; if (batchID != nil && targetNumber.intValue == target) { [batchIDs addObject:batchID]; } } if (onComplete) { onComplete(batchIDs); } }); } - (void)libraryDataForKey:(nonnull NSString *)key onFetchComplete:(nonnull void (^)(NSData *_Nullable, NSError *_Nullable))onFetchComplete setNewValue:(NSData *_Nullable (^_Nullable)(void))setValueBlock { dispatch_async(_storageQueue, ^{ NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key]; NSError *error; NSData *data = [NSData dataWithContentsOfFile:dataPath options:0 error:&error]; if (onFetchComplete) { onFetchComplete(data, error); } if (setValueBlock) { NSData *newValue = setValueBlock(); // The -isKindOfClass check is necessary because without an explicit 'return nil' in the block // the implicit return value will be the block itself. The compiler doesn't detect this. if (newValue != nil && [newValue isKindOfClass:[NSData class]] && newValue.length) { NSError *newValueError; if ([newValue writeToFile:dataPath options:NSDataWritingAtomic error:&newValueError]) { // Update storage size. [self.sizeTracker fileWasRemovedAtPath:dataPath withSize:data.length]; [self.sizeTracker fileWasAddedAtPath:dataPath withSize:newValue.length]; } else { GDTCORLogDebug(@"Error writing new value in libraryDataForKey: %@", newValueError); } } } }); } - (void)storeLibraryData:(NSData *)data forKey:(nonnull NSString *)key onComplete:(nullable void (^)(NSError *_Nullable error))onComplete { if (!data || data.length <= 0) { if (onComplete) { onComplete([NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]); } return; } dispatch_async(_storageQueue, ^{ NSError *error; NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key]; if ([data writeToFile:dataPath options:NSDataWritingAtomic error:&error]) { [self.sizeTracker fileWasAddedAtPath:dataPath withSize:data.length]; } if (onComplete) { onComplete(error); } }); } - (void)removeLibraryDataForKey:(nonnull NSString *)key onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete { dispatch_async(_storageQueue, ^{ NSError *error; NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key]; GDTCORStorageSizeBytes fileSize = [self.sizeTracker fileSizeAtURL:[NSURL fileURLWithPath:dataPath]]; if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) { if ([[NSFileManager defaultManager] removeItemAtPath:dataPath error:&error]) { [self.sizeTracker fileWasRemovedAtPath:dataPath withSize:fileSize]; } if (onComplete) { onComplete(error); } } }); } - (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEvents))onComplete { dispatch_async(_storageQueue, ^{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *targetPath = [NSString stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target]; [fileManager createDirectoryAtPath:targetPath withIntermediateDirectories:YES attributes:nil error:nil]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:targetPath]; BOOL hasEventAtLeastOneEvent = [enumerator nextObject] != nil; if (onComplete) { onComplete(hasEventAtLeastOneEvent); } }); } - (void)checkForExpirations { dispatch_async(_storageQueue, ^{ GDTCORLogDebug(@"%@", @"Checking for expired events and batches"); NSTimeInterval now = [NSDate date].timeIntervalSince1970; NSFileManager *fileManager = [NSFileManager defaultManager]; // TODO: Storage may not have enough context to remove batches because a batch may be being // uploaded but the storage has not context of it. // Find expired batches and move their events back to the main storage. // If a batch contains expired events they are expected to be removed further in the method // together with other expired events in the main storage. NSString *batchDataPath = [GDTCORFlatFileStorage batchDataStoragePath]; NSArray *batchDataPaths = [fileManager contentsOfDirectoryAtPath:batchDataPath error:nil]; for (NSString *path in batchDataPaths) { @autoreleasepool { NSString *fileName = [path lastPathComponent]; NSDictionary *batchComponents = [self batchComponentsFromFilename:fileName]; NSDate *expirationDate = batchComponents[kGDTCORBatchComponentsExpirationKey]; NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey]; if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now && batchID != nil) { NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey]; // Move all events from the expired batch back to the main storage. [self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:NO]; } } } // Find expired events and remove them from the storage. NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath]; NSString *path; while (YES) { @autoreleasepool { // Call `[enumerator nextObject]` under autorelease pool to make sure all autoreleased // objects created under the hood are released on each iteration end to avoid unnecessary // memory growth. path = [enumerator nextObject]; if (path == nil) { break; } NSString *fileName = [path lastPathComponent]; NSDictionary *eventComponents = [self eventComponentsFromFilename:fileName]; NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey]; if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) { NSString *pathToDelete = [eventDataPath stringByAppendingPathComponent:path]; NSError *error; [fileManager removeItemAtPath:pathToDelete error:&error]; if (error != nil) { GDTCORLogDebug(@"There was an error deleting an expired item: %@", error); } else { GDTCORLogDebug(@"Item deleted because it expired: %@", pathToDelete); } } } } [self.sizeTracker resetCachedSize]; }); } - (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete { if (!onComplete) { return; } dispatch_async(_storageQueue, ^{ onComplete([self.sizeTracker directoryContentSize]); }); } #pragma mark - Private not thread safe methods /** Looks for directory paths containing events for a batch with the specified ID. * @param batchID A batch ID. * @param outError A pointer to `NSError *` to assign as possible error to. * @return An array of an array of paths to directories for event batches with a specified batch ID * or `nil` in the case of an error. Usually returns a single path but potentially return more in * cases when the app is terminated while uploading a batch. */ - (nullable NSArray *)batchDirPathsForBatchID:(NSNumber *)batchID error:(NSError **_Nonnull)outError { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *batches = [fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath] error:&error]; if (batches == nil) { *outError = error; GDTCORLogDebug(@"Failed to find event file paths for batchID: %@, error: %@", batchID, error); return nil; } NSMutableArray *batchDirPaths = [NSMutableArray array]; for (NSString *path in batches) { NSDictionary *components = [self batchComponentsFromFilename:path]; NSNumber *pathBatchID = components[kGDTCORBatchComponentsBatchIDKey]; if ([pathBatchID isEqual:batchID]) { NSString *batchDirPath = [[GDTCORFlatFileStorage batchDataStoragePath] stringByAppendingPathComponent:path]; [batchDirPaths addObject:batchDirPath]; } } return [batchDirPaths copy]; } /** Makes a copy of the contents of a directory to a directory at the specified path.*/ - (BOOL)moveContentsOfDirectoryAtPath:(NSString *)sourcePath to:(NSString *)destinationPath error:(NSError **_Nonnull)outError { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *contentsPaths = [fileManager contentsOfDirectoryAtPath:sourcePath error:&error]; if (contentsPaths == nil) { *outError = error; return NO; } NSMutableArray *errors = [NSMutableArray array]; for (NSString *path in contentsPaths) { NSString *contentDestinationPath = [destinationPath stringByAppendingPathComponent:path]; NSString *contentSourcePath = [sourcePath stringByAppendingPathComponent:path]; NSError *moveError; if (![fileManager moveItemAtPath:contentSourcePath toPath:contentDestinationPath error:&moveError] && moveError) { [errors addObject:moveError]; } } if (errors.count == 0) { return YES; } else { NSError *combinedError = [NSError errorWithDomain:@"GDTCORFlatFileStorage" code:-1 userInfo:@{NSUnderlyingErrorKey : errors}]; *outError = combinedError; return NO; } } - (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID deleteEvents:(BOOL)deleteEvents { NSError *error; NSArray *batchDirPaths = [self batchDirPathsForBatchID:batchID error:&error]; if (batchDirPaths == nil) { return; } NSFileManager *fileManager = [NSFileManager defaultManager]; void (^removeBatchDir)(NSString *batchDirPath) = ^(NSString *batchDirPath) { NSError *error; if ([fileManager removeItemAtPath:batchDirPath error:&error]) { GDTCORLogDebug(@"Batch removed at path: %@", batchDirPath); } else { GDTCORLogDebug(@"Failed to remove batch at path: %@", batchDirPath); } }; for (NSString *batchDirPath in batchDirPaths) { @autoreleasepool { if (deleteEvents) { removeBatchDir(batchDirPath); } else { NSString *batchDirName = [batchDirPath lastPathComponent]; NSDictionary *components = [self batchComponentsFromFilename:batchDirName]; NSString *targetValue = [components[kGDTCORBatchComponentsTargetKey] stringValue]; NSString *destinationPath; if (targetValue) { destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath] stringByAppendingPathComponent:targetValue]; } // `- [NSFileManager moveItemAtPath:toPath:error:]` method fails if an item by the // destination path already exists (which usually is the case for the current method). Move // the events one by one instead. if (destinationPath && [self moveContentsOfDirectoryAtPath:batchDirPath to:destinationPath error:&error]) { GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath, destinationPath); } else { GDTCORLogDebug(@"Error encountered whilst moving events back: %@", error); } // Even if not all events where moved back to the storage, there is not much can be done at // this point, so cleanup batch directory now to avoid cluttering. removeBatchDir(batchDirPath); } } } [self.sizeTracker resetCachedSize]; } #pragma mark - Private helper methods + (NSString *)eventDataStoragePath { static NSString *eventDataPath; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ eventDataPath = [NSString stringWithFormat:@"%@/%@/gdt_event_data", GDTCORRootDirectory().path, NSStringFromClass([self class])]; }); NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:eventDataPath withIntermediateDirectories:YES attributes:0 error:&error]; GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error); return eventDataPath; } + (NSString *)batchDataStoragePath { static NSString *batchDataPath; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ batchDataPath = [NSString stringWithFormat:@"%@/%@/gdt_batch_data", GDTCORRootDirectory().path, NSStringFromClass([self class])]; }); NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:batchDataPath withIntermediateDirectories:YES attributes:0 error:&error]; GDTCORAssert(error == nil, @"Creating the batch data path failed: %@", error); return batchDataPath; } + (NSString *)libraryDataStoragePath { static NSString *libraryDataPath; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ libraryDataPath = [NSString stringWithFormat:@"%@/%@/gdt_library_data", GDTCORRootDirectory().path, NSStringFromClass([self class])]; }); NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:libraryDataPath withIntermediateDirectories:YES attributes:0 error:&error]; GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error); return libraryDataPath; } + (NSString *)batchPathForTarget:(GDTCORTarget)target batchID:(NSNumber *)batchID expirationDate:(NSDate *)expirationDate { return [NSString stringWithFormat:@"%@/%ld%@%@%@%llu", [GDTCORFlatFileStorage batchDataStoragePath], (long)target, kMetadataSeparator, batchID, kMetadataSeparator, ((uint64_t)expirationDate.timeIntervalSince1970)]; } + (NSString *)pathForTarget:(GDTCORTarget)target eventID:(NSString *)eventID qosTier:(NSNumber *)qosTier expirationDate:(NSDate *)expirationDate mappingID:(NSString *)mappingID { NSMutableCharacterSet *allowedChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy]; [allowedChars addCharactersInString:kMetadataSeparator]; mappingID = [mappingID stringByAddingPercentEncodingWithAllowedCharacters:allowedChars]; return [NSString stringWithFormat:@"%@/%ld/%@%@%@%@%llu%@%@", [GDTCORFlatFileStorage eventDataStoragePath], (long)target, eventID, kMetadataSeparator, qosTier, kMetadataSeparator, ((uint64_t)expirationDate.timeIntervalSince1970), kMetadataSeparator, mappingID]; } - (void)pathsForTarget:(GDTCORTarget)target eventIDs:(nullable NSSet *)eventIDs qosTiers:(nullable NSSet *)qosTiers mappingIDs:(nullable NSSet *)mappingIDs onComplete:(void (^)(NSSet *paths))onComplete { void (^completion)(NSSet *) = onComplete == nil ? ^(NSSet *paths){} : onComplete; dispatch_async(_storageQueue, ^{ NSMutableSet *paths = [[NSMutableSet alloc] init]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *targetPath = [NSString stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target]; [fileManager createDirectoryAtPath:targetPath withIntermediateDirectories:YES attributes:nil error:nil]; NSError *error; NSArray *dirPaths = [fileManager contentsOfDirectoryAtPath:targetPath error:&error]; if (error) { GDTCORLogDebug(@"There was an error reading the contents of the target path: %@", error); completion(paths); return; } BOOL checkingIDs = eventIDs.count > 0; BOOL checkingQosTiers = qosTiers.count > 0; BOOL checkingMappingIDs = mappingIDs.count > 0; BOOL checkingAnything = checkingIDs == NO && checkingQosTiers == NO && checkingMappingIDs == NO; for (NSString *path in dirPaths) { // Skip hidden files that are created as part of atomic file creation. if ([path hasPrefix:@"."]) { continue; } NSString *filePath = [targetPath stringByAppendingPathComponent:path]; if (checkingAnything) { [paths addObject:filePath]; continue; } NSString *filename = [path lastPathComponent]; NSDictionary *eventComponents = [self eventComponentsFromFilename:filename]; if (!eventComponents) { GDTCORLogDebug(@"There was an error reading the filename components: %@", eventComponents); continue; } NSString *eventID = eventComponents[kGDTCOREventComponentsEventIDKey]; NSNumber *qosTier = eventComponents[kGDTCOREventComponentsQoSTierKey]; NSString *mappingID = eventComponents[kGDTCOREventComponentsMappingIDKey]; NSNumber *eventIDMatch = checkingIDs ? @([eventIDs containsObject:eventID]) : nil; NSNumber *qosTierMatch = checkingQosTiers ? @([qosTiers containsObject:qosTier]) : nil; NSNumber *mappingIDMatch = checkingMappingIDs ? @([mappingIDs containsObject:[mappingID stringByRemovingPercentEncoding]]) : nil; if ((eventIDMatch == nil || eventIDMatch.boolValue) && (qosTierMatch == nil || qosTierMatch.boolValue) && (mappingIDMatch == nil || mappingIDMatch.boolValue)) { [paths addObject:filePath]; } } completion(paths); }); } - (void)nextBatchID:(void (^)(NSNumber *_Nullable batchID))nextBatchID { __block int32_t lastBatchID = -1; [self libraryDataForKey:gBatchIDCounterKey onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable getValueError) { if (!getValueError) { [data getBytes:(void *)&lastBatchID length:sizeof(int32_t)]; } if (data == nil) { lastBatchID = 0; } if (nextBatchID) { nextBatchID(@(lastBatchID)); } } setNewValue:^NSData *_Nullable(void) { if (lastBatchID != -1) { int32_t incrementedValue = lastBatchID + 1; return [NSData dataWithBytes:&incrementedValue length:sizeof(int32_t)]; } return nil; }]; } - (nullable NSDictionary *)eventComponentsFromFilename:(NSString *)fileName { NSArray *components = [fileName componentsSeparatedByString:kMetadataSeparator]; if (components.count >= 4) { NSString *eventID = components[0]; NSNumber *qosTier = @(components[1].integerValue); NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].longLongValue]; NSString *mappingID = [[components subarrayWithRange:NSMakeRange(3, components.count - 3)] componentsJoinedByString:kMetadataSeparator]; if (eventID == nil || qosTier == nil || mappingID == nil || expirationDate == nil) { GDTCORLogDebug(@"There was an error parsing the event filename components: %@", components); return nil; } return @{ kGDTCOREventComponentsEventIDKey : eventID, kGDTCOREventComponentsQoSTierKey : qosTier, kGDTCOREventComponentsExpirationKey : expirationDate, kGDTCOREventComponentsMappingIDKey : mappingID }; } GDTCORLogDebug(@"The event filename could not be split: %@", fileName); return nil; } - (nullable NSDictionary *)batchComponentsFromFilename:(NSString *)fileName { NSArray *components = [fileName componentsSeparatedByString:kMetadataSeparator]; if (components.count == 3) { NSNumber *target = @(components[0].integerValue); NSNumber *batchID = @(components[1].integerValue); NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].doubleValue]; if (target == nil || batchID == nil || expirationDate == nil) { GDTCORLogDebug(@"There was an error parsing the batch filename components: %@", components); return nil; } return @{ kGDTCORBatchComponentsTargetKey : target, kGDTCORBatchComponentsBatchIDKey : batchID, kGDTCORBatchComponentsExpirationKey : expirationDate }; } GDTCORLogDebug(@"The batch filename could not be split: %@", fileName); return nil; } #pragma mark - GDTCORLifecycleProtocol - (void)appWillBackground:(GDTCORApplication *)app { dispatch_async(_storageQueue, ^{ // Immediately request a background task to run until the end of the current queue of work, // and cancel it once the work is done. __block GDTCORBackgroundIdentifier bgID = [app beginBackgroundTaskWithName:@"GDTStorage" expirationHandler:^{ [app endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }]; // End the background task if it's still valid. [app endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }); } - (void)appWillTerminate:(GDTCORApplication *)application { dispatch_sync(_storageQueue, ^{ }); } @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h" @implementation GDTCORLifecycle + (void)load { [self sharedInstance]; } /** Creates/returns the singleton instance of this class. * * @return The singleton instance of this class. */ + (instancetype)sharedInstance { static GDTCORLifecycle *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[GDTCORLifecycle alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(applicationDidEnterBackground:) name:kGDTCORApplicationDidEnterBackgroundNotification object:nil]; [notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground:) name:kGDTCORApplicationWillEnterForegroundNotification object:nil]; NSString *name = kGDTCORApplicationWillTerminateNotification; [notificationCenter addObserver:self selector:@selector(applicationWillTerminate:) name:name object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)applicationDidEnterBackground:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is backgrounding."); [[GDTCORTransformer sharedInstance] appWillBackground:application]; } if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is backgrounding."); [[GDTCORUploadCoordinator sharedInstance] appWillBackground:application]; } if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is backgrounding."); [[GDTCORRegistrar sharedInstance] appWillBackground:application]; } } - (void)applicationWillEnterForeground:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is foregrounding."); [[GDTCORTransformer sharedInstance] appWillForeground:application]; } if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is foregrounding."); [[GDTCORUploadCoordinator sharedInstance] appWillForeground:application]; } if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is foregrounding."); [[GDTCORRegistrar sharedInstance] appWillForeground:application]; } } - (void)applicationWillTerminate:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is terminating."); [[GDTCORTransformer sharedInstance] appWillTerminate:application]; } if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is terminating."); [[GDTCORUploadCoordinator sharedInstance] appWillTerminate:application]; } if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is terminating."); [[GDTCORRegistrar sharedInstance] appWillTerminate:application]; } } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #ifdef GDTCOR_VERSION #define STR(x) STR_EXPAND(x) #define STR_EXPAND(x) #x NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION); #else NSString *const kGDTCORVersion = @"Unknown"; #endif // GDTCOR_VERSION const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0; NSString *const kGDTCORApplicationDidEnterBackgroundNotification = @"GDTCORApplicationDidEnterBackgroundNotification"; NSString *const kGDTCORApplicationWillEnterForegroundNotification = @"GDTCORApplicationWillEnterForegroundNotification"; NSString *const kGDTCORApplicationWillTerminateNotification = @"GDTCORApplicationWillTerminateNotification"; NSURL *GDTCORRootDirectory(void) { static NSURL *GDTPath; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; GDTPath = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]]; GDTCORLogDebug(@"GDT's state will be saved to: %@", GDTPath); }); NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:GDTPath.path withIntermediateDirectories:YES attributes:nil error:&error]; GDTCORAssert(error == nil, @"There was an error creating GDT's path"); return GDTPath; } BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags) { #if !TARGET_OS_WATCH BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable; BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired; return reachable && !connectionRequired; #else return (flags & kGDTCORNetworkReachabilityFlagsReachable) == kGDTCORNetworkReachabilityFlagsReachable; #endif } BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) { #if TARGET_OS_IOS return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN; #else // Assume network connection not WWAN on macOS, tvOS, watchOS. return NO; #endif // TARGET_OS_IOS } GDTCORNetworkType GDTCORNetworkTypeMessage() { #if !TARGET_OS_WATCH SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags]; if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable) { if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) { return GDTCORNetworkTypeMobile; } else { return GDTCORNetworkTypeWIFI; } } #endif return GDTCORNetworkTypeUNKNOWN; } GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() { #if TARGET_OS_IOS static NSDictionary *CTRadioAccessTechnologyToNetworkSubTypeMessage; static CTTelephonyNetworkInfo *networkInfo; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CTRadioAccessTechnologyToNetworkSubTypeMessage = @{ CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS), CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge), CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA), CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA), CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA), CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x), CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0), CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA), CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB), CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD), CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE), }; networkInfo = [[CTTelephonyNetworkInfo alloc] init]; }); NSString *networkCurrentRadioAccessTechnology; #if TARGET_OS_MACCATALYST NSDictionary *networkCurrentRadioAccessTechnologyDict = networkInfo.serviceCurrentRadioAccessTechnology; if (networkCurrentRadioAccessTechnologyDict.count) { networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0]; } #else // TARGET_OS_MACCATALYST if (@available(iOS 12.0, *)) { NSDictionary *networkCurrentRadioAccessTechnologyDict = networkInfo.serviceCurrentRadioAccessTechnology; if (networkCurrentRadioAccessTechnologyDict.count) { // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio // tech to another, so we'll just return the first value in the dictionary. networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0]; } } else { #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000 networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology; #endif // TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000 } #endif // TARGET_OS_MACCATALYST if (networkCurrentRadioAccessTechnology) { NSNumber *networkMobileSubtype = CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology]; return networkMobileSubtype.intValue; } else { return GDTCORNetworkMobileSubtypeUNKNOWN; } #else // TARGET_OS_IOS return GDTCORNetworkMobileSubtypeUNKNOWN; #endif // TARGET_OS_IOS } NSString *_Nonnull GDTCORDeviceModel() { static NSString *deviceModel = @"Unknown"; #if TARGET_OS_IOS || TARGET_OS_TV static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ size_t size; char *keyToExtract = "hw.machine"; sysctlbyname(keyToExtract, NULL, &size, NULL, 0); if (size > 0) { char *machine = calloc(1, size); sysctlbyname(keyToExtract, machine, &size, NULL, 0); deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding]; free(machine); } else { deviceModel = [UIDevice currentDevice].model; } }); #endif return deviceModel; } NSData *_Nullable GDTCOREncodeArchive(id obj, NSString *filePath, NSError *_Nullable *error) { BOOL result = NO; if (filePath.length > 0) { result = [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:error]; if (result == NO || *error) { GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *error); return nil; } } NSData *resultData; if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) { resultData = [NSKeyedArchiver archivedDataWithRootObject:obj requiringSecureCoding:YES error:error]; if (resultData == nil || (error != NULL && *error != nil)) { GDTCORLogDebug(@"Encoding an object failed: %@", *error); return nil; } if (filePath.length > 0) { result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; if (result == NO || *error) { GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error); } else { GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); } } } else { @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" resultData = [NSKeyedArchiver archivedDataWithRootObject:obj]; #pragma clang diagnostic pop if (filePath.length > 0) { result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; if (result == NO || *error) { GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", filePath, *error); } else { GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); } } } @catch (NSException *exception) { NSString *errorString = [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception]; *error = [NSError errorWithDomain:NSCocoaErrorDomain code:-1 userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}]; } if (filePath.length > 0) { GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@", result ? @"YES" : @"NO", filePath, *error); } } return resultData; } id _Nullable GDTCORDecodeArchive(Class archiveClass, NSString *_Nullable archivePath, NSData *_Nullable archiveData, NSError *_Nullable *error) { id unarchivedObject = nil; if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) { NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath]; if (data) { unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass fromData:data error:error]; } } else { @try { NSData *archivedData = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData]; #pragma clang diagnostic pop } @catch (NSException *exception) { NSString *errorString = [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception]; *error = [NSError errorWithDomain:NSCocoaErrorDomain code:-1 userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}]; } } return unarchivedObject; } BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) { BOOL result = NO; if (filePath.length > 0) { result = [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:outError]; if (result == NO || *outError) { GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *outError); return result; } } if (filePath.length > 0) { result = [data writeToFile:filePath options:NSDataWritingAtomic error:outError]; if (result == NO || *outError) { GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *outError); } else { GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); } } return result; } @interface GDTCORApplication () /** Private flag to match the existing `readonly` public flag. This will be accurate for all platforms, since we handle each platform's lifecycle notifications separately. */ @property(atomic, readwrite) BOOL isRunningInBackground; @end @implementation GDTCORApplication #if TARGET_OS_WATCH /** A dispatch queue on which all task semaphores will populate and remove from * gBackgroundIdentifierToSemaphoreMap. */ static dispatch_queue_t gSemaphoreQueue; /** For mapping backgroundIdentifier to task semaphore. */ static NSMutableDictionary *gBackgroundIdentifierToSemaphoreMap; #endif + (void)load { GDTCORLogDebug( @"%@", @"GDT is initializing. Please note that if you quit the app via the " "debugger and not through a lifecycle event, event data will remain on disk but " "storage won't have a reference to them since the singleton wasn't saved to disk."); #if TARGET_OS_IOS || TARGET_OS_TV // If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues. GDTCORFatalAssert( GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid, @"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same."); #endif [self sharedApplication]; } + (void)initialize { #if TARGET_OS_WATCH static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ gSemaphoreQueue = dispatch_queue_create("com.google.GDTCORApplication", DISPATCH_QUEUE_SERIAL); GDTCORLogDebug( @"%@", @"GDTCORApplication is initializing on watchOS, gSemaphoreQueue has been initialized."); gBackgroundIdentifierToSemaphoreMap = [[NSMutableDictionary alloc] init]; GDTCORLogDebug(@"%@", @"GDTCORApplication is initializing on watchOS, " @"gBackgroundIdentifierToSemaphoreMap has been initialized."); }); #endif } + (nullable GDTCORApplication *)sharedApplication { static GDTCORApplication *application; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ application = [[GDTCORApplication alloc] init]; }); return application; } - (instancetype)init { self = [super init]; if (self) { // This class will be instantiated in the foreground. _isRunningInBackground = NO; #if TARGET_OS_IOS || TARGET_OS_TV NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(iOSApplicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [notificationCenter addObserver:self selector:@selector(iOSApplicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; NSString *name = UIApplicationWillTerminateNotification; [notificationCenter addObserver:self selector:@selector(iOSApplicationWillTerminate:) name:name object:nil]; #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13, tvOS 13.0, *)) { [notificationCenter addObserver:self selector:@selector(iOSApplicationWillEnterForeground:) name:UISceneWillEnterForegroundNotification object:nil]; [notificationCenter addObserver:self selector:@selector(iOSApplicationDidEnterBackground:) name:UISceneWillDeactivateNotification object:nil]; } #endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 #elif TARGET_OS_OSX NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(macOSApplicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; #elif TARGET_OS_WATCH // TODO: Notification on watchOS platform is currently posted by strings which are frangible. // TODO: Needs improvements here. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(iOSApplicationDidEnterBackground:) name:@"UIApplicationDidEnterBackgroundNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(iOSApplicationWillEnterForeground:) name:@"UIApplicationWillEnterForegroundNotification" object:nil]; // Adds observers for app extension on watchOS platform [notificationCenter addObserver:self selector:@selector(iOSApplicationDidEnterBackground:) name:NSExtensionHostDidEnterBackgroundNotification object:nil]; [notificationCenter addObserver:self selector:@selector(iOSApplicationWillEnterForeground:) name:NSExtensionHostWillEnterForegroundNotification object:nil]; #endif } return self; } #if TARGET_OS_WATCH /** Generates and maps a unique background identifier to the given semaphore. * * @param semaphore The semaphore to map. * @return A unique GDTCORBackgroundIdentifier mapped to the given semaphore. */ + (GDTCORBackgroundIdentifier)createAndMapBackgroundIdentifierToSemaphore: (dispatch_semaphore_t)semaphore { __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; dispatch_queue_t queue = gSemaphoreQueue; NSMutableDictionary *map = gBackgroundIdentifierToSemaphoreMap; if (queue && map) { dispatch_sync(queue, ^{ bgID = arc4random(); NSNumber *bgIDNumber = @(bgID); while (bgID == GDTCORBackgroundIdentifierInvalid || map[bgIDNumber]) { bgID = arc4random(); bgIDNumber = @(bgID); } map[bgIDNumber] = semaphore; }); } return bgID; } /** Returns the semaphore mapped to given bgID and removes the value from the map. * * @param bgID The unique NSUInteger as GDTCORBackgroundIdentifier. * @return The semaphore mapped by given bgID. */ + (dispatch_semaphore_t)semaphoreForBackgroundIdentifier:(GDTCORBackgroundIdentifier)bgID { __block dispatch_semaphore_t semaphore; dispatch_queue_t queue = gSemaphoreQueue; NSMutableDictionary *map = gBackgroundIdentifierToSemaphoreMap; NSNumber *bgIDNumber = @(bgID); if (queue && map) { dispatch_sync(queue, ^{ semaphore = map[bgIDNumber]; [map removeObjectForKey:bgIDNumber]; }); } return semaphore; } #endif - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name expirationHandler:(void (^)(void))handler { __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; #if !TARGET_OS_WATCH bgID = [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name expirationHandler:handler]; #if !NDEBUG if (bgID != GDTCORBackgroundIdentifierInvalid) { GDTCORLogDebug(@"Creating background task with name:%@ bgID:%ld", name, (long)bgID); } #endif // !NDEBUG #elif TARGET_OS_WATCH dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); bgID = [GDTCORApplication createAndMapBackgroundIdentifierToSemaphore:semaphore]; if (bgID != GDTCORBackgroundIdentifierInvalid) { GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID); } [[self sharedNSProcessInfoForBackgroundTask] performExpiringActivityWithReason:name usingBlock:^(BOOL expired) { if (expired) { if (handler) { handler(); } dispatch_semaphore_signal(semaphore); GDTCORLogDebug( @"Activity with name:%@ bgID:%ld on watchOS is expiring.", name, (long)bgID); } else { dispatch_semaphore_wait( semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); } }]; #endif return bgID; } - (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID { #if !TARGET_OS_WATCH if (bgID != GDTCORBackgroundIdentifierInvalid) { GDTCORLogDebug(@"Ending background task with ID:%ld was successful", (long)bgID); [[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID]; return; } #elif TARGET_OS_WATCH if (bgID != GDTCORBackgroundIdentifierInvalid) { dispatch_semaphore_t semaphore = [GDTCORApplication semaphoreForBackgroundIdentifier:bgID]; GDTCORLogDebug(@"Ending activity with bgID:%ld on watchOS.", (long)bgID); if (semaphore) { dispatch_semaphore_signal(semaphore); GDTCORLogDebug(@"Signaling semaphore with bgID:%ld on watchOS.", (long)bgID); } else { GDTCORLogDebug(@"Semaphore with bgID:%ld is nil on watchOS.", (long)bgID); } } #endif // !TARGET_OS_WATCH } #pragma mark - App environment helpers - (BOOL)isAppExtension { BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; return appExtension; } /** Returns a UIApplication or NSProcessInfo instance if on the appropriate platform. * * @return The shared UIApplication or NSProcessInfo if on the appropriate platform. */ #if TARGET_OS_IOS || TARGET_OS_TV - (nullable UIApplication *)sharedApplicationForBackgroundTask { #elif TARGET_OS_WATCH - (nullable NSProcessInfo *)sharedNSProcessInfoForBackgroundTask { #else - (nullable id)sharedApplicationForBackgroundTask { #endif id sharedInstance = nil; #if TARGET_OS_IOS || TARGET_OS_TV if (![self isAppExtension]) { Class uiApplicationClass = NSClassFromString(@"UIApplication"); if (uiApplicationClass && [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) { sharedInstance = [uiApplicationClass sharedApplication]; } } #elif TARGET_OS_WATCH sharedInstance = [NSProcessInfo processInfo]; #endif return sharedInstance; } #pragma mark - UIApplicationDelegate and WKExtensionDelegate #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH - (void)iOSApplicationDidEnterBackground:(NSNotification *)notif { _isRunningInBackground = YES; NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is backgrounding."); [notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil]; } - (void)iOSApplicationWillEnterForeground:(NSNotification *)notif { _isRunningInBackground = NO; NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is foregrounding."); [notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil]; } #endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH #pragma mark - UIApplicationDelegate #if TARGET_OS_IOS || TARGET_OS_TV - (void)iOSApplicationWillTerminate:(NSNotification *)notif { NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating."); [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil]; } #endif // TARGET_OS_IOS || TARGET_OS_TV #pragma mark - NSApplicationDelegate #if TARGET_OS_OSX - (void)macOSApplicationWillTerminate:(NSNotification *)notif { NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating."); [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil]; } #endif // TARGET_OS_OSX @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import /** Sets the _callbackFlag ivar whenever the network changes. * * @param reachability The reachability object calling back. * @param flags The new flag values. * @param info Any data that might be passed in by the callback. */ static void GDTCORReachabilityCallback(GDTCORNetworkReachabilityRef reachability, GDTCORNetworkReachabilityFlags flags, void *info); @implementation GDTCORReachability { /** The reachability object. */ GDTCORNetworkReachabilityRef _reachabilityRef; /** The queue on which callbacks and all work will occur. */ dispatch_queue_t _reachabilityQueue; /** Flags specified by reachability callbacks. */ GDTCORNetworkReachabilityFlags _callbackFlags; } + (void)initialize { [self sharedInstance]; } + (instancetype)sharedInstance { static GDTCORReachability *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[GDTCORReachability alloc] init]; }); return sharedInstance; } + (GDTCORNetworkReachabilityFlags)currentFlags { __block GDTCORNetworkReachabilityFlags currentFlags; #if !TARGET_OS_WATCH dispatch_sync([GDTCORReachability sharedInstance] -> _reachabilityQueue, ^{ GDTCORReachability *reachability = [GDTCORReachability sharedInstance]; currentFlags = reachability->_callbackFlags ? reachability->_callbackFlags : reachability->_flags; GDTCORLogDebug(@"Initial reachability flags determined: %d", currentFlags); }); #else currentFlags = kGDTCORNetworkReachabilityFlagsReachable; #endif return currentFlags; } - (instancetype)init { self = [super init]; #if !TARGET_OS_WATCH if (self) { struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; _reachabilityQueue = dispatch_queue_create("com.google.GDTCORReachability", DISPATCH_QUEUE_SERIAL); _reachabilityRef = SCNetworkReachabilityCreateWithAddress( kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); Boolean success = SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _reachabilityQueue); if (!success) { GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@", @"The reachability queue wasn't set."); } success = SCNetworkReachabilitySetCallback(_reachabilityRef, GDTCORReachabilityCallback, NULL); if (!success) { GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@", @"The reachability callback wasn't set."); } // Get the initial set of flags. dispatch_async(_reachabilityQueue, ^{ Boolean valid = SCNetworkReachabilityGetFlags(self->_reachabilityRef, &self->_flags); if (!valid) { GDTCORLogDebug(@"%@", @"Determining reachability failed."); self->_flags = 0; } }); } #endif return self; } - (void)setCallbackFlags:(GDTCORNetworkReachabilityFlags)flags { if (_callbackFlags != flags) { self->_callbackFlags = flags; } } @end #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" static void GDTCORReachabilityCallback(GDTCORNetworkReachabilityRef reachability, GDTCORNetworkReachabilityFlags flags, void *info) { #pragma clang diagnostic pop GDTCORLogDebug(@"Reachability changed, new flags: %d", flags); [[GDTCORReachability sharedInstance] setCallbackFlags:flags]; } ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" id _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target) { return [GDTCORRegistrar sharedInstance].targetToStorage[@(target)]; } FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target) { id storage = [GDTCORRegistrar sharedInstance].targetToStorage[@(target)]; if ([storage conformsToProtocol:@protocol(GDTCORStoragePromiseProtocol)]) { return storage; } else { return nil; } } @implementation GDTCORRegistrar { /** Backing ivar for targetToUploader property. */ NSMutableDictionary> *_targetToUploader; /** Backing ivar for targetToStorage property. */ NSMutableDictionary> *_targetToStorage; } + (instancetype)sharedInstance { static GDTCORRegistrar *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[GDTCORRegistrar alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _registrarQueue = dispatch_queue_create("com.google.GDTCORRegistrar", DISPATCH_QUEUE_SERIAL); _targetToUploader = [[NSMutableDictionary alloc] init]; _targetToStorage = [[NSMutableDictionary alloc] init]; } return self; } - (void)registerUploader:(id)backend target:(GDTCORTarget)target { __weak GDTCORRegistrar *weakSelf = self; dispatch_async(_registrarQueue, ^{ GDTCORRegistrar *strongSelf = weakSelf; if (strongSelf) { GDTCORLogDebug(@"Registered an uploader: %@ for target:%ld", backend, (long)target); strongSelf->_targetToUploader[@(target)] = backend; } }); } - (void)registerStorage:(id)storage target:(GDTCORTarget)target { __weak GDTCORRegistrar *weakSelf = self; dispatch_async(_registrarQueue, ^{ GDTCORRegistrar *strongSelf = weakSelf; if (strongSelf) { GDTCORLogDebug(@"Registered storage: %@ for target:%ld", storage, (long)target); strongSelf->_targetToStorage[@(target)] = storage; } }); } - (NSMutableDictionary> *)targetToUploader { __block NSMutableDictionary> *targetToUploader; __weak GDTCORRegistrar *weakSelf = self; dispatch_sync(_registrarQueue, ^{ GDTCORRegistrar *strongSelf = weakSelf; if (strongSelf) { targetToUploader = strongSelf->_targetToUploader; } }); return targetToUploader; } - (NSMutableDictionary> *)targetToStorage { __block NSMutableDictionary> *targetToStorage; __weak GDTCORRegistrar *weakSelf = self; dispatch_sync(_registrarQueue, ^{ GDTCORRegistrar *strongSelf = weakSelf; if (strongSelf) { targetToStorage = strongSelf->_targetToStorage; } }); return targetToStorage; } #pragma mark - GDTCORLifecycleProtocol - (void)appWillBackground:(nonnull GDTCORApplication *)app { NSArray> *uploaders = [self.targetToUploader allValues]; for (id uploader in uploaders) { if ([uploader respondsToSelector:@selector(appWillBackground:)]) { [uploader appWillBackground:app]; } } NSArray> *storages = [self.targetToStorage allValues]; for (id storage in storages) { if ([storage respondsToSelector:@selector(appWillBackground:)]) { [storage appWillBackground:app]; } } } - (void)appWillForeground:(nonnull GDTCORApplication *)app { NSArray> *uploaders = [self.targetToUploader allValues]; for (id uploader in uploaders) { if ([uploader respondsToSelector:@selector(appWillForeground:)]) { [uploader appWillForeground:app]; } } NSArray> *storages = [self.targetToStorage allValues]; for (id storage in storages) { if ([storage respondsToSelector:@selector(appWillForeground:)]) { [storage appWillForeground:app]; } } } - (void)appWillTerminate:(nonnull GDTCORApplication *)app { NSArray> *uploaders = [self.targetToUploader allValues]; for (id uploader in uploaders) { if ([uploader respondsToSelector:@selector(appWillTerminate:)]) { [uploader appWillTerminate:app]; } } NSArray> *storages = [self.targetToStorage allValues]; for (id storage in storages) { if ([storage respondsToSelector:@selector(appWillTerminate:)]) { [storage appWillTerminate:app]; } } } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORStorageEventSelector.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h" @implementation GDTCORStorageEventSelector + (instancetype)eventSelectorForTarget:(GDTCORTarget)target { return [[self alloc] initWithTarget:target eventIDs:nil mappingIDs:nil qosTiers:nil]; } - (instancetype)initWithTarget:(GDTCORTarget)target eventIDs:(nullable NSSet *)eventIDs mappingIDs:(nullable NSSet *)mappingIDs qosTiers:(nullable NSSet *)qosTiers { self = [super init]; if (self) { _selectedTarget = target; _selectedEventIDs = eventIDs; _selectedMappingIDs = mappingIDs; _selectedQosTiers = qosTiers; } return self; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" @implementation GDTCORTransformer + (instancetype)sharedInstance { static GDTCORTransformer *eventTransformer; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ eventTransformer = [[self alloc] init]; }); return eventTransformer; } - (instancetype)init { return [self initWithApplication:[GDTCORApplication sharedApplication]]; } - (instancetype)initWithApplication:(id)application { self = [super init]; if (self) { _eventWritingQueue = dispatch_queue_create("com.google.GDTCORTransformer", DISPATCH_QUEUE_SERIAL); _application = application; } return self; } - (void)transformEvent:(GDTCOREvent *)event withTransformers:(NSArray> *)transformers onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion { GDTCORAssert(event, @"You can't write a nil event"); __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid; __auto_type __weak weakApplication = self.application; bgID = [self.application beginBackgroundTaskWithName:@"GDTTransformer" expirationHandler:^{ [weakApplication endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }]; __auto_type completionWrapper = ^(BOOL wasWritten, NSError *_Nullable error) { if (completion) { completion(wasWritten, error); } // The work is done, cancel the background task if it's valid. [weakApplication endBackgroundTask:bgID]; bgID = GDTCORBackgroundIdentifierInvalid; }; dispatch_async(_eventWritingQueue, ^{ GDTCOREvent *transformedEvent = event; for (id transformer in transformers) { if ([transformer respondsToSelector:@selector(transformGDTEvent:)]) { GDTCORLogDebug(@"Applying a transformer to event %@", event); transformedEvent = [transformer transformGDTEvent:event]; if (!transformedEvent) { completionWrapper(NO, nil); return; } } else { GDTCORLogError(GDTCORMCETransformerDoesntImplementTransform, @"Transformer doesn't implement transformGDTEvent: %@", transformer); completionWrapper(NO, nil); return; } } id storage = [GDTCORRegistrar sharedInstance].targetToStorage[@(event.target)]; [storage storeEvent:transformedEvent onComplete:completionWrapper]; }); } #pragma mark - GDTCORLifecycleProtocol - (void)appWillTerminate:(GDTCORApplication *)application { // Flush the queue immediately. dispatch_sync(_eventWritingQueue, ^{ }); } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h" @implementation GDTCORTransport - (nullable instancetype)initWithMappingID:(NSString *)mappingID transformers: (nullable NSArray> *)transformers target:(GDTCORTarget)target { GDTCORAssert(mappingID.length > 0, @"A mapping ID cannot be nil or empty"); GDTCORAssert(target > 0, @"A target cannot be negative or 0"); if (mappingID == nil || mappingID.length == 0 || target <= 0) { return nil; } self = [super init]; if (self) { _mappingID = mappingID; _transformers = transformers; _target = target; _transformerInstance = [GDTCORTransformer sharedInstance]; } GDTCORLogDebug(@"Transport object created. mappingID:%@ transformers:%@ target:%ld", mappingID, transformers, (long)target); return self; } - (void)sendTelemetryEvent:(GDTCOREvent *)event onComplete: (void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion { event.qosTier = GDTCOREventQoSTelemetry; [self sendEvent:event onComplete:completion]; } - (void)sendDataEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion { GDTCORAssert(event.qosTier != GDTCOREventQoSTelemetry, @"Use -sendTelemetryEvent, please."); [self sendEvent:event onComplete:completion]; } - (void)sendTelemetryEvent:(GDTCOREvent *)event { [self sendTelemetryEvent:event onComplete:nil]; } - (void)sendDataEvent:(GDTCOREvent *)event { [self sendDataEvent:event onComplete:nil]; } - (GDTCOREvent *)eventForTransport { return [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; } #pragma mark - Private helper methods /** Sends the given event through the transport pipeline. * * @param event The event to send. * @param completion A block that will be called when the event has been written or dropped. */ - (void)sendEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion { // TODO: Determine if sending an event before registration is allowed. GDTCORAssert(event, @"You can't send a nil event"); GDTCOREvent *copiedEvent = [event copy]; copiedEvent.clockSnapshot = [GDTCORClock snapshot]; [self.transformerInstance transformEvent:copiedEvent withTransformers:_transformers onComplete:completion]; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadBatch.m ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" @implementation GDTCORUploadBatch - (instancetype)initWithBatchID:(NSNumber *)batchID events:(NSSet *)events { self = [super init]; if (self) { _batchID = batchID; _events = events; } return self; } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" @implementation GDTCORUploadCoordinator + (instancetype)sharedInstance { static GDTCORUploadCoordinator *sharedUploader; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedUploader = [[GDTCORUploadCoordinator alloc] init]; [sharedUploader startTimer]; }); return sharedUploader; } - (instancetype)init { self = [super init]; if (self) { _coordinationQueue = dispatch_queue_create("com.google.GDTCORUploadCoordinator", DISPATCH_QUEUE_SERIAL); _registrar = [GDTCORRegistrar sharedInstance]; _timerInterval = 30 * NSEC_PER_SEC; _timerLeeway = 5 * NSEC_PER_SEC; } return self; } - (void)forceUploadForTarget:(GDTCORTarget)target { dispatch_async(_coordinationQueue, ^{ GDTCORLogDebug(@"Forcing an upload of target %ld", (long)target); GDTCORUploadConditions conditions = [self uploadConditions]; conditions |= GDTCORUploadConditionHighPriority; [self uploadTargets:@[ @(target) ] conditions:conditions]; }); } #pragma mark - Private helper methods /** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will * check the next-upload clocks of all targets to determine if an upload attempt can be made. */ - (void)startTimer { dispatch_async(_coordinationQueue, ^{ if (self->_timer) { // The timer has been already started. return; } // Delay the timer slightly so it doesn't run while +load calls are still running. dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC / 2); self->_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue); dispatch_source_set_timer(self->_timer, deadline, self->_timerInterval, self->_timerLeeway); dispatch_source_set_event_handler(self->_timer, ^{ if (![[GDTCORApplication sharedApplication] isRunningInBackground]) { GDTCORUploadConditions conditions = [self uploadConditions]; GDTCORLogDebug(@"%@", @"Upload timer fired"); [self uploadTargets:[self.registrar.targetToUploader allKeys] conditions:conditions]; } }); GDTCORLogDebug(@"%@", @"Upload timer started"); dispatch_resume(self->_timer); }); } /** Stops the currently running timer. */ - (void)stopTimer { if (_timer) { dispatch_source_cancel(_timer); _timer = nil; } } /** Triggers the uploader implementations for the given targets to upload. * * @param targets An array of targets to trigger. * @param conditions The set of upload conditions. */ - (void)uploadTargets:(NSArray *)targets conditions:(GDTCORUploadConditions)conditions { dispatch_async(_coordinationQueue, ^{ // TODO: The reachability signal may be not reliable enough to prevent an upload attempt. // See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details. if ((conditions & GDTCORUploadConditionNoNetwork) == GDTCORUploadConditionNoNetwork) { return; } for (NSNumber *target in targets) { id uploader = self->_registrar.targetToUploader[target]; [uploader uploadTarget:target.intValue withConditions:conditions]; } }); } - (void)signalToStoragesToCheckExpirations { // The same storage may be associated with several targets. Make sure to check for expirations // only once per storage. NSSet> *storages = [NSSet setWithArray:[_registrar.targetToStorage allValues]]; for (id storage in storages) { [storage checkForExpirations]; } } /** Returns the registered storage for the given NSNumber wrapped GDTCORTarget. * * @param target The NSNumber wrapping of a GDTCORTarget to find the storage instance of. * @return The storage instance for the given target. */ - (nullable id)storageForTarget:(NSNumber *)target { id storage = [GDTCORRegistrar sharedInstance].targetToStorage[target]; GDTCORAssert(storage, @"A storage must be registered for target %@", target); return storage; } /** Returns the current upload conditions after making determinations about the network connection. * * @return The current upload conditions. */ - (GDTCORUploadConditions)uploadConditions { GDTCORNetworkReachabilityFlags currentFlags = [GDTCORReachability currentFlags]; BOOL networkConnected = GDTCORReachabilityFlagsReachable(currentFlags); if (!networkConnected) { return GDTCORUploadConditionNoNetwork; } BOOL isWWAN = GDTCORReachabilityFlagsContainWWAN(currentFlags); if (isWWAN) { return GDTCORUploadConditionMobileData; } else { return GDTCORUploadConditionWifiData; } } #pragma mark - GDTCORLifecycleProtocol - (void)appWillForeground:(GDTCORApplication *)app { // -startTimer is thread-safe. [self startTimer]; [self signalToStoragesToCheckExpirations]; } - (void)appWillBackground:(GDTCORApplication *)app { dispatch_async(_coordinationQueue, ^{ [self stopTimer]; }); } - (void)appWillTerminate:(GDTCORApplication *)application { dispatch_sync(_coordinationQueue, ^{ [self stopTimer]; }); } @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" NS_ASSUME_NONNULL_BEGIN /** A block type that could be run instead of normal assertion logging. No return type, no params. */ typedef void (^GDTCORAssertionBlock)(void); /** Returns the result of executing a soft-linked method present in unit tests that allows a block * to be run instead of normal assertion logging. This helps ameliorate issues with catching * exceptions that occur on a dispatch_queue. * * @return A block that can be run instead of normal assert printing. */ FOUNDATION_EXPORT GDTCORAssertionBlock _Nullable GDTCORAssertionBlockToRunInstead(void); #if defined(NS_BLOCK_ASSERTIONS) #define GDTCORAssert(condition, ...) \ do { \ } while (0); #define GDTCORFatalAssert(condition, ...) \ do { \ } while (0); #else // defined(NS_BLOCK_ASSERTIONS) /** Asserts using a console log, unless a block was specified to be run instead. * * @param condition The condition you'd expect to be YES. */ #define GDTCORAssert(condition, format, ...) \ do { \ __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ if (__builtin_expect(!(condition), 0)) { \ GDTCORAssertionBlock assertionBlock = GDTCORAssertionBlockToRunInstead(); \ if (assertionBlock) { \ assertionBlock(); \ } else { \ NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ __assert_file__ = __assert_file__ ? __assert_file__ : @""; \ GDTCORLogAssert(NO, __assert_file__, __LINE__, format, ##__VA_ARGS__); \ __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ } \ } \ } while (0); /** Asserts by logging to the console and throwing an exception if NS_BLOCK_ASSERTIONS is not * defined. * * @param condition The condition you'd expect to be YES. */ #define GDTCORFatalAssert(condition, format, ...) \ do { \ __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ if (__builtin_expect(!(condition), 0)) { \ GDTCORAssertionBlock assertionBlock = GDTCORAssertionBlockToRunInstead(); \ if (assertionBlock) { \ assertionBlock(); \ } else { \ NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \ __assert_file__ = __assert_file__ ? __assert_file__ : @""; \ GDTCORLogAssert(YES, __assert_file__, __LINE__, format, ##__VA_ARGS__); \ [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \ object:self \ file:__assert_file__ \ lineNumber:__LINE__ \ description:format, ##__VA_ARGS__]; \ __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ } \ } \ } while (0); #endif // defined(NS_BLOCK_ASSERTIONS) NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" NS_ASSUME_NONNULL_BEGIN /** The class calculates and caches the specified directory content size and uses add/remove signals * from client the client to keep the size up to date without accessing file system. * This is an internal class designed to be used by `GDTCORFlatFileStorage`. * NOTE: The class is not thread-safe. The client must take care of synchronization. */ @interface GDTCORDirectorySizeTracker : NSObject - (instancetype)init NS_UNAVAILABLE; /** Initializes the object with a directory path. * @param path The directory path to track content size. */ - (instancetype)initWithDirectoryPath:(NSString *)path; /** Returns a cached or calculates (if there is no cached) directory content size. * @return The directory content size in bytes calculated based on `NSURLFileSizeKey`. */ - (GDTCORStorageSizeBytes)directoryContentSize; /** The client must call this method or `resetCachedSize` method each time a file or directory is * added to the tracked directory. * @param path The path to the added file. If the path is outside the tracked directory then the * @param fileSize The size of the added file. * method is no-op. */ - (void)fileWasAddedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize; /** The client must call this method or `resetCachedSize` method each time a file or directory is * removed from the tracked directory. * @param path The path to the removed file. If the path is outside the tracked directory then the * @param fileSize The size of the removed file. * method is no-op. */ - (void)fileWasRemovedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize; /** Invalidates cached directory size. */ - (void)resetCachedSize; /** Returns URL resource value for `NSURLFileSizeKey` key for the specified URL. */ - (GDTCORStorageSizeBytes)fileSizeAtURL:(NSURL *)fileURL; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" @class GDTCOREvent; NS_ASSUME_NONNULL_BEGIN /** A protocol defining the lifecycle events objects in the library must respond to immediately. */ @protocol GDTCORLifecycleProtocol @optional /** Indicates an imminent app termination in the rare occurrence when -applicationWillTerminate: has * been called. * * @param app The GDTCORApplication instance. */ - (void)appWillTerminate:(GDTCORApplication *)app; /** Indicates that the app is moving to background and eventual suspension or the current UIScene is * deactivating. * * @param app The GDTCORApplication instance. */ - (void)appWillBackground:(GDTCORApplication *)app; /** Indicates that the app is resuming operation or a UIScene is activating. * * @param app The GDTCORApplication instance. */ - (void)appWillForeground:(GDTCORApplication *)app; @end /** This class manages the library's response to app lifecycle events. * * When backgrounding, the library doesn't stop processing events, it's just that several background * tasks will end up being created for every event that's sent, and the stateful objects of the * library (GDTCORStorage and GDTCORUploadCoordinator instances) will deserialize themselves from * and to disk before and after every operation, respectively. */ @interface GDTCORLifecycle : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h ================================================ /* * 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 #if !TARGET_OS_WATCH #import #endif #if TARGET_OS_IOS || TARGET_OS_TV #import #elif TARGET_OS_OSX #import #elif TARGET_OS_WATCH #import #endif // TARGET_OS_IOS || TARGET_OS_TV #if TARGET_OS_IOS #import #endif NS_ASSUME_NONNULL_BEGIN /** The GoogleDataTransport library version. */ FOUNDATION_EXPORT NSString *const kGDTCORVersion; /** A notification sent out if the app is backgrounding. */ FOUNDATION_EXPORT NSString *const kGDTCORApplicationDidEnterBackgroundNotification; /** A notification sent out if the app is foregrounding. */ FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillEnterForegroundNotification; /** A notification sent out if the app is terminating. */ FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillTerminateNotification; /** The different possible network connection type. */ typedef NS_ENUM(NSInteger, GDTCORNetworkType) { GDTCORNetworkTypeUNKNOWN = 0, GDTCORNetworkTypeWIFI = 1, GDTCORNetworkTypeMobile = 2, }; /** The different possible network connection mobile subtype. */ typedef NS_ENUM(NSInteger, GDTCORNetworkMobileSubtype) { GDTCORNetworkMobileSubtypeUNKNOWN = 0, GDTCORNetworkMobileSubtypeGPRS = 1, GDTCORNetworkMobileSubtypeEdge = 2, GDTCORNetworkMobileSubtypeWCDMA = 3, GDTCORNetworkMobileSubtypeHSDPA = 4, GDTCORNetworkMobileSubtypeHSUPA = 5, GDTCORNetworkMobileSubtypeCDMA1x = 6, GDTCORNetworkMobileSubtypeCDMAEVDORev0 = 7, GDTCORNetworkMobileSubtypeCDMAEVDORevA = 8, GDTCORNetworkMobileSubtypeCDMAEVDORevB = 9, GDTCORNetworkMobileSubtypeHRPD = 10, GDTCORNetworkMobileSubtypeLTE = 11, }; #if !TARGET_OS_WATCH /** Define SCNetworkReachabilityFlags as GDTCORNetworkReachabilityFlags on non-watchOS. */ typedef SCNetworkReachabilityFlags GDTCORNetworkReachabilityFlags; /** Define SCNetworkReachabilityRef as GDTCORNetworkReachabilityRef on non-watchOS. */ typedef SCNetworkReachabilityRef GDTCORNetworkReachabilityRef; #else /** The different possible reachabilityFlags option on watchOS. */ typedef NS_OPTIONS(uint32_t, GDTCORNetworkReachabilityFlags) { kGDTCORNetworkReachabilityFlagsReachable = 1 << 1, // TODO(doudounan): Add more options on watchOS if watchOS network connection information relative // APIs available in the future. }; /** Define a struct as GDTCORNetworkReachabilityRef on watchOS to store network connection * information. */ typedef struct { // TODO(doudounan): Store network connection information on watchOS if watchOS network connection // information relative APIs available in the future. } GDTCORNetworkReachabilityRef; #endif /** Returns a URL to the root directory under which all GDT-associated data must be saved. * * @return A URL to the root directory under which all GDT-associated data must be saved. */ NSURL *GDTCORRootDirectory(void); /** Compares flags with the reachable flag (on non-watchos with both reachable and * connectionRequired flags), if available, and returns YES if network reachable. * * @param flags The set of reachability flags. * @return YES if the network is reachable, NO otherwise. */ BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags); /** Compares flags with the WWAN reachability flag, if available, and returns YES if present. * * @param flags The set of reachability flags. * @return YES if the WWAN flag is set, NO otherwise. */ BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags); /** Generates an enum message GDTCORNetworkType representing network connection type. * * @return A GDTCORNetworkType representing network connection type. */ GDTCORNetworkType GDTCORNetworkTypeMessage(void); /** Generates an enum message GDTCORNetworkMobileSubtype representing network connection mobile * subtype. * * @return A GDTCORNetworkMobileSubtype representing network connection mobile subtype. */ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void); /** Identifies the model of the device on which the library is currently working on. * * @return A NSString representing the device model. */ NSString *_Nonnull GDTCORDeviceModel(void); /** Writes the given object to the given fileURL and populates the given error if it fails. * * @param obj The object to encode. * @param filePath The path to write the object to. Can be nil if you just need the data. * @param error The error to populate if something goes wrong. * @return The data of the archive. If error is nil, it's been written to disk. */ NSData *_Nullable GDTCOREncodeArchive(id obj, NSString *_Nullable filePath, NSError *_Nullable *error); /** Decodes an object of the given class from the given archive path or data and populates the given * error if it fails. * * @param archiveClass The class of the archive's root object. * @param archivePath The path to the archived data. Don't use with the archiveData param. * @param archiveData The data to decode. Don't use with the archivePath param. * @param error The error to populate if something goes wrong. */ id _Nullable GDTCORDecodeArchive(Class archiveClass, NSString *_Nullable archivePath, NSData *_Nullable archiveData, NSError *_Nullable *error); /** Writes the provided data to a file at the provided path. Intermediate directories will be * created as needed. * @param data The file content. * @param filePath The path to the file to write the provided data. * @param outError The error to populate if something goes wrong. * @return `YES` in the case of success, `NO` otherwise. */ BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError); /** A typedef identify background identifiers. */ typedef volatile NSUInteger GDTCORBackgroundIdentifier; /** A background task's invalid sentinel value. */ FOUNDATION_EXPORT const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid; #if TARGET_OS_IOS || TARGET_OS_TV /** A protocol that wraps UIApplicationDelegate, WKExtensionDelegate or NSObject protocol, depending * on the platform. */ @protocol GDTCORApplicationDelegate #elif TARGET_OS_OSX @protocol GDTCORApplicationDelegate #elif TARGET_OS_WATCH @protocol GDTCORApplicationDelegate #else @protocol GDTCORApplicationDelegate #endif // TARGET_OS_IOS || TARGET_OS_TV @end @protocol GDTCORApplicationProtocol @required /** Flag to determine if the application is running in the background. */ @property(atomic, readonly) BOOL isRunningInBackground; /** Creates a background task with the returned identifier if on a suitable platform. * * @name name The name of the task, useful for debugging which background tasks are running. * @param handler The handler block that is called if the background task expires. * @return An identifier for the background task, or GDTCORBackgroundIdentifierInvalid if one * couldn't be created. */ - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name expirationHandler:(void (^__nullable)(void))handler; /** Ends the background task if the identifier is valid. * * @param bgID The background task to end. */ - (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID; @end /** A cross-platform application class. */ @interface GDTCORApplication : NSObject /** Creates and/or returns the shared application instance. * * @return The shared application instance. */ + (nullable GDTCORApplication *)sharedApplication; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" NS_ASSUME_NONNULL_BEGIN /** This class helps determine upload conditions by determining connectivity. */ @interface GDTCORReachability : NSObject /** The current set flags indicating network conditions */ + (GDTCORNetworkReachabilityFlags)currentFlags; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" NS_ASSUME_NONNULL_BEGIN /** Manages the registration of targets with the transport SDK. */ @interface GDTCORRegistrar : NSObject /** Creates and/or returns the singleton instance. * * @return The singleton instance of this class. */ + (instancetype)sharedInstance; /** Registers a backend implementation with the GoogleDataTransport infrastructure. * * @param backend The backend object to register. * @param target The target this backend object will be responsible for. */ - (void)registerUploader:(id)backend target:(GDTCORTarget)target; /** Registers a storage implementation with the GoogleDataTransport infrastructure. * * @param storage The storage instance to be associated with this uploader and target. * @param target The target this backend object will be responsible for. */ - (void)registerStorage:(id)storage target:(GDTCORTarget)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" NS_ASSUME_NONNULL_BEGIN /** This class enables the finding of events by matching events with the properties of this class. */ @interface GDTCORStorageEventSelector : NSObject /** The target to find events for. Required. */ @property(readonly, nonatomic) GDTCORTarget selectedTarget; /** Finds a specific event. */ @property(nullable, readonly, nonatomic) NSSet *selectedEventIDs; /** Finds all events of a mappingID. */ @property(nullable, readonly, nonatomic) NSSet *selectedMappingIDs; /** Finds all events matching the qosTiers in this list. */ @property(nullable, readonly, nonatomic) NSSet *selectedQosTiers; /** Initializes an event selector that will find all events for the given target. * * @param target The selected target. * @return An immutable event selector instance. */ + (instancetype)eventSelectorForTarget:(GDTCORTarget)target; /** Instantiates an event selector. * * @param target The selected target. * @param eventIDs Optional param to find an event matching this eventID. * @param mappingIDs Optional param to find events matching this mappingID. * @param qosTiers Optional param to find events matching the given QoS tiers. * @return An immutable event selector instance. */ - (instancetype)initWithTarget:(GDTCORTarget)target eventIDs:(nullable NSSet *)eventIDs mappingIDs:(nullable NSSet *)mappingIDs qosTiers:(nullable NSSet *)qosTiers; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" @class GDTCOREvent; @class GDTCORClock; @class GDTCORUploadBatch; @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /** The data type to represent storage size. */ typedef uint64_t GDTCORStorageSizeBytes; typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, NSSet *_Nullable batchEvents); /** Defines the interface a storage subsystem is expected to implement. */ @protocol GDTCORStorageProtocol @required /** Stores an event and calls onComplete with a non-nil error if anything went wrong. * * @param event The event to store * @param completion The completion block to call after an attempt to store the event has been made. */ - (void)storeEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; /** Returns YES if some events have been stored for the given target, NO otherwise. * * @param onComplete The completion block to invoke when determining if there are events is done. */ - (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEvents))onComplete; /** Constructs an event batch with the given event selector. Events in this batch will not be * returned in any queries or other batches until the batch is removed. * * @param eventSelector The event selector used to find the events. * @param expiration The expiration time of the batch. If removeBatchWithID:deleteEvents:onComplete: * is not called within this time frame, the batch will be removed with its events deleted. * @param onComplete The completion handler to be called when the events have been fetched. */ - (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector batchExpiration:(nonnull NSDate *)expiration onComplete:(nonnull GDTCORStorageBatchBlock)onComplete; /** Removes the event batch. * * @param batchID The batchID to remove. * @param deleteEvents If YES, the events in this batch are deleted. * @param onComplete The completion handler to call when the batch removal process has completed. */ - (void)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents onComplete:(void (^_Nullable)(void))onComplete; /** Finds the batchIDs for the given target and calls the callback block. * * @param target The target. * @param onComplete The block to invoke with the set of current batchIDs. */ - (void)batchIDsForTarget:(GDTCORTarget)target onComplete:(void (^)(NSSet *_Nullable batchIDs))onComplete; /** Checks the storage for expired events and batches, deletes them if they're expired. */ - (void)checkForExpirations; /** Persists the given data with the given key. * * @param data The data to store. * @param key The unique key to store it to. * @param onComplete An block to be run when storage of the data is complete. */ - (void)storeLibraryData:(NSData *)data forKey:(NSString *)key onComplete:(nullable void (^)(NSError *_Nullable error))onComplete; /** Retrieves the stored data for the given key and optionally sets a new value. * * @param key The key corresponding to the desired data. * @param onFetchComplete The callback to invoke with the data once it's retrieved. * @param setValueBlock This optional block can provide a new value to set. */ - (void)libraryDataForKey:(nonnull NSString *)key onFetchComplete:(nonnull void (^)(NSData *_Nullable data, NSError *_Nullable error))onFetchComplete setNewValue:(NSData *_Nullable (^_Nullable)(void))setValueBlock; /** Removes data from storage and calls the callback when complete. * * @param key The key of the data to remove. * @param onComplete The callback that will be invoked when removing the data is complete. */ - (void)removeLibraryDataForKey:(NSString *)key onComplete:(void (^)(NSError *_Nullable error))onComplete; /** Calculates and returns the total disk size that this storage consumes. * * @param onComplete The callback that will be invoked once storage size calculation is complete. */ - (void)storageSizeWithCallback:(void (^)(GDTCORStorageSizeBytes storageSize))onComplete; @end // TODO: Consider complete replacing block based API by promise API. /** Promise based version of API defined in GDTCORStorageProtocol. See API docs for corresponding * methods in GDTCORStorageProtocol. */ @protocol GDTCORStoragePromiseProtocol - (FBLPromise *> *)batchIDsForTarget:(GDTCORTarget)target; - (FBLPromise *)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents; - (FBLPromise *)removeBatchesWithIDs:(NSSet *)batchIDs deleteEvents:(BOOL)deleteEvents; - (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target deleteEvents:(BOOL)deleteEvents; /** See `hasEventsForTarget:onComplete:`. * @return A promise object that is resolved with @YES if there are events for the specified target * and @NO otherwise. */ - (FBLPromise *)hasEventsForTarget:(GDTCORTarget)target; /** See `batchWithEventSelector:batchExpiration:onComplete:` * The promise is rejected when there are no events for the specified selector. */ - (FBLPromise *)batchWithEventSelector: (GDTCORStorageEventSelector *)eventSelector batchExpiration:(NSDate *)expiration; @end /** Retrieves the storage instance for the given target. * * @param target The target. * * @return The storage instance registered for the target, or nil if there is none. */ FOUNDATION_EXPORT id _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target); // TODO: Ideally we should remove completion-based API and use promise-based one. Need to double // check if it's ok. FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target); NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" NS_ASSUME_NONNULL_BEGIN /** Options that define a set of upload conditions. This is used to help minimize end user data * consumption impact. */ typedef NS_OPTIONS(NSInteger, GDTCORUploadConditions) { /** An upload shouldn't be attempted, because there's no network. */ GDTCORUploadConditionNoNetwork = 1 << 0, /** An upload would likely use mobile data. */ GDTCORUploadConditionMobileData = 1 << 1, /** An upload would likely use wifi data. */ GDTCORUploadConditionWifiData = 1 << 2, /** An upload uses some sort of network connection, but it's unclear which. */ GDTCORUploadConditionUnclearConnection = 1 << 3, /** A high priority event has occurred. */ GDTCORUploadConditionHighPriority = 1 << 4, }; /** This protocol defines the common interface for uploader implementations. */ @protocol GDTCORUploader @required /** Uploads events to the backend using this specific backend's chosen format. * * @param conditions The conditions that the upload attempt is likely to occur under. */ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREndpoints_Private.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h" @interface GDTCOREndpoints () /** Returns the list of all the upload URLs used by the transport library. * * @return Map of the transport target and the URL used for uploading the events for that target. */ + (NSDictionary *)uploadURLs; @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" NS_ASSUME_NONNULL_BEGIN @interface GDTCOREvent () /** The unique ID of the event. This property is for testing only. */ @property(nonatomic, readwrite) NSString *eventID; /** Generates a unique event ID. */ + (NSString *)nextEventID; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h" @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /// The category extends `GDTCORFlatFileStorage` API with `GDTCORStoragePromiseProtocol` methods. @interface GDTCORFlatFileStorage (Promises) @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" @class GDTCOREvent; @class GDTCORUploadCoordinator; NS_ASSUME_NONNULL_BEGIN /** The event components eventID dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsEventIDKey; /** The event components qosTier dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsQoSTierKey; /** The event components mappingID dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsMappingIDKey; /** The event components expirationDate dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsExpirationKey; /** The batch components target dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsTargetKey; /** The batch components batchID dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsBatchIDKey; /** The batch components expiration dictionary key. */ FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsExpirationKey; /** The maximum allowed disk space taken by the stored data. */ FOUNDATION_EXPORT const uint64_t kGDTCORFlatFileStorageSizeLimit; FOUNDATION_EXPORT NSString *const GDTCORFlatFileStorageErrorDomain; typedef NS_ENUM(NSInteger, GDTCORFlatFileStorageError) { GDTCORFlatFileStorageErrorSizeLimitReached = 0 }; /** Manages the storage of events. This class is thread-safe. * * Event files will be stored as follows: * /google-sdk-events//gdt_event_data//.. * * Library data will be stored as follows: * /google-sdk-events//gdt_library_data/ * * Batch data will be stored as follows: * /google-sdk-events//gdt_batch_data/./.. */ @interface GDTCORFlatFileStorage : NSObject /** The queue on which all storage work will occur. */ @property(nonatomic) dispatch_queue_t storageQueue; /** The upload coordinator instance used by this storage instance. */ @property(nonatomic) GDTCORUploadCoordinator *uploadCoordinator; /** Creates and/or returns the storage singleton. * * @return The storage singleton. */ + (instancetype)sharedInstance; /** Returns the base directory under which all events will be stored. * * @return The base directory under which all events will be stored. */ + (NSString *)eventDataStoragePath; /** Returns the base directory under which all library data will be stored. * * @return The base directory under which all library data will be stored. */ + (NSString *)libraryDataStoragePath; /** Returns the base directory under which all batch data will be stored. * * @return The base directory under which all batch data will be stored. */ + (NSString *)batchDataStoragePath; /** */ + (NSString *)batchPathForTarget:(GDTCORTarget)target batchID:(NSNumber *)batchID expirationDate:(NSDate *)expirationDate; /** Returns a constructed storage path based on the given values. This path may not exist. * * @param target The target, which is necessary to be given a path. * @param eventID The eventID. * @param qosTier The qosTier. * @param expirationDate The expirationDate as a 1970-relative time interval. * @param mappingID The mappingID. * @return The path representing the combination of the given parameters. */ + (NSString *)pathForTarget:(GDTCORTarget)target eventID:(NSString *)eventID qosTier:(NSNumber *)qosTier expirationDate:(NSDate *)expirationDate mappingID:(NSString *)mappingID; /** Returns extant paths that match all of the given parameters. * * @param eventIDs The list of eventIDs to look for, or nil for any. * @param qosTiers The list of qosTiers to look for, or nil for any. * @param mappingIDs The list of mappingIDs to look for, or nil for any. * @param onComplete The completion to call once the paths have been discovered. */ - (void)pathsForTarget:(GDTCORTarget)target eventIDs:(nullable NSSet *)eventIDs qosTiers:(nullable NSSet *)qosTiers mappingIDs:(nullable NSSet *)mappingIDs onComplete:(void (^)(NSSet *paths))onComplete; /** Fetches the current batchID counter value from library storage, increments it, and sets the new * value. Returns nil if a batchID was not able to be created for some reason. * * @param onComplete A block to execute when creating the next batchID is complete. */ - (void)nextBatchID:(void (^)(NSNumber *_Nullable batchID))onComplete; /** Constructs a dictionary of event filename components. * * @param fileName The event filename to split. * @return The dictionary of event component keys to their values. */ - (nullable NSDictionary *)eventComponentsFromFilename:(NSString *)fileName; /** Constructs a dictionary of batch filename components. * * @param fileName The batch folder name to split. * @return The dictionary of batch component keys to their values. */ - (nullable NSDictionary *)batchComponentsFromFilename:(NSString *)fileName; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" @interface GDTCORReachability () /** Allows manually setting the flags for testing purposes. */ @property(nonatomic, readwrite) GDTCORNetworkReachabilityFlags flags; /** Creates/returns the singleton instance of this class. * * @return The singleton instance of this class. */ + (instancetype)sharedInstance; @end ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" @interface GDTCORRegistrar () NS_ASSUME_NONNULL_BEGIN /** The concurrent queue on which all registration occurs. */ @property(nonatomic, readonly) dispatch_queue_t registrarQueue; /** A map of targets to backend implementations. */ @property(atomic, readonly) NSMutableDictionary> *targetToUploader; /** A map of targets to storage instances. */ @property(atomic, readonly) NSMutableDictionary> *targetToStorage; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" @class GDTCOREvent; @protocol GDTCOREventTransformer; NS_ASSUME_NONNULL_BEGIN /** Manages the transforming of events. It's desirable for this to be its own class * because running all events through a single instance ensures that transformers are thread-safe. * Having a per-transport queue to run on isn't sufficient because transformer objects could * maintain state (or at least, there's nothing to stop them from doing that) and the same instances * may be used across multiple instances. */ @interface GDTCORTransformer : NSObject /** Instantiates or returns the event transformer singleton. * * @return The singleton instance of the event transformer. */ + (instancetype)sharedInstance; /** Writes the result of applying the given transformers' `transformGDTEvent:` method on the given * event. * * @note If the app is suspended, a background task will be created to complete work in-progress, * but this method will not send any further events until the app is resumed. * * @param event The event to apply transformers on. * @param transformers The list of transformers to apply. * @param completion A block to run when an event was written to disk or dropped. */ - (void)transformEvent:(GDTCOREvent *)event withTransformers:(nullable NSArray> *)transformers onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h" @protocol GDTCORApplicationProtocol; NS_ASSUME_NONNULL_BEGIN @interface GDTCORTransformer () /** The queue on which all work will occur. */ @property(nonatomic) dispatch_queue_t eventWritingQueue; /** The application instance that is used to begin/end background tasks. */ @property(nonatomic, readonly) id application; /** The internal initializer. Should be used in tests only to create an instance with a * particular(fake) application instance. */ - (instancetype)initWithApplication:(id)application; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h ================================================ /* * 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h" @class GDTCORTransformer; NS_ASSUME_NONNULL_BEGIN @interface GDTCORTransport () /** The mapping identifier that the target backend will use to map the transport bytes to proto. */ @property(nonatomic) NSString *mappingID; /** The transformers that will operate on events sent by this transport. */ @property(nonatomic) NSArray> *transformers; /** The target backend of this transport. */ @property(nonatomic) NSInteger target; /** The transformer instance to used to transform events. Allows injecting a fake during testing. */ @property(nonatomic) GDTCORTransformer *transformerInstance; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h ================================================ /* * 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 GDTCOREvent; NS_ASSUME_NONNULL_BEGIN /// A data object representing a batch of events scheduled for upload. @interface GDTCORUploadBatch : NSObject /// An ID used to identify the batch in the storage. @property(nonatomic, readonly) NSNumber *batchID; /// The collection of the events in the batch. @property(nonatomic, readonly) NSSet *events; /// The default initializer. See also docs for the corresponding properties. - (instancetype)initWithBatchID:(NSNumber *)batchID events:(NSSet *)events; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h ================================================ /* * Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" @class GDTCORClock; NS_ASSUME_NONNULL_BEGIN /** This class connects storage and uploader implementations, providing events to an uploader * and informing the storage what events were successfully uploaded or not. */ @interface GDTCORUploadCoordinator : NSObject /** The queue on which all upload coordination will occur. Also used by a dispatch timer. */ /** Creates and/or returrns the singleton. * * @return The singleton instance of this class. */ + (instancetype)sharedInstance; /** The queue on which all upload coordination will occur. */ @property(nonatomic, readonly) dispatch_queue_t coordinationQueue; /** A timer that will causes regular checks for events to upload. */ @property(nonatomic, readonly, nullable) dispatch_source_t timer; /** The interval the timer will fire. */ @property(nonatomic, readonly) uint64_t timerInterval; /** Some leeway given to libdispatch for the timer interval event. */ @property(nonatomic, readonly) uint64_t timerLeeway; /** The registrar object the coordinator will use. Generally used for testing. */ @property(nonatomic) GDTCORRegistrar *registrar; /** Forces the backend specified by the target to upload the provided set of events. This should * only ever happen when the QoS tier of an event requires it. * * @param target The target that should force an upload. */ - (void)forceUploadForTarget:(GDTCORTarget)target; /** Starts the upload timer. */ - (void)startTimer; /** Stops the upload timer from running. */ - (void)stopTimer; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h ================================================ /* * Copyright 2018 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 /** This class manages the device clock and produces snapshots of the current time. */ @interface GDTCORClock : NSObject /** The wallclock time, UTC, in milliseconds. */ @property(nonatomic, readonly) int64_t timeMillis; /** The offset from UTC in seconds. */ @property(nonatomic, readonly) int64_t timezoneOffsetSeconds; /** The kernel boot time when this clock was created in nanoseconds. */ @property(nonatomic, readonly) int64_t kernelBootTimeNanoseconds; /** The device uptime when this clock was created in nanoseconds. */ @property(nonatomic, readonly) int64_t uptimeNanoseconds; @property(nonatomic, readonly) int64_t kernelBootTime DEPRECATED_MSG_ATTRIBUTE( "Please use `kernelBootTimeNanoseconds` instead"); @property(nonatomic, readonly) int64_t uptime DEPRECATED_MSG_ATTRIBUTE("Please use `uptimeNanoseconds` instead"); /** Creates a GDTCORClock object using the current time and offsets. * * @return A new GDTCORClock object representing the current time state. */ + (instancetype)snapshot; /** Creates a GDTCORClock object representing a time in the future, relative to now. * * @param millisInTheFuture The millis in the future from now this clock should represent. * @return An instance representing a future time. */ + (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture; /** Compares one clock with another, returns YES if the caller is after the parameter. * * @return YES if the calling clock's time is after the given clock's time. */ - (BOOL)isAfter:(GDTCORClock *)otherClock; /** Returns value of `uptime` property in milliseconds. */ - (int64_t)uptimeMilliseconds; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h ================================================ /* * Copyright 2018 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 /** The current logging level. This value and higher will be printed. Declared as volatile to make * getting and setting atomic. */ FOUNDATION_EXPORT volatile NSInteger GDTCORConsoleLoggerLoggingLevel; /** A list of logging levels that GDT supports. */ typedef NS_ENUM(NSInteger, GDTCORLoggingLevel) { /** Causes all logs to be printed. */ GDTCORLoggingLevelDebug = 1, /** Causes all non-debug logs to be printed. */ GDTCORLoggingLevelVerbose = 2, /** Causes warnings and errors to be printed. */ GDTCORLoggingLevelWarnings = 3, /** Causes errors to be printed. This is the default value. */ GDTCORLoggingLevelErrors = 4 }; /** A list of message codes to print in the logger that help to correspond printed messages with * code locations. * * Prefixes: * - MCD => MessageCodeDebug * - MCW => MessageCodeWarning * - MCE => MessageCodeError */ typedef NS_ENUM(NSInteger, GDTCORMessageCode) { /** For debug logs. */ GDTCORMCDDebugLog = 0, /** For warning messages concerning transportBytes: not being implemented by a data object. */ GDTCORMCWDataObjectMissingBytesImpl = 1, /** For warning messages concerning a failed event upload. */ GDTCORMCWUploadFailed = 2, /** For warning messages concerning a forced event upload. */ GDTCORMCWForcedUpload = 3, /** For warning messages concerning a failed reachability call. */ GDTCORMCWReachabilityFailed = 4, /** For warning messages concerning a database warning. */ GDTCORMCWDatabaseWarning = 5, /** For warning messages concerning the reading of a event file. */ GDTCORMCWFileReadError = 6, /** For error messages concerning transformGDTEvent: not being implemented by an event transformer. */ GDTCORMCETransformerDoesntImplementTransform = 1000, /** For error messages concerning the creation of a directory failing. */ GDTCORMCEDirectoryCreationError = 1001, /** For error messages concerning the writing of a event file. */ GDTCORMCEFileWriteError = 1002, /** For error messages concerning the lack of a prioritizer for a given backend. */ GDTCORMCEPrioritizerError = 1003, /** For error messages concerning a package delivery API violation. */ GDTCORMCEDeliverTwice = 1004, /** For error messages concerning an error in an implementation of -transportBytes. */ GDTCORMCETransportBytesError = 1005, /** For general purpose error messages in a dependency. */ GDTCORMCEGeneralError = 1006, /** For fatal errors. Please go to https://github.com/firebase/firebase-ios-sdk/issues and open * an issue if you encounter an error with this code. */ GDTCORMCEFatalAssertion = 1007, /** For error messages concerning the reading of a event file. */ GDTCORMCEFileReadError = 1008, /** For errors related to running sqlite. */ GDTCORMCEDatabaseError = 1009, }; /** Prints the given code and format string to the console. * * @param code The message code describing the nature of the log. * @param logLevel The log level of this log. * @param format The format string. */ FOUNDATION_EXPORT void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *_Nonnull format, ...) NS_FORMAT_FUNCTION(3, 4); /** Prints an assert log to the console. * * @param wasFatal Send YES if the assertion should be fatal, NO otherwise. * @param file The file in which the failure occurred. * @param line The line number of the failure. * @param format The format string. */ FOUNDATION_EXPORT void GDTCORLogAssert(BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) NS_FORMAT_FUNCTION(4, 5); /** Returns the string that represents some message code. * * @param code The code to convert to a string. * @return The string representing the message code. */ FOUNDATION_EXPORT NSString *_Nonnull GDTCORMessageCodeEnumToString(GDTCORMessageCode code); #define GDTCORLogDebug(MESSAGE_FORMAT, ...) \ GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelDebug, MESSAGE_FORMAT, __VA_ARGS__); // A define to wrap GULLogWarning with slightly more convenient usage. #define GDTCORLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelWarnings, MESSAGE_FORMAT, __VA_ARGS__); // A define to wrap GULLogError with slightly more convenient usage and a failing assert. #define GDTCORLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelErrors, MESSAGE_FORMAT, __VA_ARGS__); ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h ================================================ /* * 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 "GDTCORTargets.h" NS_ASSUME_NONNULL_BEGIN /* Class that manages the endpoints used by Google data transport library. */ @interface GDTCOREndpoints : NSObject - (instancetype)init NS_UNAVAILABLE; /** Returns the upload URL for a target specified. If the target is not available, returns nil. * * @param target GoogleDataTransport target for which the upload URL is being looked up for. * @return URL that will be used for uploading the events for the provided target. */ + (nullable NSURL *)uploadURLForTarget:(GDTCORTarget)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h ================================================ /* * Copyright 2018 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 "GDTCOREventDataObject.h" #import "GDTCORTargets.h" @class GDTCORClock; NS_ASSUME_NONNULL_BEGIN /** The different possible quality of service specifiers. High values indicate high priority. */ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { /** The QoS tier wasn't set, and won't ever be sent. */ GDTCOREventQoSUnknown = 0, /** This event is internal telemetry data that should not be sent on its own if possible. */ GDTCOREventQoSTelemetry = 1, /** This event should be sent, but in a batch only roughly once per day. */ GDTCOREventQoSDaily = 2, /** This event should be sent when requested by the uploader. */ GDTCOREventQosDefault = 3, /** This event should be sent immediately along with any other data that can be batched. */ GDTCOREventQoSFast = 4, /** This event should only be uploaded on wifi. */ GDTCOREventQoSWifiOnly = 5, }; @interface GDTCOREvent : NSObject /** The unique ID of the event. */ @property(readonly, nonatomic) NSString *eventID; /** The mapping identifier, to allow backends to map the transport bytes to a proto. */ @property(nullable, readonly, nonatomic) NSString *mappingID; /** The identifier for the backend this event will eventually be sent to. */ @property(readonly, nonatomic) GDTCORTarget target; /** The data object encapsulated in the transport of your choice, as long as it implements * the GDTCOREventDataObject protocol. */ @property(nullable, nonatomic) id dataObject; /** The serialized bytes from calling [dataObject transportBytes]. */ @property(nullable, readonly, nonatomic) NSData *serializedDataObjectBytes; /** The quality of service tier this event belongs to. */ @property(nonatomic) GDTCOREventQoS qosTier; /** The clock snapshot at the time of the event. */ @property(nonatomic) GDTCORClock *clockSnapshot; /** The expiration date of the event. Default is 604800 seconds (7 days) from creation. */ @property(nonatomic) NSDate *expirationDate; /** Bytes that can be used by an uploader later on. */ @property(nullable, nonatomic) NSData *customBytes; /** Initializes an instance using the given mappingID. * * @param mappingID The mapping identifier. * @param target The event's target identifier. * @return An instance of this class. */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h ================================================ /* * Copyright 2018 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 /** This protocol defines the common interface that event protos should implement regardless of the * underlying transport technology (protobuf, nanopb, etc). */ @protocol GDTCOREventDataObject @required /** Returns the serialized proto bytes of the implementing event proto. * * @return the serialized proto bytes of the implementing event proto. */ - (NSData *)transportBytes; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h ================================================ /* * Copyright 2018 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 @class GDTCOREvent; NS_ASSUME_NONNULL_BEGIN /** Defines the API that event transformers must adopt. */ @protocol GDTCOREventTransformer @required /** Transforms an event by applying some logic to it. Events returned can be nil, for example, in * instances where the event should be sampled. * * @param event The event to transform. * @return A transformed event, or nil if the transformation dropped the event. */ - (nullable GDTCOREvent *)transformGDTEvent:(GDTCOREvent *)event; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h ================================================ /* * 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 /** The list of targets supported by the shared transport infrastructure. If adding a new target, * please use the previous value +1. */ typedef NS_ENUM(NSInteger, GDTCORTarget) { /** A target only used in testing. */ kGDTCORTargetTest = 999, /** The CCT target. */ kGDTCORTargetCCT = 1000, /** The FLL target. */ kGDTCORTargetFLL = 1001, /** The CSH target. The CSH target is a special-purpose backend. Please do not use it without * permission. */ kGDTCORTargetCSH = 1002, /** The INT target. */ kGDTCORTargetINT = 1003, }; ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h ================================================ /* * Copyright 2018 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 "GDTCOREventTransformer.h" #import "GDTCORTargets.h" @class GDTCOREvent; NS_ASSUME_NONNULL_BEGIN @interface GDTCORTransport : NSObject // Please use the designated initializer. - (instancetype)init NS_UNAVAILABLE; /** Initializes a new transport that will send events to the given target backend. * * @param mappingID The mapping identifier used by the backend to map the data object transport * bytes to a proto. * @param transformers A list of transformers to be applied to events that are sent. * @param target The target backend of this transport. * @return A transport that will send events. */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID transformers: (nullable NSArray> *)transformers target:(GDTCORTarget)target NS_DESIGNATED_INITIALIZER; /** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, * and sometimes won't be sent on their own. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. * @param completion A block that will be called when the event has been written or dropped. */ - (void)sendTelemetryEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; /** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, * and sometimes won't be sent on their own. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. */ - (void)sendTelemetryEvent:(GDTCOREvent *)event; /** Copies and sends an SDK service data event. Events send using this API are higher in priority, * and will cause a network request at some point in the relative near future. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. * @param completion A block that will be called when the event has been written or dropped. */ - (void)sendDataEvent:(GDTCOREvent *)event onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion; /** Copies and sends an SDK service data event. Events send using this API are higher in priority, * and will cause a network request at some point in the relative near future. * * @note This will convert the event's data object to data and release the original event. * * @param event The event to send. */ - (void)sendDataEvent:(GDTCOREvent *)event; /** Creates an event for use by this transport. * * @return An event that is suited for use by this transport. */ - (GDTCOREvent *)eventForTransport; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleDataTransport/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h ================================================ /* * Copyright 2018 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 "GDTCORClock.h" #import "GDTCORConsoleLogger.h" #import "GDTCOREndpoints.h" #import "GDTCOREvent.h" #import "GDTCOREventDataObject.h" #import "GDTCOREventTransformer.h" #import "GDTCORTargets.h" #import "GDTCORTransport.h" ================================================ FILE: Pods/GoogleDataTransport/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/GoogleDataTransport/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport) [![License](https://img.shields.io/cocoapods/l/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport) [![Platform](https://img.shields.io/cocoapods/p/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport) [![Actions Status][gh-datatransport-badge]][gh-actions] # GoogleDataTransport This library is for internal Google use only. It allows the logging of data and telemetry from Google SDKs. ## Integration Testing These instructions apply to minor and patch version updates. Major versions need a customized adaptation. After the CI is green: * Determine the next version for release by checking the [tagged releases](https://github.com/google/GoogleDataTransport/tags). Ensure that the next release version keeps the Swift PM and CocoaPods versions in sync. * Verify that the releasing version is the latest entry in the [CHANGELOG.md](CHANGELOG.md), updating it if necessary. * Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md) * Checkout the `main` branch and ensure it is up to date. ```console git checkout main git pull ``` * Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleDataTransport.podspec#L3)) ```console git tag CocoaPods-{version} git push origin CocoaPods-{version} ``` * Push the podspec to the designated repo * If this version of GDT is intended to launch **before or with** the next Firebase release:
Push to SpecsStaging ```console pod repo push --skip-tests staging GoogleDataTransport.podspec ``` If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with: ```console pod repo add staging git@github.com:firebase/SpecsStaging.git ```
* Otherwise:
Push to SpecsDev ```console pod repo push --skip-tests dev GoogleDataTransport.podspec ``` If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with: ```console pod repo add dev git@github.com:firebase/SpecsDev.git ```
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`. * On google3, create a workspace and new CL. Then copybara and run a global TAP.
  /google/data/ro/teams/copybara/copybara third_party/firebase/ios/Releases/GoogleDataTransport/copy.bara.sky \
  --piper-description-behavior=OVERWRITE \
  --destination-cl=YOUR_CL gdt
  
## Publishing The release process is as follows: 1. [Tag and release for Swift PM](#swift-package-manager) 2. [Publish to CocoaPods](#cocoapods) 3. [Create GitHub Release](#create-github-release) 4. [Perform post release cleanup](#post-release-cleanup) ### Swift Package Manager By creating and [pushing a tag](https://github.com/google/GoogleDataTransport/tags) for Swift PM, the newly tagged version will be immediately released for public use. Given this, please verify the intended time of release for Swift PM. * Add a version tag for Swift PM ```console git tag {version} git push origin {version} ``` *Note: Ensure that any inflight PRs that depend on the new `GoogleDataTransport` version are updated to point to the newly tagged version rather than a checksum.* ### CocoaPods * Publish the newly versioned pod to CocoaPods It's recommended to point to the `GoogleDataTransport.podspec` in `staging` to make sure the correct spec is being published. ```console pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport.podspec --skip-tests ``` The pod push was successful if the above command logs: `🚀 GoogleDataTransport ({version}) successfully published`. In addition, a new commit that publishes the new version (co-authored by [CocoaPodsAtGoogle](https://github.com/CocoaPodsAtGoogle)) should appear in the [CocoaPods specs repo](https://github.com/CocoaPods/Specs). Last, the latest version should be displayed on [GoogleDataTransport's CocoaPods page](https://cocoapods.org/pods/GoogleDataTransport). ### [Create GitHub Release](https://github.com/google/GoogleDataTransport/releases/new/) Update the [release template](https://github.com/google/GoogleDataTransport/releases/new/)'s **Tag version** and **Release title** fields with the latest version. In addition, reference the [Release Notes](./CHANGELOG.md) in the release's description. See [this release](https://github.com/google/GoogleDataTransport/releases/edit/9.0.1) for an example. *Don't forget to perform the [post release cleanup](#post-release-cleanup)!* ### Post Release Cleanup
Clean up SpecsStaging ```console pwd=$(pwd) mkdir -p /tmp/release-cleanup && cd $_ git clone git@github.com:firebase/SpecsStaging.git cd SpecsStaging/ git rm -rf GoogleDataTransport/ git commit -m "Post publish cleanup" git push origin master rm -rf /tmp/release-cleanup cd $pwd ```
## Set logging level ### Swift - Import `GoogleDataTransport` module: ```swift import GoogleDataTransport ``` - Set logging level global variable to the desired value before calling `FirebaseApp.configure()`: ```swift GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue ``` ### Objective-C - Import `GoogleDataTransport`: ```objective-c #import ``` - Set logging level global variable to the desired value before calling `-[FIRApp configure]`: ```objective-c GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug; ``` ## Prereqs - `gem install --user cocoapods cocoapods-generate` - `brew install protobuf nanopb-generator` - `easy_install --user protobuf` ## To develop - Run `./GoogleDataTransport/generate_project.sh` after installing the prereqs ## When adding new logging endpoint - Use commands similar to: - `python -c "line='https://www.firebase.com'; print line[0::2]" ` - `python -c "line='https://www.firebase.com'; print line[1::2]" ` ## When adding internal code that shouldn't be easily usable on github - Consider using go/copybara-library/scrubbing#cc_scrub ## Development Ensure that you have at least the following software: * Xcode 12.0 (or later) * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: `pod gen GoogleDataTransport.podspec --local-sources=./ --auto-open --platforms=ios` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. ### Development for Catalyst * `pod gen GoogleDataTransport.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ## Contributing See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase iOS SDK. ## License The contents of this repository is licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m ================================================ // 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/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h" #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h" #import "GoogleUtilities/Common/GULLoggerCodes.h" #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" #import #import // Implementations need to be typed before calling the implementation directly to cast the // arguments and the return types correctly. Otherwise, it will crash the app. typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)( id, SEL, GULApplication *, NSURL *, NSString *, id); typedef BOOL (*GULRealOpenURLOptionsIMP)( id, SEL, GULApplication *, NSURL *, NSDictionary *); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wstrict-prototypes" typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)( id, SEL, GULApplication *, NSString *, void (^)()); #pragma clang diagnostic pop typedef BOOL (*GULRealContinueUserActivityIMP)( id, SEL, GULApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects)); typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSData *); typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSError *); typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, GULApplication *, NSDictionary *); #if !TARGET_OS_WATCH && !TARGET_OS_OSX typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult)); #endif // !TARGET_OS_WATCH && !TARGET_OS_OSX typedef void (^GULAppDelegateInterceptorCallback)(id); // The strings below are the keys for associated objects. static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector"; static char const *const kGULRealClassKey = "GUL_realClass"; static NSString *const kGULAppDelegateKeyPath = @"delegate"; static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]"; // Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change // we disable App Delegate proxying when either of these two flags are set to NO. /** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */ static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey = @"FirebaseAppDelegateProxyEnabled"; /** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying. */ static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey = @"GoogleUtilitiesAppDelegateProxyEnabled"; /** The prefix of the App Delegate. */ static NSString *const kGULAppDelegatePrefix = @"GUL_"; /** The original instance of App Delegate. */ static id gOriginalAppDelegate; /** The original App Delegate class */ static Class gOriginalAppDelegateClass; /** The subclass of the original App Delegate. */ static Class gAppDelegateSubclass; /** Remote notification methods selectors * * We have to opt out of referencing APNS related App Delegate methods directly to prevent * an Apple review warning email about missing Push Notification Entitlement * (like here: https://github.com/firebase/firebase-ios-sdk/issues/2807). From our experience, the * warning is triggered when any of the symbols is present in the application sent to review, even * if the code is never executed. Because GULAppDelegateSwizzler may be used by applications that * are not using APNS we have to refer to the methods indirectly using selector constructed from * string. * * NOTE: None of the methods is proxied unless it is explicitly requested by calling the method * +[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods] */ static NSString *const kGULDidRegisterForRemoteNotificationsSEL = @"application:didRegisterForRemoteNotificationsWithDeviceToken:"; static NSString *const kGULDidFailToRegisterForRemoteNotificationsSEL = @"application:didFailToRegisterForRemoteNotificationsWithError:"; static NSString *const kGULDidReceiveRemoteNotificationSEL = @"application:didReceiveRemoteNotification:"; static NSString *const kGULDidReceiveRemoteNotificationWithCompletionSEL = @"application:didReceiveRemoteNotification:fetchCompletionHandler:"; /** * This class is necessary to store the delegates in an NSArray without retaining them. * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is * dealloced. Instead, this container stores a weak, zeroing reference to the object, which * automatically is set to nil by the runtime when the object is dealloced. */ @interface GULZeroingWeakContainer : NSObject /** Stores a weak object. */ @property(nonatomic, weak) id object; @end @implementation GULZeroingWeakContainer @end @interface GULAppDelegateObserver : NSObject @end @implementation GULAppDelegateObserver { BOOL _isObserving; } + (GULAppDelegateObserver *)sharedInstance { static GULAppDelegateObserver *instance; static dispatch_once_t once; dispatch_once(&once, ^{ instance = [[GULAppDelegateObserver alloc] init]; }); return instance; } - (void)observeUIApplication { if (_isObserving) { return; } [[GULAppDelegateSwizzler sharedApplication] addObserver:self forKeyPath:kGULAppDelegateKeyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; _isObserving = YES; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:kGULAppDelegateKeyPath]) { id newValue = change[NSKeyValueChangeNewKey]; id oldValue = change[NSKeyValueChangeOldKey]; if ([newValue isEqual:oldValue]) { return; } // Free the stored app delegate instance because it has been changed to a different instance to // avoid keeping it alive forever. if ([oldValue isEqual:gOriginalAppDelegate]) { gOriginalAppDelegate = nil; // Remove the observer. Parse it to NSObject to avoid warning. [[GULAppDelegateSwizzler sharedApplication] removeObserver:self forKeyPath:kGULAppDelegateKeyPath]; _isObserving = NO; } } } @end @implementation GULAppDelegateSwizzler static dispatch_once_t sProxyAppDelegateOnceToken; static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken; #pragma mark - Public methods + (BOOL)isAppDelegateProxyEnabled { NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary; id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey]; id isGoogleProxyEnabledPlistValue = infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey]; // Enabled by default. BOOL isFirebaseAppDelegateProxyEnabled = YES; BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES; if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) { isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue]; } if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) { isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue]; } // Only deactivate the proxy if it is explicitly disabled by app developers using either one of // the plist flags. return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled; } + (GULAppDelegateInterceptorID)registerAppDelegateInterceptor: (id)interceptor { NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor"); NSAssert([interceptor conformsToProtocol:@protocol(GULApplicationDelegate)], @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate"); if (!interceptor) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling000], @"AppDelegateProxy cannot add nil interceptor."); return nil; } if (![interceptor conformsToProtocol:@protocol(GULApplicationDelegate)]) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling001], @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate"); return nil; } // The ID should be the same given the same interceptor object. NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULAppDelegatePrefix, interceptor]; if (!interceptorID.length) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling002], @"AppDelegateProxy cannot create Interceptor ID."); return nil; } GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init]; weakObject.object = interceptor; [GULAppDelegateSwizzler interceptors][interceptorID] = weakObject; return interceptorID; } + (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID { NSAssert(interceptorID, @"AppDelegateProxy cannot unregister nil interceptor ID."); NSAssert(((NSString *)interceptorID).length != 0, @"AppDelegateProxy cannot unregister empty interceptor ID."); if (!interceptorID) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling003], @"AppDelegateProxy cannot unregister empty interceptor ID."); return; } GULZeroingWeakContainer *weakContainer = [GULAppDelegateSwizzler interceptors][interceptorID]; if (!weakContainer.object) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling004], @"AppDelegateProxy cannot unregister interceptor that was not registered. " "Interceptor ID %@", interceptorID); return; } [[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID]; } + (void)proxyOriginalDelegate { if ([GULAppEnvironmentUtil isAppExtension]) { return; } dispatch_once(&sProxyAppDelegateOnceToken, ^{ id originalDelegate = [GULAppDelegateSwizzler sharedApplication].delegate; [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate]; }); } + (void)proxyOriginalDelegateIncludingAPNSMethods { if ([GULAppEnvironmentUtil isAppExtension]) { return; } [self proxyOriginalDelegate]; dispatch_once(&sProxyAppDelegateRemoteNotificationOnceToken, ^{ id appDelegate = [GULAppDelegateSwizzler sharedApplication].delegate; NSMutableDictionary *realImplementationsBySelector = [objc_getAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey) mutableCopy]; [self proxyRemoteNotificationsMethodsWithAppDelegateSubClass:gAppDelegateSubclass realClass:gOriginalAppDelegateClass appDelegate:appDelegate realImplementationsBySelector:realImplementationsBySelector]; objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey, [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN); [self reassignAppDelegate]; }); } #pragma mark - Create proxy + (GULApplication *)sharedApplication { if ([GULAppEnvironmentUtil isAppExtension]) { return nil; } id sharedApplication = nil; Class uiApplicationClass = NSClassFromString(kGULApplicationClassName); if (uiApplicationClass && [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) { sharedApplication = [uiApplicationClass sharedApplication]; } return sharedApplication; } #pragma mark - Override default methods /** Creates a new subclass of the class of the given object and sets the isa value of the given * object to the new subclass. Additionally this copies methods to that new subclass that allow us * to intercept UIApplicationDelegate methods. This is better known as isa swizzling. * * @param appDelegate The object to which you want to isa swizzle. This has to conform to the * UIApplicationDelegate subclass. * @return Returns the new subclass. */ + (nullable Class)createSubclassWithObject:(id)appDelegate { Class realClass = [appDelegate class]; // Create GUL__ NSString *classNameWithPrefix = [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)]; NSString *newClassName = [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; if (NSClassFromString(newClassName)) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling005], @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: " @"%@, subclass: %@", NSStringFromClass(realClass), newClassName); return nil; } // Register the new class as subclass of the real one. Do not allocate more than the real class // size. Class appDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0); if (appDelegateSubClass == Nil) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling006], @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: " @"%@, subclass: Nil", NSStringFromClass(realClass)); return nil; } NSMutableDictionary *realImplementationsBySelector = [[NSMutableDictionary alloc] init]; // For application:continueUserActivity:restorationHandler: SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:); [self proxyDestinationSelector:continueUserActivitySEL implementationsFromSourceSelector:continueUserActivitySEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; #if TARGET_OS_IOS || TARGET_OS_TV // Add the following methods from GULAppDelegate class, and store the real implementation so it // can forward to the real one. // For application:openURL:options: SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:); if ([appDelegate respondsToSelector:applicationOpenURLOptionsSEL]) { // Only add the application:openURL:options: method if the original AppDelegate implements it. // This fixes a bug if an app only implements application:openURL:sourceApplication:annotation: // (if we add the `options` method, iOS sees that one exists and does not call the // `sourceApplication` method, which in this case is the only one the app implements). [self proxyDestinationSelector:applicationOpenURLOptionsSEL implementationsFromSourceSelector:applicationOpenURLOptionsSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; } // For application:handleEventsForBackgroundURLSession:completionHandler: SEL handleEventsForBackgroundURLSessionSEL = @selector(application: handleEventsForBackgroundURLSession:completionHandler:); [self proxyDestinationSelector:handleEventsForBackgroundURLSessionSEL implementationsFromSourceSelector:handleEventsForBackgroundURLSessionSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; #endif // TARGET_OS_IOS || TARGET_OS_TV #if TARGET_OS_IOS // For application:openURL:sourceApplication:annotation: SEL openURLSourceApplicationAnnotationSEL = @selector(application: openURL:sourceApplication:annotation:); [self proxyDestinationSelector:openURLSourceApplicationAnnotationSEL implementationsFromSourceSelector:openURLSourceApplicationAnnotationSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; #endif // TARGET_OS_IOS // Override the description too so the custom class name will not show up. [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description) withImplementationFromSourceSelector:@selector(fakeDescription) fromClass:[self class] toClass:appDelegateSubClass]; // Store original implementations to a fake property of the original delegate. objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey, [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(appDelegate, &kGULRealClassKey, realClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // The subclass size has to be exactly the same size with the original class size. The subclass // cannot have more ivars/properties than its superclass since it will cause an offset in memory // that can lead to overwriting the isa of an object in the next frame. if (class_getInstanceSize(realClass) != class_getInstanceSize(appDelegateSubClass)) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling007], @"Cannot create subclass of App Delegate, because the created subclass is not the " @"same size. %@", NSStringFromClass(realClass)); NSAssert(NO, @"Classes must be the same size to swizzle isa"); return nil; } // Make the newly created class to be the subclass of the real App Delegate class. objc_registerClassPair(appDelegateSubClass); if (object_setClass(appDelegate, appDelegateSubClass)) { GULLogDebug(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling008], @"Successfully created App Delegate Proxy automatically. To disable the " @"proxy, set the flag %@ to NO (Boolean) in the Info.plist", [GULAppDelegateSwizzler correctAppDelegateProxyKey]); } return appDelegateSubClass; } + (void)proxyRemoteNotificationsMethodsWithAppDelegateSubClass:(Class)appDelegateSubClass realClass:(Class)realClass appDelegate:(id)appDelegate realImplementationsBySelector: (NSMutableDictionary *)realImplementationsBySelector { if (realClass == nil || appDelegateSubClass == nil || appDelegate == nil || realImplementationsBySelector == nil) { // The App Delegate has not been swizzled. return; } // For application:didRegisterForRemoteNotificationsWithDeviceToken: SEL didRegisterForRemoteNotificationsSEL = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL); SEL didRegisterForRemoteNotificationsDonorSEL = @selector(application: donor_didRegisterForRemoteNotificationsWithDeviceToken:); [self proxyDestinationSelector:didRegisterForRemoteNotificationsSEL implementationsFromSourceSelector:didRegisterForRemoteNotificationsDonorSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; // For application:didFailToRegisterForRemoteNotificationsWithError: SEL didFailToRegisterForRemoteNotificationsSEL = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL); SEL didFailToRegisterForRemoteNotificationsDonorSEL = @selector(application: donor_didFailToRegisterForRemoteNotificationsWithError:); [self proxyDestinationSelector:didFailToRegisterForRemoteNotificationsSEL implementationsFromSourceSelector:didFailToRegisterForRemoteNotificationsDonorSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; // For application:didReceiveRemoteNotification: SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL); SEL didReceiveRemoteNotificationDonotSEL = @selector(application: donor_didReceiveRemoteNotification:); [self proxyDestinationSelector:didReceiveRemoteNotificationSEL implementationsFromSourceSelector:didReceiveRemoteNotificationDonotSEL 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]; } #endif // !TARGET_OS_WATCH && !TARGET_OS_OSX } /// We have to do this to invalidate the cache that caches the original respondsToSelector of /// openURL handlers. Without this, it won't call the default implementations because the system /// checks and caches them. /// Register KVO only once. Otherwise, the observing method will be called as many times as /// being registered. + (void)reassignAppDelegate { #if !TARGET_OS_WATCH id delegate = [self sharedApplication].delegate; [self sharedApplication].delegate = nil; [self sharedApplication].delegate = delegate; gOriginalAppDelegate = delegate; [[GULAppDelegateObserver sharedInstance] observeUIApplication]; #endif } #pragma mark - Helper methods + (GULMutableDictionary *)interceptors { static dispatch_once_t onceToken; static GULMutableDictionary *sInterceptors; dispatch_once(&onceToken, ^{ sInterceptors = [[GULMutableDictionary alloc] init]; }); return sInterceptors; } + (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object { NSDictionary *realImplementationBySelector = objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey); return realImplementationBySelector[NSStringFromSelector(selector)]; } + (void)proxyDestinationSelector:(SEL)destinationSelector implementationsFromSourceSelector:(SEL)sourceSelector fromClass:(Class)sourceClass toClass:(Class)destinationClass realClass:(Class)realClass storeDestinationImplementationTo: (NSMutableDictionary *)destinationImplementationsBySelector { [self addInstanceMethodWithDestinationSelector:destinationSelector withImplementationFromSourceSelector:sourceSelector fromClass:sourceClass toClass:destinationClass]; IMP sourceImplementation = [GULAppDelegateSwizzler implementationOfMethodSelector:destinationSelector fromClass:realClass]; NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation]; NSString *destinationSelectorString = NSStringFromSelector(destinationSelector); destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer; } /** Copies a method identified by the methodSelector from one class to the other. After this method * is called, performing [toClassInstance methodSelector] will be similar to calling * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method * identified by methodSelector. * * @param methodSelector The SEL that identifies both the method on the fromClass as well as the * one on the toClass. * @param fromClass The class from which a method is sourced. * @param toClass The class to which the method is added. If the class already has a method with * the same selector, this has no effect. */ + (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass { [self addInstanceMethodWithDestinationSelector:methodSelector withImplementationFromSourceSelector:methodSelector fromClass:fromClass toClass:toClass]; } /** Copies a method identified by the sourceSelector from the fromClass as a method for the * destinationSelector on the toClass. After this method is called, performing * [toClassInstance destinationSelector] will be similar to calling * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method * identified by destinationSelector. * * @param destinationSelector The SEL that identifies the method on the toClass. * @param sourceSelector The SEL that identifies the method on the fromClass. * @param fromClass The class from which a method is sourced. * @param toClass The class to which the method is added. If the class already has a method with * the same selector, this has no effect. */ + (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector withImplementationFromSourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass { Method method = class_getInstanceMethod(fromClass, sourceSelector); IMP methodIMP = method_getImplementation(method); const char *types = method_getTypeEncoding(method); if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) { GULLogWarning(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling009], @"Cannot copy method to destination selector %@ as it already exists", NSStringFromSelector(destinationSelector)); } } /** Gets the IMP of the instance method on the class identified by the selector. * * @param selector The selector of which the IMP is to be fetched. * @param aClass The class from which the IMP is to be fetched. * @return The IMP of the instance method identified by selector and aClass. */ + (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass { Method aMethod = class_getInstanceMethod(aClass, selector); return method_getImplementation(aMethod); } /** Enumerates through all the interceptors and if they respond to a given selector, executes a * GULAppDelegateInterceptorCallback with the interceptor. * * @param methodSelector The SEL to check if an interceptor responds to. * @param callback the GULAppDelegateInterceptorCallback. */ + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector callback:(GULAppDelegateInterceptorCallback)callback { if (!callback) { return; } NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary; [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { GULZeroingWeakContainer *interceptorContainer = obj; id interceptor = interceptorContainer.object; if (!interceptor) { GULLogWarning( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010], @"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key); [[GULAppDelegateSwizzler interceptors] removeObjectForKey:key]; return; } if ([interceptor respondsToSelector:methodSelector]) { callback(interceptor); } }]; } // The methods below are donor methods which are added to the dynamic subclass of the App Delegate. // They are called within the scope of the real App Delegate so |self| does not refer to the // GULAppDelegateSwizzler instance but the real App Delegate instance. #pragma mark - [Donor Methods] Overridden instance description method - (NSString *)fakeDescription { Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey); return [NSString stringWithFormat:@"<%@: %p>", realClass, self]; } #pragma mark - [Donor Methods] URL overridden handler methods #if TARGET_OS_IOS || TARGET_OS_TV - (BOOL)application:(GULApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { SEL methodSelector = @selector(application:openURL:options:); // Call the real implementation if the real App Delegate has any. NSValue *openURLIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue]; __block BOOL returnedValue = NO; // This is needed to for the library to be warning free on iOS versions < 9. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { returnedValue |= [interceptor application:application openURL:url options:options]; }]; #pragma clang diagnostic pop if (openURLOptionsIMP) { returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options); } return returnedValue; } #endif // TARGET_OS_IOS || TARGET_OS_TV #if TARGET_OS_IOS - (BOOL)application:(GULApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:); // Call the real implementation if the real App Delegate has any. NSValue *openURLSourceAppAnnotationIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = [openURLSourceAppAnnotationIMPPointer pointerValue]; __block BOOL returnedValue = NO; [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" returnedValue |= [interceptor application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; #pragma clang diagnostic pop }]; if (openURLSourceApplicationAnnotationIMP) { returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url, sourceApplication, annotation); } return returnedValue; } #endif // TARGET_OS_IOS #pragma mark - [Donor Methods] Network overridden handler methods #if TARGET_OS_IOS || TARGET_OS_TV #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wstrict-prototypes" - (void)application:(GULApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) { #pragma clang diagnostic pop SEL methodSelector = @selector(application: handleEventsForBackgroundURLSession:completionHandler:); NSValue *handleBackgroundSessionPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP = [handleBackgroundSessionPointer pointerValue]; // Notify interceptors. [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { [interceptor application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; }]; // Call the real implementation if the real App Delegate has any. if (handleBackgroundSessionIMP) { handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler); } } #endif // TARGET_OS_IOS || TARGET_OS_TV #pragma mark - [Donor Methods] User Activities overridden handler methods - (BOOL)application:(GULApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler { SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:); NSValue *continueUserActivityIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealContinueUserActivityIMP continueUserActivityIMP = continueUserActivityIMPPointer.pointerValue; __block BOOL returnedValue = NO; #if !TARGET_OS_WATCH [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { returnedValue |= [interceptor application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; }]; #endif // Call the real implementation if the real App Delegate has any. if (continueUserActivityIMP) { returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity, restorationHandler); } return returnedValue; } #pragma mark - [Donor Methods] Remote Notifications - (void)application:(GULApplication *)application donor_didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { SEL methodSelector = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL); NSValue *didRegisterForRemoteNotificationsIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = [didRegisterForRemoteNotificationsIMPPointer pointerValue]; // Notify interceptors. [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 *)(&deviceToken) atIndex:3]; [invocation invoke]; }]; // Call the real implementation if the real App Delegate has any. if (didRegisterForRemoteNotificationsIMP) { didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken); } } - (void)application:(GULApplication *)application donor_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { SEL methodSelector = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL); NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = [didFailToRegisterForRemoteNotificationsIMPPointer pointerValue]; // Notify interceptors. [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 *)(&error) atIndex:3]; [invocation invoke]; }]; // Call the real implementation if the real App Delegate has any. if (didFailToRegisterForRemoteNotificationsIMP) { didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error); } } #if !TARGET_OS_WATCH && !TARGET_OS_OSX - (void)application:(GULApplication *)application donor_didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL); NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULRealDidReceiveRemoteNotificationWithCompletionIMP didReceiveRemoteNotificationWithCompletionIMP = [didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue]; dispatch_group_t __block callbackGroup = dispatch_group_create(); NSMutableArray *__block fetchResults = [NSMutableArray array]; void (^localCompletionHandler)(UIBackgroundFetchResult) = ^void(UIBackgroundFetchResult fetchResult) { [fetchResults addObject:[NSNumber numberWithInt:(int)fetchResult]]; dispatch_group_leave(callbackGroup); }; // Notify interceptors. [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { dispatch_group_enter(callbackGroup); NSInvocation *invocation = [GULAppDelegateSwizzler appDelegateInvocationForSelector:methodSelector]; [invocation setTarget:interceptor]; [invocation setSelector:methodSelector]; [invocation setArgument:(void *)(&application) atIndex:2]; [invocation setArgument:(void *)(&userInfo) atIndex:3]; [invocation setArgument:(void *)(&localCompletionHandler) atIndex:4]; [invocation invoke]; }]; // Call the real implementation if the real App Delegate has any. if (didReceiveRemoteNotificationWithCompletionIMP) { dispatch_group_enter(callbackGroup); didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo, localCompletionHandler); } dispatch_group_notify(callbackGroup, dispatch_get_main_queue(), ^() { BOOL allFetchesFailed = YES; BOOL anyFetchHasNewData = NO; for (NSNumber *oneResult in fetchResults) { UIBackgroundFetchResult result = oneResult.intValue; switch (result) { case UIBackgroundFetchResultNoData: allFetchesFailed = NO; break; case UIBackgroundFetchResultNewData: allFetchesFailed = NO; anyFetchHasNewData = YES; break; case UIBackgroundFetchResultFailed: break; } } UIBackgroundFetchResult finalFetchResult = UIBackgroundFetchResultNoData; if (allFetchesFailed) { finalFetchResult = UIBackgroundFetchResultFailed; } else if (anyFetchHasNewData) { finalFetchResult = UIBackgroundFetchResultNewData; } else { finalFetchResult = UIBackgroundFetchResultNoData; } completionHandler(finalFetchResult); }); } #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); if (methodDescription.types == NULL) { return nil; } NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; return [NSInvocation invocationWithMethodSignature:signature]; } + (void)proxyAppDelegate:(id)appDelegate { if (![appDelegate conformsToProtocol:@protocol(GULApplicationDelegate)]) { GULLogNotice( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], @"App Delegate does not conform to UIApplicationDelegate protocol. %@", [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); return; } id originalDelegate = appDelegate; // Do not create a subclass if it is not enabled. if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) { GULLogNotice(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling011], @"App Delegate Proxy is disabled. %@", [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); return; } // Do not accept nil delegate. if (!originalDelegate) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling012], @"Cannot create App Delegate Proxy because App Delegate instance is nil. %@", [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); return; } @try { gOriginalAppDelegateClass = [originalDelegate class]; gAppDelegateSubclass = [self createSubclassWithObject:originalDelegate]; [self reassignAppDelegate]; } @catch (NSException *exception) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling013], @"Cannot create App Delegate Proxy. %@", [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); return; } } #pragma mark - Methods to print correct debug logs + (NSString *)correctAppDelegateProxyKey { return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey : kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey; } + (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated { return NSClassFromString(@"FIRCore") ? @"To log deep link campaigns manually, call the methods in " @"FIRAnalytics+AppDelegate.h." : @""; } #pragma mark - Private Methods for Testing + (void)clearInterceptors { [[self interceptors] removeAllObjects]; } + (void)resetProxyOriginalDelegateOnceToken { sProxyAppDelegateOnceToken = 0; sProxyAppDelegateRemoteNotificationOnceToken = 0; } + (id)originalDelegate { return gOriginalAppDelegate; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/GULSceneDelegateSwizzler.m ================================================ // 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 #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h" #import "GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h" #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h" #import "GoogleUtilities/Common/GULLoggerCodes.h" #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" #import #if UISCENE_SUPPORTED API_AVAILABLE(ios(13.0), tvos(13.0)) typedef void (*GULOpenURLContextsIMP)(id, SEL, UIScene *, NSSet *); API_AVAILABLE(ios(13.0), tvos(13.0)) typedef void (^GULSceneDelegateInterceptorCallback)(id); // The strings below are the keys for associated objects. static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector"; static char const *const kGULRealClassKey = "GUL_realClass"; #endif // UISCENE_SUPPORTED static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/SceneDelegateSwizzler]"; // Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change // we disable App Delegate proxying when either of these two flags are set to NO. /** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */ static NSString *const kGULFirebaseSceneDelegateProxyEnabledPlistKey = @"FirebaseAppDelegateProxyEnabled"; /** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying. */ static NSString *const kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey = @"GoogleUtilitiesAppDelegateProxyEnabled"; /** The prefix of the Scene Delegate. */ static NSString *const kGULSceneDelegatePrefix = @"GUL_"; /** * This class is necessary to store the delegates in an NSArray without retaining them. * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is * dealloced. Instead, this container stores a weak, zeroing reference to the object, which * automatically is set to nil by the runtime when the object is dealloced. */ @interface GULSceneZeroingWeakContainer : NSObject /** Stores a weak object. */ @property(nonatomic, weak) id object; @end @implementation GULSceneZeroingWeakContainer @end @implementation GULSceneDelegateSwizzler #pragma mark - Public methods + (BOOL)isSceneDelegateProxyEnabled { return [GULAppDelegateSwizzler isAppDelegateProxyEnabled]; } + (void)proxyOriginalSceneDelegate { #if UISCENE_SUPPORTED if ([GULAppEnvironmentUtil isAppExtension]) { return; } static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (@available(iOS 13.0, tvOS 13.0, *)) { if (![GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]) { return; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSceneWillConnectToNotification:) name:UISceneWillConnectNotification object:nil]; } }); #endif // UISCENE_SUPPORTED } #if UISCENE_SUPPORTED + (GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor:(id)interceptor { NSAssert(interceptor, @"SceneDelegateProxy cannot add nil interceptor"); NSAssert([interceptor conformsToProtocol:@protocol(UISceneDelegate)], @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate"); if (!interceptor) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000], @"SceneDelegateProxy cannot add nil interceptor."); return nil; } if (![interceptor conformsToProtocol:@protocol(UISceneDelegate)]) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001], @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate"); return nil; } // The ID should be the same given the same interceptor object. NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULSceneDelegatePrefix, interceptor]; if (!interceptorID.length) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002], @"SceneDelegateProxy cannot create Interceptor ID."); return nil; } GULSceneZeroingWeakContainer *weakObject = [[GULSceneZeroingWeakContainer alloc] init]; weakObject.object = interceptor; [GULSceneDelegateSwizzler interceptors][interceptorID] = weakObject; return interceptorID; } + (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID { NSAssert(interceptorID, @"SceneDelegateProxy cannot unregister nil interceptor ID."); NSAssert(((NSString *)interceptorID).length != 0, @"SceneDelegateProxy cannot unregister empty interceptor ID."); if (!interceptorID) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003], @"SceneDelegateProxy cannot unregister empty interceptor ID."); return; } GULSceneZeroingWeakContainer *weakContainer = [GULSceneDelegateSwizzler interceptors][interceptorID]; if (!weakContainer.object) { GULLogError(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004], @"SceneDelegateProxy cannot unregister interceptor that was not registered. " "Interceptor ID %@", interceptorID); return; } [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:interceptorID]; } #pragma mark - Helper methods + (GULMutableDictionary *)interceptors { static dispatch_once_t onceToken; static GULMutableDictionary *sInterceptors; dispatch_once(&onceToken, ^{ sInterceptors = [[GULMutableDictionary alloc] init]; }); return sInterceptors; } + (void)clearInterceptors { [[self interceptors] removeAllObjects]; } + (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object { NSDictionary *realImplementationBySelector = objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey); return realImplementationBySelector[NSStringFromSelector(selector)]; } + (void)proxyDestinationSelector:(SEL)destinationSelector implementationsFromSourceSelector:(SEL)sourceSelector fromClass:(Class)sourceClass toClass:(Class)destinationClass realClass:(Class)realClass storeDestinationImplementationTo: (NSMutableDictionary *)destinationImplementationsBySelector { [self addInstanceMethodWithDestinationSelector:destinationSelector withImplementationFromSourceSelector:sourceSelector fromClass:sourceClass toClass:destinationClass]; IMP sourceImplementation = [GULSceneDelegateSwizzler implementationOfMethodSelector:destinationSelector fromClass:realClass]; NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation]; NSString *destinationSelectorString = NSStringFromSelector(destinationSelector); destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer; } /** Copies a method identified by the methodSelector from one class to the other. After this method * is called, performing [toClassInstance methodSelector] will be similar to calling * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method * identified by methodSelector. * * @param methodSelector The SEL that identifies both the method on the fromClass as well as the * one on the toClass. * @param fromClass The class from which a method is sourced. * @param toClass The class to which the method is added. If the class already has a method with * the same selector, this has no effect. */ + (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass { [self addInstanceMethodWithDestinationSelector:methodSelector withImplementationFromSourceSelector:methodSelector fromClass:fromClass toClass:toClass]; } /** Copies a method identified by the sourceSelector from the fromClass as a method for the * destinationSelector on the toClass. After this method is called, performing * [toClassInstance destinationSelector] will be similar to calling * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method * identified by destinationSelector. * * @param destinationSelector The SEL that identifies the method on the toClass. * @param sourceSelector The SEL that identifies the method on the fromClass. * @param fromClass The class from which a method is sourced. * @param toClass The class to which the method is added. If the class already has a method with * the same selector, this has no effect. */ + (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector withImplementationFromSourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass { Method method = class_getInstanceMethod(fromClass, sourceSelector); IMP methodIMP = method_getImplementation(method); const char *types = method_getTypeEncoding(method); if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) { GULLogWarning( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling009], @"Cannot copy method to destination selector %@ as it already exists", NSStringFromSelector(destinationSelector)); } } /** Gets the IMP of the instance method on the class identified by the selector. * * @param selector The selector of which the IMP is to be fetched. * @param aClass The class from which the IMP is to be fetched. * @return The IMP of the instance method identified by selector and aClass. */ + (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass { Method aMethod = class_getInstanceMethod(aClass, selector); return method_getImplementation(aMethod); } /** Enumerates through all the interceptors and if they respond to a given selector, executes a * GULSceneDelegateInterceptorCallback with the interceptor. * * @param methodSelector The SEL to check if an interceptor responds to. * @param callback the GULSceneDelegateInterceptorCallback. */ + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector callback:(GULSceneDelegateInterceptorCallback)callback API_AVAILABLE(ios(13.0)) { if (!callback) { return; } NSDictionary *interceptors = [GULSceneDelegateSwizzler interceptors].dictionary; [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { GULSceneZeroingWeakContainer *interceptorContainer = obj; id interceptor = interceptorContainer.object; if (!interceptor) { GULLogWarning( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling010], @"SceneDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key); [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:key]; return; } if ([interceptor respondsToSelector:methodSelector]) { callback(interceptor); } }]; } + (void)handleSceneWillConnectToNotification:(NSNotification *)notification { if (@available(iOS 13.0, tvOS 13.0, *)) { if ([notification.object isKindOfClass:[UIScene class]]) { UIScene *scene = (UIScene *)notification.object; [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:scene]; } } } #pragma mark - [Donor Methods] UISceneDelegate URL handler - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts API_AVAILABLE(ios(13.0), tvos(13.0)) { if (@available(iOS 13.0, tvOS 13.0, *)) { SEL methodSelector = @selector(scene:openURLContexts:); // Call the real implementation if the real Scene Delegate has any. NSValue *openURLContextsIMPPointer = [GULSceneDelegateSwizzler originalImplementationForSelector:methodSelector object:self]; GULOpenURLContextsIMP openURLContextsIMP = [openURLContextsIMPPointer pointerValue]; [GULSceneDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { if ([interceptor conformsToProtocol:@protocol(UISceneDelegate)]) { id sceneInterceptor = (id)interceptor; [sceneInterceptor scene:scene openURLContexts:URLContexts]; } }]; if (openURLContextsIMP) { openURLContextsIMP(self, methodSelector, scene, URLContexts); } } } + (void)proxySceneDelegateIfNeeded:(UIScene *)scene { Class realClass = [scene.delegate class]; NSString *className = NSStringFromClass(realClass); // Skip proxying if failed to get the delegate class name for some reason (e.g. `delegate == nil`) // or the class has a prefix of kGULAppDelegatePrefix, which means it has been proxied before. if (className == nil || [className hasPrefix:kGULSceneDelegatePrefix]) { return; } NSString *classNameWithPrefix = [kGULSceneDelegatePrefix stringByAppendingString:className]; NSString *newClassName = [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; if (NSClassFromString(newClassName)) { GULLogError( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long) kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class" @": %@, subclass: %@", className, newClassName); return; } // Register the new class as subclass of the real one. Do not allocate more than the real class // size. Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0); if (sceneDelegateSubClass == Nil) { GULLogError( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long) kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class" @": %@, subclass: Nil", className); return; } NSMutableDictionary *realImplementationsBySelector = [[NSMutableDictionary alloc] init]; // For scene:openURLContexts: SEL openURLContextsSEL = @selector(scene:openURLContexts:); [self proxyDestinationSelector:openURLContextsSEL implementationsFromSourceSelector:openURLContextsSEL fromClass:[GULSceneDelegateSwizzler class] toClass:sceneDelegateSubClass realClass:realClass storeDestinationImplementationTo:realImplementationsBySelector]; // Store original implementations to a fake property of the original delegate. objc_setAssociatedObject(scene.delegate, &kGULRealIMPBySelectorKey, [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(scene.delegate, &kGULRealClassKey, realClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // The subclass size has to be exactly the same size with the original class size. The subclass // cannot have more ivars/properties than its superclass since it will cause an offset in memory // that can lead to overwriting the isa of an object in the next frame. if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) { GULLogError( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long) kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], @"Cannot create subclass of Scene Delegate, because the created subclass is not the " @"same size. %@", className); NSAssert(NO, @"Classes must be the same size to swizzle isa"); return; } // Make the newly created class to be the subclass of the real Scene Delegate class. objc_registerClassPair(sceneDelegateSubClass); if (object_setClass(scene.delegate, sceneDelegateSubClass)) { GULLogDebug( kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long) kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate], @"Successfully created Scene Delegate Proxy automatically. To disable the " @"proxy, set the flag %@ to NO (Boolean) in the Info.plist", [GULSceneDelegateSwizzler correctSceneDelegateProxyKey]); } } + (NSString *)correctSceneDelegateProxyKey { return NSClassFromString(@"FIRCore") ? kGULFirebaseSceneDelegateProxyEnabledPlistKey : kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey; } #endif // UISCENE_SUPPORTED @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h ================================================ /* * 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/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" @class GULApplication; NS_ASSUME_NONNULL_BEGIN @interface GULAppDelegateSwizzler () /** ISA Swizzles the given appDelegate as the original app delegate would be. * * @param appDelegate The object that needs to be isa swizzled. This should conform to the * application delegate protocol. */ + (void)proxyAppDelegate:(id)appDelegate; /** Returns a dictionary containing interceptor IDs mapped to a GULZeroingWeakContainer. * * @return A dictionary of the form {NSString : GULZeroingWeakContainer}, where the NSString is * the interceptorID. */ + (GULMutableDictionary *)interceptors; /** Deletes all the registered interceptors. */ + (void)clearInterceptors; /** Resets the token that prevents the app delegate proxy from being isa swizzled multiple times. */ + (void)resetProxyOriginalDelegateOnceToken; /** Returns the original app delegate that was proxied. * * @return The original app delegate instance that was proxied. */ + (id)originalDelegate; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h ================================================ /* * 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 #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" NS_ASSUME_NONNULL_BEGIN @interface GULSceneDelegateSwizzler () #if UISCENE_SUPPORTED /** Returns a dictionary containing interceptor IDs mapped to a GULZeroingWeakContainer. * * @return A dictionary of the form {NSString : GULZeroingWeakContainer}, where the NSString is * the interceptorID. */ + (GULMutableDictionary *)interceptors; /** Deletes all the registered interceptors. */ + (void)clearInterceptors; /** ISA Swizzles the given appDelegate as the original app delegate would be. * * @param scene The scene whose delegate needs to be isa swizzled. This should conform to the * scene delegate protocol. */ + (void)proxySceneDelegateIfNeeded:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0)); #endif // UISCENE_SUPPORTED @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h ================================================ /* * 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 "GULApplication.h" NS_ASSUME_NONNULL_BEGIN typedef NSString *const GULAppDelegateInterceptorID; /** This class contains methods that isa swizzle the app delegate. */ @interface GULAppDelegateSwizzler : NSProxy /** Registers an app delegate interceptor whose methods will be invoked as they're invoked on the * original app delegate. * * @param interceptor An instance of a class that conforms to the application delegate protocol. * The interceptor is NOT retained. * @return A unique GULAppDelegateInterceptorID if interceptor was successfully registered; nil * if it fails. */ + (nullable GULAppDelegateInterceptorID)registerAppDelegateInterceptor: (id)interceptor; /** Unregisters an interceptor with the given ID if it exists. * * @param interceptorID The object that was generated when the interceptor was registered. */ + (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID; /** This method ensures that the original app delegate has been proxied. Call this before * registering your interceptor. This method is safe to call multiple times (but it only proxies * the app delegate once). * * This method doesn't proxy APNS related methods: * @code * - application:didRegisterForRemoteNotificationsWithDeviceToken: * - application:didFailToRegisterForRemoteNotificationsWithError: * - application:didReceiveRemoteNotification:fetchCompletionHandler: * - application:didReceiveRemoteNotification: * @endcode * * To proxy these methods use +[GULAppDelegateSwizzler * proxyOriginalDelegateIncludingAPNSMethods]. The methods have to be proxied separately to * avoid potential warnings from Apple review about missing Push Notification Entitlement (e.g. * https://github.com/firebase/firebase-ios-sdk/issues/2807) * * The method has no effect for extensions. * * @see proxyOriginalDelegateIncludingAPNSMethods */ + (void)proxyOriginalDelegate; /** This method ensures that the original app delegate has been proxied including APNS related * methods. Call this before registering your interceptor. This method is safe to call multiple * times (but it only proxies the app delegate once) or * after +[GULAppDelegateSwizzler proxyOriginalDelegate] * * This method calls +[GULAppDelegateSwizzler proxyOriginalDelegate] under the hood. * After calling this method the following App Delegate methods will be proxied in addition to * the methods proxied by proxyOriginalDelegate: * @code * - application:didRegisterForRemoteNotificationsWithDeviceToken: * - application:didFailToRegisterForRemoteNotificationsWithError: * - application:didReceiveRemoteNotification:fetchCompletionHandler: * - application:didReceiveRemoteNotification: * @endcode * * The method has no effect for extensions. * * @see proxyOriginalDelegate */ + (void)proxyOriginalDelegateIncludingAPNSMethods; /** Indicates whether app delegate proxy is explicitly disabled or enabled. Enabled by default. * * @return YES if AppDelegateProxy is Enabled, NO otherwise. */ + (BOOL)isAppDelegateProxyEnabled; /** Returns the current sharedApplication. * * @return the current application instance if in an app, or nil if in extension or if it doesn't * exist. */ + (nullable GULApplication *)sharedApplication; /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; NS_ASSUME_NONNULL_END @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULApplication.h ================================================ /* * 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 #if TARGET_OS_IOS || TARGET_OS_TV #import #define GULApplication UIApplication #define GULApplicationDelegate UIApplicationDelegate #define GULUserActivityRestoring UIUserActivityRestoring static NSString *const kGULApplicationClassName = @"UIApplication"; #elif TARGET_OS_OSX #import #define GULApplication NSApplication #define GULApplicationDelegate NSApplicationDelegate #define GULUserActivityRestoring NSUserActivityRestoring static NSString *const kGULApplicationClassName = @"NSApplication"; #elif TARGET_OS_WATCH #import // We match the according watchOS API but swizzling should not work in watch #define GULApplication WKExtension #define GULApplicationDelegate WKExtensionDelegate #define GULUserActivityRestoring NSUserActivityRestoring static NSString *const kGULApplicationClassName = @"WKExtension"; #endif ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h ================================================ /* * 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 #import #if !TARGET_OS_OSX #import #endif // !TARGET_OS_OSX #if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000)) #define UISCENE_SUPPORTED 1 #endif NS_ASSUME_NONNULL_BEGIN typedef NSString *const GULSceneDelegateInterceptorID; /** This class contains methods that isa swizzle the scene delegate. */ @interface GULSceneDelegateSwizzler : NSProxy #if UISCENE_SUPPORTED /** Registers a scene delegate interceptor whose methods will be invoked as they're invoked on the * original scene delegate. * * @param interceptor An instance of a class that conforms to the application delegate protocol. * The interceptor is NOT retained. * @return A unique GULSceneDelegateInterceptorID if interceptor was successfully registered; nil * if it fails. */ + (nullable GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor: (id)interceptor API_AVAILABLE(ios(13.0), tvos(13.0)); /** Unregisters an interceptor with the given ID if it exists. * * @param interceptorID The object that was generated when the interceptor was registered. */ + (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID API_AVAILABLE(ios(13.0), tvos(13.0)); /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; #endif // UISCENE_SUPPORTED /** This method ensures that the original scene delegate has been proxied. Call this before * registering your interceptor. This method is safe to call multiple times (but it only proxies * the scene delegate once). * * The method has no effect for extensions. */ + (void)proxyOriginalSceneDelegate; /** Indicates whether scene delegate proxy is explicitly disabled or enabled. Enabled by default. * * @return YES if SceneDelegateProxy is Enabled, NO otherwise. */ + (BOOL)isSceneDelegateProxyEnabled; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Common/GULLoggerCodes.h ================================================ /* * 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 typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) { // App Delegate Swizzling. kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014 // Scene Delegate Swizzling. kGULSwizzlerMessageCodeSceneDelegateSwizzling000 = 1100, // I-SWZ001100 kGULSwizzlerMessageCodeSceneDelegateSwizzling001 = 1101, // I-SWZ001101 kGULSwizzlerMessageCodeSceneDelegateSwizzling002 = 1102, // I-SWZ001102 kGULSwizzlerMessageCodeSceneDelegateSwizzling003 = 1103, // I-SWZ001103 kGULSwizzlerMessageCodeSceneDelegateSwizzling004 = 1104, // I-SWZ001104 kGULSwizzlerMessageCodeSceneDelegateSwizzling005 = 1105, // I-SWZ001105 kGULSwizzlerMessageCodeSceneDelegateSwizzling006 = 1106, // I-SWZ001106 kGULSwizzlerMessageCodeSceneDelegateSwizzling007 = 1107, // I-SWZ001107 kGULSwizzlerMessageCodeSceneDelegateSwizzling008 = 1108, // I-SWZ001108 kGULSwizzlerMessageCodeSceneDelegateSwizzling009 = 1109, // I-SWZ001109 kGULSwizzlerMessageCodeSceneDelegateSwizzling010 = 1110, // I-SWZ001110 kGULSwizzlerMessageCodeSceneDelegateSwizzling011 = 1111, // I-SWZ001111 kGULSwizzlerMessageCodeSceneDelegateSwizzling012 = 1112, // I-SWZ001112 kGULSwizzlerMessageCodeSceneDelegateSwizzling013 = 1113, // I-SWZ001113 kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate = 1114, // I-SWZ001114 // Method Swizzling. kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000 }; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/GULHeartbeatDateStorage.m ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/GULHeartbeatDateStorageUserDefaults.m ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/GULSecureCoding.m ================================================ // 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)class fromData:(NSData *)data error:(NSError **)outError { return [self unarchivedObjectOfClasses:[NSSet setWithObject:class] 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h ================================================ /* * Copyright 2017 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 @interface GULAppEnvironmentUtil : NSObject /// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator, /// development environment or sideloaded. + (BOOL)isFromAppStore; /// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt. /// Returns NO otherwise. + (BOOL)isAppStoreReceiptSandbox; /// Indicates whether the app is on simulator or not at runtime depending on the device /// architecture. + (BOOL)isSimulator; /// The current device model. Returns an empty string if device model cannot be retrieved. + (nullable NSString *)deviceModel; /// The current operating system version. Returns an empty string if the system version cannot be /// retrieved. + (NSString *)systemVersion; /// 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; /// @return An Apple platform. Possible values "ios", "tvos", "macos", "watchos", "maccatalyst". + (NSString *)applePlatform; /// @return The way the library was added to the app, e.g. "swiftpm", "cocoapods", etc. + (NSString *)deploymentType; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h ================================================ /* * 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 @class FBLPromise; NS_ASSUME_NONNULL_BEGIN /// The class provides a convenient abstraction on top of the iOS Keychain API to save data. @interface GULKeychainStorage : NSObject - (instancetype)init NS_UNAVAILABLE; /** Initializes the keychain storage with Keychain Service name. * @param service A Keychain Service name that will be used to store and retrieve objects. See also * `kSecAttrService`. */ - (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; /** * 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; /** * 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; #if TARGET_OS_OSX /// If not `nil`, then only this keychain will be used to save and read data (see /// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests. @property(nonatomic, nullable) SecKeychainRef keychainRef; #endif // TARGET_OSX @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h ================================================ /* * 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 FOUNDATION_EXPORT NSString *const kGULKeychainUtilsErrorDomain; /// Helper functions to access Keychain. @interface GULKeychainUtils : NSObject /** Fetches a keychain item data matching to the provided query. * @param query A dictionary with Keychain query parameters. See docs for `SecItemCopyMatching` for * details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns Data for the first Keychain Item matching the provided query or `nil` if there is not * such an item (`outError` will be `nil` in this case) or an error occurred. */ + (nullable NSData *)getItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; /** Stores data to a Keychain Item matching to the provided query. An existing Keychain Item * matching the query parameters will be updated or a new will be created. * @param item A Keychain Item data to store. * @param query A dictionary with Keychain query parameters. See docs for `SecItemAdd` and * `SecItemUpdate` for details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns `YES` when data was successfully stored, `NO` otherwise. */ + (BOOL)setItem:(NSData *)item withQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; /** Removes a Keychain Item matching to the provided query. * @param query A dictionary with Keychain query parameters. See docs for `SecItemDelete` for * details. * @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be * assigned with an error if there is. * @returns `YES` if the item was removed successfully or doesn't exist, `NO` otherwise. */ + (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h ================================================ // 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)class fromData:(NSData *)data error:(NSError **)outError; + (nullable NSData *)archivedDataWithRootObject:(id)object error:(NSError **)outError; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m ================================================ /* * 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/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; @property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue; @property(nonatomic, readonly) NSString *service; @property(nonatomic, readonly) NSCache> *inMemoryCache; @end @implementation GULKeychainStorage - (instancetype)initWithService:(NSString *)service { NSCache *cache = [[NSCache alloc] init]; // Cache up to 5 installations. cache.countLimit = 5; return [self initWithService:service cache:cache]; } - (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache { self = [super init]; if (self) { _keychainQueue = dispatch_queue_create("com.gul.KeychainStorage.Keychain", DISPATCH_QUEUE_SERIAL); _inMemoryCacheQueue = dispatch_queue_create("com.gul.KeychainStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL); _service = [service copy]; _inMemoryCache = cache; } return self; } #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]; }); } - (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]; }); } - (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]; }); } #pragma mark - Private - (FBLPromise> *)getObjectFromKeychainForKey:(NSString *)key objectClass:(Class)objectClass accessGroup:(nullable NSString *)accessGroup { // 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; }); } - (void)resetInMemoryCache { [self.inMemoryCache removeAllObjects]; } #pragma mark - Keychain - (NSMutableDictionary *)keychainQueryWithKey:(NSString *)key accessGroup:(nullable NSString *)accessGroup { NSMutableDictionary *query = [NSMutableDictionary dictionary]; query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword; query[(__bridge NSString *)kSecAttrService] = self.service; query[(__bridge NSString *)kSecAttrAccount] = key; if (accessGroup) { query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup; } #if TARGET_OS_OSX if (self.keychainRef) { query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef); query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ]; } #endif // TARGET_OSX return query; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/SecureStorage/GULKeychainUtils.m ================================================ /* * 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/GULKeychainUtils.h" NSString *const kGULKeychainUtilsErrorDomain = @"com.gul.keychain.ErrorDomain"; @implementation GULKeychainUtils + (nullable NSData *)getItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { NSMutableDictionary *mutableQuery = [query mutableCopy]; mutableQuery[(__bridge id)kSecReturnData] = @YES; mutableQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne; CFDataRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)mutableQuery, (CFTypeRef *)&result); if (status == errSecSuccess && result != NULL) { if (outError) { *outError = nil; } return (__bridge_transfer NSData *)result; } if (status == errSecItemNotFound) { if (outError) { *outError = nil; } } else { if (outError) { *outError = [self keychainErrorWithFunction:@"SecItemCopyMatching" status:status]; } } return nil; } + (BOOL)setItem:(NSData *)item withQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { NSData *existingItem = [self getItemWithQuery:query error:outError]; if (outError && *outError) { return NO; } NSMutableDictionary *mutableQuery = [query mutableCopy]; mutableQuery[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; OSStatus status; if (!existingItem) { mutableQuery[(__bridge id)kSecValueData] = item; status = SecItemAdd((__bridge CFDictionaryRef)mutableQuery, NULL); } else { NSDictionary *attributes = @{(__bridge id)kSecValueData : item}; status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); } if (status == noErr) { if (outError) { *outError = nil; } return YES; } NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd"; if (outError) { *outError = [self keychainErrorWithFunction:function status:status]; } return NO; } + (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); if (status == noErr || status == errSecItemNotFound) { if (outError) { *outError = nil; } return YES; } if (outError) { *outError = [self keychainErrorWithFunction:@"SecItemDelete" status:status]; } return NO; } #pragma mark - Errors + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey : failureReason}; return [NSError errorWithDomain:kGULKeychainUtilsErrorDomain code:0 userInfo:userInfo]; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m ================================================ /* * 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 ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m ================================================ // Copyright 2017 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/GULAppEnvironmentUtil.h" #import #import #import #import #import #if TARGET_OS_IOS #import #endif /// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from /// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just /// provide the definitions here. #if TARGET_OS_SIMULATOR && !defined(LC_ENCRYPTION_INFO) #define LC_ENCRYPTION_INFO 0x21 struct encryption_info_command { uint32_t cmd; uint32_t cmdsize; uint32_t cryptoff; uint32_t cryptsize; uint32_t cryptid; }; #endif @implementation GULAppEnvironmentUtil /// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox. /// This will affect your data integrity when using Firebase Analytics, as it will disable some /// necessary checks. static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey = @"FirebaseAppStoreReceiptURLCheckEnabled"; /// The file name of the sandbox receipt. This is available on iOS >= 8.0 static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt"; /// The following copyright from Landon J. Fuller applies to the isAppEncrypted function. /// /// Copyright (c) 2017 Landon J. Fuller /// All rights reserved. /// /// Permission is hereby granted, free of charge, to any person obtaining a copy of this software /// and associated documentation files (the "Software"), to deal in the Software without /// restriction, including without limitation the rights to use, copy, modify, merge, publish, /// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the /// Software is furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in all copies or /// substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING /// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND /// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, /// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /// /// Comment from iPhone Dev Wiki /// Crack Prevention: /// App Store binaries are signed by both their developer and Apple. This encrypts the binary so /// that decryption keys are needed in order to make the binary readable. When iOS executes the /// binary, the decryption keys are used to decrypt the binary into a readable state where it is /// then loaded into memory and executed. iOS can tell the encryption status of a binary via the /// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero /// value then the binary is encrypted. /// /// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into /// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as /// codesignature validation has been removed. Resigning takes place because while the codesignature /// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have /// AppSync or similar to disable codesignature checks. /// /// More information at Landon Fuller's blog static BOOL IsAppEncrypted() { const struct mach_header *executableHeader = NULL; for (uint32_t i = 0; i < _dyld_image_count(); i++) { const struct mach_header *header = _dyld_get_image_header(i); if (header && header->filetype == MH_EXECUTE) { executableHeader = header; break; } } if (!executableHeader) { return NO; } BOOL is64bit = (executableHeader->magic == MH_MAGIC_64); uintptr_t cursor = (uintptr_t)executableHeader + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); const struct segment_command *segmentCommand = NULL; uint32_t i = 0; while (i++ < executableHeader->ncmds) { segmentCommand = (struct segment_command *)cursor; if (!segmentCommand) { continue; } if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) || (is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) { if (is64bit) { struct encryption_info_command_64 *cryptCmd = (struct encryption_info_command_64 *)segmentCommand; return cryptCmd && cryptCmd->cryptid != 0; } else { struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand; return cryptCmd && cryptCmd->cryptid != 0; } } cursor += segmentCommand->cmdsize; } return NO; } static BOOL HasSCInfoFolder() { #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH NSString *bundlePath = [NSBundle mainBundle].bundlePath; NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"]; return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath]; #elif TARGET_OS_OSX return NO; #endif } static BOOL HasEmbeddedMobileProvision() { #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0; #elif TARGET_OS_OSX return NO; #endif } + (BOOL)isFromAppStore { static dispatch_once_t isEncryptedOnce; static BOOL isEncrypted = NO; dispatch_once(&isEncryptedOnce, ^{ isEncrypted = IsAppEncrypted(); }); if ([GULAppEnvironmentUtil isSimulator]) { return NO; } // If an app contain the sandboxReceipt file, it means its coming from TestFlight // This must be checked before the SCInfo Folder check below since TestFlight apps may // also have an SCInfo folder. if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox]) { return NO; } if (HasSCInfoFolder()) { // When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the // main SC_Info directory. return YES; } // For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read // the iTunesMetadata.plist outside of the sandbox will be rejected by Apple. // If the app does not contain the embedded.mobileprovision which is stripped out by Apple when // the app is submitted to store, then it is highly likely that it is from Apple Store. return isEncrypted && !HasEmbeddedMobileProvision(); } + (BOOL)isAppStoreReceiptSandbox { // Since checking the App Store's receipt URL can be memory intensive, check the option in the // Info.plist if developers opted out of this check. id enableSandboxCheck = [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey]; if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] && ![enableSandboxCheck boolValue]) { return NO; } NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL; NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent; return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName]; } + (BOOL)isSimulator { #if TARGET_OS_SIMULATOR return YES; #elif TARGET_OS_MACCATALYST return NO; #elif TARGET_OS_IOS || TARGET_OS_TV NSString *platform = [GULAppEnvironmentUtil deviceModel]; return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"]; #elif TARGET_OS_OSX return NO; #endif return NO; } + (NSString *)deviceModel { static dispatch_once_t once; static NSString *deviceModel; dispatch_once(&once, ^{ struct utsname systemInfo; if (uname(&systemInfo) == 0) { deviceModel = [NSString stringWithUTF8String:systemInfo.machine]; } }); return deviceModel; } + (NSString *)systemVersion { #if TARGET_OS_IOS return [UIDevice currentDevice].systemVersion; #elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH // Assemble the systemVersion, excluding the patch version if it's 0. NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; NSMutableString *versionString = [[NSMutableString alloc] initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion]; if (osVersion.patchVersion != 0) { [versionString appendFormat:@".%ld", (long)osVersion.patchVersion]; } return versionString; #endif } + (BOOL)isAppExtension { #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH // Documented by Apple BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; return appExtension; #elif TARGET_OS_OSX return NO; #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"; // When a Catalyst app is run on macOS then both `TARGET_OS_MACCATALYST` and `TARGET_OS_IOS` are // `true`, which means the condition list is order-sensitive. #if TARGET_OS_MACCATALYST applePlatform = @"maccatalyst"; #elif TARGET_OS_IOS #if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 if (@available(iOS 14.0, *)) { // Early iOS 14 betas do not include isiOSAppOnMac (#6969) applePlatform = ([[NSProcessInfo processInfo] respondsToSelector:@selector(isiOSAppOnMac)] && [NSProcessInfo processInfo].isiOSAppOnMac) ? @"ios_on_mac" : @"ios"; } else { applePlatform = @"ios"; } #else // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 applePlatform = @"ios"; #endif // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 #elif TARGET_OS_TV applePlatform = @"tvos"; #elif TARGET_OS_OSX applePlatform = @"macos"; #elif TARGET_OS_WATCH applePlatform = @"watchos"; #endif // TARGET_OS_MACCATALYST return applePlatform; } + (NSString *)deploymentType { #if SWIFT_PACKAGE NSString *deploymentType = @"swiftpm"; #elif FIREBASE_BUILD_CARTHAGE NSString *deploymentType = @"carthage"; #elif FIREBASE_BUILD_ZIP_FILE NSString *deploymentType = @"zip"; #else NSString *deploymentType = @"cocoapods"; #endif return deploymentType; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Logger/GULLogger.m ================================================ // Copyright 2018 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/Logger/Public/GoogleUtilities/GULLogger.h" #include #import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h" /// ASL client facility name used by GULLogger. const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger"; static dispatch_once_t sGULLoggerOnceToken; static aslclient sGULLoggerClient; static dispatch_queue_t sGULClientQueue; static BOOL sGULLoggerDebugMode; static GULLoggerLevel sGULLoggerMaximumLevel; // Allow clients to register a version to include in the log. static NSString *sVersion = @""; static GULLoggerService kGULLoggerLogger = @"[GULLogger]"; #ifdef DEBUG /// The regex pattern for the message code. static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; static NSRegularExpression *sMessageCodeRegex; #endif void GULLoggerInitializeASL(void) { dispatch_once(&sGULLoggerOnceToken, ^{ NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue]; uint32_t aslOptions = ASL_OPT_STDERR; #if TARGET_OS_SIMULATOR // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag. if (majorOSVersion >= 11) { aslOptions = 0; } #else // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag. if (majorOSVersion >= 10) { aslOptions = 0; } #endif // TARGET_OS_SIMULATOR #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated // Initialize the ASL client handle. sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions); sGULLoggerMaximumLevel = GULLoggerLevelNotice; // Set the filter used by system/device log. Initialize in default mode. asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(sGULClientQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); #ifdef DEBUG sMessageCodeRegex = [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL]; #endif }); } void GULLoggerEnableSTDERR(void) { asl_add_log_file(sGULLoggerClient, STDERR_FILENO); } void GULLoggerForceDebug(void) { // We should enable debug mode if we're not running from App Store. if (![GULAppEnvironmentUtil isFromAppStore]) { sGULLoggerDebugMode = YES; GULSetLoggerLevel(GULLoggerLevelDebug); } } __attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) { if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) { GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld", (long)loggerLevel); return; } GULLoggerInitializeASL(); // We should not raise the logger level if we are running from App Store. if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { return; } sGULLoggerMaximumLevel = loggerLevel; dispatch_async(sGULClientQueue, ^{ asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel)); }); } /** * Check if the level is high enough to be loggable. */ __attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) { GULLoggerInitializeASL(); if (sGULLoggerDebugMode) { return YES; } return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel); } #ifdef DEBUG void GULResetLogger(void) { sGULLoggerOnceToken = 0; sGULLoggerDebugMode = NO; } aslclient getGULLoggerClient(void) { return sGULLoggerClient; } dispatch_queue_t getGULClientQueue(void) { return sGULClientQueue; } BOOL getGULLoggerDebugMode(void) { return sGULLoggerDebugMode; } #endif void GULLoggerRegisterVersion(NSString *version) { sVersion = version; } void GULLogBasic(GULLoggerLevel level, GULLoggerService service, BOOL forceLog, NSString *messageCode, NSString *message, va_list args_ptr) { GULLoggerInitializeASL(); if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) { return; } #ifdef DEBUG NSCAssert(messageCode.length == 11, @"Incorrect message code length."); NSRange messageCodeRange = NSMakeRange(0, messageCode.length); NSUInteger numberOfMatches = [sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange]; NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); #endif NSString *logMsg; if (args_ptr == NULL) { logMsg = message; } else { logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr]; } logMsg = [NSString stringWithFormat:@"%@ - %@[%@] %@", sVersion, service, messageCode, logMsg]; dispatch_async(sGULClientQueue, ^{ asl_log(sGULLoggerClient, NULL, (int)level, "%s", logMsg.UTF8String); }); } #pragma clang diagnostic pop /** * Generates the logging functions using macros. * * Calling GULLogError({service}, @"I-XYZ000001", @"Configure %@ failed.", @"blah") shows: * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-XYZ000001] Configure blah failed. * Calling GULLogDebug({service}, @"I-XYZ000001", @"Configure succeed.") shows: * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-XYZ000001] Configure succeed. */ #define GUL_LOGGING_FUNCTION(level) \ void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \ NSString *message, ...) { \ va_list args_ptr; \ va_start(args_ptr, message); \ GULLogBasic(GULLoggerLevel##level, service, force, messageCode, message, args_ptr); \ va_end(args_ptr); \ } GUL_LOGGING_FUNCTION(Error) GUL_LOGGING_FUNCTION(Warning) GUL_LOGGING_FUNCTION(Notice) GUL_LOGGING_FUNCTION(Info) GUL_LOGGING_FUNCTION(Debug) #undef GUL_MAKE_LOGGER #pragma mark - GULLoggerWrapper @implementation GULLoggerWrapper + (void)logWithLevel:(GULLoggerLevel)level withService:(GULLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args { GULLogBasic(level, service, NO, messageCode, message, args); } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h ================================================ /* * Copyright 2018 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 "GULLoggerLevel.h" NS_ASSUME_NONNULL_BEGIN /** * The services used in the logger. */ typedef NSString *const GULLoggerService; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Initialize GULLogger. */ extern void GULLoggerInitializeASL(void); /** * Override log level to Debug. */ void GULLoggerForceDebug(void); /** * Turn on logging to STDERR. */ extern void GULLoggerEnableSTDERR(void); /** * Changes the default logging level of GULLoggerLevelNotice to a user-specified level. * The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store. * (required) log level (one of the GULLoggerLevel enum values). */ extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel); /** * Checks if the specified logger level is loggable given the current settings. * (required) log level (one of the GULLoggerLevel enum values). */ extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel); /** * Register version to include in logs. * (required) version */ extern void GULLoggerRegisterVersion(NSString *version); /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming. * (required) log level (one of the GULLoggerLevel enum values). * (required) service name of type GULLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ extern void GULLogBasic(GULLoggerLevel level, GULLoggerService service, BOOL forceLog, NSString *messageCode, NSString *message, // On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable // See: http://stackoverflow.com/q/29095469 #if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX va_list args_ptr #else va_list _Nullable args_ptr #endif ); /** * The following functions accept the following parameters in order: * (required) service name of type GULLoggerService. * (required) message code starting from "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * See go/firebase-log-proposal for details. * (required) message string which can be a format string. * (optional) the list of arguments to substitute into the format string. * Example usage: * GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); */ extern void GULLogError(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogWarning(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogNotice(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogInfo(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); extern void GULLogDebug(GULLoggerService service, BOOL force, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(4, 5); #ifdef __cplusplus } // extern "C" #endif // __cplusplus @interface GULLoggerWrapper : NSObject /** * Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger * (required) log level (one of the GULLoggerLevel enum values). * (required) service name of type GULLoggerService. * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * (required) message string which can be a format string. * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ + (void)logWithLevel:(GULLoggerLevel)level withService:(GULLoggerService)service withCode:(NSString *)messageCode withMessage:(NSString *)message withArgs:(va_list)args; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h ================================================ /* * Copyright 2018 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 /** * The log levels used by internal logging. */ typedef NS_ENUM(NSInteger, GULLoggerLevel) { /** Error level, matches ASL_LEVEL_ERR. */ GULLoggerLevelError = 3, /** Warning level, matches ASL_LEVEL_WARNING. */ GULLoggerLevelWarning = 4, /** Notice level, matches ASL_LEVEL_NOTICE. */ GULLoggerLevelNotice = 5, /** Info level, matches ASL_LEVEL_INFO. */ GULLoggerLevelInfo = 6, /** Debug level, matches ASL_LEVEL_DEBUG. */ GULLoggerLevelDebug = 7, /** Minimum log level. */ GULLoggerLevelMin = GULLoggerLevelError, /** Maximum log level. */ GULLoggerLevelMax = GULLoggerLevelDebug } NS_SWIFT_NAME(GoogleLoggerLevel); ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/GULSwizzler.m ================================================ // 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/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h" #import #ifdef DEBUG #import "GoogleUtilities/Common/GULLoggerCodes.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/MethodSwizzler]"; #endif dispatch_queue_t GetGULSwizzlingQueue(void) { static dispatch_queue_t queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("com.google.GULSwizzler", DISPATCH_QUEUE_SERIAL); }); return queue; } @implementation GULSwizzler + (void)swizzleClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector withBlock:(nullable id)block { dispatch_sync(GetGULSwizzlingQueue(), ^{ NSAssert(selector, @"The selector cannot be NULL"); NSAssert(aClass, @"The class cannot be Nil"); Class resolvedClass = aClass; Method method = nil; if (isClassSelector) { method = class_getClassMethod(aClass, selector); resolvedClass = object_getClass(aClass); } else { method = class_getInstanceMethod(aClass, selector); } NSAssert(method, @"You're attempting to swizzle a method that doesn't exist. (%@, %@)", NSStringFromClass(resolvedClass), NSStringFromSelector(selector)); IMP newImp = imp_implementationWithBlock(block); #ifdef DEBUG IMP currentImp = class_getMethodImplementation(resolvedClass, selector); Class class = NSClassFromString(@"GULSwizzlingCache"); if (class) { SEL cacheSelector = NSSelectorFromString(@"cacheCurrentIMP:forNewIMP:forClass:withSelector:"); NSMethodSignature *methodSignature = [class methodSignatureForSelector:cacheSelector]; if (methodSignature != nil) { NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; [inv setSelector:cacheSelector]; [inv setTarget:class]; [inv setArgument:&(currentImp) atIndex:2]; [inv setArgument:&(newImp) atIndex:3]; [inv setArgument:&(resolvedClass) atIndex:4]; [inv setArgument:(void *_Nonnull)&(selector) atIndex:5]; [inv invoke]; } } #endif const char *typeEncoding = method_getTypeEncoding(method); __unused IMP originalImpOfClass = class_replaceMethod(resolvedClass, selector, newImp, typeEncoding); #ifdef DEBUG // If !originalImpOfClass, then the IMP came from a superclass. if (originalImpOfClass) { SEL selector = NSSelectorFromString(@"originalIMPOfCurrentIMP:"); NSMethodSignature *methodSignature = [class methodSignatureForSelector:selector]; if (methodSignature != nil) { NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; [inv setSelector:selector]; [inv setTarget:class]; [inv setArgument:&(currentImp) atIndex:2]; [inv invoke]; IMP testOriginal; [inv getReturnValue:&testOriginal]; if (originalImpOfClass != testOriginal) { GULLogWarning(kGULLoggerSwizzler, NO, [NSString stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeMethodSwizzling000], @"Swizzling class: %@ SEL:%@ after it has been previously been swizzled.", NSStringFromClass(resolvedClass), NSStringFromSelector(selector)); } } } #endif }); } + (nullable IMP)currentImplementationForClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector { NSAssert(selector, @"The selector cannot be NULL"); NSAssert(aClass, @"The class cannot be Nil"); if (selector == NULL || aClass == nil) { return nil; } __block IMP currentIMP = nil; dispatch_sync(GetGULSwizzlingQueue(), ^{ Method method = nil; if (isClassSelector) { method = class_getClassMethod(aClass, selector); } else { method = class_getInstanceMethod(aClass, selector); } NSAssert(method, @"The Method for this class/selector combo doesn't exist (%@, %@).", NSStringFromClass(aClass), NSStringFromSelector(selector)); if (method == nil) { return; } currentIMP = method_getImplementation(method); NSAssert(currentIMP, @"The IMP for this class/selector combo doesn't exist (%@, %@).", NSStringFromClass(aClass), NSStringFromSelector(selector)); }); return currentIMP; } + (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector { Method method = isClassSelector ? class_getClassMethod(aClass, selector) : class_getInstanceMethod(aClass, selector); return method != nil; } + (NSArray *)ivarObjectsForObject:(id)object { NSMutableArray *array = [NSMutableArray array]; unsigned int count; Ivar *vars = class_copyIvarList([object class], &count); for (NSUInteger i = 0; i < count; i++) { const char *typeEncoding = ivar_getTypeEncoding(vars[i]); // Check to see if the ivar is an object. if (strncmp(typeEncoding, "@", 1) == 0) { id ivarObject = object_getIvar(object, vars[i]); [array addObject:ivarObject]; } } free(vars); return array; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h ================================================ /* * 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. */ /** * GULOriginalIMPConvenienceMacros.h * * This header contains convenience macros for invoking the original IMP of a swizzled method. */ /** * Invokes original IMP when the original selector takes no arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. */ #define GUL_INVOKE_ORIGINAL_IMP0(__receivingObject, __swizzledSEL, __returnType, __originalIMP) \ ((__returnType(*)(id, SEL))__originalIMP)(__receivingObject, __swizzledSEL) /** * Invokes original IMP when the original selector takes 1 argument. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. */ #define GUL_INVOKE_ORIGINAL_IMP1(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1) \ ((__returnType(*)(id, SEL, __typeof__(__arg1)))__originalIMP)(__receivingObject, __swizzledSEL, \ __arg1) /** * Invokes original IMP when the original selector takes 2 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. */ #define GUL_INVOKE_ORIGINAL_IMP2(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2) /** * Invokes original IMP when the original selector takes 3 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. */ #define GUL_INVOKE_ORIGINAL_IMP3(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), \ __typeof__(__arg3)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \ __arg2, __arg3) /** * Invokes original IMP when the original selector takes 4 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. */ #define GUL_INVOKE_ORIGINAL_IMP4(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \ __arg2, __arg3, __arg4) /** * Invokes original IMP when the original selector takes 5 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. * @param __arg5 The fifth argument. */ #define GUL_INVOKE_ORIGINAL_IMP5(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4, __arg5) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4), __typeof__(__arg5)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5) /** * Invokes original IMP when the original selector takes 6 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. * @param __arg5 The fifth argument. * @param __arg6 The sixth argument. */ #define GUL_INVOKE_ORIGINAL_IMP6(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4, __arg5, __arg6) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6) /** * Invokes original IMP when the original selector takes 7 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. * @param __arg5 The fifth argument. * @param __arg6 The sixth argument. * @param __arg7 The seventh argument. */ #define GUL_INVOKE_ORIGINAL_IMP7(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ __typeof__(__arg7)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7) /** * Invokes original IMP when the original selector takes 8 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. * @param __arg5 The fifth argument. * @param __arg6 The sixth argument. * @param __arg7 The seventh argument. * @param __arg8 The eighth argument. */ #define GUL_INVOKE_ORIGINAL_IMP8(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ __typeof__(__arg7), __typeof__(__arg8)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \ __arg8) /** * Invokes original IMP when the original selector takes 9 arguments. * * @param __receivingObject The object on which the IMP is invoked. * @param __swizzledSEL The selector used for swizzling. * @param __returnType The return type of the original implementation. * @param __originalIMP The original IMP. * @param __arg1 The first argument. * @param __arg2 The second argument. * @param __arg3 The third argument. * @param __arg4 The fourth argument. * @param __arg5 The fifth argument. * @param __arg6 The sixth argument. * @param __arg7 The seventh argument. * @param __arg8 The eighth argument. * @param __arg9 The ninth argument. */ #define GUL_INVOKE_ORIGINAL_IMP9(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \ __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8, \ __arg9) \ ((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \ __typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \ __typeof__(__arg7), __typeof__(__arg8), __typeof__(__arg9)))__originalIMP)( \ __receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \ __arg8, __arg9) ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h ================================================ /* * 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 /** This class handles the runtime manipulation necessary to instrument selectors. It stores the * classes and selectors that have been swizzled, and runs all operations on its own queue. */ @interface GULSwizzler : NSObject /** Manipulates the Objective-C runtime to replace the original IMP with the supplied block. * * @param aClass The class to swizzle. * @param selector The selector of the class to swizzle. * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. * @param block The block that replaces the original IMP. */ + (void)swizzleClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector withBlock:(nullable id)block; /** Returns the current IMP for the given class and selector. * * @param aClass The class to use. * @param selector The selector to find the implementation of. * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. * @return The implementation of the selector in the runtime. */ + (nullable IMP)currentImplementationForClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector; /** Checks the runtime to see if a selector exists on a class. If a property is declared as * @dynamic, we have a reverse swizzling situation, where the implementation of a method exists * only in concrete subclasses, and NOT in the superclass. We can detect that situation using * this helper method. Similarly, we can detect situations where a class doesn't implement a * protocol method. * * @param selector The selector to check for. * @param aClass The class to check. * @param isClassSelector A BOOL specifying whether the selector is a class or instance selector. * @return YES if the method was found in this selector/class combination, NO otherwise. */ + (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector; /** Returns a list of all Objective-C (and not primitive) ivars contained by the given object. * * @param object The object whose ivars will be iterated. * @return The list of ivar objects. */ + (NSArray *)ivarObjectsForObject:(id)object; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/GULNSData+zlib.m ================================================ // Copyright 2018 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/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h" #import #define kChunkSize 1024 #define Z_DEFAULT_COMPRESSION (-1) NSString *const GULNSDataZlibErrorDomain = @"com.google.GULNSDataZlibErrorDomain"; NSString *const GULNSDataZlibErrorKey = @"GULNSDataZlibErrorKey"; NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKey"; @implementation NSData (GULGzip) + (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error { const void *bytes = [data bytes]; NSUInteger length = [data length]; if (!bytes || !length) { return nil; } #if defined(__LP64__) && __LP64__ // Don't support > 32bit length for 64 bit, see note in header. if (length > UINT_MAX) { return nil; } #endif z_stream strm; bzero(&strm, sizeof(z_stream)); // Setup the input. strm.avail_in = (unsigned int)length; strm.next_in = (unsigned char *)bytes; int windowBits = 15; // 15 to enable any window size windowBits += 32; // and +32 to enable zlib or gzip header detection. int retCode; if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { if (error) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] forKey:GULNSDataZlibErrorKey]; *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorInternal userInfo:userInfo]; } return nil; } // Hint the size at 4x the input size. NSMutableData *result = [NSMutableData dataWithCapacity:(length * 4)]; unsigned char output[kChunkSize]; // Loop to collect the data. do { // Update what we're passing in. strm.avail_out = kChunkSize; strm.next_out = output; retCode = inflate(&strm, Z_NO_FLUSH); if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { if (error) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] forKey:GULNSDataZlibErrorKey]; if (strm.msg) { NSString *message = [NSString stringWithUTF8String:strm.msg]; if (message) { [userInfo setObject:message forKey:NSLocalizedDescriptionKey]; } } *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorInternal userInfo:userInfo]; } inflateEnd(&strm); return nil; } // Collect what we got. unsigned gotBack = kChunkSize - strm.avail_out; if (gotBack > 0) { [result appendBytes:output length:gotBack]; } } while (retCode == Z_OK); // Make sure there wasn't more data tacked onto the end of a valid compressed stream. if (strm.avail_in != 0) { if (error) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in] forKey:GULNSDataZlibRemainingBytesKey]; *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorDataRemaining userInfo:userInfo]; } result = nil; } // The only way out of the loop was by hitting the end of the stream. NSAssert(retCode == Z_STREAM_END, @"Thought we finished inflate w/o getting a result of stream end, code %d", retCode); // Clean up. inflateEnd(&strm); return result; } + (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error { const void *bytes = [data bytes]; NSUInteger length = [data length]; int level = Z_DEFAULT_COMPRESSION; if (!bytes || !length) { return nil; } #if defined(__LP64__) && __LP64__ // Don't support > 32bit length for 64 bit, see note in header. if (length > UINT_MAX) { if (error) { *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorGreaterThan32BitsToCompress userInfo:nil]; } return nil; } #endif z_stream strm; bzero(&strm, sizeof(z_stream)); int memLevel = 8; // Default. int windowBits = 15 + 16; // Enable gzip header instead of zlib header. int retCode; if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) { if (error) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] forKey:GULNSDataZlibErrorKey]; *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorInternal userInfo:userInfo]; } return nil; } // Hint the size at 1/4 the input size. NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)]; unsigned char output[kChunkSize]; // Setup the input. strm.avail_in = (unsigned int)length; strm.next_in = (unsigned char *)bytes; // Collect the data. do { // update what we're passing in strm.avail_out = kChunkSize; strm.next_out = output; retCode = deflate(&strm, Z_FINISH); if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { if (error) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] forKey:GULNSDataZlibErrorKey]; *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain code:GULNSDataZlibErrorInternal userInfo:userInfo]; } deflateEnd(&strm); return nil; } // Collect what we got. unsigned gotBack = kChunkSize - strm.avail_out; if (gotBack > 0) { [result appendBytes:output length:gotBack]; } } while (retCode == Z_OK); // If the loop exits, it used all input and the stream ended. NSAssert(strm.avail_in == 0, @"Should have finished deflating without using all input, %u bytes left", strm.avail_in); NSAssert(retCode == Z_STREAM_END, @"thought we finished deflate w/o getting a result of stream end, code %d", retCode); // Clean up. deflateEnd(&strm); return result; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h ================================================ // Copyright 2018 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 /// 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 // such data. To handle data of that size you really should be streaming it rather then doing it all // in memory. @interface NSData (GULGzip) /// 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; /// 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; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain; FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber FOUNDATION_EXPORT NSString *const GULNSDataZlibRemainingBytesKey; // NSNumber typedef NS_ENUM(NSInteger, GULNSDataZlibError) { GULNSDataZlibErrorGreaterThan32BitsToCompress = 1024, // An internal zlib error. // GULNSDataZlibErrorKey will contain the error value. // NSLocalizedDescriptionKey may contain an error string from zlib. // Look in zlib.h for list of errors. GULNSDataZlibErrorInternal, // There was left over data in the buffer that was not used. // GULNSDataZlibRemainingBytesKey will contain number of remaining bytes. GULNSDataZlibErrorDataRemaining }; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/GULMutableDictionary.m ================================================ // Copyright 2017 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/Network/Public/GoogleUtilities/GULMutableDictionary.h" @implementation GULMutableDictionary { /// The mutable dictionary. NSMutableDictionary *_objects; /// Serial synchronization queue. All reads should use dispatch_sync, while writes use /// dispatch_async. dispatch_queue_t _queue; } - (instancetype)init { self = [super init]; if (self) { _objects = [[NSMutableDictionary alloc] init]; _queue = dispatch_queue_create("GULMutableDictionary", DISPATCH_QUEUE_SERIAL); } return self; } - (NSString *)description { __block NSString *description; dispatch_sync(_queue, ^{ description = self->_objects.description; }); return description; } - (id)objectForKey:(id)key { __block id object; dispatch_sync(_queue, ^{ object = [self->_objects objectForKey:key]; }); return object; } - (void)setObject:(id)object forKey:(id)key { dispatch_async(_queue, ^{ [self->_objects setObject:object forKey:key]; }); } - (void)removeObjectForKey:(id)key { dispatch_async(_queue, ^{ [self->_objects removeObjectForKey:key]; }); } - (void)removeAllObjects { dispatch_async(_queue, ^{ [self->_objects removeAllObjects]; }); } - (NSUInteger)count { __block NSUInteger count; dispatch_sync(_queue, ^{ count = self->_objects.count; }); return count; } - (id)objectForKeyedSubscript:(id)key { __block id object; dispatch_sync(_queue, ^{ object = self->_objects[key]; }); return object; } - (void)setObject:(id)obj forKeyedSubscript:(id)key { dispatch_async(_queue, ^{ self->_objects[key] = obj; }); } - (NSDictionary *)dictionary { __block NSDictionary *dictionary; dispatch_sync(_queue, ^{ dictionary = [self->_objects copy]; }); return dictionary; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/GULNetwork.m ================================================ // Copyright 2017 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/Network/Public/GoogleUtilities/GULNetwork.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" #import "GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h" #import "GoogleUtilities/Network/GULNetworkInternal.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h" #import "GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h" /// Constant string for request header Content-Encoding. static NSString *const kGULNetworkContentCompressionKey = @"Content-Encoding"; /// Constant string for request header Content-Encoding value. static NSString *const kGULNetworkContentCompressionValue = @"gzip"; /// Constant string for request header Content-Length. static NSString *const kGULNetworkContentLengthKey = @"Content-Length"; /// Constant string for request header Content-Type. static NSString *const kGULNetworkContentTypeKey = @"Content-Type"; /// Constant string for request header Content-Type value. static NSString *const kGULNetworkContentTypeValue = @"application/x-www-form-urlencoded"; /// Constant string for GET request method. static NSString *const kGULNetworkGETRequestMethod = @"GET"; /// Constant string for POST request method. static NSString *const kGULNetworkPOSTRequestMethod = @"POST"; /// Default constant string as a prefix for network logger. static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network"; @interface GULNetwork () @end @implementation GULNetwork { /// Network reachability. GULReachabilityChecker *_reachability; /// The dictionary of requests by session IDs { NSString : id }. GULMutableDictionary *_requests; } - (instancetype)init { return [self initWithReachabilityHost:kGULNetworkReachabilityHost]; } - (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost { self = [super init]; if (self) { // Setup reachability. _reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:reachabilityHost]; if (![_reachability start]) { return nil; } _requests = [[GULMutableDictionary alloc] init]; _timeoutInterval = kGULNetworkTimeOutInterval; } return self; } - (void)dealloc { _reachability.reachabilityDelegate = nil; [_reachability stop]; } #pragma mark - External Methods + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:(GULNetworkSystemCompletionHandler)completionHandler { [GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID completionHandler:completionHandler]; } - (NSString *)postURL:(NSURL *)url payload:(NSData *)payload queue:(dispatch_queue_t)queue usingBackgroundSession:(BOOL)usingBackgroundSession completionHandler:(GULNetworkCompletionHandler)handler { if (!url.absoluteString.length) { [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; return nil; } NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:timeOutInterval]; if (!request) { [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation queue:queue withHandler:handler]; return nil; } NSError *compressError = nil; NSData *compressedData = [NSData gul_dataByGzippingData:payload error:&compressError]; if (!compressedData || compressError) { if (compressError || payload.length > 0) { // If the payload is not empty but it fails to compress the payload, something has been wrong. [self handleErrorWithCode:GULErrorCodeNetworkPayloadCompression queue:queue withHandler:handler]; return nil; } compressedData = [[NSData alloc] init]; } NSString *postLength = @(compressedData.length).stringValue; // Set up the request with the compressed data. [request setValue:postLength forHTTPHeaderField:kGULNetworkContentLengthKey]; request.HTTPBody = compressedData; request.HTTPMethod = kGULNetworkPOSTRequestMethod; [request setValue:kGULNetworkContentTypeValue forHTTPHeaderField:kGULNetworkContentTypeKey]; [request setValue:kGULNetworkContentCompressionValue forHTTPHeaderField:kGULNetworkContentCompressionKey]; GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; fetcher.backgroundNetworkEnabled = usingBackgroundSession; __weak GULNetwork *weakSelf = self; NSString *requestID = [fetcher sessionIDFromAsyncPOSTRequest:request completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID, NSError *error) { GULNetwork *strongSelf = weakSelf; if (!strongSelf) { return; } dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); dispatch_async(queueToDispatch, ^{ if (sessionID.length) { [strongSelf->_requests removeObjectForKey:sessionID]; } if (handler) { handler(response, data, error); } }); }]; if (!requestID) { [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation queue:queue withHandler:handler]; return nil; } [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeNetwork000 message:@"Uploading data. Host" context:url]; _requests[requestID] = fetcher; return requestID; } - (NSString *)getURL:(NSURL *)url headers:(NSDictionary *)headers queue:(dispatch_queue_t)queue usingBackgroundSession:(BOOL)usingBackgroundSession completionHandler:(GULNetworkCompletionHandler)handler { if (!url.absoluteString.length) { [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; return nil; } NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:timeOutInterval]; if (!request) { [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation queue:queue withHandler:handler]; return nil; } request.HTTPMethod = kGULNetworkGETRequestMethod; request.allHTTPHeaderFields = headers; GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; fetcher.backgroundNetworkEnabled = usingBackgroundSession; __weak GULNetwork *weakSelf = self; NSString *requestID = [fetcher sessionIDFromAsyncGETRequest:request completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID, NSError *error) { GULNetwork *strongSelf = weakSelf; if (!strongSelf) { return; } dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); dispatch_async(queueToDispatch, ^{ if (sessionID.length) { [strongSelf->_requests removeObjectForKey:sessionID]; } if (handler) { handler(response, data, error); } }); }]; if (!requestID) { [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation queue:queue withHandler:handler]; return nil; } [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeNetwork001 message:@"Downloading data. Host" context:url]; _requests[requestID] = fetcher; return requestID; } - (BOOL)hasUploadInProgress { return _requests.count > 0; } #pragma mark - Network Reachability /// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network /// reachability has changed. - (void)reachability:(GULReachabilityChecker *)reachability statusChanged:(GULReachabilityStatus)status { _networkConnected = (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi); [_reachabilityDelegate reachabilityDidChange]; } #pragma mark - Network logger delegate - (void)setLoggerDelegate:(id)loggerDelegate { // Explicitly check whether the delegate responds to the methods because conformsToProtocol does // not work correctly even though the delegate does respond to the methods. if (!loggerDelegate || ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: messageCode:message:contexts:)] || ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: messageCode:message:context:)] || ![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel: messageCode:message:)]) { GULLogError(kGULLoggerNetwork, NO, [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002], @"Cannot set the network logger delegate: delegate does not conform to the network " "logger protocol."); return; } _loggerDelegate = loggerDelegate; } #pragma mark - Private methods /// Handles network error and calls completion handler with the error. - (void)handleErrorWithCode:(NSInteger)code queue:(dispatch_queue_t)queue withHandler:(GULNetworkCompletionHandler)handler { NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"}; NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:code userInfo:userInfo]; [self GULNetwork_logWithLevel:kGULNetworkLogLevelWarning messageCode:kGULNetworkMessageCodeNetwork002 message:@"Failed to create network request. Code, error" contexts:@[ @(code), error ]]; if (handler) { dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); dispatch_async(queueToDispatch, ^{ handler(nil, nil, error); }); } } #pragma mark - Network logger - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message contexts:(NSArray *)contexts { // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log // errors/warnings/info messages to the console log. if (_loggerDelegate) { [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts]; return; } if (_isDebugModeEnabled || logLevel == kGULNetworkLogLevelError || logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) { NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts); NSLog(@"%@", formattedMessage); GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO, [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage, NULL); } } - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message context:(id)context { if (_loggerDelegate) { [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message context:context]; return; } NSArray *contexts = context ? @[ context ] : @[]; [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts]; } - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message { if (_loggerDelegate) { [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message]; return; } [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]]; } /// Returns a string for the given log level (e.g. kGULNetworkLogLevelError -> @"ERROR"). static NSString *GULLogLevelDescriptionFromLogLevel(GULNetworkLogLevel logLevel) { static NSDictionary *levelNames = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ levelNames = @{ @(kGULNetworkLogLevelError) : @"ERROR", @(kGULNetworkLogLevelWarning) : @"WARNING", @(kGULNetworkLogLevelInfo) : @"INFO", @(kGULNetworkLogLevelDebug) : @"DEBUG" }; }); return levelNames[@(logLevel)]; } /// Returns a formatted string to be used for console logging. static NSString *GULStringWithLogMessage(NSString *message, GULNetworkLogLevel logLevel, NSArray *contexts) { if (!message) { message = @"(Message was nil)"; } else if (!message.length) { message = @"(Message was empty)"; } NSMutableString *result = [[NSMutableString alloc] initWithFormat:@"<%@/%@> %@", kGULNetworkLogTag, GULLogLevelDescriptionFromLogLevel(logLevel), message]; if (!contexts.count) { return result; } NSMutableArray *formattedContexts = [[NSMutableArray alloc] init]; for (id item in contexts) { [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")]; } [result appendString:@": "]; [result appendString:[formattedContexts componentsJoinedByString:@", "]]; return result; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkConstants.m ================================================ // Copyright 2017 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/Network/Public/GoogleUtilities/GULNetworkConstants.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" #import NSString *const kGULNetworkBackgroundSessionConfigIDPrefix = @"com.gul.network.background-upload"; NSString *const kGULNetworkApplicationSupportSubdirectory = @"GUL/Network"; NSString *const kGULNetworkTempDirectoryName = @"GULNetworkTemporaryDirectory"; const NSTimeInterval kGULNetworkTempFolderExpireTime = 60 * 60; // 1 hour const NSTimeInterval kGULNetworkTimeOutInterval = 60; // 1 minute. NSString *const kGULNetworkReachabilityHost = @"app-measurement.com"; NSString *const kGULNetworkErrorContext = @"Context"; const int kGULNetworkHTTPStatusOK = 200; const int kGULNetworkHTTPStatusNoContent = 204; const int kGULNetworkHTTPStatusCodeMultipleChoices = 300; const int kGULNetworkHTTPStatusCodeMovedPermanently = 301; const int kGULNetworkHTTPStatusCodeFound = 302; const int kGULNetworkHTTPStatusCodeNotModified = 304; const int kGULNetworkHTTPStatusCodeMovedTemporarily = 307; const int kGULNetworkHTTPStatusCodeNotFound = 404; const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic = 429; const int kGULNetworkHTTPStatusCodeUnavailable = 503; NSString *const kGULNetworkErrorDomain = @"com.gul.network.ErrorDomain"; GULLoggerService kGULLoggerNetwork = @"[GULNetwork]"; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkInternal.h ================================================ /* * Copyright 2017 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/Logger/Public/GoogleUtilities/GULLogger.h" extern NSString *const kGULNetworkErrorDomain; /// The logger service for GULNetwork. extern GULLoggerService kGULLoggerNetwork; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/GULNetworkURLSession.m ================================================ // Copyright 2017 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/Network/Public/GoogleUtilities/GULNetworkURLSession.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" #import "GoogleUtilities/Network/GULNetworkInternal.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h" #import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h" @interface GULNetworkURLSession () @end @implementation GULNetworkURLSession { /// The handler to be called when the request completes or error has occurs. GULNetworkURLSessionCompletionHandler _completionHandler; /// Session ID generated randomly with a fixed prefix. NSString *_sessionID; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" /// The session configuration. NSURLSessionConfiguration' is only available on iOS 7.0 or newer. NSURLSessionConfiguration *_sessionConfig; /// The current NSURLSession. NSURLSession *__weak _Nullable _URLSession; #pragma clang diagnostic pop /// The path to the directory where all temporary files are stored before uploading. NSURL *_networkDirectoryURL; /// The downloaded data from fetching. NSData *_downloadedData; /// The path to the temporary file which stores the uploading data. NSURL *_uploadingFileURL; /// The current request. NSURLRequest *_request; } #pragma mark - Init - (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate { self = [super init]; if (self) { // Create URL to the directory where all temporary files to upload have to be stored. #if TARGET_OS_TV NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); #else NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); #endif NSString *storageDirectory = paths.firstObject; NSArray *tempPathComponents = @[ storageDirectory, kGULNetworkApplicationSupportSubdirectory, kGULNetworkTempDirectoryName ]; _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents]; _sessionID = [NSString stringWithFormat:@"%@-%@", kGULNetworkBackgroundSessionConfigIDPrefix, [[NSUUID UUID] UUIDString]]; _loggerDelegate = networkLoggerDelegate; } return self; } #pragma mark - External Methods #pragma mark - To be called from AppDelegate + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler: (GULNetworkSystemCompletionHandler)systemCompletionHandler { // The session may not be Analytics background. Ignore those that do not have the prefix. if (![sessionID hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { return; } GULNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID]; if (fetcher != nil) { [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID]; } else { GULLogError(kGULLoggerNetwork, NO, [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003], @"Failed to retrieve background session with ID %@ after app is relaunched.", sessionID); } } #pragma mark - External Methods /// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the /// connection. - (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler API_AVAILABLE(ios(7.0)) { // NSURLSessionUploadTask does not work with NSData in the background. // To avoid this issue, write the data to a temporary file to upload it. // Make a temporary file with the data subset. _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID]; NSError *writeError; NSURLSessionUploadTask *postRequestTask; NSURLSession *session; BOOL didWriteFile = NO; // Clean up the entire temp folder to avoid temp files that remain in case the previous session // crashed and did not clean up. [self maybeRemoveTempFilesAtURL:_networkDirectoryURL expiringTime:kGULNetworkTempFolderExpireTime]; // If there is no background network enabled, no need to write to file. This will allow default // network session which runs on the foreground. if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) { didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path options:NSDataWritingAtomic error:&writeError]; if (writeError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession000 message:@"Failed to write request data to file" context:writeError]; } } if (didWriteFile) { // Exclude this file from backing up to iTunes. There are conflicting reports that excluding // directory from backing up does not exclude files of that directory from backing up. [self excludeFromBackupForURL:_uploadingFileURL]; _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; [self populateSessionConfig:_sessionConfig withRequest:request]; session = [NSURLSession sessionWithConfiguration:_sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL]; } else { // If we cannot write to file, just send it in the foreground. _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; [self populateSessionConfig:_sessionConfig withRequest:request]; session = [NSURLSession sessionWithConfiguration:_sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody]; } if (!session || !postRequestTask) { NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:GULErrorCodeNetworkRequestCreation userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; [self callCompletionHandler:handler withResponse:nil data:nil error:error]; return nil; } _URLSession = session; // Save the session into memory. [[self class] setSessionInFetcherMap:self forSessionID:_sessionID]; _request = [request copy]; // Store completion handler because background session does not accept handler block but custom // delegate. _completionHandler = [handler copy]; [postRequestTask resume]; return _sessionID; } /// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session. - (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler API_AVAILABLE(ios(7.0)) { if (_backgroundNetworkEnabled) { _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; } else { _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; } [self populateSessionConfig:_sessionConfig withRequest:request]; // Do not cache the GET request. _sessionConfig.URLCache = nil; NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request]; if (!session || !downloadTask) { NSError *error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:GULErrorCodeNetworkRequestCreation userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; [self callCompletionHandler:handler withResponse:nil data:nil error:error]; return nil; } _URLSession = session; // Save the session into memory. [[self class] setSessionInFetcherMap:self forSessionID:_sessionID]; _request = [request copy]; _completionHandler = [handler copy]; [downloadTask resume]; return _sessionID; } #pragma mark - NSURLSessionDataDelegate /// Called by the NSURLSession when the data task has received some of the expected data. /// Once the session is completed, URLSession:task:didCompleteWithError will be called and the /// completion handler will be called with the downloaded data. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { @synchronized(self) { NSMutableData *mutableData = [[NSMutableData alloc] init]; if (_downloadedData) { mutableData = _downloadedData.mutableCopy; } [mutableData appendData:data]; _downloadedData = mutableData; } } #pragma mark - NSURLSessionTaskDelegate /// Called by the NSURLSession once the download task is completed. The file is saved in the /// provided URL so we need to read the data and store into _downloadedData. Once the session is /// completed, URLSession:task:didCompleteWithError will be called and the completion handler will /// be called with the downloaded data. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)task didFinishDownloadingToURL:(NSURL *)url API_AVAILABLE(ios(7.0)) { if (!url.path) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession001 message:@"Unable to read downloaded data from empty temp path"]; _downloadedData = nil; return; } NSError *error; _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error]; if (error) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession002 message:@"Cannot read the content of downloaded data" context:error]; _downloadedData = nil; } } #if TARGET_OS_IOS || TARGET_OS_TV - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session API_AVAILABLE(ios(7.0)) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeURLSession003 message:@"Background session finished" context:session.configuration.identifier]; [self callSystemCompletionHandler:session.configuration.identifier]; } #endif - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error API_AVAILABLE(ios(7.0)) { // Avoid any chance of recursive behavior leading to it being used repeatedly. GULNetworkURLSessionCompletionHandler handler = _completionHandler; _completionHandler = nil; if (task.response) { // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7. NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP"); // The server responded so ignore the error created by the system. error = nil; } else if (!error) { error = [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:GULErrorCodeNetworkInvalidResponse userInfo:@{kGULNetworkErrorContext : @"Network Error: Empty network response"}]; } [self callCompletionHandler:handler withResponse:(NSHTTPURLResponse *)task.response data:_downloadedData error:error]; // Remove the temp file to avoid trashing devices with lots of temp files. [self removeTempItemAtURL:_uploadingFileURL]; // Try to clean up stale files again. [self maybeRemoveTempFilesAtURL:_networkDirectoryURL expiringTime:kGULNetworkTempFolderExpireTime]; // This is called without checking the sessionID here since non-background sessions // won't have an ID. [session finishTasksAndInvalidate]; // Explicitly remove the session so it won't be reused. The weak map table should // remove the session on deallocation, but dealloc may not happen immediately after // calling `finishTasksAndInvalidate`. NSString *sessionID = session.configuration.identifier; [[self class] setSessionInFetcherMap:nil forSessionID:sessionID]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler API_AVAILABLE(ios(7.0)) { // The handling is modeled after GTMSessionFetcher. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; if (serverTrust == NULL) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeURLSession004 message:@"Received empty server trust for host. Host" context:_request.URL]; completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); return; } NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; if (!credential) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning messageCode:kGULNetworkMessageCodeURLSession005 message:@"Unable to verify server identity. Host" context:_request.URL]; completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); return; } [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeURLSession006 message:@"Received SSL challenge for host. Host" context:_request.URL]; void (^callback)(BOOL) = ^(BOOL allow) { if (allow) { completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { [self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeURLSession007 message:@"Cancelling authentication challenge for host. Host" context:self->_request.URL]; completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } }; // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. CFRetain(serverTrust); // Evaluate the certificate chain. // // The delegate queue may be the main thread. Trust evaluation could cause some // blocking network activity, so we must evaluate async, as documented at // https://developer.apple.com/library/ios/technotes/tn2232/ dispatch_queue_t evaluateBackgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(evaluateBackgroundQueue, ^{ SecTrustResultType trustEval = kSecTrustResultInvalid; BOOL shouldAllow; OSStatus trustError; @synchronized([GULNetworkURLSession class]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" trustError = SecTrustEvaluate(serverTrust, &trustEval); #pragma clang dianostic pop } 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); } // Call the call back with the permission. callback(shouldAllow); CFRelease(serverTrust); }); return; } // Default handling for other Auth Challenges. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } #pragma mark - Internal Methods /// Stores system completion handler with session ID as key. - (void)addSystemCompletionHandler:(GULNetworkSystemCompletionHandler)handler forSession:(NSString *)identifier { if (!handler) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession009 message:@"Cannot store nil system completion handler in network"]; return; } if (!identifier.length) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession010 message:@"Cannot store system completion handler with empty network " "session identifier"]; return; } GULMutableDictionary *systemCompletionHandlers = [[self class] sessionIDToSystemCompletionHandlerDictionary]; if (systemCompletionHandlers[identifier]) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning messageCode:kGULNetworkMessageCodeURLSession011 message:@"Got multiple system handlers for a single session ID" context:identifier]; } systemCompletionHandlers[identifier] = handler; } /// Calls the system provided completion handler with the session ID stored in the dictionary. /// The handler will be removed from the dictionary after being called. - (void)callSystemCompletionHandler:(NSString *)identifier { GULMutableDictionary *systemCompletionHandlers = [[self class] sessionIDToSystemCompletionHandlerDictionary]; GULNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier]; if (handler) { [systemCompletionHandlers removeObjectForKey:identifier]; dispatch_async(dispatch_get_main_queue(), ^{ handler(); }); } } /// Sets or updates the session ID of this session. - (void)setSessionID:(NSString *)sessionID { _sessionID = [sessionID copy]; } /// Creates a background session configuration with the session ID using the supported method. - (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID API_AVAILABLE(ios(7.0)) { #if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \ TARGET_OS_TV || \ (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; #elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \ (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) // Do a runtime check to avoid a deprecation warning about using // +backgroundSessionConfiguration: on iOS 8. if ([NSURLSessionConfiguration respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { // Running on iOS 8+/OS X 10.10+. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; #pragma clang diagnostic pop } else { // Running on iOS 7/OS X 10.9. return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; } #else // Building with an SDK earlier than iOS 8/OS X 10.10. return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; #endif } - (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime { if (!folderURL.absoluteString.length) { return; } NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; NSArray *properties = @[ NSURLCreationDateKey ]; NSArray *directoryContent = [fileManager contentsOfDirectoryAtURL:folderURL includingPropertiesForKeys:properties options:NSDirectoryEnumerationSkipsSubdirectoryDescendants error:&error]; if (error && error.code != NSFileReadNoSuchFileError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug messageCode:kGULNetworkMessageCodeURLSession012 message:@"Cannot get files from the temporary network folder. Error" context:error]; return; } if (!directoryContent.count) { return; } NSTimeInterval now = [NSDate date].timeIntervalSince1970; for (NSURL *tempFile in directoryContent) { NSDate *creationDate; BOOL getCreationDate = [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL]; if (!getCreationDate) { continue; } NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970; if (fabs(now - creationTimeInterval) > staleTime) { [self removeTempItemAtURL:tempFile]; } } } /// Removes the temporary file written to disk for sending the request. It has to be cleaned up /// after the session is done. - (void)removeTempItemAtURL:(NSURL *)fileURL { if (!fileURL.absoluteString.length) { return; } NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession013 message:@"Failed to remove temporary uploading data file. Error" context:error.localizedDescription]; } } /// Gets the fetcher with the session ID. + (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { GULNetworkURLSession *session = [self sessionFromFetcherMapForSessionID:sessionIdentifier]; if (!session && [sessionIdentifier hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { session = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; [session setSessionID:sessionIdentifier]; [self setSessionInFetcherMap:session forSessionID:sessionIdentifier]; } return session; } /// Returns a map of the fetcher by session ID. Creates a map if it is not created. /// When reading and writing from/to the session map, don't use this method directly. /// To avoid thread safety issues, use one of the helper methods at the bottom of the /// file: setSessionInFetcherMap:forSessionID:, sessionFromFetcherMapForSessionID: + (NSMapTable *)sessionIDToFetcherMap { static NSMapTable *sessionIDToFetcherMap; static dispatch_once_t sessionMapOnceToken; dispatch_once(&sessionMapOnceToken, ^{ sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; }); return sessionIDToFetcherMap; } + (NSLock *)sessionIDToFetcherMapReadWriteLock { static NSLock *lock; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ lock = [[NSLock alloc] init]; }); return lock; } /// Returns a map of system provided completion handler by session ID. Creates a map if it is not /// created. + (GULMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary { static GULMutableDictionary *systemCompletionHandlers; static dispatch_once_t systemCompletionHandlerOnceToken; dispatch_once(&systemCompletionHandlerOnceToken, ^{ systemCompletionHandlers = [[GULMutableDictionary alloc] init]; }); return systemCompletionHandlers; } - (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID { NSString *tempName = [NSString stringWithFormat:@"GULUpload_temp_%@", sessionID]; return [_networkDirectoryURL URLByAppendingPathComponent:tempName]; } /// Makes sure that the directory to store temp files exists. If not, tries to create it and returns /// YES. If there is anything wrong, returns NO. - (BOOL)ensureTemporaryDirectoryExists { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; // Create a temporary directory if it does not exist or was deleted. if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) { return YES; } if (error && error.code != NSFileReadNoSuchFileError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning messageCode:kGULNetworkMessageCodeURLSession014 message:@"Error while trying to access Network temp folder. Error" context:error]; } NSError *writeError = nil; [fileManager createDirectoryAtURL:_networkDirectoryURL withIntermediateDirectories:YES attributes:nil error:&writeError]; if (writeError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession015 message:@"Cannot create temporary directory. Error" context:writeError]; return NO; } // Set the iCloud exclusion attribute on the Documents URL. [self excludeFromBackupForURL:_networkDirectoryURL]; return YES; } - (void)excludeFromBackupForURL:(NSURL *)url { if (!url.path) { return; } // Set the iCloud exclusion attribute on the Documents URL. NSError *preventBackupError = nil; [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError]; if (preventBackupError) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession016 message:@"Cannot exclude temporary folder from iTunes backup"]; } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler API_AVAILABLE(ios(7.0)) { NSArray *nonAllowedRedirectionCodes = @[ @(kGULNetworkHTTPStatusCodeFound), @(kGULNetworkHTTPStatusCodeMovedPermanently), @(kGULNetworkHTTPStatusCodeMovedTemporarily), @(kGULNetworkHTTPStatusCodeMultipleChoices) ]; // Allow those not in the non allowed list to be followed. if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) { completionHandler(request); return; } // Do not allow redirection if the response code is in the non-allowed list. NSURLRequest *newRequest = request; if (response) { newRequest = nil; } completionHandler(newRequest); } #pragma mark - Helper Methods + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSString *)sessionID { [[self sessionIDToFetcherMapReadWriteLock] lock]; GULNetworkURLSession *existingSession = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; if (existingSession) { if (session) { NSString *message = [NSString stringWithFormat:@"Discarding session: %@", existingSession]; [existingSession->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelInfo messageCode:kGULNetworkMessageCodeURLSession019 message:message]; } [existingSession->_URLSession finishTasksAndInvalidate]; } if (session) { [[[self class] sessionIDToFetcherMap] setObject:session forKey:sessionID]; } else { [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } [[self sessionIDToFetcherMapReadWriteLock] unlock]; } + (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID { [[self sessionIDToFetcherMapReadWriteLock] lock]; GULNetworkURLSession *session = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; [[self sessionIDToFetcherMapReadWriteLock] unlock]; return session; } - (void)callCompletionHandler:(GULNetworkURLSessionCompletionHandler)handler withResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *)error { if (error) { [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError messageCode:kGULNetworkMessageCodeURLSession017 message:@"Encounter network error. Code, error" contexts:@[ @(error.code), error ]]; } if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(response, data, self->_sessionID, error); }); } } // Always use the request parameters even if the default session configuration is more restrictive. - (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig withRequest:(NSURLRequest *)request API_AVAILABLE(ios(7.0)) { sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields; sessionConfig.timeoutIntervalForRequest = request.timeoutInterval; sessionConfig.timeoutIntervalForResource = request.timeoutInterval; sessionConfig.requestCachePolicy = request.cachePolicy; } @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h ================================================ /* * Copyright 2017 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 /// A mutable dictionary that provides atomic accessor and mutators. @interface GULMutableDictionary : NSObject /// Returns an object given a key in the dictionary or nil if not found. - (id)objectForKey:(id)key; /// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. - (void)setObject:(id)object forKey:(id)key; /// Removes the object given its session ID from the dictionary. - (void)removeObjectForKey:(id)key; /// Removes all objects. - (void)removeAllObjects; /// Returns the number of current objects in the dictionary. - (NSUInteger)count; /// Returns an object given a key in the dictionary or nil if not found. - (id)objectForKeyedSubscript:(id)key; /// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. - (void)setObject:(id)obj forKeyedSubscript:(id)key; /// Returns the immutable dictionary. - (NSDictionary *)dictionary; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h ================================================ /* * Copyright 2017 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 "GULNetworkConstants.h" #import "GULNetworkLoggerProtocol.h" #import "GULNetworkURLSession.h" /// Delegate protocol for GULNetwork events. @protocol GULNetworkReachabilityDelegate /// Tells the delegate to handle events when the network reachability changes to connected or not /// connected. - (void)reachabilityDidChange; @end /// The Network component that provides network status and handles network requests and responses. /// This is not thread safe. /// /// NOTE: /// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the /// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler: @interface GULNetwork : NSObject /// Indicates if network connectivity is available. @property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected; /// Indicates if there are any uploads in progress. @property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress; /// An optional delegate that can be used in the event when network reachability changes. @property(nonatomic, weak) id reachabilityDelegate; /// An optional delegate that can be used to log messages, warnings or errors that occur in the /// network operations. @property(nonatomic, weak) id loggerDelegate; /// Indicates whether the logger should display debug messages. @property(nonatomic, assign) BOOL isDebugModeEnabled; /// The time interval in seconds for the network request to timeout. @property(nonatomic, assign) NSTimeInterval timeoutInterval; /// Initializes with the default reachability host. - (instancetype)init; /// Initializes with a custom reachability host. - (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost; /// Handles events when background session with the given ID has finished. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; /// 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; /// 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; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h ================================================ /* * Copyright 2017 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 /// 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. typedef NS_ENUM(NSInteger, GULNetworkErrorCode) { /// Unknown error. GULNetworkErrorCodeUnknown = 0, /// Error occurs when the request URL is invalid. GULErrorCodeNetworkInvalidURL = 1, /// Error occurs when request cannot be constructed. GULErrorCodeNetworkRequestCreation = 2, /// Error occurs when payload cannot be compressed. GULErrorCodeNetworkPayloadCompression = 3, /// Error occurs when session task cannot be created. GULErrorCodeNetworkSessionTaskCreation = 4, /// Error occurs when there is no response. GULErrorCodeNetworkInvalidResponse = 5 }; #pragma mark - Network constants /// The prefix of the ID of the background session. extern NSString *const kGULNetworkBackgroundSessionConfigIDPrefix; /// The sub directory to store the files of data that is being uploaded in the background. extern NSString *const kGULNetworkApplicationSupportSubdirectory; /// Name of the temporary directory that stores files for background uploading. extern NSString *const kGULNetworkTempDirectoryName; /// The period when the temporary uploading file can stay. extern const NSTimeInterval kGULNetworkTempFolderExpireTime; /// The default network request timeout interval. extern const NSTimeInterval kGULNetworkTimeOutInterval; /// The host to check the reachability of the network. extern NSString *const kGULNetworkReachabilityHost; /// The key to get the error context of the UserInfo. extern NSString *const kGULNetworkErrorContext; #pragma mark - Network Status Code extern const int kGULNetworkHTTPStatusOK; extern const int kGULNetworkHTTPStatusNoContent; extern const int kGULNetworkHTTPStatusCodeMultipleChoices; extern const int kGULNetworkHTTPStatusCodeMovedPermanently; extern const int kGULNetworkHTTPStatusCodeFound; extern const int kGULNetworkHTTPStatusCodeNotModified; extern const int kGULNetworkHTTPStatusCodeMovedTemporarily; extern const int kGULNetworkHTTPStatusCodeNotFound; extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic; extern const int kGULNetworkHTTPStatusCodeUnavailable; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h ================================================ /* * Copyright 2017 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 "GULNetworkMessageCode.h" /// The log levels used by GULNetworkLogger. typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { kGULNetworkLogLevelError = 3, kGULNetworkLogLevelWarning = 4, kGULNetworkLogLevelInfo = 6, kGULNetworkLogLevelDebug = 7, }; @protocol GULNetworkLoggerDelegate @required /// Tells the delegate to log a message with an array of contexts and the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message contexts:(NSArray *)contexts; /// Tells the delegate to log a message with a context and the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message context:(id)context; /// Tells the delegate to log a message with the log level. - (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel messageCode:(GULNetworkMessageCode)messageCode message:(NSString *)message; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h ================================================ /* * Copyright 2017 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 // Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { // GULNetwork.m kGULNetworkMessageCodeNetwork000 = 900000, // I-NET900000 kGULNetworkMessageCodeNetwork001 = 900001, // I-NET900001 kGULNetworkMessageCodeNetwork002 = 900002, // I-NET900002 kGULNetworkMessageCodeNetwork003 = 900003, // I-NET900003 // GULNetworkURLSession.m kGULNetworkMessageCodeURLSession000 = 901000, // I-NET901000 kGULNetworkMessageCodeURLSession001 = 901001, // I-NET901001 kGULNetworkMessageCodeURLSession002 = 901002, // I-NET901002 kGULNetworkMessageCodeURLSession003 = 901003, // I-NET901003 kGULNetworkMessageCodeURLSession004 = 901004, // I-NET901004 kGULNetworkMessageCodeURLSession005 = 901005, // I-NET901005 kGULNetworkMessageCodeURLSession006 = 901006, // I-NET901006 kGULNetworkMessageCodeURLSession007 = 901007, // I-NET901007 kGULNetworkMessageCodeURLSession008 = 901008, // I-NET901008 kGULNetworkMessageCodeURLSession009 = 901009, // I-NET901009 kGULNetworkMessageCodeURLSession010 = 901010, // I-NET901010 kGULNetworkMessageCodeURLSession011 = 901011, // I-NET901011 kGULNetworkMessageCodeURLSession012 = 901012, // I-NET901012 kGULNetworkMessageCodeURLSession013 = 901013, // I-NET901013 kGULNetworkMessageCodeURLSession014 = 901014, // I-NET901014 kGULNetworkMessageCodeURLSession015 = 901015, // I-NET901015 kGULNetworkMessageCodeURLSession016 = 901016, // I-NET901016 kGULNetworkMessageCodeURLSession017 = 901017, // I-NET901017 kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018 kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019 }; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkURLSession.h ================================================ /* * Copyright 2017 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 "GULNetworkLoggerProtocol.h" NS_ASSUME_NONNULL_BEGIN typedef void (^GULNetworkCompletionHandler)(NSHTTPURLResponse *_Nullable response, NSData *_Nullable data, NSError *_Nullable error); typedef void (^GULNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *_Nullable response, NSData *_Nullable data, NSString *sessionID, NSError *_Nullable error); typedef void (^GULNetworkSystemCompletionHandler)(void); /// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses. @interface GULNetworkURLSession : NSObject /// Indicates whether the background network is enabled. Default value is NO. @property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled; /// The logger delegate to log message, errors or warnings that occur during the network operations. @property(nonatomic, weak, nullable) id loggerDelegate; /// Calls the system provided completion handler after the background session is finished. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; /// Initializes with logger delegate. - (instancetype)initWithNetworkLoggerDelegate: (nullable id)networkLoggerDelegate NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; /// Sends an asynchronous POST request and calls the provided completion handler when the request /// completes or when errors occur, and returns an ID of the session/connection. - (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler; /// Sends an asynchronous GET request and calls the provided completion handler when the request /// completes or when errors occur, and returns an ID of the session. - (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request completionHandler:(GULNetworkURLSessionCompletionHandler)handler; NS_ASSUME_NONNULL_END @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h ================================================ /* * Copyright 2017 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/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h" #if !TARGET_OS_WATCH typedef SCNetworkReachabilityRef (*GULReachabilityCreateWithNameFn)(CFAllocatorRef allocator, const char *host); typedef Boolean (*GULReachabilitySetCallbackFn)(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callback, SCNetworkReachabilityContext *context); typedef Boolean (*GULReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode); typedef Boolean (*GULReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode); typedef void (*GULReachabilityReleaseFn)(CFTypeRef cf); struct GULReachabilityApi { GULReachabilityCreateWithNameFn createWithNameFn; GULReachabilitySetCallbackFn setCallbackFn; GULReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn; GULReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn; GULReachabilityReleaseFn releaseFn; }; #endif @interface GULReachabilityChecker (Internal) - (const struct GULReachabilityApi *)reachabilityApi; - (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityChecker.m ================================================ // Copyright 2017 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/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h" #import "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h" #import "GoogleUtilities/Reachability/GULReachabilityMessageCode.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" static GULLoggerService kGULLoggerReachability = @"[GULReachability]"; #if !TARGET_OS_WATCH static void ReachabilityCallback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info); static const struct GULReachabilityApi kGULDefaultReachabilityApi = { SCNetworkReachabilityCreateWithName, SCNetworkReachabilitySetCallback, SCNetworkReachabilityScheduleWithRunLoop, SCNetworkReachabilityUnscheduleFromRunLoop, CFRelease, }; static NSString *const kGULReachabilityUnknownStatus = @"Unknown"; static NSString *const kGULReachabilityConnectedStatus = @"Connected"; static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected"; #endif @interface GULReachabilityChecker () @property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi; @property(nonatomic, assign) GULReachabilityStatus reachabilityStatus; @property(nonatomic, copy) NSString *host; #if !TARGET_OS_WATCH @property(nonatomic, assign) SCNetworkReachabilityRef reachability; #endif @end @implementation GULReachabilityChecker @synthesize reachabilityApi = reachabilityApi_; #if !TARGET_OS_WATCH @synthesize reachability = reachability_; #endif - (const struct GULReachabilityApi *)reachabilityApi { return reachabilityApi_; } - (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi { #if !TARGET_OS_WATCH if (reachability_) { GULLogError(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000], @"Cannot change reachability API while reachability is running. " @"Call stop first."); return; } reachabilityApi_ = reachabilityApi; #endif } @synthesize reachabilityStatus = reachabilityStatus_; @synthesize host = host_; @synthesize reachabilityDelegate = reachabilityDelegate_; - (BOOL)isActive { #if !TARGET_OS_WATCH return reachability_ != nil; #else return NO; #endif } - (void)setReachabilityDelegate:(id)reachabilityDelegate { if (reachabilityDelegate && (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) { GULLogError(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005], @"Reachability delegate doesn't conform to Reachability protocol."); return; } reachabilityDelegate_ = reachabilityDelegate; } - (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate withHost:(NSString *)host { self = [super init]; if (!host || !host.length) { GULLogError(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001], @"Invalid host specified"); return nil; } if (self) { #if !TARGET_OS_WATCH [self setReachabilityDelegate:reachabilityDelegate]; reachabilityApi_ = &kGULDefaultReachabilityApi; reachabilityStatus_ = kGULReachabilityUnknown; host_ = [host copy]; reachability_ = nil; #endif } return self; } - (void)dealloc { reachabilityDelegate_ = nil; [self stop]; } - (BOOL)start { #if TARGET_OS_WATCH return NO; #else if (!reachability_) { reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]); if (!reachability_) { return NO; } SCNetworkReachabilityContext context = { 0, /* version */ (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */ NULL, /* retain */ NULL, /* release */ NULL /* copyDescription */ }; if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) || !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(), kCFRunLoopCommonModes)) { reachabilityApi_->releaseFn(reachability_); reachability_ = nil; GULLogError(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002], @"Failed to start reachability handle"); return NO; } } GULLogDebug(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003], @"Monitoring the network status"); return YES; #endif } - (void)stop { #if !TARGET_OS_WATCH if (reachability_) { reachabilityStatus_ = kGULReachabilityUnknown; reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(), kCFRunLoopCommonModes); reachabilityApi_->releaseFn(reachability_); reachability_ = nil; } #endif } #if !TARGET_OS_WATCH - (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags { GULReachabilityStatus status = kGULReachabilityNotReachable; // If the Reachable flag is not set, we definitely don't have connectivity. if (flags & kSCNetworkReachabilityFlagsReachable) { // Reachable flag is set. Check further flags. if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) { // Connection required flag is not set, so we have connectivity. #if TARGET_OS_IOS || TARGET_OS_TV status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular : kGULReachabilityViaWifi; #elif TARGET_OS_OSX status = kGULReachabilityViaWifi; #endif } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand | kSCNetworkReachabilityFlagsConnectionOnTraffic)) && !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) { // If the connection on demand or connection on traffic flag is set, and user intervention // is not required, we have connectivity. #if TARGET_OS_IOS || TARGET_OS_TV status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular : kGULReachabilityViaWifi; #elif TARGET_OS_OSX status = kGULReachabilityViaWifi; #endif } } return status; } - (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags { GULReachabilityStatus status = [self statusForFlags:flags]; if (reachabilityStatus_ != status) { NSString *reachabilityStatusString; if (status == kGULReachabilityUnknown) { reachabilityStatusString = kGULReachabilityUnknownStatus; } else { reachabilityStatusString = (status == kGULReachabilityNotReachable) ? kGULReachabilityDisconnectedStatus : kGULReachabilityConnectedStatus; } GULLogDebug(kGULLoggerReachability, NO, [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004], @"Network status has changed. Code:%@, status:%@", @(status), reachabilityStatusString); reachabilityStatus_ = status; [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_]; } } #endif @end #if !TARGET_OS_WATCH static void ReachabilityCallback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) { GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info; [checker reachabilityFlagsChanged:flags]; } #endif // This function used to be at the top of the file, but it was moved here // as a workaround for a suspected compiler bug. When compiled in Release mode // and run on an iOS device with WiFi disabled, the reachability code crashed // when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter. // After unsuccessfully trying to diagnose the cause of the crash, it was // discovered that moving this function to the end of the file magically fixed // the crash. If you are going to edit this file, exercise caution and make sure // to test thoroughly with an iOS device under various network conditions. const NSString *GULReachabilityStatusString(GULReachabilityStatus status) { switch (status) { case kGULReachabilityUnknown: return @"Reachability Unknown"; case kGULReachabilityNotReachable: return @"Not reachable"; case kGULReachabilityViaWifi: return @"Reachable via Wifi"; case kGULReachabilityViaCellular: return @"Reachable via Cellular Data"; default: return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status]; } } ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Reachability/GULReachabilityMessageCode.h ================================================ /* * Copyright 2017 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 // Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. typedef NS_ENUM(NSInteger, GULReachabilityMessageCode) { // GULReachabilityChecker.m kGULReachabilityMessageCode000 = 902000, // I-NET902000 kGULReachabilityMessageCode001 = 902001, // I-NET902001 kGULReachabilityMessageCode002 = 902002, // I-NET902002 kGULReachabilityMessageCode003 = 902003, // I-NET902003 kGULReachabilityMessageCode004 = 902004, // I-NET902004 kGULReachabilityMessageCode005 = 902005, // I-NET902005 kGULReachabilityMessageCode006 = 902006, // I-NET902006 }; ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h ================================================ /* * Copyright 2017 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 #if !TARGET_OS_WATCH #import #endif /// Reachability Status typedef enum { kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. kGULReachabilityNotReachable, ///< Host is not reachable. kGULReachabilityViaWifi, ///< Host is reachable via Wifi. kGULReachabilityViaCellular, ///< Host is reachable via cellular. } GULReachabilityStatus; const NSString *GULReachabilityStatusString(GULReachabilityStatus status); @class GULReachabilityChecker; /// Google Analytics iOS Reachability Checker. @protocol GULReachabilityDelegate @required /// Called when network status has changed. - (void)reachability:(GULReachabilityChecker *)reachability statusChanged:(GULReachabilityStatus)status; @end /// Google Analytics iOS Network Status Checker. @interface GULReachabilityChecker : NSObject /// The last known reachability status, or GULReachabilityStatusUnknown if the /// checker is not active. @property(nonatomic, readonly) GULReachabilityStatus reachabilityStatus; /// The host to which reachability status is to be checked. @property(nonatomic, copy, readonly) NSString *host; /// The delegate to be notified of reachability status changes. @property(nonatomic, weak) id reachabilityDelegate; /// `YES` if the reachability checker is active, `NO` otherwise. @property(nonatomic, readonly) BOOL isActive; /// Initialize the reachability checker. Note that you must call start to begin checking for and /// receiving notifications about network status changes. /// /// @param reachabilityDelegate The delegate to be notified when reachability status to host /// changes. /// /// @param host The name of the host. /// - (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate withHost:(NSString *)host; - (instancetype)init NS_UNAVAILABLE; /// Start checking for reachability to the specified host. This has no effect if the status /// checker is already checking for connectivity. /// /// @return `YES` if initiating status checking was successful or the status checking has already /// been initiated, `NO` otherwise. - (BOOL)start; /// Stop checking for reachability to the specified host. This has no effect if the status /// checker is not checking for connectivity. - (void)stop; @end ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/UserDefaults/GULUserDefaults.m ================================================ // Copyright 2018 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/UserDefaults/Public/GoogleUtilities/GULUserDefaults.h" #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" NS_ASSUME_NONNULL_BEGIN static NSTimeInterval const kGULSynchronizeInterval = 1.0; static NSString *const kGULLogFormat = @"I-GUL%06ld"; static GULLoggerService kGULLogUserDefaultsService = @"[GoogleUtilities/UserDefaults]"; typedef NS_ENUM(NSInteger, GULUDMessageCode) { GULUDMessageCodeInvalidKeyGet = 1, GULUDMessageCodeInvalidKeySet = 2, GULUDMessageCodeInvalidObjectSet = 3, GULUDMessageCodeSynchronizeFailed = 4, }; @interface GULUserDefaults () /// Equivalent to the suite name for NSUserDefaults. @property(readonly) CFStringRef appNameRef; @property(atomic) BOOL isPreferenceFileExcluded; @end @implementation GULUserDefaults { // The application name is the same with the suite name of the NSUserDefaults, and it is used for // preferences. CFStringRef _appNameRef; } + (GULUserDefaults *)standardUserDefaults { static GULUserDefaults *standardUserDefaults; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ standardUserDefaults = [[GULUserDefaults alloc] init]; }); return standardUserDefaults; } - (instancetype)init { return [self initWithSuiteName:nil]; } - (instancetype)initWithSuiteName:(nullable NSString *)suiteName { self = [super init]; NSString *name = [suiteName copy]; if (self) { // `kCFPreferencesCurrentApplication` maps to the same defaults database as // `[NSUserDefaults standardUserDefaults]`. _appNameRef = name.length ? (__bridge_retained CFStringRef)name : kCFPreferencesCurrentApplication; } return self; } - (void)dealloc { // If we're using a custom `_appNameRef` it needs to be released. If it's a constant, it shouldn't // need to be released since we don't own it. if (CFStringCompare(_appNameRef, kCFPreferencesCurrentApplication, 0) != kCFCompareEqualTo) { CFRelease(_appNameRef); } [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(synchronize) object:nil]; } - (nullable id)objectForKey:(NSString *)defaultName { NSString *key = [defaultName copy]; if (![key isKindOfClass:[NSString class]] || !key.length) { GULLogWarning(@"", NO, [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet], @"Cannot get object for invalid user default key."); return nil; } return (__bridge_transfer id)CFPreferencesCopyAppValue((__bridge CFStringRef)key, _appNameRef); } - (void)setObject:(nullable id)value forKey:(NSString *)defaultName { NSString *key = [defaultName copy]; if (![key isKindOfClass:[NSString class]] || !key.length) { GULLogWarning(kGULLogUserDefaultsService, NO, [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet], @"Cannot set object for invalid user default key."); return; } if (!value) { CFPreferencesSetAppValue((__bridge CFStringRef)key, NULL, _appNameRef); [self scheduleSynchronize]; return; } BOOL isAcceptableValue = [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]]; if (!isAcceptableValue) { GULLogWarning(kGULLogUserDefaultsService, NO, [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet], @"Cannot set invalid object to user defaults. Must be a string, number, array, " @"dictionary, date, or data. Value: %@", value); return; } CFPreferencesSetAppValue((__bridge CFStringRef)key, (__bridge CFStringRef)value, _appNameRef); [self scheduleSynchronize]; } - (void)removeObjectForKey:(NSString *)key { [self setObject:nil forKey:key]; } #pragma mark - Getters - (NSInteger)integerForKey:(NSString *)defaultName { NSNumber *object = [self objectForKey:defaultName]; return object.integerValue; } - (float)floatForKey:(NSString *)defaultName { NSNumber *object = [self objectForKey:defaultName]; return object.floatValue; } - (double)doubleForKey:(NSString *)defaultName { NSNumber *object = [self objectForKey:defaultName]; return object.doubleValue; } - (BOOL)boolForKey:(NSString *)defaultName { NSNumber *object = [self objectForKey:defaultName]; return object.boolValue; } - (nullable NSString *)stringForKey:(NSString *)defaultName { return [self objectForKey:defaultName]; } - (nullable NSArray *)arrayForKey:(NSString *)defaultName { return [self objectForKey:defaultName]; } - (nullable NSDictionary *)dictionaryForKey:(NSString *)defaultName { return [self objectForKey:defaultName]; } #pragma mark - Setters - (void)setInteger:(NSInteger)integer forKey:(NSString *)defaultName { [self setObject:@(integer) forKey:defaultName]; } - (void)setFloat:(float)value forKey:(NSString *)defaultName { [self setObject:@(value) forKey:defaultName]; } - (void)setDouble:(double)doubleNumber forKey:(NSString *)defaultName { [self setObject:@(doubleNumber) forKey:defaultName]; } - (void)setBool:(BOOL)boolValue forKey:(NSString *)defaultName { [self setObject:@(boolValue) forKey:defaultName]; } #pragma mark - Save data - (void)synchronize { if (!CFPreferencesAppSynchronize(_appNameRef)) { GULLogError(kGULLogUserDefaultsService, NO, [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeSynchronizeFailed], @"Cannot synchronize user defaults to disk"); } } #pragma mark - Private methods - (void)scheduleSynchronize { // Synchronize data using a timer so that multiple set... calls can be coalesced under one // synchronize. [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(synchronize) object:nil]; // This method may be called on multiple queues (due to set... methods can be called on any queue) // synchronize can be scheduled on different queues, so make sure that it does not crash. If this // instance goes away, self will be released also, no one will retain it and the schedule won't be // called. [self performSelector:@selector(synchronize) withObject:nil afterDelay:kGULSynchronizeInterval]; } @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/GoogleUtilities/UserDefaults/Public/GoogleUtilities/GULUserDefaults.h ================================================ // Copyright 2018 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 /// A thread-safe user defaults that uses C functions from CFPreferences.h instead of /// `NSUserDefaults`. This is to avoid sending an `NSNotification` when it's changed from a /// background thread to avoid crashing. // TODO: Insert radar number here. @interface GULUserDefaults : NSObject /// A shared user defaults similar to +[NSUserDefaults standardUserDefaults] and accesses the same /// data of the standardUserDefaults. + (GULUserDefaults *)standardUserDefaults; /// Initializes preferences with a suite name that is the same with the NSUserDefaults' suite name. /// Both of CFPreferences and NSUserDefaults share the same plist file so their data will exactly /// the same. /// /// @param suiteName The name of the suite of the user defaults. - (instancetype)initWithSuiteName:(nullable NSString *)suiteName; #pragma mark - Getters /// Searches the receiver's search list for a default with the key 'defaultName' and return it. If /// another process has changed defaults in the search list, NSUserDefaults will automatically /// update to the latest values. If the key in question has been marked as ubiquitous via a Defaults /// Configuration File, the latest value may not be immediately available, and the registered value /// will be returned instead. - (nullable id)objectForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will return nil if the value is not an NSArray. - (nullable NSArray *)arrayForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will return nil if the value /// is not an NSDictionary. - (nullable NSDictionary *)dictionaryForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it will convert NSNumber values to their NSString /// representation. If a non-string non-number value is found, nil will be returned. - (nullable NSString *)stringForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the /// value is an NSNumber, the result of -integerValue will be returned. If the value is an NSString, /// it will be converted to NSInteger if possible. If the value is a boolean, it will be converted /// to either 1 for YES or 0 for NO. If the value is absent or can't be converted to an integer, 0 /// will be returned. - (NSInteger)integerForKey:(NSString *)defaultName; /// Similar to -integerForKey:, except that it returns a float, and boolean values will not be /// converted. - (float)floatForKey:(NSString *)defaultName; /// Similar to -integerForKey:, except that it returns a double, and boolean values will not be /// converted. - (double)doubleForKey:(NSString *)defaultName; /// Equivalent to -objectForKey:, except that it converts the returned value to a BOOL. If the value /// is an NSNumber, NO will be returned if the value is 0, YES otherwise. If the value is an /// NSString, values of "YES" or "1" will return YES, and values of "NO", "0", or any other string /// will return NO. If the value is absent or can't be converted to a BOOL, NO will be returned. - (BOOL)boolForKey:(NSString *)defaultName; #pragma mark - Setters /// Immediately stores a value (or removes the value if `nil` is passed as the value) for the /// provided key in the search list entry for the receiver's suite name in the current user and any /// host, then asynchronously stores the value persistently, where it is made available to other /// processes. - (void)setObject:(nullable id)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a float to an NSNumber. - (void)setFloat:(float)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a double to an /// NSNumber. - (void)setDouble:(double)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from an NSInteger to an /// NSNumber. - (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName; /// Equivalent to -setObject:forKey: except that the value is converted from a BOOL to an NSNumber. - (void)setBool:(BOOL)value forKey:(NSString *)defaultName; #pragma mark - Removing Defaults /// Equivalent to -[... setObject:nil forKey:defaultName] - (void)removeObjectForKey:(NSString *)defaultName; #pragma mark - Save data /// Blocks the calling thread until all in-progress set operations have completed. - (void)synchronize; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/GoogleUtilities/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================================================ The following copyright from Landon J. Fuller applies to the isAppEncrypted function in Environment/third_party/GULAppEnvironmentUtil.m. Copyright (c) 2017 Landon J. Fuller All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Comment from iPhone Dev Wiki Crack Prevention: App Store binaries are signed by both their developer and Apple. This encrypts the binary so that decryption keys are needed in order to make the binary readable. When iOS executes the binary, the decryption keys are used to decrypt the binary into a readable state where it is then loaded into memory and executed. iOS can tell the encryption status of a binary via the cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero value then the binary is encrypted. 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into a new binary file, resigning, and repackaging. This will only work on jailbroken devices as codesignature validation has been removed. Resigning takes place because while the codesignature doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have AppSync or similar to disable codesignature checks. More information at Landon Fuller's blog ================================================ FILE: Pods/GoogleUtilities/README.md ================================================ [![Version](https://img.shields.io/cocoapods/v/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities) [![License](https://img.shields.io/cocoapods/l/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities) [![Platform](https://img.shields.io/cocoapods/p/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities) [![Actions Status][gh-google-utilities-badge]][gh-actions] # GoogleUtilities GoogleUtilities provides a set of utilities for Firebase and other Google SDKs for Apple platform development. The utilities are not directly supported for non-Google library usage. ## Integration Testing These instructions apply to minor and patch version updates. Major versions need a customized adaptation. After the CI is green: * Determine the next version for release by checking the [tagged releases](https://github.com/google/GoogleUtilities/tags). Ensure that the next release version keeps the Swift PM and CocoaPods versions in sync. * Verify that the releasing version is the latest entry in the [CHANGELOG.md](CHANGELOG.md), updating it if necessary. * Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md) * Checkout the `main` branch and ensure it is up to date ```console git checkout main git pull ``` * Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleUtilities.podspec#L3)) ```console git tag CocoaPods-{version} git push origin CocoaPods-{version} ``` * Push the podspec to the designated repo * If this version of GoogleUtilities is intended to launch **before or with** the next Firebase release:
Push to SpecsStaging ```console pod repo push --skip-tests staging GoogleUtilities.podspec ``` If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with: ```console pod repo add staging git@github.com:firebase/SpecsStaging.git ```
* Otherwise:
Push to SpecsDev ```console pod repo push --skip-tests dev GoogleUtilities.podspec ``` If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with: ```console pod repo add dev git@github.com:firebase/SpecsDev.git ```
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`. * On google3, run copybara using the command below. Then, start a global TAP on the generated CL. Deflake as needed. ```console third_party/firebase/ios/Releases/run_copy_bara.py --directory GoogleUtilities --branch main ``` ## Publishing The release process is as follows: 1. [Tag and release for Swift PM](#swift-package-manager) 2. [Publish to CocoaPods](#cocoapods) 3. [Create GitHub Release](#create-github-release) 4. [Perform post release cleanup](#post-release-cleanup) ### Swift Package Manager By creating and [pushing a tag](https://github.com/google/GoogleUtilities/tags) for Swift PM, the newly tagged version will be immediately released for public use. Given this, please verify the intended time of release for Swift PM. * Add a version tag for Swift PM ```console git tag {version} git push origin {version} ``` *Note: Ensure that any inflight PRs that depend on the new `GoogleUtilities` version are updated to point to the newly tagged version rather than a checksum.* ### CocoaPods * Publish the newly versioned pod to CocoaPods It's recommended to point to the `GoogleUtilities.podspec` in `staging` to make sure the correct spec is being published. ```console pod trunk push ~/.cocoapods/repos/staging/GoogleUtilities/{version}/GoogleUtilities.podspec ``` *Note: In some cases, it may be acceptable to `pod trunk push` with the `--skip-tests` flag. Please double check with the maintainers before doing so.* The pod push was successful if the above command logs: `🚀 GoogleUtilities ({version}) successfully published`. In addition, a new commit that publishes the new version (co-authored by [CocoaPodsAtGoogle](https://github.com/CocoaPodsAtGoogle)) should appear in the [CocoaPods specs repo](https://github.com/CocoaPods/Specs). Last, the latest version should be displayed on [GoogleUtilities's CocoaPods page](https://cocoapods.org/pods/GoogleUtilities). ### [Create GitHub Release](https://github.com/google/GoogleUtilities/releases/new/) Update the [release template](https://github.com/google/GoogleUtilities/releases/new/)'s **Tag version** and **Release title** fields with the latest version. In addition, reference the [Release Notes](./CHANGELOG.md) in the release's description. See [this release](https://github.com/google/GoogleUtilities/releases/edit/9.0.1) for an example. *Don't forget to perform the [post release cleanup](#post-release-cleanup)!* ### Post Release Cleanup
Clean up SpecsStaging ```console pwd=$(pwd) mkdir -p /tmp/release-cleanup && cd $_ git clone git@github.com:firebase/SpecsStaging.git cd SpecsStaging/ git rm -rf GoogleUtilities/ git commit -m "Post publish cleanup" git push origin master rm -rf /tmp/release-cleanup cd $pwd ```
## Development To develop in this repository, ensure that you have at least the following software: * Xcode 12.0 (or later) * CocoaPods 1.10.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: `pod gen GoogleUtilities.podspec --local-sources=./ --auto-open --platforms=ios` Note: If the CocoaPods cache is out of date, you may need to run `pod repo update` before the `pod gen` command. Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for those platforms. Since 10.2, Xcode does not properly handle multi-platform CocoaPods workspaces. ### Development for Catalyst * `pod gen GoogleUtilities.podspec --local-sources=./ --auto-open --platforms=ios` * Check the Mac box in the App-iOS Build Settings * Sign the App in the Settings Signing & Capabilities tab * Click Pods in the Project Manager * Add Signing to the iOS host app and unit test targets * Select the Unit-unit scheme * Run it to build and test Alternatively disable signing in each target: * Go to Build Settings tab * Click `+` * Select `Add User-Defined Setting` * Add `CODE_SIGNING_REQUIRED` setting with a value of `NO` ### Code Formatting To ensure that the code is formatted consistently, run the script [./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console brew install clang-format@12 brew install mint ``` ### Running Unit Tests Select a scheme and press Command-u to build a component and run its unit tests. ## Contributing See [Contributing](CONTRIBUTING.md). ## License The contents of this repository is licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg ================================================ FILE: Pods/Pods.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 072CEA044D2EF26F03496D5996BBF59F /* Firebase */ = { isa = PBXAggregateTarget; buildConfigurationList = C6E4A38F24896DABC7A364E6DB2C7A7F /* Build configuration list for PBXAggregateTarget "Firebase" */; buildPhases = ( ); dependencies = ( 60B836A00436698D56971C2DF9FACBD3 /* PBXTargetDependency */, 07F7AACA5DCE705327010DA25FBEF165 /* PBXTargetDependency */, ); name = Firebase; }; B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */ = { isa = PBXAggregateTarget; buildConfigurationList = B703E077B86E18E406CBE29C0DBD8D5F /* Build configuration list for PBXAggregateTarget "GoogleAppMeasurement" */; buildPhases = ( CB5AF08085F7665FEB6F74BC117BDB28 /* [CP] Copy XCFrameworks */, ); dependencies = ( 7DA3592F4AED73F685BA39AED564AB8C /* PBXTargetDependency */, D4BF4589DADC31160B77BB8112E61B37 /* PBXTargetDependency */, ); name = GoogleAppMeasurement; }; C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */ = { isa = PBXAggregateTarget; buildConfigurationList = 252336D783BCD0B4415B0521B0962F14 /* Build configuration list for PBXAggregateTarget "FirebaseAnalytics" */; buildPhases = ( 36517C47DEF3A4F55B4079EB176CAC62 /* [CP] Copy XCFrameworks */, ); dependencies = ( E7A21F113AF491DD9E75451709813C86 /* PBXTargetDependency */, 7AFDA11FCD515215D24F9BDC345530D3 /* PBXTargetDependency */, E57689F1671B9303E2E6FC780D60D89A /* PBXTargetDependency */, 17945370CE1B53AF222677EE98E01A93 /* PBXTargetDependency */, 12F6562F185F60896BBCDADD4019778E /* PBXTargetDependency */, ); name = FirebaseAnalytics; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 007F57C0030B0CA201A359FD816A3CE1 /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = F2EC88E51FC34B63E85F84778E9C618A /* SDImageFrame.m */; }; 0184B6B56F7F8EC2CAA4300B5344B419 /* GULKeychainUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A75B93924C163BDD4BFE27BE82320E0 /* GULKeychainUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01CB8221A492BC65C076670906A3D08E /* GDTCORReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 87BD1262A5EDC0CE9D84214096454004 /* GDTCORReachability.h */; settings = {ATTRIBUTES = (Project, ); }; }; 02252699D8D416E0AD793B30C95C4BD6 /* GULNSData+zlib.h in Headers */ = {isa = PBXBuildFile; fileRef = BD01D4D2C9BD8D230AC07E17D1BC2580 /* GULNSData+zlib.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0311D5903A8C13AE6158796BC69AD994 /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B4DFE57A7C5FC68155C295484A72007 /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; }; 036B849251F9D940A08CE9945DF158D2 /* GDTCORRegistrar.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB1409CD0C3B140ECAB6006D2DAD8BC /* GDTCORRegistrar.m */; }; 03860676C8B2758B55FA5C92421B12FC /* FIRConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7055DAE8B926D478FFCB05DC46E7F844 /* FIRConfigurationInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; 03C3E562593A65D5184DAE4A6DDD883F /* FIRApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C2FEB053783A6171CE3DECCFF936A28 /* FIRApp.m */; }; 044993D3D534546E1F168C7CABEFD382 /* FIRCurrentDateProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B17DB1B80794FEE3D7DC12CB3641AB30 /* FIRCurrentDateProvider.m */; }; 047DCD22E37D894B69D095C8E537819F /* FBLPromise+Then.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 8D85590DB78FA3CE98F5876D319CDC9F /* FBLPromise+Then.h */; }; 04AA4C56A3620BE2A70364D47CE08AF3 /* FBLPromise+Wrap.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 47F2FC011CE490B793305158ACB2048E /* FBLPromise+Wrap.h */; }; 05102BD9E7BCB80DC7B64A34D0E7850B /* SDWebImageCacheSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = EA1C659029357A11E03301278FD8D706 /* SDWebImageCacheSerializer.m */; }; 05133941F28BBE0615644D0AFEC73AF3 /* FIRInstallationsItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E08253131AF6EBD5597DF21EBF9B98D /* FIRInstallationsItem.m */; }; 05214C0A74E896C781C8CB7719A9984C /* FBLPromise+Testing.h in Headers */ = {isa = PBXBuildFile; fileRef = F0647D92B0FBA5FC4BF6371012FEE181 /* FBLPromise+Testing.h */; }; 052F793DE768D1EDB5C1EC87A5A21F58 /* FIRDiagnosticsData.m in Sources */ = {isa = PBXBuildFile; fileRef = FEA2BFC7C6DBDD183F3EC0220976D86F /* FIRDiagnosticsData.m */; }; 05D76FA82DD3C37B76FFC1689CEF9981 /* SDWebImageDownloaderDecryptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 65F9EFAA484830CADC6FE4DE20867D8D /* SDWebImageDownloaderDecryptor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 06A3ABB4421B935533DD7D039442ADCF /* GULHeartbeatDateStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = F36DCFF7B72CE2940864E1364072151A /* GULHeartbeatDateStorage.m */; }; 07A41F67B40B3C6C6C3DB862F1F633F8 /* SDWebImageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BD491CEBE34A4FF9B067D4431A9525D /* SDWebImageManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 07E6B04D64953307335FF701FF858427 /* FIRInstallationsItem+RegisterInstallationAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = BAB945382DE7BCE1238478DF4341D1C3 /* FIRInstallationsItem+RegisterInstallationAPI.h */; settings = {ATTRIBUTES = (Project, ); }; }; 0A251DC0DF1E1EE8AC74D44C769312C3 /* GDTCORAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 813CEE72D58D250DD2A09A4157C51303 /* GDTCORAssert.h */; settings = {ATTRIBUTES = (Project, ); }; }; 0AA4EFD2D280F30CA93FD51643349602 /* uk.lproj in Resources */ = {isa = PBXBuildFile; fileRef = FEE086E4BCB7B965DFED58170011ADD2 /* uk.lproj */; }; 0B63C55C547C48DEE7EBAE9C9DFD3675 /* GULUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = D69D4E5A3DF64985BBF9D3080CCD6144 /* GULUserDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0BFA90EFCD3407716FFC60D140325188 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D519F9451A80C810AEF071E0A50F225E /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0C663893D044728B8050AD706689AA09 /* FIRCoreDiagnosticsInterop.h in Headers */ = {isa = PBXBuildFile; fileRef = D605D91349EBFFC6D84A254293FFF56F /* FIRCoreDiagnosticsInterop.h */; settings = {ATTRIBUTES = (Project, ); }; }; 0C76BA74E22A5CFD8207C10026401D80 /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 17A959EECDBC8B53DAF62371054DAAE9 /* SDAnimatedImagePlayer.m */; }; 0C80C3D7BC320914043FB768C88C8587 /* FIRInstallationsStoredItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9484BA52176E0E66CBDC23122D2948A2 /* FIRInstallationsStoredItem.m */; }; 0C8BD39D8D79C1A9BEFC904C457DCE89 /* FIRInstallationsAuthTokenResult.m in Sources */ = {isa = PBXBuildFile; fileRef = D54DA10983EF09ABC480187282A87A58 /* FIRInstallationsAuthTokenResult.m */; }; 0CC10185DD0B362B0934EDE2C557D7E0 /* FIRDependency.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9B5348D8E36B88FD0E3329BCF4479D /* FIRDependency.m */; }; 0D0AF4DF934B7BE39EC6E287B003BBA6 /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = EC28ED48C1E6F76572193AD374027BB7 /* SDMemoryCache.m */; }; 0D2B3D451D64A5A0AE5CE51E975D7494 /* SDImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DEBF0EE7331EEAD283D9D6370A2B3FF /* SDImageCache.m */; }; 0D4FBF072F6431A5D5324C83FC2E5DE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F030A051DB2F7C17CB74A9C0FBD7B56 /* Security.framework */; }; 0D7F532169AFF271D684865CF89DA8A7 /* FIRAppAssociationRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CBCB878C4026B383CA6687B4DC52D58 /* FIRAppAssociationRegistration.h */; settings = {ATTRIBUTES = (Project, ); }; }; 0DEECB4887F8ED8A8304156D1C440639 /* SDWebImage-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6BFCD79351B8CDEDCAB2DE6EDB89AE /* SDWebImage-dummy.m */; }; 0F4322740A001E3D7BFD2C333BCF907D /* SDImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B66ADF3FBC020B8BAE5E1C0C6EA3C650 /* SDImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0FC35E1D19F49ACD5B5957B86D32D4EA /* FBLPromise+Wrap.h in Headers */ = {isa = PBXBuildFile; fileRef = 47F2FC011CE490B793305158ACB2048E /* FBLPromise+Wrap.h */; }; 0FFA0C960F26666918569059FB8E92BC /* SDImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = E2A88BFB19C447C4ED3DE4168952FE80 /* SDImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1058BF78DCB651C56D33B7198AA0D355 /* FirebaseCoreDiagnostics-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 34A40FBEB8C0F4629E017EA065211E5C /* FirebaseCoreDiagnostics-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 10E9E4249C8C22A571EBFECF3C0676CC /* UIImageView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FDCDC0DC5EBD4242B3B44E167355E955 /* UIImageView+WebCache.m */; }; 116DB4571C6E70C54479C15F140245AE /* GDTCORReachability_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C1150A7649D44398A5F001918CBE0C16 /* GDTCORReachability_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; 11A2D8485890983668BBBDB3BE7602AC /* GoogleUtilities-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 71783D9D8F34A0AB09F92BAF64FC155E /* GoogleUtilities-dummy.m */; }; 11E79385CD0DA86166E63D21E7E078E9 /* SDImageIOAnimatedCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = BB81C8F74DB13FEF4D842A7A4127FEBF /* SDImageIOAnimatedCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 138DFC533930F784B55D9BB8AACC0E06 /* SDImageCacheDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = FF3EF4B23861192F1BF203B7E288A145 /* SDImageCacheDefine.m */; }; 1471F3BFCD49A1FBD39DA3CD4C3DA8B9 /* FBLPromise+Timeout.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A93FB313CA659376FAA369092F2D036 /* FBLPromise+Timeout.h */; }; 14B49802A4BD5C9EB648342B539ACC0E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; 153AB0D2CFAFC43A733B6B2A551FBE76 /* GDTCORTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = BCBC03DFAC32230F28CDE2D8E913A269 /* GDTCORTransformer.m */; }; 1554350BBDCB525AA477C1F345852437 /* FBLPromise+Do.h in Headers */ = {isa = PBXBuildFile; fileRef = ECF677E408094637A67960F364C6FA0D /* FBLPromise+Do.h */; }; 164DEB7B33A09DD8663942511488AD16 /* SDImageGIFCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = B6A08E238C0EA041AF882D5F75E6C3C9 /* SDImageGIFCoder.m */; }; 168AB5EA0EBEB75D0434B55D0B97AD47 /* SDImageIOAnimatedCoderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = A243783F2DB10CD105EA1B585E02DF32 /* SDImageIOAnimatedCoderInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 18191A26B494BB7777A6420722A46B29 /* FBLPromise+Await.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 1F96A93009C7CAECE802F2AF89962662 /* FBLPromise+Await.h */; }; 18303B692A81A7D708973A6B9CBA75D8 /* SDAsyncBlockOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 0440093C278700F6A5362B45AD5DBC91 /* SDAsyncBlockOperation.h */; settings = {ATTRIBUTES = (Private, ); }; }; 19BDF52B1C43F4A0EDAA7FA633B9BA88 /* FIRComponentContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 82AAE98FB8E850A8ECBA43F727AD3F4B /* FIRComponentContainer.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1AF4F328009E37FD64DA9CA51CC90932 /* FBLPromise+Do.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = ECF677E408094637A67960F364C6FA0D /* FBLPromise+Do.h */; }; 1CBE26399B4391159FDD5EF576CA6592 /* FBLPromiseError.h in Headers */ = {isa = PBXBuildFile; fileRef = E76B8095179CC1855E5CFA2FF2597A24 /* FBLPromiseError.h */; }; 1D635C91D8BC72532AAD16B6C063D7C0 /* FIRInstallationsHTTPError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E3D719558B6C623CB196272CE80D92B /* FIRInstallationsHTTPError.h */; settings = {ATTRIBUTES = (Project, ); }; }; 1DD6D013806F64DED16DD22BAED3C369 /* FIRFirebaseUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = F34C30C677383D5895A28720200014A4 /* FIRFirebaseUserAgent.h */; settings = {ATTRIBUTES = (Project, ); }; }; 1E1FF13F9AB197201C44EEC0D4E9E9EF /* FBLPromiseError.m in Sources */ = {isa = PBXBuildFile; fileRef = FCA00A91EC23FFE0892B9D0F0B84F617 /* FBLPromiseError.m */; }; 1E49C6EF4C021764307A50165CAA713A /* sv.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 030D92BA7629B33D7DEDDD755B2FB34B /* sv.lproj */; }; 20004B99F5050F59E747AB8DFAD7E891 /* SDWebImageDownloaderOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 22CACEEBCAA12953A3BA4DB2820EB06A /* SDWebImageDownloaderOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21D05279AA20EE7A1707A76C11BBCC00 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DFCA2753EDD74C8B7D46CD39BC4F756 /* NSBezierPath+SDRoundedCorners.m */; }; 22A29883D6BD2C46CFCB90EFE1E6B1B6 /* ja.lproj in Resources */ = {isa = PBXBuildFile; fileRef = D2D3BD16668BB31898CA2909AB1211CB /* ja.lproj */; }; 23490DF877054EEA5F983AD117033631 /* FBLPromise+Await.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F96A93009C7CAECE802F2AF89962662 /* FBLPromise+Await.h */; }; 23D8684E3F488264A90E5954D59B902C /* GDTCORStorageProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F3BFEC70D8BA82BE6E262B3750543AC /* GDTCORStorageProtocol.h */; settings = {ATTRIBUTES = (Project, ); }; }; 242A43C50C10362A01FA1F662EF7CF2A /* FIRInstallationsStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 33282DA51418F8E945CDA2BC7DDA734E /* FIRInstallationsStatus.h */; settings = {ATTRIBUTES = (Project, ); }; }; 242C08FFF99332102B7BF8E2590DA079 /* GULLoggerLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = 82EAA36E76AE8F4E664A68EC8EB36298 /* GULLoggerLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24DA814C2F7EA7C4187E2BF0B6466E9A /* SDAnimatedImageRep.m in Sources */ = {isa = PBXBuildFile; fileRef = EB886795A3DF2E63330CE092DB9B5F46 /* SDAnimatedImageRep.m */; }; 25FE5A4220B051E800D649E94BDCF418 /* FBLPromise+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = 490C3A82D71E20AB4B5BC07D75101D21 /* FBLPromise+Catch.m */; }; 26BF15D648EFA59FCA18AE285AC5B2A1 /* UIImage+MemoryCacheCost.h in Headers */ = {isa = PBXBuildFile; fileRef = 44E9E3AB162F34A2457874535E2D437A /* UIImage+MemoryCacheCost.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2711092C95505064BFF763747C805405 /* FirebaseInstallationsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EAF64B24EEF15C83158B0A1B832978D /* FirebaseInstallationsInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; 2749C745E4CCB505CACD055CAAF7B51A /* GDTCORUploadBatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFBDA1EBD167AFD2083DC8D9D565001 /* GDTCORUploadBatch.m */; }; 275F99E74511043432C892810081DCAF /* SDDiskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B960865AD112FB963F482E45E567B56 /* SDDiskCache.m */; }; 276A3E64698148850F514C10F440DDD0 /* FBLPromisePrivate.h in Copy . Private Headers */ = {isa = PBXBuildFile; fileRef = 278F2C97E692BBEB7227CFD22EE4333A /* FBLPromisePrivate.h */; }; 278F5B2C01E923AA0C691D79BD0F321F /* UIImage+MultiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C1F9B1C56AC542B7695BF945F7E8A5A /* UIImage+MultiFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2797DBBF0C00C0C9AED6C496F1AA1CB8 /* FBLPromise+Wrap.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEA3009F10DE79DB402A683F6839FC8 /* FBLPromise+Wrap.m */; }; 28532908E40E423052AA43B0000028BF /* UIImage+Metadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BC48CD5B43C672FF7213F9BE34110DB /* UIImage+Metadata.m */; }; 2A43AC77BE9EF6259E27876705069383 /* FIRLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = C18C4496C07F34201871F99AD27069A0 /* FIRLogger.h */; settings = {ATTRIBUTES = (Project, ); }; }; 2A866696A7C9DB4CB42A963DE9551B40 /* FIRInstallationsIIDTokenStore.h in Headers */ = {isa = PBXBuildFile; fileRef = E43C759DC1C3554D8988244726FFB280 /* FIRInstallationsIIDTokenStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; 2AF06216CDA7D6C38831BB201F73B566 /* FIRInstallationsItem+RegisterInstallationAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = E9D885B6C37485841C093413F8E4B5CE /* FIRInstallationsItem+RegisterInstallationAPI.m */; }; 2C4D5AB870A681E4EA5340FBD70FA010 /* FirebaseCore-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E666E3C34AF4E53A131BD4AF0F6ABA47 /* FirebaseCore-dummy.m */; }; 2C4FBEAF9B8A4CEAF32D5EA477EB6531 /* PromisesObjC-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BF54C90D1DF4DD3950B8DCD5C09D18C /* PromisesObjC-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2C762E5C654B718C19CC98FEB27C2DA4 /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = EACF04048B0827BDA828E835A7EA14C6 /* SDFileAttributeHelper.m */; }; 2CC0D5EBD928AAABF1DB1AE58E8710B3 /* FIRInstallationsLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = CAFA66ED08EE2214A12519DB72F11801 /* FIRInstallationsLogger.m */; }; 2D3E6969DF4465FC38E09B23A2562DE2 /* GULReachabilityChecker+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A6FE3762E8CAD647386716CD1698F48 /* GULReachabilityChecker+Internal.h */; settings = {ATTRIBUTES = (Project, ); }; }; 2D5F3E52A136C35D74B92551B645D588 /* FBLPromise+Validate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CD99347EC1D85D2C2FDDB3D1F9DF393 /* FBLPromise+Validate.h */; }; 2D7E7D5F025A1006C0D373B5884B6E52 /* SDWebImageDownloaderResponseModifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 233FC59CE7BD0ABCB6EFD98AF26F4228 /* SDWebImageDownloaderResponseModifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2D874A9744A25E43B747743945547339 /* GDTCORRegistrar.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FD16FE46197F77B6D4E28CDE644CC73 /* GDTCORRegistrar.h */; settings = {ATTRIBUTES = (Project, ); }; }; 2DD7B33B4FB4F06F6BDB9FDE23463CCC /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90E823CCF25045F00FF4E98E871586A6 /* CFNetwork.framework */; }; 2E1CFA6F381B7FD85B94D8043BA56219 /* GoogleDataTransport-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 932BDDF177FD04B28E9285096E7D291C /* GoogleDataTransport-dummy.m */; }; 2E30ED9EE4AA861FAB2BD7E875BFFCC2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7887F0BAB621B4623BFE017BC5FA339D /* UIKit.framework */; }; 2E658D649B1C8158B79BC43FFC5B298E /* GDTCORConsoleLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 22B6E2A2A128A5A1F1B6F8D6C0EF4BBE /* GDTCORConsoleLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2F85FF5597762E2E3C6961C6DD690A60 /* UIView+WebCacheOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = E2A2FCE7B47213F9F0585EA70EA2024A /* UIView+WebCacheOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3015C91C523C5133A639F57DE0425100 /* SDImageAssetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D7D4D1C90894E8A533206E5679B1871 /* SDImageAssetManager.m */; }; 3098423E501E28F376604CE13D8CD5E0 /* FIRCoreDiagnosticsConnector.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E7ED49344AE0AC6AD92262E047201BD /* FIRCoreDiagnosticsConnector.m */; }; 326BA8EC7036E90AE3AE390C40069CAA /* cs.lproj in Resources */ = {isa = PBXBuildFile; fileRef = D52688E7BC72717ABC1A8FD85DD32AE4 /* cs.lproj */; }; 32DD9B533A167E3D45FB7C01D4157D5B /* FIRComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 92EA46401122BB560F222964810DAE14 /* FIRComponent.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3339C7240948FC777912F0CCA95C3073 /* ko.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 98A2C33C3CFB15DFB0AD91D948975967 /* ko.lproj */; }; 336BA7F271A33087B951BA4C6C6B2C6F /* SDImageAssetManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BB1D1192CEC6C26D481BAB47C75BCA56 /* SDImageAssetManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; 33C0E2EFD1C8046A2020D25FD1F1611F /* SDImageAWebPCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = DFF930E1A1EF9818E6781AA056A03AD3 /* SDImageAWebPCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 343B53EB998CC5B609B8DE84BAD882E0 /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = A4FB500E6FBEE5F4910B4E4F3894E917 /* SDWebImageIndicator.m */; }; 3464ADF2B770D2FE01006027F4882723 /* FBLPromise+Recover.m in Sources */ = {isa = PBXBuildFile; fileRef = BE65A7532D1E92D1171FAB3E3DBF0EC2 /* FBLPromise+Recover.m */; }; 34816FB2D38145B7EC7D7A14CF5C93D8 /* FBLPromise+All.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D110B53C055335FB5D62021AD131E9F /* FBLPromise+All.m */; }; 36F35669F3AFF65477B2366FC09A1B3F /* SDWebImageOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 73F76A840EAA1BA85CD5CE2578134A93 /* SDWebImageOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 377AF2CA4D196DF568CEF1FB627B9B51 /* FIRInstallationsErrorUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E7EB7DF29D03415C41C56734E0C9017 /* FIRInstallationsErrorUtil.h */; settings = {ATTRIBUTES = (Project, ); }; }; 37E3C820BC838123661E1391BEAF8092 /* UIImageView+HighlightedWebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = DA01E7E21E39AD745F362344A6F1CA3D /* UIImageView+HighlightedWebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 388894A87C75381A940680CDD02C263E /* GDTCORReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 5352D3BC21FF5229DD4FC39829C48E7C /* GDTCORReachability.m */; }; 38B61754E5D4BFD359FDE9EF1BE4A661 /* FBLPromise+Delay.m in Sources */ = {isa = PBXBuildFile; fileRef = 239EF7C666D42E75FB20E65D3491B74A /* FBLPromise+Delay.m */; }; 38CF7D5EE6C28780FE3F9C1E63CFF3D4 /* SDAnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 615A6102144F4931AF689AB96C01D719 /* SDAnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3919CF80EC56ACAB6A3808C398ED1968 /* FBLPromise+All.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D82EC7D361BCA9009FD349978107D1C /* FBLPromise+All.h */; }; 3A899B9C5A8C258ED40829D2BBB6CF78 /* FBLPromise+Reduce.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = F1A5B63B766812910C3FC66A4D5BD359 /* FBLPromise+Reduce.h */; }; 3B72C235C1AB8944333C2322F08B6B46 /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 09F19A6EF0F8A7D0CDEEA09F1E90D4A1 /* SDWebImageTransition.m */; }; 3BA8E6BD1A15686C195985599A525C84 /* GDTCCTUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 11DC645818CEA0B49FB41D10974587E3 /* GDTCCTUploadOperation.m */; }; 3C1CC94B45A08AF7DC9A6DAF03574A52 /* Pods-Spotify-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F124022073EADCF94FCBED857E5EABBB /* Pods-Spotify-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3C22E2D2D6CDBD066DEDBEA8CEB50170 /* nl.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 700956653190B059B98E6D0401813D2F /* nl.lproj */; }; 3CDE39AF868FA2AFB18B9739A7F71A7F /* GULMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = BD3097914838C55DE30D9D377048A664 /* GULMutableDictionary.m */; }; 3E24B31FF1BB232157618DC8EE918071 /* tr.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 45A1925DBA110CDCB56C30C2AEC79D96 /* tr.lproj */; }; 3E5A84FD8D62B473AF24D16F36112D81 /* FBLPromise+Testing.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = F0647D92B0FBA5FC4BF6371012FEE181 /* FBLPromise+Testing.h */; }; 3F567CE353442AE137B71B258409F5DA /* SDImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5304860EBF8FA61DE4A892566862746D /* SDImageLoadersManager.m */; }; 4075297FF5A21C84EF513521FD7B34EF /* SDWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = CEB675D419ACF104AD5C8F2619C4FCF5 /* SDWeakProxy.m */; }; 407A10BF833B480FECDAFC51A259D6D4 /* FBLPromise+Retry.h in Headers */ = {isa = PBXBuildFile; fileRef = C869A091C5104F60176DCBAC53E9FCD6 /* FBLPromise+Retry.h */; }; 41286FFA3116FB4073CFA821DFB09A03 /* AppiraterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4687296E13EF95A9B5206235744E21EC /* AppiraterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 412EE262DCED176A73591124DBDBA97C /* UIImage+MemoryCacheCost.m in Sources */ = {isa = PBXBuildFile; fileRef = 35329B868A55A887B96B3891C90CE411 /* UIImage+MemoryCacheCost.m */; }; 41382DAAC306E1762433B4D291DAD21D /* PromisesObjC-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 36C228F973FB461AAC3DE1B3CAC5533C /* PromisesObjC-dummy.m */; }; 41522E9AD19ACF8675C10B4035405E95 /* FIRAnalyticsConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 668CE7FB58FBCA03E7BF9FFA7D4AEA8C /* FIRAnalyticsConfiguration.h */; settings = {ATTRIBUTES = (Project, ); }; }; 41EB77C2185B44AC4251285C57FD024F /* GDTCOREndpoints.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AE71CC3E7412E1F33D2D46AB258531C /* GDTCOREndpoints.h */; settings = {ATTRIBUTES = (Public, ); }; }; 421ADEBE65337F1EB365DD161EBAC21C /* firebasecore.nanopb.c in Sources */ = {isa = PBXBuildFile; fileRef = 495F9A8ED0CBC83C181879060E49F21E /* firebasecore.nanopb.c */; }; 425F70D9B529938D9F19EE92FDBAA4E5 /* zh-Hans.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 255E93987C91799073F418C741D2F912 /* zh-Hans.lproj */; }; 42822E48A1441AD8E0011D37C1625B36 /* SDAnimatedImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = CF04F211D2AE46EEBF166D2CC374B517 /* SDAnimatedImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 429E5CC928985357D07058FDEE8069FA /* UIView+WebCacheOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = EE2390C8AD53927134B524CBC997D50E /* UIView+WebCacheOperation.m */; }; 42C1C2FC054DE4C8F98D5D9EABB99732 /* GULApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = B134BD979A35DB81CA4CBD64BE86AAC5 /* GULApplication.h */; settings = {ATTRIBUTES = (Public, ); }; }; 44C7A4ACB41F2B0E588E01C98A0BBE31 /* ar.lproj in Resources */ = {isa = PBXBuildFile; fileRef = EE0CECAF5DA5AAE13E1615E7B14147CB /* ar.lproj */; }; 457289616B6777C1D443FBC91A2F0325 /* GDTCORLifecycle.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CD317B6F97D4204C18D7618DBB86C03 /* GDTCORLifecycle.h */; settings = {ATTRIBUTES = (Project, ); }; }; 4623448C58E0E271F0DF5F4CC6CAFD0D /* SDImageCachesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BFEAB29224248CA276353656139D1668 /* SDImageCachesManager.m */; }; 4634DFDEF1A08A4C90EF8CCDBEF04003 /* FBLPromise+Always.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = FB9896C32514099F18D7AA422602FB04 /* FBLPromise+Always.h */; }; 4649E8C6659D310A609EF296697D6C4F /* GDTCORDirectorySizeTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 606B14BF9E8B55AA634896F3FE6C2102 /* GDTCORDirectorySizeTracker.m */; }; 474163E6CF20CA1AC3698759F2B6D51B /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0BC2BA98097A46FED66139FADDB5C2 /* NSButton+WebCache.m */; }; 475453293C1679D47716F8E17703824E /* Appirater-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD9056754A12ED3E38D68945FE70B586 /* Appirater-dummy.m */; }; 4795853F6479422BFD716A20342E99EB /* FIRInstallationsSingleOperationPromiseCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 7387A62B07188200FF8487FD3A2CF2A5 /* FIRInstallationsSingleOperationPromiseCache.m */; }; 4844A5C12A400CAABFD7CF2283FDAEAC /* FIRInstallationsIIDStore.m in Sources */ = {isa = PBXBuildFile; fileRef = D5FB0717B52FE2A1C59CA75D8CE483EA /* FIRInstallationsIIDStore.m */; }; 48920AFFC7C0E2FEA632DAA2BD63287D /* SDWebImage-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = A6344AC78A87A0720BE33CE02655D669 /* SDWebImage-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48CB1FD2981BA256E4F2582237164A2F /* FIRCoreDiagnosticsData.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D509EE7C37DC62C5F5119B2692E792 /* FIRCoreDiagnosticsData.h */; settings = {ATTRIBUTES = (Project, ); }; }; 493EEC1796ACFA77A28E7630060A059E /* Appirater-Appirater in Resources */ = {isa = PBXBuildFile; fileRef = 1E9CD753F6BD26760EFAA1DF57482D50 /* Appirater-Appirater */; }; 498AB54D6531F0B57EAAD113AA0CF0F9 /* SDAsyncBlockOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DED0CE98C3CC1D5B1BEBB3F2409DF55E /* SDAsyncBlockOperation.m */; }; 499C6F529CC2F5D8A828374ED7DE1069 /* FBLPromise+Race.m in Sources */ = {isa = PBXBuildFile; fileRef = EFD6BF26B215DD780EA6145DFEEC525A /* FBLPromise+Race.m */; }; 49F97D8BA4E81A5E0EF866A293392AF2 /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4695D22C004206B1EF601650EB58F1 /* SDWebImageDefine.m */; }; 4A27F890C33243C4548A25318264B375 /* UIImage+ForceDecode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FCDB01EEB669605F883EA4E79E56AD2 /* UIImage+ForceDecode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4AD40F5B3B6CBC790C65872ACCC69B0B /* NSURLSession+GULPromises.m in Sources */ = {isa = PBXBuildFile; fileRef = 61C15E699851C928BE91C72A5169AEB2 /* NSURLSession+GULPromises.m */; }; 4D9A4DD4DA000DB1A7233197AB5C9998 /* GDTCCTUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 018833202B971E61CF36DA9775AED769 /* GDTCCTUploader.h */; settings = {ATTRIBUTES = (Project, ); }; }; 4E024B5A716EC026E33AC8EF18023F4C /* Appirater-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D2771347E37390095BF7D36EB989050F /* Appirater-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4EDE67DB34FEC06E0EC09F4CCC914EF8 /* FIRInstallationsAPIService.h in Headers */ = {isa = PBXBuildFile; fileRef = F1DFF11C55F9B1D83DF7C7397FDACBA2 /* FIRInstallationsAPIService.h */; settings = {ATTRIBUTES = (Project, ); }; }; 4F5982101CB9FB68613E826F137B5E95 /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 80FE85865E814FFFE9FC4BD1E92FF02B /* SDAssociatedObject.m */; }; 4F5CFA3002CC0285EAA22323D1E017B7 /* FIRApp.h in Headers */ = {isa = PBXBuildFile; fileRef = C2B6C797B8E1770AE7FB92B865BB499B /* FIRApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4FB353F2B2E7D7AE0811D7C538DA2B36 /* SDAnimatedImageRep.h in Headers */ = {isa = PBXBuildFile; fileRef = B95FF8BC5E40163C886E3DB1B394DD75 /* SDAnimatedImageRep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 50FB78A15928E090B123F944AF92656A /* GDTCORAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 363CE7A6599529EED98580D11773BD01 /* GDTCORAssert.m */; }; 519B33DCAE0FB73CC313A2C2AD434449 /* SDImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A10001AFFFB54BB477050134573A9C4 /* SDImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F891EA712D8F14CF1DD77BF20B62A9 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C78361E6DA67CDEC2A04C25F1B40D31F /* ImageIO.framework */; }; 523861A018452CE8BB166B59A7872BDA /* SDImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = DD910C5803E3E0E5E17C5ED00AFEEEC0 /* SDImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5331944436C43951CC5FA7788DEE9BCF /* FIRInstallationsBackoffController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E03F27C2DCF8DB117CBC29C875DCD95 /* FIRInstallationsBackoffController.m */; }; 536E9C0D3855A6AF7CADCB9FBAFC613D /* SDImageIOCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DD11686FB044C460680737D6E66EC0C /* SDImageIOCoder.m */; }; 551EE2BA0A3A93F089EF76F85871B5EE /* GULNetworkURLSession.h in Headers */ = {isa = PBXBuildFile; fileRef = EF3303042C21F9396439AB3DE71C7BB2 /* GULNetworkURLSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; 55770A7793FDC52677664F24B19FC7C6 /* FIRComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 00287C1BECC76217689476B210B41A36 /* FIRComponent.m */; }; 55A09541F17B3F7BD4C62636A15F6212 /* Appirater.m in Sources */ = {isa = PBXBuildFile; fileRef = 0466C52ED49E6E1C863BAFB6E1AE5B92 /* Appirater.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; 565EF7659358278D993E12356980AE0C /* FBLPromise+Delay.h in Headers */ = {isa = PBXBuildFile; fileRef = B8633B4212F9E21E512835226D2BEC2E /* FBLPromise+Delay.h */; }; 56E2818C97A95CF9EC25FF22E92B0F3E /* GDTCORClock.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F6FA7F2B642E68ECD25BCCB0DDAAA75 /* GDTCORClock.m */; }; 573CBCA281A2BB0F097329EE28BCFE60 /* pb_encode.c in Sources */ = {isa = PBXBuildFile; fileRef = 90D9BC28AA40B8F7AAC5DBAFFFA2503A /* pb_encode.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc"; }; }; 57D8A29DA38A71184A984913BBC01CEC /* FBLPromises.h in Headers */ = {isa = PBXBuildFile; fileRef = 7283DD0E961B31F830AEB840A7B59650 /* FBLPromises.h */; }; 57E1C476BA5F8DD1ED740FE8DBC05D8A /* SDAnimatedImagePlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FA3607B121AEFB0595C76441513C64D /* SDAnimatedImagePlayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 57F70A5C8A2F7A13B0E33409440F4ABD /* FBLPromise+Async.h in Headers */ = {isa = PBXBuildFile; fileRef = 7FDFC5FEAC4A550A50CD516405F6DFF2 /* FBLPromise+Async.h */; }; 58FAE3A59FBE896EBBDACB0808067CA5 /* SDDeviceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32DB218DD71AD0570B0E339E5319F93B /* SDDeviceHelper.m */; }; 5911F99F8CABED05F9F701DC450E4BC8 /* FIRHeartbeatInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E16667A1668B640A863FF171D2CB5FB /* FIRHeartbeatInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; 593E6264A36C1B39D5885E85304C8916 /* FIRLoggerLevel.h in Headers */ = {isa = PBXBuildFile; fileRef = A4A6DED8C7023B5E793A748ED43129EE /* FIRLoggerLevel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5A3E5B854E8188DDF4E958BBC9198600 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; 5ABA716A48A3468C5B83F83D69DDF5D2 /* pb.h in Headers */ = {isa = PBXBuildFile; fileRef = 15C9B0DD52D5C611E1AB4BB7A9A92DF5 /* pb.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5B3271A67137276C4B2CD554BAB9F4DA /* UIImage+MultiFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 50AF89B1AE50DF562243E31C949BD5B3 /* UIImage+MultiFormat.m */; }; 5B69919844A3BD774BAABB186D6A524F /* SDWebImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 370CEA1E37CB5E0D01FDBFF39E1020BE /* SDWebImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5BBE7719912593F1DEFF8E763EAD4ED6 /* SDImageCacheDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = 5852FC7ED30AAEEA07547AF6D71F3D85 /* SDImageCacheDefine.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5C10B5CF2AEA5FB4A63F282ECA9F7CB7 /* ro.lproj in Resources */ = {isa = PBXBuildFile; fileRef = D053D0A9CE99EC16E5ED88D7BA8C3A65 /* ro.lproj */; }; 5C4E850422971D9660A700996C75898F /* GULAppDelegateSwizzler_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C5DD83B8521D66EB85A5A45A92F810F /* GULAppDelegateSwizzler_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; 5F1DA411EF4EF25F7278116F9DBD8FB8 /* FIRInstallationsStoredAuthToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F25465DB16E55EFF41F24C620E24E05 /* FIRInstallationsStoredAuthToken.m */; }; 5F49C3C9A3733CD50642E0B57D405D44 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 079B6435FC4625DBC4A439EDF7FD5844 /* SDWebImageError.m */; }; 5FCA234EC54B026FF4FE2CCCFCB1EE7E /* pb_common.c in Sources */ = {isa = PBXBuildFile; fileRef = 983C23FA76E7A245275E7A252B67A2A0 /* pb_common.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc -fno-objc-arc"; }; }; 611CE969CEC379BB77D8885D915A8095 /* GULSceneDelegateSwizzler_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 56EBF81C7BB707870CF8F18F19C219BC /* GULSceneDelegateSwizzler_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; 61CD5A24511DC980EFF397CF57AC50F5 /* SDInternalMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = C48BA4C3D0C62019243E819DD277C007 /* SDInternalMacros.m */; }; 620EBEBCDCF8233A6CED99BD83B4C8D1 /* FBLPromises.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 7283DD0E961B31F830AEB840A7B59650 /* FBLPromises.h */; }; 62A22A4C10675F9B65C7677CFD6C4676 /* SDWebImagePrefetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E7185858699874B61C27769747820E1 /* SDWebImagePrefetcher.m */; }; 6337C7DF0D550595E0FC08E973C61709 /* GDTCORUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 2FC875B27ACA63CA271CA0D921CF94C3 /* GDTCORUploader.h */; settings = {ATTRIBUTES = (Project, ); }; }; 64CDB33E49BDBE16653C88756E26B8CB /* SDImageCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D8AA9949B871C5C08476B12041D2D99 /* SDImageCoder.m */; }; 64DF55F7B991438E693B9A078807E55B /* FBLPromise+Do.m in Sources */ = {isa = PBXBuildFile; fileRef = A6888DFA1022D0AF9FB4109175558CEC /* FBLPromise+Do.m */; }; 6503E1414B68019CD76455273240A263 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BBDEBAD931B605980FE3DCE9FA337AE /* SystemConfiguration.framework */; }; 658CF66D84C572191E43DBCA78450144 /* FIRInstallationsStoredItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 25D0D7E32DDD7AE56C23799445C13A57 /* FIRInstallationsStoredItem.h */; settings = {ATTRIBUTES = (Project, ); }; }; 65F85B6A937820FAA8F2ACA55AAC93D7 /* GDTCORStorageEventSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BE80A247D8376A910A19BFB10B99430 /* GDTCORStorageEventSelector.m */; }; 67337806BBF45E0F89460C0ECB98F7CC /* FIRInstallationsAuthTokenResult.h in Headers */ = {isa = PBXBuildFile; fileRef = D8ADAB8E982AFC5F2BD545EB091C78BD /* FIRInstallationsAuthTokenResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; 67B0CAAD8E88B9A2357C41E73EA2EB48 /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = F9DD6F50E5F16B3EE2AFBBBA48053FA5 /* SDWebImageCacheKeyFilter.m */; }; 67B3965694762B918FFBF665F842D881 /* FIRConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = A3142D0BC0A7099D82BA56A3FD637656 /* FIRConfiguration.m */; }; 67B3DCBA30FDEDA18E0D4E3B2C16C911 /* SDImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = F538296E4FA1BF861A19524F47E7A6D5 /* SDImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 685481CA7A154E8AAE00089FC07BDC74 /* GULAppDelegateSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = 16A5A619D3393C3652027A63F8D6406E /* GULAppDelegateSwizzler.m */; }; 694921E04F46359CCC4C2A3DF0ADBD04 /* FirebaseInstallations-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = EDF07392617868FE264D5E58788D279F /* FirebaseInstallations-dummy.m */; }; 6A60904A4A5C9CB728F36E68131A5629 /* GULURLSessionDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F2853ABAFCEFBF960A5040D649074EC /* GULURLSessionDataResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6A9D73033878EECC7CAD51C2C82AF26B /* FIRLibrary.h in Headers */ = {isa = PBXBuildFile; fileRef = C76E61D46C6D64C4743B6A7256A286EF /* FIRLibrary.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6ABBB7192F4FA78A90D45CA78172AC86 /* GDTCORConsoleLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 93498C80BEE42599CD4B49D1DCDE8DAA /* GDTCORConsoleLogger.m */; }; 6B83391B3231B704CBEADFE1F3ABE0F1 /* firebasecore.nanopb.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A58961C9BBD35E7B60ECDB62E56F7DC /* firebasecore.nanopb.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6BDCB1B9157D9AF6B3A78C0E0619FFA2 /* pt-BR.lproj in Resources */ = {isa = PBXBuildFile; fileRef = C3AECB4945D348C50164B9454BA98CB3 /* pt-BR.lproj */; }; 6C301FF2C25C6329F7EA45C87F96C660 /* FIRInstallationsIIDStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 703B93588CDC01E3116988AAD425A74D /* FIRInstallationsIIDStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6C93ABDF963DDA7C78AADD9F22CD5CB1 /* NSBezierPath+SDRoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = E06211AF2F4E3047043DD4BCDC40403C /* NSBezierPath+SDRoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6CFA7E848AADCF3237F5D70EB4019775 /* FIRVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 724419369F9B475400E63E05193D0394 /* FIRVersion.m */; }; 6DA57657B2030D8076B7B341150BC06F /* GDTCORDirectorySizeTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 66E791BF8C770CDCE827420B1959B849 /* GDTCORDirectorySizeTracker.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6EFEF0AC86E0308D25D125522708DD7F /* FIRInstallationsLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 99108619FD685C7E04AA2798D8184E88 /* FIRInstallationsLogger.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6F030A5B151E155313EFC0C843C68018 /* GDTCORUploadBatch.h in Headers */ = {isa = PBXBuildFile; fileRef = EC47234C0173B2827334091E5C04C152 /* GDTCORUploadBatch.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6F333D5505E952CF9C517DAE5114AC2D /* FIRInstallationsStore.h in Headers */ = {isa = PBXBuildFile; fileRef = B46C4D5C2FF1FB2417D06ADB91D1CEBA /* FIRInstallationsStore.h */; settings = {ATTRIBUTES = (Project, ); }; }; 6F4B172730B7293C1D988FBE34B45E20 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = FF32D6A22D56F5787F289A4BEA15E6A1 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6F7307112195D5B6083F450AB2657AE2 /* GDTCCTNanopbHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CBEB10B215FDB65E86331667E87AE84 /* GDTCCTNanopbHelpers.m */; }; 6F9B1AAD95146EC2AE89F5B821D611D6 /* FIRCoreDiagnosticsInterop.h in Headers */ = {isa = PBXBuildFile; fileRef = 5805E31EC45A505EEE3457FA2B9FA351 /* FIRCoreDiagnosticsInterop.h */; settings = {ATTRIBUTES = (Project, ); }; }; 701F3C59F0434AC67482392B26FB6F0A /* FIRBundleUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 303FE2537CE3769B58923B71632FED1C /* FIRBundleUtil.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7061D35639F93885B8A80B3B7FE090F7 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BBDEBAD931B605980FE3DCE9FA337AE /* SystemConfiguration.framework */; }; 7100C2CFC22D1689ADA78A36E65B697B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BBDEBAD931B605980FE3DCE9FA337AE /* SystemConfiguration.framework */; }; 71093AA6681FFAD2FA263E3FADE983D1 /* GDTCORRegistrar_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0338A87088B87F5A5D1BB03CA2AB274B /* GDTCORRegistrar_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; 714EB6036BF393E60ADE1F6F2C7E8A1F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; 71A8CC130B0CB8E978F51381AFD722C7 /* FBLPromise+Always.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFCC2FB818BA3289E85B84DD2F068A3 /* FBLPromise+Always.m */; }; 72BE14CDABBE9D94A85828F987136A6E /* FBLPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = F35AA80907A78AC92F34C61F368248E7 /* FBLPromise.h */; }; 72C3E949015B30F289F28DEDE6BA8F84 /* FIRCurrentDateProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B349AD374EBA10C2D49FC4BA8A53F0CF /* FIRCurrentDateProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; 73F2DE8B48DC2F7C87529AB3901B9E94 /* UIImage+Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 859F0A43E3649885E17F4D515A86A159 /* UIImage+Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; 74849A92CED98D844690D9779D6F6DD1 /* FIRComponentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B185568A19C67E5278D6DB507473C2B /* FIRComponentType.m */; }; 74B90EA2A2E4ADC58C8693B6FB534D85 /* GULLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 4468F16CABC244B58ACDC50E87F8B3C4 /* GULLogger.m */; }; 74C6F951FCB4661E545409C0AF80D2E7 /* GoogleUtilities-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F54F5C198006F0294B0657489850E48 /* GoogleUtilities-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 762A59E0FF6F1A92B360C6E0A5E4A7CA /* nb.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4F72FBD1344FC32D12AA727F983904D6 /* nb.lproj */; }; 773B2DCA4762991C7F8FAE2FC860253D /* GDTCOREvent.h in Headers */ = {isa = PBXBuildFile; fileRef = BBB794E91F141A51A62086A95A79B3B0 /* GDTCOREvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 77B7C664156789B282A6AECEB7F380C7 /* FIRAppInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 66B0B7773B93F50516AFA7A4AD80001F /* FIRAppInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; 78AE96580BD551EB905F72C21AB81E97 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 99AC313325A72709F61AD9B82245FC4F /* SDImageCachesManagerOperation.m */; }; 78E730C2966F82AB15136ED2AFC85095 /* FIRComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = D71B48D7039219AEB311FB0DA2E1E389 /* FIRComponent.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7975CB02E10D105FA23A30B254C25F4F /* GULNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 0502AAFCB22CA8A06A21C20517BE89B4 /* GULNetwork.m */; }; 79B821DE59EE806496A9FD4FF3FEDAB7 /* FBLPromise+Then.m in Sources */ = {isa = PBXBuildFile; fileRef = 18637DFB49F868B5320F06FCD27ABD99 /* FBLPromise+Then.m */; }; 7A0703CD6D58504249D27BB436F68B39 /* FBLPromise+Validate.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 2CD99347EC1D85D2C2FDDB3D1F9DF393 /* FBLPromise+Validate.h */; }; 7A1EBC1F342550748C7D0A21F7BFA7D6 /* FIRInstallationsItem.h in Headers */ = {isa = PBXBuildFile; fileRef = D29D1ACD3388DF30F5213782D0089AAE /* FIRInstallationsItem.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7A424FED21A399FE2610DCB710A4A3FB /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BBCA689ED4AD568D1203A32DDCB2654 /* SDWebImageOptionsProcessor.m */; }; 7A6E617487687A7EC3ED28D9090EC0E4 /* GDTCORTargets.h in Headers */ = {isa = PBXBuildFile; fileRef = F589BA173CD44451894C642200BAD5A9 /* GDTCORTargets.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AE82F1B24F7A2E9B28BAF431611111F /* FIRHeartbeatInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CDC11191CD5FAC6E9AA96E2BC7DAD05 /* FIRHeartbeatInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7AF1433C05B27E85A58B5222B73620BB /* FIRInstallationsSingleOperationPromiseCache.h in Headers */ = {isa = PBXBuildFile; fileRef = A55020CA6EDE9396075075D402E84AEC /* FIRInstallationsSingleOperationPromiseCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7CA7517C4920C00FDAB34D5152390BA6 /* es.lproj in Resources */ = {isa = PBXBuildFile; fileRef = EF7A55F92FBA10C1CA52F98BF5B091C9 /* es.lproj */; }; 7D41A53C422B8849362A0A50C943F0E1 /* GDTCORTransport_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 67A976AAF1EAC9BADF423E17C96B88FE /* GDTCORTransport_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; 7D9E8F8A5D02148FCC2DBD6319BCC3D1 /* SDWebImageDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = 91D7D93CD15EE8BBB672073A9EE9172E /* SDWebImageDefine.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7E6862A73246BB1A0E7BCEBAF50FFBE1 /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = BA37D2C1168173A7796B05F93479A231 /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7E7E08BEE28A0144B41743F4C265B0AC /* sk.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 573447DBCFF573125A97AC8FE08CF8FB /* sk.lproj */; }; 7EF58E7AF295CBE99496A96A5D8C13EA /* FBLPromise+Any.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2BE9FCAE442C062B1070B5F83D9D9 /* FBLPromise+Any.m */; }; 7FC014E10F04D9761116EE2481087F70 /* GULOriginalIMPConvenienceMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = C2439987FF247C8C084181EBCF11641F /* GULOriginalIMPConvenienceMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; 809A09224ABB6A00BEF6B72EFAB30B4E /* SDImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = B1C3F5E1F2DA4D224862FC3F811D0917 /* SDImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 80D4A72C26C2EF865F9DCA400D2F1E70 /* SDImageGraphics.h in Headers */ = {isa = PBXBuildFile; fileRef = B195FEEB2B6FD768A1270ABE2938AA39 /* SDImageGraphics.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81534784497FC1ECE74D5C18D43AF3EB /* GULReachabilityChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = FC22CB2FD3FE9BC3D607B5CA39155B83 /* GULReachabilityChecker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 832067DBD9BAC003B8E5687562E6BBCE /* FBLPromise+Retry.m in Sources */ = {isa = PBXBuildFile; fileRef = B0893F86784B88FBEBDDAD469BA72394 /* FBLPromise+Retry.m */; }; 83747CB2CE756E6DE235E7A419CE6B64 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4958CF39D5BE0CE693F9BABAB470D5A1 /* it.lproj */; }; 83ACE2DB5F2F7F9352D11E9BD334AD96 /* FBLPromise+Any.h in Headers */ = {isa = PBXBuildFile; fileRef = 70FBB40CF13B29C7EC31294E2767F546 /* FBLPromise+Any.h */; }; 83F5B3ABAFBB47CDA2E64D9C879F2167 /* FIRInstallationsIIDTokenStore.m in Sources */ = {isa = PBXBuildFile; fileRef = DDDFE2B6438138D00D92226D62FA76AD /* FIRInstallationsIIDTokenStore.m */; }; 842007158292E13DF5B3878A89BD4727 /* FBLPromise+Race.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 447B191325C26D8C11C3D317EC3FBAA8 /* FBLPromise+Race.h */; }; 8433F353F3016EFF0316F48188D6B651 /* SDInternalMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 804DFCC909792374DF921D69BE1F1C18 /* SDInternalMacros.h */; settings = {ATTRIBUTES = (Private, ); }; }; 84B624227D73D3746CEFD48E49D74635 /* SDAssociatedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = C6A1BDEE41C3F181B6BAC16ADB84601E /* SDAssociatedObject.h */; settings = {ATTRIBUTES = (Private, ); }; }; 84B818EEE3E3EE948F29B61A752BFC0B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; 84F3CB5D0530A295C9D23F675A5904B5 /* FIRLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0720E29DA86F4D52C08D6942FFEF190A /* FIRLogger.m */; }; 854D7A4A2872566E4D06870518053225 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 37B1C403940FAFD0BDA8092C0726458A /* SDImageAPNGCoder.m */; }; 85DBD9AB9CEC227EB078B8EC52906809 /* GULUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = CF805F43FFDD645D95C1ACEABC114049 /* GULUserDefaults.m */; }; 868244D3347466936301E48DA39BE842 /* fa.lproj in Resources */ = {isa = PBXBuildFile; fileRef = A86DA94DC9AB6D6E95B73BF4F59C3EF1 /* fa.lproj */; }; 86A4EDC386D953055E2EFD288F8549E7 /* GDTCORFlatFileStorage+Promises.h in Headers */ = {isa = PBXBuildFile; fileRef = E9E0C507183FA6B14B3FED0BB9BFE8E5 /* GDTCORFlatFileStorage+Promises.h */; settings = {ATTRIBUTES = (Project, ); }; }; 86DDBF748A2FAB2E5426D9C62F4E97B6 /* GULAppEnvironmentUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B538C0DED9A96F51790585E9AF03D97 /* GULAppEnvironmentUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; 878C1E830C12ED8A433F6FD3DAC38946 /* FIRComponentContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BA2A90BF46E4D5796820E1A900E2C0B /* FIRComponentContainer.h */; settings = {ATTRIBUTES = (Project, ); }; }; 87EE04B662C6A6A6E3731E657170FBB8 /* GULReachabilityChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = EF82E76BE97070A77C122D8CD5A2A852 /* GULReachabilityChecker.m */; }; 8821E3636BB86560FC2F2D2CFF545F16 /* SDImageCodersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A1C27AE73C820EC4ACBF12FAEEA1D93 /* SDImageCodersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 888BEE4C3B238AC407C59D2194939AB9 /* SDImageCacheConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D8A688C63FDD5533A68B0CD80B0501E /* SDImageCacheConfig.m */; }; 89A0AC3A2E24FDA5CEDF545C0050C90C /* GULHeartbeatDateStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 05850FDD1539ACB6AB1C9DB0142B8403 /* GULHeartbeatDateStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8B6E3CBB15DD0345506BCEA811AEAA43 /* FIRComponentContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = AE8073B498E3701A492F026966747912 /* FIRComponentContainer.m */; }; 8BFCDB90BD5C326BE36EE7599F016413 /* FIRInstallationsBackoffController.h in Headers */ = {isa = PBXBuildFile; fileRef = 382D07A53D722649DCAD5C5BC3BDCAC1 /* FIRInstallationsBackoffController.h */; settings = {ATTRIBUTES = (Project, ); }; }; 8C207CEE5189EAF1C9408F8FCE291E11 /* el.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 99C91ADC1F402BD8ABEE63EAB8134747 /* el.lproj */; }; 8C5D0B2A7B52DF68D03820244993E91B /* SDImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F824E440C097E5A5FE69CFBC7293F15 /* SDImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8C9D609988FE2993BB116B4B00FAC57B /* SDWebImagePrefetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = B12CEE2C03EFBD7EBAF1717349B42067 /* SDWebImagePrefetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8C9EAF8201E92AB60FFE3A04A6A1D47B /* FIRCoreDiagnosticsConnector.h in Headers */ = {isa = PBXBuildFile; fileRef = 747BA4839026A5B48F78F7F2F59B01D8 /* FIRCoreDiagnosticsConnector.h */; settings = {ATTRIBUTES = (Project, ); }; }; 8D374E688F039AFD9D5F03C0A24DC4B6 /* GULSecureCoding.m in Sources */ = {isa = PBXBuildFile; fileRef = AB007F418E0ED0E20061B9ED7D831C76 /* GULSecureCoding.m */; }; 8D3BB2AEBD692793E70D2D3A2BED2135 /* FirebaseInstallations-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 54D381D3559D8677EA0632FDB5EEA05A /* FirebaseInstallations-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8E2FC561736D6CE4D3D7F50A4DE1A540 /* he.lproj in Resources */ = {isa = PBXBuildFile; fileRef = D005D4ECC0D56EB73527C065B49D192C /* he.lproj */; }; 8F88549F032EFFEA9C281860520CC879 /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 43363A5F804039F569FD9ABD9433F07B /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 909CBA577868D6E976AC82340B50343E /* SDImageCachesManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AA6F310E602B6F29B722C01961258A4 /* SDImageCachesManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 90A7255991F8B76C78A1C6A47F0BD3A5 /* GULSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C61DBDC1219C0AC0AD0FF84A0133692 /* GULSwizzler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9173483B65737D0C0DD2D1F3427AFE45 /* FIROptionsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 15F02952828900F1CC5E66B83274F6E0 /* FIROptionsInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; 92851B2DB4A4C54E24A1720ADEC60E84 /* FIRDependency.h in Headers */ = {isa = PBXBuildFile; fileRef = B3DC2C81260A550A30B6C664556BAD8A /* FIRDependency.h */; settings = {ATTRIBUTES = (Private, ); }; }; 92B70011891306D90A55DC048EEE2DCC /* fi.lproj in Resources */ = {isa = PBXBuildFile; fileRef = AF5AFDC2F2B6C3E31AFC75FE3430CA49 /* fi.lproj */; }; 940AEABD2B727E9478DD58CB3EF383F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; 95D0E52FDA5F7738F1D6F56696279BF8 /* FIRVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 38A7BC4B65A58A155D087DA3EFFC1D79 /* FIRVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96479C9C76FDC513C03ADD58168F6911 /* SDImageIOAnimatedCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = A9A5B5BEF1CFAFF4AEB69AFD4C20968A /* SDImageIOAnimatedCoder.m */; }; 96E8F1107963D738A8DD84FF5D7C4563 /* FBLPromise+Recover.h in Headers */ = {isa = PBXBuildFile; fileRef = 6806286AE468436D210DE24F56FFD7A2 /* FBLPromise+Recover.h */; }; 9749F5178C242B199D8F80DBC55F1041 /* GDTCORTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = FFC85887576AED4EB7D6127CC1FDC955 /* GDTCORTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; 975390A7158A9A542B397F21D63DDE3F /* FIRInstallationsIDController.h in Headers */ = {isa = PBXBuildFile; fileRef = EE06EB2E171945BC2050FB3D4A038D02 /* FIRInstallationsIDController.h */; settings = {ATTRIBUTES = (Project, ); }; }; 97E10B0D702DED48D5FD1062AA27E9AB /* hu.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 90548723A968948DA9E270815D5DFEA2 /* hu.lproj */; }; 9917CEAD8CE1CEB7FEC031255AED8FA8 /* SDWebImageError.h in Headers */ = {isa = PBXBuildFile; fileRef = C41FA4729FC85108299943DF13BE21D7 /* SDWebImageError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 994327308CC7ECE7850C804FA0EA5F21 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D2E7ABE07934F659148C41C852087FD /* NSImage+Compatibility.m */; }; 99709E1C4991C49EE5601FF9461B2969 /* SDImageHEICCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 4600B5D11E621BE294D5D7A8106BF312 /* SDImageHEICCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9ADF36BDEE9C92870ACB3F6E17CB0136 /* FIRInstallationsErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 46849267116D15C2F916F95AF7ED6264 /* FIRInstallationsErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9B9D9E2182957C42538AD42CF824823B /* FIRBundleUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = DD42FC19A2CC2BEA3D2DBD52D86D8F36 /* FIRBundleUtil.m */; }; 9C096C865369C21D252A0F4F90BE21D9 /* SDWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 415A874BEFEAB372A795B3F77C86139D /* SDWeakProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9CEB5B454088D6BCC3A11C759A76161A /* SDAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D75DB99AD94B959D922B1BF144AEFE15 /* SDAnimatedImage.m */; }; 9D7EBB3A7377C116526B8A511F8CE245 /* SDDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 33802E944806D1B99AD085FE7F697A74 /* SDDisplayLink.m */; }; 9E6AB9E70A18A858DCBC3B7D57575F47 /* cct.nanopb.h in Headers */ = {isa = PBXBuildFile; fileRef = AB33B7FB74C324A4584D1D93AF8B1DB5 /* cct.nanopb.h */; settings = {ATTRIBUTES = (Project, ); }; }; 9EBE4FF936493E44E9CF7A19860170A8 /* GULHeartbeatDateStorable.h in Headers */ = {isa = PBXBuildFile; fileRef = 447875125DF9F0D89F9C6EF9340E1570 /* GULHeartbeatDateStorable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F4966331D34AA8CE85C4E7161B8D732 /* FBLPromise+Timeout.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A8A40185ABE648F7A15E613D0C0559F /* FBLPromise+Timeout.m */; }; A083AAC3481062E5DE223D480757A83B /* UIButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D53EBA2326B86A037D7D89C9C9849AB1 /* UIButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; A0857AD2616E974FC2D53A08EC963D28 /* GDTCORTransformer_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ADF13875C4D61EE62B5265D074DB171 /* GDTCORTransformer_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; A179E49E5148E014ACB35B3234A79829 /* pt.lproj in Resources */ = {isa = PBXBuildFile; fileRef = FC3C259DB11E51E08F2D5630A5145839 /* pt.lproj */; }; A20FADF8B2F5E792711B7E906B93E692 /* GULKeychainStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B8AE32764C24267738A026CE787CE667 /* GULKeychainStorage.m */; }; A24B4B60656BA387B9979BD877A23983 /* SDDisplayLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CE88DC6249A53E194E886FD268B4F22 /* SDDisplayLink.h */; settings = {ATTRIBUTES = (Private, ); }; }; A2CE92DE43AB450DE1AAA129BFBADCBE /* SDGraphicsImageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B207171596C8E59567CB49A2BC0B7C9 /* SDGraphicsImageRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; A331C78D95C334AC3C328874419FB071 /* zh-Hant.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 8C5EE75C53160A323F9D43000C9258C2 /* zh-Hant.lproj */; }; A3BFF0CDB72BC173BEA1C8ADDBDB0D1D /* UIView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E45C724EB1ABA21537759F2AA0CDBF /* UIView+WebCache.m */; }; A3F8A995C1C713901F9B4503D4103A47 /* GDTCORStorageEventSelector.h in Headers */ = {isa = PBXBuildFile; fileRef = FFBEACDBC00E47FDCE130333C01BDD52 /* GDTCORStorageEventSelector.h */; settings = {ATTRIBUTES = (Project, ); }; }; A4AA6C3C19244F1FF335C090930C609A /* FIRDependency.h in Headers */ = {isa = PBXBuildFile; fileRef = C887379F2FAEEA195D743E2A7E8695B2 /* FIRDependency.h */; settings = {ATTRIBUTES = (Project, ); }; }; A5D6F583F2BCDF15588F8BA972A682CE /* FIRInstallations.h in Headers */ = {isa = PBXBuildFile; fileRef = F463B32358D0340C851CB2FCB8E3B0A5 /* FIRInstallations.h */; settings = {ATTRIBUTES = (Public, ); }; }; A65D64846FFDD7178C8708AF52B5CECF /* FBLPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = B21C4B6B899118FE97F08797C6CC6D78 /* FBLPromise.m */; }; A70259457F23AD83937DF1CA0DAF46D0 /* GULMutableDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 73009AD0D7FBF3D5781BD8B87D5E1D98 /* GULMutableDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; A9A87DA629538D239C4B7401EB56AEB5 /* SDImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 841780DAC592F44AB2E8D35D36E32285 /* SDImageTransformer.m */; }; AA3AE0DF5E35D3E0D2CAE7972D69373E /* SDAnimatedImageView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F03A138FFFD47F81003F3160EC8E9953 /* SDAnimatedImageView+WebCache.m */; }; AB471FF0764CF95AAC04F43A451C54D5 /* FBLPromise+Timeout.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 2A93FB313CA659376FAA369092F2D036 /* FBLPromise+Timeout.h */; }; ABEAA41C35A6CD25C93E622FB129C379 /* SDImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = B3522FD698E8EFD6BDE90D04D1ABC13D /* SDImageLoader.m */; }; AC11317554C3B930BCC98EB6937EEC91 /* FBLPromise+Reduce.m in Sources */ = {isa = PBXBuildFile; fileRef = FF60EBD5E154FA9A791F0C421B4D9AEE /* FBLPromise+Reduce.m */; }; AC551E9186602377EC1751E316CDCEC2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F030A051DB2F7C17CB74A9C0FBD7B56 /* Security.framework */; }; AC76D5E67DCEFB22BE84148317D7B5A5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; ACC1D2855B9D8168F64EAAB7869DE334 /* ca.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4E579AE58A2F6343FA943DA78FDA6A24 /* ca.lproj */; }; ACFBB1893DBF761BB053683178D43A45 /* FIRInstallations.m in Sources */ = {isa = PBXBuildFile; fileRef = 07E9568C4EE8BF77740269310C486888 /* FIRInstallations.m */; }; AD0CFDA140E346502935BA58225E6EF3 /* SDWebImageDownloaderRequestModifier.h in Headers */ = {isa = PBXBuildFile; fileRef = D9304F4CFD273BA0BC3D8FD8EA8CE973 /* SDWebImageDownloaderRequestModifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; AE1F2017E0DEFDFDFA2143A647D2BC15 /* Pods-Spotify-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = A27A53F9FCBFE297E53C77376ACB64D1 /* Pods-Spotify-dummy.m */; }; AE89C60F9CD4D6E1400DD304CE2A2B9E /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 14DB6413E93A38460FE253FC259C28D5 /* UIColor+SDHexString.m */; }; AF8528758C11B8B5E020066DE3EF45B8 /* GULHeartbeatDateStorageUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F86D0619F7550A5DBDF634B8413E25F /* GULHeartbeatDateStorageUserDefaults.m */; }; B04279CC3476C6D75EDE63B360B89DEC /* SDWebImageTransitionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 9487AD43770C35ED6D6CF71B197AE775 /* SDWebImageTransitionInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; B128DE666CCA7CF6B0F09411D8209A92 /* SDWebImageCacheKeyFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 296AEDDAB76C6DA79B00DFB17FE94B13 /* SDWebImageCacheKeyFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; B1F3BDEEBCD13FE138D33F45470AFA71 /* FIRComponentContainerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C2EDD3FE368044C57FFE267748AD31CE /* FIRComponentContainerInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; B233AA226EBC0EB5843452893330638B /* GULNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = EDF0E36BC33BC1C752AA5C64455974C1 /* GULNSData+zlib.m */; }; B309A5100D011205E23108FA864CEE9F /* th.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 8A0DEDA4144F466B62361967B86ACC25 /* th.lproj */; }; B3580A5C39E7E3B32DE3AC015B6DA74A /* GULAppEnvironmentUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D2DC9C0FCEB534D5193B9600BF0552FC /* GULAppEnvironmentUtil.m */; }; B48A893A8058A155BCFA6211E8BBAEE4 /* id.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 4AC80933AD4E17DC2DDD4A5156E1FADB /* id.lproj */; }; B48E2B8546BDCA558F808B1CB4849CEF /* FIRInstallationsIDController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DDBED0511F929D50127372614C07E38 /* FIRInstallationsIDController.m */; }; B502D35637FD94ED622BFEE8D65D2E14 /* FBLPromise+Delay.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = B8633B4212F9E21E512835226D2BEC2E /* FBLPromise+Delay.h */; }; B519045D5C102170273EDD0B9D141F94 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; B595EBF813D80EAD2870F0D888B6F1DE /* FIROptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BF0F7EE147A98C2F11F7C4C32C5B6E3 /* FIROptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5AFC5251D252C4CF6AC6A609E167BFF /* GULNetworkMessageCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 54836C20C54693641AA8386E05C05C5C /* GULNetworkMessageCode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5C56C4D45E7E7A9630F9417A6E39C1D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; B82109186D3614122E6130F9710A241F /* GDTCOREvent_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CEB2E6515F73C730A4DE300BCA32CE76 /* GDTCOREvent_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; B88C351562CE8FA4255EA9023213BE4E /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FC4393F5EB9D10CAC25D438B9ECB2A3 /* SDGraphicsImageRenderer.m */; }; B98DAA42533894F17AD50F5DA1EFD347 /* GDTCOREvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 74B387BF4963DADE929360BC4D95BF9F /* GDTCOREvent.m */; }; B99344EDD10321BF98BD222F9CA56993 /* FBLPromise+Retry.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = C869A091C5104F60176DCBAC53E9FCD6 /* FBLPromise+Retry.h */; }; BAF01AD02AE3A4066A56C40095FE11F6 /* FBLPromise+Async.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 7FDFC5FEAC4A550A50CD516405F6DFF2 /* FBLPromise+Async.h */; }; BBB3C52BD5CE5A8F90FBD5650B283EA1 /* GULReachabilityMessageCode.h in Headers */ = {isa = PBXBuildFile; fileRef = FEE20DA431EFAB6E9DE3F14B005CCF75 /* GULReachabilityMessageCode.h */; settings = {ATTRIBUTES = (Project, ); }; }; BCB7B61DB37BA3EDB8CBBF258DC66446 /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C98C55DA7C8E2B36CE7527095DED78 /* UIImage+ExtendedCacheData.m */; }; BCD010E21F3444C11F5F87821E7F832D /* FirebaseCoreInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 45405CBF1024B18B4589ED32B6B9D343 /* FirebaseCoreInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; BD074722CD72470954ADB5B1198E01C5 /* NSData+ImageContentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 31527FC5A967FDE9F076D544F4923295 /* NSData+ImageContentType.h */; settings = {ATTRIBUTES = (Public, ); }; }; BD5380B89F6EA6442F55F457CB377251 /* FBLPromise+Then.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D85590DB78FA3CE98F5876D319CDC9F /* FBLPromise+Then.h */; }; BDE45185614935653E3E24E4F8AE8812 /* GDTCCTCompressionHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B56D652DA90C728A3D4AA16170D7FA2 /* GDTCCTCompressionHelper.h */; settings = {ATTRIBUTES = (Project, ); }; }; BE141B44BBCE0650A03846E0D38F86EC /* FIRHeartbeatInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 8454F0C4E786C6A2499ED8DA5879A808 /* FIRHeartbeatInfo.m */; }; BE649A29E5FB32C54FDAE55994804F6D /* SDWebImageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F848EBF5A77518F1437E59128B898ACE /* SDWebImageOperation.m */; }; BE6B23777B825686F33ED40AABA4F17C /* GDTCORUploadCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = F6FF94050EDDF80FD2501F4E30F18E4D /* GDTCORUploadCoordinator.h */; settings = {ATTRIBUTES = (Project, ); }; }; BF9A6E2CB8F67686C4B7E04538664919 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B6AEBABD0DD760910C2037F73CBBA7 /* UIImage+Transform.m */; }; BFC98970FF0DF6305A33A564BD19BF7C /* GDTCORClock.h in Headers */ = {isa = PBXBuildFile; fileRef = BCEE4E5F9E2A1F4352E21C55CC500B15 /* GDTCORClock.h */; settings = {ATTRIBUTES = (Public, ); }; }; C02890A41D0AB13F90A47695B7F2867F /* da.lproj in Resources */ = {isa = PBXBuildFile; fileRef = F4A7F01DAC8F3E158C6CB70DD5E60C24 /* da.lproj */; }; C0EB460750BF5FBED872DD25D3B316CB /* GULLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 32B1014C844F5619D536DFD1800B11D5 /* GULLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; C14230A8434F2DC9A5100B4DF5EDA4B4 /* FIRAppAssociationRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F0A5A8418FA40166272625AFB5330A /* FIRAppAssociationRegistration.m */; }; C21AEB2BC0F95C132FDE2206B40199F9 /* FIRAppInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 48972591E3AF0F13726EC5D4C6BEDFDC /* FIRAppInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; C22DB2BCE626C82201E5409BD16B0C97 /* FBLPromiseError.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = E76B8095179CC1855E5CFA2FF2597A24 /* FBLPromiseError.h */; }; C27BD8C6B4E713A8E7B7B76B80E45823 /* SDWebImageDownloaderRequestModifier.m in Sources */ = {isa = PBXBuildFile; fileRef = D92B4CC4E2762BD017DDAA71533D1487 /* SDWebImageDownloaderRequestModifier.m */; }; C2D82385F4FDB67F6613CBBE8E2BD850 /* SDMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B722DB0B3C51C3645037E3399784B69 /* SDMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; C2FF212194FBD30D199C4958030720D1 /* FBLPromise+Any.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 70FBB40CF13B29C7EC31294E2767F546 /* FBLPromise+Any.h */; }; C326A2B1DC92AC4C4C105C1D73C78E13 /* GDTCOREndpoints_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = EF8FCF0158C3FC321B0CCC8F87E286F6 /* GDTCOREndpoints_Private.h */; settings = {ATTRIBUTES = (Project, ); }; }; C40413C3772DC573FF3086BAB7D1EE28 /* GULSceneDelegateSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = D327D14764B30DDEEB17666FA535871D /* GULSceneDelegateSwizzler.h */; settings = {ATTRIBUTES = (Public, ); }; }; C4762BD85B320B9EBE199F7CDFBCED12 /* UIImageView+HighlightedWebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A259FD968B2F3F103051FCCB13D1EF8F /* UIImageView+HighlightedWebCache.m */; }; C4A78B8FC2DC76FBCF43D9D53BF90767 /* SDImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F0A654F37AEA764934660E26E3BD2475 /* SDImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; C4E92D0B4B466595D3F4649BD0827A15 /* GDTCOREndpoints.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FFD95B9DC7DB4011FC5445F7059CE /* GDTCOREndpoints.m */; }; C50420FA308E51F5885CE0A2BA817622 /* GULNetworkConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C8ECB26C6DABE3DBF37DBC44D5A8C5C /* GULNetworkConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; C55A2D001850D3F407D968CFF1C545E2 /* UIButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F3DF4F3E573CB800D27E86168DD5EECB /* UIButton+WebCache.m */; }; C6B19B09709899C1D9F1062017C9E497 /* FBLPromise+All.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 4D82EC7D361BCA9009FD349978107D1C /* FBLPromise+All.h */; }; C6C08B108C2F5D3E6828AFC268E87341 /* FIRAnalyticsConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B055BE0C98324B8E1EE0B588C14B23C /* FIRAnalyticsConfiguration.m */; }; C6CF8D2EE14A91A98FC8E5BA749F6D3C /* ru.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 3446029F86E0312850EAC5C98A32C65C /* ru.lproj */; }; C7E8A80BE6BE5452380D8CA71F278B8A /* SDImageGIFCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = FB7600C02A26CBAC1E67CFF0278A198D /* SDImageGIFCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8161051C207F9AECD0562B6B9E0E579 /* FIRLibrary.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1CFCA4C81C90732F8DB42EA1F017A3 /* FIRLibrary.h */; settings = {ATTRIBUTES = (Project, ); }; }; CA04A500A668FCCEB250C8A61F0643DA /* FIRCoreDiagnosticsConnector.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DFB6F1D058B390F2A1340D6D90391C0 /* FIRCoreDiagnosticsConnector.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA050FC8C58FA008C11AB981F0AC4363 /* FBLPromise+Always.h in Headers */ = {isa = PBXBuildFile; fileRef = FB9896C32514099F18D7AA422602FB04 /* FBLPromise+Always.h */; }; CA180BD40DFE51946C1F45D45D5E3784 /* GDTCOREventTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = EC2AA38D1492EE015AE393D7FCDBCDF1 /* GDTCOREventTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; CEBCB78AD6ADE2D0F18F7D9E07D5C4DB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; CF4DAD38BC9565548EC08D14D2B6541C /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28A55FC857FA4F7D9FE98F3B5AA514E8 /* CoreTelephony.framework */; }; CF8968D878454B5BAE07037D28947D93 /* GDTCOREvent+GDTCCTSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EF33965A3795DEE7A531EC8C56300B4 /* GDTCOREvent+GDTCCTSupport.h */; settings = {ATTRIBUTES = (Project, ); }; }; D00ADB6F62DCD79FF4AE5477907E4B08 /* FIRDiagnosticsData.h in Headers */ = {isa = PBXBuildFile; fileRef = EE1ABBBCF34996AB6FAD3A7099D80CF5 /* FIRDiagnosticsData.h */; settings = {ATTRIBUTES = (Project, ); }; }; D0C26B55798A5A3824920D1D426AEE51 /* FIRInstallationsAPIService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A35F8BE947883A35C48CDFFF5BACB72 /* FIRInstallationsAPIService.m */; }; D1D3F45D15F5C5775073C7747ED94B55 /* GULSceneDelegateSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = E19DD2593A51A0FEDEA7A4688C0D8645 /* GULSceneDelegateSwizzler.m */; }; D288AE30D1159545D1E3CDB721EA5841 /* GoogleDataTransport-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 881ACDC9E0325D12479F43AD079D1CBB /* GoogleDataTransport-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; D3212FE5382F2D56A8C80829D8780188 /* FirebaseCore-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FB1BA9182BB6D6E47E4DD049005A29 /* FirebaseCore-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; D4D727FEE4399BCF942F3512A383917B /* FIRInstallationsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = A328CD57DBA12543441049EBF409F205 /* FIRInstallationsStore.m */; }; D556C63635E7A83C7A9B04FB104F7339 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = CA1E5C47B1D161A6D7326A63F73039E1 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; D5573400F45ACFA5316D7AC2BA779056 /* FIRComponentType.h in Headers */ = {isa = PBXBuildFile; fileRef = B3310E7F0BD29950EA79269F8864E7B0 /* FIRComponentType.h */; settings = {ATTRIBUTES = (Private, ); }; }; D63055B89D739A7EA319FBE861ADEED5 /* FBLPromise+Reduce.h in Headers */ = {isa = PBXBuildFile; fileRef = F1A5B63B766812910C3FC66A4D5BD359 /* FBLPromise+Reduce.h */; }; D6742E4A0E030524C50C7BFD7FEB7292 /* FBLPromise+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F035755BD3D0F891EF8F32718F2E74 /* FBLPromise+Testing.m */; }; D68F08F717E95BABFB43AE705D943426 /* SDWebImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 18281399F797A6B19033D53D2B0992E3 /* SDWebImageDownloader.m */; }; D7068278536821417BB567EEED782F73 /* FIRComponentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 5514ED015845BE4DF10DF85BF4983A59 /* FIRComponentType.h */; settings = {ATTRIBUTES = (Project, ); }; }; D7BA04ADFE3104907F100E9370184A65 /* UIImage+ExtendedCacheData.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C388E0E2142C8BAB1FBE711F1A33688 /* UIImage+ExtendedCacheData.h */; settings = {ATTRIBUTES = (Public, ); }; }; D7D077F0FA5CAC32ACB65372453C701F /* SDImageAWebPCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = F59DA2817374C9606A05E0D57A6993AB /* SDImageAWebPCoder.m */; }; D88076DA0593D2835645CA5A92FFA37C /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = B4A0C1F3F59FF0ECC737E3A22B64F7F0 /* SDAnimatedImageView.m */; }; D88E2E3BC8A27A085ABC3AF8AF4C7F86 /* GULSecureCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 476A318EB831BFCD3B77190E73149735 /* GULSecureCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; D975B260AB44081399BCB612916CE675 /* UIView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = C8CC6A0CB955A36A57FB8DC86DC7122B /* UIView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; D9B8E5DA0091D211AACED8612BA1153B /* GULNetworkLoggerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DC79300A6D291A17A3804A3511C41D9B /* GULNetworkLoggerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D9CFDF531DF1146A1A3E694A360CB0A7 /* FIRInstallationsHTTPError.m in Sources */ = {isa = PBXBuildFile; fileRef = B5E3DBB2F100E0A299BCFC77EBC69A7E /* FIRInstallationsHTTPError.m */; }; DA16EA1A389CE66496E2FCBCCDA0D0ED /* GDTCORPlatform.m in Sources */ = {isa = PBXBuildFile; fileRef = 809CE53E4184869CD1D4910C0A359884 /* GDTCORPlatform.m */; }; DB048A769D5DB1BE84466C9EA4EBBF1B /* GULLoggerCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 08586FD84613D5B8D0037AF3714A8D9F /* GULLoggerCodes.h */; settings = {ATTRIBUTES = (Project, ); }; }; DB61822BEA875B413C858E1166590C9F /* SDWebImageDownloaderOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1925BCD73D2687C697D5448145F92862 /* SDWebImageDownloaderOperation.m */; }; DB68D9AFA817850ED9EBBE7F3F378AF5 /* SDImageCodersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C87340AB40E7872EBB32D3E37F151B98 /* SDImageCodersManager.m */; }; DBBAC864AD405BED53782364E0F0D5F3 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A023925AC3740397148C89A37EA72B8 /* UIImage+GIF.h */; settings = {ATTRIBUTES = (Public, ); }; }; DBCEB21658D3825FA81F27822AADAD11 /* en.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 8C7297F9F351E0DFB107547CC454F81B /* en.lproj */; }; DBDF09CBE34BC99BD85677273C4B6296 /* GULNetworkInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 18D28D4E30FB28D835221C737B53B7E0 /* GULNetworkInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; DC8E06FD7CA80D1461C4E73105FD45AC /* FBLPromise+Catch.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BC2750E276CE3D6725F398532CEB28B /* FBLPromise+Catch.h */; }; DD5D476FF518ACB3772717620E7D393A /* pl.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 0A31AE9C96B6817F39D60A3B4CB0E787 /* pl.lproj */; }; DD9CF5BA0F1AF0448E29B8E7FE427C20 /* GULNetworkConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = BA44BA3AD4121BF4B3BA03128355DA0A /* GULNetworkConstants.m */; }; DE0BA471FB4FC10C0B25BDE4F22F5B76 /* SDWebImageDownloaderDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 68332238690458AE7A254B6097B8BD22 /* SDWebImageDownloaderDecryptor.m */; }; DE2AB52CD8EFC8E819452F737447F4B0 /* FirebaseCoreDiagnostics-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D955AAA9A402178AFCBD9F07B289BECB /* FirebaseCoreDiagnostics-dummy.m */; }; DE3AC691EE0E078459ECBE58059F1260 /* NSURLSession+GULPromises.h in Headers */ = {isa = PBXBuildFile; fileRef = 2494BB0409778ADBC0785D40DE703573 /* NSURLSession+GULPromises.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECA57AF64C5C257919649ECBB75F0B9 /* SDWebImageCacheSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = 57A489F83B821E73C9F898D59FD839D5 /* SDWebImageCacheSerializer.h */; settings = {ATTRIBUTES = (Public, ); }; }; DEE74CD0E78DE0DB4056A8A3244B6ED1 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = 2186649006C5A621B8BA7DDCC5C27AB4 /* UIImage+GIF.m */; }; E20DFCFF1B1EA67C678445D94AFC6782 /* GDTCORPlatform.h in Headers */ = {isa = PBXBuildFile; fileRef = DC55F3B02A125C1D8CBC59176095F043 /* GDTCORPlatform.h */; settings = {ATTRIBUTES = (Project, ); }; }; E2657EA2FA2825ED6DBE1C1BFEA5907C /* GDTCCTUploadOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 08F469B9832F15DBDE81EB155A43226B /* GDTCCTUploadOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; E33A7CDF3107CD3426ADD0D906100CD4 /* SDImageHEICCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 483340B3D5102879F6C45F55BD02FF1E /* SDImageHEICCoder.m */; }; E4153F4FB8707C1B1B62815836B8F358 /* GDTCCTNanopbHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = FE8190044D8F103421DED7166A530383 /* GDTCCTNanopbHelpers.h */; settings = {ATTRIBUTES = (Project, ); }; }; E41BE1E98D3A9AADCA187D34E0A0F1F0 /* FBLPromise+Recover.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 6806286AE468436D210DE24F56FFD7A2 /* FBLPromise+Recover.h */; }; E4AF87F8012F919F00E2E8698A1A3C33 /* SDWebImageCompat.m in Sources */ = {isa = PBXBuildFile; fileRef = D6368995E44A12ED775E22745EE36796 /* SDWebImageCompat.m */; }; E4CA0B80D962B7042730CCA24DF7A6CE /* Appirater.h in Headers */ = {isa = PBXBuildFile; fileRef = 350451991B16222891CB7702FAA4C26A /* Appirater.h */; settings = {ATTRIBUTES = (Public, ); }; }; E50990C8CCC727688A0EC4D123CDD295 /* nanopb-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 29AE365492E6E96E5708A9AD6E31746F /* nanopb-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; E515780850EBAD40342EBF2997BFC2D2 /* SDWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 62E087DDBCC7CE3CA61BACD11EE3931E /* SDWebImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; E556832E0C191FCFABB9E97C87D243EF /* SDmetamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 579C123C87D8545B91CD64649918A148 /* SDmetamacros.h */; settings = {ATTRIBUTES = (Private, ); }; }; E5C6051933677CDF392BF6356271A92E /* SDImageIOCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 1416C43E8091C630E21516532F691796 /* SDImageIOCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5C7FA5788C38AD0308CC3B712DD53A2 /* fr.lproj in Resources */ = {isa = PBXBuildFile; fileRef = E80E4257A841B7AA5C92B2CB19B16F0B /* fr.lproj */; }; E62B328C763183320A9105E6E8EB1860 /* GULHeartbeatDateStorageUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = BB06018D1785388A5C83E9EF4D57ED16 /* GULHeartbeatDateStorageUserDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; }; E78F445F25943A96E2AE21A4AD7EEA67 /* pb_decode.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0F742A22FC71CED5A18A108E514A8B /* pb_decode.h */; settings = {ATTRIBUTES = (Public, ); }; }; E7AB534DD8C859013ADE863B6D2CBA7D /* FIROptionsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 0323C2AB5A72DF893829A51D8F181F49 /* FIROptionsInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; E7FA1E1CD066D0335A95376C73E3F8ED /* SDImageCacheConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = B83BA9762F2888371B5ADE95B066DCB4 /* SDImageCacheConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; E8CE28499C17965A931605D4F47AAEBF /* NSImage+Compatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 2264D3828F48F1D2D87C73BE181475A2 /* NSImage+Compatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; E8D519B63C2077979F799769F9A35072 /* SDImageCachesManagerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 98368FFE6F3A2C1CB4B79C360D245377 /* SDImageCachesManagerOperation.h */; settings = {ATTRIBUTES = (Private, ); }; }; E8D668C3BC3581332C51EAE1359D2022 /* GULKeychainStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = A40C76162676EC9A3E0C33C1F29CB93F /* GULKeychainStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; E92BFF50D3F84D5D8BA6E845FAEF8DA3 /* FBLPromisePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 278F2C97E692BBEB7227CFD22EE4333A /* FBLPromisePrivate.h */; }; E9358141369A46CD15097FD5B56279AC /* hy.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 9FB349B1B1D5B036FA774AC001CEBCB4 /* hy.lproj */; }; E96C68381CF2BD5D022DE8DC47200C53 /* FBLPromise+Async.m in Sources */ = {isa = PBXBuildFile; fileRef = 068EFE7F14EBD412EB944B5B38CE3CB3 /* FBLPromise+Async.m */; }; E9AD74230A2E90C1FE84024CA1917ACC /* GULNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = A8B52265053B7A072C3CFD52774A4E64 /* GULNetwork.h */; settings = {ATTRIBUTES = (Public, ); }; }; E9BF3DACAEE3509B06DBDA16478ED73F /* GoogleDataTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = 28859EE1F335E36BD76813D7AC4608D9 /* GoogleDataTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; E9CF46857D61D2A8AF4C3722C27C61C6 /* SDImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E3D624D33EC772ADA4910D9A6A067AD /* SDImageCoderHelper.m */; }; EAABECDA1C6109F989C004C1DCEBE07E /* GDTCORFlatFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 736D3261A8A25DCBBECD54A6C5C48166 /* GDTCORFlatFileStorage.m */; }; EB3A214553ED6CFAF04498A2DBAFE0EF /* GDTCOREventDataObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C31DC7C6755337262C1BDDB6677C635 /* GDTCOREventDataObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; ECB05ADC1FE7A52E0D9ACCB9B027EF2B /* pb_encode.h in Headers */ = {isa = PBXBuildFile; fileRef = FDF404E683FC3DE61DCE91FF8B1E0842 /* pb_encode.h */; settings = {ATTRIBUTES = (Public, ); }; }; ED39F6A91DF2FC44DD73DEE50BF7D393 /* FIRInstallationsAuthTokenResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AA30DDE545C3AB8E5A8BCD08CA5B923 /* FIRInstallationsAuthTokenResultInternal.h */; settings = {ATTRIBUTES = (Project, ); }; }; ED9E9EC31A5AC510BBF8568956DCA102 /* GDTCCTCompressionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 219546DB6A6974432E1E0283F24A3329 /* GDTCCTCompressionHelper.m */; }; EDDBA077EC19453A85E90DBC87AB8C2B /* FBLPromise.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = F35AA80907A78AC92F34C61F368248E7 /* FBLPromise.h */; }; EE207039B394C5D8BBA475814B4E9830 /* GULNetworkURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 142559AF1A21A92BBB53100F317735FE /* GULNetworkURLSession.m */; }; EF17DB58C7BE9A3304D466EF87041014 /* FIRCoreDiagnostics.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D88DFE4BAAB878102C9DB69C90E1EE /* FIRCoreDiagnostics.m */; }; EF75B2C28FA102334A044548123860E2 /* FIRCoreDiagnosticsData.h in Headers */ = {isa = PBXBuildFile; fileRef = AAC1202619BDEC6CB408DEFB276233E1 /* FIRCoreDiagnosticsData.h */; settings = {ATTRIBUTES = (Project, ); }; }; EF78F3AE3374BED3DC0FBF7B944B9057 /* GDTCCTUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = DEEA9E90B955F3FF75BD5CC9F8F49886 /* GDTCCTUploader.m */; }; EFAB05A7F5A8FC35E27F219708D544AD /* FIRInstallationsStoredAuthToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 4187F6DA61736F8AD4201616D23D8061 /* FIRInstallationsStoredAuthToken.h */; settings = {ATTRIBUTES = (Project, ); }; }; F02178012C14330925DC2AB2DF60482B /* GDTCORTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 193B43A46E5628F605735E9C2EB9C5B1 /* GDTCORTransport.m */; }; F077E74D9855D0D7FCF570F8E8ED4A1E /* SDFileAttributeHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FEE29B50BED2D3B4F195FBAF219817D9 /* SDFileAttributeHelper.h */; settings = {ATTRIBUTES = (Private, ); }; }; F0E28CEF30861F3589A4A662CEA76D80 /* SDImageGraphics.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CD38DB179EE57D03D1E4AE0BBA047 /* SDImageGraphics.m */; }; F10FCC767B08C019DEE8FFB1516299EA /* GULAppDelegateSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C35CA5CB6C0053B485FCFDFD30A1662 /* GULAppDelegateSwizzler.h */; settings = {ATTRIBUTES = (Public, ); }; }; F1372E076B4B28D2923EA22ED4D6DFCA /* FirebaseInstallations.h in Headers */ = {isa = PBXBuildFile; fileRef = 183B8EEF2BA8C735371935889864D6B7 /* FirebaseInstallations.h */; settings = {ATTRIBUTES = (Public, ); }; }; F161112B04B9AC7D276D9F588BA6E313 /* SDDeviceHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FC23427538EE1DBB11F5EBBAAB6A513D /* SDDeviceHelper.h */; settings = {ATTRIBUTES = (Private, ); }; }; F17E3C439BBF49E6486B54C84DD0C190 /* FIRCoreDiagnostics.h in Headers */ = {isa = PBXBuildFile; fileRef = A2CD1B738ABAB7BA344D7969860B494C /* FIRCoreDiagnostics.h */; settings = {ATTRIBUTES = (Public, ); }; }; F1CFF76FC50BF026284051C6CAB9B389 /* de.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 8CDC01574AEDC72431059BB3FDE16F59 /* de.lproj */; }; F1F24427266F56690C90DBC258A8E712 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */; }; F269152E11B82F412D3BD37F3E13E6C1 /* pb_decode.c in Sources */ = {isa = PBXBuildFile; fileRef = D8BE8FDFF77007F5278B6A0E5BC01E6A /* pb_decode.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc -fno-objc-arc"; }; }; F270EDD68B781B1BE74FE134DADC2FE1 /* FBLPromise+Await.m in Sources */ = {isa = PBXBuildFile; fileRef = EC847DD51A92AE89F533B4312E344982 /* FBLPromise+Await.m */; }; F2B107BBDDF18E390CE5F31FEAC37B5B /* FIRInstallationsErrorUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6237C8DA5406A972EAFEEC2CB46AA910 /* FIRInstallationsErrorUtil.m */; }; F2EC479E66AABB97FBFB289846C339EF /* GULURLSessionDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CE0245F2D926AF50D3BFBA488887C1 /* GULURLSessionDataResponse.m */; }; F3824BB13CE13E5071200A50EA544D21 /* nanopb-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FF8CD99087C3A386C87B8B64F6814912 /* nanopb-dummy.m */; }; F3B6F6BEAD0AFF34EAE82B95A7C56037 /* GDTCORLifecycle.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E80D119146AEB6CE705A642B0A4012D /* GDTCORLifecycle.m */; }; F3BF79D7923FCC0DBCCE33CC97D7134B /* cct.nanopb.c in Sources */ = {isa = PBXBuildFile; fileRef = 47C2768639337908388E00A97B570D97 /* cct.nanopb.c */; }; F417C8E4B3D73E496E4764CF2FE9EDDD /* FirebaseCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3BB2E5C745E6BEBDF556017B5389A3F8 /* FirebaseCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; F421606C12494E53B7F6B5EAE13352F1 /* vi.lproj in Resources */ = {isa = PBXBuildFile; fileRef = C6911F1DC733532D91E29D6B0433A787 /* vi.lproj */; }; F4232EBD3CD57E497ECED11C9BECC958 /* GDTCORUploadCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AB43C522E93B03C169E99C63451C091 /* GDTCORUploadCoordinator.m */; }; F4310C2EF76937100D2C74EBB593BFC4 /* GULSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = 75E9E1284F8AE50551FF56600DDDCEBF /* GULSwizzler.m */; }; F463DEB020D5C466D4613947AE4BE16F /* SDAnimatedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3808D78CAA3EAA14B869A1667210193C /* SDAnimatedImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; F4811CFAC05932CE436CC2C32BAD91E6 /* UIColor+SDHexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D628BFBCC6276E5E277ED26143A0529 /* UIColor+SDHexString.h */; settings = {ATTRIBUTES = (Private, ); }; }; F497CD01EF6568475FD363A248D78A4B /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = A62E17FF50F14A446211D0E0BA00FD24 /* UIImage+ForceDecode.m */; }; F4BE3CF05C223248D90AC0F46BAAB9FF /* FIRLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 4953F3D24A404D00A99D8A29BE61104C /* FIRLogger.h */; settings = {ATTRIBUTES = (Private, ); }; }; F56971E69EB803E06C38E2751620E1E8 /* ms.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 867CE4FF42F759E76A71FEF4453E8079 /* ms.lproj */; }; F5734286C309C6E2FF84E06DA25F51EF /* FBLPromise+Race.h in Headers */ = {isa = PBXBuildFile; fileRef = 447B191325C26D8C11C3D317EC3FBAA8 /* FBLPromise+Race.h */; }; F595C8E6D01D6FFEC46D540A198AD062 /* FIROptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA476F642687D5FC7AA27BDDA27DEBC /* FIROptions.m */; }; F646BABB56E9E66692865720E3FB71DA /* FIRConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = A8689F656BCD2DE359141CDE820E425F /* FIRConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; F7289F163C285103D6CCAB39447153CA /* SDWebImageDownloaderConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = F53274C0B6BDFB6F2402643CF71B2F77 /* SDWebImageDownloaderConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; F8FE94E2FE6E57F616878424F22528CA /* GDTCORTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = B06B80A4EE003DEDFC1F89E8907C6C16 /* GDTCORTransformer.h */; settings = {ATTRIBUTES = (Project, ); }; }; FA10A9A289B0F3E86CCCE9E2A7E94EF2 /* FirebaseCoreInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC9689AF5C4E7C5E057221C42B74F092 /* FirebaseCoreInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; FA1435956493F6C517F5A097360C11D2 /* pb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = 06D5568DC434FEF3EF84595EE271B95A /* pb_common.h */; settings = {ATTRIBUTES = (Public, ); }; }; FA24FF48B63577BA0DC691E35C9F7FA9 /* FIRFirebaseUserAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 893980C67675F297617FB935213E4671 /* FIRFirebaseUserAgent.m */; }; FA9814200D820A4CB4EE5032B8E5973A /* FBLPromise+Validate.m in Sources */ = {isa = PBXBuildFile; fileRef = 281F885809D3382CEFAC6F78D5E16CA0 /* FBLPromise+Validate.m */; }; FB0E00B080CD4EE84EF1BF65C47F3185 /* GULKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F80FBEC0B534E33030C9ECCABDD9BE62 /* GULKeychainUtils.m */; }; FB359FC2B8375E01A8A8F84D3C50EE82 /* SDWebImageDownloaderResponseModifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 25BB086F66AE0B62674631094FA38DB9 /* SDWebImageDownloaderResponseModifier.m */; }; FE0189D2FE038AD82C4CCF5A801A50A3 /* SDDiskCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E74C0A2D416186897FBBC33BC5608B /* SDDiskCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; FE05A1BF611E69C94BC7A9EADBE10B2F /* SDWebImageDownloaderConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = ACAACBFBB72CC5DBA48E5939C7C05CBB /* SDWebImageDownloaderConfig.m */; }; FE3E0BB2B4E000770F2FC5CB9FDB9097 /* FBLPromise+Catch.h in Copy . Public Headers */ = {isa = PBXBuildFile; fileRef = 1BC2750E276CE3D6725F398532CEB28B /* FBLPromise+Catch.h */; }; FE42751E23F9C15EB7A8D8D0569D95F1 /* GDTCOREvent+GDTCCTSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 135A77B30A220981214A503DF9FB9D3A /* GDTCOREvent+GDTCCTSupport.m */; }; FE4845B6FDD260059CDF9E26965CA31B /* SDWebImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AA24773B096CF90111EA4F2AA2C464D /* SDWebImageManager.m */; }; FE4C5664159A808BD7984CB35D69CFAB /* GDTCORFlatFileStorage+Promises.m in Sources */ = {isa = PBXBuildFile; fileRef = 79AB37844F04B4F0CF83A66B9FBD9175 /* GDTCORFlatFileStorage+Promises.m */; }; FE7AB9DA939B0E528ED9F339C6DCC201 /* GDTCORFlatFileStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B1EB303FD52ADD9ABA791AD8C33496F /* GDTCORFlatFileStorage.h */; settings = {ATTRIBUTES = (Project, ); }; }; FED292AF8AD5924263FC0A8D0B8AF26D /* SDWebImageCompat.h in Headers */ = {isa = PBXBuildFile; fileRef = 83495546D2D6077CD99D793135E8A7EC /* SDWebImageCompat.h */; settings = {ATTRIBUTES = (Public, ); }; }; FF66F3E7374A9398D5B08AA23273BC0E /* NSData+ImageContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D682D19C50EF105FF8EF05D677CA964 /* NSData+ImageContentType.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 017C8F825B2F93FFCB2F173D75B0623D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; remoteInfo = FirebaseCore; }; 019A23FE239BD69A9982DFBA9D477A03 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 0CCABEB11C23BF29F6C694531602F292 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 620E05868772C10B4920DC7E324F2C87; remoteInfo = FirebaseCoreDiagnostics; }; 107F5D2853F4AAFB42A81181FDB1E1DD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = B53D977A951AFC38B21751B706C1DF83; remoteInfo = GoogleAppMeasurement; }; 13D9CFCD8B7BD73E7C9DE7108252C17E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 179483F0AEF1C518BA00B35B81A30639 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 3847153A6E5EEFB86565BA840768F429; remoteInfo = SDWebImage; }; 1CA617FF6BC13C778A37FCD64EB5171C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; remoteInfo = nanopb; }; 1EA446CB14655B63DDF173D56348CF0C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 299349E1BCD52B21826853337CF6A1C4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = C49E7A4D59E5C8BE8DE9FB1EFB150185; remoteInfo = FirebaseAnalytics; }; 2F4219A4D7ABBC820468F9119F658E52 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 3068F1E435C57EA32BE211ECBD073BC6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; remoteInfo = nanopb; }; 3EABBDC05624720577DC38CFFE35516E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = C49E7A4D59E5C8BE8DE9FB1EFB150185; remoteInfo = FirebaseAnalytics; }; 41A499968FD5AC9EAE0375E50C6432F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = E56E80821D169C98E99A62EBCBC60B2C; remoteInfo = "Appirater-Appirater"; }; 42781FFD2FA1F2EDE97339A0136EDCD2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; remoteInfo = nanopb; }; 4605540DE00C391F1995371BAF739423 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; remoteInfo = PromisesObjC; }; 4AE8370CEECA1D1E24DE01B095051767 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = CE9FA8ACD6205C77B753798CD736FAEF; remoteInfo = Appirater; }; 4AF8ADE52426F73572B9DCF5DAF9E166 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 620E05868772C10B4920DC7E324F2C87; remoteInfo = FirebaseCoreDiagnostics; }; 5A2B9082AADF585B09978FD211A672E2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; remoteInfo = FirebaseCore; }; 5ED9D700A988839F2AED6A8424F05591 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 64BB0EA73EC74FA5D442D982996CEE10 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 6536CA055DA2CE1A6BC4ECCA78BE9E3D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8D7F5D5DD528D21A72DC87ADA5B12E2D; remoteInfo = GoogleUtilities; }; 66522F2C26FB2DA91D7F26867BBFD45D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; remoteInfo = nanopb; }; 75A52B3F3AF7540549FE42CCD7D77E21 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; remoteInfo = PromisesObjC; }; 8C5626444E1A84EEEC6B1F447CDC3835 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 87803597EB3F20FC46472B85392EC4FD; remoteInfo = FirebaseInstallations; }; A977FEFABC64BF1427A465B2BB858E38 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = D2B5E7DCCBBFB32341D857D01211A1A3; remoteInfo = nanopb; }; B16C5D96A221C36E30318D4000EF80F2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; remoteInfo = FirebaseCore; }; BC0ECFA70CF24D5AD9BA339E3D2F9476 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 87803597EB3F20FC46472B85392EC4FD; remoteInfo = FirebaseInstallations; }; D09E65E94D05A8E445515725C350BE0D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 5C0371EE948D0357B8EE0E34ABB44BF0; remoteInfo = GoogleDataTransport; }; D34555543663478CC798C373DD6F3097 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 072CEA044D2EF26F03496D5996BBF59F; remoteInfo = Firebase; }; E01CEC52D49138FF303416CCAC27428F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 5C0371EE948D0357B8EE0E34ABB44BF0; remoteInfo = GoogleDataTransport; }; E36D5F0B788F0DF60B17237B05CF025C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; remoteInfo = PromisesObjC; }; ECA56526923E8FA4E91B31255FA8A6C0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 4402AFF83DBDC4DD07E198685FDC2DF2; remoteInfo = FirebaseCore; }; FDD63A1FAEDABC2F1E1949DCC5AF3165 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = B53D977A951AFC38B21751B706C1DF83; remoteInfo = GoogleAppMeasurement; }; FF55F8919770C873139545DDB9B82E39 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 2BBF7206D7FAC92C82A042A99C4A98F8; remoteInfo = PromisesObjC; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 88441CA33AAB2BA346D73F3E23B24BC1 /* Copy . Public Headers */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(PUBLIC_HEADERS_FOLDER_PATH)/."; dstSubfolderSpec = 16; files = ( EDDBA077EC19453A85E90DBC87AB8C2B /* FBLPromise.h in Copy . Public Headers */, C6B19B09709899C1D9F1062017C9E497 /* FBLPromise+All.h in Copy . Public Headers */, 4634DFDEF1A08A4C90EF8CCDBEF04003 /* FBLPromise+Always.h in Copy . Public Headers */, C2FF212194FBD30D199C4958030720D1 /* FBLPromise+Any.h in Copy . Public Headers */, BAF01AD02AE3A4066A56C40095FE11F6 /* FBLPromise+Async.h in Copy . Public Headers */, 18191A26B494BB7777A6420722A46B29 /* FBLPromise+Await.h in Copy . Public Headers */, FE3E0BB2B4E000770F2FC5CB9FDB9097 /* FBLPromise+Catch.h in Copy . Public Headers */, B502D35637FD94ED622BFEE8D65D2E14 /* FBLPromise+Delay.h in Copy . Public Headers */, 1AF4F328009E37FD64DA9CA51CC90932 /* FBLPromise+Do.h in Copy . Public Headers */, 842007158292E13DF5B3878A89BD4727 /* FBLPromise+Race.h in Copy . Public Headers */, E41BE1E98D3A9AADCA187D34E0A0F1F0 /* FBLPromise+Recover.h in Copy . Public Headers */, 3A899B9C5A8C258ED40829D2BBB6CF78 /* FBLPromise+Reduce.h in Copy . Public Headers */, B99344EDD10321BF98BD222F9CA56993 /* FBLPromise+Retry.h in Copy . Public Headers */, 3E5A84FD8D62B473AF24D16F36112D81 /* FBLPromise+Testing.h in Copy . Public Headers */, 047DCD22E37D894B69D095C8E537819F /* FBLPromise+Then.h in Copy . Public Headers */, AB471FF0764CF95AAC04F43A451C54D5 /* FBLPromise+Timeout.h in Copy . Public Headers */, 7A0703CD6D58504249D27BB436F68B39 /* FBLPromise+Validate.h in Copy . Public Headers */, 04AA4C56A3620BE2A70364D47CE08AF3 /* FBLPromise+Wrap.h in Copy . Public Headers */, C22DB2BCE626C82201E5409BD16B0C97 /* FBLPromiseError.h in Copy . Public Headers */, 620EBEBCDCF8233A6CED99BD83B4C8D1 /* FBLPromises.h in Copy . Public Headers */, ); name = "Copy . Public Headers"; runOnlyForDeploymentPostprocessing = 0; }; C1CF4B15D01CD8E372C0028680453569 /* Copy . Private Headers */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(PRIVATE_HEADERS_FOLDER_PATH)/."; dstSubfolderSpec = 16; files = ( 276A3E64698148850F514C10F440DDD0 /* FBLPromisePrivate.h in Copy . Private Headers */, ); name = "Copy . Private Headers"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 00287C1BECC76217689476B210B41A36 /* FIRComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponent.m; path = FirebaseCore/Sources/FIRComponent.m; sourceTree = ""; }; 00F035755BD3D0F891EF8F32718F2E74 /* FBLPromise+Testing.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Testing.m"; path = "Sources/FBLPromises/FBLPromise+Testing.m"; sourceTree = ""; }; 018833202B971E61CF36DA9775AED769 /* GDTCCTUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTUploader.h; path = GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h; sourceTree = ""; }; 02EBCB4694F493398B30D145521759BB /* Pods-Spotify.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Spotify.debug.xcconfig"; sourceTree = ""; }; 030D92BA7629B33D7DEDDD755B2FB34B /* sv.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = sv.lproj; sourceTree = ""; }; 0323C2AB5A72DF893829A51D8F181F49 /* FIROptionsInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROptionsInternal.h; path = FirebaseCore/Sources/Private/FIROptionsInternal.h; sourceTree = ""; }; 0338A87088B87F5A5D1BB03CA2AB274B /* GDTCORRegistrar_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORRegistrar_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h; sourceTree = ""; }; 036C70BC91046AD6992330F90128AC83 /* FirebaseInstallations.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseInstallations.modulemap; sourceTree = ""; }; 04119D68F2961D5912F24F5066E00D0D /* FirebaseAnalytics.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAnalytics.debug.xcconfig; sourceTree = ""; }; 0440093C278700F6A5362B45AD5DBC91 /* SDAsyncBlockOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAsyncBlockOperation.h; path = SDWebImage/Private/SDAsyncBlockOperation.h; sourceTree = ""; }; 0466C52ED49E6E1C863BAFB6E1AE5B92 /* Appirater.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = Appirater.m; sourceTree = ""; }; 0502AAFCB22CA8A06A21C20517BE89B4 /* GULNetwork.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetwork.m; path = GoogleUtilities/Network/GULNetwork.m; sourceTree = ""; }; 05850FDD1539ACB6AB1C9DB0142B8403 /* GULHeartbeatDateStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULHeartbeatDateStorage.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h; sourceTree = ""; }; 061F4EB74D749A7F360F62B20F46294A /* nanopb-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "nanopb-Info.plist"; sourceTree = ""; }; 068EFE7F14EBD412EB944B5B38CE3CB3 /* FBLPromise+Async.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Async.m"; path = "Sources/FBLPromises/FBLPromise+Async.m"; sourceTree = ""; }; 06D5568DC434FEF3EF84595EE271B95A /* pb_common.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_common.h; sourceTree = ""; }; 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = nanopb; path = nanopb.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0720E29DA86F4D52C08D6942FFEF190A /* FIRLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRLogger.m; path = FirebaseCore/Sources/FIRLogger.m; sourceTree = ""; }; 073BC434BDEB48DDF3B92DF041D277CE /* FirebaseCoreDiagnostics-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseCoreDiagnostics-Info.plist"; sourceTree = ""; }; 079B6435FC4625DBC4A439EDF7FD5844 /* SDWebImageError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageError.m; path = SDWebImage/Core/SDWebImageError.m; sourceTree = ""; }; 07E9568C4EE8BF77740269310C486888 /* FIRInstallations.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallations.m; path = FirebaseInstallations/Source/Library/FIRInstallations.m; sourceTree = ""; }; 08586FD84613D5B8D0037AF3714A8D9F /* GULLoggerCodes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLoggerCodes.h; path = GoogleUtilities/Common/GULLoggerCodes.h; sourceTree = ""; }; 08F469B9832F15DBDE81EB155A43226B /* GDTCCTUploadOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTUploadOperation.h; path = GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h; sourceTree = ""; }; 09F19A6EF0F8A7D0CDEEA09F1E90D4A1 /* SDWebImageTransition.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageTransition.m; path = SDWebImage/Core/SDWebImageTransition.m; sourceTree = ""; }; 0A31AE9C96B6817F39D60A3B4CB0E787 /* pl.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = pl.lproj; sourceTree = ""; }; 0ADF13875C4D61EE62B5265D074DB171 /* GDTCORTransformer_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransformer_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h; sourceTree = ""; }; 0B538C0DED9A96F51790585E9AF03D97 /* GULAppEnvironmentUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppEnvironmentUtil.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h; sourceTree = ""; }; 0B722DB0B3C51C3645037E3399784B69 /* SDMemoryCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDMemoryCache.h; path = SDWebImage/Core/SDMemoryCache.h; sourceTree = ""; }; 0BBCA689ED4AD568D1203A32DDCB2654 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = SDWebImage/Core/SDWebImageOptionsProcessor.m; sourceTree = ""; }; 0DFCC2FB818BA3289E85B84DD2F068A3 /* FBLPromise+Always.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Always.m"; path = "Sources/FBLPromises/FBLPromise+Always.m"; sourceTree = ""; }; 0E3D719558B6C623CB196272CE80D92B /* FIRInstallationsHTTPError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsHTTPError.h; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h; sourceTree = ""; }; 0E80D119146AEB6CE705A642B0A4012D /* GDTCORLifecycle.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORLifecycle.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m; sourceTree = ""; }; 0F824E440C097E5A5FE69CFBC7293F15 /* SDImageTransformer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageTransformer.h; path = SDWebImage/Core/SDImageTransformer.h; sourceTree = ""; }; 0FC4393F5EB9D10CAC25D438B9ECB2A3 /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = SDWebImage/Core/SDGraphicsImageRenderer.m; sourceTree = ""; }; 0FCDB01EEB669605F883EA4E79E56AD2 /* UIImage+ForceDecode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+ForceDecode.h"; path = "SDWebImage/Core/UIImage+ForceDecode.h"; sourceTree = ""; }; 11DC645818CEA0B49FB41D10974587E3 /* GDTCCTUploadOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTUploadOperation.m; path = GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m; sourceTree = ""; }; 135A77B30A220981214A503DF9FB9D3A /* GDTCOREvent+GDTCCTSupport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GDTCOREvent+GDTCCTSupport.m"; path = "GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m"; sourceTree = ""; }; 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseInstallations; path = FirebaseInstallations.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1416C43E8091C630E21516532F691796 /* SDImageIOCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageIOCoder.h; path = SDWebImage/Core/SDImageIOCoder.h; sourceTree = ""; }; 142559AF1A21A92BBB53100F317735FE /* GULNetworkURLSession.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetworkURLSession.m; path = GoogleUtilities/Network/GULNetworkURLSession.m; sourceTree = ""; }; 14DB6413E93A38460FE253FC259C28D5 /* UIColor+SDHexString.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIColor+SDHexString.m"; path = "SDWebImage/Private/UIColor+SDHexString.m"; sourceTree = ""; }; 15C9B0DD52D5C611E1AB4BB7A9A92DF5 /* pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb.h; sourceTree = ""; }; 15F02952828900F1CC5E66B83274F6E0 /* FIROptionsInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROptionsInternal.h; path = FirebaseCore/Sources/Private/FIROptionsInternal.h; sourceTree = ""; }; 161553D9C89B2C716D3056754C86B4A5 /* GoogleAppMeasurement-xcframeworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "GoogleAppMeasurement-xcframeworks.sh"; sourceTree = ""; }; 16A5A619D3393C3652027A63F8D6406E /* GULAppDelegateSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULAppDelegateSwizzler.m; path = GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m; sourceTree = ""; }; 17A959EECDBC8B53DAF62371054DAAE9 /* SDAnimatedImagePlayer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImagePlayer.m; path = SDWebImage/Core/SDAnimatedImagePlayer.m; sourceTree = ""; }; 18281399F797A6B19033D53D2B0992E3 /* SDWebImageDownloader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloader.m; path = SDWebImage/Core/SDWebImageDownloader.m; sourceTree = ""; }; 183B8EEF2BA8C735371935889864D6B7 /* FirebaseInstallations.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseInstallations.h; path = FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FirebaseInstallations.h; sourceTree = ""; }; 18637DFB49F868B5320F06FCD27ABD99 /* FBLPromise+Then.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Then.m"; path = "Sources/FBLPromises/FBLPromise+Then.m"; sourceTree = ""; }; 18D28D4E30FB28D835221C737B53B7E0 /* GULNetworkInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkInternal.h; path = GoogleUtilities/Network/GULNetworkInternal.h; sourceTree = ""; }; 1925BCD73D2687C697D5448145F92862 /* SDWebImageDownloaderOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderOperation.m; path = SDWebImage/Core/SDWebImageDownloaderOperation.m; sourceTree = ""; }; 193B43A46E5628F605735E9C2EB9C5B1 /* GDTCORTransport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORTransport.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m; sourceTree = ""; }; 1A58961C9BBD35E7B60ECDB62E56F7DC /* firebasecore.nanopb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = firebasecore.nanopb.h; path = Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h; sourceTree = ""; }; 1A6FE3762E8CAD647386716CD1698F48 /* GULReachabilityChecker+Internal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GULReachabilityChecker+Internal.h"; path = "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h"; sourceTree = ""; }; 1AA24773B096CF90111EA4F2AA2C464D /* SDWebImageManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageManager.m; path = SDWebImage/Core/SDWebImageManager.m; sourceTree = ""; }; 1AA30DDE545C3AB8E5A8BCD08CA5B923 /* FIRInstallationsAuthTokenResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAuthTokenResultInternal.h; path = FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResultInternal.h; sourceTree = ""; }; 1AA6F310E602B6F29B722C01961258A4 /* SDImageCachesManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCachesManager.h; path = SDWebImage/Core/SDImageCachesManager.h; sourceTree = ""; }; 1B185568A19C67E5278D6DB507473C2B /* FIRComponentType.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponentType.m; path = FirebaseCore/Sources/FIRComponentType.m; sourceTree = ""; }; 1BC2750E276CE3D6725F398532CEB28B /* FBLPromise+Catch.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Catch.h"; path = "Sources/FBLPromises/include/FBLPromise+Catch.h"; sourceTree = ""; }; 1BF0F7EE147A98C2F11F7C4C32C5B6E3 /* FIROptions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIROptions.h; path = FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h; sourceTree = ""; }; 1CBEB10B215FDB65E86331667E87AE84 /* GDTCCTNanopbHelpers.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTNanopbHelpers.m; path = GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m; sourceTree = ""; }; 1DFB6F1D058B390F2A1340D6D90391C0 /* FIRCoreDiagnosticsConnector.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsConnector.h; path = FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h; sourceTree = ""; }; 1E9CD753F6BD26760EFAA1DF57482D50 /* Appirater-Appirater */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Appirater-Appirater"; path = Appirater.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 1F54F5C198006F0294B0657489850E48 /* GoogleUtilities-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleUtilities-umbrella.h"; sourceTree = ""; }; 1F96A93009C7CAECE802F2AF89962662 /* FBLPromise+Await.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Await.h"; path = "Sources/FBLPromises/include/FBLPromise+Await.h"; sourceTree = ""; }; 20481C9B0D8652965A8D3CA6CF4FB6DF /* Appirater.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Appirater.modulemap; sourceTree = ""; }; 20DAC9944D4D96AD4E142D77D43D9F68 /* Pods-Spotify.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Spotify.release.xcconfig"; sourceTree = ""; }; 2186649006C5A621B8BA7DDCC5C27AB4 /* UIImage+GIF.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+GIF.m"; path = "SDWebImage/Core/UIImage+GIF.m"; sourceTree = ""; }; 219546DB6A6974432E1E0283F24A3329 /* GDTCCTCompressionHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTCompressionHelper.m; path = GoogleDataTransport/GDTCCTLibrary/GDTCCTCompressionHelper.m; sourceTree = ""; }; 2264D3828F48F1D2D87C73BE181475A2 /* NSImage+Compatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSImage+Compatibility.h"; path = "SDWebImage/Core/NSImage+Compatibility.h"; sourceTree = ""; }; 22B6E2A2A128A5A1F1B6F8D6C0EF4BBE /* GDTCORConsoleLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORConsoleLogger.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h; sourceTree = ""; }; 22B79826946DBE0D731ED6479069F8C1 /* PromisesObjC-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "PromisesObjC-Info.plist"; sourceTree = ""; }; 22CACEEBCAA12953A3BA4DB2820EB06A /* SDWebImageDownloaderOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderOperation.h; path = SDWebImage/Core/SDWebImageDownloaderOperation.h; sourceTree = ""; }; 233FC59CE7BD0ABCB6EFD98AF26F4228 /* SDWebImageDownloaderResponseModifier.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderResponseModifier.h; path = SDWebImage/Core/SDWebImageDownloaderResponseModifier.h; sourceTree = ""; }; 2368DD7767138A73CD7D9EADED904481 /* FirebaseCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCore.release.xcconfig; sourceTree = ""; }; 239EF7C666D42E75FB20E65D3491B74A /* FBLPromise+Delay.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Delay.m"; path = "Sources/FBLPromises/FBLPromise+Delay.m"; sourceTree = ""; }; 23D4D9F87518207E73E88D8FBC7C8DE3 /* Appirater */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Appirater; path = Appirater.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2494BB0409778ADBC0785D40DE703573 /* NSURLSession+GULPromises.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSURLSession+GULPromises.h"; path = "GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h"; sourceTree = ""; }; 255E93987C91799073F418C741D2F912 /* zh-Hans.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = "zh-Hans.lproj"; sourceTree = ""; }; 2565C009A42B8D093CEB285EE59D4EE0 /* Pods-Spotify.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-Spotify.modulemap"; sourceTree = ""; }; 25BB086F66AE0B62674631094FA38DB9 /* SDWebImageDownloaderResponseModifier.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderResponseModifier.m; path = SDWebImage/Core/SDWebImageDownloaderResponseModifier.m; sourceTree = ""; }; 25D0D7E32DDD7AE56C23799445C13A57 /* FIRInstallationsStoredItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStoredItem.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h; sourceTree = ""; }; 25E74C0A2D416186897FBBC33BC5608B /* SDDiskCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDDiskCache.h; path = SDWebImage/Core/SDDiskCache.h; sourceTree = ""; }; 26C98C55DA7C8E2B36CE7527095DED78 /* UIImage+ExtendedCacheData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ExtendedCacheData.m"; path = "SDWebImage/Core/UIImage+ExtendedCacheData.m"; sourceTree = ""; }; 278F2C97E692BBEB7227CFD22EE4333A /* FBLPromisePrivate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromisePrivate.h; path = Sources/FBLPromises/include/FBLPromisePrivate.h; sourceTree = ""; }; 27B6AEBABD0DD760910C2037F73CBBA7 /* UIImage+Transform.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Transform.m"; path = "SDWebImage/Core/UIImage+Transform.m"; sourceTree = ""; }; 27EFCAB3E6A3DE572444045971D498C5 /* GoogleAppMeasurement.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleAppMeasurement.release.xcconfig; sourceTree = ""; }; 281F885809D3382CEFAC6F78D5E16CA0 /* FBLPromise+Validate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Validate.m"; path = "Sources/FBLPromises/FBLPromise+Validate.m"; sourceTree = ""; }; 28859EE1F335E36BD76813D7AC4608D9 /* GoogleDataTransport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GoogleDataTransport.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h; sourceTree = ""; }; 28A55FC857FA4F7D9FE98F3B5AA514E8 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; }; 296AEDDAB76C6DA79B00DFB17FE94B13 /* SDWebImageCacheKeyFilter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheKeyFilter.h; path = SDWebImage/Core/SDWebImageCacheKeyFilter.h; sourceTree = ""; }; 29AE365492E6E96E5708A9AD6E31746F /* nanopb-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "nanopb-umbrella.h"; sourceTree = ""; }; 2A1C27AE73C820EC4ACBF12FAEEA1D93 /* SDImageCodersManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCodersManager.h; path = SDWebImage/Core/SDImageCodersManager.h; sourceTree = ""; }; 2A93FB313CA659376FAA369092F2D036 /* FBLPromise+Timeout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Timeout.h"; path = "Sources/FBLPromises/include/FBLPromise+Timeout.h"; sourceTree = ""; }; 2B055BE0C98324B8E1EE0B588C14B23C /* FIRAnalyticsConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAnalyticsConfiguration.m; path = FirebaseCore/Sources/FIRAnalyticsConfiguration.m; sourceTree = ""; }; 2B207171596C8E59567CB49A2BC0B7C9 /* SDGraphicsImageRenderer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDGraphicsImageRenderer.h; path = SDWebImage/Core/SDGraphicsImageRenderer.h; sourceTree = ""; }; 2CD99347EC1D85D2C2FDDB3D1F9DF393 /* FBLPromise+Validate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Validate.h"; path = "Sources/FBLPromises/include/FBLPromise+Validate.h"; sourceTree = ""; }; 2D682D19C50EF105FF8EF05D677CA964 /* NSData+ImageContentType.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+ImageContentType.m"; path = "SDWebImage/Core/NSData+ImageContentType.m"; sourceTree = ""; }; 2DDBED0511F929D50127372614C07E38 /* FIRInstallationsIDController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIDController.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m; sourceTree = ""; }; 2E7185858699874B61C27769747820E1 /* SDWebImagePrefetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImagePrefetcher.m; path = SDWebImage/Core/SDWebImagePrefetcher.m; sourceTree = ""; }; 2F4695D22C004206B1EF601650EB58F1 /* SDWebImageDefine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDefine.m; path = SDWebImage/Core/SDWebImageDefine.m; sourceTree = ""; }; 2FC875B27ACA63CA271CA0D921CF94C3 /* GDTCORUploader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploader.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h; sourceTree = ""; }; 303FE2537CE3769B58923B71632FED1C /* FIRBundleUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRBundleUtil.h; path = FirebaseCore/Sources/FIRBundleUtil.h; sourceTree = ""; }; 31527FC5A967FDE9F076D544F4923295 /* NSData+ImageContentType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSData+ImageContentType.h"; path = "SDWebImage/Core/NSData+ImageContentType.h"; sourceTree = ""; }; 32073165CB0BA8A73E608799A7994586 /* SDWebImage.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SDWebImage.debug.xcconfig; sourceTree = ""; }; 32B0EB1571B6CA660B7AD773851B90C5 /* FirebaseAnalytics.xcframework */ = {isa = PBXFileReference; includeInIndex = 1; name = FirebaseAnalytics.xcframework; path = Frameworks/FirebaseAnalytics.xcframework; sourceTree = ""; }; 32B1014C844F5619D536DFD1800B11D5 /* GULLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLogger.h; path = GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h; sourceTree = ""; }; 32DB218DD71AD0570B0E339E5319F93B /* SDDeviceHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDDeviceHelper.m; path = SDWebImage/Private/SDDeviceHelper.m; sourceTree = ""; }; 33282DA51418F8E945CDA2BC7DDA734E /* FIRInstallationsStatus.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStatus.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h; sourceTree = ""; }; 3347A1AB6546F0A3977529B8F199DC41 /* PromisesObjC */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = PromisesObjC; path = FBLPromises.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33802E944806D1B99AD085FE7F697A74 /* SDDisplayLink.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDDisplayLink.m; path = SDWebImage/Private/SDDisplayLink.m; sourceTree = ""; }; 3446029F86E0312850EAC5C98A32C65C /* ru.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ru.lproj; sourceTree = ""; }; 34A40FBEB8C0F4629E017EA065211E5C /* FirebaseCoreDiagnostics-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseCoreDiagnostics-umbrella.h"; sourceTree = ""; }; 350451991B16222891CB7702FAA4C26A /* Appirater.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = Appirater.h; sourceTree = ""; }; 35329B868A55A887B96B3891C90CE411 /* UIImage+MemoryCacheCost.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+MemoryCacheCost.m"; path = "SDWebImage/Core/UIImage+MemoryCacheCost.m"; sourceTree = ""; }; 363CE7A6599529EED98580D11773BD01 /* GDTCORAssert.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORAssert.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m; sourceTree = ""; }; 36C228F973FB461AAC3DE1B3CAC5533C /* PromisesObjC-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "PromisesObjC-dummy.m"; sourceTree = ""; }; 370CEA1E37CB5E0D01FDBFF39E1020BE /* SDWebImageDownloader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloader.h; path = SDWebImage/Core/SDWebImageDownloader.h; sourceTree = ""; }; 37B1C403940FAFD0BDA8092C0726458A /* SDImageAPNGCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageAPNGCoder.m; path = SDWebImage/Core/SDImageAPNGCoder.m; sourceTree = ""; }; 3808D78CAA3EAA14B869A1667210193C /* SDAnimatedImageView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = SDWebImage/Core/SDAnimatedImageView.h; sourceTree = ""; }; 382D07A53D722649DCAD5C5BC3BDCAC1 /* FIRInstallationsBackoffController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsBackoffController.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h; sourceTree = ""; }; 38A7BC4B65A58A155D087DA3EFFC1D79 /* FIRVersion.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRVersion.h; path = FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h; sourceTree = ""; }; 3A023925AC3740397148C89A37EA72B8 /* UIImage+GIF.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+GIF.h"; path = "SDWebImage/Core/UIImage+GIF.h"; sourceTree = ""; }; 3AE71CC3E7412E1F33D2D46AB258531C /* GDTCOREndpoints.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREndpoints.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h; sourceTree = ""; }; 3B56D652DA90C728A3D4AA16170D7FA2 /* GDTCCTCompressionHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTCompressionHelper.h; path = GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h; sourceTree = ""; }; 3BB2E5C745E6BEBDF556017B5389A3F8 /* FirebaseCore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseCore.h; path = FirebaseCore/Sources/Public/FirebaseCore/FirebaseCore.h; sourceTree = ""; }; 3C2FEB053783A6171CE3DECCFF936A28 /* FIRApp.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRApp.m; path = FirebaseCore/Sources/FIRApp.m; sourceTree = ""; }; 3C31DC7C6755337262C1BDDB6677C635 /* GDTCOREventDataObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREventDataObject.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h; sourceTree = ""; }; 3C388E0E2142C8BAB1FBE711F1A33688 /* UIImage+ExtendedCacheData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+ExtendedCacheData.h"; path = "SDWebImage/Core/UIImage+ExtendedCacheData.h"; sourceTree = ""; }; 3EAF64B24EEF15C83158B0A1B832978D /* FirebaseInstallationsInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseInstallationsInternal.h; path = FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h; sourceTree = ""; }; 3F2853ABAFCEFBF960A5040D649074EC /* GULURLSessionDataResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULURLSessionDataResponse.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h; sourceTree = ""; }; 3FA3607B121AEFB0595C76441513C64D /* SDAnimatedImagePlayer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImagePlayer.h; path = SDWebImage/Core/SDAnimatedImagePlayer.h; sourceTree = ""; }; 40CE0245F2D926AF50D3BFBA488887C1 /* GULURLSessionDataResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULURLSessionDataResponse.m; path = GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.m; sourceTree = ""; }; 415A874BEFEAB372A795B3F77C86139D /* SDWeakProxy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWeakProxy.h; path = SDWebImage/Private/SDWeakProxy.h; sourceTree = ""; }; 4187F6DA61736F8AD4201616D23D8061 /* FIRInstallationsStoredAuthToken.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStoredAuthToken.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h; sourceTree = ""; }; 43363A5F804039F569FD9ABD9433F07B /* SDWebImageIndicator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageIndicator.h; path = SDWebImage/Core/SDWebImageIndicator.h; sourceTree = ""; }; 43864D2A6701CB0D5A279382ABEF4FD9 /* Pods-Spotify-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Spotify-acknowledgements.plist"; sourceTree = ""; }; 4468F16CABC244B58ACDC50E87F8B3C4 /* GULLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULLogger.m; path = GoogleUtilities/Logger/GULLogger.m; sourceTree = ""; }; 447875125DF9F0D89F9C6EF9340E1570 /* GULHeartbeatDateStorable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULHeartbeatDateStorable.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h; sourceTree = ""; }; 447B191325C26D8C11C3D317EC3FBAA8 /* FBLPromise+Race.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Race.h"; path = "Sources/FBLPromises/include/FBLPromise+Race.h"; sourceTree = ""; }; 44E9E3AB162F34A2457874535E2D437A /* UIImage+MemoryCacheCost.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+MemoryCacheCost.h"; path = "SDWebImage/Core/UIImage+MemoryCacheCost.h"; sourceTree = ""; }; 45405CBF1024B18B4589ED32B6B9D343 /* FirebaseCoreInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseCoreInternal.h; path = FirebaseCore/Sources/Private/FirebaseCoreInternal.h; sourceTree = ""; }; 45A1925DBA110CDCB56C30C2AEC79D96 /* tr.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = tr.lproj; sourceTree = ""; }; 45D88DFE4BAAB878102C9DB69C90E1EE /* FIRCoreDiagnostics.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCoreDiagnostics.m; path = Firebase/CoreDiagnostics/FIRCDLibrary/FIRCoreDiagnostics.m; sourceTree = ""; }; 4600B5D11E621BE294D5D7A8106BF312 /* SDImageHEICCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageHEICCoder.h; path = SDWebImage/Core/SDImageHEICCoder.h; sourceTree = ""; }; 46849267116D15C2F916F95AF7ED6264 /* FIRInstallationsErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsErrors.h; path = FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsErrors.h; sourceTree = ""; }; 4687296E13EF95A9B5206235744E21EC /* AppiraterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = AppiraterDelegate.h; sourceTree = ""; }; 476A318EB831BFCD3B77190E73149735 /* GULSecureCoding.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSecureCoding.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h; sourceTree = ""; }; 47C2768639337908388E00A97B570D97 /* cct.nanopb.c */ = {isa = PBXFileReference; includeInIndex = 1; name = cct.nanopb.c; path = GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c; sourceTree = ""; }; 47F2FC011CE490B793305158ACB2048E /* FBLPromise+Wrap.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Wrap.h"; path = "Sources/FBLPromises/include/FBLPromise+Wrap.h"; sourceTree = ""; }; 483340B3D5102879F6C45F55BD02FF1E /* SDImageHEICCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageHEICCoder.m; path = SDWebImage/Core/SDImageHEICCoder.m; sourceTree = ""; }; 48972591E3AF0F13726EC5D4C6BEDFDC /* FIRAppInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAppInternal.h; path = FirebaseCore/Sources/Private/FIRAppInternal.h; sourceTree = ""; }; 490C3A82D71E20AB4B5BC07D75101D21 /* FBLPromise+Catch.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Catch.m"; path = "Sources/FBLPromises/FBLPromise+Catch.m"; sourceTree = ""; }; 4953F3D24A404D00A99D8A29BE61104C /* FIRLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLogger.h; path = FirebaseCore/Sources/Private/FIRLogger.h; sourceTree = ""; }; 4958CF39D5BE0CE693F9BABAB470D5A1 /* it.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = it.lproj; sourceTree = ""; }; 495F9A8ED0CBC83C181879060E49F21E /* firebasecore.nanopb.c */ = {isa = PBXFileReference; includeInIndex = 1; name = firebasecore.nanopb.c; path = Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.c; sourceTree = ""; }; 4AABAA72A7A163BFFFA781CE2598D89B /* Pods-Spotify-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Spotify-frameworks.sh"; sourceTree = ""; }; 4AC80933AD4E17DC2DDD4A5156E1FADB /* id.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = id.lproj; sourceTree = ""; }; 4CD317B6F97D4204C18D7618DBB86C03 /* GDTCORLifecycle.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORLifecycle.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h; sourceTree = ""; }; 4D06A1E8D589DEE651EA569B253B9DA6 /* GoogleUtilities.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleUtilities.release.xcconfig; sourceTree = ""; }; 4D7D4D1C90894E8A533206E5679B1871 /* SDImageAssetManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageAssetManager.m; path = SDWebImage/Private/SDImageAssetManager.m; sourceTree = ""; }; 4D82EC7D361BCA9009FD349978107D1C /* FBLPromise+All.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+All.h"; path = "Sources/FBLPromises/include/FBLPromise+All.h"; sourceTree = ""; }; 4D8AA9949B871C5C08476B12041D2D99 /* SDImageCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCoder.m; path = SDWebImage/Core/SDImageCoder.m; sourceTree = ""; }; 4DEBF0EE7331EEAD283D9D6370A2B3FF /* SDImageCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCache.m; path = SDWebImage/Core/SDImageCache.m; sourceTree = ""; }; 4E43BB424620088D2FC848F87869992C /* FirebaseInstallations.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseInstallations.debug.xcconfig; sourceTree = ""; }; 4E579AE58A2F6343FA943DA78FDA6A24 /* ca.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ca.lproj; sourceTree = ""; }; 4F72FBD1344FC32D12AA727F983904D6 /* nb.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = nb.lproj; sourceTree = ""; }; 4F86D0619F7550A5DBDF634B8413E25F /* GULHeartbeatDateStorageUserDefaults.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULHeartbeatDateStorageUserDefaults.m; path = GoogleUtilities/Environment/GULHeartbeatDateStorageUserDefaults.m; sourceTree = ""; }; 50A0F26807A7475A7CAB02BAC26EA368 /* Appirater-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Appirater-Info.plist"; sourceTree = ""; }; 50AF89B1AE50DF562243E31C949BD5B3 /* UIImage+MultiFormat.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+MultiFormat.m"; path = "SDWebImage/Core/UIImage+MultiFormat.m"; sourceTree = ""; }; 5304860EBF8FA61DE4A892566862746D /* SDImageLoadersManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageLoadersManager.m; path = SDWebImage/Core/SDImageLoadersManager.m; sourceTree = ""; }; 5352D3BC21FF5229DD4FC39829C48E7C /* GDTCORReachability.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORReachability.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORReachability.m; sourceTree = ""; }; 5415E857C6437079EA6CE8D49BC4D00F /* Firebase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Firebase.h; path = CoreOnly/Sources/Firebase.h; sourceTree = ""; }; 54836C20C54693641AA8386E05C05C5C /* GULNetworkMessageCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkMessageCode.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h; sourceTree = ""; }; 54D381D3559D8677EA0632FDB5EEA05A /* FirebaseInstallations-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseInstallations-umbrella.h"; sourceTree = ""; }; 5514ED015845BE4DF10DF85BF4983A59 /* FIRComponentType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentType.h; path = FirebaseCore/Sources/Private/FIRComponentType.h; sourceTree = ""; }; 561783788BEFAECA7A813F1442497E1D /* Pods-Spotify-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Spotify-acknowledgements.markdown"; sourceTree = ""; }; 56EBF81C7BB707870CF8F18F19C219BC /* GULSceneDelegateSwizzler_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSceneDelegateSwizzler_Private.h; path = GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h; sourceTree = ""; }; 573447DBCFF573125A97AC8FE08CF8FB /* sk.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = sk.lproj; sourceTree = ""; }; 579C123C87D8545B91CD64649918A148 /* SDmetamacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDmetamacros.h; path = SDWebImage/Private/SDmetamacros.h; sourceTree = ""; }; 57A489F83B821E73C9F898D59FD839D5 /* SDWebImageCacheSerializer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheSerializer.h; path = SDWebImage/Core/SDWebImageCacheSerializer.h; sourceTree = ""; }; 5805E31EC45A505EEE3457FA2B9FA351 /* FIRCoreDiagnosticsInterop.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsInterop.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h; sourceTree = ""; }; 5852FC7ED30AAEEA07547AF6D71F3D85 /* SDImageCacheDefine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCacheDefine.h; path = SDWebImage/Core/SDImageCacheDefine.h; sourceTree = ""; }; 5A8A40185ABE648F7A15E613D0C0559F /* FBLPromise+Timeout.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Timeout.m"; path = "Sources/FBLPromises/FBLPromise+Timeout.m"; sourceTree = ""; }; 5BE80A247D8376A910A19BFB10B99430 /* GDTCORStorageEventSelector.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORStorageEventSelector.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORStorageEventSelector.m; sourceTree = ""; }; 5BEA3009F10DE79DB402A683F6839FC8 /* FBLPromise+Wrap.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Wrap.m"; path = "Sources/FBLPromises/FBLPromise+Wrap.m"; sourceTree = ""; }; 5C5DD83B8521D66EB85A5A45A92F810F /* GULAppDelegateSwizzler_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppDelegateSwizzler_Private.h; path = GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h; sourceTree = ""; }; 5CBCB878C4026B383CA6687B4DC52D58 /* FIRAppAssociationRegistration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAppAssociationRegistration.h; path = FirebaseCore/Sources/FIRAppAssociationRegistration.h; sourceTree = ""; }; 5DD11686FB044C460680737D6E66EC0C /* SDImageIOCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageIOCoder.m; path = SDWebImage/Core/SDImageIOCoder.m; sourceTree = ""; }; 5E16667A1668B640A863FF171D2CB5FB /* FIRHeartbeatInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRHeartbeatInfo.h; path = FirebaseCore/Sources/Private/FIRHeartbeatInfo.h; sourceTree = ""; }; 5F030A051DB2F7C17CB74A9C0FBD7B56 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; 5FD16FE46197F77B6D4E28CDE644CC73 /* GDTCORRegistrar.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORRegistrar.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h; sourceTree = ""; }; 606B14BF9E8B55AA634896F3FE6C2102 /* GDTCORDirectorySizeTracker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORDirectorySizeTracker.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m; sourceTree = ""; }; 615A6102144F4931AF689AB96C01D719 /* SDAnimatedImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImage.h; path = SDWebImage/Core/SDAnimatedImage.h; sourceTree = ""; }; 61C15E699851C928BE91C72A5169AEB2 /* NSURLSession+GULPromises.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSURLSession+GULPromises.m"; path = "GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.m"; sourceTree = ""; }; 6237C8DA5406A972EAFEEC2CB46AA910 /* FIRInstallationsErrorUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsErrorUtil.m; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m; sourceTree = ""; }; 62E087DDBCC7CE3CA61BACD11EE3931E /* SDWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImage.h; path = WebImage/SDWebImage.h; sourceTree = ""; }; 63FB1BA9182BB6D6E47E4DD049005A29 /* FirebaseCore-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FirebaseCore-umbrella.h"; sourceTree = ""; }; 65F9EFAA484830CADC6FE4DE20867D8D /* SDWebImageDownloaderDecryptor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderDecryptor.h; path = SDWebImage/Core/SDWebImageDownloaderDecryptor.h; sourceTree = ""; }; 668CE7FB58FBCA03E7BF9FFA7D4AEA8C /* FIRAnalyticsConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAnalyticsConfiguration.h; path = FirebaseCore/Sources/FIRAnalyticsConfiguration.h; sourceTree = ""; }; 66B0B7773B93F50516AFA7A4AD80001F /* FIRAppInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRAppInternal.h; path = FirebaseCore/Sources/Private/FIRAppInternal.h; sourceTree = ""; }; 66E791BF8C770CDCE827420B1959B849 /* GDTCORDirectorySizeTracker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORDirectorySizeTracker.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h; sourceTree = ""; }; 67A976AAF1EAC9BADF423E17C96B88FE /* GDTCORTransport_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransport_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h; sourceTree = ""; }; 6806286AE468436D210DE24F56FFD7A2 /* FBLPromise+Recover.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Recover.h"; path = "Sources/FBLPromises/include/FBLPromise+Recover.h"; sourceTree = ""; }; 68332238690458AE7A254B6097B8BD22 /* SDWebImageDownloaderDecryptor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderDecryptor.m; path = SDWebImage/Core/SDWebImageDownloaderDecryptor.m; sourceTree = ""; }; 695ADBFC9C4D04D17897085D1ABBBAA7 /* GoogleAppMeasurement.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleAppMeasurement.debug.xcconfig; sourceTree = ""; }; 6A35F8BE947883A35C48CDFFF5BACB72 /* FIRInstallationsAPIService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsAPIService.m; path = FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m; sourceTree = ""; }; 6AB43C522E93B03C169E99C63451C091 /* GDTCORUploadCoordinator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORUploadCoordinator.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m; sourceTree = ""; }; 6AFBDA1EBD167AFD2083DC8D9D565001 /* GDTCORUploadBatch.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORUploadBatch.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORUploadBatch.m; sourceTree = ""; }; 6BA2A90BF46E4D5796820E1A900E2C0B /* FIRComponentContainer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentContainer.h; path = FirebaseCore/Sources/Private/FIRComponentContainer.h; sourceTree = ""; }; 6C61DBDC1219C0AC0AD0FF84A0133692 /* GULSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSwizzler.h; path = GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h; sourceTree = ""; }; 6C8ECB26C6DABE3DBF37DBC44D5A8C5C /* GULNetworkConstants.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkConstants.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h; sourceTree = ""; }; 6E03F27C2DCF8DB117CBC29C875DCD95 /* FIRInstallationsBackoffController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsBackoffController.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.m; sourceTree = ""; }; 6E7EB7DF29D03415C41C56734E0C9017 /* FIRInstallationsErrorUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsErrorUtil.h; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h; sourceTree = ""; }; 6F25465DB16E55EFF41F24C620E24E05 /* FIRInstallationsStoredAuthToken.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStoredAuthToken.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m; sourceTree = ""; }; 700956653190B059B98E6D0401813D2F /* nl.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = nl.lproj; sourceTree = ""; }; 703B93588CDC01E3116988AAD425A74D /* FIRInstallationsIIDStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIIDStore.h; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h; sourceTree = ""; }; 7055DAE8B926D478FFCB05DC46E7F844 /* FIRConfigurationInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRConfigurationInternal.h; path = FirebaseCore/Sources/FIRConfigurationInternal.h; sourceTree = ""; }; 70FBB40CF13B29C7EC31294E2767F546 /* FBLPromise+Any.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Any.h"; path = "Sources/FBLPromises/include/FBLPromise+Any.h"; sourceTree = ""; }; 71783D9D8F34A0AB09F92BAF64FC155E /* GoogleUtilities-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleUtilities-dummy.m"; sourceTree = ""; }; 724419369F9B475400E63E05193D0394 /* FIRVersion.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRVersion.m; path = FirebaseCore/Sources/FIRVersion.m; sourceTree = ""; }; 7283DD0E961B31F830AEB840A7B59650 /* FBLPromises.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromises.h; path = Sources/FBLPromises/include/FBLPromises.h; sourceTree = ""; }; 73009AD0D7FBF3D5781BD8B87D5E1D98 /* GULMutableDictionary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULMutableDictionary.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h; sourceTree = ""; }; 736D3261A8A25DCBBECD54A6C5C48166 /* GDTCORFlatFileStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORFlatFileStorage.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m; sourceTree = ""; }; 7387A62B07188200FF8487FD3A2CF2A5 /* FIRInstallationsSingleOperationPromiseCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsSingleOperationPromiseCache.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.m; sourceTree = ""; }; 73F76A840EAA1BA85CD5CE2578134A93 /* SDWebImageOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageOperation.h; path = SDWebImage/Core/SDWebImageOperation.h; sourceTree = ""; }; 747BA4839026A5B48F78F7F2F59B01D8 /* FIRCoreDiagnosticsConnector.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsConnector.h; path = FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h; sourceTree = ""; }; 74B387BF4963DADE929360BC4D95BF9F /* GDTCOREvent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCOREvent.m; path = GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m; sourceTree = ""; }; 75E9E1284F8AE50551FF56600DDDCEBF /* GULSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSwizzler.m; path = GoogleUtilities/MethodSwizzler/GULSwizzler.m; sourceTree = ""; }; 7887F0BAB621B4623BFE017BC5FA339D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 78E45C724EB1ABA21537759F2AA0CDBF /* UIView+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCache.m"; path = "SDWebImage/Core/UIView+WebCache.m"; sourceTree = ""; }; 79AB37844F04B4F0CF83A66B9FBD9175 /* GDTCORFlatFileStorage+Promises.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GDTCORFlatFileStorage+Promises.m"; path = "GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m"; sourceTree = ""; }; 7A10001AFFFB54BB477050134573A9C4 /* SDImageCoderHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCoderHelper.h; path = SDWebImage/Core/SDImageCoderHelper.h; sourceTree = ""; }; 7B1EB303FD52ADD9ABA791AD8C33496F /* GDTCORFlatFileStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORFlatFileStorage.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h; sourceTree = ""; }; 7B4DFE57A7C5FC68155C295484A72007 /* SDWebImageTransition.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageTransition.h; path = SDWebImage/Core/SDWebImageTransition.h; sourceTree = ""; }; 7BBDEBAD931B605980FE3DCE9FA337AE /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; 7BF54C90D1DF4DD3950B8DCD5C09D18C /* PromisesObjC-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PromisesObjC-umbrella.h"; sourceTree = ""; }; 7CE88DC6249A53E194E886FD268B4F22 /* SDDisplayLink.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDDisplayLink.h; path = SDWebImage/Private/SDDisplayLink.h; sourceTree = ""; }; 7D8A688C63FDD5533A68B0CD80B0501E /* SDImageCacheConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCacheConfig.m; path = SDWebImage/Core/SDImageCacheConfig.m; sourceTree = ""; }; 7EF33965A3795DEE7A531EC8C56300B4 /* GDTCOREvent+GDTCCTSupport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GDTCOREvent+GDTCCTSupport.h"; path = "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h"; sourceTree = ""; }; 7F0F742A22FC71CED5A18A108E514A8B /* pb_decode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_decode.h; sourceTree = ""; }; 7F3BFEC70D8BA82BE6E262B3750543AC /* GDTCORStorageProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORStorageProtocol.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h; sourceTree = ""; }; 7FDFC5FEAC4A550A50CD516405F6DFF2 /* FBLPromise+Async.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Async.h"; path = "Sources/FBLPromises/include/FBLPromise+Async.h"; sourceTree = ""; }; 804DFCC909792374DF921D69BE1F1C18 /* SDInternalMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDInternalMacros.h; path = SDWebImage/Private/SDInternalMacros.h; sourceTree = ""; }; 809CE53E4184869CD1D4910C0A359884 /* GDTCORPlatform.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORPlatform.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m; sourceTree = ""; }; 80FE85865E814FFFE9FC4BD1E92FF02B /* SDAssociatedObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAssociatedObject.m; path = SDWebImage/Private/SDAssociatedObject.m; sourceTree = ""; }; 813CEE72D58D250DD2A09A4157C51303 /* GDTCORAssert.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORAssert.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h; sourceTree = ""; }; 82AAE98FB8E850A8ECBA43F727AD3F4B /* FIRComponentContainer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentContainer.h; path = FirebaseCore/Sources/Private/FIRComponentContainer.h; sourceTree = ""; }; 82EAA36E76AE8F4E664A68EC8EB36298 /* GULLoggerLevel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULLoggerLevel.h; path = GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h; sourceTree = ""; }; 83495546D2D6077CD99D793135E8A7EC /* SDWebImageCompat.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageCompat.h; path = SDWebImage/Core/SDWebImageCompat.h; sourceTree = ""; }; 83D038C4E24C71BFAE211A6E0CDA58BA /* nanopb.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = nanopb.debug.xcconfig; sourceTree = ""; }; 841780DAC592F44AB2E8D35D36E32285 /* SDImageTransformer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageTransformer.m; path = SDWebImage/Core/SDImageTransformer.m; sourceTree = ""; }; 8454F0C4E786C6A2499ED8DA5879A808 /* FIRHeartbeatInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRHeartbeatInfo.m; path = FirebaseCore/Sources/FIRHeartbeatInfo.m; sourceTree = ""; }; 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleDataTransport; path = GoogleDataTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 859E0D4CB486D5A4710D605554199885 /* FirebaseCore-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseCore-Info.plist"; sourceTree = ""; }; 859F0A43E3649885E17F4D515A86A159 /* UIImage+Metadata.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+Metadata.h"; path = "SDWebImage/Core/UIImage+Metadata.h"; sourceTree = ""; }; 867CE4FF42F759E76A71FEF4453E8079 /* ms.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ms.lproj; sourceTree = ""; }; 87BD1262A5EDC0CE9D84214096454004 /* GDTCORReachability.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORReachability.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h; sourceTree = ""; }; 881ACDC9E0325D12479F43AD079D1CBB /* GoogleDataTransport-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleDataTransport-umbrella.h"; sourceTree = ""; }; 893980C67675F297617FB935213E4671 /* FIRFirebaseUserAgent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRFirebaseUserAgent.m; path = FirebaseCore/Sources/FIRFirebaseUserAgent.m; sourceTree = ""; }; 8A0DEDA4144F466B62361967B86ACC25 /* th.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = th.lproj; sourceTree = ""; }; 8A7AE3C5E6E2073C8FAC46FF8333849F /* ResourceBundle-Appirater-Appirater-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Appirater-Appirater-Info.plist"; sourceTree = ""; }; 8B4E22AE507DF453D28673AD92339EB1 /* FirebaseInstallations.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseInstallations.release.xcconfig; sourceTree = ""; }; 8BD491CEBE34A4FF9B067D4431A9525D /* SDWebImageManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageManager.h; path = SDWebImage/Core/SDWebImageManager.h; sourceTree = ""; }; 8C1F9B1C56AC542B7695BF945F7E8A5A /* UIImage+MultiFormat.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+MultiFormat.h"; path = "SDWebImage/Core/UIImage+MultiFormat.h"; sourceTree = ""; }; 8C5EE75C53160A323F9D43000C9258C2 /* zh-Hant.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = "zh-Hant.lproj"; sourceTree = ""; }; 8C7297F9F351E0DFB107547CC454F81B /* en.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = en.lproj; sourceTree = ""; }; 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseCoreDiagnostics; path = FirebaseCoreDiagnostics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8CDC01574AEDC72431059BB3FDE16F59 /* de.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = de.lproj; sourceTree = ""; }; 8CDC11191CD5FAC6E9AA96E2BC7DAD05 /* FIRHeartbeatInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRHeartbeatInfo.h; path = FirebaseCore/Sources/Private/FIRHeartbeatInfo.h; sourceTree = ""; }; 8D2E7ABE07934F659148C41C852087FD /* NSImage+Compatibility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSImage+Compatibility.m"; path = "SDWebImage/Core/NSImage+Compatibility.m"; sourceTree = ""; }; 8D628BFBCC6276E5E277ED26143A0529 /* UIColor+SDHexString.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIColor+SDHexString.h"; path = "SDWebImage/Private/UIColor+SDHexString.h"; sourceTree = ""; }; 8D85590DB78FA3CE98F5876D319CDC9F /* FBLPromise+Then.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Then.h"; path = "Sources/FBLPromises/include/FBLPromise+Then.h"; sourceTree = ""; }; 8DFCA2753EDD74C8B7D46CD39BC4F756 /* NSBezierPath+SDRoundedCorners.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBezierPath+SDRoundedCorners.m"; path = "SDWebImage/Private/NSBezierPath+SDRoundedCorners.m"; sourceTree = ""; }; 8E3D624D33EC772ADA4910D9A6A067AD /* SDImageCoderHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCoderHelper.m; path = SDWebImage/Core/SDImageCoderHelper.m; sourceTree = ""; }; 8E7ED49344AE0AC6AD92262E047201BD /* FIRCoreDiagnosticsConnector.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCoreDiagnosticsConnector.m; path = FirebaseCore/Sources/FIRCoreDiagnosticsConnector.m; sourceTree = ""; }; 8F2CD38DB179EE57D03D1E4AE0BBA047 /* SDImageGraphics.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageGraphics.m; path = SDWebImage/Core/SDImageGraphics.m; sourceTree = ""; }; 8F6FA7F2B642E68ECD25BCCB0DDAAA75 /* GDTCORClock.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORClock.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORClock.m; sourceTree = ""; }; 90548723A968948DA9E270815D5DFEA2 /* hu.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = hu.lproj; sourceTree = ""; }; 90D9BC28AA40B8F7AAC5DBAFFFA2503A /* pb_encode.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_encode.c; sourceTree = ""; }; 90E823CCF25045F00FF4E98E871586A6 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; 91D7D93CD15EE8BBB672073A9EE9172E /* SDWebImageDefine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDefine.h; path = SDWebImage/Core/SDWebImageDefine.h; sourceTree = ""; }; 91EF3E33FDEAEF5BB90A91327C8718B6 /* SDWebImage-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SDWebImage-Info.plist"; sourceTree = ""; }; 92EA46401122BB560F222964810DAE14 /* FIRComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponent.h; path = FirebaseCore/Sources/Private/FIRComponent.h; sourceTree = ""; }; 932BDDF177FD04B28E9285096E7D291C /* GoogleDataTransport-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleDataTransport-dummy.m"; sourceTree = ""; }; 93498C80BEE42599CD4B49D1DCDE8DAA /* GDTCORConsoleLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORConsoleLogger.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m; sourceTree = ""; }; 94342AAF24F20C2012C9E17B8D8076FA /* GoogleUtilities.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleUtilities.modulemap; sourceTree = ""; }; 9484BA52176E0E66CBDC23122D2948A2 /* FIRInstallationsStoredItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStoredItem.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m; sourceTree = ""; }; 9487AD43770C35ED6D6CF71B197AE775 /* SDWebImageTransitionInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageTransitionInternal.h; path = SDWebImage/Private/SDWebImageTransitionInternal.h; sourceTree = ""; }; 94FAEBC41B0BE3E27929CC570CB58423 /* FirebaseInstallations-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FirebaseInstallations-Info.plist"; sourceTree = ""; }; 97DFD7435A40FB6C7611C58681006DC8 /* Pods-Spotify */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-Spotify"; path = Pods_Spotify.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 98368FFE6F3A2C1CB4B79C360D245377 /* SDImageCachesManagerOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCachesManagerOperation.h; path = SDWebImage/Private/SDImageCachesManagerOperation.h; sourceTree = ""; }; 983C23FA76E7A245275E7A252B67A2A0 /* pb_common.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_common.c; sourceTree = ""; }; 98A2C33C3CFB15DFB0AD91D948975967 /* ko.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ko.lproj; sourceTree = ""; }; 99108619FD685C7E04AA2798D8184E88 /* FIRInstallationsLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsLogger.h; path = FirebaseInstallations/Source/Library/FIRInstallationsLogger.h; sourceTree = ""; }; 99AC313325A72709F61AD9B82245FC4F /* SDImageCachesManagerOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCachesManagerOperation.m; path = SDWebImage/Private/SDImageCachesManagerOperation.m; sourceTree = ""; }; 99C91ADC1F402BD8ABEE63EAB8134747 /* el.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = el.lproj; sourceTree = ""; }; 9A75B93924C163BDD4BFE27BE82320E0 /* GULKeychainUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULKeychainUtils.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h; sourceTree = ""; }; 9B960865AD112FB963F482E45E567B56 /* SDDiskCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDDiskCache.m; path = SDWebImage/Core/SDDiskCache.m; sourceTree = ""; }; 9BA476F642687D5FC7AA27BDDA27DEBC /* FIROptions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIROptions.m; path = FirebaseCore/Sources/FIROptions.m; sourceTree = ""; }; 9BC48CD5B43C672FF7213F9BE34110DB /* UIImage+Metadata.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Metadata.m"; path = "SDWebImage/Core/UIImage+Metadata.m"; sourceTree = ""; }; 9C35CA5CB6C0053B485FCFDFD30A1662 /* GULAppDelegateSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULAppDelegateSwizzler.h; path = GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h; sourceTree = ""; }; 9D110B53C055335FB5D62021AD131E9F /* FBLPromise+All.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+All.m"; path = "Sources/FBLPromises/FBLPromise+All.m"; sourceTree = ""; }; 9D1FFD95B9DC7DB4011FC5445F7059CE /* GDTCOREndpoints.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCOREndpoints.m; path = GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 9DB1409CD0C3B140ECAB6006D2DAD8BC /* GDTCORRegistrar.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORRegistrar.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m; sourceTree = ""; }; 9E08253131AF6EBD5597DF21EBF9B98D /* FIRInstallationsItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsItem.m; path = FirebaseInstallations/Source/Library/FIRInstallationsItem.m; sourceTree = ""; }; 9FB349B1B1D5B036FA774AC001CEBCB4 /* hy.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = hy.lproj; sourceTree = ""; }; A0F0A5A8418FA40166272625AFB5330A /* FIRAppAssociationRegistration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRAppAssociationRegistration.m; path = FirebaseCore/Sources/FIRAppAssociationRegistration.m; sourceTree = ""; }; A1B2BE9FCAE442C062B1070B5F83D9D9 /* FBLPromise+Any.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Any.m"; path = "Sources/FBLPromises/FBLPromise+Any.m"; sourceTree = ""; }; A243783F2DB10CD105EA1B585E02DF32 /* SDImageIOAnimatedCoderInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageIOAnimatedCoderInternal.h; path = SDWebImage/Private/SDImageIOAnimatedCoderInternal.h; sourceTree = ""; }; A259FD968B2F3F103051FCCB13D1EF8F /* UIImageView+HighlightedWebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+HighlightedWebCache.m"; path = "SDWebImage/Core/UIImageView+HighlightedWebCache.m"; sourceTree = ""; }; A27A53F9FCBFE297E53C77376ACB64D1 /* Pods-Spotify-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Spotify-dummy.m"; sourceTree = ""; }; A2CD1B738ABAB7BA344D7969860B494C /* FIRCoreDiagnostics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnostics.h; path = Firebase/CoreDiagnostics/FIRCDLibrary/Public/FIRCoreDiagnostics.h; sourceTree = ""; }; A3142D0BC0A7099D82BA56A3FD637656 /* FIRConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRConfiguration.m; path = FirebaseCore/Sources/FIRConfiguration.m; sourceTree = ""; }; A328CD57DBA12543441049EBF409F205 /* FIRInstallationsStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsStore.m; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.m; sourceTree = ""; }; A40C76162676EC9A3E0C33C1F29CB93F /* GULKeychainStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULKeychainStorage.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h; sourceTree = ""; }; A4A6DED8C7023B5E793A748ED43129EE /* FIRLoggerLevel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLoggerLevel.h; path = FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h; sourceTree = ""; }; A4FB500E6FBEE5F4910B4E4F3894E917 /* SDWebImageIndicator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageIndicator.m; path = SDWebImage/Core/SDWebImageIndicator.m; sourceTree = ""; }; A55020CA6EDE9396075075D402E84AEC /* FIRInstallationsSingleOperationPromiseCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsSingleOperationPromiseCache.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h; sourceTree = ""; }; A6178B53BBB7ED0452CEE088B25AA064 /* GoogleUtilities-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleUtilities-Info.plist"; sourceTree = ""; }; A62E17FF50F14A446211D0E0BA00FD24 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "SDWebImage/Core/UIImage+ForceDecode.m"; sourceTree = ""; }; A6344AC78A87A0720BE33CE02655D669 /* SDWebImage-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SDWebImage-umbrella.h"; sourceTree = ""; }; A6888DFA1022D0AF9FB4109175558CEC /* FBLPromise+Do.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Do.m"; path = "Sources/FBLPromises/FBLPromise+Do.m"; sourceTree = ""; }; A7D509EE7C37DC62C5F5119B2692E792 /* FIRCoreDiagnosticsData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsData.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h; sourceTree = ""; }; A8689F656BCD2DE359141CDE820E425F /* FIRConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRConfiguration.h; path = FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h; sourceTree = ""; }; A86DA94DC9AB6D6E95B73BF4F59C3EF1 /* fa.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = fa.lproj; sourceTree = ""; }; A8B52265053B7A072C3CFD52774A4E64 /* GULNetwork.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetwork.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULNetwork.h; sourceTree = ""; }; A9A5B5BEF1CFAFF4AEB69AFD4C20968A /* SDImageIOAnimatedCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageIOAnimatedCoder.m; path = SDWebImage/Core/SDImageIOAnimatedCoder.m; sourceTree = ""; }; AA8BA4718A81C87F777362D613444D44 /* Firebase.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Firebase.debug.xcconfig; sourceTree = ""; }; AAC1202619BDEC6CB408DEFB276233E1 /* FIRCoreDiagnosticsData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsData.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h; sourceTree = ""; }; AB007F418E0ED0E20061B9ED7D831C76 /* GULSecureCoding.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSecureCoding.m; path = GoogleUtilities/Environment/GULSecureCoding.m; sourceTree = ""; }; AB33B7FB74C324A4584D1D93AF8B1DB5 /* cct.nanopb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = cct.nanopb.h; path = GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h; sourceTree = ""; }; ACAACBFBB72CC5DBA48E5939C7C05CBB /* SDWebImageDownloaderConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderConfig.m; path = SDWebImage/Core/SDWebImageDownloaderConfig.m; sourceTree = ""; }; ACE81B64A65F8AF15E7C822CDF6A73E6 /* FirebaseCore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCore.debug.xcconfig; sourceTree = ""; }; AE780E851F5F67AAA9C4AE38402A4086 /* GoogleDataTransport.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleDataTransport.modulemap; sourceTree = ""; }; AE8073B498E3701A492F026966747912 /* FIRComponentContainer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRComponentContainer.m; path = FirebaseCore/Sources/FIRComponentContainer.m; sourceTree = ""; }; AF5AFDC2F2B6C3E31AFC75FE3430CA49 /* fi.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = fi.lproj; sourceTree = ""; }; B06B80A4EE003DEDFC1F89E8907C6C16 /* GDTCORTransformer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransformer.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h; sourceTree = ""; }; B0893F86784B88FBEBDDAD469BA72394 /* FBLPromise+Retry.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Retry.m"; path = "Sources/FBLPromises/FBLPromise+Retry.m"; sourceTree = ""; }; B0B214D775196BA7CA8E17E53048A493 /* SDWebImage */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SDWebImage; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B12CEE2C03EFBD7EBAF1717349B42067 /* SDWebImagePrefetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImagePrefetcher.h; path = SDWebImage/Core/SDWebImagePrefetcher.h; sourceTree = ""; }; B134BD979A35DB81CA4CBD64BE86AAC5 /* GULApplication.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULApplication.h; path = GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULApplication.h; sourceTree = ""; }; B17DB1B80794FEE3D7DC12CB3641AB30 /* FIRCurrentDateProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRCurrentDateProvider.m; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.m; sourceTree = ""; }; B195FEEB2B6FD768A1270ABE2938AA39 /* SDImageGraphics.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageGraphics.h; path = SDWebImage/Core/SDImageGraphics.h; sourceTree = ""; }; B1C3F5E1F2DA4D224862FC3F811D0917 /* SDImageAPNGCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageAPNGCoder.h; path = SDWebImage/Core/SDImageAPNGCoder.h; sourceTree = ""; }; B21C4B6B899118FE97F08797C6CC6D78 /* FBLPromise.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBLPromise.m; path = Sources/FBLPromises/FBLPromise.m; sourceTree = ""; }; B3310E7F0BD29950EA79269F8864E7B0 /* FIRComponentType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentType.h; path = FirebaseCore/Sources/Private/FIRComponentType.h; sourceTree = ""; }; B349AD374EBA10C2D49FC4BA8A53F0CF /* FIRCurrentDateProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCurrentDateProvider.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h; sourceTree = ""; }; B3522FD698E8EFD6BDE90D04D1ABC13D /* SDImageLoader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageLoader.m; path = SDWebImage/Core/SDImageLoader.m; sourceTree = ""; }; B3DC2C81260A550A30B6C664556BAD8A /* FIRDependency.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDependency.h; path = FirebaseCore/Sources/Private/FIRDependency.h; sourceTree = ""; }; B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleUtilities; path = GoogleUtilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B46C4D5C2FF1FB2417D06ADB91D1CEBA /* FIRInstallationsStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsStore.h; path = FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h; sourceTree = ""; }; B4A0C1F3F59FF0ECC737E3A22B64F7F0 /* SDAnimatedImageView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = SDWebImage/Core/SDAnimatedImageView.m; sourceTree = ""; }; B526E38BA63C966AF02EA09F26802535 /* PromisesObjC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PromisesObjC.release.xcconfig; sourceTree = ""; }; B5E3DBB2F100E0A299BCFC77EBC69A7E /* FIRInstallationsHTTPError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsHTTPError.m; path = FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.m; sourceTree = ""; }; B66ADF3FBC020B8BAE5E1C0C6EA3C650 /* SDImageCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCache.h; path = SDWebImage/Core/SDImageCache.h; sourceTree = ""; }; B6A08E238C0EA041AF882D5F75E6C3C9 /* SDImageGIFCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageGIFCoder.m; path = SDWebImage/Core/SDImageGIFCoder.m; sourceTree = ""; }; B74D76E7DA492E2DD4E2C7CE3A5874F9 /* Appirater.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Appirater.debug.xcconfig; sourceTree = ""; }; B83BA9762F2888371B5ADE95B066DCB4 /* SDImageCacheConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCacheConfig.h; path = SDWebImage/Core/SDImageCacheConfig.h; sourceTree = ""; }; B8633B4212F9E21E512835226D2BEC2E /* FBLPromise+Delay.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Delay.h"; path = "Sources/FBLPromises/include/FBLPromise+Delay.h"; sourceTree = ""; }; B8AE32764C24267738A026CE787CE667 /* GULKeychainStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULKeychainStorage.m; path = GoogleUtilities/Environment/SecureStorage/GULKeychainStorage.m; sourceTree = ""; }; B95FF8BC5E40163C886E3DB1B394DD75 /* SDAnimatedImageRep.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageRep.h; path = SDWebImage/Core/SDAnimatedImageRep.h; sourceTree = ""; }; BA37D2C1168173A7796B05F93479A231 /* NSButton+WebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSButton+WebCache.h"; path = "SDWebImage/Core/NSButton+WebCache.h"; sourceTree = ""; }; BA393B4B24C719DDDC878C0A08B2D1BB /* GoogleDataTransport.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleDataTransport.debug.xcconfig; sourceTree = ""; }; BA44BA3AD4121BF4B3BA03128355DA0A /* GULNetworkConstants.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULNetworkConstants.m; path = GoogleUtilities/Network/GULNetworkConstants.m; sourceTree = ""; }; BAB945382DE7BCE1238478DF4341D1C3 /* FIRInstallationsItem+RegisterInstallationAPI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FIRInstallationsItem+RegisterInstallationAPI.h"; path = "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h"; sourceTree = ""; }; BB06018D1785388A5C83E9EF4D57ED16 /* GULHeartbeatDateStorageUserDefaults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULHeartbeatDateStorageUserDefaults.h; path = GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h; sourceTree = ""; }; BB1D1192CEC6C26D481BAB47C75BCA56 /* SDImageAssetManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageAssetManager.h; path = SDWebImage/Private/SDImageAssetManager.h; sourceTree = ""; }; BB81C8F74DB13FEF4D842A7A4127FEBF /* SDImageIOAnimatedCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageIOAnimatedCoder.h; path = SDWebImage/Core/SDImageIOAnimatedCoder.h; sourceTree = ""; }; BBB794E91F141A51A62086A95A79B3B0 /* GDTCOREvent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREvent.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h; sourceTree = ""; }; BCBC03DFAC32230F28CDE2D8E913A269 /* GDTCORTransformer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCORTransformer.m; path = GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m; sourceTree = ""; }; BCEE4E5F9E2A1F4352E21C55CC500B15 /* GDTCORClock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORClock.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h; sourceTree = ""; }; BD01D4D2C9BD8D230AC07E17D1BC2580 /* GULNSData+zlib.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GULNSData+zlib.h"; path = "GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h"; sourceTree = ""; }; BD3097914838C55DE30D9D377048A664 /* GULMutableDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULMutableDictionary.m; path = GoogleUtilities/Network/GULMutableDictionary.m; sourceTree = ""; }; BE1CFCA4C81C90732F8DB42EA1F017A3 /* FIRLibrary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLibrary.h; path = FirebaseCore/Sources/Private/FIRLibrary.h; sourceTree = ""; }; BE37397A0A9B84C77A7B34A9406A3952 /* GoogleDataTransport.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleDataTransport.release.xcconfig; sourceTree = ""; }; BE65A7532D1E92D1171FAB3E3DBF0EC2 /* FBLPromise+Recover.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Recover.m"; path = "Sources/FBLPromises/FBLPromise+Recover.m"; sourceTree = ""; }; BFEAB29224248CA276353656139D1668 /* SDImageCachesManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCachesManager.m; path = SDWebImage/Core/SDImageCachesManager.m; sourceTree = ""; }; C0FF8273009BBF51F9894931F3B54A5E /* Appirater-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Appirater-prefix.pch"; sourceTree = ""; }; C1150A7649D44398A5F001918CBE0C16 /* GDTCORReachability_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORReachability_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h; sourceTree = ""; }; C18C4496C07F34201871F99AD27069A0 /* FIRLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLogger.h; path = FirebaseCore/Sources/Private/FIRLogger.h; sourceTree = ""; }; C2439987FF247C8C084181EBCF11641F /* GULOriginalIMPConvenienceMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULOriginalIMPConvenienceMacros.h; path = GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULOriginalIMPConvenienceMacros.h; sourceTree = ""; }; C2B6C797B8E1770AE7FB92B865BB499B /* FIRApp.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRApp.h; path = FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h; sourceTree = ""; }; C2EDD3FE368044C57FFE267748AD31CE /* FIRComponentContainerInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponentContainerInternal.h; path = FirebaseCore/Sources/FIRComponentContainerInternal.h; sourceTree = ""; }; C3AECB4945D348C50164B9454BA98CB3 /* pt-BR.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = "pt-BR.lproj"; sourceTree = ""; }; C41FA4729FC85108299943DF13BE21D7 /* SDWebImageError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageError.h; path = SDWebImage/Core/SDWebImageError.h; sourceTree = ""; }; C48BA4C3D0C62019243E819DD277C007 /* SDInternalMacros.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDInternalMacros.m; path = SDWebImage/Private/SDInternalMacros.m; sourceTree = ""; }; C52C466DF84E99205E54E0AEC7DF14B2 /* FirebaseCoreDiagnostics.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCoreDiagnostics.debug.xcconfig; sourceTree = ""; }; C5674D03956C9BD31033CABEEFE4F2E1 /* PromisesObjC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PromisesObjC.debug.xcconfig; sourceTree = ""; }; C5E56AF681704D05296BF6544BFD6DBC /* nanopb.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = nanopb.release.xcconfig; sourceTree = ""; }; C6911F1DC733532D91E29D6B0433A787 /* vi.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = vi.lproj; sourceTree = ""; }; C6A1BDEE41C3F181B6BAC16ADB84601E /* SDAssociatedObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAssociatedObject.h; path = SDWebImage/Private/SDAssociatedObject.h; sourceTree = ""; }; C76E61D46C6D64C4743B6A7256A286EF /* FIRLibrary.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRLibrary.h; path = FirebaseCore/Sources/Private/FIRLibrary.h; sourceTree = ""; }; C78361E6DA67CDEC2A04C25F1B40D31F /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; }; C869A091C5104F60176DCBAC53E9FCD6 /* FBLPromise+Retry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Retry.h"; path = "Sources/FBLPromises/include/FBLPromise+Retry.h"; sourceTree = ""; }; C87340AB40E7872EBB32D3E37F151B98 /* SDImageCodersManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCodersManager.m; path = SDWebImage/Core/SDImageCodersManager.m; sourceTree = ""; }; C887379F2FAEEA195D743E2A7E8695B2 /* FIRDependency.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDependency.h; path = FirebaseCore/Sources/Private/FIRDependency.h; sourceTree = ""; }; C8CC6A0CB955A36A57FB8DC86DC7122B /* UIView+WebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCache.h"; path = "SDWebImage/Core/UIView+WebCache.h"; sourceTree = ""; }; CA1E5C47B1D161A6D7326A63F73039E1 /* UIImage+Transform.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+Transform.h"; path = "SDWebImage/Core/UIImage+Transform.h"; sourceTree = ""; }; CAFA66ED08EE2214A12519DB72F11801 /* FIRInstallationsLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsLogger.m; path = FirebaseInstallations/Source/Library/FIRInstallationsLogger.m; sourceTree = ""; }; CC9B5348D8E36B88FD0E3329BCF4479D /* FIRDependency.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDependency.m; path = FirebaseCore/Sources/FIRDependency.m; sourceTree = ""; }; CD9056754A12ED3E38D68945FE70B586 /* Appirater-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Appirater-dummy.m"; sourceTree = ""; }; CE0BC2BA98097A46FED66139FADDB5C2 /* NSButton+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/Core/NSButton+WebCache.m"; sourceTree = ""; }; CE6BFCD79351B8CDEDCAB2DE6EDB89AE /* SDWebImage-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SDWebImage-dummy.m"; sourceTree = ""; }; CE9EF082FAA32B5B2D0B0C7CC14C11E6 /* Appirater.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Appirater.release.xcconfig; sourceTree = ""; }; CEB2E6515F73C730A4DE300BCA32CE76 /* GDTCOREvent_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREvent_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h; sourceTree = ""; }; CEB675D419ACF104AD5C8F2619C4FCF5 /* SDWeakProxy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWeakProxy.m; path = SDWebImage/Private/SDWeakProxy.m; sourceTree = ""; }; CF04F211D2AE46EEBF166D2CC374B517 /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "SDWebImage/Core/SDAnimatedImageView+WebCache.h"; sourceTree = ""; }; CF805F43FFDD645D95C1ACEABC114049 /* GULUserDefaults.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULUserDefaults.m; path = GoogleUtilities/UserDefaults/GULUserDefaults.m; sourceTree = ""; }; D005D4ECC0D56EB73527C065B49D192C /* he.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = he.lproj; sourceTree = ""; }; D053D0A9CE99EC16E5ED88D7BA8C3A65 /* ro.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ro.lproj; sourceTree = ""; }; D2771347E37390095BF7D36EB989050F /* Appirater-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Appirater-umbrella.h"; sourceTree = ""; }; D29D1ACD3388DF30F5213782D0089AAE /* FIRInstallationsItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsItem.h; path = FirebaseInstallations/Source/Library/FIRInstallationsItem.h; sourceTree = ""; }; D2D3BD16668BB31898CA2909AB1211CB /* ja.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ja.lproj; sourceTree = ""; }; D2DC9C0FCEB534D5193B9600BF0552FC /* GULAppEnvironmentUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULAppEnvironmentUtil.m; path = GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m; sourceTree = ""; }; D327D14764B30DDEEB17666FA535871D /* GULSceneDelegateSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULSceneDelegateSwizzler.h; path = GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h; sourceTree = ""; }; D368B1A08214DF1EF6A0A9F4860161C0 /* FirebaseCoreDiagnostics.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseCoreDiagnostics.modulemap; sourceTree = ""; }; D43779F7D174B41F97107A9631DFA91A /* GoogleAppMeasurement.xcframework */ = {isa = PBXFileReference; includeInIndex = 1; name = GoogleAppMeasurement.xcframework; path = Frameworks/GoogleAppMeasurement.xcframework; sourceTree = ""; }; D519F9451A80C810AEF071E0A50F225E /* UIImageView+WebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImageView+WebCache.h"; path = "SDWebImage/Core/UIImageView+WebCache.h"; sourceTree = ""; }; D52688E7BC72717ABC1A8FD85DD32AE4 /* cs.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = cs.lproj; sourceTree = ""; }; D53EBA2326B86A037D7D89C9C9849AB1 /* UIButton+WebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIButton+WebCache.h"; path = "SDWebImage/Core/UIButton+WebCache.h"; sourceTree = ""; }; D54DA10983EF09ABC480187282A87A58 /* FIRInstallationsAuthTokenResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsAuthTokenResult.m; path = FirebaseInstallations/Source/Library/FIRInstallationsAuthTokenResult.m; sourceTree = ""; }; D5FB0717B52FE2A1C59CA75D8CE483EA /* FIRInstallationsIIDStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIIDStore.m; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.m; sourceTree = ""; }; D605D91349EBFFC6D84A254293FFF56F /* FIRCoreDiagnosticsInterop.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRCoreDiagnosticsInterop.h; path = Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h; sourceTree = ""; }; D6368995E44A12ED775E22745EE36796 /* SDWebImageCompat.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCompat.m; path = SDWebImage/Core/SDWebImageCompat.m; sourceTree = ""; }; D69D4E5A3DF64985BBF9D3080CCD6144 /* GULUserDefaults.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULUserDefaults.h; path = GoogleUtilities/UserDefaults/Public/GoogleUtilities/GULUserDefaults.h; sourceTree = ""; }; D71B48D7039219AEB311FB0DA2E1E389 /* FIRComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRComponent.h; path = FirebaseCore/Sources/Private/FIRComponent.h; sourceTree = ""; }; D75DB99AD94B959D922B1BF144AEFE15 /* SDAnimatedImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImage.m; path = SDWebImage/Core/SDAnimatedImage.m; sourceTree = ""; }; D8ADAB8E982AFC5F2BD545EB091C78BD /* FIRInstallationsAuthTokenResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAuthTokenResult.h; path = FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsAuthTokenResult.h; sourceTree = ""; }; D8BE8FDFF77007F5278B6A0E5BC01E6A /* pb_decode.c */ = {isa = PBXFileReference; includeInIndex = 1; path = pb_decode.c; sourceTree = ""; }; D92B4CC4E2762BD017DDAA71533D1487 /* SDWebImageDownloaderRequestModifier.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderRequestModifier.m; path = SDWebImage/Core/SDWebImageDownloaderRequestModifier.m; sourceTree = ""; }; D9304F4CFD273BA0BC3D8FD8EA8CE973 /* SDWebImageDownloaderRequestModifier.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderRequestModifier.h; path = SDWebImage/Core/SDWebImageDownloaderRequestModifier.h; sourceTree = ""; }; D934FF1B0DBCF555CAF470E0CF5AA900 /* GoogleUtilities.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleUtilities.debug.xcconfig; sourceTree = ""; }; D955AAA9A402178AFCBD9F07B289BECB /* FirebaseCoreDiagnostics-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseCoreDiagnostics-dummy.m"; sourceTree = ""; }; DA01E7E21E39AD745F362344A6F1CA3D /* UIImageView+HighlightedWebCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImageView+HighlightedWebCache.h"; path = "SDWebImage/Core/UIImageView+HighlightedWebCache.h"; sourceTree = ""; }; DC55F3B02A125C1D8CBC59176095F043 /* GDTCORPlatform.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORPlatform.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h; sourceTree = ""; }; DC743DAAB18B30FCDB192836851ADE13 /* PromisesObjC.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = PromisesObjC.modulemap; sourceTree = ""; }; DC79300A6D291A17A3804A3511C41D9B /* GULNetworkLoggerProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkLoggerProtocol.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkLoggerProtocol.h; sourceTree = ""; }; DD050D11898BDC1AE3F6FC357A319586 /* FirebaseCoreDiagnostics.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseCoreDiagnostics.release.xcconfig; sourceTree = ""; }; DD42FC19A2CC2BEA3D2DBD52D86D8F36 /* FIRBundleUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRBundleUtil.m; path = FirebaseCore/Sources/FIRBundleUtil.m; sourceTree = ""; }; DD910C5803E3E0E5E17C5ED00AFEEEC0 /* SDImageFrame.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageFrame.h; path = SDWebImage/Core/SDImageFrame.h; sourceTree = ""; }; DDDFE2B6438138D00D92226D62FA76AD /* FIRInstallationsIIDTokenStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRInstallationsIIDTokenStore.m; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.m; sourceTree = ""; }; DED0CE98C3CC1D5B1BEBB3F2409DF55E /* SDAsyncBlockOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAsyncBlockOperation.m; path = SDWebImage/Private/SDAsyncBlockOperation.m; sourceTree = ""; }; DEEA9E90B955F3FF75BD5CC9F8F49886 /* GDTCCTUploader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GDTCCTUploader.m; path = GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m; sourceTree = ""; }; DFF930E1A1EF9818E6781AA056A03AD3 /* SDImageAWebPCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageAWebPCoder.h; path = SDWebImage/Core/SDImageAWebPCoder.h; sourceTree = ""; }; E06211AF2F4E3047043DD4BCDC40403C /* NSBezierPath+SDRoundedCorners.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBezierPath+SDRoundedCorners.h"; path = "SDWebImage/Private/NSBezierPath+SDRoundedCorners.h"; sourceTree = ""; }; E19DD2593A51A0FEDEA7A4688C0D8645 /* GULSceneDelegateSwizzler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULSceneDelegateSwizzler.m; path = GoogleUtilities/AppDelegateSwizzler/GULSceneDelegateSwizzler.m; sourceTree = ""; }; E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; E2A2FCE7B47213F9F0585EA70EA2024A /* UIView+WebCacheOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCacheOperation.h"; path = "SDWebImage/Core/UIView+WebCacheOperation.h"; sourceTree = ""; }; E2A88BFB19C447C4ED3DE4168952FE80 /* SDImageCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageCoder.h; path = SDWebImage/Core/SDImageCoder.h; sourceTree = ""; }; E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FirebaseCore; path = FirebaseCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E30EDBDA563FB09CF5AE6965F2558FE9 /* Pods-Spotify-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Spotify-Info.plist"; sourceTree = ""; }; E43C759DC1C3554D8988244726FFB280 /* FIRInstallationsIIDTokenStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIIDTokenStore.h; path = FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h; sourceTree = ""; }; E666E3C34AF4E53A131BD4AF0F6ABA47 /* FirebaseCore-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseCore-dummy.m"; sourceTree = ""; }; E6921E05E12367D79F30526D90D84BE6 /* SDWebImage-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SDWebImage-prefix.pch"; sourceTree = ""; }; E76B8095179CC1855E5CFA2FF2597A24 /* FBLPromiseError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromiseError.h; path = Sources/FBLPromises/include/FBLPromiseError.h; sourceTree = ""; }; E80E4257A841B7AA5C92B2CB19B16F0B /* fr.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = fr.lproj; sourceTree = ""; }; E9D885B6C37485841C093413F8E4B5CE /* FIRInstallationsItem+RegisterInstallationAPI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FIRInstallationsItem+RegisterInstallationAPI.m"; path = "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.m"; sourceTree = ""; }; E9E0C507183FA6B14B3FED0BB9BFE8E5 /* GDTCORFlatFileStorage+Promises.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GDTCORFlatFileStorage+Promises.h"; path = "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h"; sourceTree = ""; }; EA1C659029357A11E03301278FD8D706 /* SDWebImageCacheSerializer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCacheSerializer.m; path = SDWebImage/Core/SDWebImageCacheSerializer.m; sourceTree = ""; }; EACF04048B0827BDA828E835A7EA14C6 /* SDFileAttributeHelper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDFileAttributeHelper.m; path = SDWebImage/Private/SDFileAttributeHelper.m; sourceTree = ""; }; EB886795A3DF2E63330CE092DB9B5F46 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = SDWebImage/Core/SDAnimatedImageRep.m; sourceTree = ""; }; EC28ED48C1E6F76572193AD374027BB7 /* SDMemoryCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDMemoryCache.m; path = SDWebImage/Core/SDMemoryCache.m; sourceTree = ""; }; EC2AA38D1492EE015AE393D7FCDBCDF1 /* GDTCOREventTransformer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREventTransformer.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h; sourceTree = ""; }; EC47234C0173B2827334091E5C04C152 /* GDTCORUploadBatch.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploadBatch.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h; sourceTree = ""; }; EC847DD51A92AE89F533B4312E344982 /* FBLPromise+Await.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Await.m"; path = "Sources/FBLPromises/FBLPromise+Await.m"; sourceTree = ""; }; EC9689AF5C4E7C5E057221C42B74F092 /* FirebaseCoreInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FirebaseCoreInternal.h; path = FirebaseCore/Sources/Private/FirebaseCoreInternal.h; sourceTree = ""; }; ECF677E408094637A67960F364C6FA0D /* FBLPromise+Do.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Do.h"; path = "Sources/FBLPromises/include/FBLPromise+Do.h"; sourceTree = ""; }; EDF07392617868FE264D5E58788D279F /* FirebaseInstallations-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FirebaseInstallations-dummy.m"; sourceTree = ""; }; EDF0E36BC33BC1C752AA5C64455974C1 /* GULNSData+zlib.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GULNSData+zlib.m"; path = "GoogleUtilities/NSData+zlib/GULNSData+zlib.m"; sourceTree = ""; }; EE06EB2E171945BC2050FB3D4A038D02 /* FIRInstallationsIDController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsIDController.h; path = FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h; sourceTree = ""; }; EE0CECAF5DA5AAE13E1615E7B14147CB /* ar.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = ar.lproj; sourceTree = ""; }; EE1ABBBCF34996AB6FAD3A7099D80CF5 /* FIRDiagnosticsData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRDiagnosticsData.h; path = FirebaseCore/Sources/FIRDiagnosticsData.h; sourceTree = ""; }; EE2390C8AD53927134B524CBC997D50E /* UIView+WebCacheOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCacheOperation.m"; path = "SDWebImage/Core/UIView+WebCacheOperation.m"; sourceTree = ""; }; EF3303042C21F9396439AB3DE71C7BB2 /* GULNetworkURLSession.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULNetworkURLSession.h; path = GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkURLSession.h; sourceTree = ""; }; EF7A55F92FBA10C1CA52F98BF5B091C9 /* es.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = es.lproj; sourceTree = ""; }; EF82E76BE97070A77C122D8CD5A2A852 /* GULReachabilityChecker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULReachabilityChecker.m; path = GoogleUtilities/Reachability/GULReachabilityChecker.m; sourceTree = ""; }; EF8FCF0158C3FC321B0CCC8F87E286F6 /* GDTCOREndpoints_Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCOREndpoints_Private.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCOREndpoints_Private.h; sourceTree = ""; }; EFD6BF26B215DD780EA6145DFEEC525A /* FBLPromise+Race.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Race.m"; path = "Sources/FBLPromises/FBLPromise+Race.m"; sourceTree = ""; }; F03A138FFFD47F81003F3160EC8E9953 /* SDAnimatedImageView+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "SDAnimatedImageView+WebCache.m"; path = "SDWebImage/Core/SDAnimatedImageView+WebCache.m"; sourceTree = ""; }; F0647D92B0FBA5FC4BF6371012FEE181 /* FBLPromise+Testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Testing.h"; path = "Sources/FBLPromises/include/FBLPromise+Testing.h"; sourceTree = ""; }; F0A654F37AEA764934660E26E3BD2475 /* SDImageLoadersManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageLoadersManager.h; path = SDWebImage/Core/SDImageLoadersManager.h; sourceTree = ""; }; F124022073EADCF94FCBED857E5EABBB /* Pods-Spotify-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Spotify-umbrella.h"; sourceTree = ""; }; F1A5B63B766812910C3FC66A4D5BD359 /* FBLPromise+Reduce.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Reduce.h"; path = "Sources/FBLPromises/include/FBLPromise+Reduce.h"; sourceTree = ""; }; F1DFF11C55F9B1D83DF7C7397FDACBA2 /* FIRInstallationsAPIService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallationsAPIService.h; path = FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h; sourceTree = ""; }; F2EC88E51FC34B63E85F84778E9C618A /* SDImageFrame.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageFrame.m; path = SDWebImage/Core/SDImageFrame.m; sourceTree = ""; }; F34C30C677383D5895A28720200014A4 /* FIRFirebaseUserAgent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRFirebaseUserAgent.h; path = FirebaseCore/Sources/FIRFirebaseUserAgent.h; sourceTree = ""; }; F35AA80907A78AC92F34C61F368248E7 /* FBLPromise.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBLPromise.h; path = Sources/FBLPromises/include/FBLPromise.h; sourceTree = ""; }; F36DCFF7B72CE2940864E1364072151A /* GULHeartbeatDateStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULHeartbeatDateStorage.m; path = GoogleUtilities/Environment/GULHeartbeatDateStorage.m; sourceTree = ""; }; F3A8A80738539221543E61697604F331 /* GoogleDataTransport-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleDataTransport-Info.plist"; sourceTree = ""; }; F3DF4F3E573CB800D27E86168DD5EECB /* UIButton+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIButton+WebCache.m"; path = "SDWebImage/Core/UIButton+WebCache.m"; sourceTree = ""; }; F457C5FADED7F6B16F8F8C265309AEA7 /* nanopb-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "nanopb-prefix.pch"; sourceTree = ""; }; F463B32358D0340C851CB2FCB8E3B0A5 /* FIRInstallations.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FIRInstallations.h; path = FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallations.h; sourceTree = ""; }; F4A7F01DAC8F3E158C6CB70DD5E60C24 /* da.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = da.lproj; sourceTree = ""; }; F53274C0B6BDFB6F2402643CF71B2F77 /* SDWebImageDownloaderConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderConfig.h; path = SDWebImage/Core/SDWebImageDownloaderConfig.h; sourceTree = ""; }; F538296E4FA1BF861A19524F47E7A6D5 /* SDImageLoader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageLoader.h; path = SDWebImage/Core/SDImageLoader.h; sourceTree = ""; }; F582A3CDAC1DDA3FCEA8CB2C13293A4D /* Firebase.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Firebase.release.xcconfig; sourceTree = ""; }; F589BA173CD44451894C642200BAD5A9 /* GDTCORTargets.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTargets.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h; sourceTree = ""; }; F59DA2817374C9606A05E0D57A6993AB /* SDImageAWebPCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageAWebPCoder.m; path = SDWebImage/Core/SDImageAWebPCoder.m; sourceTree = ""; }; F6FF94050EDDF80FD2501F4E30F18E4D /* GDTCORUploadCoordinator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORUploadCoordinator.h; path = GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h; sourceTree = ""; }; F80FBEC0B534E33030C9ECCABDD9BE62 /* GULKeychainUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GULKeychainUtils.m; path = GoogleUtilities/Environment/SecureStorage/GULKeychainUtils.m; sourceTree = ""; }; F848EBF5A77518F1437E59128B898ACE /* SDWebImageOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOperation.m; path = SDWebImage/Core/SDWebImageOperation.m; sourceTree = ""; }; F917DFD015523A1E48D920C0FAC03683 /* nanopb.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = nanopb.modulemap; sourceTree = ""; }; F9DD6F50E5F16B3EE2AFBBBA48053FA5 /* SDWebImageCacheKeyFilter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCacheKeyFilter.m; path = SDWebImage/Core/SDWebImageCacheKeyFilter.m; sourceTree = ""; }; FA50FFBEC226EAB798930460C18C4350 /* SDWebImage.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SDWebImage.modulemap; sourceTree = ""; }; FAE1DA94ED4CE423E2B4A57176826E98 /* FirebaseAnalytics.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FirebaseAnalytics.release.xcconfig; sourceTree = ""; }; FB7600C02A26CBAC1E67CFF0278A198D /* SDImageGIFCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageGIFCoder.h; path = SDWebImage/Core/SDImageGIFCoder.h; sourceTree = ""; }; FB9896C32514099F18D7AA422602FB04 /* FBLPromise+Always.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "FBLPromise+Always.h"; path = "Sources/FBLPromises/include/FBLPromise+Always.h"; sourceTree = ""; }; FC10065E60D607F83A9C96DF9332A2FD /* FirebaseAnalytics-xcframeworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "FirebaseAnalytics-xcframeworks.sh"; sourceTree = ""; }; FC22CB2FD3FE9BC3D607B5CA39155B83 /* GULReachabilityChecker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULReachabilityChecker.h; path = GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h; sourceTree = ""; }; FC23427538EE1DBB11F5EBBAAB6A513D /* SDDeviceHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDDeviceHelper.h; path = SDWebImage/Private/SDDeviceHelper.h; sourceTree = ""; }; FC3C259DB11E51E08F2D5630A5145839 /* pt.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = pt.lproj; sourceTree = ""; }; FC6A0016A0D695FE4F0D2BCB7B58E53B /* SDWebImage.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SDWebImage.release.xcconfig; sourceTree = ""; }; FCA00A91EC23FFE0892B9D0F0B84F617 /* FBLPromiseError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBLPromiseError.m; path = Sources/FBLPromises/FBLPromiseError.m; sourceTree = ""; }; FDCDC0DC5EBD4242B3B44E167355E955 /* UIImageView+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+WebCache.m"; path = "SDWebImage/Core/UIImageView+WebCache.m"; sourceTree = ""; }; FDF404E683FC3DE61DCE91FF8B1E0842 /* pb_encode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = pb_encode.h; sourceTree = ""; }; FDF958DE0F2E4C471517C45865738FDF /* FirebaseCore.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FirebaseCore.modulemap; sourceTree = ""; }; FE8190044D8F103421DED7166A530383 /* GDTCCTNanopbHelpers.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCCTNanopbHelpers.h; path = GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h; sourceTree = ""; }; FEA2BFC7C6DBDD183F3EC0220976D86F /* FIRDiagnosticsData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FIRDiagnosticsData.m; path = FirebaseCore/Sources/FIRDiagnosticsData.m; sourceTree = ""; }; FEE086E4BCB7B965DFED58170011ADD2 /* uk.lproj */ = {isa = PBXFileReference; includeInIndex = 1; path = uk.lproj; sourceTree = ""; }; FEE20DA431EFAB6E9DE3F14B005CCF75 /* GULReachabilityMessageCode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GULReachabilityMessageCode.h; path = GoogleUtilities/Reachability/GULReachabilityMessageCode.h; sourceTree = ""; }; FEE29B50BED2D3B4F195FBAF219817D9 /* SDFileAttributeHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDFileAttributeHelper.h; path = SDWebImage/Private/SDFileAttributeHelper.h; sourceTree = ""; }; FF32D6A22D56F5787F289A4BEA15E6A1 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = SDWebImage/Core/SDWebImageOptionsProcessor.h; sourceTree = ""; }; FF3EF4B23861192F1BF203B7E288A145 /* SDImageCacheDefine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCacheDefine.m; path = SDWebImage/Core/SDImageCacheDefine.m; sourceTree = ""; }; FF60EBD5E154FA9A791F0C421B4D9AEE /* FBLPromise+Reduce.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "FBLPromise+Reduce.m"; path = "Sources/FBLPromises/FBLPromise+Reduce.m"; sourceTree = ""; }; FF8CD99087C3A386C87B8B64F6814912 /* nanopb-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "nanopb-dummy.m"; sourceTree = ""; }; FFBEACDBC00E47FDCE130333C01BDD52 /* GDTCORStorageEventSelector.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORStorageEventSelector.h; path = GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h; sourceTree = ""; }; FFC85887576AED4EB7D6127CC1FDC955 /* GDTCORTransport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GDTCORTransport.h; path = GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2F90AC4B1394693187B33E9454925C26 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 557258F7F82A8926BA01926A0CB40E15 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F1F24427266F56690C90DBC258A8E712 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 751E8AA0AFD15DFD93F5C1D10458E77C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2DD7B33B4FB4F06F6BDB9FDE23463CCC /* CFNetwork.framework in Frameworks */, B5C56C4D45E7E7A9630F9417A6E39C1D /* Foundation.framework in Frameworks */, 7061D35639F93885B8A80B3B7FE090F7 /* SystemConfiguration.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; A9D2F93060742D670740AA52D4F1A7BE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 84B818EEE3E3EE948F29B61A752BFC0B /* Foundation.framework in Frameworks */, 0D4FBF072F6431A5D5324C83FC2E5DE7 /* Security.framework in Frameworks */, 6503E1414B68019CD76455273240A263 /* SystemConfiguration.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; ADD00E3E8A0426067D803A942C3BA84C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 14B49802A4BD5C9EB648342B539ACC0E /* Foundation.framework in Frameworks */, 2E30ED9EE4AA861FAB2BD7E875BFFCC2 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; D7D014542D95DD38C8C2502010D4CD43 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CEBCB78AD6ADE2D0F18F7D9E07D5C4DB /* Foundation.framework in Frameworks */, AC551E9186602377EC1751E316CDCEC2 /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; D8CEF8654739A822265F965518353CC2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( AC76D5E67DCEFB22BE84148317D7B5A5 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; DA11FA7A020AFDCA73B93EDF3D3F1073 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CF4DAD38BC9565548EC08D14D2B6541C /* CoreTelephony.framework in Frameworks */, 940AEABD2B727E9478DD58CB3EF383F6 /* Foundation.framework in Frameworks */, 7100C2CFC22D1689ADA78A36E65B697B /* SystemConfiguration.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F0861647ADFD6A6E877396A52F43B90A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 714EB6036BF393E60ADE1F6F2C7E8A1F /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F9DE14B7D99DBDE04A0AC39B6C4F83E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B519045D5C102170273EDD0B9D141F94 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; FB6A1E8A43CF1D3ADFB49BA355F0DEA1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 5A3E5B854E8188DDF4E958BBC9198600 /* Foundation.framework in Frameworks */, 51F891EA712D8F14CF1DD77BF20B62A9 /* ImageIO.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 02D4B1661A567187466821708E44DE0A /* CoreOnly */ = { isa = PBXGroup; children = ( 5415E857C6437079EA6CE8D49BC4D00F /* Firebase.h */, ); name = CoreOnly; sourceTree = ""; }; 058C19C88B10171A17924987A122D927 /* Frameworks */ = { isa = PBXGroup; children = ( 32B0EB1571B6CA660B7AD773851B90C5 /* FirebaseAnalytics.xcframework */, ); name = Frameworks; sourceTree = ""; }; 0C6CEC6D687B3736917288BC43D2B350 /* AppDelegateSwizzler */ = { isa = PBXGroup; children = ( 9C35CA5CB6C0053B485FCFDFD30A1662 /* GULAppDelegateSwizzler.h */, 16A5A619D3393C3652027A63F8D6406E /* GULAppDelegateSwizzler.m */, 5C5DD83B8521D66EB85A5A45A92F810F /* GULAppDelegateSwizzler_Private.h */, B134BD979A35DB81CA4CBD64BE86AAC5 /* GULApplication.h */, 08586FD84613D5B8D0037AF3714A8D9F /* GULLoggerCodes.h */, D327D14764B30DDEEB17666FA535871D /* GULSceneDelegateSwizzler.h */, E19DD2593A51A0FEDEA7A4688C0D8645 /* GULSceneDelegateSwizzler.m */, 56EBF81C7BB707870CF8F18F19C219BC /* GULSceneDelegateSwizzler_Private.h */, ); name = AppDelegateSwizzler; sourceTree = ""; }; 16EF75AC256F0C94C6273A51CFE74355 /* Targets Support Files */ = { isa = PBXGroup; children = ( B7C5491CCF4E11B34D2C388422157234 /* Pods-Spotify */, ); name = "Targets Support Files"; sourceTree = ""; }; 1AE034B84201B1046BCCE51577E15354 /* FirebaseCore */ = { isa = PBXGroup; children = ( 668CE7FB58FBCA03E7BF9FFA7D4AEA8C /* FIRAnalyticsConfiguration.h */, 2B055BE0C98324B8E1EE0B588C14B23C /* FIRAnalyticsConfiguration.m */, C2B6C797B8E1770AE7FB92B865BB499B /* FIRApp.h */, 3C2FEB053783A6171CE3DECCFF936A28 /* FIRApp.m */, 5CBCB878C4026B383CA6687B4DC52D58 /* FIRAppAssociationRegistration.h */, A0F0A5A8418FA40166272625AFB5330A /* FIRAppAssociationRegistration.m */, 48972591E3AF0F13726EC5D4C6BEDFDC /* FIRAppInternal.h */, 303FE2537CE3769B58923B71632FED1C /* FIRBundleUtil.h */, DD42FC19A2CC2BEA3D2DBD52D86D8F36 /* FIRBundleUtil.m */, 92EA46401122BB560F222964810DAE14 /* FIRComponent.h */, 00287C1BECC76217689476B210B41A36 /* FIRComponent.m */, 82AAE98FB8E850A8ECBA43F727AD3F4B /* FIRComponentContainer.h */, AE8073B498E3701A492F026966747912 /* FIRComponentContainer.m */, C2EDD3FE368044C57FFE267748AD31CE /* FIRComponentContainerInternal.h */, B3310E7F0BD29950EA79269F8864E7B0 /* FIRComponentType.h */, 1B185568A19C67E5278D6DB507473C2B /* FIRComponentType.m */, A8689F656BCD2DE359141CDE820E425F /* FIRConfiguration.h */, A3142D0BC0A7099D82BA56A3FD637656 /* FIRConfiguration.m */, 7055DAE8B926D478FFCB05DC46E7F844 /* FIRConfigurationInternal.h */, 1DFB6F1D058B390F2A1340D6D90391C0 /* FIRCoreDiagnosticsConnector.h */, 8E7ED49344AE0AC6AD92262E047201BD /* FIRCoreDiagnosticsConnector.m */, A7D509EE7C37DC62C5F5119B2692E792 /* FIRCoreDiagnosticsData.h */, D605D91349EBFFC6D84A254293FFF56F /* FIRCoreDiagnosticsInterop.h */, B3DC2C81260A550A30B6C664556BAD8A /* FIRDependency.h */, CC9B5348D8E36B88FD0E3329BCF4479D /* FIRDependency.m */, EE1ABBBCF34996AB6FAD3A7099D80CF5 /* FIRDiagnosticsData.h */, FEA2BFC7C6DBDD183F3EC0220976D86F /* FIRDiagnosticsData.m */, 3BB2E5C745E6BEBDF556017B5389A3F8 /* FirebaseCore.h */, EC9689AF5C4E7C5E057221C42B74F092 /* FirebaseCoreInternal.h */, F34C30C677383D5895A28720200014A4 /* FIRFirebaseUserAgent.h */, 893980C67675F297617FB935213E4671 /* FIRFirebaseUserAgent.m */, 5E16667A1668B640A863FF171D2CB5FB /* FIRHeartbeatInfo.h */, 8454F0C4E786C6A2499ED8DA5879A808 /* FIRHeartbeatInfo.m */, C76E61D46C6D64C4743B6A7256A286EF /* FIRLibrary.h */, 4953F3D24A404D00A99D8A29BE61104C /* FIRLogger.h */, 0720E29DA86F4D52C08D6942FFEF190A /* FIRLogger.m */, A4A6DED8C7023B5E793A748ED43129EE /* FIRLoggerLevel.h */, 1BF0F7EE147A98C2F11F7C4C32C5B6E3 /* FIROptions.h */, 9BA476F642687D5FC7AA27BDDA27DEBC /* FIROptions.m */, 0323C2AB5A72DF893829A51D8F181F49 /* FIROptionsInternal.h */, 38A7BC4B65A58A155D087DA3EFFC1D79 /* FIRVersion.h */, 724419369F9B475400E63E05193D0394 /* FIRVersion.m */, 54C7F450DCD0A9B6831BDE3285A54B1F /* Support Files */, ); name = FirebaseCore; path = FirebaseCore; sourceTree = ""; }; 1AFCC02FC929E5DD450B688E200653B7 /* Support Files */ = { isa = PBXGroup; children = ( DC743DAAB18B30FCDB192836851ADE13 /* PromisesObjC.modulemap */, 36C228F973FB461AAC3DE1B3CAC5533C /* PromisesObjC-dummy.m */, 22B79826946DBE0D731ED6479069F8C1 /* PromisesObjC-Info.plist */, 7BF54C90D1DF4DD3950B8DCD5C09D18C /* PromisesObjC-umbrella.h */, C5674D03956C9BD31033CABEEFE4F2E1 /* PromisesObjC.debug.xcconfig */, B526E38BA63C966AF02EA09F26802535 /* PromisesObjC.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/PromisesObjC"; sourceTree = ""; }; 1FC49855849F77DD042774C08FB9A2BD /* Products */ = { isa = PBXGroup; children = ( 23D4D9F87518207E73E88D8FBC7C8DE3 /* Appirater */, 1E9CD753F6BD26760EFAA1DF57482D50 /* Appirater-Appirater */, E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore */, 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics */, 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations */, 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport */, B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities */, 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb */, 97DFD7435A40FB6C7611C58681006DC8 /* Pods-Spotify */, 3347A1AB6546F0A3977529B8F199DC41 /* PromisesObjC */, B0B214D775196BA7CA8E17E53048A493 /* SDWebImage */, ); name = Products; sourceTree = ""; }; 2152BB9B7EF0C69F7B86DE1EF926D67F /* Firebase */ = { isa = PBXGroup; children = ( 02D4B1661A567187466821708E44DE0A /* CoreOnly */, EF5B961421EC2787E78401B772CC91D5 /* Support Files */, ); name = Firebase; path = Firebase; sourceTree = ""; }; 278C4253D3F115DE5037DF8D472B252D /* AdIdSupport */ = { isa = PBXGroup; children = ( AC1136BE3B047EDE3C63B82F0FADFAAC /* Frameworks */, ); name = AdIdSupport; sourceTree = ""; }; 3082F193C8D1BA24D51A7BC01CEBD841 /* SDWebImage */ = { isa = PBXGroup; children = ( 6FEF14F9A1B0B218432F7694289CDFF5 /* Core */, 82ABAFE15F86B5F2F88A2DAA6D6FF5C9 /* Support Files */, ); name = SDWebImage; path = SDWebImage; sourceTree = ""; }; 3DA5ECD28DB682EEB7CE60E41A95AC39 /* MethodSwizzler */ = { isa = PBXGroup; children = ( C2439987FF247C8C084181EBCF11641F /* GULOriginalIMPConvenienceMacros.h */, 6C61DBDC1219C0AC0AD0FF84A0133692 /* GULSwizzler.h */, 75E9E1284F8AE50551FF56600DDDCEBF /* GULSwizzler.m */, ); name = MethodSwizzler; sourceTree = ""; }; 48EA86F33969C2420662B0A62E5A9FCE /* Support Files */ = { isa = PBXGroup; children = ( F917DFD015523A1E48D920C0FAC03683 /* nanopb.modulemap */, FF8CD99087C3A386C87B8B64F6814912 /* nanopb-dummy.m */, 061F4EB74D749A7F360F62B20F46294A /* nanopb-Info.plist */, F457C5FADED7F6B16F8F8C265309AEA7 /* nanopb-prefix.pch */, 29AE365492E6E96E5708A9AD6E31746F /* nanopb-umbrella.h */, 83D038C4E24C71BFAE211A6E0CDA58BA /* nanopb.debug.xcconfig */, C5E56AF681704D05296BF6544BFD6DBC /* nanopb.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/nanopb"; sourceTree = ""; }; 4DEE105FF0FD390E62D91CCF24486B2A /* Support Files */ = { isa = PBXGroup; children = ( FC10065E60D607F83A9C96DF9332A2FD /* FirebaseAnalytics-xcframeworks.sh */, 04119D68F2961D5912F24F5066E00D0D /* FirebaseAnalytics.debug.xcconfig */, FAE1DA94ED4CE423E2B4A57176826E98 /* FirebaseAnalytics.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/FirebaseAnalytics"; sourceTree = ""; }; 54C7F450DCD0A9B6831BDE3285A54B1F /* Support Files */ = { isa = PBXGroup; children = ( FDF958DE0F2E4C471517C45865738FDF /* FirebaseCore.modulemap */, E666E3C34AF4E53A131BD4AF0F6ABA47 /* FirebaseCore-dummy.m */, 859E0D4CB486D5A4710D605554199885 /* FirebaseCore-Info.plist */, 63FB1BA9182BB6D6E47E4DD049005A29 /* FirebaseCore-umbrella.h */, ACE81B64A65F8AF15E7C822CDF6A73E6 /* FirebaseCore.debug.xcconfig */, 2368DD7767138A73CD7D9EADED904481 /* FirebaseCore.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/FirebaseCore"; sourceTree = ""; }; 5544FD9AFB77FFE3A00CB2505C4726A3 /* UserDefaults */ = { isa = PBXGroup; children = ( D69D4E5A3DF64985BBF9D3080CCD6144 /* GULUserDefaults.h */, CF805F43FFDD645D95C1ACEABC114049 /* GULUserDefaults.m */, ); name = UserDefaults; sourceTree = ""; }; 5633E1A74655186D803F0C91BD6B16C0 /* NSData+zlib */ = { isa = PBXGroup; children = ( BD01D4D2C9BD8D230AC07E17D1BC2580 /* GULNSData+zlib.h */, EDF0E36BC33BC1C752AA5C64455974C1 /* GULNSData+zlib.m */, ); name = "NSData+zlib"; sourceTree = ""; }; 5D0B7910C5928FF0F06049E99FAACD41 /* Support Files */ = { isa = PBXGroup; children = ( AE780E851F5F67AAA9C4AE38402A4086 /* GoogleDataTransport.modulemap */, 932BDDF177FD04B28E9285096E7D291C /* GoogleDataTransport-dummy.m */, F3A8A80738539221543E61697604F331 /* GoogleDataTransport-Info.plist */, 881ACDC9E0325D12479F43AD079D1CBB /* GoogleDataTransport-umbrella.h */, BA393B4B24C719DDDC878C0A08B2D1BB /* GoogleDataTransport.debug.xcconfig */, BE37397A0A9B84C77A7B34A9406A3952 /* GoogleDataTransport.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/GoogleDataTransport"; sourceTree = ""; }; 62A2F567C67C028B3713BC180E3A5B8C /* FirebaseInstallations */ = { isa = PBXGroup; children = ( 66B0B7773B93F50516AFA7A4AD80001F /* FIRAppInternal.h */, D71B48D7039219AEB311FB0DA2E1E389 /* FIRComponent.h */, 6BA2A90BF46E4D5796820E1A900E2C0B /* FIRComponentContainer.h */, 5514ED015845BE4DF10DF85BF4983A59 /* FIRComponentType.h */, 747BA4839026A5B48F78F7F2F59B01D8 /* FIRCoreDiagnosticsConnector.h */, B349AD374EBA10C2D49FC4BA8A53F0CF /* FIRCurrentDateProvider.h */, B17DB1B80794FEE3D7DC12CB3641AB30 /* FIRCurrentDateProvider.m */, C887379F2FAEEA195D743E2A7E8695B2 /* FIRDependency.h */, 45405CBF1024B18B4589ED32B6B9D343 /* FirebaseCoreInternal.h */, 183B8EEF2BA8C735371935889864D6B7 /* FirebaseInstallations.h */, 3EAF64B24EEF15C83158B0A1B832978D /* FirebaseInstallationsInternal.h */, 8CDC11191CD5FAC6E9AA96E2BC7DAD05 /* FIRHeartbeatInfo.h */, F463B32358D0340C851CB2FCB8E3B0A5 /* FIRInstallations.h */, 07E9568C4EE8BF77740269310C486888 /* FIRInstallations.m */, F1DFF11C55F9B1D83DF7C7397FDACBA2 /* FIRInstallationsAPIService.h */, 6A35F8BE947883A35C48CDFFF5BACB72 /* FIRInstallationsAPIService.m */, D8ADAB8E982AFC5F2BD545EB091C78BD /* FIRInstallationsAuthTokenResult.h */, D54DA10983EF09ABC480187282A87A58 /* FIRInstallationsAuthTokenResult.m */, 1AA30DDE545C3AB8E5A8BCD08CA5B923 /* FIRInstallationsAuthTokenResultInternal.h */, 382D07A53D722649DCAD5C5BC3BDCAC1 /* FIRInstallationsBackoffController.h */, 6E03F27C2DCF8DB117CBC29C875DCD95 /* FIRInstallationsBackoffController.m */, 46849267116D15C2F916F95AF7ED6264 /* FIRInstallationsErrors.h */, 6E7EB7DF29D03415C41C56734E0C9017 /* FIRInstallationsErrorUtil.h */, 6237C8DA5406A972EAFEEC2CB46AA910 /* FIRInstallationsErrorUtil.m */, 0E3D719558B6C623CB196272CE80D92B /* FIRInstallationsHTTPError.h */, B5E3DBB2F100E0A299BCFC77EBC69A7E /* FIRInstallationsHTTPError.m */, EE06EB2E171945BC2050FB3D4A038D02 /* FIRInstallationsIDController.h */, 2DDBED0511F929D50127372614C07E38 /* FIRInstallationsIDController.m */, 703B93588CDC01E3116988AAD425A74D /* FIRInstallationsIIDStore.h */, D5FB0717B52FE2A1C59CA75D8CE483EA /* FIRInstallationsIIDStore.m */, E43C759DC1C3554D8988244726FFB280 /* FIRInstallationsIIDTokenStore.h */, DDDFE2B6438138D00D92226D62FA76AD /* FIRInstallationsIIDTokenStore.m */, D29D1ACD3388DF30F5213782D0089AAE /* FIRInstallationsItem.h */, 9E08253131AF6EBD5597DF21EBF9B98D /* FIRInstallationsItem.m */, BAB945382DE7BCE1238478DF4341D1C3 /* FIRInstallationsItem+RegisterInstallationAPI.h */, E9D885B6C37485841C093413F8E4B5CE /* FIRInstallationsItem+RegisterInstallationAPI.m */, 99108619FD685C7E04AA2798D8184E88 /* FIRInstallationsLogger.h */, CAFA66ED08EE2214A12519DB72F11801 /* FIRInstallationsLogger.m */, A55020CA6EDE9396075075D402E84AEC /* FIRInstallationsSingleOperationPromiseCache.h */, 7387A62B07188200FF8487FD3A2CF2A5 /* FIRInstallationsSingleOperationPromiseCache.m */, 33282DA51418F8E945CDA2BC7DDA734E /* FIRInstallationsStatus.h */, B46C4D5C2FF1FB2417D06ADB91D1CEBA /* FIRInstallationsStore.h */, A328CD57DBA12543441049EBF409F205 /* FIRInstallationsStore.m */, 4187F6DA61736F8AD4201616D23D8061 /* FIRInstallationsStoredAuthToken.h */, 6F25465DB16E55EFF41F24C620E24E05 /* FIRInstallationsStoredAuthToken.m */, 25D0D7E32DDD7AE56C23799445C13A57 /* FIRInstallationsStoredItem.h */, 9484BA52176E0E66CBDC23122D2948A2 /* FIRInstallationsStoredItem.m */, BE1CFCA4C81C90732F8DB42EA1F017A3 /* FIRLibrary.h */, C18C4496C07F34201871F99AD27069A0 /* FIRLogger.h */, 15F02952828900F1CC5E66B83274F6E0 /* FIROptionsInternal.h */, BA6859418815F5231E3CEAAA21D0F012 /* Support Files */, ); name = FirebaseInstallations; path = FirebaseInstallations; sourceTree = ""; }; 6E122AC728989052F6E7D19E4F6218E6 /* Logger */ = { isa = PBXGroup; children = ( 32B1014C844F5619D536DFD1800B11D5 /* GULLogger.h */, 4468F16CABC244B58ACDC50E87F8B3C4 /* GULLogger.m */, 82EAA36E76AE8F4E664A68EC8EB36298 /* GULLoggerLevel.h */, ); name = Logger; sourceTree = ""; }; 6EF332B85D74AC3B2E0615B4F1FB4DC5 /* Support Files */ = { isa = PBXGroup; children = ( D368B1A08214DF1EF6A0A9F4860161C0 /* FirebaseCoreDiagnostics.modulemap */, D955AAA9A402178AFCBD9F07B289BECB /* FirebaseCoreDiagnostics-dummy.m */, 073BC434BDEB48DDF3B92DF041D277CE /* FirebaseCoreDiagnostics-Info.plist */, 34A40FBEB8C0F4629E017EA065211E5C /* FirebaseCoreDiagnostics-umbrella.h */, C52C466DF84E99205E54E0AEC7DF14B2 /* FirebaseCoreDiagnostics.debug.xcconfig */, DD050D11898BDC1AE3F6FC357A319586 /* FirebaseCoreDiagnostics.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/FirebaseCoreDiagnostics"; sourceTree = ""; }; 6FEF14F9A1B0B218432F7694289CDFF5 /* Core */ = { isa = PBXGroup; children = ( E06211AF2F4E3047043DD4BCDC40403C /* NSBezierPath+SDRoundedCorners.h */, 8DFCA2753EDD74C8B7D46CD39BC4F756 /* NSBezierPath+SDRoundedCorners.m */, BA37D2C1168173A7796B05F93479A231 /* NSButton+WebCache.h */, CE0BC2BA98097A46FED66139FADDB5C2 /* NSButton+WebCache.m */, 31527FC5A967FDE9F076D544F4923295 /* NSData+ImageContentType.h */, 2D682D19C50EF105FF8EF05D677CA964 /* NSData+ImageContentType.m */, 2264D3828F48F1D2D87C73BE181475A2 /* NSImage+Compatibility.h */, 8D2E7ABE07934F659148C41C852087FD /* NSImage+Compatibility.m */, 615A6102144F4931AF689AB96C01D719 /* SDAnimatedImage.h */, D75DB99AD94B959D922B1BF144AEFE15 /* SDAnimatedImage.m */, 3FA3607B121AEFB0595C76441513C64D /* SDAnimatedImagePlayer.h */, 17A959EECDBC8B53DAF62371054DAAE9 /* SDAnimatedImagePlayer.m */, B95FF8BC5E40163C886E3DB1B394DD75 /* SDAnimatedImageRep.h */, EB886795A3DF2E63330CE092DB9B5F46 /* SDAnimatedImageRep.m */, 3808D78CAA3EAA14B869A1667210193C /* SDAnimatedImageView.h */, B4A0C1F3F59FF0ECC737E3A22B64F7F0 /* SDAnimatedImageView.m */, CF04F211D2AE46EEBF166D2CC374B517 /* SDAnimatedImageView+WebCache.h */, F03A138FFFD47F81003F3160EC8E9953 /* SDAnimatedImageView+WebCache.m */, C6A1BDEE41C3F181B6BAC16ADB84601E /* SDAssociatedObject.h */, 80FE85865E814FFFE9FC4BD1E92FF02B /* SDAssociatedObject.m */, 0440093C278700F6A5362B45AD5DBC91 /* SDAsyncBlockOperation.h */, DED0CE98C3CC1D5B1BEBB3F2409DF55E /* SDAsyncBlockOperation.m */, FC23427538EE1DBB11F5EBBAAB6A513D /* SDDeviceHelper.h */, 32DB218DD71AD0570B0E339E5319F93B /* SDDeviceHelper.m */, 25E74C0A2D416186897FBBC33BC5608B /* SDDiskCache.h */, 9B960865AD112FB963F482E45E567B56 /* SDDiskCache.m */, 7CE88DC6249A53E194E886FD268B4F22 /* SDDisplayLink.h */, 33802E944806D1B99AD085FE7F697A74 /* SDDisplayLink.m */, FEE29B50BED2D3B4F195FBAF219817D9 /* SDFileAttributeHelper.h */, EACF04048B0827BDA828E835A7EA14C6 /* SDFileAttributeHelper.m */, 2B207171596C8E59567CB49A2BC0B7C9 /* SDGraphicsImageRenderer.h */, 0FC4393F5EB9D10CAC25D438B9ECB2A3 /* SDGraphicsImageRenderer.m */, B1C3F5E1F2DA4D224862FC3F811D0917 /* SDImageAPNGCoder.h */, 37B1C403940FAFD0BDA8092C0726458A /* SDImageAPNGCoder.m */, BB1D1192CEC6C26D481BAB47C75BCA56 /* SDImageAssetManager.h */, 4D7D4D1C90894E8A533206E5679B1871 /* SDImageAssetManager.m */, DFF930E1A1EF9818E6781AA056A03AD3 /* SDImageAWebPCoder.h */, F59DA2817374C9606A05E0D57A6993AB /* SDImageAWebPCoder.m */, B66ADF3FBC020B8BAE5E1C0C6EA3C650 /* SDImageCache.h */, 4DEBF0EE7331EEAD283D9D6370A2B3FF /* SDImageCache.m */, B83BA9762F2888371B5ADE95B066DCB4 /* SDImageCacheConfig.h */, 7D8A688C63FDD5533A68B0CD80B0501E /* SDImageCacheConfig.m */, 5852FC7ED30AAEEA07547AF6D71F3D85 /* SDImageCacheDefine.h */, FF3EF4B23861192F1BF203B7E288A145 /* SDImageCacheDefine.m */, 1AA6F310E602B6F29B722C01961258A4 /* SDImageCachesManager.h */, BFEAB29224248CA276353656139D1668 /* SDImageCachesManager.m */, 98368FFE6F3A2C1CB4B79C360D245377 /* SDImageCachesManagerOperation.h */, 99AC313325A72709F61AD9B82245FC4F /* SDImageCachesManagerOperation.m */, E2A88BFB19C447C4ED3DE4168952FE80 /* SDImageCoder.h */, 4D8AA9949B871C5C08476B12041D2D99 /* SDImageCoder.m */, 7A10001AFFFB54BB477050134573A9C4 /* SDImageCoderHelper.h */, 8E3D624D33EC772ADA4910D9A6A067AD /* SDImageCoderHelper.m */, 2A1C27AE73C820EC4ACBF12FAEEA1D93 /* SDImageCodersManager.h */, C87340AB40E7872EBB32D3E37F151B98 /* SDImageCodersManager.m */, DD910C5803E3E0E5E17C5ED00AFEEEC0 /* SDImageFrame.h */, F2EC88E51FC34B63E85F84778E9C618A /* SDImageFrame.m */, FB7600C02A26CBAC1E67CFF0278A198D /* SDImageGIFCoder.h */, B6A08E238C0EA041AF882D5F75E6C3C9 /* SDImageGIFCoder.m */, B195FEEB2B6FD768A1270ABE2938AA39 /* SDImageGraphics.h */, 8F2CD38DB179EE57D03D1E4AE0BBA047 /* SDImageGraphics.m */, 4600B5D11E621BE294D5D7A8106BF312 /* SDImageHEICCoder.h */, 483340B3D5102879F6C45F55BD02FF1E /* SDImageHEICCoder.m */, BB81C8F74DB13FEF4D842A7A4127FEBF /* SDImageIOAnimatedCoder.h */, A9A5B5BEF1CFAFF4AEB69AFD4C20968A /* SDImageIOAnimatedCoder.m */, A243783F2DB10CD105EA1B585E02DF32 /* SDImageIOAnimatedCoderInternal.h */, 1416C43E8091C630E21516532F691796 /* SDImageIOCoder.h */, 5DD11686FB044C460680737D6E66EC0C /* SDImageIOCoder.m */, F538296E4FA1BF861A19524F47E7A6D5 /* SDImageLoader.h */, B3522FD698E8EFD6BDE90D04D1ABC13D /* SDImageLoader.m */, F0A654F37AEA764934660E26E3BD2475 /* SDImageLoadersManager.h */, 5304860EBF8FA61DE4A892566862746D /* SDImageLoadersManager.m */, 0F824E440C097E5A5FE69CFBC7293F15 /* SDImageTransformer.h */, 841780DAC592F44AB2E8D35D36E32285 /* SDImageTransformer.m */, 804DFCC909792374DF921D69BE1F1C18 /* SDInternalMacros.h */, C48BA4C3D0C62019243E819DD277C007 /* SDInternalMacros.m */, 0B722DB0B3C51C3645037E3399784B69 /* SDMemoryCache.h */, EC28ED48C1E6F76572193AD374027BB7 /* SDMemoryCache.m */, 579C123C87D8545B91CD64649918A148 /* SDmetamacros.h */, 415A874BEFEAB372A795B3F77C86139D /* SDWeakProxy.h */, CEB675D419ACF104AD5C8F2619C4FCF5 /* SDWeakProxy.m */, 62E087DDBCC7CE3CA61BACD11EE3931E /* SDWebImage.h */, 296AEDDAB76C6DA79B00DFB17FE94B13 /* SDWebImageCacheKeyFilter.h */, F9DD6F50E5F16B3EE2AFBBBA48053FA5 /* SDWebImageCacheKeyFilter.m */, 57A489F83B821E73C9F898D59FD839D5 /* SDWebImageCacheSerializer.h */, EA1C659029357A11E03301278FD8D706 /* SDWebImageCacheSerializer.m */, 83495546D2D6077CD99D793135E8A7EC /* SDWebImageCompat.h */, D6368995E44A12ED775E22745EE36796 /* SDWebImageCompat.m */, 91D7D93CD15EE8BBB672073A9EE9172E /* SDWebImageDefine.h */, 2F4695D22C004206B1EF601650EB58F1 /* SDWebImageDefine.m */, 370CEA1E37CB5E0D01FDBFF39E1020BE /* SDWebImageDownloader.h */, 18281399F797A6B19033D53D2B0992E3 /* SDWebImageDownloader.m */, F53274C0B6BDFB6F2402643CF71B2F77 /* SDWebImageDownloaderConfig.h */, ACAACBFBB72CC5DBA48E5939C7C05CBB /* SDWebImageDownloaderConfig.m */, 65F9EFAA484830CADC6FE4DE20867D8D /* SDWebImageDownloaderDecryptor.h */, 68332238690458AE7A254B6097B8BD22 /* SDWebImageDownloaderDecryptor.m */, 22CACEEBCAA12953A3BA4DB2820EB06A /* SDWebImageDownloaderOperation.h */, 1925BCD73D2687C697D5448145F92862 /* SDWebImageDownloaderOperation.m */, D9304F4CFD273BA0BC3D8FD8EA8CE973 /* SDWebImageDownloaderRequestModifier.h */, D92B4CC4E2762BD017DDAA71533D1487 /* SDWebImageDownloaderRequestModifier.m */, 233FC59CE7BD0ABCB6EFD98AF26F4228 /* SDWebImageDownloaderResponseModifier.h */, 25BB086F66AE0B62674631094FA38DB9 /* SDWebImageDownloaderResponseModifier.m */, C41FA4729FC85108299943DF13BE21D7 /* SDWebImageError.h */, 079B6435FC4625DBC4A439EDF7FD5844 /* SDWebImageError.m */, 43363A5F804039F569FD9ABD9433F07B /* SDWebImageIndicator.h */, A4FB500E6FBEE5F4910B4E4F3894E917 /* SDWebImageIndicator.m */, 8BD491CEBE34A4FF9B067D4431A9525D /* SDWebImageManager.h */, 1AA24773B096CF90111EA4F2AA2C464D /* SDWebImageManager.m */, 73F76A840EAA1BA85CD5CE2578134A93 /* SDWebImageOperation.h */, F848EBF5A77518F1437E59128B898ACE /* SDWebImageOperation.m */, FF32D6A22D56F5787F289A4BEA15E6A1 /* SDWebImageOptionsProcessor.h */, 0BBCA689ED4AD568D1203A32DDCB2654 /* SDWebImageOptionsProcessor.m */, B12CEE2C03EFBD7EBAF1717349B42067 /* SDWebImagePrefetcher.h */, 2E7185858699874B61C27769747820E1 /* SDWebImagePrefetcher.m */, 7B4DFE57A7C5FC68155C295484A72007 /* SDWebImageTransition.h */, 09F19A6EF0F8A7D0CDEEA09F1E90D4A1 /* SDWebImageTransition.m */, 9487AD43770C35ED6D6CF71B197AE775 /* SDWebImageTransitionInternal.h */, D53EBA2326B86A037D7D89C9C9849AB1 /* UIButton+WebCache.h */, F3DF4F3E573CB800D27E86168DD5EECB /* UIButton+WebCache.m */, 8D628BFBCC6276E5E277ED26143A0529 /* UIColor+SDHexString.h */, 14DB6413E93A38460FE253FC259C28D5 /* UIColor+SDHexString.m */, 3C388E0E2142C8BAB1FBE711F1A33688 /* UIImage+ExtendedCacheData.h */, 26C98C55DA7C8E2B36CE7527095DED78 /* UIImage+ExtendedCacheData.m */, 0FCDB01EEB669605F883EA4E79E56AD2 /* UIImage+ForceDecode.h */, A62E17FF50F14A446211D0E0BA00FD24 /* UIImage+ForceDecode.m */, 3A023925AC3740397148C89A37EA72B8 /* UIImage+GIF.h */, 2186649006C5A621B8BA7DDCC5C27AB4 /* UIImage+GIF.m */, 44E9E3AB162F34A2457874535E2D437A /* UIImage+MemoryCacheCost.h */, 35329B868A55A887B96B3891C90CE411 /* UIImage+MemoryCacheCost.m */, 859F0A43E3649885E17F4D515A86A159 /* UIImage+Metadata.h */, 9BC48CD5B43C672FF7213F9BE34110DB /* UIImage+Metadata.m */, 8C1F9B1C56AC542B7695BF945F7E8A5A /* UIImage+MultiFormat.h */, 50AF89B1AE50DF562243E31C949BD5B3 /* UIImage+MultiFormat.m */, CA1E5C47B1D161A6D7326A63F73039E1 /* UIImage+Transform.h */, 27B6AEBABD0DD760910C2037F73CBBA7 /* UIImage+Transform.m */, DA01E7E21E39AD745F362344A6F1CA3D /* UIImageView+HighlightedWebCache.h */, A259FD968B2F3F103051FCCB13D1EF8F /* UIImageView+HighlightedWebCache.m */, D519F9451A80C810AEF071E0A50F225E /* UIImageView+WebCache.h */, FDCDC0DC5EBD4242B3B44E167355E955 /* UIImageView+WebCache.m */, C8CC6A0CB955A36A57FB8DC86DC7122B /* UIView+WebCache.h */, 78E45C724EB1ABA21537759F2AA0CDBF /* UIView+WebCache.m */, E2A2FCE7B47213F9F0585EA70EA2024A /* UIView+WebCacheOperation.h */, EE2390C8AD53927134B524CBC997D50E /* UIView+WebCacheOperation.m */, ); name = Core; sourceTree = ""; }; 71DD4FAB1BC8065512ABDD1F5001E5C8 /* GoogleDataTransport */ = { isa = PBXGroup; children = ( 47C2768639337908388E00A97B570D97 /* cct.nanopb.c */, AB33B7FB74C324A4584D1D93AF8B1DB5 /* cct.nanopb.h */, 3B56D652DA90C728A3D4AA16170D7FA2 /* GDTCCTCompressionHelper.h */, 219546DB6A6974432E1E0283F24A3329 /* GDTCCTCompressionHelper.m */, FE8190044D8F103421DED7166A530383 /* GDTCCTNanopbHelpers.h */, 1CBEB10B215FDB65E86331667E87AE84 /* GDTCCTNanopbHelpers.m */, 018833202B971E61CF36DA9775AED769 /* GDTCCTUploader.h */, DEEA9E90B955F3FF75BD5CC9F8F49886 /* GDTCCTUploader.m */, 08F469B9832F15DBDE81EB155A43226B /* GDTCCTUploadOperation.h */, 11DC645818CEA0B49FB41D10974587E3 /* GDTCCTUploadOperation.m */, 813CEE72D58D250DD2A09A4157C51303 /* GDTCORAssert.h */, 363CE7A6599529EED98580D11773BD01 /* GDTCORAssert.m */, BCEE4E5F9E2A1F4352E21C55CC500B15 /* GDTCORClock.h */, 8F6FA7F2B642E68ECD25BCCB0DDAAA75 /* GDTCORClock.m */, 22B6E2A2A128A5A1F1B6F8D6C0EF4BBE /* GDTCORConsoleLogger.h */, 93498C80BEE42599CD4B49D1DCDE8DAA /* GDTCORConsoleLogger.m */, 66E791BF8C770CDCE827420B1959B849 /* GDTCORDirectorySizeTracker.h */, 606B14BF9E8B55AA634896F3FE6C2102 /* GDTCORDirectorySizeTracker.m */, 3AE71CC3E7412E1F33D2D46AB258531C /* GDTCOREndpoints.h */, 9D1FFD95B9DC7DB4011FC5445F7059CE /* GDTCOREndpoints.m */, EF8FCF0158C3FC321B0CCC8F87E286F6 /* GDTCOREndpoints_Private.h */, BBB794E91F141A51A62086A95A79B3B0 /* GDTCOREvent.h */, 74B387BF4963DADE929360BC4D95BF9F /* GDTCOREvent.m */, 7EF33965A3795DEE7A531EC8C56300B4 /* GDTCOREvent+GDTCCTSupport.h */, 135A77B30A220981214A503DF9FB9D3A /* GDTCOREvent+GDTCCTSupport.m */, CEB2E6515F73C730A4DE300BCA32CE76 /* GDTCOREvent_Private.h */, 3C31DC7C6755337262C1BDDB6677C635 /* GDTCOREventDataObject.h */, EC2AA38D1492EE015AE393D7FCDBCDF1 /* GDTCOREventTransformer.h */, 7B1EB303FD52ADD9ABA791AD8C33496F /* GDTCORFlatFileStorage.h */, 736D3261A8A25DCBBECD54A6C5C48166 /* GDTCORFlatFileStorage.m */, E9E0C507183FA6B14B3FED0BB9BFE8E5 /* GDTCORFlatFileStorage+Promises.h */, 79AB37844F04B4F0CF83A66B9FBD9175 /* GDTCORFlatFileStorage+Promises.m */, 4CD317B6F97D4204C18D7618DBB86C03 /* GDTCORLifecycle.h */, 0E80D119146AEB6CE705A642B0A4012D /* GDTCORLifecycle.m */, DC55F3B02A125C1D8CBC59176095F043 /* GDTCORPlatform.h */, 809CE53E4184869CD1D4910C0A359884 /* GDTCORPlatform.m */, 87BD1262A5EDC0CE9D84214096454004 /* GDTCORReachability.h */, 5352D3BC21FF5229DD4FC39829C48E7C /* GDTCORReachability.m */, C1150A7649D44398A5F001918CBE0C16 /* GDTCORReachability_Private.h */, 5FD16FE46197F77B6D4E28CDE644CC73 /* GDTCORRegistrar.h */, 9DB1409CD0C3B140ECAB6006D2DAD8BC /* GDTCORRegistrar.m */, 0338A87088B87F5A5D1BB03CA2AB274B /* GDTCORRegistrar_Private.h */, FFBEACDBC00E47FDCE130333C01BDD52 /* GDTCORStorageEventSelector.h */, 5BE80A247D8376A910A19BFB10B99430 /* GDTCORStorageEventSelector.m */, 7F3BFEC70D8BA82BE6E262B3750543AC /* GDTCORStorageProtocol.h */, F589BA173CD44451894C642200BAD5A9 /* GDTCORTargets.h */, B06B80A4EE003DEDFC1F89E8907C6C16 /* GDTCORTransformer.h */, BCBC03DFAC32230F28CDE2D8E913A269 /* GDTCORTransformer.m */, 0ADF13875C4D61EE62B5265D074DB171 /* GDTCORTransformer_Private.h */, FFC85887576AED4EB7D6127CC1FDC955 /* GDTCORTransport.h */, 193B43A46E5628F605735E9C2EB9C5B1 /* GDTCORTransport.m */, 67A976AAF1EAC9BADF423E17C96B88FE /* GDTCORTransport_Private.h */, EC47234C0173B2827334091E5C04C152 /* GDTCORUploadBatch.h */, 6AFBDA1EBD167AFD2083DC8D9D565001 /* GDTCORUploadBatch.m */, F6FF94050EDDF80FD2501F4E30F18E4D /* GDTCORUploadCoordinator.h */, 6AB43C522E93B03C169E99C63451C091 /* GDTCORUploadCoordinator.m */, 2FC875B27ACA63CA271CA0D921CF94C3 /* GDTCORUploader.h */, 28859EE1F335E36BD76813D7AC4608D9 /* GoogleDataTransport.h */, 5D0B7910C5928FF0F06049E99FAACD41 /* Support Files */, ); name = GoogleDataTransport; path = GoogleDataTransport; sourceTree = ""; }; 7486EB8C246AC69530D40BBBFC09B321 /* Reachability */ = { isa = PBXGroup; children = ( FC22CB2FD3FE9BC3D607B5CA39155B83 /* GULReachabilityChecker.h */, EF82E76BE97070A77C122D8CD5A2A852 /* GULReachabilityChecker.m */, 1A6FE3762E8CAD647386716CD1698F48 /* GULReachabilityChecker+Internal.h */, FEE20DA431EFAB6E9DE3F14B005CCF75 /* GULReachabilityMessageCode.h */, ); name = Reachability; sourceTree = ""; }; 8275CCB4E8732497B1021FCBC240F3C2 /* Frameworks */ = { isa = PBXGroup; children = ( 8955292BD5F544B74B743CA93E987E41 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 82ABAFE15F86B5F2F88A2DAA6D6FF5C9 /* Support Files */ = { isa = PBXGroup; children = ( FA50FFBEC226EAB798930460C18C4350 /* SDWebImage.modulemap */, CE6BFCD79351B8CDEDCAB2DE6EDB89AE /* SDWebImage-dummy.m */, 91EF3E33FDEAEF5BB90A91327C8718B6 /* SDWebImage-Info.plist */, E6921E05E12367D79F30526D90D84BE6 /* SDWebImage-prefix.pch */, A6344AC78A87A0720BE33CE02655D669 /* SDWebImage-umbrella.h */, 32073165CB0BA8A73E608799A7994586 /* SDWebImage.debug.xcconfig */, FC6A0016A0D695FE4F0D2BCB7B58E53B /* SDWebImage.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/SDWebImage"; sourceTree = ""; }; 85E90CB4A586E4CFFEF67024B592C673 /* GoogleUtilities */ = { isa = PBXGroup; children = ( 0C6CEC6D687B3736917288BC43D2B350 /* AppDelegateSwizzler */, BE0E4A57483DCC04AFA0CDB035E033DF /* Environment */, 6E122AC728989052F6E7D19E4F6218E6 /* Logger */, 3DA5ECD28DB682EEB7CE60E41A95AC39 /* MethodSwizzler */, A176A63F3EF04F64107A20A0172D49D1 /* Network */, 5633E1A74655186D803F0C91BD6B16C0 /* NSData+zlib */, 7486EB8C246AC69530D40BBBFC09B321 /* Reachability */, 9C746AB702E7728A50B880BFE02EE1F0 /* Support Files */, 5544FD9AFB77FFE3A00CB2505C4726A3 /* UserDefaults */, ); name = GoogleUtilities; path = GoogleUtilities; sourceTree = ""; }; 892DA6ED03D417C2A024B29B7963B0C8 /* PromisesObjC */ = { isa = PBXGroup; children = ( F35AA80907A78AC92F34C61F368248E7 /* FBLPromise.h */, B21C4B6B899118FE97F08797C6CC6D78 /* FBLPromise.m */, 4D82EC7D361BCA9009FD349978107D1C /* FBLPromise+All.h */, 9D110B53C055335FB5D62021AD131E9F /* FBLPromise+All.m */, FB9896C32514099F18D7AA422602FB04 /* FBLPromise+Always.h */, 0DFCC2FB818BA3289E85B84DD2F068A3 /* FBLPromise+Always.m */, 70FBB40CF13B29C7EC31294E2767F546 /* FBLPromise+Any.h */, A1B2BE9FCAE442C062B1070B5F83D9D9 /* FBLPromise+Any.m */, 7FDFC5FEAC4A550A50CD516405F6DFF2 /* FBLPromise+Async.h */, 068EFE7F14EBD412EB944B5B38CE3CB3 /* FBLPromise+Async.m */, 1F96A93009C7CAECE802F2AF89962662 /* FBLPromise+Await.h */, EC847DD51A92AE89F533B4312E344982 /* FBLPromise+Await.m */, 1BC2750E276CE3D6725F398532CEB28B /* FBLPromise+Catch.h */, 490C3A82D71E20AB4B5BC07D75101D21 /* FBLPromise+Catch.m */, B8633B4212F9E21E512835226D2BEC2E /* FBLPromise+Delay.h */, 239EF7C666D42E75FB20E65D3491B74A /* FBLPromise+Delay.m */, ECF677E408094637A67960F364C6FA0D /* FBLPromise+Do.h */, A6888DFA1022D0AF9FB4109175558CEC /* FBLPromise+Do.m */, 447B191325C26D8C11C3D317EC3FBAA8 /* FBLPromise+Race.h */, EFD6BF26B215DD780EA6145DFEEC525A /* FBLPromise+Race.m */, 6806286AE468436D210DE24F56FFD7A2 /* FBLPromise+Recover.h */, BE65A7532D1E92D1171FAB3E3DBF0EC2 /* FBLPromise+Recover.m */, F1A5B63B766812910C3FC66A4D5BD359 /* FBLPromise+Reduce.h */, FF60EBD5E154FA9A791F0C421B4D9AEE /* FBLPromise+Reduce.m */, C869A091C5104F60176DCBAC53E9FCD6 /* FBLPromise+Retry.h */, B0893F86784B88FBEBDDAD469BA72394 /* FBLPromise+Retry.m */, F0647D92B0FBA5FC4BF6371012FEE181 /* FBLPromise+Testing.h */, 00F035755BD3D0F891EF8F32718F2E74 /* FBLPromise+Testing.m */, 8D85590DB78FA3CE98F5876D319CDC9F /* FBLPromise+Then.h */, 18637DFB49F868B5320F06FCD27ABD99 /* FBLPromise+Then.m */, 2A93FB313CA659376FAA369092F2D036 /* FBLPromise+Timeout.h */, 5A8A40185ABE648F7A15E613D0C0559F /* FBLPromise+Timeout.m */, 2CD99347EC1D85D2C2FDDB3D1F9DF393 /* FBLPromise+Validate.h */, 281F885809D3382CEFAC6F78D5E16CA0 /* FBLPromise+Validate.m */, 47F2FC011CE490B793305158ACB2048E /* FBLPromise+Wrap.h */, 5BEA3009F10DE79DB402A683F6839FC8 /* FBLPromise+Wrap.m */, E76B8095179CC1855E5CFA2FF2597A24 /* FBLPromiseError.h */, FCA00A91EC23FFE0892B9D0F0B84F617 /* FBLPromiseError.m */, 278F2C97E692BBEB7227CFD22EE4333A /* FBLPromisePrivate.h */, 7283DD0E961B31F830AEB840A7B59650 /* FBLPromises.h */, 1AFCC02FC929E5DD450B688E200653B7 /* Support Files */, ); name = PromisesObjC; path = PromisesObjC; sourceTree = ""; }; 8955292BD5F544B74B743CA93E987E41 /* iOS */ = { isa = PBXGroup; children = ( 90E823CCF25045F00FF4E98E871586A6 /* CFNetwork.framework */, 28A55FC857FA4F7D9FE98F3B5AA514E8 /* CoreTelephony.framework */, E21B3C078686406E2ECFD3BC65D566D7 /* Foundation.framework */, C78361E6DA67CDEC2A04C25F1B40D31F /* ImageIO.framework */, 5F030A051DB2F7C17CB74A9C0FBD7B56 /* Security.framework */, 7BBDEBAD931B605980FE3DCE9FA337AE /* SystemConfiguration.framework */, 7887F0BAB621B4623BFE017BC5FA339D /* UIKit.framework */, ); name = iOS; sourceTree = ""; }; 8F79F2409CDA67E13113AA0DCFE8B8D7 /* Support Files */ = { isa = PBXGroup; children = ( 20481C9B0D8652965A8D3CA6CF4FB6DF /* Appirater.modulemap */, CD9056754A12ED3E38D68945FE70B586 /* Appirater-dummy.m */, 50A0F26807A7475A7CAB02BAC26EA368 /* Appirater-Info.plist */, C0FF8273009BBF51F9894931F3B54A5E /* Appirater-prefix.pch */, D2771347E37390095BF7D36EB989050F /* Appirater-umbrella.h */, B74D76E7DA492E2DD4E2C7CE3A5874F9 /* Appirater.debug.xcconfig */, CE9EF082FAA32B5B2D0B0C7CC14C11E6 /* Appirater.release.xcconfig */, 8A7AE3C5E6E2073C8FAC46FF8333849F /* ResourceBundle-Appirater-Appirater-Info.plist */, ); name = "Support Files"; path = "../Target Support Files/Appirater"; sourceTree = ""; }; 98ABACC26B4DE659B90DF14F58AC6F5A /* Resources */ = { isa = PBXGroup; children = ( EE0CECAF5DA5AAE13E1615E7B14147CB /* ar.lproj */, 4E579AE58A2F6343FA943DA78FDA6A24 /* ca.lproj */, D52688E7BC72717ABC1A8FD85DD32AE4 /* cs.lproj */, F4A7F01DAC8F3E158C6CB70DD5E60C24 /* da.lproj */, 8CDC01574AEDC72431059BB3FDE16F59 /* de.lproj */, 99C91ADC1F402BD8ABEE63EAB8134747 /* el.lproj */, 8C7297F9F351E0DFB107547CC454F81B /* en.lproj */, EF7A55F92FBA10C1CA52F98BF5B091C9 /* es.lproj */, A86DA94DC9AB6D6E95B73BF4F59C3EF1 /* fa.lproj */, AF5AFDC2F2B6C3E31AFC75FE3430CA49 /* fi.lproj */, E80E4257A841B7AA5C92B2CB19B16F0B /* fr.lproj */, D005D4ECC0D56EB73527C065B49D192C /* he.lproj */, 90548723A968948DA9E270815D5DFEA2 /* hu.lproj */, 9FB349B1B1D5B036FA774AC001CEBCB4 /* hy.lproj */, 4AC80933AD4E17DC2DDD4A5156E1FADB /* id.lproj */, 4958CF39D5BE0CE693F9BABAB470D5A1 /* it.lproj */, D2D3BD16668BB31898CA2909AB1211CB /* ja.lproj */, 98A2C33C3CFB15DFB0AD91D948975967 /* ko.lproj */, 867CE4FF42F759E76A71FEF4453E8079 /* ms.lproj */, 4F72FBD1344FC32D12AA727F983904D6 /* nb.lproj */, 700956653190B059B98E6D0401813D2F /* nl.lproj */, 0A31AE9C96B6817F39D60A3B4CB0E787 /* pl.lproj */, FC3C259DB11E51E08F2D5630A5145839 /* pt.lproj */, C3AECB4945D348C50164B9454BA98CB3 /* pt-BR.lproj */, D053D0A9CE99EC16E5ED88D7BA8C3A65 /* ro.lproj */, 3446029F86E0312850EAC5C98A32C65C /* ru.lproj */, 573447DBCFF573125A97AC8FE08CF8FB /* sk.lproj */, 030D92BA7629B33D7DEDDD755B2FB34B /* sv.lproj */, 8A0DEDA4144F466B62361967B86ACC25 /* th.lproj */, 45A1925DBA110CDCB56C30C2AEC79D96 /* tr.lproj */, FEE086E4BCB7B965DFED58170011ADD2 /* uk.lproj */, C6911F1DC733532D91E29D6B0433A787 /* vi.lproj */, 255E93987C91799073F418C741D2F912 /* zh-Hans.lproj */, 8C5EE75C53160A323F9D43000C9258C2 /* zh-Hant.lproj */, ); name = Resources; sourceTree = ""; }; 9C746AB702E7728A50B880BFE02EE1F0 /* Support Files */ = { isa = PBXGroup; children = ( 94342AAF24F20C2012C9E17B8D8076FA /* GoogleUtilities.modulemap */, 71783D9D8F34A0AB09F92BAF64FC155E /* GoogleUtilities-dummy.m */, A6178B53BBB7ED0452CEE088B25AA064 /* GoogleUtilities-Info.plist */, 1F54F5C198006F0294B0657489850E48 /* GoogleUtilities-umbrella.h */, D934FF1B0DBCF555CAF470E0CF5AA900 /* GoogleUtilities.debug.xcconfig */, 4D06A1E8D589DEE651EA569B253B9DA6 /* GoogleUtilities.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/GoogleUtilities"; sourceTree = ""; }; 9E278DC92C4390DDC86926F315CF7F51 /* FirebaseAnalytics */ = { isa = PBXGroup; children = ( F6AE86F671E4F982DD5BB48EAAB525A9 /* AdIdSupport */, 4DEE105FF0FD390E62D91CCF24486B2A /* Support Files */, ); name = FirebaseAnalytics; path = FirebaseAnalytics; sourceTree = ""; }; A176A63F3EF04F64107A20A0172D49D1 /* Network */ = { isa = PBXGroup; children = ( 73009AD0D7FBF3D5781BD8B87D5E1D98 /* GULMutableDictionary.h */, BD3097914838C55DE30D9D377048A664 /* GULMutableDictionary.m */, A8B52265053B7A072C3CFD52774A4E64 /* GULNetwork.h */, 0502AAFCB22CA8A06A21C20517BE89B4 /* GULNetwork.m */, 6C8ECB26C6DABE3DBF37DBC44D5A8C5C /* GULNetworkConstants.h */, BA44BA3AD4121BF4B3BA03128355DA0A /* GULNetworkConstants.m */, 18D28D4E30FB28D835221C737B53B7E0 /* GULNetworkInternal.h */, DC79300A6D291A17A3804A3511C41D9B /* GULNetworkLoggerProtocol.h */, 54836C20C54693641AA8386E05C05C5C /* GULNetworkMessageCode.h */, EF3303042C21F9396439AB3DE71C7BB2 /* GULNetworkURLSession.h */, 142559AF1A21A92BBB53100F317735FE /* GULNetworkURLSession.m */, ); name = Network; sourceTree = ""; }; A21029CE5CC909068E9BC378006F9D29 /* decode */ = { isa = PBXGroup; children = ( ); name = decode; sourceTree = ""; }; A756C5B446990E8A8C4B65394FCF4FEF /* GoogleAppMeasurement */ = { isa = PBXGroup; children = ( 278C4253D3F115DE5037DF8D472B252D /* AdIdSupport */, D522B9F055E8193E1EB8C99ACB45C3A0 /* Support Files */, ); name = GoogleAppMeasurement; path = GoogleAppMeasurement; sourceTree = ""; }; AC1136BE3B047EDE3C63B82F0FADFAAC /* Frameworks */ = { isa = PBXGroup; children = ( D43779F7D174B41F97107A9631DFA91A /* GoogleAppMeasurement.xcframework */, ); name = Frameworks; sourceTree = ""; }; B238E6220B3EC0E0373BE9408386D8A6 /* encode */ = { isa = PBXGroup; children = ( ); name = encode; sourceTree = ""; }; B62CFD72F1B5C3EDED2D6436396B062F /* Appirater */ = { isa = PBXGroup; children = ( 350451991B16222891CB7702FAA4C26A /* Appirater.h */, 0466C52ED49E6E1C863BAFB6E1AE5B92 /* Appirater.m */, 4687296E13EF95A9B5206235744E21EC /* AppiraterDelegate.h */, 98ABACC26B4DE659B90DF14F58AC6F5A /* Resources */, 8F79F2409CDA67E13113AA0DCFE8B8D7 /* Support Files */, ); name = Appirater; path = Appirater; sourceTree = ""; }; B7C5491CCF4E11B34D2C388422157234 /* Pods-Spotify */ = { isa = PBXGroup; children = ( 2565C009A42B8D093CEB285EE59D4EE0 /* Pods-Spotify.modulemap */, 561783788BEFAECA7A813F1442497E1D /* Pods-Spotify-acknowledgements.markdown */, 43864D2A6701CB0D5A279382ABEF4FD9 /* Pods-Spotify-acknowledgements.plist */, A27A53F9FCBFE297E53C77376ACB64D1 /* Pods-Spotify-dummy.m */, 4AABAA72A7A163BFFFA781CE2598D89B /* Pods-Spotify-frameworks.sh */, E30EDBDA563FB09CF5AE6965F2558FE9 /* Pods-Spotify-Info.plist */, F124022073EADCF94FCBED857E5EABBB /* Pods-Spotify-umbrella.h */, 02EBCB4694F493398B30D145521759BB /* Pods-Spotify.debug.xcconfig */, 20DAC9944D4D96AD4E142D77D43D9F68 /* Pods-Spotify.release.xcconfig */, ); name = "Pods-Spotify"; path = "Target Support Files/Pods-Spotify"; sourceTree = ""; }; BA6859418815F5231E3CEAAA21D0F012 /* Support Files */ = { isa = PBXGroup; children = ( 036C70BC91046AD6992330F90128AC83 /* FirebaseInstallations.modulemap */, EDF07392617868FE264D5E58788D279F /* FirebaseInstallations-dummy.m */, 94FAEBC41B0BE3E27929CC570CB58423 /* FirebaseInstallations-Info.plist */, 54D381D3559D8677EA0632FDB5EEA05A /* FirebaseInstallations-umbrella.h */, 4E43BB424620088D2FC848F87869992C /* FirebaseInstallations.debug.xcconfig */, 8B4E22AE507DF453D28673AD92339EB1 /* FirebaseInstallations.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/FirebaseInstallations"; sourceTree = ""; }; BE0E4A57483DCC04AFA0CDB035E033DF /* Environment */ = { isa = PBXGroup; children = ( 0B538C0DED9A96F51790585E9AF03D97 /* GULAppEnvironmentUtil.h */, D2DC9C0FCEB534D5193B9600BF0552FC /* GULAppEnvironmentUtil.m */, 447875125DF9F0D89F9C6EF9340E1570 /* GULHeartbeatDateStorable.h */, 05850FDD1539ACB6AB1C9DB0142B8403 /* GULHeartbeatDateStorage.h */, F36DCFF7B72CE2940864E1364072151A /* GULHeartbeatDateStorage.m */, BB06018D1785388A5C83E9EF4D57ED16 /* GULHeartbeatDateStorageUserDefaults.h */, 4F86D0619F7550A5DBDF634B8413E25F /* GULHeartbeatDateStorageUserDefaults.m */, A40C76162676EC9A3E0C33C1F29CB93F /* GULKeychainStorage.h */, B8AE32764C24267738A026CE787CE667 /* GULKeychainStorage.m */, 9A75B93924C163BDD4BFE27BE82320E0 /* GULKeychainUtils.h */, F80FBEC0B534E33030C9ECCABDD9BE62 /* GULKeychainUtils.m */, 476A318EB831BFCD3B77190E73149735 /* GULSecureCoding.h */, AB007F418E0ED0E20061B9ED7D831C76 /* GULSecureCoding.m */, 3F2853ABAFCEFBF960A5040D649074EC /* GULURLSessionDataResponse.h */, 40CE0245F2D926AF50D3BFBA488887C1 /* GULURLSessionDataResponse.m */, 2494BB0409778ADBC0785D40DE703573 /* NSURLSession+GULPromises.h */, 61C15E699851C928BE91C72A5169AEB2 /* NSURLSession+GULPromises.m */, ); name = Environment; sourceTree = ""; }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, 8275CCB4E8732497B1021FCBC240F3C2 /* Frameworks */, D97DF81019DCDCF585FD2F0B010BD2B2 /* Pods */, 1FC49855849F77DD042774C08FB9A2BD /* Products */, 16EF75AC256F0C94C6273A51CFE74355 /* Targets Support Files */, ); sourceTree = ""; }; D522B9F055E8193E1EB8C99ACB45C3A0 /* Support Files */ = { isa = PBXGroup; children = ( 161553D9C89B2C716D3056754C86B4A5 /* GoogleAppMeasurement-xcframeworks.sh */, 695ADBFC9C4D04D17897085D1ABBBAA7 /* GoogleAppMeasurement.debug.xcconfig */, 27EFCAB3E6A3DE572444045971D498C5 /* GoogleAppMeasurement.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/GoogleAppMeasurement"; sourceTree = ""; }; D97DF81019DCDCF585FD2F0B010BD2B2 /* Pods */ = { isa = PBXGroup; children = ( B62CFD72F1B5C3EDED2D6436396B062F /* Appirater */, 2152BB9B7EF0C69F7B86DE1EF926D67F /* Firebase */, 9E278DC92C4390DDC86926F315CF7F51 /* FirebaseAnalytics */, 1AE034B84201B1046BCCE51577E15354 /* FirebaseCore */, E46377C0E529F23020C710F7BB8AA1AA /* FirebaseCoreDiagnostics */, 62A2F567C67C028B3713BC180E3A5B8C /* FirebaseInstallations */, A756C5B446990E8A8C4B65394FCF4FEF /* GoogleAppMeasurement */, 71DD4FAB1BC8065512ABDD1F5001E5C8 /* GoogleDataTransport */, 85E90CB4A586E4CFFEF67024B592C673 /* GoogleUtilities */, E2D425967250F754DBAE550A79EDDA23 /* nanopb */, 892DA6ED03D417C2A024B29B7963B0C8 /* PromisesObjC */, 3082F193C8D1BA24D51A7BC01CEBD841 /* SDWebImage */, ); name = Pods; sourceTree = ""; }; E2D425967250F754DBAE550A79EDDA23 /* nanopb */ = { isa = PBXGroup; children = ( 15C9B0DD52D5C611E1AB4BB7A9A92DF5 /* pb.h */, 983C23FA76E7A245275E7A252B67A2A0 /* pb_common.c */, 06D5568DC434FEF3EF84595EE271B95A /* pb_common.h */, D8BE8FDFF77007F5278B6A0E5BC01E6A /* pb_decode.c */, 7F0F742A22FC71CED5A18A108E514A8B /* pb_decode.h */, 90D9BC28AA40B8F7AAC5DBAFFFA2503A /* pb_encode.c */, FDF404E683FC3DE61DCE91FF8B1E0842 /* pb_encode.h */, A21029CE5CC909068E9BC378006F9D29 /* decode */, B238E6220B3EC0E0373BE9408386D8A6 /* encode */, 48EA86F33969C2420662B0A62E5A9FCE /* Support Files */, ); name = nanopb; path = nanopb; sourceTree = ""; }; E46377C0E529F23020C710F7BB8AA1AA /* FirebaseCoreDiagnostics */ = { isa = PBXGroup; children = ( A2CD1B738ABAB7BA344D7969860B494C /* FIRCoreDiagnostics.h */, 45D88DFE4BAAB878102C9DB69C90E1EE /* FIRCoreDiagnostics.m */, AAC1202619BDEC6CB408DEFB276233E1 /* FIRCoreDiagnosticsData.h */, 5805E31EC45A505EEE3457FA2B9FA351 /* FIRCoreDiagnosticsInterop.h */, 495F9A8ED0CBC83C181879060E49F21E /* firebasecore.nanopb.c */, 1A58961C9BBD35E7B60ECDB62E56F7DC /* firebasecore.nanopb.h */, 6EF332B85D74AC3B2E0615B4F1FB4DC5 /* Support Files */, ); name = FirebaseCoreDiagnostics; path = FirebaseCoreDiagnostics; sourceTree = ""; }; EF5B961421EC2787E78401B772CC91D5 /* Support Files */ = { isa = PBXGroup; children = ( AA8BA4718A81C87F777362D613444D44 /* Firebase.debug.xcconfig */, F582A3CDAC1DDA3FCEA8CB2C13293A4D /* Firebase.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/Firebase"; sourceTree = ""; }; F6AE86F671E4F982DD5BB48EAAB525A9 /* AdIdSupport */ = { isa = PBXGroup; children = ( 058C19C88B10171A17924987A122D927 /* Frameworks */, ); name = AdIdSupport; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 104737A7F2F8176AF07A40A019B4DC37 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 6C93ABDF963DDA7C78AADD9F22CD5CB1 /* NSBezierPath+SDRoundedCorners.h in Headers */, 7E6862A73246BB1A0E7BCEBAF50FFBE1 /* NSButton+WebCache.h in Headers */, BD074722CD72470954ADB5B1198E01C5 /* NSData+ImageContentType.h in Headers */, E8CE28499C17965A931605D4F47AAEBF /* NSImage+Compatibility.h in Headers */, 38CF7D5EE6C28780FE3F9C1E63CFF3D4 /* SDAnimatedImage.h in Headers */, 57E1C476BA5F8DD1ED740FE8DBC05D8A /* SDAnimatedImagePlayer.h in Headers */, 4FB353F2B2E7D7AE0811D7C538DA2B36 /* SDAnimatedImageRep.h in Headers */, F463DEB020D5C466D4613947AE4BE16F /* SDAnimatedImageView.h in Headers */, 42822E48A1441AD8E0011D37C1625B36 /* SDAnimatedImageView+WebCache.h in Headers */, 84B624227D73D3746CEFD48E49D74635 /* SDAssociatedObject.h in Headers */, 18303B692A81A7D708973A6B9CBA75D8 /* SDAsyncBlockOperation.h in Headers */, F161112B04B9AC7D276D9F588BA6E313 /* SDDeviceHelper.h in Headers */, FE0189D2FE038AD82C4CCF5A801A50A3 /* SDDiskCache.h in Headers */, A24B4B60656BA387B9979BD877A23983 /* SDDisplayLink.h in Headers */, F077E74D9855D0D7FCF570F8E8ED4A1E /* SDFileAttributeHelper.h in Headers */, A2CE92DE43AB450DE1AAA129BFBADCBE /* SDGraphicsImageRenderer.h in Headers */, 809A09224ABB6A00BEF6B72EFAB30B4E /* SDImageAPNGCoder.h in Headers */, 336BA7F271A33087B951BA4C6C6B2C6F /* SDImageAssetManager.h in Headers */, 33C0E2EFD1C8046A2020D25FD1F1611F /* SDImageAWebPCoder.h in Headers */, 0F4322740A001E3D7BFD2C333BCF907D /* SDImageCache.h in Headers */, E7FA1E1CD066D0335A95376C73E3F8ED /* SDImageCacheConfig.h in Headers */, 5BBE7719912593F1DEFF8E763EAD4ED6 /* SDImageCacheDefine.h in Headers */, 909CBA577868D6E976AC82340B50343E /* SDImageCachesManager.h in Headers */, E8D519B63C2077979F799769F9A35072 /* SDImageCachesManagerOperation.h in Headers */, 0FFA0C960F26666918569059FB8E92BC /* SDImageCoder.h in Headers */, 519B33DCAE0FB73CC313A2C2AD434449 /* SDImageCoderHelper.h in Headers */, 8821E3636BB86560FC2F2D2CFF545F16 /* SDImageCodersManager.h in Headers */, 523861A018452CE8BB166B59A7872BDA /* SDImageFrame.h in Headers */, C7E8A80BE6BE5452380D8CA71F278B8A /* SDImageGIFCoder.h in Headers */, 80D4A72C26C2EF865F9DCA400D2F1E70 /* SDImageGraphics.h in Headers */, 99709E1C4991C49EE5601FF9461B2969 /* SDImageHEICCoder.h in Headers */, 11E79385CD0DA86166E63D21E7E078E9 /* SDImageIOAnimatedCoder.h in Headers */, 168AB5EA0EBEB75D0434B55D0B97AD47 /* SDImageIOAnimatedCoderInternal.h in Headers */, E5C6051933677CDF392BF6356271A92E /* SDImageIOCoder.h in Headers */, 67B3DCBA30FDEDA18E0D4E3B2C16C911 /* SDImageLoader.h in Headers */, C4A78B8FC2DC76FBCF43D9D53BF90767 /* SDImageLoadersManager.h in Headers */, 8C5D0B2A7B52DF68D03820244993E91B /* SDImageTransformer.h in Headers */, 8433F353F3016EFF0316F48188D6B651 /* SDInternalMacros.h in Headers */, C2D82385F4FDB67F6613CBBE8E2BD850 /* SDMemoryCache.h in Headers */, E556832E0C191FCFABB9E97C87D243EF /* SDmetamacros.h in Headers */, 9C096C865369C21D252A0F4F90BE21D9 /* SDWeakProxy.h in Headers */, E515780850EBAD40342EBF2997BFC2D2 /* SDWebImage.h in Headers */, 48920AFFC7C0E2FEA632DAA2BD63287D /* SDWebImage-umbrella.h in Headers */, B128DE666CCA7CF6B0F09411D8209A92 /* SDWebImageCacheKeyFilter.h in Headers */, DECA57AF64C5C257919649ECBB75F0B9 /* SDWebImageCacheSerializer.h in Headers */, FED292AF8AD5924263FC0A8D0B8AF26D /* SDWebImageCompat.h in Headers */, 7D9E8F8A5D02148FCC2DBD6319BCC3D1 /* SDWebImageDefine.h in Headers */, 5B69919844A3BD774BAABB186D6A524F /* SDWebImageDownloader.h in Headers */, F7289F163C285103D6CCAB39447153CA /* SDWebImageDownloaderConfig.h in Headers */, 05D76FA82DD3C37B76FFC1689CEF9981 /* SDWebImageDownloaderDecryptor.h in Headers */, 20004B99F5050F59E747AB8DFAD7E891 /* SDWebImageDownloaderOperation.h in Headers */, AD0CFDA140E346502935BA58225E6EF3 /* SDWebImageDownloaderRequestModifier.h in Headers */, 2D7E7D5F025A1006C0D373B5884B6E52 /* SDWebImageDownloaderResponseModifier.h in Headers */, 9917CEAD8CE1CEB7FEC031255AED8FA8 /* SDWebImageError.h in Headers */, 8F88549F032EFFEA9C281860520CC879 /* SDWebImageIndicator.h in Headers */, 07A41F67B40B3C6C6C3DB862F1F633F8 /* SDWebImageManager.h in Headers */, 36F35669F3AFF65477B2366FC09A1B3F /* SDWebImageOperation.h in Headers */, 6F4B172730B7293C1D988FBE34B45E20 /* SDWebImageOptionsProcessor.h in Headers */, 8C9D609988FE2993BB116B4B00FAC57B /* SDWebImagePrefetcher.h in Headers */, 0311D5903A8C13AE6158796BC69AD994 /* SDWebImageTransition.h in Headers */, B04279CC3476C6D75EDE63B360B89DEC /* SDWebImageTransitionInternal.h in Headers */, A083AAC3481062E5DE223D480757A83B /* UIButton+WebCache.h in Headers */, F4811CFAC05932CE436CC2C32BAD91E6 /* UIColor+SDHexString.h in Headers */, D7BA04ADFE3104907F100E9370184A65 /* UIImage+ExtendedCacheData.h in Headers */, 4A27F890C33243C4548A25318264B375 /* UIImage+ForceDecode.h in Headers */, DBBAC864AD405BED53782364E0F0D5F3 /* UIImage+GIF.h in Headers */, 26BF15D648EFA59FCA18AE285AC5B2A1 /* UIImage+MemoryCacheCost.h in Headers */, 73F2DE8B48DC2F7C87529AB3901B9E94 /* UIImage+Metadata.h in Headers */, 278F5B2C01E923AA0C691D79BD0F321F /* UIImage+MultiFormat.h in Headers */, D556C63635E7A83C7A9B04FB104F7339 /* UIImage+Transform.h in Headers */, 37E3C820BC838123661E1391BEAF8092 /* UIImageView+HighlightedWebCache.h in Headers */, 0BFA90EFCD3407716FFC60D140325188 /* UIImageView+WebCache.h in Headers */, D975B260AB44081399BCB612916CE675 /* UIView+WebCache.h in Headers */, 2F85FF5597762E2E3C6961C6DD690A60 /* UIView+WebCacheOperation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 18C6945B276FCD1F88687DAA6C122954 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( E4CA0B80D962B7042730CCA24DF7A6CE /* Appirater.h in Headers */, 4E024B5A716EC026E33AC8EF18023F4C /* Appirater-umbrella.h in Headers */, 41286FFA3116FB4073CFA821DFB09A03 /* AppiraterDelegate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 29E404AD0982126E200F09A0FBE7DCF1 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( E50990C8CCC727688A0EC4D123CDD295 /* nanopb-umbrella.h in Headers */, 5ABA716A48A3468C5B83F83D69DDF5D2 /* pb.h in Headers */, FA1435956493F6C517F5A097360C11D2 /* pb_common.h in Headers */, E78F445F25943A96E2AE21A4AD7EEA67 /* pb_decode.h in Headers */, ECB05ADC1FE7A52E0D9ACCB9B027EF2B /* pb_encode.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 3BD6EB14C2F3B8C73B1A7BF26F2227EC /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 74C6F951FCB4661E545409C0AF80D2E7 /* GoogleUtilities-umbrella.h in Headers */, F10FCC767B08C019DEE8FFB1516299EA /* GULAppDelegateSwizzler.h in Headers */, 5C4E850422971D9660A700996C75898F /* GULAppDelegateSwizzler_Private.h in Headers */, 86DDBF748A2FAB2E5426D9C62F4E97B6 /* GULAppEnvironmentUtil.h in Headers */, 42C1C2FC054DE4C8F98D5D9EABB99732 /* GULApplication.h in Headers */, 9EBE4FF936493E44E9CF7A19860170A8 /* GULHeartbeatDateStorable.h in Headers */, 89A0AC3A2E24FDA5CEDF545C0050C90C /* GULHeartbeatDateStorage.h in Headers */, E62B328C763183320A9105E6E8EB1860 /* GULHeartbeatDateStorageUserDefaults.h in Headers */, E8D668C3BC3581332C51EAE1359D2022 /* GULKeychainStorage.h in Headers */, 0184B6B56F7F8EC2CAA4300B5344B419 /* GULKeychainUtils.h in Headers */, C0EB460750BF5FBED872DD25D3B316CB /* GULLogger.h in Headers */, DB048A769D5DB1BE84466C9EA4EBBF1B /* GULLoggerCodes.h in Headers */, 242C08FFF99332102B7BF8E2590DA079 /* GULLoggerLevel.h in Headers */, A70259457F23AD83937DF1CA0DAF46D0 /* GULMutableDictionary.h in Headers */, E9AD74230A2E90C1FE84024CA1917ACC /* GULNetwork.h in Headers */, C50420FA308E51F5885CE0A2BA817622 /* GULNetworkConstants.h in Headers */, DBDF09CBE34BC99BD85677273C4B6296 /* GULNetworkInternal.h in Headers */, D9B8E5DA0091D211AACED8612BA1153B /* GULNetworkLoggerProtocol.h in Headers */, B5AFC5251D252C4CF6AC6A609E167BFF /* GULNetworkMessageCode.h in Headers */, 551EE2BA0A3A93F089EF76F85871B5EE /* GULNetworkURLSession.h in Headers */, 02252699D8D416E0AD793B30C95C4BD6 /* GULNSData+zlib.h in Headers */, 7FC014E10F04D9761116EE2481087F70 /* GULOriginalIMPConvenienceMacros.h in Headers */, 81534784497FC1ECE74D5C18D43AF3EB /* GULReachabilityChecker.h in Headers */, 2D3E6969DF4465FC38E09B23A2562DE2 /* GULReachabilityChecker+Internal.h in Headers */, BBB3C52BD5CE5A8F90FBD5650B283EA1 /* GULReachabilityMessageCode.h in Headers */, C40413C3772DC573FF3086BAB7D1EE28 /* GULSceneDelegateSwizzler.h in Headers */, 611CE969CEC379BB77D8885D915A8095 /* GULSceneDelegateSwizzler_Private.h in Headers */, D88E2E3BC8A27A085ABC3AF8AF4C7F86 /* GULSecureCoding.h in Headers */, 90A7255991F8B76C78A1C6A47F0BD3A5 /* GULSwizzler.h in Headers */, 6A60904A4A5C9CB728F36E68131A5629 /* GULURLSessionDataResponse.h in Headers */, 0B63C55C547C48DEE7EBAE9C9DFD3675 /* GULUserDefaults.h in Headers */, DE3AC691EE0E078459ECBE58059F1260 /* NSURLSession+GULPromises.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 3C2A858ED01432DE4CA8738997604903 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 41522E9AD19ACF8675C10B4035405E95 /* FIRAnalyticsConfiguration.h in Headers */, 4F5CFA3002CC0285EAA22323D1E017B7 /* FIRApp.h in Headers */, 0D7F532169AFF271D684865CF89DA8A7 /* FIRAppAssociationRegistration.h in Headers */, C21AEB2BC0F95C132FDE2206B40199F9 /* FIRAppInternal.h in Headers */, 701F3C59F0434AC67482392B26FB6F0A /* FIRBundleUtil.h in Headers */, 32DD9B533A167E3D45FB7C01D4157D5B /* FIRComponent.h in Headers */, 19BDF52B1C43F4A0EDAA7FA633B9BA88 /* FIRComponentContainer.h in Headers */, B1F3BDEEBCD13FE138D33F45470AFA71 /* FIRComponentContainerInternal.h in Headers */, D5573400F45ACFA5316D7AC2BA779056 /* FIRComponentType.h in Headers */, F646BABB56E9E66692865720E3FB71DA /* FIRConfiguration.h in Headers */, 03860676C8B2758B55FA5C92421B12FC /* FIRConfigurationInternal.h in Headers */, CA04A500A668FCCEB250C8A61F0643DA /* FIRCoreDiagnosticsConnector.h in Headers */, 48CB1FD2981BA256E4F2582237164A2F /* FIRCoreDiagnosticsData.h in Headers */, 0C663893D044728B8050AD706689AA09 /* FIRCoreDiagnosticsInterop.h in Headers */, 92851B2DB4A4C54E24A1720ADEC60E84 /* FIRDependency.h in Headers */, D00ADB6F62DCD79FF4AE5477907E4B08 /* FIRDiagnosticsData.h in Headers */, F417C8E4B3D73E496E4764CF2FE9EDDD /* FirebaseCore.h in Headers */, D3212FE5382F2D56A8C80829D8780188 /* FirebaseCore-umbrella.h in Headers */, FA10A9A289B0F3E86CCCE9E2A7E94EF2 /* FirebaseCoreInternal.h in Headers */, 1DD6D013806F64DED16DD22BAED3C369 /* FIRFirebaseUserAgent.h in Headers */, 5911F99F8CABED05F9F701DC450E4BC8 /* FIRHeartbeatInfo.h in Headers */, 6A9D73033878EECC7CAD51C2C82AF26B /* FIRLibrary.h in Headers */, F4BE3CF05C223248D90AC0F46BAAB9FF /* FIRLogger.h in Headers */, 593E6264A36C1B39D5885E85304C8916 /* FIRLoggerLevel.h in Headers */, B595EBF813D80EAD2870F0D888B6F1DE /* FIROptions.h in Headers */, E7AB534DD8C859013ADE863B6D2CBA7D /* FIROptionsInternal.h in Headers */, 95D0E52FDA5F7738F1D6F56696279BF8 /* FIRVersion.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 4FB87B3C00E20BB81A2110643D08AC3E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 72BE14CDABBE9D94A85828F987136A6E /* FBLPromise.h in Headers */, 3919CF80EC56ACAB6A3808C398ED1968 /* FBLPromise+All.h in Headers */, CA050FC8C58FA008C11AB981F0AC4363 /* FBLPromise+Always.h in Headers */, 83ACE2DB5F2F7F9352D11E9BD334AD96 /* FBLPromise+Any.h in Headers */, 57F70A5C8A2F7A13B0E33409440F4ABD /* FBLPromise+Async.h in Headers */, 23490DF877054EEA5F983AD117033631 /* FBLPromise+Await.h in Headers */, DC8E06FD7CA80D1461C4E73105FD45AC /* FBLPromise+Catch.h in Headers */, 565EF7659358278D993E12356980AE0C /* FBLPromise+Delay.h in Headers */, 1554350BBDCB525AA477C1F345852437 /* FBLPromise+Do.h in Headers */, F5734286C309C6E2FF84E06DA25F51EF /* FBLPromise+Race.h in Headers */, 96E8F1107963D738A8DD84FF5D7C4563 /* FBLPromise+Recover.h in Headers */, D63055B89D739A7EA319FBE861ADEED5 /* FBLPromise+Reduce.h in Headers */, 407A10BF833B480FECDAFC51A259D6D4 /* FBLPromise+Retry.h in Headers */, 05214C0A74E896C781C8CB7719A9984C /* FBLPromise+Testing.h in Headers */, BD5380B89F6EA6442F55F457CB377251 /* FBLPromise+Then.h in Headers */, 1471F3BFCD49A1FBD39DA3CD4C3DA8B9 /* FBLPromise+Timeout.h in Headers */, 2D5F3E52A136C35D74B92551B645D588 /* FBLPromise+Validate.h in Headers */, 0FC35E1D19F49ACD5B5957B86D32D4EA /* FBLPromise+Wrap.h in Headers */, 1CBE26399B4391159FDD5EF576CA6592 /* FBLPromiseError.h in Headers */, E92BFF50D3F84D5D8BA6E845FAEF8DA3 /* FBLPromisePrivate.h in Headers */, 57D8A29DA38A71184A984913BBC01CEC /* FBLPromises.h in Headers */, 2C4FBEAF9B8A4CEAF32D5EA477EB6531 /* PromisesObjC-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 6EE8726894AAD543472DED1FFF731ED9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( F17E3C439BBF49E6486B54C84DD0C190 /* FIRCoreDiagnostics.h in Headers */, EF75B2C28FA102334A044548123860E2 /* FIRCoreDiagnosticsData.h in Headers */, 6F9B1AAD95146EC2AE89F5B821D611D6 /* FIRCoreDiagnosticsInterop.h in Headers */, 6B83391B3231B704CBEADFE1F3ABE0F1 /* firebasecore.nanopb.h in Headers */, 1058BF78DCB651C56D33B7198AA0D355 /* FirebaseCoreDiagnostics-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 9D4712D998CD283461FDCE793EF01F34 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 77B7C664156789B282A6AECEB7F380C7 /* FIRAppInternal.h in Headers */, 78E730C2966F82AB15136ED2AFC85095 /* FIRComponent.h in Headers */, 878C1E830C12ED8A433F6FD3DAC38946 /* FIRComponentContainer.h in Headers */, D7068278536821417BB567EEED782F73 /* FIRComponentType.h in Headers */, 8C9EAF8201E92AB60FFE3A04A6A1D47B /* FIRCoreDiagnosticsConnector.h in Headers */, 72C3E949015B30F289F28DEDE6BA8F84 /* FIRCurrentDateProvider.h in Headers */, A4AA6C3C19244F1FF335C090930C609A /* FIRDependency.h in Headers */, BCD010E21F3444C11F5F87821E7F832D /* FirebaseCoreInternal.h in Headers */, F1372E076B4B28D2923EA22ED4D6DFCA /* FirebaseInstallations.h in Headers */, 8D3BB2AEBD692793E70D2D3A2BED2135 /* FirebaseInstallations-umbrella.h in Headers */, 2711092C95505064BFF763747C805405 /* FirebaseInstallationsInternal.h in Headers */, 7AE82F1B24F7A2E9B28BAF431611111F /* FIRHeartbeatInfo.h in Headers */, A5D6F583F2BCDF15588F8BA972A682CE /* FIRInstallations.h in Headers */, 4EDE67DB34FEC06E0EC09F4CCC914EF8 /* FIRInstallationsAPIService.h in Headers */, 67337806BBF45E0F89460C0ECB98F7CC /* FIRInstallationsAuthTokenResult.h in Headers */, ED39F6A91DF2FC44DD73DEE50BF7D393 /* FIRInstallationsAuthTokenResultInternal.h in Headers */, 8BFCDB90BD5C326BE36EE7599F016413 /* FIRInstallationsBackoffController.h in Headers */, 9ADF36BDEE9C92870ACB3F6E17CB0136 /* FIRInstallationsErrors.h in Headers */, 377AF2CA4D196DF568CEF1FB627B9B51 /* FIRInstallationsErrorUtil.h in Headers */, 1D635C91D8BC72532AAD16B6C063D7C0 /* FIRInstallationsHTTPError.h in Headers */, 975390A7158A9A542B397F21D63DDE3F /* FIRInstallationsIDController.h in Headers */, 6C301FF2C25C6329F7EA45C87F96C660 /* FIRInstallationsIIDStore.h in Headers */, 2A866696A7C9DB4CB42A963DE9551B40 /* FIRInstallationsIIDTokenStore.h in Headers */, 7A1EBC1F342550748C7D0A21F7BFA7D6 /* FIRInstallationsItem.h in Headers */, 07E6B04D64953307335FF701FF858427 /* FIRInstallationsItem+RegisterInstallationAPI.h in Headers */, 6EFEF0AC86E0308D25D125522708DD7F /* FIRInstallationsLogger.h in Headers */, 7AF1433C05B27E85A58B5222B73620BB /* FIRInstallationsSingleOperationPromiseCache.h in Headers */, 242A43C50C10362A01FA1F662EF7CF2A /* FIRInstallationsStatus.h in Headers */, 6F333D5505E952CF9C517DAE5114AC2D /* FIRInstallationsStore.h in Headers */, EFAB05A7F5A8FC35E27F219708D544AD /* FIRInstallationsStoredAuthToken.h in Headers */, 658CF66D84C572191E43DBCA78450144 /* FIRInstallationsStoredItem.h in Headers */, C8161051C207F9AECD0562B6B9E0E579 /* FIRLibrary.h in Headers */, 2A43AC77BE9EF6259E27876705069383 /* FIRLogger.h in Headers */, 9173483B65737D0C0DD2D1F3427AFE45 /* FIROptionsInternal.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; AAEE67A053DA6BFFD832AC12B98C70ED /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 3C1CC94B45A08AF7DC9A6DAF03574A52 /* Pods-Spotify-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; E3A6282DA9E6C688D6415ABB30AAD875 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 9E6AB9E70A18A858DCBC3B7D57575F47 /* cct.nanopb.h in Headers */, BDE45185614935653E3E24E4F8AE8812 /* GDTCCTCompressionHelper.h in Headers */, E4153F4FB8707C1B1B62815836B8F358 /* GDTCCTNanopbHelpers.h in Headers */, 4D9A4DD4DA000DB1A7233197AB5C9998 /* GDTCCTUploader.h in Headers */, E2657EA2FA2825ED6DBE1C1BFEA5907C /* GDTCCTUploadOperation.h in Headers */, 0A251DC0DF1E1EE8AC74D44C769312C3 /* GDTCORAssert.h in Headers */, BFC98970FF0DF6305A33A564BD19BF7C /* GDTCORClock.h in Headers */, 2E658D649B1C8158B79BC43FFC5B298E /* GDTCORConsoleLogger.h in Headers */, 6DA57657B2030D8076B7B341150BC06F /* GDTCORDirectorySizeTracker.h in Headers */, 41EB77C2185B44AC4251285C57FD024F /* GDTCOREndpoints.h in Headers */, C326A2B1DC92AC4C4C105C1D73C78E13 /* GDTCOREndpoints_Private.h in Headers */, 773B2DCA4762991C7F8FAE2FC860253D /* GDTCOREvent.h in Headers */, CF8968D878454B5BAE07037D28947D93 /* GDTCOREvent+GDTCCTSupport.h in Headers */, B82109186D3614122E6130F9710A241F /* GDTCOREvent_Private.h in Headers */, EB3A214553ED6CFAF04498A2DBAFE0EF /* GDTCOREventDataObject.h in Headers */, CA180BD40DFE51946C1F45D45D5E3784 /* GDTCOREventTransformer.h in Headers */, FE7AB9DA939B0E528ED9F339C6DCC201 /* GDTCORFlatFileStorage.h in Headers */, 86A4EDC386D953055E2EFD288F8549E7 /* GDTCORFlatFileStorage+Promises.h in Headers */, 457289616B6777C1D443FBC91A2F0325 /* GDTCORLifecycle.h in Headers */, E20DFCFF1B1EA67C678445D94AFC6782 /* GDTCORPlatform.h in Headers */, 01CB8221A492BC65C076670906A3D08E /* GDTCORReachability.h in Headers */, 116DB4571C6E70C54479C15F140245AE /* GDTCORReachability_Private.h in Headers */, 2D874A9744A25E43B747743945547339 /* GDTCORRegistrar.h in Headers */, 71093AA6681FFAD2FA263E3FADE983D1 /* GDTCORRegistrar_Private.h in Headers */, A3F8A995C1C713901F9B4503D4103A47 /* GDTCORStorageEventSelector.h in Headers */, 23D8684E3F488264A90E5954D59B902C /* GDTCORStorageProtocol.h in Headers */, 7A6E617487687A7EC3ED28D9090EC0E4 /* GDTCORTargets.h in Headers */, F8FE94E2FE6E57F616878424F22528CA /* GDTCORTransformer.h in Headers */, A0857AD2616E974FC2D53A08EC963D28 /* GDTCORTransformer_Private.h in Headers */, 9749F5178C242B199D8F80DBC55F1041 /* GDTCORTransport.h in Headers */, 7D41A53C422B8849362A0A50C943F0E1 /* GDTCORTransport_Private.h in Headers */, 6F030A5B151E155313EFC0C843C68018 /* GDTCORUploadBatch.h in Headers */, BE6B23777B825686F33ED40AABA4F17C /* GDTCORUploadCoordinator.h in Headers */, 6337C7DF0D550595E0FC08E973C61709 /* GDTCORUploader.h in Headers */, E9BF3DACAEE3509B06DBDA16478ED73F /* GoogleDataTransport.h in Headers */, D288AE30D1159545D1E3CDB721EA5841 /* GoogleDataTransport-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */ = { isa = PBXNativeTarget; buildConfigurationList = 46F0398C816ED3AA63BC7D085DC1F978 /* Build configuration list for PBXNativeTarget "PromisesObjC" */; buildPhases = ( 4FB87B3C00E20BB81A2110643D08AC3E /* Headers */, C1CF4B15D01CD8E372C0028680453569 /* Copy . Private Headers */, 88441CA33AAB2BA346D73F3E23B24BC1 /* Copy . Public Headers */, 2362F9FEF0DF2A76C2B1EA4130714BDE /* Sources */, F0861647ADFD6A6E877396A52F43B90A /* Frameworks */, 7979E800B2981CF5A3261B4DB562886F /* Resources */, 8BE12CEDA6334EF21206A8668571B037 /* Create Symlinks to Header Folders */, ); buildRules = ( ); dependencies = ( ); name = PromisesObjC; productName = FBLPromises; productReference = 3347A1AB6546F0A3977529B8F199DC41 /* PromisesObjC */; productType = "com.apple.product-type.framework"; }; 329161CB25D509AB980F6AF24CA7547D /* Pods-Spotify */ = { isa = PBXNativeTarget; buildConfigurationList = F9932C06D533E1975770B00862F43DBE /* Build configuration list for PBXNativeTarget "Pods-Spotify" */; buildPhases = ( AAEE67A053DA6BFFD832AC12B98C70ED /* Headers */, BB25ABCA5E3180D0B10028570ACE7E37 /* Sources */, F9DE14B7D99DBDE04A0AC39B6C4F83E3 /* Frameworks */, 1E8AB121F0B1EA326E5FF93A31A85B08 /* Resources */, ); buildRules = ( ); dependencies = ( 13B6A2818085404337546918562D2A58 /* PBXTargetDependency */, 0FCFAC8662107973C6B3833F8221E054 /* PBXTargetDependency */, 1FDE3859A86742A341F1C4B4A8709B65 /* PBXTargetDependency */, 05418E3E8DB5758F81B68AA1AFE84497 /* PBXTargetDependency */, AFE857B2C1F21EEB8EB7DD94E4956E90 /* PBXTargetDependency */, 7A7789E4FA83CCD3B641714A3466F165 /* PBXTargetDependency */, 395489EFA954E16360E8BADF0B7F052F /* PBXTargetDependency */, B08F1C872AAD239599E5E808A891DE1E /* PBXTargetDependency */, 4CF797E9B128427F0965C0BC52003A00 /* PBXTargetDependency */, F71CBD8551E56023AD9C7EC515E0F71C /* PBXTargetDependency */, 018BD7B2414A119D291C56A682084ECF /* PBXTargetDependency */, A1161D8622C2DC03EAB4D7D9F4C33503 /* PBXTargetDependency */, ); name = "Pods-Spotify"; productName = Pods_Spotify; productReference = 97DFD7435A40FB6C7611C58681006DC8 /* Pods-Spotify */; productType = "com.apple.product-type.framework"; }; 3847153A6E5EEFB86565BA840768F429 /* SDWebImage */ = { isa = PBXNativeTarget; buildConfigurationList = BABD4A24CFCBE5B6FE4680AA062FCF04 /* Build configuration list for PBXNativeTarget "SDWebImage" */; buildPhases = ( 104737A7F2F8176AF07A40A019B4DC37 /* Headers */, 4B6FB7A17469A2800E7231A1444746CB /* Sources */, FB6A1E8A43CF1D3ADFB49BA355F0DEA1 /* Frameworks */, DC20DB2B5A8B3C34EAC16992C6449775 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = SDWebImage; productName = SDWebImage; productReference = B0B214D775196BA7CA8E17E53048A493 /* SDWebImage */; productType = "com.apple.product-type.framework"; }; 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */ = { isa = PBXNativeTarget; buildConfigurationList = EECB93EB4A16F755B006CF1CB0DE9A22 /* Build configuration list for PBXNativeTarget "FirebaseCore" */; buildPhases = ( 3C2A858ED01432DE4CA8738997604903 /* Headers */, E1D3B401B4C2E4B3A4F768E6DA5C8F14 /* Sources */, ADD00E3E8A0426067D803A942C3BA84C /* Frameworks */, FE94F70A1A799458E1F82D5F18A02AE4 /* Resources */, ); buildRules = ( ); dependencies = ( C229E2B3E24D709FC76B001754EFA1EF /* PBXTargetDependency */, 201B11D0246FB95D4543FB6BE95EF698 /* PBXTargetDependency */, ); name = FirebaseCore; productName = FirebaseCore; productReference = E2B63D462DB7F827C4B11FD51E4F8E2D /* FirebaseCore */; productType = "com.apple.product-type.framework"; }; 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */ = { isa = PBXNativeTarget; buildConfigurationList = 9F539D1E2A2664475FDEEE8A0269B722 /* Build configuration list for PBXNativeTarget "GoogleDataTransport" */; buildPhases = ( E3A6282DA9E6C688D6415ABB30AAD875 /* Headers */, 2D65A612FEAB4C79C4228721C3B473E7 /* Sources */, DA11FA7A020AFDCA73B93EDF3D3F1073 /* Frameworks */, 9747C1EB513D24C4C51D4CC2EE56F574 /* Resources */, ); buildRules = ( ); dependencies = ( 7E29F90F20C2FBF0E612ED469B18B7F8 /* PBXTargetDependency */, 8AA30AE8AB0BFA17B7E024F8E7E96BDC /* PBXTargetDependency */, 85FEAC43D8F304E679980E6B43979D20 /* PBXTargetDependency */, ); name = GoogleDataTransport; productName = GoogleDataTransport; productReference = 856B5CD56F194FAD26EA91620B66D614 /* GoogleDataTransport */; productType = "com.apple.product-type.framework"; }; 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */ = { isa = PBXNativeTarget; buildConfigurationList = F1704FE42FCDBE50057E175108258411 /* Build configuration list for PBXNativeTarget "FirebaseCoreDiagnostics" */; buildPhases = ( 6EE8726894AAD543472DED1FFF731ED9 /* Headers */, B696C829F99E67EEF6DF2D6048F1EE28 /* Sources */, D8CEF8654739A822265F965518353CC2 /* Frameworks */, 3B88A2AE4E79B707F0B45A336C40956B /* Resources */, ); buildRules = ( ); dependencies = ( A8C51E5D5646759D40788B521985C440 /* PBXTargetDependency */, 0F96D323478F9DB4B93A3015A06674B7 /* PBXTargetDependency */, 951408F8CDF7F8851879BBD03CA335CB /* PBXTargetDependency */, ); name = FirebaseCoreDiagnostics; productName = FirebaseCoreDiagnostics; productReference = 8CC9178C366942FD6FF6A115604EAD58 /* FirebaseCoreDiagnostics */; productType = "com.apple.product-type.framework"; }; 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */ = { isa = PBXNativeTarget; buildConfigurationList = E8B9EEC407F92382DE95FD8E4661E000 /* Build configuration list for PBXNativeTarget "FirebaseInstallations" */; buildPhases = ( 9D4712D998CD283461FDCE793EF01F34 /* Headers */, C11285BF080C3F0235B5C0730640FD1C /* Sources */, D7D014542D95DD38C8C2502010D4CD43 /* Frameworks */, C3A13DEB80BBD9B7449455172FD1FE8C /* Resources */, ); buildRules = ( ); dependencies = ( 77FE8B60CDB8A7EE71694A946F1D253A /* PBXTargetDependency */, 978C41BEAC6DA5656985B8AC107682DF /* PBXTargetDependency */, 5B83E357C22146EA67403D7A55EEDE16 /* PBXTargetDependency */, ); name = FirebaseInstallations; productName = FirebaseInstallations; productReference = 13C8C8B254851998F9289F71229B28A2 /* FirebaseInstallations */; productType = "com.apple.product-type.framework"; }; 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */ = { isa = PBXNativeTarget; buildConfigurationList = 2C558C338876A0E05F2989B002E36AF0 /* Build configuration list for PBXNativeTarget "GoogleUtilities" */; buildPhases = ( 3BD6EB14C2F3B8C73B1A7BF26F2227EC /* Headers */, 6C798BFA74D7509FE9165B55DD4F7984 /* Sources */, A9D2F93060742D670740AA52D4F1A7BE /* Frameworks */, 0640D57AE09D405801C7DE0987AB64A0 /* Resources */, ); buildRules = ( ); dependencies = ( CB5398E5AA2AE6280A5A199E1C1B4DC6 /* PBXTargetDependency */, ); name = GoogleUtilities; productName = GoogleUtilities; productReference = B43874C6CBB50E7134FBEC24BABFE14F /* GoogleUtilities */; productType = "com.apple.product-type.framework"; }; CE9FA8ACD6205C77B753798CD736FAEF /* Appirater */ = { isa = PBXNativeTarget; buildConfigurationList = AE99F33A27C881BD800348FEEB1F30ED /* Build configuration list for PBXNativeTarget "Appirater" */; buildPhases = ( 18C6945B276FCD1F88687DAA6C122954 /* Headers */, 2CB09AF1060077D0DF2C1F44242EF097 /* Sources */, 751E8AA0AFD15DFD93F5C1D10458E77C /* Frameworks */, 771D9E5FCBE5E71EDCB5627928B0C598 /* Resources */, ); buildRules = ( ); dependencies = ( 4D830A4469F8E53C2692049849EE1926 /* PBXTargetDependency */, ); name = Appirater; productName = Appirater; productReference = 23D4D9F87518207E73E88D8FBC7C8DE3 /* Appirater */; productType = "com.apple.product-type.framework"; }; D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */ = { isa = PBXNativeTarget; buildConfigurationList = DE2E2EB059407E49369C43F9C869DE26 /* Build configuration list for PBXNativeTarget "nanopb" */; buildPhases = ( 29E404AD0982126E200F09A0FBE7DCF1 /* Headers */, 233B17B303030E7271F339D634D6D491 /* Sources */, 557258F7F82A8926BA01926A0CB40E15 /* Frameworks */, 25A696811D01AA3CB193F0776F075E7C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = nanopb; productName = nanopb; productReference = 06FC5C9CF96D60C50FCD47D339C91951 /* nanopb */; productType = "com.apple.product-type.framework"; }; E56E80821D169C98E99A62EBCBC60B2C /* Appirater-Appirater */ = { isa = PBXNativeTarget; buildConfigurationList = A48EEF29879720928C371CC9994578C8 /* Build configuration list for PBXNativeTarget "Appirater-Appirater" */; buildPhases = ( A8FBB3E0FEA1E4F406E6833EF981197D /* Sources */, 2F90AC4B1394693187B33E9454925C26 /* Frameworks */, 05AC6C41AA92542B0304B49BCF06A720 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Appirater-Appirater"; productName = Appirater; productReference = 1E9CD753F6BD26760EFAA1DF57482D50 /* Appirater-Appirater */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BFDFE7DC352907FC980B868725387E98 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 1240; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 10.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, ar, ca, cs, da, de, el, en, es, fa, fi, fr, he, hu, hy, id, it, ja, ko, ms, nb, nl, pl, pt, "pt-BR", ro, ru, sk, sv, th, tr, uk, vi, "zh-Hans", "zh-Hant", ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; productRefGroup = 1FC49855849F77DD042774C08FB9A2BD /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( CE9FA8ACD6205C77B753798CD736FAEF /* Appirater */, E56E80821D169C98E99A62EBCBC60B2C /* Appirater-Appirater */, 072CEA044D2EF26F03496D5996BBF59F /* Firebase */, C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */, 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */, 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */, 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */, B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */, 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */, 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */, D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */, 329161CB25D509AB980F6AF24CA7547D /* Pods-Spotify */, 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */, 3847153A6E5EEFB86565BA840768F429 /* SDWebImage */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 05AC6C41AA92542B0304B49BCF06A720 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 44C7A4ACB41F2B0E588E01C98A0BBE31 /* ar.lproj in Resources */, ACC1D2855B9D8168F64EAAB7869DE334 /* ca.lproj in Resources */, 326BA8EC7036E90AE3AE390C40069CAA /* cs.lproj in Resources */, C02890A41D0AB13F90A47695B7F2867F /* da.lproj in Resources */, F1CFF76FC50BF026284051C6CAB9B389 /* de.lproj in Resources */, 8C207CEE5189EAF1C9408F8FCE291E11 /* el.lproj in Resources */, DBCEB21658D3825FA81F27822AADAD11 /* en.lproj in Resources */, 7CA7517C4920C00FDAB34D5152390BA6 /* es.lproj in Resources */, 868244D3347466936301E48DA39BE842 /* fa.lproj in Resources */, 92B70011891306D90A55DC048EEE2DCC /* fi.lproj in Resources */, E5C7FA5788C38AD0308CC3B712DD53A2 /* fr.lproj in Resources */, 8E2FC561736D6CE4D3D7F50A4DE1A540 /* he.lproj in Resources */, 97E10B0D702DED48D5FD1062AA27E9AB /* hu.lproj in Resources */, E9358141369A46CD15097FD5B56279AC /* hy.lproj in Resources */, B48A893A8058A155BCFA6211E8BBAEE4 /* id.lproj in Resources */, 83747CB2CE756E6DE235E7A419CE6B64 /* it.lproj in Resources */, 22A29883D6BD2C46CFCB90EFE1E6B1B6 /* ja.lproj in Resources */, 3339C7240948FC777912F0CCA95C3073 /* ko.lproj in Resources */, F56971E69EB803E06C38E2751620E1E8 /* ms.lproj in Resources */, 762A59E0FF6F1A92B360C6E0A5E4A7CA /* nb.lproj in Resources */, 3C22E2D2D6CDBD066DEDBEA8CEB50170 /* nl.lproj in Resources */, DD5D476FF518ACB3772717620E7D393A /* pl.lproj in Resources */, A179E49E5148E014ACB35B3234A79829 /* pt.lproj in Resources */, 6BDCB1B9157D9AF6B3A78C0E0619FFA2 /* pt-BR.lproj in Resources */, 5C10B5CF2AEA5FB4A63F282ECA9F7CB7 /* ro.lproj in Resources */, C6CF8D2EE14A91A98FC8E5BA749F6D3C /* ru.lproj in Resources */, 7E7E08BEE28A0144B41743F4C265B0AC /* sk.lproj in Resources */, 1E49C6EF4C021764307A50165CAA713A /* sv.lproj in Resources */, B309A5100D011205E23108FA864CEE9F /* th.lproj in Resources */, 3E24B31FF1BB232157618DC8EE918071 /* tr.lproj in Resources */, 0AA4EFD2D280F30CA93FD51643349602 /* uk.lproj in Resources */, F421606C12494E53B7F6B5EAE13352F1 /* vi.lproj in Resources */, 425F70D9B529938D9F19EE92FDBAA4E5 /* zh-Hans.lproj in Resources */, A331C78D95C334AC3C328874419FB071 /* zh-Hant.lproj in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 0640D57AE09D405801C7DE0987AB64A0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 1E8AB121F0B1EA326E5FF93A31A85B08 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 25A696811D01AA3CB193F0776F075E7C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 3B88A2AE4E79B707F0B45A336C40956B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 771D9E5FCBE5E71EDCB5627928B0C598 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 493EEC1796ACFA77A28E7630060A059E /* Appirater-Appirater in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 7979E800B2981CF5A3261B4DB562886F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 9747C1EB513D24C4C51D4CC2EE56F574 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; C3A13DEB80BBD9B7449455172FD1FE8C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; DC20DB2B5A8B3C34EAC16992C6449775 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; FE94F70A1A799458E1F82D5F18A02AE4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 36517C47DEF3A4F55B4079EB176CAC62 /* [CP] Copy XCFrameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks-input-files.xcfilelist", ); name = "[CP] Copy XCFrameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks.sh\"\n"; showEnvVarsInLog = 0; }; 8BE12CEDA6334EF21206A8668571B037 /* Create Symlinks to Header Folders */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Create Symlinks to Header Folders"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "cd \"$CONFIGURATION_BUILD_DIR/$WRAPPER_NAME\" || exit 1\nif [ ! -d Versions ]; then\n # Not a versioned framework, so no need to do anything\n exit 0\nfi\n\npublic_path=\"${PUBLIC_HEADERS_FOLDER_PATH#$CONTENTS_FOLDER_PATH/}\"\nif [ ! -f \"$public_path\" ]; then\n ln -fs \"${PUBLIC_HEADERS_FOLDER_PATH#$WRAPPER_NAME/}\" \"$public_path\"\nfi\n\nprivate_path=\"${PRIVATE_HEADERS_FOLDER_PATH#$CONTENTS_FOLDER_PATH/}\"\nif [ ! -f \"$private_path\" ]; then\n ln -fs \"${PRIVATE_HEADERS_FOLDER_PATH#$WRAPPER_NAME/}\" \"$private_path\"\nfi\n"; }; CB5AF08085F7665FEB6F74BC117BDB28 /* [CP] Copy XCFrameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks-input-files.xcfilelist", ); name = "[CP] Copy XCFrameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 233B17B303030E7271F339D634D6D491 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F3824BB13CE13E5071200A50EA544D21 /* nanopb-dummy.m in Sources */, 5FCA234EC54B026FF4FE2CCCFCB1EE7E /* pb_common.c in Sources */, F269152E11B82F412D3BD37F3E13E6C1 /* pb_decode.c in Sources */, 573CBCA281A2BB0F097329EE28BCFE60 /* pb_encode.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2362F9FEF0DF2A76C2B1EA4130714BDE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A65D64846FFDD7178C8708AF52B5CECF /* FBLPromise.m in Sources */, 34816FB2D38145B7EC7D7A14CF5C93D8 /* FBLPromise+All.m in Sources */, 71A8CC130B0CB8E978F51381AFD722C7 /* FBLPromise+Always.m in Sources */, 7EF58E7AF295CBE99496A96A5D8C13EA /* FBLPromise+Any.m in Sources */, E96C68381CF2BD5D022DE8DC47200C53 /* FBLPromise+Async.m in Sources */, F270EDD68B781B1BE74FE134DADC2FE1 /* FBLPromise+Await.m in Sources */, 25FE5A4220B051E800D649E94BDCF418 /* FBLPromise+Catch.m in Sources */, 38B61754E5D4BFD359FDE9EF1BE4A661 /* FBLPromise+Delay.m in Sources */, 64DF55F7B991438E693B9A078807E55B /* FBLPromise+Do.m in Sources */, 499C6F529CC2F5D8A828374ED7DE1069 /* FBLPromise+Race.m in Sources */, 3464ADF2B770D2FE01006027F4882723 /* FBLPromise+Recover.m in Sources */, AC11317554C3B930BCC98EB6937EEC91 /* FBLPromise+Reduce.m in Sources */, 832067DBD9BAC003B8E5687562E6BBCE /* FBLPromise+Retry.m in Sources */, D6742E4A0E030524C50C7BFD7FEB7292 /* FBLPromise+Testing.m in Sources */, 79B821DE59EE806496A9FD4FF3FEDAB7 /* FBLPromise+Then.m in Sources */, 9F4966331D34AA8CE85C4E7161B8D732 /* FBLPromise+Timeout.m in Sources */, FA9814200D820A4CB4EE5032B8E5973A /* FBLPromise+Validate.m in Sources */, 2797DBBF0C00C0C9AED6C496F1AA1CB8 /* FBLPromise+Wrap.m in Sources */, 1E1FF13F9AB197201C44EEC0D4E9E9EF /* FBLPromiseError.m in Sources */, 41382DAAC306E1762433B4D291DAD21D /* PromisesObjC-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2CB09AF1060077D0DF2C1F44242EF097 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 55A09541F17B3F7BD4C62636A15F6212 /* Appirater.m in Sources */, 475453293C1679D47716F8E17703824E /* Appirater-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2D65A612FEAB4C79C4228721C3B473E7 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F3BF79D7923FCC0DBCCE33CC97D7134B /* cct.nanopb.c in Sources */, ED9E9EC31A5AC510BBF8568956DCA102 /* GDTCCTCompressionHelper.m in Sources */, 6F7307112195D5B6083F450AB2657AE2 /* GDTCCTNanopbHelpers.m in Sources */, EF78F3AE3374BED3DC0FBF7B944B9057 /* GDTCCTUploader.m in Sources */, 3BA8E6BD1A15686C195985599A525C84 /* GDTCCTUploadOperation.m in Sources */, 50FB78A15928E090B123F944AF92656A /* GDTCORAssert.m in Sources */, 56E2818C97A95CF9EC25FF22E92B0F3E /* GDTCORClock.m in Sources */, 6ABBB7192F4FA78A90D45CA78172AC86 /* GDTCORConsoleLogger.m in Sources */, 4649E8C6659D310A609EF296697D6C4F /* GDTCORDirectorySizeTracker.m in Sources */, C4E92D0B4B466595D3F4649BD0827A15 /* GDTCOREndpoints.m in Sources */, B98DAA42533894F17AD50F5DA1EFD347 /* GDTCOREvent.m in Sources */, FE42751E23F9C15EB7A8D8D0569D95F1 /* GDTCOREvent+GDTCCTSupport.m in Sources */, EAABECDA1C6109F989C004C1DCEBE07E /* GDTCORFlatFileStorage.m in Sources */, FE4C5664159A808BD7984CB35D69CFAB /* GDTCORFlatFileStorage+Promises.m in Sources */, F3B6F6BEAD0AFF34EAE82B95A7C56037 /* GDTCORLifecycle.m in Sources */, DA16EA1A389CE66496E2FCBCCDA0D0ED /* GDTCORPlatform.m in Sources */, 388894A87C75381A940680CDD02C263E /* GDTCORReachability.m in Sources */, 036B849251F9D940A08CE9945DF158D2 /* GDTCORRegistrar.m in Sources */, 65F85B6A937820FAA8F2ACA55AAC93D7 /* GDTCORStorageEventSelector.m in Sources */, 153AB0D2CFAFC43A733B6B2A551FBE76 /* GDTCORTransformer.m in Sources */, F02178012C14330925DC2AB2DF60482B /* GDTCORTransport.m in Sources */, 2749C745E4CCB505CACD055CAAF7B51A /* GDTCORUploadBatch.m in Sources */, F4232EBD3CD57E497ECED11C9BECC958 /* GDTCORUploadCoordinator.m in Sources */, 2E1CFA6F381B7FD85B94D8043BA56219 /* GoogleDataTransport-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4B6FB7A17469A2800E7231A1444746CB /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 21D05279AA20EE7A1707A76C11BBCC00 /* NSBezierPath+SDRoundedCorners.m in Sources */, 474163E6CF20CA1AC3698759F2B6D51B /* NSButton+WebCache.m in Sources */, FF66F3E7374A9398D5B08AA23273BC0E /* NSData+ImageContentType.m in Sources */, 994327308CC7ECE7850C804FA0EA5F21 /* NSImage+Compatibility.m in Sources */, 9CEB5B454088D6BCC3A11C759A76161A /* SDAnimatedImage.m in Sources */, 0C76BA74E22A5CFD8207C10026401D80 /* SDAnimatedImagePlayer.m in Sources */, 24DA814C2F7EA7C4187E2BF0B6466E9A /* SDAnimatedImageRep.m in Sources */, D88076DA0593D2835645CA5A92FFA37C /* SDAnimatedImageView.m in Sources */, AA3AE0DF5E35D3E0D2CAE7972D69373E /* SDAnimatedImageView+WebCache.m in Sources */, 4F5982101CB9FB68613E826F137B5E95 /* SDAssociatedObject.m in Sources */, 498AB54D6531F0B57EAAD113AA0CF0F9 /* SDAsyncBlockOperation.m in Sources */, 58FAE3A59FBE896EBBDACB0808067CA5 /* SDDeviceHelper.m in Sources */, 275F99E74511043432C892810081DCAF /* SDDiskCache.m in Sources */, 9D7EBB3A7377C116526B8A511F8CE245 /* SDDisplayLink.m in Sources */, 2C762E5C654B718C19CC98FEB27C2DA4 /* SDFileAttributeHelper.m in Sources */, B88C351562CE8FA4255EA9023213BE4E /* SDGraphicsImageRenderer.m in Sources */, 854D7A4A2872566E4D06870518053225 /* SDImageAPNGCoder.m in Sources */, 3015C91C523C5133A639F57DE0425100 /* SDImageAssetManager.m in Sources */, D7D077F0FA5CAC32ACB65372453C701F /* SDImageAWebPCoder.m in Sources */, 0D2B3D451D64A5A0AE5CE51E975D7494 /* SDImageCache.m in Sources */, 888BEE4C3B238AC407C59D2194939AB9 /* SDImageCacheConfig.m in Sources */, 138DFC533930F784B55D9BB8AACC0E06 /* SDImageCacheDefine.m in Sources */, 4623448C58E0E271F0DF5F4CC6CAFD0D /* SDImageCachesManager.m in Sources */, 78AE96580BD551EB905F72C21AB81E97 /* SDImageCachesManagerOperation.m in Sources */, 64CDB33E49BDBE16653C88756E26B8CB /* SDImageCoder.m in Sources */, E9CF46857D61D2A8AF4C3722C27C61C6 /* SDImageCoderHelper.m in Sources */, DB68D9AFA817850ED9EBBE7F3F378AF5 /* SDImageCodersManager.m in Sources */, 007F57C0030B0CA201A359FD816A3CE1 /* SDImageFrame.m in Sources */, 164DEB7B33A09DD8663942511488AD16 /* SDImageGIFCoder.m in Sources */, F0E28CEF30861F3589A4A662CEA76D80 /* SDImageGraphics.m in Sources */, E33A7CDF3107CD3426ADD0D906100CD4 /* SDImageHEICCoder.m in Sources */, 96479C9C76FDC513C03ADD58168F6911 /* SDImageIOAnimatedCoder.m in Sources */, 536E9C0D3855A6AF7CADCB9FBAFC613D /* SDImageIOCoder.m in Sources */, ABEAA41C35A6CD25C93E622FB129C379 /* SDImageLoader.m in Sources */, 3F567CE353442AE137B71B258409F5DA /* SDImageLoadersManager.m in Sources */, A9A87DA629538D239C4B7401EB56AEB5 /* SDImageTransformer.m in Sources */, 61CD5A24511DC980EFF397CF57AC50F5 /* SDInternalMacros.m in Sources */, 0D0AF4DF934B7BE39EC6E287B003BBA6 /* SDMemoryCache.m in Sources */, 4075297FF5A21C84EF513521FD7B34EF /* SDWeakProxy.m in Sources */, 0DEECB4887F8ED8A8304156D1C440639 /* SDWebImage-dummy.m in Sources */, 67B0CAAD8E88B9A2357C41E73EA2EB48 /* SDWebImageCacheKeyFilter.m in Sources */, 05102BD9E7BCB80DC7B64A34D0E7850B /* SDWebImageCacheSerializer.m in Sources */, E4AF87F8012F919F00E2E8698A1A3C33 /* SDWebImageCompat.m in Sources */, 49F97D8BA4E81A5E0EF866A293392AF2 /* SDWebImageDefine.m in Sources */, D68F08F717E95BABFB43AE705D943426 /* SDWebImageDownloader.m in Sources */, FE05A1BF611E69C94BC7A9EADBE10B2F /* SDWebImageDownloaderConfig.m in Sources */, DE0BA471FB4FC10C0B25BDE4F22F5B76 /* SDWebImageDownloaderDecryptor.m in Sources */, DB61822BEA875B413C858E1166590C9F /* SDWebImageDownloaderOperation.m in Sources */, C27BD8C6B4E713A8E7B7B76B80E45823 /* SDWebImageDownloaderRequestModifier.m in Sources */, FB359FC2B8375E01A8A8F84D3C50EE82 /* SDWebImageDownloaderResponseModifier.m in Sources */, 5F49C3C9A3733CD50642E0B57D405D44 /* SDWebImageError.m in Sources */, 343B53EB998CC5B609B8DE84BAD882E0 /* SDWebImageIndicator.m in Sources */, FE4845B6FDD260059CDF9E26965CA31B /* SDWebImageManager.m in Sources */, BE649A29E5FB32C54FDAE55994804F6D /* SDWebImageOperation.m in Sources */, 7A424FED21A399FE2610DCB710A4A3FB /* SDWebImageOptionsProcessor.m in Sources */, 62A22A4C10675F9B65C7677CFD6C4676 /* SDWebImagePrefetcher.m in Sources */, 3B72C235C1AB8944333C2322F08B6B46 /* SDWebImageTransition.m in Sources */, C55A2D001850D3F407D968CFF1C545E2 /* UIButton+WebCache.m in Sources */, AE89C60F9CD4D6E1400DD304CE2A2B9E /* UIColor+SDHexString.m in Sources */, BCB7B61DB37BA3EDB8CBBF258DC66446 /* UIImage+ExtendedCacheData.m in Sources */, F497CD01EF6568475FD363A248D78A4B /* UIImage+ForceDecode.m in Sources */, DEE74CD0E78DE0DB4056A8A3244B6ED1 /* UIImage+GIF.m in Sources */, 412EE262DCED176A73591124DBDBA97C /* UIImage+MemoryCacheCost.m in Sources */, 28532908E40E423052AA43B0000028BF /* UIImage+Metadata.m in Sources */, 5B3271A67137276C4B2CD554BAB9F4DA /* UIImage+MultiFormat.m in Sources */, BF9A6E2CB8F67686C4B7E04538664919 /* UIImage+Transform.m in Sources */, C4762BD85B320B9EBE199F7CDFBCED12 /* UIImageView+HighlightedWebCache.m in Sources */, 10E9E4249C8C22A571EBFECF3C0676CC /* UIImageView+WebCache.m in Sources */, A3BFF0CDB72BC173BEA1C8ADDBDB0D1D /* UIView+WebCache.m in Sources */, 429E5CC928985357D07058FDEE8069FA /* UIView+WebCacheOperation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6C798BFA74D7509FE9165B55DD4F7984 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 11A2D8485890983668BBBDB3BE7602AC /* GoogleUtilities-dummy.m in Sources */, 685481CA7A154E8AAE00089FC07BDC74 /* GULAppDelegateSwizzler.m in Sources */, B3580A5C39E7E3B32DE3AC015B6DA74A /* GULAppEnvironmentUtil.m in Sources */, 06A3ABB4421B935533DD7D039442ADCF /* GULHeartbeatDateStorage.m in Sources */, AF8528758C11B8B5E020066DE3EF45B8 /* GULHeartbeatDateStorageUserDefaults.m in Sources */, A20FADF8B2F5E792711B7E906B93E692 /* GULKeychainStorage.m in Sources */, FB0E00B080CD4EE84EF1BF65C47F3185 /* GULKeychainUtils.m in Sources */, 74B90EA2A2E4ADC58C8693B6FB534D85 /* GULLogger.m in Sources */, 3CDE39AF868FA2AFB18B9739A7F71A7F /* GULMutableDictionary.m in Sources */, 7975CB02E10D105FA23A30B254C25F4F /* GULNetwork.m in Sources */, DD9CF5BA0F1AF0448E29B8E7FE427C20 /* GULNetworkConstants.m in Sources */, EE207039B394C5D8BBA475814B4E9830 /* GULNetworkURLSession.m in Sources */, B233AA226EBC0EB5843452893330638B /* GULNSData+zlib.m in Sources */, 87EE04B662C6A6A6E3731E657170FBB8 /* GULReachabilityChecker.m in Sources */, D1D3F45D15F5C5775073C7747ED94B55 /* GULSceneDelegateSwizzler.m in Sources */, 8D374E688F039AFD9D5F03C0A24DC4B6 /* GULSecureCoding.m in Sources */, F4310C2EF76937100D2C74EBB593BFC4 /* GULSwizzler.m in Sources */, F2EC479E66AABB97FBFB289846C339EF /* GULURLSessionDataResponse.m in Sources */, 85DBD9AB9CEC227EB078B8EC52906809 /* GULUserDefaults.m in Sources */, 4AD40F5B3B6CBC790C65872ACCC69B0B /* NSURLSession+GULPromises.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; A8FBB3E0FEA1E4F406E6833EF981197D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B696C829F99E67EEF6DF2D6048F1EE28 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( EF17DB58C7BE9A3304D466EF87041014 /* FIRCoreDiagnostics.m in Sources */, 421ADEBE65337F1EB365DD161EBAC21C /* firebasecore.nanopb.c in Sources */, DE2AB52CD8EFC8E819452F737447F4B0 /* FirebaseCoreDiagnostics-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BB25ABCA5E3180D0B10028570ACE7E37 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( AE1F2017E0DEFDFDFA2143A647D2BC15 /* Pods-Spotify-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C11285BF080C3F0235B5C0730640FD1C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 044993D3D534546E1F168C7CABEFD382 /* FIRCurrentDateProvider.m in Sources */, 694921E04F46359CCC4C2A3DF0ADBD04 /* FirebaseInstallations-dummy.m in Sources */, ACFBB1893DBF761BB053683178D43A45 /* FIRInstallations.m in Sources */, D0C26B55798A5A3824920D1D426AEE51 /* FIRInstallationsAPIService.m in Sources */, 0C8BD39D8D79C1A9BEFC904C457DCE89 /* FIRInstallationsAuthTokenResult.m in Sources */, 5331944436C43951CC5FA7788DEE9BCF /* FIRInstallationsBackoffController.m in Sources */, F2B107BBDDF18E390CE5F31FEAC37B5B /* FIRInstallationsErrorUtil.m in Sources */, D9CFDF531DF1146A1A3E694A360CB0A7 /* FIRInstallationsHTTPError.m in Sources */, B48E2B8546BDCA558F808B1CB4849CEF /* FIRInstallationsIDController.m in Sources */, 4844A5C12A400CAABFD7CF2283FDAEAC /* FIRInstallationsIIDStore.m in Sources */, 83F5B3ABAFBB47CDA2E64D9C879F2167 /* FIRInstallationsIIDTokenStore.m in Sources */, 05133941F28BBE0615644D0AFEC73AF3 /* FIRInstallationsItem.m in Sources */, 2AF06216CDA7D6C38831BB201F73B566 /* FIRInstallationsItem+RegisterInstallationAPI.m in Sources */, 2CC0D5EBD928AAABF1DB1AE58E8710B3 /* FIRInstallationsLogger.m in Sources */, 4795853F6479422BFD716A20342E99EB /* FIRInstallationsSingleOperationPromiseCache.m in Sources */, D4D727FEE4399BCF942F3512A383917B /* FIRInstallationsStore.m in Sources */, 5F1DA411EF4EF25F7278116F9DBD8FB8 /* FIRInstallationsStoredAuthToken.m in Sources */, 0C80C3D7BC320914043FB768C88C8587 /* FIRInstallationsStoredItem.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; E1D3B401B4C2E4B3A4F768E6DA5C8F14 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C6C08B108C2F5D3E6828AFC268E87341 /* FIRAnalyticsConfiguration.m in Sources */, 03C3E562593A65D5184DAE4A6DDD883F /* FIRApp.m in Sources */, C14230A8434F2DC9A5100B4DF5EDA4B4 /* FIRAppAssociationRegistration.m in Sources */, 9B9D9E2182957C42538AD42CF824823B /* FIRBundleUtil.m in Sources */, 55770A7793FDC52677664F24B19FC7C6 /* FIRComponent.m in Sources */, 8B6E3CBB15DD0345506BCEA811AEAA43 /* FIRComponentContainer.m in Sources */, 74849A92CED98D844690D9779D6F6DD1 /* FIRComponentType.m in Sources */, 67B3965694762B918FFBF665F842D881 /* FIRConfiguration.m in Sources */, 3098423E501E28F376604CE13D8CD5E0 /* FIRCoreDiagnosticsConnector.m in Sources */, 0CC10185DD0B362B0934EDE2C557D7E0 /* FIRDependency.m in Sources */, 052F793DE768D1EDB5C1EC87A5A21F58 /* FIRDiagnosticsData.m in Sources */, 2C4D5AB870A681E4EA5340FBD70FA010 /* FirebaseCore-dummy.m in Sources */, FA24FF48B63577BA0DC691E35C9F7FA9 /* FIRFirebaseUserAgent.m in Sources */, BE141B44BBCE0650A03846E0D38F86EC /* FIRHeartbeatInfo.m in Sources */, 84F3CB5D0530A295C9D23F675A5904B5 /* FIRLogger.m in Sources */, F595C8E6D01D6FFEC46D540A198AD062 /* FIROptions.m in Sources */, 6CFA7E848AADCF3237F5D70EB4019775 /* FIRVersion.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 018BD7B2414A119D291C56A682084ECF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SDWebImage; target = 3847153A6E5EEFB86565BA840768F429 /* SDWebImage */; targetProxy = 179483F0AEF1C518BA00B35B81A30639 /* PBXContainerItemProxy */; }; 05418E3E8DB5758F81B68AA1AFE84497 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCore; target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; targetProxy = B16C5D96A221C36E30318D4000EF80F2 /* PBXContainerItemProxy */; }; 07F7AACA5DCE705327010DA25FBEF165 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCore; target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; targetProxy = 5A2B9082AADF585B09978FD211A672E2 /* PBXContainerItemProxy */; }; 0F96D323478F9DB4B93A3015A06674B7 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 5ED9D700A988839F2AED6A8424F05591 /* PBXContainerItemProxy */; }; 0FCFAC8662107973C6B3833F8221E054 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Firebase; target = 072CEA044D2EF26F03496D5996BBF59F /* Firebase */; targetProxy = D34555543663478CC798C373DD6F3097 /* PBXContainerItemProxy */; }; 12F6562F185F60896BBCDADD4019778E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = nanopb; target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; targetProxy = 3068F1E435C57EA32BE211ECBD073BC6 /* PBXContainerItemProxy */; }; 13B6A2818085404337546918562D2A58 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Appirater; target = CE9FA8ACD6205C77B753798CD736FAEF /* Appirater */; targetProxy = 4AE8370CEECA1D1E24DE01B095051767 /* PBXContainerItemProxy */; }; 17945370CE1B53AF222677EE98E01A93 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 1EA446CB14655B63DDF173D56348CF0C /* PBXContainerItemProxy */; }; 1FDE3859A86742A341F1C4B4A8709B65 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseAnalytics; target = C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */; targetProxy = 3EABBDC05624720577DC38CFFE35516E /* PBXContainerItemProxy */; }; 201B11D0246FB95D4543FB6BE95EF698 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 6536CA055DA2CE1A6BC4ECCA78BE9E3D /* PBXContainerItemProxy */; }; 395489EFA954E16360E8BADF0B7F052F /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleAppMeasurement; target = B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */; targetProxy = FDD63A1FAEDABC2F1E1949DCC5AF3165 /* PBXContainerItemProxy */; }; 4CF797E9B128427F0965C0BC52003A00 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 13D9CFCD8B7BD73E7C9DE7108252C17E /* PBXContainerItemProxy */; }; 4D830A4469F8E53C2692049849EE1926 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Appirater-Appirater"; target = E56E80821D169C98E99A62EBCBC60B2C /* Appirater-Appirater */; targetProxy = 41A499968FD5AC9EAE0375E50C6432F6 /* PBXContainerItemProxy */; }; 5B83E357C22146EA67403D7A55EEDE16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = PromisesObjC; target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; targetProxy = 75A52B3F3AF7540549FE42CCD7D77E21 /* PBXContainerItemProxy */; }; 60B836A00436698D56971C2DF9FACBD3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseAnalytics; target = C49E7A4D59E5C8BE8DE9FB1EFB150185 /* FirebaseAnalytics */; targetProxy = 299349E1BCD52B21826853337CF6A1C4 /* PBXContainerItemProxy */; }; 77FE8B60CDB8A7EE71694A946F1D253A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCore; target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; targetProxy = ECA56526923E8FA4E91B31255FA8A6C0 /* PBXContainerItemProxy */; }; 7A7789E4FA83CCD3B641714A3466F165 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseInstallations; target = 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */; targetProxy = BC0ECFA70CF24D5AD9BA339E3D2F9476 /* PBXContainerItemProxy */; }; 7AFDA11FCD515215D24F9BDC345530D3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseInstallations; target = 87803597EB3F20FC46472B85392EC4FD /* FirebaseInstallations */; targetProxy = 8C5626444E1A84EEEC6B1F447CDC3835 /* PBXContainerItemProxy */; }; 7DA3592F4AED73F685BA39AED564AB8C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 2F4219A4D7ABBC820468F9119F658E52 /* PBXContainerItemProxy */; }; 7E29F90F20C2FBF0E612ED469B18B7F8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 019A23FE239BD69A9982DFBA9D477A03 /* PBXContainerItemProxy */; }; 85FEAC43D8F304E679980E6B43979D20 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = nanopb; target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; targetProxy = 42781FFD2FA1F2EDE97339A0136EDCD2 /* PBXContainerItemProxy */; }; 8AA30AE8AB0BFA17B7E024F8E7E96BDC /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = PromisesObjC; target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; targetProxy = 4605540DE00C391F1995371BAF739423 /* PBXContainerItemProxy */; }; 951408F8CDF7F8851879BBD03CA335CB /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = nanopb; target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; targetProxy = A977FEFABC64BF1427A465B2BB858E38 /* PBXContainerItemProxy */; }; 978C41BEAC6DA5656985B8AC107682DF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleUtilities; target = 8D7F5D5DD528D21A72DC87ADA5B12E2D /* GoogleUtilities */; targetProxy = 64BB0EA73EC74FA5D442D982996CEE10 /* PBXContainerItemProxy */; }; A1161D8622C2DC03EAB4D7D9F4C33503 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = nanopb; target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; targetProxy = 1CA617FF6BC13C778A37FCD64EB5171C /* PBXContainerItemProxy */; }; A8C51E5D5646759D40788B521985C440 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleDataTransport; target = 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */; targetProxy = D09E65E94D05A8E445515725C350BE0D /* PBXContainerItemProxy */; }; AFE857B2C1F21EEB8EB7DD94E4956E90 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCoreDiagnostics; target = 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */; targetProxy = 0CCABEB11C23BF29F6C694531602F292 /* PBXContainerItemProxy */; }; B08F1C872AAD239599E5E808A891DE1E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleDataTransport; target = 5C0371EE948D0357B8EE0E34ABB44BF0 /* GoogleDataTransport */; targetProxy = E01CEC52D49138FF303416CCAC27428F /* PBXContainerItemProxy */; }; C229E2B3E24D709FC76B001754EFA1EF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCoreDiagnostics; target = 620E05868772C10B4920DC7E324F2C87 /* FirebaseCoreDiagnostics */; targetProxy = 4AF8ADE52426F73572B9DCF5DAF9E166 /* PBXContainerItemProxy */; }; CB5398E5AA2AE6280A5A199E1C1B4DC6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = PromisesObjC; target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; targetProxy = E36D5F0B788F0DF60B17237B05CF025C /* PBXContainerItemProxy */; }; D4BF4589DADC31160B77BB8112E61B37 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = nanopb; target = D2B5E7DCCBBFB32341D857D01211A1A3 /* nanopb */; targetProxy = 66522F2C26FB2DA91D7F26867BBFD45D /* PBXContainerItemProxy */; }; E57689F1671B9303E2E6FC780D60D89A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = GoogleAppMeasurement; target = B53D977A951AFC38B21751B706C1DF83 /* GoogleAppMeasurement */; targetProxy = 107F5D2853F4AAFB42A81181FDB1E1DD /* PBXContainerItemProxy */; }; E7A21F113AF491DD9E75451709813C86 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FirebaseCore; target = 4402AFF83DBDC4DD07E198685FDC2DF2 /* FirebaseCore */; targetProxy = 017C8F825B2F93FFCB2F173D75B0623D /* PBXContainerItemProxy */; }; F71CBD8551E56023AD9C7EC515E0F71C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = PromisesObjC; target = 2BBF7206D7FAC92C82A042A99C4A98F8 /* PromisesObjC */; targetProxy = FF55F8919770C873139545DDB9B82E39 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 00403A510F1318D67AA55C1B4DCB17FB /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = FC6A0016A0D695FE4F0D2BCB7B58E53B /* SDWebImage.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap"; PRODUCT_MODULE_NAME = SDWebImage; PRODUCT_NAME = SDWebImage; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 0C87BBC2FC7A3B2EE909C0E531227BDB /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = CE9EF082FAA32B5B2D0B0C7CC14C11E6 /* Appirater.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Appirater/Appirater-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Appirater/Appirater-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Appirater/Appirater.modulemap"; PRODUCT_MODULE_NAME = Appirater; PRODUCT_NAME = Appirater; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 0D9DB464E7E8F78ACFF191182E388FEC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F582A3CDAC1DDA3FCEA8CB2C13293A4D /* Firebase.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 12221EC8440A840E28E5CC199D922CA0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B74D76E7DA492E2DD4E2C7CE3A5874F9 /* Appirater.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Appirater/Appirater-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Appirater/Appirater-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Appirater/Appirater.modulemap"; PRODUCT_MODULE_NAME = Appirater; PRODUCT_NAME = Appirater; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 134E3B546F96D2583CC628D41DBDB180 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4D06A1E8D589DEE651EA569B253B9DA6 /* GoogleUtilities.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities.modulemap"; PRODUCT_MODULE_NAME = GoogleUtilities; PRODUCT_NAME = GoogleUtilities; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 1D34E69BAFE3E030F9A2D5B95C86A102 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C5674D03956C9BD31033CABEEFE4F2E1 /* PromisesObjC.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/PromisesObjC/PromisesObjC-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/PromisesObjC/PromisesObjC.modulemap"; PRODUCT_MODULE_NAME = FBLPromises; PRODUCT_NAME = FBLPromises; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 1DAF018696B7F8174780EDABB5475B36 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B526E38BA63C966AF02EA09F26802535 /* PromisesObjC.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/PromisesObjC/PromisesObjC-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/PromisesObjC/PromisesObjC.modulemap"; PRODUCT_MODULE_NAME = FBLPromises; PRODUCT_NAME = FBLPromises; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 28EC231B69D7402FF9B7D4F4C4D0F3C8 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 02EBCB4694F493398B30D145521759BB /* Pods-Spotify.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-Spotify/Pods-Spotify-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-Spotify/Pods-Spotify.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 2D52ABB3E2BF011FFF739456894CD19F /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = BE37397A0A9B84C77A7B34A9406A3952 /* GoogleDataTransport.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport.modulemap"; PRODUCT_MODULE_NAME = GoogleDataTransport; PRODUCT_NAME = GoogleDataTransport; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 332926E851FE7305012BBEC55FFC5AEF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B74D76E7DA492E2DD4E2C7CE3A5874F9 /* Appirater.debug.xcconfig */; buildSettings = { CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Appirater"; IBSC_MODULE = Appirater; INFOPLIST_FILE = "Target Support Files/Appirater/ResourceBundle-Appirater-Appirater-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; PRODUCT_NAME = Appirater; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; name = Debug; }; 48EC2697B4F9A66BC24175655B4A998F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = AA8BA4718A81C87F777362D613444D44 /* Firebase.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 4E3EE982B78A1A75CC6801762E8BE464 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C52C466DF84E99205E54E0AEC7DF14B2 /* FirebaseCoreDiagnostics.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.modulemap"; PRODUCT_MODULE_NAME = FirebaseCoreDiagnostics; PRODUCT_NAME = FirebaseCoreDiagnostics; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 546A2D53BEC6F376A6E2D211570B5396 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 83D038C4E24C71BFAE211A6E0CDA58BA /* nanopb.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/nanopb/nanopb-prefix.pch"; INFOPLIST_FILE = "Target Support Files/nanopb/nanopb-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/nanopb/nanopb.modulemap"; PRODUCT_MODULE_NAME = nanopb; PRODUCT_NAME = nanopb; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 58F714734C27F841BF6B29B3CCDA724A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8B4E22AE507DF453D28673AD92339EB1 /* FirebaseInstallations.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations.modulemap"; PRODUCT_MODULE_NAME = FirebaseInstallations; PRODUCT_NAME = FirebaseInstallations; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 5A47CD60E027340722BF16BAD14FC1C4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4E43BB424620088D2FC848F87869992C /* FirebaseInstallations.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseInstallations/FirebaseInstallations.modulemap"; PRODUCT_MODULE_NAME = FirebaseInstallations; PRODUCT_NAME = FirebaseInstallations; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 5F4590E8E717F8F4D96D01E8C70174C2 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 27EFCAB3E6A3DE572444045971D498C5 /* GoogleAppMeasurement.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6DDFA8139140EA62C563D6E8645B44CF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = FAE1DA94ED4CE423E2B4A57176826E98 /* FirebaseAnalytics.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 71243899B61152433AAE34EE86456EE7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 748047E47075D6AC3282B32DDE15B438 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BA393B4B24C719DDDC878C0A08B2D1BB /* GoogleDataTransport.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/GoogleDataTransport/GoogleDataTransport.modulemap"; PRODUCT_MODULE_NAME = GoogleDataTransport; PRODUCT_NAME = GoogleDataTransport; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 75D70EEEA0A456E14F7338A7F342964E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 7678840E7DB9D444CD0C242F0787F45D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 695ADBFC9C4D04D17897085D1ABBBAA7 /* GoogleAppMeasurement.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 7A93960A243AF7227E8EBFB56CD4EAA7 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2368DD7767138A73CD7D9EADED904481 /* FirebaseCore.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseCore/FirebaseCore-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseCore/FirebaseCore.modulemap"; PRODUCT_MODULE_NAME = FirebaseCore; PRODUCT_NAME = FirebaseCore; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; AF21AE07DDE4F10D240706F04AD4567F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = ACE81B64A65F8AF15E7C822CDF6A73E6 /* FirebaseCore.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseCore/FirebaseCore-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseCore/FirebaseCore.modulemap"; PRODUCT_MODULE_NAME = FirebaseCore; PRODUCT_NAME = FirebaseCore; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; B0A83E2B33F87D1C7856C6DF93404465 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 32073165CB0BA8A73E608799A7994586 /* SDWebImage.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap"; PRODUCT_MODULE_NAME = SDWebImage; PRODUCT_NAME = SDWebImage; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; DA067B3AF66E86FD25FFB915E435453E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = CE9EF082FAA32B5B2D0B0C7CC14C11E6 /* Appirater.release.xcconfig */; buildSettings = { CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Appirater"; IBSC_MODULE = Appirater; INFOPLIST_FILE = "Target Support Files/Appirater/ResourceBundle-Appirater-Appirater-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; PRODUCT_NAME = Appirater; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; name = Release; }; DFD5C00CB965B63CB8D2E4C518CF8B54 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = DD050D11898BDC1AE3F6FC357A319586 /* FirebaseCoreDiagnostics.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.modulemap"; PRODUCT_MODULE_NAME = FirebaseCoreDiagnostics; PRODUCT_NAME = FirebaseCoreDiagnostics; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; E468F7D6C45D35D750FB1213735ABBD5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = C5E56AF681704D05296BF6544BFD6DBC /* nanopb.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/nanopb/nanopb-prefix.pch"; INFOPLIST_FILE = "Target Support Files/nanopb/nanopb-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/nanopb/nanopb.modulemap"; PRODUCT_MODULE_NAME = nanopb; PRODUCT_NAME = nanopb; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; E83625FA8D055DEE663B783F0918F038 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 04119D68F2961D5912F24F5066E00D0D /* FirebaseAnalytics.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; EB2683577A50A4F30A82DDF33DFE8068 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D934FF1B0DBCF555CAF470E0CF5AA900 /* GoogleUtilities.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/GoogleUtilities/GoogleUtilities.modulemap"; PRODUCT_MODULE_NAME = GoogleUtilities; PRODUCT_NAME = GoogleUtilities; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; ECD847006548A7DD0D41165F6B50CA85 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 20DAC9944D4D96AD4E142D77D43D9F68 /* Pods-Spotify.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-Spotify/Pods-Spotify-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-Spotify/Pods-Spotify.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 252336D783BCD0B4415B0521B0962F14 /* Build configuration list for PBXAggregateTarget "FirebaseAnalytics" */ = { isa = XCConfigurationList; buildConfigurations = ( E83625FA8D055DEE663B783F0918F038 /* Debug */, 6DDFA8139140EA62C563D6E8645B44CF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C558C338876A0E05F2989B002E36AF0 /* Build configuration list for PBXNativeTarget "GoogleUtilities" */ = { isa = XCConfigurationList; buildConfigurations = ( EB2683577A50A4F30A82DDF33DFE8068 /* Debug */, 134E3B546F96D2583CC628D41DBDB180 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 46F0398C816ED3AA63BC7D085DC1F978 /* Build configuration list for PBXNativeTarget "PromisesObjC" */ = { isa = XCConfigurationList; buildConfigurations = ( 1D34E69BAFE3E030F9A2D5B95C86A102 /* Debug */, 1DAF018696B7F8174780EDABB5475B36 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( 75D70EEEA0A456E14F7338A7F342964E /* Debug */, 71243899B61152433AAE34EE86456EE7 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 9F539D1E2A2664475FDEEE8A0269B722 /* Build configuration list for PBXNativeTarget "GoogleDataTransport" */ = { isa = XCConfigurationList; buildConfigurations = ( 748047E47075D6AC3282B32DDE15B438 /* Debug */, 2D52ABB3E2BF011FFF739456894CD19F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A48EEF29879720928C371CC9994578C8 /* Build configuration list for PBXNativeTarget "Appirater-Appirater" */ = { isa = XCConfigurationList; buildConfigurations = ( 332926E851FE7305012BBEC55FFC5AEF /* Debug */, DA067B3AF66E86FD25FFB915E435453E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; AE99F33A27C881BD800348FEEB1F30ED /* Build configuration list for PBXNativeTarget "Appirater" */ = { isa = XCConfigurationList; buildConfigurations = ( 12221EC8440A840E28E5CC199D922CA0 /* Debug */, 0C87BBC2FC7A3B2EE909C0E531227BDB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B703E077B86E18E406CBE29C0DBD8D5F /* Build configuration list for PBXAggregateTarget "GoogleAppMeasurement" */ = { isa = XCConfigurationList; buildConfigurations = ( 7678840E7DB9D444CD0C242F0787F45D /* Debug */, 5F4590E8E717F8F4D96D01E8C70174C2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BABD4A24CFCBE5B6FE4680AA062FCF04 /* Build configuration list for PBXNativeTarget "SDWebImage" */ = { isa = XCConfigurationList; buildConfigurations = ( B0A83E2B33F87D1C7856C6DF93404465 /* Debug */, 00403A510F1318D67AA55C1B4DCB17FB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C6E4A38F24896DABC7A364E6DB2C7A7F /* Build configuration list for PBXAggregateTarget "Firebase" */ = { isa = XCConfigurationList; buildConfigurations = ( 48EC2697B4F9A66BC24175655B4A998F /* Debug */, 0D9DB464E7E8F78ACFF191182E388FEC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DE2E2EB059407E49369C43F9C869DE26 /* Build configuration list for PBXNativeTarget "nanopb" */ = { isa = XCConfigurationList; buildConfigurations = ( 546A2D53BEC6F376A6E2D211570B5396 /* Debug */, E468F7D6C45D35D750FB1213735ABBD5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E8B9EEC407F92382DE95FD8E4661E000 /* Build configuration list for PBXNativeTarget "FirebaseInstallations" */ = { isa = XCConfigurationList; buildConfigurations = ( 5A47CD60E027340722BF16BAD14FC1C4 /* Debug */, 58F714734C27F841BF6B29B3CCDA724A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; EECB93EB4A16F755B006CF1CB0DE9A22 /* Build configuration list for PBXNativeTarget "FirebaseCore" */ = { isa = XCConfigurationList; buildConfigurations = ( AF21AE07DDE4F10D240706F04AD4567F /* Debug */, 7A93960A243AF7227E8EBFB56CD4EAA7 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F1704FE42FCDBE50057E175108258411 /* Build configuration list for PBXNativeTarget "FirebaseCoreDiagnostics" */ = { isa = XCConfigurationList; buildConfigurations = ( 4E3EE982B78A1A75CC6801762E8BE464 /* Debug */, DFD5C00CB965B63CB8D2E4C518CF8B54 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F9932C06D533E1975770B00862F43DBE /* Build configuration list for PBXNativeTarget "Pods-Spotify" */ = { isa = XCConfigurationList; buildConfigurations = ( 28EC231B69D7402FF9B7D4F4C4D0F3C8 /* Debug */, ECD847006548A7DD0D41165F6B50CA85 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; } ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/Appirater-Appirater.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/Appirater.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/Firebase.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/FirebaseAnalytics.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/FirebaseCore.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/FirebaseCoreDiagnostics.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/FirebaseInstallations.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/GoogleAppMeasurement.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/GoogleDataTransport.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/GoogleUtilities.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/Pods-Spotify.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/PromisesObjC.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/SDWebImage.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/nanopb.xcscheme ================================================ ================================================ FILE: Pods/Pods.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Appirater-Appirater.xcscheme isShown orderHint 1 Appirater.xcscheme isShown orderHint 0 Firebase.xcscheme isShown orderHint 2 FirebaseAnalytics.xcscheme isShown orderHint 3 FirebaseCore.xcscheme isShown orderHint 4 FirebaseCoreDiagnostics.xcscheme isShown orderHint 5 FirebaseInstallations.xcscheme isShown orderHint 6 GoogleAppMeasurement.xcscheme isShown orderHint 7 GoogleDataTransport.xcscheme isShown orderHint 8 GoogleUtilities.xcscheme isShown orderHint 9 Pods-Spotify.xcscheme isShown orderHint 11 PromisesObjC.xcscheme isShown orderHint 12 SDWebImage.xcscheme isShown orderHint 13 nanopb.xcscheme isShown orderHint 10 SuppressBuildableAutocreation ================================================ FILE: Pods/PromisesObjC/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Pods/PromisesObjC/README.md ================================================ [![Apache License](https://img.shields.io/github/license/google/promises.svg)](LICENSE) [![Travis](https://api.travis-ci.org/google/promises.svg?branch=master)](https://travis-ci.org/google/promises) [![Gitter Chat](https://badges.gitter.im/google/promises.svg)](https://gitter.im/google/promises) ![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-blue.svg?longCache=true&style=flat) ![Languages](https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg?longCache=true&style=flat) ![Package Managers](https://img.shields.io/badge/supports-Bazel%20%7C%20SwiftPM%20%7C%20CocoaPods%20%7C%20Carthage-yellow.svg?longCache=true&style=flat) # Promises Promises is a modern framework that provides a synchronization construct for Objective-C and Swift to facilitate writing asynchronous code. * [Introduction](g3doc/index.md) * [The problem with async code](g3doc/index.md#the-problem-with-async-code) * [Promises to the rescue](g3doc/index.md#promises-to-the-rescue) * [What is a promise?](g3doc/index.md#what-is-a-promise) * [Framework](g3doc/index.md#framework) * [Features](g3doc/index.md#features) * [Benchmark](g3doc/index.md#benchmark) * [Getting started](g3doc/index.md#getting-started) * [Add dependency](g3doc/index.md#add-dependency) * [Import](g3doc/index.md#import) * [Adopt](g3doc/index.md#adopt) * [Basics](g3doc/index.md#basics) * [Creating promises](g3doc/index.md#creating-promises) * [Async](g3doc/index.md#async) * [Do](g3doc/index.md#do) * [Pending](g3doc/index.md#pending) * [Resolved](g3doc/index.md#create-a-resolved-promise) * [Observing fulfillment](g3doc/index.md#observing-fulfillment) * [Then](g3doc/index.md#then) * [Observing rejection](g3doc/index.md#observing-rejection) * [Catch](g3doc/index.md#catch) * [Extensions](g3doc/index.md#extensions) * [All](g3doc/index.md#all) * [Always](g3doc/index.md#always) * [Any](g3doc/index.md#any) * [AwaitPromise](g3doc/index.md#awaitpromise) * [Delay](g3doc/index.md#delay) * [Race](g3doc/index.md#race) * [Recover](g3doc/index.md#recover) * [Reduce](g3doc/index.md#reduce) * [Retry](g3doc/index.md#retry) * [Timeout](g3doc/index.md#timeout) * [Validate](g3doc/index.md#validate) * [Wrap](g3doc/index.md#wrap) * [Advanced topics](g3doc/index.md#advanced-topics) * [Default dispatch queue](g3doc/index.md#default-dispatch-queue) * [Ownership and retain cycles](g3doc/index.md#ownership-and-retain-cycles) * [Testing](g3doc/index.md#testing) * [Objective-C <-> Swift interoperability](g3doc/index.md#objective-c---swift-interoperability) * [Dot-syntax in Objective-C](g3doc/index.md#dot-syntax-in-objective-c) * [Anti-patterns](g3doc/index.md#anti-patterns) * [Broken chain](g3doc/index.md#broken-chain) * [Nested promises](g3doc/index.md#nested-promises) ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+All.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+All.h" #import "FBLPromise+Async.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (AllAdditions) + (FBLPromise *)all:(NSArray *)promises { return [self onQueue:self.defaultDispatchQueue all:promises]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue all:(NSArray *)allPromises { NSParameterAssert(queue); NSParameterAssert(allPromises); if (allPromises.count == 0) { return [[FBLPromise alloc] initWithResolution:@[]]; } NSMutableArray *promises = [allPromises mutableCopy]; return [FBLPromise onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { for (NSUInteger i = 0; i < promises.count; ++i) { id promise = promises[i]; if ([promise isKindOfClass:self]) { continue; } else if ([promise isKindOfClass:[NSError class]]) { reject(promise); return; } else { [promises replaceObjectAtIndex:i withObject:[[FBLPromise alloc] initWithResolution:promise]]; } } for (FBLPromise *promise in promises) { [promise observeOnQueue:queue fulfill:^(id __unused _) { // Wait until all are fulfilled. for (FBLPromise *promise in promises) { if (!promise.isFulfilled) { return; } } // If called multiple times, only the first one affects the result. fulfill([promises valueForKey:NSStringFromSelector(@selector(value))]); } reject:^(NSError *error) { reject(error); }]; } }]; } @end @implementation FBLPromise (DotSyntax_AllAdditions) + (FBLPromise * (^)(NSArray *))all { return ^(NSArray *promises) { return [self all:promises]; }; } + (FBLPromise * (^)(dispatch_queue_t, NSArray *))allOn { return ^(dispatch_queue_t queue, NSArray *promises) { return [self onQueue:queue all:promises]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Always.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Always.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (AlwaysAdditions) - (FBLPromise *)always:(FBLPromiseAlwaysWorkBlock)work { return [self onQueue:FBLPromise.defaultDispatchQueue always:work]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work { NSParameterAssert(queue); NSParameterAssert(work); return [self chainOnQueue:queue chainedFulfill:^id(id value) { work(); return value; } chainedReject:^id(NSError *error) { work(); return error; }]; } @end @implementation FBLPromise (DotSyntax_AlwaysAdditions) - (FBLPromise * (^)(FBLPromiseAlwaysWorkBlock))always { return ^(FBLPromiseAlwaysWorkBlock work) { return [self always:work]; }; } - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseAlwaysWorkBlock))alwaysOn { return ^(dispatch_queue_t queue, FBLPromiseAlwaysWorkBlock work) { return [self onQueue:queue always:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Any.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Any.h" #import "FBLPromise+Async.h" #import "FBLPromisePrivate.h" static NSArray *FBLPromiseCombineValuesAndErrors(NSArray *promises) { NSMutableArray *combinedValuesAndErrors = [[NSMutableArray alloc] init]; for (FBLPromise *promise in promises) { if (promise.isFulfilled) { [combinedValuesAndErrors addObject:promise.value ?: [NSNull null]]; continue; } if (promise.isRejected) { [combinedValuesAndErrors addObject:promise.error]; continue; } assert(!promise.isPending); }; return combinedValuesAndErrors; } @implementation FBLPromise (AnyAdditions) + (FBLPromise *)any:(NSArray *)promises { return [self onQueue:FBLPromise.defaultDispatchQueue any:promises]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue any:(NSArray *)anyPromises { NSParameterAssert(queue); NSParameterAssert(anyPromises); if (anyPromises.count == 0) { return [[FBLPromise alloc] initWithResolution:@[]]; } NSMutableArray *promises = [anyPromises mutableCopy]; return [FBLPromise onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { for (NSUInteger i = 0; i < promises.count; ++i) { id promise = promises[i]; if ([promise isKindOfClass:self]) { continue; } else { [promises replaceObjectAtIndex:i withObject:[[FBLPromise alloc] initWithResolution:promise]]; } } for (FBLPromise *promise in promises) { [promise observeOnQueue:queue fulfill:^(id __unused _) { // Wait until all are resolved. for (FBLPromise *promise in promises) { if (promise.isPending) { return; } } // If called multiple times, only the first one affects the result. fulfill(FBLPromiseCombineValuesAndErrors(promises)); } reject:^(NSError *error) { BOOL atLeastOneIsFulfilled = NO; for (FBLPromise *promise in promises) { if (promise.isPending) { return; } if (promise.isFulfilled) { atLeastOneIsFulfilled = YES; } } if (atLeastOneIsFulfilled) { fulfill(FBLPromiseCombineValuesAndErrors(promises)); } else { reject(error); } }]; } }]; } @end @implementation FBLPromise (DotSyntax_AnyAdditions) + (FBLPromise * (^)(NSArray *))any { return ^(NSArray *promises) { return [self any:promises]; }; } + (FBLPromise * (^)(dispatch_queue_t, NSArray *))anyOn { return ^(dispatch_queue_t queue, NSArray *promises) { return [self onQueue:queue any:promises]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Async.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Async.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (AsyncAdditions) + (instancetype)async:(FBLPromiseAsyncWorkBlock)work { return [self onQueue:self.defaultDispatchQueue async:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work { NSParameterAssert(queue); NSParameterAssert(work); FBLPromise *promise = [[FBLPromise alloc] initPending]; dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ work( ^(id __nullable value) { if ([value isKindOfClass:[FBLPromise class]]) { [(FBLPromise *)value observeOnQueue:queue fulfill:^(id __nullable value) { [promise fulfill:value]; } reject:^(NSError *error) { [promise reject:error]; }]; } else { [promise fulfill:value]; } }, ^(NSError *error) { [promise reject:error]; }); }); return promise; } @end @implementation FBLPromise (DotSyntax_AsyncAdditions) + (FBLPromise* (^)(FBLPromiseAsyncWorkBlock))async { return ^(FBLPromiseAsyncWorkBlock work) { return [self async:work]; }; } + (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAsyncWorkBlock))asyncOn { return ^(dispatch_queue_t queue, FBLPromiseAsyncWorkBlock work) { return [self onQueue:queue async:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Await.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Await.h" #import "FBLPromisePrivate.h" id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **outError) { assert(promise); static dispatch_once_t onceToken; static dispatch_queue_t queue; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("com.google.FBLPromises.Await", DISPATCH_QUEUE_CONCURRENT); }); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); id __block resolution; NSError __block *blockError; [promise chainOnQueue:queue chainedFulfill:^id(id value) { resolution = value; dispatch_semaphore_signal(semaphore); return value; } chainedReject:^id(NSError *error) { blockError = error; dispatch_semaphore_signal(semaphore); return error; }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (outError) { *outError = blockError; } return resolution; } ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Catch.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Catch.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (CatchAdditions) - (FBLPromise *)catch:(FBLPromiseCatchWorkBlock)reject { return [self onQueue:FBLPromise.defaultDispatchQueue catch:reject]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue catch:(FBLPromiseCatchWorkBlock)reject { NSParameterAssert(queue); NSParameterAssert(reject); return [self chainOnQueue:queue chainedFulfill:nil chainedReject:^id(NSError *error) { reject(error); return error; }]; } @end @implementation FBLPromise (DotSyntax_CatchAdditions) - (FBLPromise* (^)(FBLPromiseCatchWorkBlock))catch { return ^(FBLPromiseCatchWorkBlock catch) { return [self catch:catch]; }; } - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseCatchWorkBlock))catchOn { return ^(dispatch_queue_t queue, FBLPromiseCatchWorkBlock catch) { return [self onQueue:queue catch:catch]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Delay.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Delay.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (DelayAdditions) - (FBLPromise *)delay:(NSTimeInterval)interval { return [self onQueue:FBLPromise.defaultDispatchQueue delay:interval]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval { NSParameterAssert(queue); FBLPromise *promise = [[FBLPromise alloc] initPending]; [self observeOnQueue:queue fulfill:^(id __nullable value) { dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ [promise fulfill:value]; }); } reject:^(NSError *error) { [promise reject:error]; }]; return promise; } @end @implementation FBLPromise (DotSyntax_DelayAdditions) - (FBLPromise * (^)(NSTimeInterval))delay { return ^(NSTimeInterval interval) { return [self delay:interval]; }; } - (FBLPromise * (^)(dispatch_queue_t, NSTimeInterval))delayOn { return ^(dispatch_queue_t queue, NSTimeInterval interval) { return [self onQueue:queue delay:interval]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Do.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Do.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (DoAdditions) + (instancetype)do:(FBLPromiseDoWorkBlock)work { return [self onQueue:self.defaultDispatchQueue do:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work { NSParameterAssert(queue); NSParameterAssert(work); FBLPromise *promise = [[FBLPromise alloc] initPending]; dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ id value = work(); if ([value isKindOfClass:[FBLPromise class]]) { [(FBLPromise *)value observeOnQueue:queue fulfill:^(id __nullable value) { [promise fulfill:value]; } reject:^(NSError *error) { [promise reject:error]; }]; } else { [promise fulfill:value]; } }); return promise; } @end @implementation FBLPromise (DotSyntax_DoAdditions) + (FBLPromise* (^)(dispatch_queue_t, FBLPromiseDoWorkBlock))doOn { return ^(dispatch_queue_t queue, FBLPromiseDoWorkBlock work) { return [self onQueue:queue do:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Race.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Race.h" #import "FBLPromise+Async.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (RaceAdditions) + (instancetype)race:(NSArray *)promises { return [self onQueue:self.defaultDispatchQueue race:promises]; } + (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)racePromises { NSParameterAssert(queue); NSAssert(racePromises.count > 0, @"No promises to observe"); NSArray *promises = [racePromises copy]; return [FBLPromise onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { for (id promise in promises) { if (![promise isKindOfClass:self]) { fulfill(promise); return; } } // Subscribe all, but only the first one to resolve will change // the resulting promise's state. for (FBLPromise *promise in promises) { [promise observeOnQueue:queue fulfill:fulfill reject:reject]; } }]; } @end @implementation FBLPromise (DotSyntax_RaceAdditions) + (FBLPromise * (^)(NSArray *))race { return ^(NSArray *promises) { return [self race:promises]; }; } + (FBLPromise * (^)(dispatch_queue_t, NSArray *))raceOn { return ^(dispatch_queue_t queue, NSArray *promises) { return [self onQueue:queue race:promises]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Recover.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Recover.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (RecoverAdditions) - (FBLPromise *)recover:(FBLPromiseRecoverWorkBlock)recovery { return [self onQueue:FBLPromise.defaultDispatchQueue recover:recovery]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery { NSParameterAssert(queue); NSParameterAssert(recovery); return [self chainOnQueue:queue chainedFulfill:nil chainedReject:^id(NSError *error) { return recovery(error); }]; } @end @implementation FBLPromise (DotSyntax_RecoverAdditions) - (FBLPromise * (^)(FBLPromiseRecoverWorkBlock))recover { return ^(FBLPromiseRecoverWorkBlock recovery) { return [self recover:recovery]; }; } - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRecoverWorkBlock))recoverOn { return ^(dispatch_queue_t queue, FBLPromiseRecoverWorkBlock recovery) { return [self onQueue:queue recover:recovery]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Reduce.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Reduce.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (ReduceAdditions) - (FBLPromise *)reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer { return [self onQueue:FBLPromise.defaultDispatchQueue reduce:items combine:reducer]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer { NSParameterAssert(queue); NSParameterAssert(items); NSParameterAssert(reducer); FBLPromise *promise = self; for (id item in items) { promise = [promise chainOnQueue:queue chainedFulfill:^id(id value) { return reducer(value, item); } chainedReject:nil]; } return promise; } @end @implementation FBLPromise (DotSyntax_ReduceAdditions) - (FBLPromise * (^)(NSArray *, FBLPromiseReducerBlock))reduce { return ^(NSArray *items, FBLPromiseReducerBlock reducer) { return [self reduce:items combine:reducer]; }; } - (FBLPromise * (^)(dispatch_queue_t, NSArray *, FBLPromiseReducerBlock))reduceOn { return ^(dispatch_queue_t queue, NSArray *items, FBLPromiseReducerBlock reducer) { return [self onQueue:queue reduce:items combine:reducer]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Retry.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Retry.h" #import "FBLPromisePrivate.h" NSInteger const FBLPromiseRetryDefaultAttemptsCount = 1; NSTimeInterval const FBLPromiseRetryDefaultDelayInterval = 1.0; static void FBLPromiseRetryAttempt(FBLPromise *promise, dispatch_queue_t queue, NSInteger count, NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate, FBLPromiseRetryWorkBlock work) { __auto_type retrier = ^(id __nullable value) { if ([value isKindOfClass:[NSError class]]) { if (count <= 0 || (predicate && !predicate(count, value))) { [promise reject:value]; } else { dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ FBLPromiseRetryAttempt(promise, queue, count - 1, interval, predicate, work); }); } } else { [promise fulfill:value]; } }; id value = work(); if ([value isKindOfClass:[FBLPromise class]]) { [(FBLPromise *)value observeOnQueue:queue fulfill:retrier reject:retrier]; } else { retrier(value); } } @implementation FBLPromise (RetryAdditions) + (FBLPromise *)retry:(FBLPromiseRetryWorkBlock)work { return [self onQueue:FBLPromise.defaultDispatchQueue retry:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue retry:(FBLPromiseRetryWorkBlock)work { return [self onQueue:queue attempts:FBLPromiseRetryDefaultAttemptsCount retry:work]; } + (FBLPromise *)attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work { return [self onQueue:FBLPromise.defaultDispatchQueue attempts:count retry:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work { return [self onQueue:queue attempts:count delay:FBLPromiseRetryDefaultDelayInterval condition:nil retry:work]; } + (FBLPromise *)attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work { return [self onQueue:FBLPromise.defaultDispatchQueue attempts:count delay:interval condition:predicate retry:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work { NSParameterAssert(queue); NSParameterAssert(work); FBLPromise *promise = [[FBLPromise alloc] initPending]; FBLPromiseRetryAttempt(promise, queue, count, interval, predicate, work); return promise; } @end @implementation FBLPromise (DotSyntax_RetryAdditions) + (FBLPromise * (^)(FBLPromiseRetryWorkBlock))retry { return ^id(FBLPromiseRetryWorkBlock work) { return [self retry:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRetryWorkBlock))retryOn { return ^id(dispatch_queue_t queue, FBLPromiseRetryWorkBlock work) { return [self onQueue:queue retry:work]; }; } + (FBLPromise * (^)(NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock, FBLPromiseRetryWorkBlock))retryAgain { return ^id(NSInteger count, NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate, FBLPromiseRetryWorkBlock work) { return [self attempts:count delay:interval condition:predicate retry:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock, FBLPromiseRetryWorkBlock))retryAgainOn { return ^id(dispatch_queue_t queue, NSInteger count, NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate, FBLPromiseRetryWorkBlock work) { return [self onQueue:queue attempts:count delay:interval condition:predicate retry:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Testing.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Testing.h" BOOL FBLWaitForPromisesWithTimeout(NSTimeInterval timeout) { BOOL isTimedOut = NO; NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; static NSTimeInterval const minimalTimeout = 0.01; static int64_t const minimalTimeToWait = (int64_t)(minimalTimeout * NSEC_PER_SEC); dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, minimalTimeToWait); dispatch_group_t dispatchGroup = FBLPromise.dispatchGroup; NSRunLoop *runLoop = NSRunLoop.currentRunLoop; while (dispatch_group_wait(dispatchGroup, waitTime)) { isTimedOut = timeoutDate.timeIntervalSinceNow < 0.0; if (isTimedOut) { break; } [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:minimalTimeout]]; } return !isTimedOut; } @implementation FBLPromise (TestingAdditions) // These properties are implemented in the FBLPromise class itself. @dynamic isPending; @dynamic isFulfilled; @dynamic isRejected; @dynamic value; @dynamic error; + (dispatch_group_t)dispatchGroup { static dispatch_group_t gDispatchGroup; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ gDispatchGroup = dispatch_group_create(); }); return gDispatchGroup; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Then.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Then.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (ThenAdditions) - (FBLPromise *)then:(FBLPromiseThenWorkBlock)work { return [self onQueue:FBLPromise.defaultDispatchQueue then:work]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work { NSParameterAssert(queue); NSParameterAssert(work); return [self chainOnQueue:queue chainedFulfill:work chainedReject:nil]; } @end @implementation FBLPromise (DotSyntax_ThenAdditions) - (FBLPromise* (^)(FBLPromiseThenWorkBlock))then { return ^(FBLPromiseThenWorkBlock work) { return [self then:work]; }; } - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseThenWorkBlock))thenOn { return ^(dispatch_queue_t queue, FBLPromiseThenWorkBlock work) { return [self onQueue:queue then:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Timeout.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Timeout.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (TimeoutAdditions) - (FBLPromise *)timeout:(NSTimeInterval)interval { return [self onQueue:FBLPromise.defaultDispatchQueue timeout:interval]; } - (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval { NSParameterAssert(queue); FBLPromise *promise = [[FBLPromise alloc] initPending]; [self observeOnQueue:queue fulfill:^(id __nullable value) { [promise fulfill:value]; } reject:^(NSError *error) { [promise reject:error]; }]; typeof(self) __weak weakPromise = promise; dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{ NSError *timedOutError = [[NSError alloc] initWithDomain:FBLPromiseErrorDomain code:FBLPromiseErrorCodeTimedOut userInfo:nil]; [weakPromise reject:timedOutError]; }); return promise; } @end @implementation FBLPromise (DotSyntax_TimeoutAdditions) - (FBLPromise* (^)(NSTimeInterval))timeout { return ^(NSTimeInterval interval) { return [self timeout:interval]; }; } - (FBLPromise* (^)(dispatch_queue_t, NSTimeInterval))timeoutOn { return ^(dispatch_queue_t queue, NSTimeInterval interval) { return [self onQueue:queue timeout:interval]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Validate.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Validate.h" #import "FBLPromisePrivate.h" @implementation FBLPromise (ValidateAdditions) - (FBLPromise*)validate:(FBLPromiseValidateWorkBlock)predicate { return [self onQueue:FBLPromise.defaultDispatchQueue validate:predicate]; } - (FBLPromise*)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate { NSParameterAssert(queue); NSParameterAssert(predicate); FBLPromiseChainedFulfillBlock chainedFulfill = ^id(id value) { return predicate(value) ? value : [[NSError alloc] initWithDomain:FBLPromiseErrorDomain code:FBLPromiseErrorCodeValidationFailure userInfo:nil]; }; return [self chainOnQueue:queue chainedFulfill:chainedFulfill chainedReject:nil]; } @end @implementation FBLPromise (DotSyntax_ValidateAdditions) - (FBLPromise* (^)(FBLPromiseValidateWorkBlock))validate { return ^(FBLPromiseValidateWorkBlock predicate) { return [self validate:predicate]; }; } - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseValidateWorkBlock))validateOn { return ^(dispatch_queue_t queue, FBLPromiseValidateWorkBlock predicate) { return [self onQueue:queue validate:predicate]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise+Wrap.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Wrap.h" #import "FBLPromise+Async.h" @implementation FBLPromise (WrapAdditions) + (instancetype)wrapCompletion:(void (^)(FBLPromiseCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapCompletion:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue wrapCompletion:(void (^)(FBLPromiseCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { work(^{ fulfill(nil); }); }]; } + (instancetype)wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapObjectCompletion:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { work(^(id __nullable value) { fulfill(value); }); }]; } + (instancetype)wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapErrorCompletion:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(NSError *__nullable error) { if (error) { reject(error); } else { fulfill(nil); } }); }]; } + (instancetype)wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapObjectOrErrorCompletion:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(id __nullable value, NSError *__nullable error) { if (error) { reject(error); } else { fulfill(value); } }); }]; } + (instancetype)wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapErrorOrObjectCompletion:work]; } + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(NSError *__nullable error, id __nullable value) { if (error) { reject(error); } else { fulfill(value); } }); }]; } + (FBLPromise *)wrap2ObjectsOrErrorCompletion: (void (^)(FBLPromise2ObjectsOrErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrap2ObjectsOrErrorCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrap2ObjectsOrErrorCompletion:(void (^)(FBLPromise2ObjectsOrErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(id __nullable value1, id __nullable value2, NSError *__nullable error) { if (error) { reject(error); } else { fulfill(@[ value1 ?: [NSNull null], value2 ?: [NSNull null] ]); } }); }]; } + (FBLPromise *)wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapBoolCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { work(^(BOOL value) { fulfill(@(value)); }); }]; } + (FBLPromise *)wrapBoolOrErrorCompletion: (void (^)(FBLPromiseBoolOrErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapBoolOrErrorCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapBoolOrErrorCompletion:(void (^)(FBLPromiseBoolOrErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(BOOL value, NSError *__nullable error) { if (error) { reject(error); } else { fulfill(@(value)); } }); }]; } + (FBLPromise *)wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapIntegerCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { work(^(NSInteger value) { fulfill(@(value)); }); }]; } + (FBLPromise *)wrapIntegerOrErrorCompletion: (void (^)(FBLPromiseIntegerOrErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapIntegerOrErrorCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapIntegerOrErrorCompletion:(void (^)(FBLPromiseIntegerOrErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(NSInteger value, NSError *__nullable error) { if (error) { reject(error); } else { fulfill(@(value)); } }); }]; } + (FBLPromise *)wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapDoubleCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:(dispatch_queue_t)queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) { work(^(double value) { fulfill(@(value)); }); }]; } + (FBLPromise *)wrapDoubleOrErrorCompletion: (void (^)(FBLPromiseDoubleOrErrorCompletion))work { return [self onQueue:self.defaultDispatchQueue wrapDoubleOrErrorCompletion:work]; } + (FBLPromise *)onQueue:(dispatch_queue_t)queue wrapDoubleOrErrorCompletion:(void (^)(FBLPromiseDoubleOrErrorCompletion))work { NSParameterAssert(queue); NSParameterAssert(work); return [self onQueue:queue async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { work(^(double value, NSError *__nullable error) { if (error) { reject(error); } else { fulfill(@(value)); } }); }]; } @end @implementation FBLPromise (DotSyntax_WrapAdditions) + (FBLPromise * (^)(void (^)(FBLPromiseCompletion)))wrapCompletion { return ^(void (^work)(FBLPromiseCompletion)) { return [self wrapCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseCompletion)))wrapCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseCompletion)) { return [self onQueue:queue wrapCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletion { return ^(void (^work)(FBLPromiseObjectCompletion)) { return [self wrapObjectCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseObjectCompletion)) { return [self onQueue:queue wrapObjectCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletion { return ^(void (^work)(FBLPromiseErrorCompletion)) { return [self wrapErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseErrorCompletion)) { return [self onQueue:queue wrapErrorCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletion { return ^(void (^work)(FBLPromiseObjectOrErrorCompletion)) { return [self wrapObjectOrErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseObjectOrErrorCompletion)) { return [self onQueue:queue wrapObjectOrErrorCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletion { return ^(void (^work)(FBLPromiseErrorOrObjectCompletion)) { return [self wrapErrorOrObjectCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseErrorOrObjectCompletion)) { return [self onQueue:queue wrapErrorOrObjectCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletion { return ^(void (^work)(FBLPromise2ObjectsOrErrorCompletion)) { return [self wrap2ObjectsOrErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromise2ObjectsOrErrorCompletion)) { return [self onQueue:queue wrap2ObjectsOrErrorCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletion { return ^(void (^work)(FBLPromiseBoolCompletion)) { return [self wrapBoolCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseBoolCompletion)) { return [self onQueue:queue wrapBoolCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseBoolOrErrorCompletion))) wrapBoolOrErrorCompletion { return ^(void (^work)(FBLPromiseBoolOrErrorCompletion)) { return [self wrapBoolOrErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseBoolOrErrorCompletion))) wrapBoolOrErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseBoolOrErrorCompletion)) { return [self onQueue:queue wrapBoolOrErrorCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletion { return ^(void (^work)(FBLPromiseIntegerCompletion)) { return [self wrapIntegerCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseIntegerCompletion)) { return [self onQueue:queue wrapIntegerCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletion { return ^(void (^work)(FBLPromiseIntegerOrErrorCompletion)) { return [self wrapIntegerOrErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseIntegerOrErrorCompletion)) { return [self onQueue:queue wrapIntegerOrErrorCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletion { return ^(void (^work)(FBLPromiseDoubleCompletion)) { return [self wrapDoubleCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseDoubleCompletion)) { return [self onQueue:queue wrapDoubleCompletion:work]; }; } + (FBLPromise * (^)(void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletion { return ^(void (^work)(FBLPromiseDoubleOrErrorCompletion)) { return [self wrapDoubleOrErrorCompletion:work]; }; } + (FBLPromise * (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletionOn { return ^(dispatch_queue_t queue, void (^work)(FBLPromiseDoubleOrErrorCompletion)) { return [self onQueue:queue wrapDoubleOrErrorCompletion:work]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromisePrivate.h" /** All states a promise can be in. */ typedef NS_ENUM(NSInteger, FBLPromiseState) { FBLPromiseStatePending = 0, FBLPromiseStateFulfilled, FBLPromiseStateRejected, }; typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution); static dispatch_queue_t gFBLPromiseDefaultDispatchQueue; @implementation FBLPromise { /** Current state of the promise. */ FBLPromiseState _state; /** Set of arbitrary objects to keep strongly while the promise is pending. Becomes nil after the promise has been resolved. */ NSMutableSet *__nullable _pendingObjects; /** Value to fulfill the promise with. Can be nil if the promise is still pending, was resolved with nil or after it has been rejected. */ id __nullable _value; /** Error to reject the promise with. Can be nil if the promise is still pending or after it has been fulfilled. */ NSError *__nullable _error; /** List of observers to notify when the promise gets resolved. */ NSMutableArray *_observers; } + (void)initialize { if (self == [FBLPromise class]) { gFBLPromiseDefaultDispatchQueue = dispatch_get_main_queue(); } } + (dispatch_queue_t)defaultDispatchQueue { @synchronized(self) { return gFBLPromiseDefaultDispatchQueue; } } + (void)setDefaultDispatchQueue:(dispatch_queue_t)queue { NSParameterAssert(queue); @synchronized(self) { gFBLPromiseDefaultDispatchQueue = queue; } } + (instancetype)pendingPromise { return [[self alloc] initPending]; } + (instancetype)resolvedWith:(nullable id)resolution { return [[self alloc] initWithResolution:resolution]; } - (void)fulfill:(nullable id)value { if ([value isKindOfClass:[NSError class]]) { [self reject:(NSError *)value]; } else { @synchronized(self) { if (_state == FBLPromiseStatePending) { _state = FBLPromiseStateFulfilled; _value = value; _pendingObjects = nil; for (FBLPromiseObserver observer in _observers) { observer(_state, _value); } _observers = nil; dispatch_group_leave(FBLPromise.dispatchGroup); } } } } - (void)reject:(NSError *)error { NSAssert([error isKindOfClass:[NSError class]], @"Invalid error type."); if (![error isKindOfClass:[NSError class]]) { // Give up on invalid error type in Release mode. @throw error; // NOLINT } @synchronized(self) { if (_state == FBLPromiseStatePending) { _state = FBLPromiseStateRejected; _error = error; _pendingObjects = nil; for (FBLPromiseObserver observer in _observers) { observer(_state, _error); } _observers = nil; dispatch_group_leave(FBLPromise.dispatchGroup); } } } #pragma mark - NSObject - (NSString *)description { if (self.isFulfilled) { return [NSString stringWithFormat:@"<%@ %p> Fulfilled: %@", NSStringFromClass([self class]), self, self.value]; } if (self.isRejected) { return [NSString stringWithFormat:@"<%@ %p> Rejected: %@", NSStringFromClass([self class]), self, self.error]; } return [NSString stringWithFormat:@"<%@ %p> Pending", NSStringFromClass([self class]), self]; } #pragma mark - Private - (instancetype)initPending { self = [super init]; if (self) { dispatch_group_enter(FBLPromise.dispatchGroup); } return self; } - (instancetype)initWithResolution:(nullable id)resolution { self = [super init]; if (self) { if ([resolution isKindOfClass:[NSError class]]) { _state = FBLPromiseStateRejected; _error = (NSError *)resolution; } else { _state = FBLPromiseStateFulfilled; _value = resolution; } } return self; } - (void)dealloc { if (_state == FBLPromiseStatePending) { dispatch_group_leave(FBLPromise.dispatchGroup); } } - (BOOL)isPending { @synchronized(self) { return _state == FBLPromiseStatePending; } } - (BOOL)isFulfilled { @synchronized(self) { return _state == FBLPromiseStateFulfilled; } } - (BOOL)isRejected { @synchronized(self) { return _state == FBLPromiseStateRejected; } } - (nullable id)value { @synchronized(self) { return _value; } } - (NSError *__nullable)error { @synchronized(self) { return _error; } } - (void)addPendingObject:(id)object { NSParameterAssert(object); @synchronized(self) { if (_state == FBLPromiseStatePending) { if (!_pendingObjects) { _pendingObjects = [[NSMutableSet alloc] init]; } [_pendingObjects addObject:object]; } } } - (void)observeOnQueue:(dispatch_queue_t)queue fulfill:(FBLPromiseOnFulfillBlock)onFulfill reject:(FBLPromiseOnRejectBlock)onReject { NSParameterAssert(queue); NSParameterAssert(onFulfill); NSParameterAssert(onReject); @synchronized(self) { switch (_state) { case FBLPromiseStatePending: { if (!_observers) { _observers = [[NSMutableArray alloc] init]; } [_observers addObject:^(FBLPromiseState state, id __nullable resolution) { dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ switch (state) { case FBLPromiseStatePending: break; case FBLPromiseStateFulfilled: onFulfill(resolution); break; case FBLPromiseStateRejected: onReject(resolution); break; } }); }]; break; } case FBLPromiseStateFulfilled: { dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ onFulfill(self->_value); }); break; } case FBLPromiseStateRejected: { dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{ onReject(self->_error); }); break; } } } } - (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill chainedReject:(FBLPromiseChainedRejectBlock)chainedReject { NSParameterAssert(queue); FBLPromise *promise = [[FBLPromise alloc] initPending]; __auto_type resolver = ^(id __nullable value) { if ([value isKindOfClass:[FBLPromise class]]) { [(FBLPromise *)value observeOnQueue:queue fulfill:^(id __nullable value) { [promise fulfill:value]; } reject:^(NSError *error) { [promise reject:error]; }]; } else { [promise fulfill:value]; } }; [self observeOnQueue:queue fulfill:^(id __nullable value) { value = chainedFulfill ? chainedFulfill(value) : value; resolver(value); } reject:^(NSError *error) { id value = chainedReject ? chainedReject(error) : error; resolver(value); }]; return promise; } @end @implementation FBLPromise (DotSyntaxAdditions) + (instancetype (^)(void))pending { return ^(void) { return [self pendingPromise]; }; } + (instancetype (^)(id __nullable))resolved { return ^(id resolution) { return [self resolvedWith:resolution]; }; } @end ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/FBLPromiseError.m ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromiseError.h" NSErrorDomain const FBLPromiseErrorDomain = @"com.google.FBLPromises.Error"; ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+All.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AllAdditions) /** Wait until all of the given promises are fulfilled. If one of the given promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param promises Promises to wait for. @return Promise of an array containing the values of input promises in the same order. */ + (FBLPromise *)all:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Wait until all of the given promises are fulfilled. If one of the given promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected FBLPromise correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param queue A queue to dispatch on. @param promises Promises to wait for. @return Promise of an array containing the values of input promises in the same order. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue all:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `all` operators. Usage: FBLPromise.all(@[ ... ]) */ @interface FBLPromise(DotSyntax_AllAdditions) + (FBLPromise * (^)(NSArray *))all FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))allOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Always.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AlwaysAdditions) typedef void (^FBLPromiseAlwaysWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); /** @param work A block that always executes, no matter if the receiver is rejected or fulfilled. @return A new pending promise to be resolved with same resolution as the receiver. */ - (FBLPromise *)always:(FBLPromiseAlwaysWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to dispatch on. @param work A block that always executes, no matter if the receiver is rejected or fulfilled. @return A new pending promise to be resolved with same resolution as the receiver. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `always` operators. Usage: promise.always(^{...}) */ @interface FBLPromise(DotSyntax_AlwaysAdditions) - (FBLPromise* (^)(FBLPromiseAlwaysWorkBlock))always FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAlwaysWorkBlock))alwaysOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Any.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AnyAdditions) /** Waits until all of the given promises are either fulfilled or rejected. If all promises are rejected, then the returned promise is rejected with same error as the last one rejected. If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of values or `NSErrors`, matching the original order of fulfilled or rejected promises respectively. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param promises Promises to wait for. @return Promise of array containing the values or `NSError`s of input promises in the same order. */ + (FBLPromise *)any:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Waits until all of the given promises are either fulfilled or rejected. If all promises are rejected, then the returned promise is rejected with same error as the last one rejected. If at least one of the promises is fulfilled, the resulting promise is fulfilled with an array of values or `NSError`s, matching the original order of fulfilled or rejected promises respectively. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. Promises resolved with `nil` become `NSNull` instances in the resulting array. @param queue A queue to dispatch on. @param promises Promises to wait for. @return Promise of array containing the values or `NSError`s of input promises in the same order. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue any:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `any` operators. Usage: FBLPromise.any(@[ ... ]) */ @interface FBLPromise(DotSyntax_AnyAdditions) + (FBLPromise * (^)(NSArray *))any FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))anyOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Async.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(AsyncAdditions) typedef void (^FBLPromiseFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseAsyncWorkBlock)(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously. @param work A block to perform any operations needed to resolve the promise. @return A new pending promise. */ + (instancetype)async:(FBLPromiseAsyncWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously on the given queue. @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @return A new pending promise. */ + (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `async` operators. Usage: FBLPromise.async(^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { ... }) */ @interface FBLPromise(DotSyntax_AsyncAdditions) + (FBLPromise* (^)(FBLPromiseAsyncWorkBlock))async FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, FBLPromiseAsyncWorkBlock))asyncOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Await.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Waits for promise resolution. The current thread blocks until the promise is resolved. @param promise Promise to wait for. @param error Error the promise was rejected with, or `nil` if the promise was fulfilled. @return Value the promise was fulfilled with. If the promise was rejected, the return value is always `nil`, but the error out arg is not. */ FOUNDATION_EXTERN id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **error) NS_REFINED_FOR_SWIFT; NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Catch.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(CatchAdditions) typedef void (^FBLPromiseCatchWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with same resolution as the receiver. If receiver is rejected, then `reject` block is executed asynchronously. @param reject A block to handle the error that receiver was rejected with. @return A new pending promise. */ - (FBLPromise *)catch:(FBLPromiseCatchWorkBlock)reject NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with same resolution as the receiver. If receiver is rejected, then `reject` block is executed asynchronously on the given queue. @param queue A queue to invoke the `reject` block on. @param reject A block to handle the error that receiver was rejected with. @return A new pending promise. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue catch:(FBLPromiseCatchWorkBlock)reject NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `catch` operators. Usage: promise.catch(^(NSError *error) { ... }) */ @interface FBLPromise(DotSyntax_CatchAdditions) - (FBLPromise* (^)(FBLPromiseCatchWorkBlock))catch FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseCatchWorkBlock))catchOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Delay.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(DelayAdditions) /** Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or rejects with the same error immediately. @param interval Time to wait in seconds. @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects with the same error immediately. */ - (FBLPromise *)delay:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); /** Creates a new pending promise that fulfills with the same value as `self` after the `delay`, or rejects with the same error immediately. @param queue A queue to dispatch on. @param interval Time to wait in seconds. @return A new pending promise that fulfills at least `delay` seconds later than `self`, or rejects with the same error immediately. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `delay` operators. Usage: promise.delay(...) */ @interface FBLPromise(DotSyntax_DelayAdditions) - (FBLPromise * (^)(NSTimeInterval))delay FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, NSTimeInterval))delayOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Do.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(DoAdditions) typedef id __nullable (^FBLPromiseDoWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously. @param work A block that returns a value or an error used to resolve the promise. @return A new pending promise. */ + (instancetype)do:(FBLPromiseDoWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise and executes `work` block asynchronously on the given queue. @param queue A queue to invoke the `work` block on. @param work A block that returns a value or an error used to resolve the promise. @return A new pending promise. */ + (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `do` operators. Usage: FBLPromise.doOn(queue, ^(NSError *error) { ... }) */ @interface FBLPromise(DotSyntax_DoAdditions) + (FBLPromise * (^)(dispatch_queue_t, FBLPromiseDoWorkBlock))doOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Race.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(RaceAdditions) /** Wait until any of the given promises are fulfilled. If one of the promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. @param promises Promises to wait for. @return A new pending promise to be resolved with the same resolution as the first promise, among the given ones, which was resolved. */ + (instancetype)race:(NSArray *)promises NS_SWIFT_UNAVAILABLE(""); /** Wait until any of the given promises are fulfilled. If one of the promises is rejected, then the returned promise is rejected with same error. If any other arbitrary value or `NSError` appears in the array instead of `FBLPromise`, it's implicitly considered a pre-fulfilled or pre-rejected `FBLPromise` correspondingly. @param queue A queue to dispatch on. @param promises Promises to wait for. @return A new pending promise to be resolved with the same resolution as the first promise, among the given ones, which was resolved. */ + (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)promises NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `race` operators. Usage: FBLPromise.race(@[ ... ]) */ @interface FBLPromise(DotSyntax_RaceAdditions) + (FBLPromise * (^)(NSArray *))race FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSArray *))raceOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Recover.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(RecoverAdditions) typedef id __nullable (^FBLPromiseRecoverWorkBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); /** Provides a new promise to recover in case the receiver gets rejected. @param recovery A block to handle the error that the receiver was rejected with. @return A new pending promise to use instead of the rejected one that gets resolved with resolution returned from `recovery` block. */ - (FBLPromise *)recover:(FBLPromiseRecoverWorkBlock)recovery NS_SWIFT_UNAVAILABLE(""); /** Provides a new promise to recover in case the receiver gets rejected. @param queue A queue to dispatch on. @param recovery A block to handle the error that the receiver was rejected with. @return A new pending promise to use instead of the rejected one that gets resolved with resolution returned from `recovery` block. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `recover` operators. Usage: promise.recover(^id(NSError *error) {...}) */ @interface FBLPromise(DotSyntax_RecoverAdditions) - (FBLPromise * (^)(FBLPromiseRecoverWorkBlock))recover FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRecoverWorkBlock))recoverOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Reduce.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ReduceAdditions) typedef id __nullable (^FBLPromiseReducerBlock)(Value __nullable partial, id next) NS_SWIFT_UNAVAILABLE(""); /** Sequentially reduces a collection of values to a single promise using a given combining block and the value `self` resolves with as initial value. @param items An array of values to process in order. @param reducer A block to combine an accumulating value and an element of the sequence into the new accumulating value or a promise resolved with it, to be used in the next call of the `reducer` or returned to the caller. @return A new pending promise returned from the last `reducer` invocation. Or `self` if `items` is empty. */ - (FBLPromise *)reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); /** Sequentially reduces a collection of values to a single promise using a given combining block and the value `self` resolves with as initial value. @param queue A queue to dispatch on. @param items An array of values to process in order. @param reducer A block to combine an accumulating value and an element of the sequence into the new accumulating value or a promise resolved with it, to be used in the next call of the `reducer` or returned to the caller. @return A new pending promise returned from the last `reducer` invocation. Or `self` if `items` is empty. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue reduce:(NSArray *)items combine:(FBLPromiseReducerBlock)reducer NS_SWIFT_UNAVAILABLE(""); @end /** Convenience dot-syntax wrappers for `FBLPromise` `reduce` operators. Usage: promise.reduce(values, ^id(id partial, id next) { ... }) */ @interface FBLPromise(DotSyntax_ReduceAdditions) - (FBLPromise * (^)(NSArray *, FBLPromiseReducerBlock))reduce FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, NSArray *, FBLPromiseReducerBlock))reduceOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Retry.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** The default number of retry attempts is 1. */ FOUNDATION_EXTERN NSInteger const FBLPromiseRetryDefaultAttemptsCount NS_REFINED_FOR_SWIFT; /** The default delay interval before making a retry attempt is 1.0 second. */ FOUNDATION_EXTERN NSTimeInterval const FBLPromiseRetryDefaultDelayInterval NS_REFINED_FOR_SWIFT; @interface FBLPromise(RetryAdditions) typedef id __nullable (^FBLPromiseRetryWorkBlock)(void) NS_SWIFT_UNAVAILABLE(""); typedef BOOL (^FBLPromiseRetryPredicateBlock)(NSInteger, NSError *) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on rejection where the `work` block is retried after a delay of `FBLPromiseRetryDefaultDelayInterval` second(s). @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. Defaults to `FBLPromiseRetryDefaultAttemptsCount` attempt(s) on rejection where the `work` block is retried on the given `queue` after a delay of `FBLPromiseRetryDefaultDelayInterval` second(s). @param queue A queue to invoke the `work` block on. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. @param queue A queue to invoke the `work` block on. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously, or rejects with the same error after all retry attempts have been exhausted. On rejection, the `work` block is retried after the given delay `interval` and will continue to retry until the number of specified attempts have been exhausted or will bail early if the given condition is not met. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param interval Time to wait before the next retry attempt. @param predicate Condition to check before the next retry attempt. The predicate block provides the the number of remaining retry attempts and the error that the promise was rejected with. @param work A block that executes asynchronously on the default queue and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted or if the given condition is not met. */ + (FBLPromise *)attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise that fulfills with the same value as the promise returned from `work` block, which executes asynchronously on the given `queue`, or rejects with the same error after all retry attempts have been exhausted. On rejection, the `work` block is retried after the given delay `interval` and will continue to retry until the number of specified attempts have been exhausted or will bail early if the given condition is not met. @param queue A queue to invoke the `work` block on. @param count Max number of retry attempts. The `work` block will be executed once if the specified count is less than or equal to zero. @param interval Time to wait before the next retry attempt. @param predicate Condition to check before the next retry attempt. The predicate block provides the the number of remaining retry attempts and the error that the promise was rejected with. @param work A block that executes asynchronously on the given `queue` and returns a value or an error used to resolve the promise. @return A new pending promise that fulfills with the same value as the promise returned from `work` block, or rejects with the same error after all retry attempts have been exhausted or if the given condition is not met. */ + (FBLPromise *)onQueue:(dispatch_queue_t)queue attempts:(NSInteger)count delay:(NSTimeInterval)interval condition:(nullable FBLPromiseRetryPredicateBlock)predicate retry:(FBLPromiseRetryWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise+Retry` operators. Usage: FBLPromise.retry(^id { ... }) */ @interface FBLPromise(DotSyntax_RetryAdditions) + (FBLPromise * (^)(FBLPromiseRetryWorkBlock))retry FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, FBLPromiseRetryWorkBlock))retryOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock __nullable, FBLPromiseRetryWorkBlock))retryAgain FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise * (^)(dispatch_queue_t, NSInteger, NSTimeInterval, FBLPromiseRetryPredicateBlock __nullable, FBLPromiseRetryWorkBlock))retryAgainOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Testing.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Waits for all scheduled promises blocks. @param timeout Maximum time to wait. @return YES if all promises blocks have completed before the timeout and NO otherwise. */ FOUNDATION_EXTERN BOOL FBLWaitForPromisesWithTimeout(NSTimeInterval timeout) NS_REFINED_FOR_SWIFT; @interface FBLPromise(TestingAdditions) /** Dispatch group for promises that is typically used to wait for all scheduled blocks. */ @property(class, nonatomic, readonly) dispatch_group_t dispatchGroup NS_REFINED_FOR_SWIFT; /** Properties to get the current state of the promise. */ @property(nonatomic, readonly) BOOL isPending NS_REFINED_FOR_SWIFT; @property(nonatomic, readonly) BOOL isFulfilled NS_REFINED_FOR_SWIFT; @property(nonatomic, readonly) BOOL isRejected NS_REFINED_FOR_SWIFT; /** Value the promise was fulfilled with. Can be nil if the promise is still pending, was resolved with nil or after it has been rejected. */ @property(nonatomic, readonly, nullable) Value value NS_REFINED_FOR_SWIFT; /** Error the promise was rejected with. Can be nil if the promise is still pending or after it has been fulfilled. */ @property(nonatomic, readonly, nullable) NSError *error NS_REFINED_FOR_SWIFT; @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Then.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ThenAdditions) typedef id __nullable (^FBLPromiseThenWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with resolution returned from `work` block: either value, error or another promise. The `work` block is executed asynchronously only when the receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with the same error. @param work A block to handle the value that receiver was fulfilled with. @return A new pending promise to be resolved with resolution returned from the `work` block. */ - (FBLPromise *)then:(FBLPromiseThenWorkBlock)work NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise which eventually gets resolved with resolution returned from `work` block: either value, error or another promise. The `work` block is executed asynchronously when the receiver is fulfilled. If receiver is rejected, the returned promise is also rejected with the same error. @param queue A queue to invoke the `work` block on. @param work A block to handle the value that receiver was fulfilled with. @return A new pending promise to be resolved with resolution returned from the `work` block. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `then` operators. Usage: promise.then(^id(id value) { ... }) */ @interface FBLPromise(DotSyntax_ThenAdditions) - (FBLPromise* (^)(FBLPromiseThenWorkBlock))then FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, FBLPromiseThenWorkBlock))thenOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Timeout.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(TimeoutAdditions) /** Waits for a promise with the specified `timeout`. @param interval Time to wait in seconds. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)timeout:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE(""); /** Waits for a promise with the specified `timeout`. @param queue A queue to dispatch on. @param interval Time to wait in seconds. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeTimedOut` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `timeout` operators. Usage: promise.timeout(...) */ @interface FBLPromise(DotSyntax_TimeoutAdditions) - (FBLPromise* (^)(NSTimeInterval))timeout FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise* (^)(dispatch_queue_t, NSTimeInterval))timeoutOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Validate.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN @interface FBLPromise(ValidateAdditions) typedef BOOL (^FBLPromiseValidateWorkBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); /** Validates a fulfilled value or rejects the value if it can not be validated. @param predicate An expression to validate. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)validate:(FBLPromiseValidateWorkBlock)predicate NS_SWIFT_UNAVAILABLE(""); /** Validates a fulfilled value or rejects the value if it can not be validated. @param queue A queue to dispatch on. @param predicate An expression to validate. @return A new pending promise that gets either resolved with same resolution as the receiver or rejected with `FBLPromiseErrorCodeValidationFailure` error code in `FBLPromiseErrorDomain`. */ - (FBLPromise *)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate NS_REFINED_FOR_SWIFT; @end /** Convenience dot-syntax wrappers for `FBLPromise` `validate` operators. Usage: promise.validate(^BOOL(id value) { ... }) */ @interface FBLPromise(DotSyntax_ValidateAdditions) - (FBLPromise * (^)(FBLPromiseValidateWorkBlock))validate FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); - (FBLPromise * (^)(dispatch_queue_t, FBLPromiseValidateWorkBlock))validateOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise+Wrap.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise.h" NS_ASSUME_NONNULL_BEGIN /** Different types of completion handlers available to be wrapped with promise. */ typedef void (^FBLPromiseCompletion)(void) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseObjectCompletion)(id __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseErrorCompletion)(NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseObjectOrErrorCompletion)(id __nullable, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseErrorOrObjectCompletion)(NSError* __nullable, id __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromise2ObjectsOrErrorCompletion)(id __nullable, id __nullable, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseBoolCompletion)(BOOL) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseBoolOrErrorCompletion)(BOOL, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseIntegerCompletion)(NSInteger) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseIntegerOrErrorCompletion)(NSInteger, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseDoubleCompletion)(double) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseDoubleOrErrorCompletion)(double, NSError* __nullable) NS_SWIFT_UNAVAILABLE(""); /** Provides an easy way to convert methods that use common callback patterns into promises. */ @interface FBLPromise(WrapAdditions) /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with `nil` when completion handler is invoked. */ + (instancetype)wrapCompletion:(void (^)(FBLPromiseCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with `nil` when completion handler is invoked. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapCompletion:(void (^)(FBLPromiseCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler. */ + (instancetype)wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectCompletion:(void (^)(FBLPromiseObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error provided by completion handler. If error is `nil`, fulfills with `nil`, otherwise rejects with the error. */ + (instancetype)wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error provided by completion handler. If error is `nil`, fulfills with `nil`, otherwise rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorCompletion:(void (^)(FBLPromiseErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler if error is `nil`. Otherwise, rejects with the error. */ + (instancetype)wrapObjectOrErrorCompletion: (void (^)(FBLPromiseObjectOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an object provided by completion handler if error is `nil`. Otherwise, rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error or object provided by completion handler. If error is not `nil`, rejects with the error. */ + (instancetype)wrapErrorOrObjectCompletion: (void (^)(FBLPromiseErrorOrObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an error or object provided by completion handler. If error is not `nil`, rejects with the error. */ + (instancetype)onQueue:(dispatch_queue_t)queue wrapErrorOrObjectCompletion:(void (^)(FBLPromiseErrorOrObjectCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an array of objects provided by completion handler in order if error is `nil`. Otherwise, rejects with the error. */ + (FBLPromise*)wrap2ObjectsOrErrorCompletion: (void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an array of objects provided by completion handler in order if error is `nil`. Otherwise, rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrap2ObjectsOrErrorCompletion:(void (^)(FBLPromise2ObjectsOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO. */ + (FBLPromise*)wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapBoolCompletion:(void (^)(FBLPromiseBoolCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapBoolOrErrorCompletion: (void (^)(FBLPromiseBoolOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping YES/NO when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapBoolOrErrorCompletion:(void (^)(FBLPromiseBoolOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer. */ + (FBLPromise*)wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapIntegerCompletion:(void (^)(FBLPromiseIntegerCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapIntegerOrErrorCompletion: (void (^)(FBLPromiseIntegerOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping an integer when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapIntegerOrErrorCompletion:(void (^)(FBLPromiseIntegerOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double. */ + (FBLPromise*)wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapDoubleCompletion:(void (^)(FBLPromiseDoubleCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)wrapDoubleOrErrorCompletion: (void (^)(FBLPromiseDoubleOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); /** @param queue A queue to invoke the `work` block on. @param work A block to perform any operations needed to resolve the promise. @returns A promise that resolves with an `NSNumber` wrapping a double when error is `nil`. Otherwise rejects with the error. */ + (FBLPromise*)onQueue:(dispatch_queue_t)queue wrapDoubleOrErrorCompletion:(void (^)(FBLPromiseDoubleOrErrorCompletion handler))work NS_SWIFT_UNAVAILABLE(""); @end /** Convenience dot-syntax wrappers for `FBLPromise` `wrap` operators. Usage: FBLPromise.wrapCompletion(^(FBLPromiseCompletion handler) {...}) */ @interface FBLPromise(DotSyntax_WrapAdditions) + (FBLPromise* (^)(void (^)(FBLPromiseCompletion)))wrapCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseCompletion)))wrapCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseObjectCompletion)))wrapObjectCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseErrorCompletion)))wrapErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseObjectOrErrorCompletion)))wrapObjectOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseErrorOrObjectCompletion)))wrapErrorOrObjectCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromise2ObjectsOrErrorCompletion))) wrap2ObjectsOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseBoolCompletion)))wrapBoolCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseBoolOrErrorCompletion)))wrapBoolOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerCompletion)))wrapIntegerCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseIntegerOrErrorCompletion))) wrapIntegerOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleCompletion)))wrapDoubleCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletion FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (FBLPromise* (^)(dispatch_queue_t, void (^)(FBLPromiseDoubleOrErrorCompletion))) wrapDoubleOrErrorCompletionOn FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromise.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromiseError.h" NS_ASSUME_NONNULL_BEGIN /** Promises synchronization construct in Objective-C. */ @interface FBLPromise<__covariant Value> : NSObject /** Default dispatch queue used for `FBLPromise`, which is `main` if a queue is not specified. */ @property(class) dispatch_queue_t defaultDispatchQueue NS_REFINED_FOR_SWIFT; /** Creates a pending promise. */ + (instancetype)pendingPromise NS_REFINED_FOR_SWIFT; /** Creates a resolved promise. @param resolution An object to resolve the promise with: either a value or an error. @return A new resolved promise. */ + (instancetype)resolvedWith:(nullable id)resolution NS_REFINED_FOR_SWIFT; /** Synchronously fulfills the promise with a value. @param value An arbitrary value to fulfill the promise with, including `nil`. */ - (void)fulfill:(nullable Value)value NS_REFINED_FOR_SWIFT; /** Synchronously rejects the promise with an error. @param error An error to reject the promise with. */ - (void)reject:(NSError *)error NS_REFINED_FOR_SWIFT; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; @end @interface FBLPromise() /** Adds an object to the set of pending objects to keep strongly while the promise is pending. Used by the Swift wrappers to keep them alive until the underlying ObjC promise is resolved. @param object An object to add. */ - (void)addPendingObject:(id)object NS_REFINED_FOR_SWIFT; @end #ifdef FBL_PROMISES_DOT_SYNTAX_IS_DEPRECATED #define FBL_PROMISES_DOT_SYNTAX __attribute__((deprecated)) #else #define FBL_PROMISES_DOT_SYNTAX #endif @interface FBLPromise(DotSyntaxAdditions) /** Convenience dot-syntax wrappers for FBLPromise. Usage: FBLPromise.pending() FBLPromise.resolved(value) */ + (instancetype (^)(void))pending FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); + (instancetype (^)(id __nullable))resolved FBL_PROMISES_DOT_SYNTAX NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromiseError.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 FOUNDATION_EXTERN NSErrorDomain const FBLPromiseErrorDomain NS_REFINED_FOR_SWIFT; /** Possible error codes in `FBLPromiseErrorDomain`. */ typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) { /** Promise failed to resolve in time. */ FBLPromiseErrorCodeTimedOut = 1, /** Validation predicate returned false. */ FBLPromiseErrorCodeValidationFailure = 2, } NS_REFINED_FOR_SWIFT; NS_INLINE BOOL FBLPromiseErrorIsTimedOut(NSError *error) NS_SWIFT_UNAVAILABLE("") { return error.domain == FBLPromiseErrorDomain && error.code == FBLPromiseErrorCodeTimedOut; } NS_INLINE BOOL FBLPromiseErrorIsValidationFailure(NSError *error) NS_SWIFT_UNAVAILABLE("") { return error.domain == FBLPromiseErrorDomain && error.code == FBLPromiseErrorCodeValidationFailure; } NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromisePrivate.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+Testing.h" NS_ASSUME_NONNULL_BEGIN /** Miscellaneous low-level private interfaces available to extend standard FBLPromise functionality. */ @interface FBLPromise() typedef void (^FBLPromiseOnFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); typedef void (^FBLPromiseOnRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); typedef id __nullable (^__nullable FBLPromiseChainedFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE(""); typedef id __nullable (^__nullable FBLPromiseChainedRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE(""); /** Creates a pending promise. */ - (instancetype)initPending NS_SWIFT_UNAVAILABLE(""); /** Creates a resolved promise. @param resolution An object to resolve the promise with: either a value or an error. @return A new resolved promise. */ - (instancetype)initWithResolution:(nullable id)resolution NS_SWIFT_UNAVAILABLE(""); /** Invokes `fulfill` and `reject` blocks on `queue` when the receiver gets either fulfilled or rejected respectively. */ - (void)observeOnQueue:(dispatch_queue_t)queue fulfill:(FBLPromiseOnFulfillBlock)onFulfill reject:(FBLPromiseOnRejectBlock)onReject NS_SWIFT_UNAVAILABLE(""); /** Returns a new promise which gets resolved with the return value of `chainedFulfill` or `chainedReject` blocks respectively. The blocks are invoked when the receiver gets either fulfilled or rejected. If `nil` is passed to either block arg, the returned promise is resolved with the same resolution as the receiver. */ - (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill chainedReject:(FBLPromiseChainedRejectBlock)chainedReject NS_SWIFT_UNAVAILABLE(""); @end NS_ASSUME_NONNULL_END ================================================ FILE: Pods/PromisesObjC/Sources/FBLPromises/include/FBLPromises.h ================================================ /** Copyright 2018 Google Inc. All rights reserved. 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 "FBLPromise+All.h" #import "FBLPromise+Always.h" #import "FBLPromise+Any.h" #import "FBLPromise+Async.h" #import "FBLPromise+Await.h" #import "FBLPromise+Catch.h" #import "FBLPromise+Delay.h" #import "FBLPromise+Do.h" #import "FBLPromise+Race.h" #import "FBLPromise+Recover.h" #import "FBLPromise+Reduce.h" #import "FBLPromise+Retry.h" #import "FBLPromise+Then.h" #import "FBLPromise+Timeout.h" #import "FBLPromise+Validate.h" #import "FBLPromise+Wrap.h" ================================================ FILE: Pods/SDWebImage/LICENSE ================================================ Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Pods/SDWebImage/README.md ================================================

[![Build Status](http://img.shields.io/travis/SDWebImage/SDWebImage/master.svg?style=flat)](https://travis-ci.org/SDWebImage/SDWebImage) [![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/SDWebImage/SDWebImage) [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://swift.org/package-manager/) [![Mac Catalyst compatible](https://img.shields.io/badge/Catalyst-compatible-brightgreen.svg)](https://developer.apple.com/documentation/xcode/creating_a_mac_version_of_your_ipad_app/) [![codecov](https://codecov.io/gh/SDWebImage/SDWebImage/branch/master/graph/badge.svg)](https://codecov.io/gh/SDWebImage/SDWebImage) This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like `UIImageView`, `UIButton`, `MKAnnotationView`. ## Features - [x] Categories for `UIImageView`, `UIButton`, `MKAnnotationView` adding web image and cache management - [x] An asynchronous image downloader - [x] An asynchronous memory + disk image caching with automatic cache expiration handling - [x] A background image decompression to avoid frame rate drop - [x] [Progressive image loading](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#progressive-animation) (including animated image, like GIF showing in Web browser) - [x] [Thumbnail image decoding](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#thumbnail-decoding-550) to save CPU && Memory for large images - [x] [Extendable image coder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#custom-coder-420) to support massive image format, like WebP - [x] [Full-stack solution for animated images](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) which keep a balance between CPU && Memory - [x] [Customizable and composable transformations](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#transformer-50) can be applied to the images right after download - [x] [Customizable and multiple caches system](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#custom-cache-50) - [x] [Customizable and multiple loaders system](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#custom-loader-50) to expand the capabilities, like [Photos Library](https://github.com/SDWebImage/SDWebImagePhotosPlugin) - [x] [Image loading indicators](https://github.com/SDWebImage/SDWebImage/wiki/How-to-use#use-view-indicator-50) - [x] [Image loading transition animation](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#image-transition-430) - [x] A guarantee that the same URL won't be downloaded several times - [x] A guarantee that bogus URLs won't be retried again and again - [x] A guarantee that main thread will never be blocked - [x] Modern Objective-C and better Swift support - [x] Performances! ## Supported Image Formats - Image formats supported by Apple system (JPEG, PNG, TIFF, BMP, ...), including [GIF](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#gif-coder)/[APNG](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#apng-coder) animated image - HEIC format from iOS 11/macOS 10.13, including animated HEIC from iOS 13/macOS 10.15 via [SDWebImageHEICCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#heic-coder). For lower firmware, use coder plugin [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) - WebP format from iOS 14/macOS 11.0 via [SDWebImageAWebPCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#awebp-coder). For lower firmware, use coder plugin [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List) ## Additional modules and Ecosystem In order to keep SDWebImage focused and limited to the core features, but also allow extensibility and custom behaviors, during the 5.0 refactoring we focused on modularizing the library. As such, we have moved/built new modules to [SDWebImage org](https://github.com/SDWebImage). #### SwiftUI [SwiftUI](https://developer.apple.com/xcode/swiftui/) is an innovative UI framework written in Swift to build user interfaces across all Apple platforms. We support SwiftUI by building a brand new framework called [SDWebImageSwiftUI](https://github.com/SDWebImage/SDWebImageSwiftUI), which is built on top of SDWebImage core functions (caching, loading and animation). The new framework introduce two View structs `WebImage` and `AnimatedImage` for SwiftUI world, `ImageIndicator` modifier for any View, `ImageManager` observable object for data source. Supports iOS 13+/macOS 10.15+/tvOS 13+/watchOS 6+ and Swift 5.1. Have a nice try and provide feedback! #### Coders for additional image formats - [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - coder for WebP format. iOS 8+/macOS 10.10+. Based on [libwebp](https://chromium.googlesource.com/webm/libwebp) - [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) - coder for HEIF format, iOS 8+/macOS 10.10+ support. Based on [libheif](https://github.com/strukturag/libheif) - [SDWebImageBPGCoder](https://github.com/SDWebImage/SDWebImageBPGCoder) - coder for BPG format. Based on [libbpg](https://github.com/mirrorer/libbpg) - [SDWebImageFLIFCoder](https://github.com/SDWebImage/SDWebImageFLIFCoder) - coder for FLIF format. Based on [libflif](https://github.com/FLIF-hub/FLIF) - [SDWebImageAVIFCoder](https://github.com/SDWebImage/SDWebImageAVIFCoder) - coder for AVIF (AV1-based) format. Based on [libavif](https://github.com/AOMediaCodec/libavif) - [SDWebImagePDFCoder](https://github.com/SDWebImage/SDWebImagePDFCoder) - coder for PDF vector format. Using built-in frameworks - [SDWebImageSVGCoder](https://github.com/SDWebImage/SDWebImageSVGCoder) - coder for SVG vector format. Using built-in frameworks - [SDWebImageLottieCoder](https://github.com/SDWebImage/SDWebImageLottieCoder) - coder for Lottie animation format. Based on [rlottie](https://github.com/Samsung/rlottie) - and more from community! #### Custom Caches - [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to support caching images with [YYCache](https://github.com/ibireme/YYCache) - [SDWebImagePINPlugin](https://github.com/SDWebImage/SDWebImagePINPlugin) - plugin to support caching images with [PINCache](https://github.com/pinterest/PINCache) #### Custom Loaders - [SDWebImagePhotosPlugin](https://github.com/SDWebImage/SDWebImagePhotosPlugin) - plugin to support loading images from Photos (using `Photos.framework`) - [SDWebImageLinkPlugin](https://github.com/SDWebImage/SDWebImageLinkPlugin) - plugin to support loading images from rich link url, as well as `LPLinkView` (using `LinkPresentation.framework`) #### Integration with 3rd party libraries - [SDWebImageLottiePlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [Lottie-iOS](https://github.com/airbnb/lottie-ios), vector animation rending with remote JSON files - [SDWebImageSVGKitPlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [SVGKit](https://github.com/SVGKit/SVGKit), SVG rendering using Core Animation, iOS 8+/macOS 10.10+ support - [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin) - plugin to support [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) as the engine for animated GIFs - [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to integrate [YYImage](https://github.com/ibireme/YYImage) & [YYCache](https://github.com/ibireme/YYCache) for image rendering & caching #### Community driven popular libraries - [FirebaseUI](https://github.com/firebase/FirebaseUI-iOS) - Firebase Storage binding for query images, based on SDWebImage loader system - [react-native-fast-image](https://github.com/DylanVann/react-native-fast-image) - React Native fast image component, based on SDWebImage Animated Image solution - [flutter_image_compress](https://github.com/OpenFlutter/flutter_image_compress) - Flutter compresses image plugin, based on SDWebImage WebP coder plugin #### Make our lives easier - [libwebp-Xcode](https://github.com/SDWebImage/libwebp-Xcode) - A wrapper for [libwebp](https://chromium.googlesource.com/webm/libwebp) + an Xcode project. - [libheif-Xcode](https://github.com/SDWebImage/libheif-Xcode) - A wrapper for [libheif](https://github.com/strukturag/libheif) + an Xcode project. - [libavif-Xcode](https://github.com/SDWebImage/libavif-Xcode) - A wrapper for [libavif](https://github.com/AOMediaCodec/libavif) + an Xcode project. - and more third-party C/C++ image codec libraries with CocoaPods/Carthage/SwiftPM support. You can use those directly, or create similar components of your own, by using the customizable architecture of SDWebImage. ## Requirements - iOS 9.0 or later - tvOS 9.0 or later - watchOS 2.0 or later - macOS 10.11 or later (10.15 for Catalyst) - Xcode 11.0 or later #### Backwards compatibility - For iOS 8, macOS 10.10 or Xcode < 11, use [any 5.x version up to 5.9.5](https://github.com/SDWebImage/SDWebImage/releases/tag/5.9.5) - For iOS 7, macOS 10.9 or Xcode < 8, use [any 4.x version up to 4.4.6](https://github.com/SDWebImage/SDWebImage/releases/tag/4.4.6) - For macOS 10.8, use [any 4.x version up to 4.3.0](https://github.com/SDWebImage/SDWebImage/releases/tag/4.3.0) - For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/releases/tag/3.7.6) - For iOS < 5.0, please use the last [2.0 version](https://github.com/SDWebImage/SDWebImage/tree/2.0-compat). ## Getting Started - Read this Readme doc - Read the [How to use section](https://github.com/SDWebImage/SDWebImage#how-to-use) - Read the [Latest Documentation](https://sdwebimage.github.io/) and [CocoaDocs for old version](http://cocoadocs.org/docsets/SDWebImage/) - Try the example by downloading the project from Github or even easier using CocoaPods try `pod try SDWebImage` - Read the [Installation Guide](https://github.com/SDWebImage/SDWebImage/wiki/Installation-Guide) - Read the [SDWebImage 5.0 Migration Guide](https://github.com/SDWebImage/SDWebImage/blob/master/Docs/SDWebImage-5.0-Migration-guide.md) to get an idea of the changes from 4.x to 5.x - Read the [SDWebImage 4.0 Migration Guide](https://github.com/SDWebImage/SDWebImage/blob/master/Docs/SDWebImage-4.0-Migration-guide.md) to get an idea of the changes from 3.x to 4.x - Read the [Common Problems](https://github.com/SDWebImage/SDWebImage/wiki/Common-Problems) to find the solution for common problems - Go to the [Wiki Page](https://github.com/SDWebImage/SDWebImage/wiki) for more information such as [Advanced Usage](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage) ## Who Uses It - Find out [who uses SDWebImage](https://github.com/SDWebImage/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list. ## Communication - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage). (Tag 'sdwebimage') - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage). - If you **found a bug**, open an issue. - If you **have a feature request**, open an issue. - If you **need IRC channel**, use [Gitter](https://gitter.im/SDWebImage/community). ## Contribution - If you **want to contribute**, read the [Contributing Guide](https://github.com/SDWebImage/SDWebImage/blob/master/.github/CONTRIBUTING.md) - For **development contribution guide**, read the [How-To-Contribute](https://github.com/SDWebImage/SDWebImage/wiki/How-to-Contribute) - For **understanding code architecture**, read the [Code Architecture Analysis](https://github.com/SDWebImage/SDWebImage/wiki/5.6-Code-Architecture-Analysis) ## How To Use * Objective-C ```objective-c #import ... [imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; ``` * Swift ```swift import SDWebImage imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png")) ``` - For details about how to use the library and clear examples, see [The detailed How to use](https://github.com/SDWebImage/SDWebImage/blob/master/Docs/HowToUse.md) ## Animated Images (GIF) support In 5.0, we introduced a brand new mechanism for supporting animated images. This includes animated image loading, rendering, decoding, and also supports customizations (for advanced users). This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnimatedImage` is subclass of `UIImage/NSImage`, and `SDAnimatedImageView` is subclass of `UIImageView/NSImageView`, to make them compatible with the common frameworks APIs. The `SDAnimatedImageView` supports the familiar image loading category methods, works like drop-in replacement for `UIImageView/NSImageView`. Don't have `UIView` (like `WatchKit` or `CALayer`)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering. See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information. * Objective-C ```objective-c SDAnimatedImageView *imageView = [SDAnimatedImageView new]; SDAnimatedImage *animatedImage = [SDAnimatedImage imageNamed:@"image.gif"]; imageView.image = animatedImage; ``` * Swift ```swift let imageView = SDAnimatedImageView() let animatedImage = SDAnimatedImage(named: "image.gif") imageView.image = animatedImage ``` #### FLAnimatedImage integration has its own dedicated repo In order to clean up things and make our core project do less things, we decided that the `FLAnimatedImage` integration does not belong here. From 5.0, this will still be available, but under a dedicated repo [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin). ## Installation There are four ways to use SDWebImage in your project: - using CocoaPods - using Carthage - using Swift Package Manager - manual install (build frameworks or embed Xcode Project) ### Installation with CocoaPods [CocoaPods](http://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the [Get Started](http://cocoapods.org/#get_started) section for more details. #### Podfile ``` platform :ios, '8.0' pod 'SDWebImage', '~> 5.0' ``` ##### Swift and static framework Swift project previously had to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods work. However, starting with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`: ``` platform :ios, '8.0' # Uncomment the next line when you want all Pods as static framework # use_modular_headers! pod 'SDWebImage', :modular_headers => true ``` See more on [CocoaPods 1.5.0 — Swift Static Libraries](http://blog.cocoapods.org/CocoaPods-1.5.0/) If not, you still need to add `use_frameworks!` to use SDWebImage as dynamic framework: ``` platform :ios, '8.0' use_frameworks! pod 'SDWebImage' ``` #### Subspecs There are 2 subspecs available now: `Core` and `MapKit` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `MapKit`, you need to specify it). Podfile example: ``` pod 'SDWebImage/MapKit' ``` ### Installation with Carthage [Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods. To install with carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage) Carthage users can point to this repository and use whichever generated framework they'd like: SDWebImage, SDWebImageMapKit or both. Make the following entry in your Cartfile: `github "SDWebImage/SDWebImage"` Then run `carthage update` If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained [over at Carthage](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). > NOTE: At this time, Carthage does not provide a way to build only specific repository subcomponents (or equivalent of CocoaPods's subspecs). All components and their dependencies will be built with the above command. However, you don't need to copy frameworks you aren't using into your project. For instance, if you aren't using `SDWebImageMapKit`, feel free to delete that framework from the Carthage Build directory after `carthage update` completes. ### Installation with Swift Package Manager (Xcode 11+) [Swift Package Manager](https://swift.org/package-manager/) (SwiftPM) is a tool for managing the distribution of Swift code as well as C-family dependency. From Xcode 11, SwiftPM got natively integrated with Xcode. SDWebImage support SwiftPM from version 5.1.0. To use SwiftPM, you should use Xcode 11 to open your project. Click `File` -> `Swift Packages` -> `Add Package Dependency`, enter [SDWebImage repo's URL](https://github.com/SDWebImage/SDWebImage.git). Or you can login Xcode with your GitHub account and just type `SDWebImage` to search. After select the package, you can choose the dependency type (tagged version, branch or commit). Then Xcode will setup all the stuff for you. If you're a framework author and use SDWebImage as a dependency, update your `Package.swift` file: ```swift let package = Package( // 5.1.0 ..< 6.0.0 dependencies: [ .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.1.0") ], // ... ) ``` ### Manual Installation Guide See more on [Manual install Guide](https://github.com/SDWebImage/SDWebImage/wiki/Installation-Guide#manual-installation-guide) ### Import headers in your source files In the source files where you need to use the library, import the umbrella header file: ```objective-c #import ``` It's also recommend to use the module import syntax, available for CocoaPods(enable `modular_headers`)/Carthage/SwiftPM. ```objecitivec @import SDWebImage; ``` ### Build Project At this point your workspace should build without error. If you are having problem, post to the Issue and the community can help you solve it. ## Data Collection Practices As required by the [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), here's SDWebImage's list of [Data Collection Practices](https://sdwebimage.github.io/DataCollection/index.html). ## Author - [Olivier Poitrey](https://github.com/rs) ## Collaborators - [Konstantinos K.](https://github.com/mythodeia) - [Bogdan Poplauschi](https://github.com/bpoplauschi) - [Chester Liu](https://github.com/skyline75489) - [DreamPiggy](https://github.com/dreampiggy) - [Wu Zhong](https://github.com/zhongwuzw) ## Credits Thank you to all the people who have already contributed to SDWebImage. [![Contributors](https://opencollective.com/SDWebImage/contributors.svg?width=890)](https://github.com/SDWebImage/SDWebImage/graphs/contributors) ## Licenses All source code is licensed under the [MIT License](https://github.com/SDWebImage/SDWebImage/blob/master/LICENSE). ## Architecture To learn about SDWebImage's architecture design for contribution, read [The Core of SDWebImage v5.6 Architecture](https://github.com/SDWebImage/SDWebImage/wiki/5.6-Code-Architecture-Analysis). Thanks @looseyi for the post and translation. #### High Level Diagram

#### Overall Class Diagram

#### Top Level API Diagram

#### Main Sequence Diagram

#### More detailed diagrams - [Manager API Diagram](https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/Docs/Diagrams/SDWebImageManagerClassDiagram.png) - [Coders API Diagram](https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/Docs/Diagrams/SDWebImageCodersClassDiagram.png) - [Loader API Diagram](https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/Docs/Diagrams/SDWebImageLoaderClassDiagram.png) - [Cache API Diagram](https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/Docs/Diagrams/SDWebImageCacheClassDiagram.png) ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSButton+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_MAC #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with NSButton. */ @interface NSButton (WebCache) #pragma mark - Image /** * Get the current image URL. */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentImageURL; /** * Set the button `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the button `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; #pragma mark - Alternate Image /** * Get the current alternateImage URL. */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentAlternateImageURL; /** * Set the button `alternateImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; /** * Set the button `alternateImage` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @see sd_setAlternateImageWithURL:placeholderImage:options: */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the button `alternateImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the button `alternateImage` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the button `alternateImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the alternateImage was retrieved from the local cache or from the network. * The fourth parameter is the original alternateImage url. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `alternateImage` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the alternateImage was retrieved from the local cache or from the network. * The fourth parameter is the original alternateImage url. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the button `alternateImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the alternateImage was retrieved from the local cache or from the network. * The fourth parameter is the original alternateImage url. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `alternateImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while alternateImage is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the alternateImage was retrieved from the local cache or from the network. * The fourth parameter is the original alternateImage url. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `alternateImage` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the alternateImage. * @param placeholder The alternateImage to be set initially, until the alternateImage request finishes. * @param options The options to use when downloading the alternateImage. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while alternateImage is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the alternateImage parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the alternateImage was retrieved from the local cache or from the network. * The fourth parameter is the original alternateImage url. */ - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; #pragma mark - Cancel /** * Cancel the current image download */ - (void)sd_cancelCurrentImageLoad; /** * Cancel the current alternateImage download */ - (void)sd_cancelCurrentAlternateImageLoad; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSButton+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "NSButton+WebCache.h" #if SD_MAC #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageOperation"; @implementation NSButton (WebCache) #pragma mark - Image - (void)sd_setImageWithURL:(nullable NSURL *)url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { self.sd_currentImageURL = url; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:context setImageBlock:nil progress:progressBlock completed:^(NSImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } #pragma mark - Alternate Image - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url { [self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setAlternateImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setAlternateImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setAlternateImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { self.sd_currentAlternateImageURL = url; SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextSetImageOperationKey] = SDAlternateImageOperationKey; @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:mutableContext setImageBlock:^(NSImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { @strongify(self); self.alternateImage = image; } progress:progressBlock completed:^(NSImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } #pragma mark - Cancel - (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])]; } - (void)sd_cancelCurrentAlternateImageLoad { [self sd_cancelImageLoadOperationWithKey:SDAlternateImageOperationKey]; } #pragma mar - Private - (NSURL *)sd_currentImageURL { return objc_getAssociatedObject(self, @selector(sd_currentImageURL)); } - (void)setSd_currentImageURL:(NSURL *)sd_currentImageURL { objc_setAssociatedObject(self, @selector(sd_currentImageURL), sd_currentImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSURL *)sd_currentAlternateImageURL { return objc_getAssociatedObject(self, @selector(sd_currentAlternateImageURL)); } - (void)setSd_currentAlternateImageURL:(NSURL *)sd_currentAlternateImageURL { objc_setAssociatedObject(self, @selector(sd_currentAlternateImageURL), sd_currentAlternateImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSData+ImageContentType.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Fabrice Aneche * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /** You can use switch case like normal enum. It's also recommended to add a default case. You should not assume anything about the raw value. For custom coder plugin, it can also extern the enum for supported format. See `SDImageCoder` for more detailed information. */ typedef NSInteger SDImageFormat NS_TYPED_EXTENSIBLE_ENUM; static const SDImageFormat SDImageFormatUndefined = -1; static const SDImageFormat SDImageFormatJPEG = 0; static const SDImageFormat SDImageFormatPNG = 1; static const SDImageFormat SDImageFormatGIF = 2; static const SDImageFormat SDImageFormatTIFF = 3; static const SDImageFormat SDImageFormatWebP = 4; static const SDImageFormat SDImageFormatHEIC = 5; static const SDImageFormat SDImageFormatHEIF = 6; static const SDImageFormat SDImageFormatPDF = 7; static const SDImageFormat SDImageFormatSVG = 8; /** NSData category about the image content type and UTI. */ @interface NSData (ImageContentType) /** * Return image format * * @param data the input image data * * @return the image format as `SDImageFormat` (enum) */ + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data; /** * Convert SDImageFormat to UTType * * @param format Format as SDImageFormat * @return The UTType as CFStringRef * @note For unknown format, `kUTTypeImage` abstract type will return */ + (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:)); /** * Convert UTType to SDImageFormat * * @param uttype The UTType as CFStringRef * @return The Format as SDImageFormat * @note For unknown type, `SDImageFormatUndefined` will return */ + (SDImageFormat)sd_imageFormatFromUTType:(nonnull CFStringRef)uttype; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSData+ImageContentType.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Fabrice Aneche * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "NSData+ImageContentType.h" #if SD_MAC #import #else #import #endif #import "SDImageIOAnimatedCoderInternal.h" #define kSVGTagEnd @"" @implementation NSData (ImageContentType) + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data { if (!data) { return SDImageFormatUndefined; } // File signatures table: http://www.garykessler.net/library/file_sigs.html uint8_t c; [data getBytes:&c length:1]; switch (c) { case 0xFF: return SDImageFormatJPEG; case 0x89: return SDImageFormatPNG; case 0x47: return SDImageFormatGIF; case 0x49: case 0x4D: return SDImageFormatTIFF; case 0x52: { if (data.length >= 12) { //RIFF....WEBP NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return SDImageFormatWebP; } } break; } case 0x00: { if (data.length >= 12) { //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding]; if ([testString isEqualToString:@"ftypheic"] || [testString isEqualToString:@"ftypheix"] || [testString isEqualToString:@"ftyphevc"] || [testString isEqualToString:@"ftyphevx"]) { return SDImageFormatHEIC; } //....ftypmif1 ....ftypmsf1 if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) { return SDImageFormatHEIF; } } break; } case 0x25: { if (data.length >= 4) { //%PDF NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding]; if ([testString isEqualToString:@"PDF"]) { return SDImageFormatPDF; } } } case 0x3C: { // Check end with SVG tag if ([data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound) { return SDImageFormatSVG; } } } return SDImageFormatUndefined; } + (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format { CFStringRef UTType; switch (format) { case SDImageFormatJPEG: UTType = kUTTypeJPEG; break; case SDImageFormatPNG: UTType = kUTTypePNG; break; case SDImageFormatGIF: UTType = kUTTypeGIF; break; case SDImageFormatTIFF: UTType = kUTTypeTIFF; break; case SDImageFormatWebP: UTType = kSDUTTypeWebP; break; case SDImageFormatHEIC: UTType = kSDUTTypeHEIC; break; case SDImageFormatHEIF: UTType = kSDUTTypeHEIF; break; case SDImageFormatPDF: UTType = kUTTypePDF; break; case SDImageFormatSVG: UTType = kUTTypeScalableVectorGraphics; break; default: // default is kUTTypeImage abstract type UTType = kUTTypeImage; break; } return UTType; } + (SDImageFormat)sd_imageFormatFromUTType:(CFStringRef)uttype { if (!uttype) { return SDImageFormatUndefined; } SDImageFormat imageFormat; if (CFStringCompare(uttype, kUTTypeJPEG, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatJPEG; } else if (CFStringCompare(uttype, kUTTypePNG, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatPNG; } else if (CFStringCompare(uttype, kUTTypeGIF, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatGIF; } else if (CFStringCompare(uttype, kUTTypeTIFF, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatTIFF; } else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatWebP; } else if (CFStringCompare(uttype, kSDUTTypeHEIC, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatHEIC; } else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatHEIF; } else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatPDF; } else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatSVG; } else { imageFormat = SDImageFormatUndefined; } return imageFormat; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSImage+Compatibility.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_MAC /** This category is provided to easily write cross-platform(AppKit/UIKit) code. For common usage, see `UIImage+Metadata.h`. */ @interface NSImage (Compatibility) /** The underlying Core Graphics image object. This will actually use `CGImageForProposedRect` with the image size. */ @property (nonatomic, readonly, nullable) CGImageRef CGImage; /** The underlying Core Image data. This will actually use `bestRepresentationForRect` with the image size to find the `NSCIImageRep`. */ @property (nonatomic, readonly, nullable) CIImage *CIImage; /** The scale factor of the image. This wil actually use `bestRepresentationForRect` with image size and pixel size to calculate the scale factor. If failed, use the default value 1.0. Should be greater than or equal to 1.0. */ @property (nonatomic, readonly) CGFloat scale; // These are convenience methods to make AppKit's `NSImage` match UIKit's `UIImage` behavior. The scale factor should be greater than or equal to 1.0. /** Returns an image object with the scale factor and orientation. The representation is created from the Core Graphics image object. @note The difference between this and `initWithCGImage:size` is that `initWithCGImage:size` will actually create a `NSCGImageSnapshotRep` representation and always use `backingScaleFactor` as scale factor. So we should avoid it and use `NSBitmapImageRep` with `initWithCGImage:` instead. @note The difference between this and UIKit's `UIImage` equivalent method is the way to process orientation. If the provided image orientation is not equal to Up orientation, this method will firstly rotate the CGImage to the correct orientation to work compatible with `NSImageView`. However, UIKit will not actually rotate CGImage and just store it as `imageOrientation` property. @param cgImage A Core Graphics image object @param scale The image scale factor @param orientation The orientation of the image data @return The image object */ - (nonnull instancetype)initWithCGImage:(nonnull CGImageRef)cgImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation; /** Initializes and returns an image object with the specified Core Image object. The representation is `NSCIImageRep`. @param ciImage A Core Image image object @param scale The image scale factor @param orientation The orientation of the image data @return The image object */ - (nonnull instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation; /** Returns an image object with the scale factor. The representation is created from the image data. @note The difference between these this and `initWithData:` is that `initWithData:` will always use `backingScaleFactor` as scale factor. @param data The image data @param scale The image scale factor @return The image object */ - (nullable instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/NSImage+Compatibility.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "NSImage+Compatibility.h" #if SD_MAC #import "SDImageCoderHelper.h" @implementation NSImage (Compatibility) - (nullable CGImageRef)CGImage { NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); CGImageRef cgImage = [self CGImageForProposedRect:&imageRect context:nil hints:nil]; return cgImage; } - (nullable CIImage *)CIImage { NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; if (![imageRep isKindOfClass:NSCIImageRep.class]) { return nil; } return ((NSCIImageRep *)imageRep).CIImage; } - (CGFloat)scale { CGFloat scale = 1; NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; CGFloat width = imageRep.size.width; CGFloat height = imageRep.size.height; NSUInteger pixelWidth = imageRep.pixelsWide; NSUInteger pixelHeight = imageRep.pixelsHigh; if (width > 0 && height > 0) { CGFloat widthScale = pixelWidth / width; CGFloat heightScale = pixelHeight / height; if (widthScale == heightScale && widthScale >= 1) { // Protect because there may be `NSImageRepMatchesDevice` (0) scale = widthScale; } } return scale; } - (instancetype)initWithCGImage:(nonnull CGImageRef)cgImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation { NSBitmapImageRep *imageRep; if (orientation != kCGImagePropertyOrientationUp) { // AppKit design is different from UIKit. Where CGImage based image rep does not respect to any orientation. Only data based image rep which contains the EXIF metadata can automatically detect orientation. // This should be nonnull, until the memory is exhausted cause `CGBitmapContextCreate` failed. CGImageRef rotatedCGImage = [SDImageCoderHelper CGImageCreateDecoded:cgImage orientation:orientation]; imageRep = [[NSBitmapImageRep alloc] initWithCGImage:rotatedCGImage]; CGImageRelease(rotatedCGImage); } else { imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; } if (scale < 1) { scale = 1; } CGFloat pixelWidth = imageRep.pixelsWide; CGFloat pixelHeight = imageRep.pixelsHigh; NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale); self = [self initWithSize:size]; if (self) { imageRep.size = size; [self addRepresentation:imageRep]; } return self; } - (instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation { NSCIImageRep *imageRep; if (orientation != kCGImagePropertyOrientationUp) { CIImage *rotatedCIImage = [ciImage imageByApplyingOrientation:orientation]; imageRep = [[NSCIImageRep alloc] initWithCIImage:rotatedCIImage]; } else { imageRep = [[NSCIImageRep alloc] initWithCIImage:ciImage]; } if (scale < 1) { scale = 1; } CGFloat pixelWidth = imageRep.pixelsWide; CGFloat pixelHeight = imageRep.pixelsHigh; NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale); self = [self initWithSize:size]; if (self) { imageRep.size = size; [self addRepresentation:imageRep]; } return self; } - (instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale { NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithData:data]; if (!imageRep) { return nil; } if (scale < 1) { scale = 1; } CGFloat pixelWidth = imageRep.pixelsWide; CGFloat pixelHeight = imageRep.pixelsHigh; NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale); self = [self initWithSize:size]; if (self) { imageRep.size = size; [self addRepresentation:imageRep]; } return self; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImage.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDImageCoder.h" /** This is the protocol for SDAnimatedImage class only but not for SDAnimatedImageCoder. If you want to provide a custom animated image class with full advanced function, you can conform to this instead of the base protocol. */ @protocol SDAnimatedImage @required /** Initializes and returns the image object with the specified data, scale factor and possible animation decoding options. @note We use this to create animated image instance for normal animation decoding. @param data The data object containing the image data. @param scale The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property. @param options A dictionary containing any animation decoding options. @return An initialized object */ - (nullable instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale options:(nullable SDImageCoderOptions *)options; /** Initializes the image with an animated coder. You can use the coder to decode the image frame later. @note We use this with animated coder which conforms to `SDProgressiveImageCoder` for progressive animation decoding. @param animatedCoder An animated coder which conform `SDAnimatedImageCoder` protocol @param scale The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property. @return An initialized object */ - (nullable instancetype)initWithAnimatedCoder:(nonnull id)animatedCoder scale:(CGFloat)scale; @optional // These methods are used for optional advanced feature, like image frame preloading. /** Pre-load all animated image frame into memory. Then later frame image request can directly return the frame for index without decoding. This method may be called on background thread. @note If one image instance is shared by lots of imageViews, the CPU performance for large animated image will drop down because the request frame index will be random (not in order) and the decoder should take extra effort to keep it re-entrant. You can use this to reduce CPU usage if need. Attention this will consume more memory usage. */ - (void)preloadAllFrames; /** Unload all animated image frame from memory if are already pre-loaded. Then later frame image request need decoding. You can use this to free up the memory usage if need. */ - (void)unloadAllFrames; /** Returns a Boolean value indicating whether all animated image frames are already pre-loaded into memory. */ @property (nonatomic, assign, readonly, getter=isAllFramesLoaded) BOOL allFramesLoaded; /** Return the animated image coder if the image is created with `initWithAnimatedCoder:scale:` method. @note We use this with animated coder which conforms to `SDProgressiveImageCoder` for progressive animation decoding. */ @property (nonatomic, strong, readonly, nullable) id animatedCoder; @end /** The image class which supports animating on `SDAnimatedImageView`. You can also use it on normal UIImageView/NSImageView. */ @interface SDAnimatedImage : UIImage // This class override these methods from UIImage(NSImage), and it supports NSSecureCoding. // You should use these methods to create a new animated image. Use other methods just call super instead. // Pay attention, when the animated image frame count <= 1, all the `SDAnimatedImageProvider` protocol methods will return nil or 0 value, you'd better check the frame count before usage and keep fallback. + (nullable instancetype)imageNamed:(nonnull NSString *)name; // Cache in memory, no Asset Catalog support #if __has_include() + (nullable instancetype)imageNamed:(nonnull NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; // Cache in memory, no Asset Catalog support #else + (nullable instancetype)imageNamed:(nonnull NSString *)name inBundle:(nullable NSBundle *)bundle; // Cache in memory, no Asset Catalog support #endif + (nullable instancetype)imageWithContentsOfFile:(nonnull NSString *)path; + (nullable instancetype)imageWithData:(nonnull NSData *)data; + (nullable instancetype)imageWithData:(nonnull NSData *)data scale:(CGFloat)scale; - (nullable instancetype)initWithContentsOfFile:(nonnull NSString *)path; - (nullable instancetype)initWithData:(nonnull NSData *)data; - (nullable instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale; /** Current animated image format. */ @property (nonatomic, assign, readonly) SDImageFormat animatedImageFormat; /** Current animated image data, you can use this to grab the compressed format data and create another animated image instance. If this image instance is an animated image created by using animated image coder (which means using the API listed above or using `initWithAnimatedCoder:scale:`), this property is non-nil. */ @property (nonatomic, copy, readonly, nullable) NSData *animatedImageData; /** The scale factor of the image. @note For UIKit, this just call super instead. @note For AppKit, `NSImage` can contains multiple image representations with different scales. However, this class does not do that from the design. We process the scale like UIKit. This will actually be calculated from image size and pixel size. */ @property (nonatomic, readonly) CGFloat scale; // By default, animated image frames are returned by decoding just in time without keeping into memory. But you can choose to preload them into memory as well, See the description in `SDAnimatedImage` protocol. // After preloaded, there is no huge difference on performance between this and UIImage's `animatedImageWithImages:duration:`. But UIImage's animation have some issues such like blanking and pausing during segue when using in `UIImageView`. It's recommend to use only if need. - (void)preloadAllFrames; - (void)unloadAllFrames; @property (nonatomic, assign, readonly, getter=isAllFramesLoaded) BOOL allFramesLoaded; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImage.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImage.h" #import "NSImage+Compatibility.h" #import "SDImageCoder.h" #import "SDImageCodersManager.h" #import "SDImageFrame.h" #import "UIImage+MemoryCacheCost.h" #import "UIImage+Metadata.h" #import "UIImage+MultiFormat.h" #import "SDImageCoderHelper.h" #import "SDImageAssetManager.h" #import "objc/runtime.h" static CGFloat SDImageScaleFromPath(NSString *string) { if (string.length == 0 || [string hasSuffix:@"/"]) return 1; NSString *name = string.stringByDeletingPathExtension; __block CGFloat scale = 1; NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil]; [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue; }]; return scale; } @interface SDAnimatedImage () @property (nonatomic, strong) id animatedCoder; @property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat; @property (atomic, copy) NSArray *loadedAnimatedImageFrames; // Mark as atomic to keep thread-safe @property (nonatomic, assign, getter=isAllFramesLoaded) BOOL allFramesLoaded; @end @implementation SDAnimatedImage @dynamic scale; // call super #pragma mark - UIImage override method + (instancetype)imageNamed:(NSString *)name { #if __has_include() return [self imageNamed:name inBundle:nil compatibleWithTraitCollection:nil]; #else return [self imageNamed:name inBundle:nil]; #endif } #if __has_include() + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection { if (!traitCollection) { traitCollection = UIScreen.mainScreen.traitCollection; } CGFloat scale = traitCollection.displayScale; return [self imageNamed:name inBundle:bundle scale:scale]; } #else + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle { return [self imageNamed:name inBundle:bundle scale:0]; } #endif // 0 scale means automatically check + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale { if (!name) { return nil; } if (!bundle) { bundle = [NSBundle mainBundle]; } SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager]; SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name]; if ([image isKindOfClass:[SDAnimatedImage class]]) { return image; } NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale]; if (!path) { return image; } NSData *data = [NSData dataWithContentsOfFile:path]; if (!data) { return image; } image = [[self alloc] initWithData:data scale:scale]; if (image) { [assetManager storeImage:image forName:name]; } return image; } + (instancetype)imageWithContentsOfFile:(NSString *)path { return [[self alloc] initWithContentsOfFile:path]; } + (instancetype)imageWithData:(NSData *)data { return [[self alloc] initWithData:data]; } + (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale { return [[self alloc] initWithData:data scale:scale]; } - (instancetype)initWithContentsOfFile:(NSString *)path { NSData *data = [NSData dataWithContentsOfFile:path]; return [self initWithData:data scale:SDImageScaleFromPath(path)]; } - (instancetype)initWithData:(NSData *)data { return [self initWithData:data scale:1]; } - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale { return [self initWithData:data scale:scale options:nil]; } - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options { if (!data || data.length == 0) { return nil; } data = [data copy]; // avoid mutable data id animatedCoder = nil; for (idcoder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) { if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { if ([coder canDecodeFromData:data]) { if (!options) { options = @{SDImageCoderDecodeScaleFactor : @(scale)}; } animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options]; break; } } } if (!animatedCoder) { return nil; } return [self initWithAnimatedCoder:animatedCoder scale:scale]; } - (instancetype)initWithAnimatedCoder:(id)animatedCoder scale:(CGFloat)scale { if (!animatedCoder) { return nil; } UIImage *image = [animatedCoder animatedImageFrameAtIndex:0]; if (!image) { return nil; } #if SD_MAC self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp]; #else self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation]; #endif if (self) { // Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP) if (animatedCoder.animatedImageFrameCount > 1) { _animatedCoder = animatedCoder; } NSData *data = [animatedCoder animatedImageData]; SDImageFormat format = [NSData sd_imageFormatForImageData:data]; _animatedImageFormat = format; } return self; } #pragma mark - Preload - (void)preloadAllFrames { if (!_animatedCoder) { return; } if (!self.isAllFramesLoaded) { NSMutableArray *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount]; for (size_t i = 0; i < self.animatedImageFrameCount; i++) { UIImage *image = [self animatedImageFrameAtIndex:i]; NSTimeInterval duration = [self animatedImageDurationAtIndex:i]; SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:` [frames addObject:frame]; } self.loadedAnimatedImageFrames = frames; self.allFramesLoaded = YES; } } - (void)unloadAllFrames { if (!_animatedCoder) { return; } if (self.isAllFramesLoaded) { self.loadedAnimatedImageFrames = nil; self.allFramesLoaded = NO; } } #pragma mark - NSSecureCoding - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { _animatedImageFormat = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(animatedImageFormat))]; NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))]; if (!animatedImageData) { return self; } CGFloat scale = self.scale; id animatedCoder = nil; for (idcoder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) { if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { if ([coder canDecodeFromData:animatedImageData]) { animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}]; break; } } } if (!animatedCoder) { return self; } if (animatedCoder.animatedImageFrameCount > 1) { _animatedCoder = animatedCoder; } } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeInteger:self.animatedImageFormat forKey:NSStringFromSelector(@selector(animatedImageFormat))]; NSData *animatedImageData = self.animatedImageData; if (animatedImageData) { [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))]; } } + (BOOL)supportsSecureCoding { return YES; } #pragma mark - SDAnimatedImageProvider - (NSData *)animatedImageData { return [self.animatedCoder animatedImageData]; } - (NSUInteger)animatedImageLoopCount { return [self.animatedCoder animatedImageLoopCount]; } - (NSUInteger)animatedImageFrameCount { return [self.animatedCoder animatedImageFrameCount]; } - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { if (index >= self.animatedImageFrameCount) { return nil; } if (self.isAllFramesLoaded) { SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index]; return frame.image; } return [self.animatedCoder animatedImageFrameAtIndex:index]; } - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { if (index >= self.animatedImageFrameCount) { return 0; } if (self.isAllFramesLoaded) { SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index]; return frame.duration; } return [self.animatedCoder animatedImageDurationAtIndex:index]; } @end @implementation SDAnimatedImage (MemoryCacheCost) - (NSUInteger)sd_memoryCost { NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost)); if (value != nil) { return value.unsignedIntegerValue; } CGImageRef imageRef = self.CGImage; if (!imageRef) { return 0; } NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef); NSUInteger frameCount = 1; if (self.isAllFramesLoaded) { frameCount = self.animatedImageFrameCount; } frameCount = frameCount > 0 ? frameCount : 1; NSUInteger cost = bytesPerFrame * frameCount; return cost; } @end @implementation SDAnimatedImage (Metadata) - (BOOL)sd_isAnimated { return YES; } - (NSUInteger)sd_imageLoopCount { return self.animatedImageLoopCount; } - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { return; } - (SDImageFormat)sd_imageFormat { return self.animatedImageFormat; } - (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat { return; } - (BOOL)sd_isVector { return NO; } @end @implementation SDAnimatedImage (MultiFormat) + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { return [self sd_imageWithData:data scale:1]; } + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale { return [self sd_imageWithData:data scale:scale firstFrameOnly:NO]; } + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly { if (!data) { return nil; } return [[self alloc] initWithData:data scale:scale options:@{SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}]; } - (nullable NSData *)sd_imageData { NSData *imageData = self.animatedImageData; if (imageData) { return imageData; } else { return [self sd_imageDataAsFormat:self.animatedImageFormat]; } } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat { return [self sd_imageDataAsFormat:imageFormat compressionQuality:1]; } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality { return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO]; } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly { if (firstFrameOnly) { // First frame, use super implementation return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly]; } NSUInteger frameCount = self.animatedImageFrameCount; if (frameCount <= 1) { // Static image, use super implementation return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly]; } // Keep animated image encoding, loop each frame. NSMutableArray *frames = [NSMutableArray arrayWithCapacity:frameCount]; for (size_t i = 0; i < frameCount; i++) { UIImage *image = [self animatedImageFrameAtIndex:i]; NSTimeInterval duration = [self animatedImageDurationAtIndex:i]; SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; [frames addObject:frame]; } UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames]; NSData *imageData = [animatedImage sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly]; return imageData; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImagePlayer.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDImageCoder.h" typedef NS_ENUM(NSUInteger, SDAnimatedImagePlaybackMode) { /** * From first to last frame and stop or next loop. */ SDAnimatedImagePlaybackModeNormal = 0, /** * From last frame to first frame and stop or next loop. */ SDAnimatedImagePlaybackModeReverse, /** * From first frame to last frame and reverse again, like reciprocating. */ SDAnimatedImagePlaybackModeBounce, /** * From last frame to first frame and reverse again, like reversed reciprocating. */ SDAnimatedImagePlaybackModeReversedBounce, }; /// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering. @interface SDAnimatedImagePlayer : NSObject /// Current playing frame image. This value is KVO Compliance. @property (nonatomic, readonly, nullable) UIImage *currentFrame; /// Current frame index, zero based. This value is KVO Compliance. @property (nonatomic, readonly) NSUInteger currentFrameIndex; /// Current loop count since its latest animating. This value is KVO Compliance. @property (nonatomic, readonly) NSUInteger currentLoopCount; /// Total frame count for animated image rendering. Defaults is animated image's frame count. /// @note For progressive animation, you can update this value when your provider receive more frames. @property (nonatomic, assign) NSUInteger totalFrameCount; /// Total loop count for animated image rendering. Default is animated image's loop count. @property (nonatomic, assign) NSUInteger totalLoopCount; /// The animation playback rate. Default is 1.0 /// `1.0` means the normal speed. /// `0.0` means stopping the animation. /// `0.0-1.0` means the slow speed. /// `> 1.0` means the fast speed. /// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future) @property (nonatomic, assign) double playbackRate; /// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal. @property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode; /// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0. /// `0` means automatically adjust by calculating current memory usage. /// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU) /// `NSUIntegerMax` means cache all the buffer. (Lowest CPU and Highest Memory) @property (nonatomic, assign) NSUInteger maxBufferSize; /// You can specify a runloop mode to let it rendering. /// Default is NSRunLoopCommonModes on multi-core device, NSDefaultRunLoopMode on single-core device @property (nonatomic, copy, nonnull) NSRunLoopMode runLoopMode; /// Create a player with animated image provider. If the provider's `animatedImageFrameCount` is less than 1, returns nil. /// The provider can be any protocol implementation, like `SDAnimatedImage`, `SDImageGIFCoder`, etc. /// @note This provider can represent mutable content, like progressive animated loading. But you need to update the frame count by yourself /// @param provider The animated provider - (nullable instancetype)initWithProvider:(nonnull id)provider; /// Create a player with animated image provider. If the provider's `animatedImageFrameCount` is less than 1, returns nil. /// The provider can be any protocol implementation, like `SDAnimatedImage` or `SDImageGIFCoder`, etc. /// @note This provider can represent mutable content, like progressive animated loading. But you need to update the frame count by yourself /// @param provider The animated provider + (nullable instancetype)playerWithProvider:(nonnull id)provider; /// The handler block when current frame and index changed. @property (nonatomic, copy, nullable) void (^animationFrameHandler)(NSUInteger index, UIImage * _Nonnull frame); /// The handler block when one loop count finished. @property (nonatomic, copy, nullable) void (^animationLoopHandler)(NSUInteger loopCount); /// Return the status whether animation is playing. @property (nonatomic, readonly) BOOL isPlaying; /// Start the animation. Or resume the previously paused animation. - (void)startPlaying; /// Pause the animation. Keep the current frame index and loop count. - (void)pausePlaying; /// Stop the animation. Reset the current frame index and loop count. - (void)stopPlaying; /// Seek to the desired frame index and loop count. /// @note This can be used for advanced control like progressive loading, or skipping specify frames. /// @param index The frame index /// @param loopCount The loop count - (void)seekToFrameAtIndex:(NSUInteger)index loopCount:(NSUInteger)loopCount; /// Clear the frame cache buffer. The frame cache buffer size can be controlled by `maxBufferSize`. /// By default, when stop or pause the animation, the frame buffer is still kept to ready for the next restart - (void)clearFrameBuffer; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImagePlayer.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImagePlayer.h" #import "NSImage+Compatibility.h" #import "SDDisplayLink.h" #import "SDDeviceHelper.h" #import "SDInternalMacros.h" @interface SDAnimatedImagePlayer () { SD_LOCK_DECLARE(_lock); NSRunLoopMode _runLoopMode; } @property (nonatomic, strong, readwrite) UIImage *currentFrame; @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex; @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount; @property (nonatomic, strong) id animatedProvider; @property (nonatomic, strong) NSMutableDictionary *frameBuffer; @property (nonatomic, assign) NSTimeInterval currentTime; @property (nonatomic, assign) BOOL bufferMiss; @property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable; @property (nonatomic, assign) BOOL shouldReverse; @property (nonatomic, assign) NSUInteger maxBufferCount; @property (nonatomic, strong) NSOperationQueue *fetchQueue; @property (nonatomic, strong) SDDisplayLink *displayLink; @end @implementation SDAnimatedImagePlayer - (instancetype)initWithProvider:(id)provider { self = [super init]; if (self) { NSUInteger animatedImageFrameCount = provider.animatedImageFrameCount; // Check the frame count if (animatedImageFrameCount <= 1) { return nil; } self.totalFrameCount = animatedImageFrameCount; // Get the current frame and loop count. self.totalLoopCount = provider.animatedImageLoopCount; self.animatedProvider = provider; self.playbackRate = 1.0; SD_LOCK_INIT(_lock); #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; } + (instancetype)playerWithProvider:(id)provider { SDAnimatedImagePlayer *player = [[SDAnimatedImagePlayer alloc] initWithProvider:provider]; return player; } #pragma mark - Life Cycle - (void)dealloc { #if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } - (void)didReceiveMemoryWarning:(NSNotification *)notification { [_fetchQueue cancelAllOperations]; [_fetchQueue addOperationWithBlock:^{ NSNumber *currentFrameIndex = @(self.currentFrameIndex); SD_LOCK(self->_lock); NSArray *keys = self.frameBuffer.allKeys; // only keep the next frame for later rendering for (NSNumber * key in keys) { if (![key isEqualToNumber:currentFrameIndex]) { [self.frameBuffer removeObjectForKey:key]; } } SD_UNLOCK(self->_lock); }]; } #pragma mark - Private - (NSOperationQueue *)fetchQueue { if (!_fetchQueue) { _fetchQueue = [[NSOperationQueue alloc] init]; _fetchQueue.maxConcurrentOperationCount = 1; } return _fetchQueue; } - (NSMutableDictionary *)frameBuffer { if (!_frameBuffer) { _frameBuffer = [NSMutableDictionary dictionary]; } return _frameBuffer; } - (SDDisplayLink *)displayLink { if (!_displayLink) { _displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode]; [_displayLink stop]; } return _displayLink; } - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode { if ([_runLoopMode isEqual:runLoopMode]) { return; } if (_displayLink) { if (_runLoopMode) { [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runLoopMode]; } if (runLoopMode.length > 0) { [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode]; } } _runLoopMode = [runLoopMode copy]; } - (NSRunLoopMode)runLoopMode { if (!_runLoopMode) { _runLoopMode = [[self class] defaultRunLoopMode]; } return _runLoopMode; } #pragma mark - State Control - (void)setupCurrentFrame { if (self.currentFrameIndex != 0) { return; } if (self.playbackMode == SDAnimatedImagePlaybackModeReverse || self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) { self.currentFrameIndex = self.totalFrameCount - 1; } if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) { UIImage *image = (UIImage *)self.animatedProvider; // Use the poster image if available #if SD_MAC UIImage *posterFrame = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation]; #endif if (posterFrame) { self.currentFrame = posterFrame; SD_LOCK(self->_lock); self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame; SD_UNLOCK(self->_lock); [self handleFrameChange]; } } } - (void)resetCurrentFrameStatus { // These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback. _currentFrame = nil; _currentFrameIndex = 0; _currentLoopCount = 0; _currentTime = 0; _bufferMiss = NO; _needsDisplayWhenImageBecomesAvailable = NO; } - (void)clearFrameBuffer { SD_LOCK(_lock); [_frameBuffer removeAllObjects]; SD_UNLOCK(_lock); } #pragma mark - Animation Control - (void)startPlaying { [self.displayLink start]; // Setup frame [self setupCurrentFrame]; // Calculate max buffer size [self calculateMaxBufferCount]; } - (void)stopPlaying { [_fetchQueue cancelAllOperations]; // Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method. [_displayLink stop]; // We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct. [self resetCurrentFrameStatus]; } - (void)pausePlaying { [_fetchQueue cancelAllOperations]; [_displayLink stop]; } - (BOOL)isPlaying { return _displayLink.isRunning; } - (void)seekToFrameAtIndex:(NSUInteger)index loopCount:(NSUInteger)loopCount { if (index >= self.totalFrameCount) { return; } self.currentFrameIndex = index; self.currentLoopCount = loopCount; self.currentFrame = [self.animatedProvider animatedImageFrameAtIndex:index]; [self handleFrameChange]; } #pragma mark - Core Render - (void)displayDidRefresh:(SDDisplayLink *)displayLink { // If for some reason a wild call makes it through when we shouldn't be animating, bail. // Early return! if (!self.isPlaying) { return; } NSUInteger totalFrameCount = self.totalFrameCount; if (totalFrameCount <= 1) { // Total frame count less than 1, wrong configuration and stop animating [self stopPlaying]; return; } NSTimeInterval playbackRate = self.playbackRate; if (playbackRate <= 0) { // Does not support <= 0 play rate [self stopPlaying]; return; } // Calculate refresh duration NSTimeInterval duration = self.displayLink.duration; NSUInteger currentFrameIndex = self.currentFrameIndex; NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount; if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) { nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount; } else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce || self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) { if (currentFrameIndex == 0) { self.shouldReverse = false; } else if (currentFrameIndex == totalFrameCount - 1) { self.shouldReverse = true; } nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1); nextFrameIndex %= totalFrameCount; } // Check if we need to display new frame firstly BOOL bufferFull = NO; if (self.needsDisplayWhenImageBecomesAvailable) { UIImage *currentFrame; SD_LOCK(_lock); currentFrame = self.frameBuffer[@(currentFrameIndex)]; SD_UNLOCK(_lock); // Update the current frame if (currentFrame) { SD_LOCK(_lock); // Remove the frame buffer if need if (self.frameBuffer.count > self.maxBufferCount) { self.frameBuffer[@(currentFrameIndex)] = nil; } // Check whether we can stop fetch if (self.frameBuffer.count == totalFrameCount) { bufferFull = YES; } SD_UNLOCK(_lock); // Update the current frame immediately self.currentFrame = currentFrame; [self handleFrameChange]; self.bufferMiss = NO; self.needsDisplayWhenImageBecomesAvailable = NO; } else { self.bufferMiss = YES; } } // Check if we have the frame buffer if (!self.bufferMiss) { // Then check if timestamp is reached self.currentTime += duration; NSTimeInterval currentDuration = [self.animatedProvider animatedImageDurationAtIndex:currentFrameIndex]; currentDuration = currentDuration / playbackRate; if (self.currentTime < currentDuration) { // Current frame timestamp not reached, return return; } // Otherwise, we should be ready to display next frame self.needsDisplayWhenImageBecomesAvailable = YES; self.currentFrameIndex = nextFrameIndex; self.currentTime -= currentDuration; NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex]; nextDuration = nextDuration / playbackRate; if (self.currentTime > nextDuration) { // Do not skip frame self.currentTime = nextDuration; } // Update the loop count when last frame rendered if (nextFrameIndex == 0) { // Update the loop count self.currentLoopCount++; [self handleLoopChange]; // if reached the max loop count, stop animating, 0 means loop indefinitely NSUInteger maxLoopCount = self.totalLoopCount; if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) { [self stopPlaying]; return; } } } // Since we support handler, check animating state again if (!self.isPlaying) { return; } // Check if we should prefetch next frame or current frame // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame // Or, most cases, the decode speed is faster than render speed, we fetch next frame NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex; UIImage *fetchFrame; SD_LOCK(_lock); fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)]; SD_UNLOCK(_lock); if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) { // Prefetch next frame in background queue id animatedProvider = self.animatedProvider; @weakify(self); NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ @strongify(self); if (!self) { return; } UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex]; BOOL isAnimating = self.displayLink.isRunning; if (isAnimating) { SD_LOCK(self->_lock); self.frameBuffer[@(fetchFrameIndex)] = frame; SD_UNLOCK(self->_lock); } }]; [self.fetchQueue addOperation:operation]; } } - (void)handleFrameChange { if (self.animationFrameHandler) { self.animationFrameHandler(self.currentFrameIndex, self.currentFrame); } } - (void)handleLoopChange { if (self.animationLoopHandler) { self.animationLoopHandler(self.currentLoopCount); } } #pragma mark - Util - (void)calculateMaxBufferCount { NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage); if (bytes == 0) bytes = 1024; NSUInteger max = 0; if (self.maxBufferSize > 0) { max = self.maxBufferSize; } else { // Calculate based on current memory, these factors are by experience NSUInteger total = [SDDeviceHelper totalMemory]; NSUInteger free = [SDDeviceHelper freeMemory]; max = MIN(total * 0.2, free * 0.6); } NSUInteger maxBufferCount = (double)max / (double)bytes; if (!maxBufferCount) { // At least 1 frame maxBufferCount = 1; } self.maxBufferCount = maxBufferCount; } + (NSString *)defaultRunLoopMode { // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations. return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageRep.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_MAC /** A subclass of `NSBitmapImageRep` to fix that GIF duration issue because `NSBitmapImageRep` will reset `NSImageCurrentFrameDuration` by using `kCGImagePropertyGIFDelayTime` but not `kCGImagePropertyGIFUnclampedDelayTime`. This also fix the GIF loop count issue, which will use the Netscape standard (See http://www6.uniovi.es/gifanim/gifabout.htm) to only place once when the `kCGImagePropertyGIFLoopCount` is nil. This is what modern browser's behavior. Built in GIF coder use this instead of `NSBitmapImageRep` for better GIF rendering. If you do not want this, only enable `SDImageIOCoder`, which just call `NSImage` API and actually use `NSBitmapImageRep` for GIF image. This also support APNG format using `SDImageAPNGCoder`. Which provide full alpha-channel support and the correct duration match the `kCGImagePropertyAPNGUnclampedDelayTime`. */ @interface SDAnimatedImageRep : NSBitmapImageRep @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageRep.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImageRep.h" #if SD_MAC #import "SDImageIOAnimatedCoderInternal.h" #import "SDImageGIFCoder.h" #import "SDImageAPNGCoder.h" #import "SDImageHEICCoder.h" #import "SDImageAWebPCoder.h" @implementation SDAnimatedImageRep { CGImageSourceRef _imageSource; } - (void)dealloc { if (_imageSource) { CFRelease(_imageSource); _imageSource = NULL; } } // `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer + (instancetype)imageRepWithData:(NSData *)data { SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; return imageRep; } // We should override init method for `NSBitmapImageRep` to do initialize about animated image format #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" - (instancetype)initWithData:(NSData *)data { self = [super initWithData:data]; if (self) { CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) data, NULL); if (!imageSource) { return self; } _imageSource = imageSource; NSUInteger frameCount = CGImageSourceGetCount(imageSource); if (frameCount <= 1) { return self; } CFStringRef type = CGImageSourceGetType(imageSource); if (!type) { return self; } if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) { // GIF // Fix the `NSBitmapImageRep` GIF loop count calculation issue // Which will use 0 when there are no loop count information metadata in GIF data NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource]; [self setProperty:NSImageLoopCount withValue:@(loopCount)]; } else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) { // APNG // Do initialize about frame count, current frame/duration and loop count [self setProperty:NSImageFrameCount withValue:@(frameCount)]; [self setProperty:NSImageCurrentFrame withValue:@(0)]; NSUInteger loopCount = [SDImageAPNGCoder imageLoopCountWithSource:imageSource]; [self setProperty:NSImageLoopCount withValue:@(loopCount)]; } else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) { // HEIC // Do initialize about frame count, current frame/duration and loop count [self setProperty:NSImageFrameCount withValue:@(frameCount)]; [self setProperty:NSImageCurrentFrame withValue:@(0)]; NSUInteger loopCount = [SDImageHEICCoder imageLoopCountWithSource:imageSource]; [self setProperty:NSImageLoopCount withValue:@(loopCount)]; } else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) { // WebP // Do initialize about frame count, current frame/duration and loop count [self setProperty:NSImageFrameCount withValue:@(frameCount)]; [self setProperty:NSImageCurrentFrame withValue:@(0)]; NSUInteger loopCount = [SDImageAWebPCoder imageLoopCountWithSource:imageSource]; [self setProperty:NSImageLoopCount withValue:@(loopCount)]; } } return self; } // `NSBitmapImageRep` will use `kCGImagePropertyGIFDelayTime` whenever you call `setProperty:withValue:` with `NSImageCurrentFrame` to change the current frame. We override it and use the actual `kCGImagePropertyGIFUnclampedDelayTime` if need. - (void)setProperty:(NSBitmapImageRepPropertyKey)property withValue:(id)value { [super setProperty:property withValue:value]; if ([property isEqualToString:NSImageCurrentFrame]) { // Access the image source CGImageSourceRef imageSource = _imageSource; if (!imageSource) { return; } // Check format type CFStringRef type = CGImageSourceGetType(imageSource); if (!type) { return; } NSUInteger index = [value unsignedIntegerValue]; NSTimeInterval frameDuration = 0; if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) { // GIF frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource]; } else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) { // APNG frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource]; } else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) { // HEIC frameDuration = [SDImageHEICCoder frameDurationAtIndex:index source:imageSource]; } else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) { // WebP frameDuration = [SDImageAWebPCoder frameDurationAtIndex:index source:imageSource]; } if (!frameDuration) { return; } // Reset super frame duration with the actual frame duration [super setProperty:NSImageCurrentFrameDuration withValue:@(frameDuration)]; } } #pragma clang diagnostic pop @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageView+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImageView.h" #if SD_UIKIT || SD_MAC #import "SDWebImageManager.h" /** Integrates SDWebImage async downloading and caching of remote images with SDAnimatedImageView. */ @interface SDAnimatedImageView (WebCache) /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageView+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImageView+WebCache.h" #if SD_UIKIT || SD_MAC #import "UIView+WebCache.h" #import "SDAnimatedImage.h" @implementation SDAnimatedImageView (WebCache) - (void)sd_setImageWithURL:(nullable NSURL *)url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { Class animatedImageClass = [SDAnimatedImage class]; SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextAnimatedImageClass] = animatedImageClass; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:mutableContext setImageBlock:nil progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageView.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_UIKIT || SD_MAC #import "SDAnimatedImage.h" #import "SDAnimatedImagePlayer.h" /** A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering. Call `setImage:` with `UIImage(NSImage)` which conforms to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering For UIKit: use `-startAnimating`, `-stopAnimating` to control animating. `isAnimating` to check animation state. For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed. */ @interface SDAnimatedImageView : UIImageView /** The internal animation player. This property is only used for advanced usage, like inspecting/debugging animation status, control progressive loading, complicated animation frame index control, etc. @warning Pay attention if you directly update the player's property like `totalFrameCount`, `totalLoopCount`, the same property on `SDAnimatedImageView` may not get synced. */ @property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player; /** Current display frame image. This value is KVO Compliance. */ @property (nonatomic, strong, readonly, nullable) UIImage *currentFrame; /** Current frame index, zero based. This value is KVO Compliance. */ @property (nonatomic, assign, readonly) NSUInteger currentFrameIndex; /** Current loop count since its latest animating. This value is KVO Compliance. */ @property (nonatomic, assign, readonly) NSUInteger currentLoopCount; /** YES to choose `animationRepeatCount` property for animation loop count. No to use animated image's `animatedImageLoopCount` instead. Default is NO. */ @property (nonatomic, assign) BOOL shouldCustomLoopCount; /** Total loop count for animated image rendering. Default is animated image's loop count. If you need to set custom loop count, set `shouldCustomLoopCount` to YES and change this value. This class override UIImageView's `animationRepeatCount` property on iOS, use this property as well. */ @property (nonatomic, assign) NSInteger animationRepeatCount; /** The animation playback rate. Default is 1.0. `1.0` means the normal speed. `0.0` means stopping the animation. `0.0-1.0` means the slow speed. `> 1.0` means the fast speed. `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future) */ @property (nonatomic, assign) double playbackRate; /// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal. @property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode; /** Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0. `0` means automatically adjust by calculating current memory usage. `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU) `NSUIntegerMax` means cache all the buffer. (Lowest CPU and Highest Memory) */ @property (nonatomic, assign) NSUInteger maxBufferSize; /** Whehter or not to enable incremental image load for animated image. This is for the animated image which `sd_isIncremental` is YES (See `UIImage+Metadata.h`). If enable, animated image rendering will stop at the last frame available currently, and continue when another `setImage:` trigger, where the new animated image's `animatedImageData` should be updated from the previous one. If the `sd_isIncremental` is NO. The incremental image load stop. @note If you are confused about this description, open Chrome browser to view some large GIF images with low network speed to see the animation behavior. @note The best practice to use incremental load is using `initWithAnimatedCoder:scale:` in `SDAnimatedImage` with animated coder which conform to `SDProgressiveImageCoder` as well. Then call incremental update and incremental decode method to produce the image. Default is YES. Set to NO to only render the static poster for incremental animated image. */ @property (nonatomic, assign) BOOL shouldIncrementalLoad; /** Whether or not to clear the frame buffer cache when animation stopped. See `maxBufferSize` This is useful when you want to limit the memory usage during frequently visibility changes (such as image view inside a list view, then push and pop) Default is NO. */ @property (nonatomic, assign) BOOL clearBufferWhenStopped; /** Whether or not to reset the current frame index when animation stopped. For some of use case, you may want to reset the frame index to 0 when stop, but some other want to keep the current frame index. Default is NO. */ @property (nonatomic, assign) BOOL resetFrameIndexWhenStopped; /** If the image which conforms to `SDAnimatedImage` protocol has more than one frame, set this value to `YES` will automatically play/stop the animation when the view become visible/invisible. Default is YES. */ @property (nonatomic, assign) BOOL autoPlayAnimatedImage; /** You can specify a runloop mode to let it rendering. Default is NSRunLoopCommonModes on multi-core device, NSDefaultRunLoopMode on single-core device @note This is useful for some cases, for example, always specify NSDefaultRunLoopMode, if you want to pause the animation when user scroll (for Mac user, drag the mouse or touchpad) */ @property (nonatomic, copy, nonnull) NSRunLoopMode runLoopMode; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDAnimatedImageView.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAnimatedImageView.h" #if SD_UIKIT || SD_MAC #import "UIImage+Metadata.h" #import "NSImage+Compatibility.h" #import "SDInternalMacros.h" #import "objc/runtime.h" @interface UIImageView () @end @interface SDAnimatedImageView () { BOOL _initFinished; // Extra flag to mark the `commonInit` is called NSRunLoopMode _runLoopMode; NSUInteger _maxBufferSize; double _playbackRate; SDAnimatedImagePlaybackMode _playbackMode; } @property (nonatomic, strong, readwrite) SDAnimatedImagePlayer *player; @property (nonatomic, strong, readwrite) UIImage *currentFrame; @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex; @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount; @property (nonatomic, assign) BOOL shouldAnimate; @property (nonatomic, assign) BOOL isProgressive; @property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer. @end @implementation SDAnimatedImageView #if SD_UIKIT @dynamic animationRepeatCount; // we re-use this property from `UIImageView` super class on iOS. #endif #pragma mark - Initializers #if SD_MAC + (instancetype)imageViewWithImage:(NSImage *)image { NSRect frame = NSMakeRect(0, 0, image.size.width, image.size.height); SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] initWithFrame:frame]; [imageView setImage:image]; return imageView; } #else // -initWithImage: isn't documented as a designated initializer of UIImageView, but it actually seems to be. // Using -initWithImage: doesn't call any of the other designated initializers. - (instancetype)initWithImage:(UIImage *)image { self = [super initWithImage:image]; if (self) { [self commonInit]; } return self; } // -initWithImage:highlightedImage: also isn't documented as a designated initializer of UIImageView, but it doesn't call any other designated initializers. - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { self = [super initWithImage:image highlightedImage:highlightedImage]; if (self) { [self commonInit]; } return self; } #endif - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } - (void)commonInit { // Pay attention that UIKit's `initWithImage:` will trigger a `setImage:` during initialization before this `commonInit`. // So the properties which rely on this order, should using lazy-evaluation or do extra check in `setImage:`. self.autoPlayAnimatedImage = YES; self.shouldCustomLoopCount = NO; self.shouldIncrementalLoad = YES; self.playbackRate = 1.0; #if SD_MAC self.wantsLayer = YES; #endif // Mark commonInit finished _initFinished = YES; } #pragma mark - Accessors #pragma mark Public - (void)setImage:(UIImage *)image { if (self.image == image) { return; } // Check Progressive rendering [self updateIsProgressiveWithImage:image]; if (!self.isProgressive) { // Stop animating self.player = nil; self.currentFrame = nil; self.currentFrameIndex = 0; self.currentLoopCount = 0; } // We need call super method to keep function. This will impliedly call `setNeedsDisplay`. But we have no way to avoid this when using animated image. So we call `setNeedsDisplay` again at the end. super.image = image; if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) { if (!self.player) { id provider; // Check progressive loading if (self.isProgressive) { provider = [self progressiveAnimatedCoderForImage:image]; } else { provider = (id)image; } // Create animated player self.player = [SDAnimatedImagePlayer playerWithProvider:provider]; } else { // Update Frame Count self.player.totalFrameCount = [(id)image animatedImageFrameCount]; } if (!self.player) { // animated player nil means the image format is not supported, or frame count <= 1 return; } // Custom Loop Count if (self.shouldCustomLoopCount) { self.player.totalLoopCount = self.animationRepeatCount; } // RunLoop Mode self.player.runLoopMode = self.runLoopMode; // Max Buffer Size self.player.maxBufferSize = self.maxBufferSize; // Play Rate self.player.playbackRate = self.playbackRate; // Play Mode self.player.playbackMode = self.playbackMode; // Setup handler @weakify(self); self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) { @strongify(self); self.currentFrameIndex = index; self.currentFrame = frame; [self.imageViewLayer setNeedsDisplay]; }; self.player.animationLoopHandler = ^(NSUInteger loopCount) { @strongify(self); // Progressive image reach the current last frame index. Keep the state and pause animating. Wait for later restart if (self.isProgressive) { NSUInteger lastFrameIndex = self.player.totalFrameCount - 1; [self.player seekToFrameAtIndex:lastFrameIndex loopCount:0]; [self.player pausePlaying]; } else { self.currentLoopCount = loopCount; } }; // Ensure disabled highlighting; it's not supported (see `-setHighlighted:`). super.highlighted = NO; [self stopAnimating]; [self checkPlay]; [self.imageViewLayer setNeedsDisplay]; } } #pragma mark - Configuration - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode { _runLoopMode = [runLoopMode copy]; self.player.runLoopMode = runLoopMode; } - (NSRunLoopMode)runLoopMode { if (!_runLoopMode) { _runLoopMode = [[self class] defaultRunLoopMode]; } return _runLoopMode; } + (NSString *)defaultRunLoopMode { // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations. return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode; } - (void)setMaxBufferSize:(NSUInteger)maxBufferSize { _maxBufferSize = maxBufferSize; self.player.maxBufferSize = maxBufferSize; } - (NSUInteger)maxBufferSize { return _maxBufferSize; // Defaults to 0 } - (void)setPlaybackRate:(double)playbackRate { _playbackRate = playbackRate; self.player.playbackRate = playbackRate; } - (double)playbackRate { if (!_initFinished) { return 1.0; // Defaults to 1.0 } return _playbackRate; } - (void)setPlaybackMode:(SDAnimatedImagePlaybackMode)playbackMode { _playbackMode = playbackMode; self.player.playbackMode = playbackMode; } - (SDAnimatedImagePlaybackMode)playbackMode { if (!_initFinished) { return SDAnimatedImagePlaybackModeNormal; // Default mode is normal } return _playbackMode; } - (BOOL)shouldIncrementalLoad { if (!_initFinished) { return YES; // Defaults to YES } return _initFinished; } #pragma mark - UIView Method Overrides #pragma mark Observing View-Related Changes #if SD_MAC - (void)viewDidMoveToSuperview #else - (void)didMoveToSuperview #endif { #if SD_MAC [super viewDidMoveToSuperview]; #else [super didMoveToSuperview]; #endif [self checkPlay]; } #if SD_MAC - (void)viewDidMoveToWindow #else - (void)didMoveToWindow #endif { #if SD_MAC [super viewDidMoveToWindow]; #else [super didMoveToWindow]; #endif [self checkPlay]; } #if SD_MAC - (void)setAlphaValue:(CGFloat)alphaValue #else - (void)setAlpha:(CGFloat)alpha #endif { #if SD_MAC [super setAlphaValue:alphaValue]; #else [super setAlpha:alpha]; #endif [self checkPlay]; } - (void)setHidden:(BOOL)hidden { [super setHidden:hidden]; [self checkPlay]; } #pragma mark - UIImageView Method Overrides #pragma mark Image Data - (void)setAnimationRepeatCount:(NSInteger)animationRepeatCount { #if SD_UIKIT [super setAnimationRepeatCount:animationRepeatCount]; #else _animationRepeatCount = animationRepeatCount; #endif if (self.shouldCustomLoopCount) { self.player.totalLoopCount = animationRepeatCount; } } - (void)startAnimating { if (self.player) { [self updateShouldAnimate]; if (self.shouldAnimate) { [self.player startPlaying]; } } else { #if SD_UIKIT [super startAnimating]; #else [super setAnimates:YES]; #endif } } - (void)stopAnimating { if (self.player) { if (self.resetFrameIndexWhenStopped) { [self.player stopPlaying]; } else { [self.player pausePlaying]; } if (self.clearBufferWhenStopped) { [self.player clearFrameBuffer]; } } else { #if SD_UIKIT [super stopAnimating]; #else [super setAnimates:NO]; #endif } } #if SD_UIKIT - (BOOL)isAnimating { if (self.player) { return self.player.isPlaying; } else { return [super isAnimating]; } } #endif #if SD_MAC - (BOOL)animates { if (self.player) { return self.player.isPlaying; } else { return [super animates]; } } - (void)setAnimates:(BOOL)animates { if (animates) { [self startAnimating]; } else { [self stopAnimating]; } } #endif #pragma mark Highlighted Image Unsupport - (void)setHighlighted:(BOOL)highlighted { // Highlighted image is unsupported for animated images, but implementing it breaks the image view when embedded in a UICollectionViewCell. if (!self.player) { [super setHighlighted:highlighted]; } } #pragma mark - Private Methods #pragma mark Animation /// Check if it should be played - (void)checkPlay { // Only handle for SDAnimatedImage, leave UIAnimatedImage or animationImages for super implementation control if (self.player && self.autoPlayAnimatedImage) { [self updateShouldAnimate]; if (self.shouldAnimate) { [self startAnimating]; } else { [self stopAnimating]; } } } // Don't repeatedly check our window & superview in `-displayDidRefresh:` for performance reasons. // Just update our cached value whenever the animated image or visibility (window, superview, hidden, alpha) is changed. - (void)updateShouldAnimate { #if SD_MAC BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alphaValue > 0.0; #else BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0; #endif self.shouldAnimate = self.player && isVisible; } // Update progressive status only after `setImage:` call. - (void)updateIsProgressiveWithImage:(UIImage *)image { self.isProgressive = NO; if (!self.shouldIncrementalLoad) { // Early return return; } // We must use `image.class conformsToProtocol:` instead of `image conformsToProtocol:` here // Because UIKit on macOS, using internal hard-coded override method, which returns NO id currentAnimatedCoder = [self progressiveAnimatedCoderForImage:image]; if (currentAnimatedCoder) { UIImage *previousImage = self.image; if (!previousImage) { // If current animated coder supports progressive, and no previous image to check, start progressive loading self.isProgressive = YES; } else { id previousAnimatedCoder = [self progressiveAnimatedCoderForImage:previousImage]; if (previousAnimatedCoder == currentAnimatedCoder) { // If current animated coder is the same as previous, start progressive loading self.isProgressive = YES; } } } } // Check if image can represent a `Progressive Animated Image` during loading - (id)progressiveAnimatedCoderForImage:(UIImage *)image { if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental && [image respondsToSelector:@selector(animatedCoder)]) { id animatedCoder = [(id)image animatedCoder]; if ([animatedCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { return (id)animatedCoder; } } return nil; } #pragma mark Providing the Layer's Content #pragma mark - CALayerDelegate - (void)displayLayer:(CALayer *)layer { UIImage *currentFrame = self.currentFrame; if (currentFrame) { layer.contentsScale = currentFrame.scale; layer.contents = (__bridge id)currentFrame.CGImage; } else { // If we have no animation frames, call super implementation. iOS 14+ UIImageView use this delegate method for rendering. if ([UIImageView instancesRespondToSelector:@selector(displayLayer:)]) { [super displayLayer:layer]; } } } #if SD_MAC // NSImageView use a subview. We need this subview's layer for actual rendering. // Why using this design may because of properties like `imageAlignment` and `imageScaling`, which it's not available for UIImageView.contentMode (it's impossible to align left and keep aspect ratio at the same time) - (NSView *)imageView { NSImageView *imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageView)); if (!imageView) { // macOS 10.14 imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageSubview)); } return imageView; } // on macOS, it's the imageView subview's layer (we use layer-hosting view to let CALayerDelegate works) - (CALayer *)imageViewLayer { NSView *imageView = self.imageView; if (!imageView) { return nil; } if (!_imageViewLayer) { _imageViewLayer = [CALayer new]; _imageViewLayer.delegate = self; imageView.layer = _imageViewLayer; imageView.wantsLayer = YES; } return _imageViewLayer; } #else // on iOS, it's the imageView itself's layer - (CALayer *)imageViewLayer { return self.layer; } #endif @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDDiskCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" @class SDImageCacheConfig; /** A protocol to allow custom disk cache used in SDImageCache. */ @protocol SDDiskCache // All of these method are called from the same global queue to avoid blocking on main queue and thread-safe problem. But it's also recommend to ensure thread-safe yourself using lock or other ways. @required /** Create a new disk cache based on the specified path. You can check `maxDiskSize` and `maxDiskAge` used for disk cache. @param cachePath Full path of a directory in which the cache will write data. Once initialized you should not read and write to this directory. @param config The cache config to be used to create the cache. @return A new cache object, or nil if an error occurs. */ - (nullable instancetype)initWithCachePath:(nonnull NSString *)cachePath config:(nonnull SDImageCacheConfig *)config; /** Returns a boolean value that indicates whether a given key is in cache. This method may blocks the calling thread until file read finished. @param key A string identifying the data. If nil, just return NO. @return Whether the key is in cache. */ - (BOOL)containsDataForKey:(nonnull NSString *)key; /** Returns the data associated with a given key. This method may blocks the calling thread until file read finished. @param key A string identifying the data. If nil, just return nil. @return The value associated with key, or nil if no value is associated with key. */ - (nullable NSData *)dataForKey:(nonnull NSString *)key; /** Sets the value of the specified key in the cache. This method may blocks the calling thread until file write finished. @param data The data to be stored in the cache. @param key The key with which to associate the value. If nil, this method has no effect. */ - (void)setData:(nullable NSData *)data forKey:(nonnull NSString *)key; /** Returns the extended data associated with a given key. This method may blocks the calling thread until file read finished. @param key A string identifying the data. If nil, just return nil. @return The value associated with key, or nil if no value is associated with key. */ - (nullable NSData *)extendedDataForKey:(nonnull NSString *)key; /** Set extended data with a given key. @discussion You can set any extended data to exist cache key. Without override the exist disk file data. on UNIX, the common way for this is to use the Extended file attributes (xattr) @param extendedData The extended data (pass nil to remove). @param key The key with which to associate the value. If nil, this method has no effect. */ - (void)setExtendedData:(nullable NSData *)extendedData forKey:(nonnull NSString *)key; /** Removes the value of the specified key in the cache. This method may blocks the calling thread until file delete finished. @param key The key identifying the value to be removed. If nil, this method has no effect. */ - (void)removeDataForKey:(nonnull NSString *)key; /** Empties the cache. This method may blocks the calling thread until file delete finished. */ - (void)removeAllData; /** Removes the expired data from the cache. You can choose the data to remove base on `ageLimit`, `countLimit` and `sizeLimit` options. */ - (void)removeExpiredData; /** The cache path for key @param key A string identifying the value @return The cache path for key. Or nil if the key can not associate to a path */ - (nullable NSString *)cachePathForKey:(nonnull NSString *)key; /** Returns the number of data in this cache. This method may blocks the calling thread until file read finished. @return The total data count. */ - (NSUInteger)totalCount; /** Returns the total size (in bytes) of data in this cache. This method may blocks the calling thread until file read finished. @return The total data size in bytes. */ - (NSUInteger)totalSize; @end /** The built-in disk cache. */ @interface SDDiskCache : NSObject /** Cache Config object - storing all kind of settings. */ @property (nonatomic, strong, readonly, nonnull) SDImageCacheConfig *config; - (nonnull instancetype)init NS_UNAVAILABLE; /** Move the cache directory from old location to new location, the old location will be removed after finish. If the old location does not exist, does nothing. If the new location does not exist, only do a movement of directory. If the new location does exist, will move and merge the files from old location. If the new location does exist, but is not a directory, will remove it and do a movement of directory. @param srcPath old location of cache directory @param dstPath new location of cache directory */ - (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDDiskCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDDiskCache.h" #import "SDImageCacheConfig.h" #import "SDFileAttributeHelper.h" #import static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDiskCache"; @interface SDDiskCache () @property (nonatomic, copy) NSString *diskCachePath; @property (nonatomic, strong, nonnull) NSFileManager *fileManager; @end @implementation SDDiskCache - (instancetype)init { NSAssert(NO, @"Use `initWithCachePath:` with the disk cache path"); return nil; } #pragma mark - SDcachePathForKeyDiskCache Protocol - (instancetype)initWithCachePath:(NSString *)cachePath config:(nonnull SDImageCacheConfig *)config { if (self = [super init]) { _diskCachePath = cachePath; _config = config; [self commonInit]; } return self; } - (void)commonInit { if (self.config.fileManager) { self.fileManager = self.config.fileManager; } else { self.fileManager = [NSFileManager new]; } } - (BOOL)containsDataForKey:(NSString *)key { NSParameterAssert(key); NSString *filePath = [self cachePathForKey:key]; BOOL exists = [self.fileManager fileExistsAtPath:filePath]; // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension if (!exists) { exists = [self.fileManager fileExistsAtPath:filePath.stringByDeletingPathExtension]; } return exists; } - (NSData *)dataForKey:(NSString *)key { NSParameterAssert(key); NSString *filePath = [self cachePathForKey:key]; NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil]; if (data) { return data; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil]; if (data) { return data; } return nil; } - (void)setData:(NSData *)data forKey:(NSString *)key { NSParameterAssert(data); NSParameterAssert(key); if (![self.fileManager fileExistsAtPath:self.diskCachePath]) { [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // get cache Path for image key NSString *cachePathForKey = [self cachePathForKey:key]; // transform to NSURL NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; // disable iCloud backup if (self.config.shouldDisableiCloud) { // ignore iCloud backup resource value error [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; } } - (NSData *)extendedDataForKey:(NSString *)key { NSParameterAssert(key); // get cache Path for image key NSString *cachePathForKey = [self cachePathForKey:key]; NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil]; return extendedData; } - (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key { NSParameterAssert(key); // get cache Path for image key NSString *cachePathForKey = [self cachePathForKey:key]; if (!extendedData) { // Remove [SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil]; } else { // Override [SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil]; } } - (void)removeDataForKey:(NSString *)key { NSParameterAssert(key); NSString *filePath = [self cachePathForKey:key]; [self.fileManager removeItemAtPath:filePath error:nil]; } - (void)removeAllData { [self.fileManager removeItemAtPath:self.diskCachePath error:nil]; [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } - (void)removeExpiredData { NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; // Compute content date key to be used for tests NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey; switch (self.config.diskCacheExpireType) { case SDImageCacheConfigExpireTypeAccessDate: cacheContentDateKey = NSURLContentAccessDateKey; break; case SDImageCacheConfigExpireTypeModificationDate: cacheContentDateKey = NSURLContentModificationDateKey; break; case SDImageCacheConfigExpireTypeCreationDate: cacheContentDateKey = NSURLCreationDateKey; break; case SDImageCacheConfigExpireTypeChangeDate: cacheContentDateKey = NSURLAttributeModificationDateKey; break; default: break; } NSArray *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey]; // This enumerator prefetches useful properties for our cache files. NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge]; NSMutableDictionary *> *cacheFiles = [NSMutableDictionary dictionary]; NSUInteger currentCacheSize = 0; // Enumerate all of the files in the cache directory. This loop has two purposes: // // 1. Removing files that are older than the expiration date. // 2. Storing file attributes for the size-based cleanup pass. NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSError *error; NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error]; // Skip directories and errors. if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // Remove files that are older than the expiration date; NSDate *modifiedDate = resourceValues[cacheContentDateKey]; if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // Store a reference to this file and account for its total size. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += totalAllocatedSize.unsignedIntegerValue; cacheFiles[fileURL] = resourceValues; } for (NSURL *fileURL in urlsToDelete) { [self.fileManager removeItemAtURL:fileURL error:nil]; } // If our remaining disk cache exceeds a configured maximum size, perform a second // size-based cleanup pass. We delete the oldest files first. NSUInteger maxDiskSize = self.config.maxDiskSize; if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) { // Target half of our maximum cache size for this cleanup pass. const NSUInteger desiredCacheSize = maxDiskSize / 2; // Sort the remaining cache files by their last modification time or last access time (oldest first). NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]]; }]; // Delete files until we fall below our desired cache size. for (NSURL *fileURL in sortedFiles) { if ([self.fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= totalAllocatedSize.unsignedIntegerValue; if (currentCacheSize < desiredCacheSize) { break; } } } } } - (nullable NSString *)cachePathForKey:(NSString *)key { NSParameterAssert(key); return [self cachePathForKey:key inPath:self.diskCachePath]; } - (NSUInteger)totalSize { NSUInteger size = 0; NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath]; for (NSString *fileName in fileEnumerator) { NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName]; NSDictionary *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil]; size += [attrs fileSize]; } return size; } - (NSUInteger)totalCount { NSUInteger count = 0; NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath]; count = fileEnumerator.allObjects.count; return count; } #pragma mark - Cache paths - (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path { NSString *filename = SDDiskCacheFileNameForKey(key); return [path stringByAppendingPathComponent:filename]; } - (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath { NSParameterAssert(srcPath); NSParameterAssert(dstPath); // Check if old path is equal to new path if ([srcPath isEqualToString:dstPath]) { return; } BOOL isDirectory; // Check if old path is directory if (![self.fileManager fileExistsAtPath:srcPath isDirectory:&isDirectory] || !isDirectory) { return; } // Check if new path is directory if (![self.fileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] || !isDirectory) { if (!isDirectory) { // New path is not directory, remove file [self.fileManager removeItemAtPath:dstPath error:nil]; } NSString *dstParentPath = [dstPath stringByDeletingLastPathComponent]; // Creates any non-existent parent directories as part of creating the directory in path if (![self.fileManager fileExistsAtPath:dstParentPath]) { [self.fileManager createDirectoryAtPath:dstParentPath withIntermediateDirectories:YES attributes:nil error:NULL]; } // New directory does not exist, rename directory [self.fileManager moveItemAtPath:srcPath toPath:dstPath error:nil]; } else { // New directory exist, merge the files NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtPath:srcPath]; NSString *file; while ((file = [dirEnumerator nextObject])) { [self.fileManager moveItemAtPath:[srcPath stringByAppendingPathComponent:file] toPath:[dstPath stringByAppendingPathComponent:file] error:nil]; } // Remove the old path [self.fileManager removeItemAtPath:srcPath error:nil]; } } #pragma mark - Hash #define SD_MAX_FILE_EXTENSION_LENGTH (NAME_MAX - CC_MD5_DIGEST_LENGTH * 2 - 1) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" static inline NSString * _Nonnull SDDiskCacheFileNameForKey(NSString * _Nullable key) { const char *str = key.UTF8String; if (str == NULL) { str = ""; } unsigned char r[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), r); NSURL *keyURL = [NSURL URLWithString:key]; NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension; // File system has file name length limit, we need to check if ext is too long, we don't add it to the filename if (ext.length > SD_MAX_FILE_EXTENSION_LENGTH) { ext = nil; } NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]]; return filename; } #pragma clang diagnostic pop @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDGraphicsImageRenderer.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" /** These following class are provided to use `UIGraphicsImageRenderer` with polyfill, which allows write cross-platform(AppKit/UIKit) code and avoid runtime version check. Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage. Which means, if you draw CGImage/CIImage which contains grayscale only, the underlaying bitmap context use grayscale, it's managed by system and not a fixed type. (actually, the `kCGContextTypeAutomatic`) For usage, See more in Apple's documentation: https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer For UIKit on iOS/tvOS 10+, these method just use the same `UIGraphicsImageRenderer` API. For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior (but without dynamic bitmap support) */ typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context); typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) { SDGraphicsImageRendererFormatRangeUnspecified = -1, SDGraphicsImageRendererFormatRangeAutomatic = 0, SDGraphicsImageRendererFormatRangeExtended, SDGraphicsImageRendererFormatRangeStandard }; /// A set of drawing attributes that represent the configuration of an image renderer context. @interface SDGraphicsImageRendererFormat : NSObject /// The display scale of the image renderer context. /// The default value is equal to the scale of the main screen. @property (nonatomic) CGFloat scale; /// A Boolean value indicating whether the underlying Core Graphics context has an alpha channel. /// The default value is NO. @property (nonatomic) BOOL opaque; /// Specifying whether the bitmap context should use extended color. /// For iOS 12+, the value is from system `preferredRange` property /// For iOS 10-11, the value is from system `prefersExtendedRange` property /// For iOS 9-, the value is `.standard` @property (nonatomic) SDGraphicsImageRendererFormatRange preferredRange; /// Init the default format. See each properties's default value. - (nonnull instancetype)init; /// Returns a new format best suited for the main screen’s current configuration. + (nonnull instancetype)preferredFormat; @end /// A graphics renderer for creating Core Graphics-backed images. @interface SDGraphicsImageRenderer : NSObject /// Creates an image renderer for drawing images of a given size. /// @param size The size of images output from the renderer, specified in points. /// @return An initialized image renderer. - (nonnull instancetype)initWithSize:(CGSize)size; /// Creates a new image renderer with a given size and format. /// @param size The size of images output from the renderer, specified in points. /// @param format A SDGraphicsImageRendererFormat object that encapsulates the format used to create the renderer context. /// @return An initialized image renderer. - (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull SDGraphicsImageRendererFormat *)format; /// Creates an image by following a set of drawing instructions. /// @param actions A SDGraphicsImageDrawingActions block that, when invoked by the renderer, executes a set of drawing instructions to create the output image. /// @note You should not retain or use the context outside the block, it's non-escaping. /// @return A UIImage object created by the supplied drawing actions. - (nonnull UIImage *)imageWithActions:(nonnull NS_NOESCAPE SDGraphicsImageDrawingActions)actions; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDGraphicsImageRenderer.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDGraphicsImageRenderer.h" #import "SDImageGraphics.h" @interface SDGraphicsImageRendererFormat () #if SD_UIKIT @property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0)); #endif @end @implementation SDGraphicsImageRendererFormat @synthesize scale = _scale; @synthesize opaque = _opaque; @synthesize preferredRange = _preferredRange; #pragma mark - Property - (CGFloat)scale { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { return self.uiformat.scale; } else { return _scale; } #else return _scale; #endif } - (void)setScale:(CGFloat)scale { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { self.uiformat.scale = scale; } else { _scale = scale; } #else _scale = scale; #endif } - (BOOL)opaque { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { return self.uiformat.opaque; } else { return _opaque; } #else return _opaque; #endif } - (void)setOpaque:(BOOL)opaque { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { self.uiformat.opaque = opaque; } else { _opaque = opaque; } #else _opaque = opaque; #endif } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (SDGraphicsImageRendererFormatRange)preferredRange { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { if (@available(iOS 12.0, tvOS 12.0, *)) { return (SDGraphicsImageRendererFormatRange)self.uiformat.preferredRange; } else { BOOL prefersExtendedRange = self.uiformat.prefersExtendedRange; if (prefersExtendedRange) { return SDGraphicsImageRendererFormatRangeExtended; } else { return SDGraphicsImageRendererFormatRangeStandard; } } } else { return _preferredRange; } #else return _preferredRange; #endif } - (void)setPreferredRange:(SDGraphicsImageRendererFormatRange)preferredRange { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { if (@available(iOS 12.0, tvOS 12.0, *)) { self.uiformat.preferredRange = (UIGraphicsImageRendererFormatRange)preferredRange; } else { switch (preferredRange) { case SDGraphicsImageRendererFormatRangeExtended: self.uiformat.prefersExtendedRange = YES; break; case SDGraphicsImageRendererFormatRangeStandard: self.uiformat.prefersExtendedRange = NO; default: // Automatic means default break; } } } else { _preferredRange = preferredRange; } #else _preferredRange = preferredRange; #endif } #pragma clang diagnostic pop - (instancetype)init { self = [super init]; if (self) { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.10, *)) { UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init]; self.uiformat = uiformat; } else { #endif #if SD_WATCH CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; #elif SD_UIKIT CGFloat screenScale = [UIScreen mainScreen].scale; #elif SD_MAC CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; #endif self.scale = screenScale; self.opaque = NO; self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; #if SD_UIKIT } #endif } return self; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" - (instancetype)initForMainScreen { self = [super init]; if (self) { #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageRendererFormat *uiformat; // iOS 11.0.0 GM does have `preferredFormat`, but iOS 11 betas did not (argh!) if ([UIGraphicsImageRenderer respondsToSelector:@selector(preferredFormat)]) { uiformat = [UIGraphicsImageRendererFormat preferredFormat]; } else { uiformat = [UIGraphicsImageRendererFormat defaultFormat]; } self.uiformat = uiformat; } else { #endif #if SD_WATCH CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; #elif SD_UIKIT CGFloat screenScale = [UIScreen mainScreen].scale; #elif SD_MAC CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; #endif self.scale = screenScale; self.opaque = NO; self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; #if SD_UIKIT } #endif } return self; } #pragma clang diagnostic pop + (instancetype)preferredFormat { SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen]; return format; } @end @interface SDGraphicsImageRenderer () @property (nonatomic, assign) CGSize size; @property (nonatomic, strong) SDGraphicsImageRendererFormat *format; #if SD_UIKIT @property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0), tvos(10.0)); #endif @end @implementation SDGraphicsImageRenderer - (instancetype)initWithSize:(CGSize)size { return [self initWithSize:size format:SDGraphicsImageRendererFormat.preferredFormat]; } - (instancetype)initWithSize:(CGSize)size format:(SDGraphicsImageRendererFormat *)format { NSParameterAssert(format); self = [super init]; if (self) { self.size = size; self.format = format; #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageRendererFormat *uiformat = format.uiformat; self.uirenderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:uiformat]; } #endif } return self; } - (UIImage *)imageWithActions:(NS_NOESCAPE SDGraphicsImageDrawingActions)actions { NSParameterAssert(actions); #if SD_UIKIT if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) { if (actions) { actions(rendererContext.CGContext); } }; return [self.uirenderer imageWithActions:uiactions]; } else { #endif SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale); CGContextRef context = SDGraphicsGetCurrentContext(); if (actions) { actions(context); } UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); SDGraphicsEndImageContext(); return image; #if SD_UIKIT } #endif } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageAPNGCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageIOAnimatedCoder.h" /** Built in coder using ImageIO that supports APNG encoding/decoding */ @interface SDImageAPNGCoder : SDImageIOAnimatedCoder @property (nonatomic, class, readonly, nonnull) SDImageAPNGCoder *sharedCoder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageAPNGCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageAPNGCoder.h" #if SD_MAC #import #else #import #endif @implementation SDImageAPNGCoder + (instancetype)sharedCoder { static SDImageAPNGCoder *coder; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coder = [[SDImageAPNGCoder alloc] init]; }); return coder; } #pragma mark - Subclass Override + (SDImageFormat)imageFormat { return SDImageFormatPNG; } + (NSString *)imageUTType { return (__bridge NSString *)kUTTypePNG; } + (NSString *)dictionaryProperty { return (__bridge NSString *)kCGImagePropertyPNGDictionary; } + (NSString *)unclampedDelayTimeProperty { return (__bridge NSString *)kCGImagePropertyAPNGUnclampedDelayTime; } + (NSString *)delayTimeProperty { return (__bridge NSString *)kCGImagePropertyAPNGDelayTime; } + (NSString *)loopCountProperty { return (__bridge NSString *)kCGImagePropertyAPNGLoopCount; } + (NSUInteger)defaultLoopCount { return 0; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageAWebPCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageIOAnimatedCoder.h" /** This coder is used for Google WebP and Animated WebP(AWebP) image format. Image/IO provide the WebP decoding support in iOS 14/macOS 11/tvOS 14/watchOS 7+. @note Currently Image/IO seems does not supports WebP encoding, if you need WebP encoding, use the custom codec below. @note If you need to support lower firmware version for WebP, you can have a try at https://github.com/SDWebImage/SDWebImageWebPCoder */ API_AVAILABLE(ios(14.0), tvos(14.0), macos(11.0), watchos(7.0)) @interface SDImageAWebPCoder : SDImageIOAnimatedCoder @property (nonatomic, class, readonly, nonnull) SDImageAWebPCoder *sharedCoder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageAWebPCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageAWebPCoder.h" #import "SDImageIOAnimatedCoderInternal.h" // These constants are available from iOS 14+ and Xcode 12. This raw value is used for toolchain and firmware compatibility static NSString * kSDCGImagePropertyWebPDictionary = @"{WebP}"; static NSString * kSDCGImagePropertyWebPLoopCount = @"LoopCount"; static NSString * kSDCGImagePropertyWebPDelayTime = @"DelayTime"; static NSString * kSDCGImagePropertyWebPUnclampedDelayTime = @"UnclampedDelayTime"; @implementation SDImageAWebPCoder + (void)initialize { #if __IPHONE_14_0 || __TVOS_14_0 || __MAC_11_0 || __WATCHOS_7_0 // Xcode 12 if (@available(iOS 14, tvOS 14, macOS 11, watchOS 7, *)) { // Use SDK instead of raw value kSDCGImagePropertyWebPDictionary = (__bridge NSString *)kCGImagePropertyWebPDictionary; kSDCGImagePropertyWebPLoopCount = (__bridge NSString *)kCGImagePropertyWebPLoopCount; kSDCGImagePropertyWebPDelayTime = (__bridge NSString *)kCGImagePropertyWebPDelayTime; kSDCGImagePropertyWebPUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyWebPUnclampedDelayTime; } #endif } + (instancetype)sharedCoder { static SDImageAWebPCoder *coder; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coder = [[SDImageAWebPCoder alloc] init]; }); return coder; } #pragma mark - SDImageCoder - (BOOL)canDecodeFromData:(nullable NSData *)data { switch ([NSData sd_imageFormatForImageData:data]) { case SDImageFormatWebP: // Check WebP decoding compatibility return [self.class canDecodeFromFormat:SDImageFormatWebP]; default: return NO; } } - (BOOL)canIncrementalDecodeFromData:(NSData *)data { return [self canDecodeFromData:data]; } - (BOOL)canEncodeToFormat:(SDImageFormat)format { switch (format) { case SDImageFormatWebP: // Check WebP encoding compatibility return [self.class canEncodeToFormat:SDImageFormatWebP]; default: return NO; } } #pragma mark - Subclass Override + (SDImageFormat)imageFormat { return SDImageFormatWebP; } + (NSString *)imageUTType { return (__bridge NSString *)kSDUTTypeWebP; } + (NSString *)dictionaryProperty { return kSDCGImagePropertyWebPDictionary; } + (NSString *)unclampedDelayTimeProperty { return kSDCGImagePropertyWebPUnclampedDelayTime; } + (NSString *)delayTimeProperty { return kSDCGImagePropertyWebPDelayTime; } + (NSString *)loopCountProperty { return kSDCGImagePropertyWebPLoopCount; } + (NSUInteger)defaultLoopCount { return 0; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDImageCacheConfig.h" #import "SDImageCacheDefine.h" #import "SDMemoryCache.h" #import "SDDiskCache.h" /// Image Cache Options typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { /** * By default, we do not query image data when the image is already cached in memory. This mask can force to query image data at the same time. However, this query is asynchronously unless you specify `SDImageCacheQueryMemoryDataSync` */ SDImageCacheQueryMemoryData = 1 << 0, /** * By default, when you only specify `SDImageCacheQueryMemoryData`, we query the memory image data asynchronously. Combined this mask as well to query the memory image data synchronously. */ SDImageCacheQueryMemoryDataSync = 1 << 1, /** * By default, when the memory cache miss, we query the disk cache asynchronously. This mask can force to query disk cache (when memory cache miss) synchronously. @note These 3 query options can be combined together. For the full list about these masks combination, see wiki page. */ SDImageCacheQueryDiskDataSync = 1 << 2, /** * By default, images are decoded respecting their original size. On iOS, this flag will scale down the * images to a size compatible with the constrained memory of devices. */ SDImageCacheScaleDownLargeImages = 1 << 3, /** * By default, we will decode the image in the background during cache query and download from the network. This can help to improve performance because when rendering image on the screen, it need to be firstly decoded. But this happen on the main queue by Core Animation. * However, this process may increase the memory usage as well. If you are experiencing a issue due to excessive memory consumption, This flag can prevent decode the image. */ SDImageCacheAvoidDecodeImage = 1 << 4, /** * By default, we decode the animated image. This flag can force decode the first frame only and produce the static image. */ SDImageCacheDecodeFirstFrameOnly = 1 << 5, /** * By default, for `SDAnimatedImage`, we decode the animated image frame during rendering to reduce memory usage. This flag actually trigger `preloadAllAnimatedImageFrames = YES` after image load from disk cache */ SDImageCachePreloadAllFrames = 1 << 6, /** * By default, when you use `SDWebImageContextAnimatedImageClass` context option (like using `SDAnimatedImageView` which designed to use `SDAnimatedImage`), we may still use `UIImage` when the memory cache hit, or image decoder is not available, to behave as a fallback solution. * Using this option, can ensure we always produce image with your provided class. If failed, an error with code `SDWebImageErrorBadImageData` will be used. * Note this options is not compatible with `SDImageCacheDecodeFirstFrameOnly`, which always produce a UIImage/NSImage. */ SDImageCacheMatchAnimatedImageClass = 1 << 7, }; /** * SDImageCache maintains a memory cache and a disk cache. Disk cache write operations are performed * asynchronous so it doesn’t add unnecessary latency to the UI. */ @interface SDImageCache : NSObject #pragma mark - Properties /** * Cache Config object - storing all kind of settings. * The property is copy so change of current config will not accidentally affect other cache's config. */ @property (nonatomic, copy, nonnull, readonly) SDImageCacheConfig *config; /** * The memory cache implementation object used for current image cache. * By default we use `SDMemoryCache` class, you can also use this to call your own implementation class method. * @note To customize this class, check `SDImageCacheConfig.memoryCacheClass` property. */ @property (nonatomic, strong, readonly, nonnull) id memoryCache; /** * The disk cache implementation object used for current image cache. * By default we use `SDMemoryCache` class, you can also use this to call your own implementation class method. * @note To customize this class, check `SDImageCacheConfig.diskCacheClass` property. * @warning When calling method about read/write in disk cache, be sure to either make your disk cache implementation IO-safe or using the same access queue to avoid issues. */ @property (nonatomic, strong, readonly, nonnull) id diskCache; /** * The disk cache's root path */ @property (nonatomic, copy, nonnull, readonly) NSString *diskCachePath; /** * The additional disk cache path to check if the query from disk cache not exist; * The `key` param is the image cache key. The returned file path will be used to load the disk cache. If return nil, ignore it. * Useful if you want to bundle pre-loaded images with your app */ @property (nonatomic, copy, nullable) SDImageCacheAdditionalCachePathBlock additionalCachePathBlock; #pragma mark - Singleton and initialization /** * Returns global shared cache instance */ @property (nonatomic, class, readonly, nonnull) SDImageCache *sharedImageCache; /** * Control the default disk cache directory. This will effect all the SDImageCache instance created after modification, even for shared image cache. * This can be used to share the same disk cache with the App and App Extension (Today/Notification Widget) using `- [NSFileManager.containerURLForSecurityApplicationGroupIdentifier:]`. * @note If you pass nil, the value will be reset to `~/Library/Caches/com.hackemist.SDImageCache`. * @note We still preserve the `namespace` arg, which means, if you change this property into `/path/to/use`, the `SDImageCache.sharedImageCache.diskCachePath` should be `/path/to/use/default` because shared image cache use `default` as namespace. * Defaults to nil. */ @property (nonatomic, class, readwrite, null_resettable) NSString *defaultDiskCacheDirectory; /** * Init a new cache store with a specific namespace * The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/) * * @param ns The namespace to use for this cache store */ - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns; /** * Init a new cache store with a specific namespace and directory. * The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/) * * @param ns The namespace to use for this cache store * @param directory Directory to cache disk images in */ - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns diskCacheDirectory:(nullable NSString *)directory; /** * Init a new cache store with a specific namespace, directory and config. * The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/) * * @param ns The namespace to use for this cache store * @param directory Directory to cache disk images in * @param config The cache config to be used to create the cache. You can provide custom memory cache or disk cache class in the cache config */ - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns diskCacheDirectory:(nullable NSString *)directory config:(nullable SDImageCacheConfig *)config NS_DESIGNATED_INITIALIZER; #pragma mark - Cache paths /** Get the cache path for a certain key @param key The unique image cache key @return The cache path. You can check `lastPathComponent` to grab the file name. */ - (nullable NSString *)cachePathForKey:(nullable NSString *)key; #pragma mark - Store Ops /** * Asynchronously store an image into memory and disk cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL * @param completionBlock A block executed after the operation is finished */ - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** * Asynchronously store an image into memory and disk cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL * @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously * @param completionBlock A block executed after the operation is finished * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG. */ - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** * Asynchronously store an image into memory and disk cache at the given key. * * @param image The image to store * @param imageData The image data as returned by the server, this representation will be used for disk storage * instead of converting the given image object into a storable/compressed image format in order * to save quality and CPU * @param key The unique image cache key, usually it's image absolute URL * @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously * @param completionBlock A block executed after the operation is finished * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG. */ - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** * Synchronously store image into memory cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL */ - (void)storeImageToMemory:(nullable UIImage*)image forKey:(nullable NSString *)key; /** * Synchronously store image data into disk cache at the given key. * * @param imageData The image data to store * @param key The unique image cache key, usually it's image absolute URL */ - (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key; #pragma mark - Contains and Check Ops /** * Asynchronously check if image exists in disk cache already (does not load the image) * * @param key the key describing the url * @param completionBlock the block to be executed when the check is done. * @note the completion block will be always executed on the main queue */ - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDImageCacheCheckCompletionBlock)completionBlock; /** * Synchronously check if image data exists in disk cache already (does not load the image) * * @param key the key describing the url */ - (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key; #pragma mark - Query and Retrieve Ops /** * Synchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded. * * @param key The unique key used to store the wanted image * @return The image data for the given key, or nil if not found. */ - (nullable NSData *)diskImageDataForKey:(nullable NSString *)key; /** * Asynchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded. * * @param key The unique key used to store the wanted image * @param completionBlock the block to be executed when the query is done. * @note the completion block will be always executed on the main queue */ - (void)diskImageDataQueryForKey:(nullable NSString *)key completion:(nullable SDImageCacheQueryDataCompletionBlock)completionBlock; /** * Asynchronously queries the cache with operation and call the completion when done. * * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. * @param doneBlock The completion block. Will not get called if the operation is cancelled * * @return a NSOperation instance containing the cache op */ - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; /** * Asynchronously queries the cache with operation and call the completion when done. * * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. * @param options A mask to specify options to use for this cache query * @param doneBlock The completion block. Will not get called if the operation is cancelled * * @return a NSOperation instance containing the cache op */ - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; /** * Asynchronously queries the cache with operation and call the completion when done. * * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. * @param options A mask to specify options to use for this cache query * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param doneBlock The completion block. Will not get called if the operation is cancelled * * @return a NSOperation instance containing the cache op */ - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; /** * Asynchronously queries the cache with operation and call the completion when done. * * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. * @param options A mask to specify options to use for this cache query * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediately. * @param doneBlock The completion block. Will not get called if the operation is cancelled * * @return a NSOperation instance containing the cache op */ - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; /** * Synchronously query the memory cache. * * @param key The unique key used to store the image * @return The image for the given key, or nil if not found. */ - (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key; /** * Synchronously query the disk cache. * * @param key The unique key used to store the image * @return The image for the given key, or nil if not found. */ - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key; /** * Synchronously query the disk cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc) * * @param key The unique key used to store the image * @param options A mask to specify options to use for this cache query * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @return The image for the given key, or nil if not found. */ - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context; /** * Synchronously query the cache (memory and or disk) after checking the memory cache. * * @param key The unique key used to store the image * @return The image for the given key, or nil if not found. */ - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key; /** * Synchronously query the cache (memory and or disk) after checking the memory cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc) * * @param key The unique key used to store the image * @param options A mask to specify options to use for this cache query * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @return The image for the given key, or nil if not found. */ - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context; #pragma mark - Remove Ops /** * Asynchronously remove the image from memory and disk cache * * @param key The unique image cache key * @param completion A block that should be executed after the image has been removed (optional) */ - (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion; /** * Asynchronously remove the image from memory and optionally disk cache * * @param key The unique image cache key * @param fromDisk Also remove cache entry from disk if YES. If NO, the completion block is called synchronously * @param completion A block that should be executed after the image has been removed (optional) */ - (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion; /** Synchronously remove the image from memory cache. @param key The unique image cache key */ - (void)removeImageFromMemoryForKey:(nullable NSString *)key; /** Synchronously remove the image from disk cache. @param key The unique image cache key */ - (void)removeImageFromDiskForKey:(nullable NSString *)key; #pragma mark - Cache clean Ops /** * Synchronously Clear all memory cached images */ - (void)clearMemory; /** * Asynchronously clear all disk cached images. Non-blocking method - returns immediately. * @param completion A block that should be executed after cache expiration completes (optional) */ - (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion; /** * Asynchronously remove all expired cached image from disk. Non-blocking method - returns immediately. * @param completionBlock A block that should be executed after cache expiration completes (optional) */ - (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock; #pragma mark - Cache Info /** * Get the total bytes size of images in the disk cache */ - (NSUInteger)totalDiskSize; /** * Get the number of images in the disk cache */ - (NSUInteger)totalDiskCount; /** * Asynchronously calculate the disk cache's size. */ - (void)calculateSizeWithCompletionBlock:(nullable SDImageCacheCalculateSizeBlock)completionBlock; @end /** * SDImageCache is the built-in image cache implementation for web image manager. It adopts `SDImageCache` protocol to provide the function for web image manager to use for image loading process. */ @interface SDImageCache (SDImageCache) @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCache.h" #import "NSImage+Compatibility.h" #import "SDImageCodersManager.h" #import "SDImageCoderHelper.h" #import "SDAnimatedImage.h" #import "UIImage+MemoryCacheCost.h" #import "UIImage+Metadata.h" #import "UIImage+ExtendedCacheData.h" static NSString * _defaultDiskCacheDirectory; @interface SDImageCache () #pragma mark - Properties @property (nonatomic, strong, readwrite, nonnull) id memoryCache; @property (nonatomic, strong, readwrite, nonnull) id diskCache; @property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config; @property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath; @property (nonatomic, strong, nullable) dispatch_queue_t ioQueue; @end @implementation SDImageCache #pragma mark - Singleton, init, dealloc + (nonnull instancetype)sharedImageCache { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } + (NSString *)defaultDiskCacheDirectory { if (!_defaultDiskCacheDirectory) { _defaultDiskCacheDirectory = [[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"]; } return _defaultDiskCacheDirectory; } + (void)setDefaultDiskCacheDirectory:(NSString *)defaultDiskCacheDirectory { _defaultDiskCacheDirectory = [defaultDiskCacheDirectory copy]; } - (instancetype)init { return [self initWithNamespace:@"default"]; } - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns { return [self initWithNamespace:ns diskCacheDirectory:nil]; } - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns diskCacheDirectory:(nullable NSString *)directory { return [self initWithNamespace:ns diskCacheDirectory:directory config:SDImageCacheConfig.defaultCacheConfig]; } - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns diskCacheDirectory:(nullable NSString *)directory config:(nullable SDImageCacheConfig *)config { if ((self = [super init])) { NSAssert(ns, @"Cache namespace should not be nil"); // Create IO serial queue _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL); if (!config) { config = SDImageCacheConfig.defaultCacheConfig; } _config = [config copy]; // Init the memory cache NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol"); _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config]; // Init the disk cache if (!directory) { // Use default disk cache directory directory = [self.class defaultDiskCacheDirectory]; } _diskCachePath = [directory stringByAppendingPathComponent:ns]; NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol"); _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config]; // Check and migrate disk cache directory if need [self migrateDiskCacheDirectory]; #if SD_UIKIT // Subscribe to app events [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; #endif #if SD_MAC [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; #endif } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Cache paths - (nullable NSString *)cachePathForKey:(nullable NSString *)key { if (!key) { return nil; } return [self.diskCache cachePathForKey:key]; } + (nullable NSString *)userCacheDirectory { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); return paths.firstObject; } - (void)migrateDiskCacheDirectory { if ([self.diskCache isKindOfClass:[SDDiskCache class]]) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // ~/Library/Caches/com.hackemist.SDImageCache/default/ NSString *newDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"]; // ~/Library/Caches/default/com.hackemist.SDWebImageCache.default/ NSString *oldDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"]; dispatch_async(self.ioQueue, ^{ [((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath]; }); }); } } #pragma mark - Store Ops - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock { [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { return [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:toDisk completion:completionBlock]; } - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toMemory:(BOOL)toMemory toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { if (!image || !key) { if (completionBlock) { completionBlock(); } return; } // if memory cache is enabled if (toMemory && self.config.shouldCacheImagesInMemory) { NSUInteger cost = image.sd_memoryCost; [self.memoryCache setObject:image forKey:key cost:cost]; } if (!toDisk) { if (completionBlock) { completionBlock(); } return; } dispatch_async(self.ioQueue, ^{ @autoreleasepool { NSData *data = imageData; if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) { // If image is custom animated image class, prefer its original animated data data = [((id)image) animatedImageData]; } if (!data && image) { // Check image's associated image format, may return .undefined SDImageFormat format = image.sd_imageFormat; if (format == SDImageFormatUndefined) { // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14) if (image.sd_isAnimated) { format = SDImageFormatGIF; } else { // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG; } } data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil]; } [self _storeImageDataToDisk:data forKey:key]; [self _archivedDataWithImage:image forKey:key]; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); } - (void)_archivedDataWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return; } // Check extended data id extendedObject = image.sd_extendedObject; if (![extendedObject conformsToProtocol:@protocol(NSCoding)]) { return; } NSData *extendedData; if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) { NSError *error; extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error]; if (error) { NSLog(@"NSKeyedArchiver archive failed with error: %@", error); } } else { @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject]; #pragma clang diagnostic pop } @catch (NSException *exception) { NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception); } } if (extendedData) { [self.diskCache setExtendedData:extendedData forKey:key]; } } - (void)storeImageToMemory:(UIImage *)image forKey:(NSString *)key { if (!image || !key) { return; } NSUInteger cost = image.sd_memoryCost; [self.memoryCache setObject:image forKey:key cost:cost]; } - (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key { if (!imageData || !key) { return; } dispatch_sync(self.ioQueue, ^{ [self _storeImageDataToDisk:imageData forKey:key]; }); } // Make sure to call from io queue by caller - (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key { if (!imageData || !key) { return; } [self.diskCache setData:imageData forKey:key]; } #pragma mark - Query and Retrieve Ops - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDImageCacheCheckCompletionBlock)completionBlock { dispatch_async(self.ioQueue, ^{ BOOL exists = [self _diskImageDataExistsWithKey:key]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(exists); }); } }); } - (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key { if (!key) { return NO; } __block BOOL exists = NO; dispatch_sync(self.ioQueue, ^{ exists = [self _diskImageDataExistsWithKey:key]; }); return exists; } // Make sure to call from io queue by caller - (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key { if (!key) { return NO; } return [self.diskCache containsDataForKey:key]; } - (void)diskImageDataQueryForKey:(NSString *)key completion:(SDImageCacheQueryDataCompletionBlock)completionBlock { dispatch_async(self.ioQueue, ^{ NSData *imageData = [self diskImageDataBySearchingAllPathsForKey:key]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(imageData); }); } }); } - (nullable NSData *)diskImageDataForKey:(nullable NSString *)key { if (!key) { return nil; } __block NSData *imageData = nil; dispatch_sync(self.ioQueue, ^{ imageData = [self diskImageDataBySearchingAllPathsForKey:key]; }); return imageData; } - (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key { return [self.memoryCache objectForKey:key]; } - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key { return [self imageFromDiskCacheForKey:key options:0 context:nil]; } - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context { NSData *data = [self diskImageDataForKey:key]; UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context]; BOOL shouldCacheToMomery = YES; if (context[SDWebImageContextStoreCacheType]) { SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue]; shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); } if (diskImage && self.config.shouldCacheImagesInMemory && shouldCacheToMomery) { NSUInteger cost = diskImage.sd_memoryCost; [self.memoryCache setObject:diskImage forKey:key cost:cost]; } return diskImage; } - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key { return [self imageFromCacheForKey:key options:0 context:nil]; } - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context { // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { return image; } // Second check the disk cache... image = [self imageFromDiskCacheForKey:key options:options context:context]; return image; } - (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key { if (!key) { return nil; } NSData *data = [self.diskCache dataForKey:key]; if (data) { return data; } // Addtional cache path for custom pre-load cache if (self.additionalCachePathBlock) { NSString *filePath = self.additionalCachePathBlock(key); if (filePath) { data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil]; } } return data; } - (nullable UIImage *)diskImageForKey:(nullable NSString *)key { NSData *data = [self diskImageDataForKey:key]; return [self diskImageForKey:key data:data]; } - (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data { return [self diskImageForKey:key data:data options:0 context:nil]; } - (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context { if (!data) { return nil; } UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context); [self _unarchiveObjectWithImage:image forKey:key]; return image; } - (void)_unarchiveObjectWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return; } // Check extended data NSData *extendedData = [self.diskCache extendedDataForKey:key]; if (!extendedData) { return; } id extendedObject; if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) { NSError *error; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error]; unarchiver.requiresSecureCoding = NO; extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error]; if (error) { NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error); } } else { @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData]; #pragma clang diagnostic pop } @catch (NSException *exception) { NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception); } } image.sd_extendedObject = extendedObject; } - (nullable NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDImageCacheQueryCompletionBlock)doneBlock { return [self queryCacheOperationForKey:key options:0 done:doneBlock]; } - (nullable NSOperation *)queryCacheOperationForKey:(NSString *)key options:(SDImageCacheOptions)options done:(SDImageCacheQueryCompletionBlock)doneBlock { return [self queryCacheOperationForKey:key options:options context:nil done:doneBlock]; } - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock { return [self queryCacheOperationForKey:key options:options context:context cacheType:SDImageCacheTypeAll done:doneBlock]; } - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock { if (!key) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } // Invalid cache type if (queryCacheType == SDImageCacheTypeNone) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } // First check the in-memory cache... UIImage *image; if (queryCacheType != SDImageCacheTypeDisk) { image = [self imageFromMemoryCacheForKey:key]; } if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { // Ensure static image Class animatedImageClass = image.class; if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) { #if SD_MAC image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation]; #endif } } else if (options & SDImageCacheMatchAnimatedImageClass) { // Check image class matching Class animatedImageClass = image.class; Class desiredImageClass = context[SDWebImageContextAnimatedImageClass]; if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) { image = nil; } } } BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData)); if (shouldQueryMemoryOnly) { if (doneBlock) { doneBlock(image, nil, SDImageCacheTypeMemory); } return nil; } // Second check the disk cache... NSOperation *operation = [NSOperation new]; // Check whether we need to synchronously query disk // 1. in-memory cache hit & memoryDataSync // 2. in-memory cache miss & diskDataSync BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) || (!image && options & SDImageCacheQueryDiskDataSync)); void(^queryDiskBlock)(void) = ^{ if (operation.isCancelled) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return; } @autoreleasepool { NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key]; UIImage *diskImage; if (image) { // the image is from in-memory cache, but need image data diskImage = image; } else if (diskData) { BOOL shouldCacheToMomery = YES; if (context[SDWebImageContextStoreCacheType]) { SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue]; shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory); } // decode image data only if in-memory cache missed diskImage = [self diskImageForKey:key data:diskData options:options context:context]; if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) { NSUInteger cost = diskImage.sd_memoryCost; [self.memoryCache setObject:diskImage forKey:key cost:cost]; } } if (doneBlock) { if (shouldQueryDiskSync) { doneBlock(diskImage, diskData, SDImageCacheTypeDisk); } else { dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); } } } }; // Query in ioQueue to keep IO-safe if (shouldQueryDiskSync) { dispatch_sync(self.ioQueue, queryDiskBlock); } else { dispatch_async(self.ioQueue, queryDiskBlock); } return operation; } #pragma mark - Remove Ops - (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion { [self removeImageForKey:key fromDisk:YES withCompletion:completion]; } - (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion { [self removeImageForKey:key fromMemory:YES fromDisk:fromDisk withCompletion:completion]; } - (void)removeImageForKey:(nullable NSString *)key fromMemory:(BOOL)fromMemory fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion { if (key == nil) { return; } if (fromMemory && self.config.shouldCacheImagesInMemory) { [self.memoryCache removeObjectForKey:key]; } if (fromDisk) { dispatch_async(self.ioQueue, ^{ [self.diskCache removeDataForKey:key]; if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(); }); } }); } else if (completion) { completion(); } } - (void)removeImageFromMemoryForKey:(NSString *)key { if (!key) { return; } [self.memoryCache removeObjectForKey:key]; } - (void)removeImageFromDiskForKey:(NSString *)key { if (!key) { return; } dispatch_sync(self.ioQueue, ^{ [self _removeImageFromDiskForKey:key]; }); } // Make sure to call from io queue by caller - (void)_removeImageFromDiskForKey:(NSString *)key { if (!key) { return; } [self.diskCache removeDataForKey:key]; } #pragma mark - Cache clean Ops - (void)clearMemory { [self.memoryCache removeAllObjects]; } - (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion { dispatch_async(self.ioQueue, ^{ [self.diskCache removeAllData]; if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(); }); } }); } - (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ [self.diskCache removeExpiredData]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); } #pragma mark - UIApplicationWillTerminateNotification #if SD_UIKIT || SD_MAC - (void)applicationWillTerminate:(NSNotification *)notification { // On iOS/macOS, the async opeartion to remove exipred data will be terminated quickly // Try using the sync operation to ensure we reomve the exipred data if (!self.config.shouldRemoveExpiredDataWhenTerminate) { return; } dispatch_sync(self.ioQueue, ^{ [self.diskCache removeExpiredData]; }); } #endif #pragma mark - UIApplicationDidEnterBackgroundNotification #if SD_UIKIT - (void)applicationDidEnterBackground:(NSNotification *)notification { if (!self.config.shouldRemoveExpiredDataWhenEnterBackground) { return; } Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // Clean up any unfinished task business by marking where you // stopped or ending the task outright. [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; // Start the long-running task and return immediately. [self deleteOldFilesWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; } #endif #pragma mark - Cache Info - (NSUInteger)totalDiskSize { __block NSUInteger size = 0; dispatch_sync(self.ioQueue, ^{ size = [self.diskCache totalSize]; }); return size; } - (NSUInteger)totalDiskCount { __block NSUInteger count = 0; dispatch_sync(self.ioQueue, ^{ count = [self.diskCache totalCount]; }); return count; } - (void)calculateSizeWithCompletionBlock:(nullable SDImageCacheCalculateSizeBlock)completionBlock { dispatch_async(self.ioQueue, ^{ NSUInteger fileCount = [self.diskCache totalCount]; NSUInteger totalSize = [self.diskCache totalSize]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(fileCount, totalSize); }); } }); } #pragma mark - Helper + (SDWebImageOptions)imageOptionsFromCacheOptions:(SDImageCacheOptions)cacheOptions { SDWebImageOptions options = 0; if (cacheOptions & SDImageCacheScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages; if (cacheOptions & SDImageCacheDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly; if (cacheOptions & SDImageCachePreloadAllFrames) options |= SDWebImagePreloadAllFrames; if (cacheOptions & SDImageCacheAvoidDecodeImage) options |= SDWebImageAvoidDecodeImage; if (cacheOptions & SDImageCacheMatchAnimatedImageClass) options |= SDWebImageMatchAnimatedImageClass; return options; } @end @implementation SDImageCache (SDImageCache) #pragma mark - SDImageCache - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock]; } - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock { SDImageCacheOptions cacheOptions = 0; if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData; if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync; if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync; if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages; if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage; if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly; if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames; if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass; return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock]; } - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { switch (cacheType) { case SDImageCacheTypeNone: { [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock]; } break; case SDImageCacheTypeMemory: { [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock]; } break; case SDImageCacheTypeDisk: { [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock]; } break; case SDImageCacheTypeAll: { [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock]; } break; default: { if (completionBlock) { completionBlock(); } } break; } } - (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock { switch (cacheType) { case SDImageCacheTypeNone: { [self removeImageForKey:key fromMemory:NO fromDisk:NO withCompletion:completionBlock]; } break; case SDImageCacheTypeMemory: { [self removeImageForKey:key fromMemory:YES fromDisk:NO withCompletion:completionBlock]; } break; case SDImageCacheTypeDisk: { [self removeImageForKey:key fromMemory:NO fromDisk:YES withCompletion:completionBlock]; } break; case SDImageCacheTypeAll: { [self removeImageForKey:key fromMemory:YES fromDisk:YES withCompletion:completionBlock]; } break; default: { if (completionBlock) { completionBlock(); } } break; } } - (void)containsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock { switch (cacheType) { case SDImageCacheTypeNone: { if (completionBlock) { completionBlock(SDImageCacheTypeNone); } } break; case SDImageCacheTypeMemory: { BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil); if (completionBlock) { completionBlock(isInMemoryCache ? SDImageCacheTypeMemory : SDImageCacheTypeNone); } } break; case SDImageCacheTypeDisk: { [self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { if (completionBlock) { completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone); } }]; } break; case SDImageCacheTypeAll: { BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil); if (isInMemoryCache) { if (completionBlock) { completionBlock(SDImageCacheTypeMemory); } return; } [self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { if (completionBlock) { completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone); } }]; } break; default: if (completionBlock) { completionBlock(SDImageCacheTypeNone); } break; } } - (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock { switch (cacheType) { case SDImageCacheTypeNone: { if (completionBlock) { completionBlock(); } } break; case SDImageCacheTypeMemory: { [self clearMemory]; if (completionBlock) { completionBlock(); } } break; case SDImageCacheTypeDisk: { [self clearDiskOnCompletion:completionBlock]; } break; case SDImageCacheTypeAll: { [self clearMemory]; [self clearDiskOnCompletion:completionBlock]; } break; default: { if (completionBlock) { completionBlock(); } } break; } } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheConfig.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// Image Cache Expire Type typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) { /** * When the image cache is accessed it will update this value */ SDImageCacheConfigExpireTypeAccessDate, /** * When the image cache is created or modified it will update this value (Default) */ SDImageCacheConfigExpireTypeModificationDate, /** * When the image cache is created it will update this value */ SDImageCacheConfigExpireTypeCreationDate, /** * When the image cache is created, modified, renamed, file attribute updated (like permission, xattr) it will update this value */ SDImageCacheConfigExpireTypeChangeDate, }; /** The class contains all the config for image cache @note This class conform to NSCopying, make sure to add the property in `copyWithZone:` as well. */ @interface SDImageCacheConfig : NSObject /** Gets the default cache config used for shared instance or initialization when it does not provide any cache config. Such as `SDImageCache.sharedImageCache`. @note You can modify the property on default cache config, which can be used for later created cache instance. The already created cache instance does not get affected. */ @property (nonatomic, class, readonly, nonnull) SDImageCacheConfig *defaultCacheConfig; /** * Whether or not to disable iCloud backup * Defaults to YES. */ @property (assign, nonatomic) BOOL shouldDisableiCloud; /** * Whether or not to use memory cache * @note When the memory cache is disabled, the weak memory cache will also be disabled. * Defaults to YES. */ @property (assign, nonatomic) BOOL shouldCacheImagesInMemory; /* * The option to control weak memory cache for images. When enable, `SDImageCache`'s memory cache will use a weak maptable to store the image at the same time when it stored to memory, and get removed at the same time. * However when memory warning is triggered, since the weak maptable does not hold a strong reference to image instance, even when the memory cache itself is purged, some images which are held strongly by UIImageViews or other live instances can be recovered again, to avoid later re-query from disk cache or network. This may be helpful for the case, for example, when app enter background and memory is purged, cause cell flashing after re-enter foreground. * Defaults to YES. You can change this option dynamically. */ @property (assign, nonatomic) BOOL shouldUseWeakMemoryCache; /** * Whether or not to remove the expired disk data when application entering the background. (Not works for macOS) * Defaults to YES. */ @property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground; /** * Whether or not to remove the expired disk data when application been terminated. This operation is processed in sync to ensure clean up. * Defaults to YES. */ @property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenTerminate; /** * The reading options while reading cache from disk. * Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance. */ @property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions; /** * The writing options while writing cache to disk. * Defaults to `NSDataWritingAtomic`. You can set this to `NSDataWritingWithoutOverwriting` to prevent overwriting an existing file. */ @property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions; /** * The maximum length of time to keep an image in the disk cache, in seconds. * Setting this to a negative value means no expiring. * Setting this to zero means that all cached files would be removed when do expiration check. * Defaults to 1 week. */ @property (assign, nonatomic) NSTimeInterval maxDiskAge; /** * The maximum size of the disk cache, in bytes. * Defaults to 0. Which means there is no cache size limit. */ @property (assign, nonatomic) NSUInteger maxDiskSize; /** * The maximum "total cost" of the in-memory image cache. The cost function is the bytes size held in memory. * @note The memory cost is bytes size in memory, but not simple pixels count. For common ARGB8888 image, one pixel is 4 bytes (32 bits). * Defaults to 0. Which means there is no memory cost limit. */ @property (assign, nonatomic) NSUInteger maxMemoryCost; /** * The maximum number of objects in-memory image cache should hold. * Defaults to 0. Which means there is no memory count limit. */ @property (assign, nonatomic) NSUInteger maxMemoryCount; /* * The attribute which the clear cache will be checked against when clearing the disk cache * Default is Modified Date */ @property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType; /** * The custom file manager for disk cache. Pass nil to let disk cache choose the proper file manager. * Defaults to nil. * @note This value does not support dynamic changes. Which means further modification on this value after cache initialized has no effect. * @note Since `NSFileManager` does not support `NSCopying`. We just pass this by reference during copying. So it's not recommend to set this value on `defaultCacheConfig`. */ @property (strong, nonatomic, nullable) NSFileManager *fileManager; /** * The custom memory cache class. Provided class instance must conform to `SDMemoryCache` protocol to allow usage. * Defaults to built-in `SDMemoryCache` class. * @note This value does not support dynamic changes. Which means further modification on this value after cache initialized has no effect. */ @property (assign, nonatomic, nonnull) Class memoryCacheClass; /** * The custom disk cache class. Provided class instance must conform to `SDDiskCache` protocol to allow usage. * Defaults to built-in `SDDiskCache` class. * @note This value does not support dynamic changes. Which means further modification on this value after cache initialized has no effect. */ @property (assign ,nonatomic, nonnull) Class diskCacheClass; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheConfig.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCacheConfig.h" #import "SDMemoryCache.h" #import "SDDiskCache.h" static SDImageCacheConfig *_defaultCacheConfig; static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week @implementation SDImageCacheConfig + (SDImageCacheConfig *)defaultCacheConfig { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _defaultCacheConfig = [SDImageCacheConfig new]; }); return _defaultCacheConfig; } - (instancetype)init { if (self = [super init]) { _shouldDisableiCloud = YES; _shouldCacheImagesInMemory = YES; _shouldUseWeakMemoryCache = YES; _shouldRemoveExpiredDataWhenEnterBackground = YES; _shouldRemoveExpiredDataWhenTerminate = YES; _diskCacheReadingOptions = 0; _diskCacheWritingOptions = NSDataWritingAtomic; _maxDiskAge = kDefaultCacheMaxDiskAge; _maxDiskSize = 0; _diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate; _memoryCacheClass = [SDMemoryCache class]; _diskCacheClass = [SDDiskCache class]; } return self; } - (id)copyWithZone:(NSZone *)zone { SDImageCacheConfig *config = [[[self class] allocWithZone:zone] init]; config.shouldDisableiCloud = self.shouldDisableiCloud; config.shouldCacheImagesInMemory = self.shouldCacheImagesInMemory; config.shouldUseWeakMemoryCache = self.shouldUseWeakMemoryCache; config.shouldRemoveExpiredDataWhenEnterBackground = self.shouldRemoveExpiredDataWhenEnterBackground; config.shouldRemoveExpiredDataWhenTerminate = self.shouldRemoveExpiredDataWhenTerminate; config.diskCacheReadingOptions = self.diskCacheReadingOptions; config.diskCacheWritingOptions = self.diskCacheWritingOptions; config.maxDiskAge = self.maxDiskAge; config.maxDiskSize = self.maxDiskSize; config.maxMemoryCost = self.maxMemoryCost; config.maxMemoryCount = self.maxMemoryCount; config.diskCacheExpireType = self.diskCacheExpireType; config.fileManager = self.fileManager; // NSFileManager does not conform to NSCopying, just pass the reference config.memoryCacheClass = self.memoryCacheClass; config.diskCacheClass = self.diskCacheClass; return config; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheDefine.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageOperation.h" #import "SDWebImageDefine.h" /// Image Cache Type typedef NS_ENUM(NSInteger, SDImageCacheType) { /** * For query and contains op in response, means the image isn't available in the image cache * For op in request, this type is not available and take no effect. */ SDImageCacheTypeNone, /** * For query and contains op in response, means the image was obtained from the disk cache. * For op in request, means process only disk cache. */ SDImageCacheTypeDisk, /** * For query and contains op in response, means the image was obtained from the memory cache. * For op in request, means process only memory cache. */ SDImageCacheTypeMemory, /** * For query and contains op in response, this type is not available and take no effect. * For op in request, means process both memory cache and disk cache. */ SDImageCacheTypeAll }; typedef void(^SDImageCacheCheckCompletionBlock)(BOOL isInCache); typedef void(^SDImageCacheQueryDataCompletionBlock)(NSData * _Nullable data); typedef void(^SDImageCacheCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize); typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSString * _Nonnull key); typedef void(^SDImageCacheQueryCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType); typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCacheType); /** This is the built-in decoding process for image query from cache. @note If you want to implement your custom loader with `queryImageForKey:options:context:completion:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. @param imageData The image data from the cache. Should not be nil @param cacheKey The image cache key from the input. Should not be nil @param options The options arg from the input @param context The context arg from the input @return The decoded image for current image data query from cache */ FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context); /** This is the image cache protocol to provide custom image cache for `SDWebImageManager`. Though the best practice to custom image cache, is to write your own class which conform `SDMemoryCache` or `SDDiskCache` protocol for `SDImageCache` class (See more on `SDImageCacheConfig.memoryCacheClass & SDImageCacheConfig.diskCacheClass`). However, if your own cache implementation contains more advanced feature beyond `SDImageCache` itself, you can consider to provide this instead. For example, you can even use a cache manager like `SDImageCachesManager` to register multiple caches. */ @protocol SDImageCache @required /** Query the cached image from image cache for given key. The operation can be used to cancel the query. If image is cached in memory, completion is called synchronously, else asynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`) @param key The image cache key @param options A mask to specify options to use for this query @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @param completionBlock The completion block. Will not get called if the operation is cancelled @return The operation for this query */ - (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock; /** Query the cached image from image cache for given key. The operation can be used to cancel the query. If image is cached in memory, completion is called synchronously, else asynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`) @param key The image cache key @param options A mask to specify options to use for this query @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @param cacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediately. @param completionBlock The completion block. Will not get called if the operation is cancelled @return The operation for this query */ - (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock; /** Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously. @param image The image to store @param imageData The image data to be used for disk storage @param key The image cache key @param cacheType The image store op cache type @param completionBlock A block executed after the operation is finished */ - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** Remove the image from image cache for the given key. If cache type is memory only, completion is called synchronously, else asynchronously. @param key The image cache key @param cacheType The image remove op cache type @param completionBlock A block executed after the operation is finished */ - (void)removeImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** Check if image cache contains the image for the given key (does not load the image). If image is cached in memory, completion is called synchronously, else asynchronously. @param key The image cache key @param cacheType The image contains op cache type @param completionBlock A block executed after the operation is finished. */ - (void)containsImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock; /** Clear all the cached images for image cache. If cache type is memory only, completion is called synchronously, else asynchronously. @param cacheType The image clear op cache type @param completionBlock A block executed after the operation is finished */ - (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheDefine.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCacheDefine.h" #import "SDImageCodersManager.h" #import "SDImageCoderHelper.h" #import "SDAnimatedImage.h" #import "UIImage+Metadata.h" #import "SDInternalMacros.h" UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) { UIImage *image; BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio]; NSValue *thumbnailSizeValue; BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages); if (shouldScaleDown) { CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4; CGFloat dimension = ceil(sqrt(thumbnailPixels)); thumbnailSizeValue = @(CGSizeMake(dimension, dimension)); } if (context[SDWebImageContextImageThumbnailPixelSize]) { thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; } SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2]; mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame); mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale); mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue; mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue; mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; // Grab the image coder id imageCoder; if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { imageCoder = context[SDWebImageContextImageCoder]; } else { imageCoder = [SDImageCodersManager sharedManager]; } if (!decodeFirstFrame) { Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; // check whether we should use `SDAnimatedImage` if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) { image = [[animatedImageClass alloc] initWithData:imageData scale:scale options:coderOptions]; if (image) { // Preload frames if supported if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) { [((id)image) preloadAllFrames]; } } else { // Check image class matching if (options & SDWebImageMatchAnimatedImageClass) { return nil; } } } } if (!image) { image = [imageCoder decodedImageWithData:imageData options:coderOptions]; } if (image) { BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage); if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) { // `SDAnimatedImage` do not decode shouldDecode = NO; } else if (image.sd_isAnimated) { // animated image do not decode shouldDecode = NO; } if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } } return image; } ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCachesManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageCacheDefine.h" /// Policy for cache operation typedef NS_ENUM(NSUInteger, SDImageCachesManagerOperationPolicy) { SDImageCachesManagerOperationPolicySerial, // process all caches serially (from the highest priority to the lowest priority cache by order) SDImageCachesManagerOperationPolicyConcurrent, // process all caches concurrently SDImageCachesManagerOperationPolicyHighestOnly, // process the highest priority cache only SDImageCachesManagerOperationPolicyLowestOnly // process the lowest priority cache only }; /** A caches manager to manage multiple caches. */ @interface SDImageCachesManager : NSObject /** Returns the global shared caches manager instance. By default we will set [`SDImageCache.sharedImageCache`] into the caches array. */ @property (nonatomic, class, readonly, nonnull) SDImageCachesManager *sharedManager; // These are op policy for cache manager. /** Operation policy for query op. Defaults to `Serial`, means query all caches serially (one completion called then next begin) until one cache query success (`image` != nil). */ @property (nonatomic, assign) SDImageCachesManagerOperationPolicy queryOperationPolicy; /** Operation policy for store op. Defaults to `HighestOnly`, means store to the highest priority cache only. */ @property (nonatomic, assign) SDImageCachesManagerOperationPolicy storeOperationPolicy; /** Operation policy for remove op. Defaults to `Concurrent`, means remove all caches concurrently. */ @property (nonatomic, assign) SDImageCachesManagerOperationPolicy removeOperationPolicy; /** Operation policy for contains op. Defaults to `Serial`, means check all caches serially (one completion called then next begin) until one cache check success (`containsCacheType` != None). */ @property (nonatomic, assign) SDImageCachesManagerOperationPolicy containsOperationPolicy; /** Operation policy for clear op. Defaults to `Concurrent`, means clear all caches concurrently. */ @property (nonatomic, assign) SDImageCachesManagerOperationPolicy clearOperationPolicy; /** All caches in caches manager. The caches array is a priority queue, which means the later added cache will have the highest priority */ @property (nonatomic, copy, nullable) NSArray> *caches; /** Add a new cache to the end of caches array. Which has the highest priority. @param cache cache */ - (void)addCache:(nonnull id)cache; /** Remove a cache in the caches array. @param cache cache */ - (void)removeCache:(nonnull id)cache; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCachesManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCachesManager.h" #import "SDImageCachesManagerOperation.h" #import "SDImageCache.h" #import "SDInternalMacros.h" @interface SDImageCachesManager () @property (nonatomic, strong, nonnull) NSMutableArray> *imageCaches; @end @implementation SDImageCachesManager { SD_LOCK_DECLARE(_cachesLock); } + (SDImageCachesManager *)sharedManager { static dispatch_once_t onceToken; static SDImageCachesManager *manager; dispatch_once(&onceToken, ^{ manager = [[SDImageCachesManager alloc] init]; }); return manager; } - (instancetype)init { self = [super init]; if (self) { self.queryOperationPolicy = SDImageCachesManagerOperationPolicySerial; self.storeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly; self.removeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; self.containsOperationPolicy = SDImageCachesManagerOperationPolicySerial; self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; // initialize with default image caches _imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]]; SD_LOCK_INIT(_cachesLock); } return self; } - (NSArray> *)caches { SD_LOCK(_cachesLock); NSArray> *caches = [_imageCaches copy]; SD_UNLOCK(_cachesLock); return caches; } - (void)setCaches:(NSArray> *)caches { SD_LOCK(_cachesLock); [_imageCaches removeAllObjects]; if (caches.count) { [_imageCaches addObjectsFromArray:caches]; } SD_UNLOCK(_cachesLock); } #pragma mark - Cache IO operations - (void)addCache:(id)cache { if (![cache conformsToProtocol:@protocol(SDImageCache)]) { return; } SD_LOCK(_cachesLock); [_imageCaches addObject:cache]; SD_UNLOCK(_cachesLock); } - (void)removeCache:(id)cache { if (![cache conformsToProtocol:@protocol(SDImageCache)]) { return; } SD_LOCK(_cachesLock); [_imageCaches removeObject:cache]; SD_UNLOCK(_cachesLock); } #pragma mark - SDImageCache - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock { return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock]; } - (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock { if (!key) { return nil; } NSArray> *caches = self.caches; NSUInteger count = caches.count; if (count == 0) { return nil; } else if (count == 1) { return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } switch (self.queryOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; return operation; } break; case SDImageCachesManagerOperationPolicySerial: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; return operation; } break; default: return nil; break; } } - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock { if (!key) { return; } NSArray> *caches = self.caches; NSUInteger count = caches.count; if (count == 0) { return; } else if (count == 1) { [caches.firstObject storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock]; return; } switch (self.storeOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; [cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; [cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self concurrentStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; } break; case SDImageCachesManagerOperationPolicySerial: { [self serialStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator]; } break; default: break; } } - (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock { if (!key) { return; } NSArray> *caches = self.caches; NSUInteger count = caches.count; if (count == 0) { return; } else if (count == 1) { [caches.firstObject removeImageForKey:key cacheType:cacheType completion:completionBlock]; return; } switch (self.removeOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; [cache removeImageForKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; [cache removeImageForKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self concurrentRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; } break; case SDImageCachesManagerOperationPolicySerial: { [self serialRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator]; } break; default: break; } } - (void)containsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock { if (!key) { return; } NSArray> *caches = self.caches; NSUInteger count = caches.count; if (count == 0) { return; } else if (count == 1) { [caches.firstObject containsImageForKey:key cacheType:cacheType completion:completionBlock]; return; } switch (self.clearOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; [cache containsImageForKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; [cache containsImageForKey:key cacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self concurrentContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; } break; case SDImageCachesManagerOperationPolicySerial: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self serialContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; } break; default: break; } } - (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock { NSArray> *caches = self.caches; NSUInteger count = caches.count; if (count == 0) { return; } else if (count == 1) { [caches.firstObject clearWithCacheType:cacheType completion:completionBlock]; return; } switch (self.clearOperationPolicy) { case SDImageCachesManagerOperationPolicyHighestOnly: { id cache = caches.lastObject; [cache clearWithCacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyLowestOnly: { id cache = caches.firstObject; [cache clearWithCacheType:cacheType completion:completionBlock]; } break; case SDImageCachesManagerOperationPolicyConcurrent: { SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new]; [operation beginWithTotalCount:caches.count]; [self concurrentClearWithCacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation]; } break; case SDImageCachesManagerOperationPolicySerial: { [self serialClearWithCacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator]; } break; default: break; } } #pragma mark - Concurrent Operation - (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (image) { // Success [operation done]; if (completionBlock) { completionBlock(image, data, cacheType); } return; } if (operation.pendingCount == 0) { // Complete [operation done]; if (completionBlock) { completionBlock(nil, nil, SDImageCacheTypeNone); } } }]; } } - (void)concurrentStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { [cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:^{ if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (operation.pendingCount == 0) { // Complete [operation done]; if (completionBlock) { completionBlock(); } } }]; } } - (void)concurrentRemoveImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { [cache removeImageForKey:key cacheType:cacheType completion:^{ if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (operation.pendingCount == 0) { // Complete [operation done]; if (completionBlock) { completionBlock(); } } }]; } } - (void)concurrentContainsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { [cache containsImageForKey:key cacheType:cacheType completion:^(SDImageCacheType containsCacheType) { if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (containsCacheType != SDImageCacheTypeNone) { // Success [operation done]; if (completionBlock) { completionBlock(containsCacheType); } return; } if (operation.pendingCount == 0) { // Complete [operation done]; if (completionBlock) { completionBlock(SDImageCacheTypeNone); } } }]; } } - (void)concurrentClearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); for (id cache in enumerator) { [cache clearWithCacheType:cacheType completion:^{ if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (operation.pendingCount == 0) { // Complete [operation done]; if (completionBlock) { completionBlock(); } } }]; } } #pragma mark - Serial Operation - (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); id cache = enumerator.nextObject; if (!cache) { // Complete [operation done]; if (completionBlock) { completionBlock(nil, nil, SDImageCacheTypeNone); } return; } @weakify(self); [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { @strongify(self); if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (image) { // Success [operation done]; if (completionBlock) { completionBlock(image, data, cacheType); } return; } // Next [self serialQueryImageForKey:key options:options context:context cacheType:queryCacheType completion:completionBlock enumerator:enumerator operation:operation]; }]; } - (void)serialStoreImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator { NSParameterAssert(enumerator); id cache = enumerator.nextObject; if (!cache) { // Complete if (completionBlock) { completionBlock(); } return; } @weakify(self); [cache storeImage:image imageData:imageData forKey:key cacheType:cacheType completion:^{ @strongify(self); // Next [self serialStoreImage:image imageData:imageData forKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator]; }]; } - (void)serialRemoveImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator { NSParameterAssert(enumerator); id cache = enumerator.nextObject; if (!cache) { // Complete if (completionBlock) { completionBlock(); } return; } @weakify(self); [cache removeImageForKey:key cacheType:cacheType completion:^{ @strongify(self); // Next [self serialRemoveImageForKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator]; }]; } - (void)serialContainsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheContainsCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation { NSParameterAssert(enumerator); NSParameterAssert(operation); id cache = enumerator.nextObject; if (!cache) { // Complete [operation done]; if (completionBlock) { completionBlock(SDImageCacheTypeNone); } return; } @weakify(self); [cache containsImageForKey:key cacheType:cacheType completion:^(SDImageCacheType containsCacheType) { @strongify(self); if (operation.isCancelled) { // Cancelled return; } if (operation.isFinished) { // Finished return; } [operation completeOne]; if (containsCacheType != SDImageCacheTypeNone) { // Success [operation done]; if (completionBlock) { completionBlock(containsCacheType); } return; } // Next [self serialContainsImageForKey:key cacheType:cacheType completion:completionBlock enumerator:enumerator operation:operation]; }]; } - (void)serialClearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock enumerator:(NSEnumerator> *)enumerator { NSParameterAssert(enumerator); id cache = enumerator.nextObject; if (!cache) { // Complete if (completionBlock) { completionBlock(); } return; } @weakify(self); [cache clearWithCacheType:cacheType completion:^{ @strongify(self); // Next [self serialClearWithCacheType:cacheType completion:completionBlock enumerator:enumerator]; }]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "NSData+ImageContentType.h" typedef NSString * SDImageCoderOption NS_STRING_ENUM; typedef NSDictionary SDImageCoderOptions; typedef NSMutableDictionary SDImageCoderMutableOptions; #pragma mark - Coder Options // These options are for image decoding /** A Boolean value indicating whether to decode the first frame only for animated image during decoding. (NSNumber). If not provide, decode animated image if need. @note works for `SDImageCoder`. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrameOnly; /** A CGFloat value which is greater than or equal to 1.0. This value specify the image scale factor for decoding. If not provide, use 1.0. (NSNumber) @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor; /** A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format). Defaults to YES. @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAspectRatio; /** A CGSize value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.preserveAspectRatio`) the value size. Defaults to CGSizeZero, which means no thumbnail generation at all. @note Supports for animated image as well. @note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both. @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize; // These options are for image encoding /** A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need. @note works for `SDImageCoder`. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeFirstFrameOnly; /** A double value between 0.0-1.0 indicating the encode compression quality to produce the image data. 1.0 resulting in no compression and 0.0 resulting in the maximum compression possible. If not provide, use 1.0. (NSNumber) @note works for `SDImageCoder` */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressionQuality; /** A UIColor(NSColor) value to used for non-alpha image encoding when the input image has alpha channel, the background color will be used to compose the alpha one. If not provide, use white color. @note works for `SDImageCoder` */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeBackgroundColor; /** A CGSize value indicating the max image resolution in pixels during encoding. For vector image, this also effect the output vector data information about width and height. The encoder will not generate the encoded image larger than this limit. Note it always use the aspect ratio of input image.. Defaults to CGSizeZero, which means no max size limit at all. @note Supports for animated image as well. @note The output image's width is limited to pixel size's width, the output image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both. @note works for `SDImageCoder` */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxPixelSize; /** A NSUInteger value specify the max output data bytes size after encoding. Some lossy format like JPEG/HEIF supports the hint for codec to automatically reduce the quality and match the file size you want. Note this option will override the `SDImageCoderEncodeCompressionQuality`, because now the quality is decided by the encoder. (NSNumber) @note This is a hint, no guarantee for output size because of compression algorithm limit. And this options does not works for vector images. @note works for `SDImageCoder` */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxFileSize; /** A Boolean value indicating the encoding format should contains a thumbnail image into the output data. Only some of image format (like JPEG/HEIF/AVIF) support this behavior. The embed thumbnail will be used during next time thumbnail decoding (provided `.thumbnailPixelSize`), which is faster than full image thumbnail decoding. (NSNumber) Defaults to NO, which does not embed any thumbnail. @note The thumbnail image's pixel size is not defined, the encoder can choose the proper pixel size which is suitable for encoding quality. @note works for `SDImageCoder` */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumbnail; /** A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext) This option is ignored for all built-in coders and take no effect. But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only. See `SDWebImageContext` for more detailed information. */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); #pragma mark - Coder /** This is the image coder protocol to provide custom image decoding/encoding. These methods are all required to implement. @note Pay attention that these methods are not called from main queue. */ @protocol SDImageCoder @required #pragma mark - Decoding /** Returns YES if this coder can decode some data. Otherwise, the data should be passed to another coder. @param data The image data so we can look at it @return YES if this coder can decode the data, NO otherwise */ - (BOOL)canDecodeFromData:(nullable NSData *)data; /** Decode the image data to image. @note This protocol may supports decode animated image frames. You can use `+[SDImageCoderHelper animatedImageWithFrames:]` to produce an animated image with frames. @param data The image data to be decoded @param options A dictionary containing any decoding options. Pass @{SDImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for image. Pass @{SDImageCoderDecodeFirstFrameOnly: @(YES)} to decode the first frame only. @return The decoded image from data */ - (nullable UIImage *)decodedImageWithData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options; #pragma mark - Encoding /** Returns YES if this coder can encode some image. Otherwise, it should be passed to another coder. For custom coder which introduce new image format, you'd better define a new `SDImageFormat` using like this. If you're creating public coder plugin for new image format, also update `https://github.com/rs/SDWebImage/wiki/Coder-Plugin-List` to avoid same value been defined twice. * @code static const SDImageFormat SDImageFormatHEIF = 10; * @endcode @param format The image format @return YES if this coder can encode the image, NO otherwise */ - (BOOL)canEncodeToFormat:(SDImageFormat)format NS_SWIFT_NAME(canEncode(to:)); /** Encode the image to image data. @note This protocol may supports encode animated image frames. You can use `+[SDImageCoderHelper framesFromAnimatedImage:]` to assemble an animated image with frames. @param image The image to be encoded @param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible @param options A dictionary containing any encoding options. Pass @{SDImageCoderEncodeCompressionQuality: @(1)} to specify compression quality. @return The encoded image data */ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options; @end #pragma mark - Progressive Coder /** This is the image coder protocol to provide custom progressive image decoding. These methods are all required to implement. @note Pay attention that these methods are not called from main queue. */ @protocol SDProgressiveImageCoder @required /** Returns YES if this coder can incremental decode some data. Otherwise, it should be passed to another coder. @param data The image data so we can look at it @return YES if this coder can decode the data, NO otherwise */ - (BOOL)canIncrementalDecodeFromData:(nullable NSData *)data; /** Because incremental decoding need to keep the decoded context, we will alloc a new instance with the same class for each download operation to avoid conflicts This init method should not return nil @param options A dictionary containing any progressive decoding options (instance-level). Pass @{SDImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for progressive animated image (each frames should use the same scale). @return A new instance to do incremental decoding for the specify image format */ - (nonnull instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options; /** Update the incremental decoding when new image data available @param data The image data has been downloaded so far @param finished Whether the download has finished */ - (void)updateIncrementalData:(nullable NSData *)data finished:(BOOL)finished; /** Incremental decode the current image data to image. @note Due to the performance issue for progressive decoding and the integration for image view. This method may only return the first frame image even if the image data is animated image. If you want progressive animated image decoding, conform to `SDAnimatedImageCoder` protocol as well and use `animatedImageFrameAtIndex:` instead. @param options A dictionary containing any progressive decoding options. Pass @{SDImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for progressive image @return The decoded image from current data */ - (nullable UIImage *)incrementalDecodedImageWithOptions:(nullable SDImageCoderOptions *)options; @end #pragma mark - Animated Image Provider /** This is the animated image protocol to provide the basic function for animated image rendering. It's adopted by `SDAnimatedImage` and `SDAnimatedImageCoder` */ @protocol SDAnimatedImageProvider @required /** The original animated image data for current image. If current image is not an animated format, return nil. We may use this method to grab back the original image data if need, such as NSCoding or compare. @return The animated image data */ @property (nonatomic, copy, readonly, nullable) NSData *animatedImageData; /** Total animated frame count. If the frame count is less than 1, then the methods below will be ignored. @return Total animated frame count. */ @property (nonatomic, assign, readonly) NSUInteger animatedImageFrameCount; /** Animation loop count, 0 means infinite looping. @return Animation loop count */ @property (nonatomic, assign, readonly) NSUInteger animatedImageLoopCount; /** Returns the frame image from a specified index. @note The index maybe randomly if one image was set to different imageViews, keep it re-entrant. (It's not recommend to store the images into array because it's memory consuming) @param index Frame index (zero based). @return Frame's image */ - (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index; /** Returns the frames's duration from a specified index. @note The index maybe randomly if one image was set to different imageViews, keep it re-entrant. (It's recommend to store the durations into array because it's not memory-consuming) @param index Frame index (zero based). @return Frame's duration */ - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index; @end #pragma mark - Animated Coder /** This is the animated image coder protocol for custom animated image class like `SDAnimatedImage`. Through it inherit from `SDImageCoder`. We currentlly only use the method `canDecodeFromData:` to detect the proper coder for specify animated image format. */ @protocol SDAnimatedImageCoder @required /** Because animated image coder should keep the original data, we will alloc a new instance with the same class for the specify animated image data The init method should return nil if it can't decode the specify animated image data to produce any frame. After the instance created, we may call methods in `SDAnimatedImageProvider` to produce animated image frame. @param data The animated image data to be decode @param options A dictionary containing any animated decoding options (instance-level). Pass @{SDImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for animated image (each frames should use the same scale). @return A new instance to do animated decoding for specify image data */ - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCoder.h" SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor"; SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio"; SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize"; SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality"; SDImageCoderOption const SDImageCoderEncodeBackgroundColor = @"encodeBackgroundColor"; SDImageCoderOption const SDImageCoderEncodeMaxPixelSize = @"encodeMaxPixelSize"; SDImageCoderOption const SDImageCoderEncodeMaxFileSize = @"encodeMaxFileSize"; SDImageCoderOption const SDImageCoderEncodeEmbedThumbnail = @"encodeEmbedThumbnail"; SDImageCoderOption const SDImageCoderWebImageContext = @"webImageContext"; ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoderHelper.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDImageFrame.h" /** Provide some common helper methods for building the image decoder/encoder. */ @interface SDImageCoderHelper : NSObject /** Return an animated image with frames array. For UIKit, this will apply the patch and then create animated UIImage. The patch is because that `+[UIImage animatedImageWithImages:duration:]` just use the average of duration for each image. So it will not work if different frame has different duration. Therefore we repeat the specify frame for specify times to let it work. For AppKit, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not) @param frames The frames array. If no frames or frames is empty, return nil @return A animated image for rendering on UIImageView(UIKit) or NSImageView(AppKit) */ + (UIImage * _Nullable)animatedImageWithFrames:(NSArray * _Nullable)frames; /** Return frames array from an animated image. For UIKit, this will unapply the patch for the description above and then create frames array. This will also work for normal animated UIImage. For AppKit, NSImage does not support animates other than GIF. This will try to decode the GIF imageRep and then create frames array. @param animatedImage A animated image. If it's not animated, return nil @return The frames array */ + (NSArray * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage NS_SWIFT_NAME(frames(from:)); /** Return the shared device-dependent RGB color space. This follows The Get Rule. On iOS, it's created with deviceRGB (if available, use sRGB). On macOS, it's from the screen colorspace (if failed, use deviceRGB) Because it's shared, you should not retain or release this object. @return The device-dependent RGB color space */ + (CGColorSpaceRef _Nonnull)colorSpaceGetDeviceRGB CF_RETURNS_NOT_RETAINED; /** Check whether CGImage contains alpha channel. @param cgImage The CGImage @return Return YES if CGImage contains alpha channel, otherwise return NO */ + (BOOL)CGImageContainsAlpha:(_Nonnull CGImageRef)cgImage; /** Create a decoded CGImage by the provided CGImage. This follows The Create Rule and you are response to call release after usage. It will detect whether image contains alpha channel, then create a new bitmap context with the same size of image, and draw it. This can ensure that the image do not need extra decoding after been set to the imageView. @note This actually call `CGImageCreateDecoded:orientation:` with the Up orientation. @param cgImage The CGImage @return A new created decoded image */ + (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage CF_RETURNS_RETAINED; /** Create a decoded CGImage by the provided CGImage and orientation. This follows The Create Rule and you are response to call release after usage. It will detect whether image contains alpha channel, then create a new bitmap context with the same size of image, and draw it. This can ensure that the image do not need extra decoding after been set to the imageView. @param cgImage The CGImage @param orientation The EXIF image orientation. @return A new created decoded image */ + (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation CF_RETURNS_RETAINED; /** Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage. It will detect whether the image size matching the scale size, if not, stretch the image to the target size. @param cgImage The CGImage @param size The scale size in pixel. @return A new created scaled image */ + (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED; /** Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image @param image The image to be decoded @return The decoded image */ + (UIImage * _Nullable)decodedImageWithImage:(UIImage * _Nullable)image; /** Return the decoded and probably scaled down image by the provided image. If the image pixels bytes size large than the limit bytes, will try to scale down. Or just works as `decodedImageWithImage:`, never scale up. @warning You should not pass too small bytes, the suggestion value should be larger than 1MB. Even we use Tile Decoding to avoid OOM, however, small bytes will consume much more CPU time because we need to iterate more times to draw each tile. @param image The image to be decoded and scaled down @param bytes The limit bytes size. Provide 0 to use the build-in limit. @return The decoded and probably scaled down image */ + (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes; /** Control the default limit bytes to scale down largest images. This value must be larger than 4 Bytes (at least 1x1 pixel). Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS. */ @property (class, readwrite) NSUInteger defaultScaleDownLimitBytes; #if SD_UIKIT || SD_WATCH /** Convert an EXIF image orientation to an iOS one. @param exifOrientation EXIF orientation @return iOS orientation */ + (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation NS_SWIFT_NAME(imageOrientation(from:)); /** Convert an iOS orientation to an EXIF image orientation. @param imageOrientation iOS orientation @return EXIF orientation */ + (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation; #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoderHelper.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCoderHelper.h" #import "SDImageFrame.h" #import "NSImage+Compatibility.h" #import "NSData+ImageContentType.h" #import "SDAnimatedImageRep.h" #import "UIImage+ForceDecode.h" #import "SDAssociatedObject.h" #import "UIImage+Metadata.h" #import "SDInternalMacros.h" #import static inline size_t SDByteAlign(size_t size, size_t alignment) { return ((size + (alignment - 1)) / alignment) * alignment; } static const size_t kBytesPerPixel = 4; static const size_t kBitsPerComponent = 8; static const CGFloat kBytesPerMB = 1024.0f * 1024.0f; /* * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set * Suggested value for iPad1 and iPhone 3GS: 60. * Suggested value for iPad2 and iPhone 4: 120. * Suggested value for iPhone 3G and iPod 2 and earlier devices: 30. */ #if SD_MAC static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB; #elif SD_UIKIT static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB; #elif SD_WATCH static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB; #endif static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet. @implementation SDImageCoderHelper + (UIImage *)animatedImageWithFrames:(NSArray *)frames { NSUInteger frameCount = frames.count; if (frameCount == 0) { return nil; } UIImage *animatedImage; #if SD_UIKIT || SD_WATCH NSUInteger durations[frameCount]; for (size_t i = 0; i < frameCount; i++) { durations[i] = frames[i].duration * 1000; } NSUInteger const gcd = gcdArray(frameCount, durations); __block NSUInteger totalDuration = 0; NSMutableArray *animatedImages = [NSMutableArray arrayWithCapacity:frameCount]; [frames enumerateObjectsUsingBlock:^(SDImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) { UIImage *image = frame.image; NSUInteger duration = frame.duration * 1000; totalDuration += duration; NSUInteger repeatCount; if (gcd) { repeatCount = duration / gcd; } else { repeatCount = 1; } for (size_t i = 0; i < repeatCount; ++i) { [animatedImages addObject:image]; } }]; animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f]; #else NSMutableData *imageData = [NSMutableData data]; CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF]; // Create an image destination. GIF does not support EXIF image orientation CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL); if (!imageDestination) { // Handle failure. return nil; } for (size_t i = 0; i < frameCount; i++) { @autoreleasepool { SDImageFrame *frame = frames[i]; NSTimeInterval frameDuration = frame.duration; CGImageRef frameImageRef = frame.image.CGImage; NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}}; CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties); } } // Finalize the destination. if (CGImageDestinationFinalize(imageDestination) == NO) { // Handle failure. CFRelease(imageDestination); return nil; } CFRelease(imageDestination); CGFloat scale = MAX(frames.firstObject.image.scale, 1); SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData]; NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); imageRep.size = size; animatedImage = [[NSImage alloc] initWithSize:size]; [animatedImage addRepresentation:imageRep]; #endif return animatedImage; } + (NSArray *)framesFromAnimatedImage:(UIImage *)animatedImage { if (!animatedImage) { return nil; } NSMutableArray *frames = [NSMutableArray array]; NSUInteger frameCount = 0; #if SD_UIKIT || SD_WATCH NSArray *animatedImages = animatedImage.images; frameCount = animatedImages.count; if (frameCount == 0) { return nil; } NSTimeInterval avgDuration = animatedImage.duration / frameCount; if (avgDuration == 0) { avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit) } __block NSUInteger index = 0; __block NSUInteger repeatCount = 1; __block UIImage *previousImage = animatedImages.firstObject; [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) { // ignore first if (idx == 0) { return; } if ([image isEqual:previousImage]) { repeatCount++; } else { SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount]; [frames addObject:frame]; repeatCount = 1; index++; } previousImage = image; // last one if (idx == frameCount - 1) { SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount]; [frames addObject:frame]; } }]; #else NSRect imageRect = NSMakeRect(0, 0, animatedImage.size.width, animatedImage.size.height); NSImageRep *imageRep = [animatedImage bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapImageRep = (NSBitmapImageRep *)imageRep; } if (!bitmapImageRep) { return nil; } frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue]; if (frameCount == 0) { return nil; } CGFloat scale = animatedImage.scale; for (size_t i = 0; i < frameCount; i++) { @autoreleasepool { // NSBitmapImageRep need to manually change frame. "Good taste" API [bitmapImageRep setProperty:NSImageCurrentFrame withValue:@(i)]; NSTimeInterval frameDuration = [[bitmapImageRep valueForProperty:NSImageCurrentFrameDuration] doubleValue]; NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapImageRep.CGImage scale:scale orientation:kCGImagePropertyOrientationUp]; SDImageFrame *frame = [SDImageFrame frameWithImage:frameImage duration:frameDuration]; [frames addObject:frame]; } } #endif return frames; } + (CGColorSpaceRef)colorSpaceGetDeviceRGB { #if SD_MAC CGColorSpaceRef screenColorSpace = NSScreen.mainScreen.colorSpace.CGColorSpace; if (screenColorSpace) { return screenColorSpace; } #endif static CGColorSpaceRef colorSpace; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ #if SD_UIKIT if (@available(iOS 9.0, tvOS 9.0, *)) { colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } else { colorSpace = CGColorSpaceCreateDeviceRGB(); } #else colorSpace = CGColorSpaceCreateDeviceRGB(); #endif }); return colorSpace; } + (BOOL)CGImageContainsAlpha:(CGImageRef)cgImage { if (!cgImage) { return NO; } CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage); BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); return hasAlpha; } + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage { return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp]; } + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation { if (!cgImage) { return NULL; } size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); if (width == 0 || height == 0) return NULL; size_t newWidth; size_t newHeight; switch (orientation) { case kCGImagePropertyOrientationLeft: case kCGImagePropertyOrientationLeftMirrored: case kCGImagePropertyOrientationRight: case kCGImagePropertyOrientationRightMirrored: { // These orientation should swap width & height newWidth = height; newHeight = width; } break; default: { newWidth = width; newHeight = height; } break; } BOOL hasAlpha = [self CGImageContainsAlpha:cgImage]; // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]` // Though you can use any supported bitmapInfo (see: https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB ) and let Core Graphics reorder it when you call `CGContextDrawImage` // But since our build-in coders use this bitmapInfo, this can have a little performance benefit CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo); if (!context) { return NULL; } // Apply transform CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight)); CGContextConcatCTM(context, transform); CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height CGImageRef newImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); return newImageRef; } + (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size { if (!cgImage) { return NULL; } size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); if (width == size.width && height == size.height) { CGImageRetain(cgImage); return cgImage; } __block vImage_Buffer input_buffer = {}, output_buffer = {}; @onExit { if (input_buffer.data) free(input_buffer.data); if (output_buffer.data) free(output_buffer.data); }; BOOL hasAlpha = [self CGImageContainsAlpha:cgImage]; // iOS display alpha info (BGRA8888/BGRX8888) CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; vImage_CGImageFormat format = (vImage_CGImageFormat) { .bitsPerComponent = 8, .bitsPerPixel = 32, .colorSpace = NULL, .bitmapInfo = bitmapInfo, .version = 0, .decode = NULL, .renderingIntent = kCGRenderingIntentDefault, }; vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags); if (a_ret != kvImageNoError) return NULL; output_buffer.width = MAX(size.width, 0); output_buffer.height = MAX(size.height, 0); output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64); output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height); if (!output_buffer.data) return NULL; vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling); if (ret != kvImageNoError) return NULL; CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret); if (ret != kvImageNoError) { CGImageRelease(outputImage); return NULL; } return outputImage; } + (UIImage *)decodedImageWithImage:(UIImage *)image { if (![self shouldDecodeImage:image]) { return image; } CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage]; if (!imageRef) { return image; } #if SD_MAC UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation]; #endif CGImageRelease(imageRef); SDImageCopyAssociatedObject(image, decodedImage); decodedImage.sd_isDecoded = YES; return decodedImage; } + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes { if (![self shouldDecodeImage:image]) { return image; } if (![self shouldScaleDownImage:image limitBytes:bytes]) { return [self decodedImageWithImage:image]; } CGFloat destTotalPixels; CGFloat tileTotalPixels; if (bytes == 0) { bytes = kDestImageLimitBytes; } destTotalPixels = bytes / kBytesPerPixel; tileTotalPixels = destTotalPixels / 3; CGContextRef destContext; // autorelease the bitmap context and all vars to help system to free memory when there are memory warning. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory]; @autoreleasepool { CGImageRef sourceImageRef = image.CGImage; CGSize sourceResolution = CGSizeZero; sourceResolution.width = CGImageGetWidth(sourceImageRef); sourceResolution.height = CGImageGetHeight(sourceImageRef); CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height; // Determine the scale ratio to apply to the input image // that results in an output image of the defined size. // see kDestImageSizeMB, and how it relates to destTotalPixels. CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels); CGSize destResolution = CGSizeZero; destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale)); destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale)); // device color space CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB]; BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef]; // iOS display alpha info (BGRA8888/BGRX8888) CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; // kCGImageAlphaNone is not supported in CGBitmapContextCreate. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst // to create bitmap graphics contexts without alpha info. destContext = CGBitmapContextCreate(NULL, destResolution.width, destResolution.height, kBitsPerComponent, 0, colorspaceRef, bitmapInfo); if (destContext == NULL) { return image; } CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh); // Now define the size of the rectangle to be used for the // incremental bits from the input image to the output image. // we use a source tile width equal to the width of the source // image due to the way that iOS retrieves image data from disk. // iOS must decode an image from disk in full width 'bands', even // if current graphics context is clipped to a subrect within that // band. Therefore we fully utilize all of the pixel data that results // from a decoding operation by anchoring our tile size to the full // width of the input image. CGRect sourceTile = CGRectZero; sourceTile.size.width = sourceResolution.width; // The source tile height is dynamic. Since we specified the size // of the source tile in MB, see how many rows of pixels high it // can be given the input image width. sourceTile.size.height = MAX(1, (int)(tileTotalPixels / sourceTile.size.width)); sourceTile.origin.x = 0.0f; // The output tile is the same proportions as the input tile, but // scaled to image scale. CGRect destTile; destTile.size.width = destResolution.width; destTile.size.height = sourceTile.size.height * imageScale; destTile.origin.x = 0.0f; // The source seem overlap is proportionate to the destination seem overlap. // this is the amount of pixels to overlap each tile as we assemble the output image. float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height); CGImageRef sourceTileImageRef; // calculate the number of read/write operations required to assemble the // output image. int iterations = (int)( sourceResolution.height / sourceTile.size.height ); // If tile height doesn't divide the image height evenly, add another iteration // to account for the remaining pixels. int remainder = (int)sourceResolution.height % (int)sourceTile.size.height; if(remainder) { iterations++; } // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations. float sourceTileHeightMinusOverlap = sourceTile.size.height; sourceTile.size.height += sourceSeemOverlap; destTile.size.height += kDestSeemOverlap; for( int y = 0; y < iterations; ++y ) { @autoreleasepool { sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap; destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap); sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile ); if( y == iterations - 1 && remainder ) { float dify = destTile.size.height; destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale; dify -= destTile.size.height; destTile.origin.y += dify; } CGContextDrawImage( destContext, destTile, sourceTileImageRef ); CGImageRelease( sourceTileImageRef ); } } CGImageRef destImageRef = CGBitmapContextCreateImage(destContext); CGContextRelease(destContext); if (destImageRef == NULL) { return image; } #if SD_MAC UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation]; #endif CGImageRelease(destImageRef); if (destImage == nil) { return image; } SDImageCopyAssociatedObject(image, destImage); destImage.sd_isDecoded = YES; return destImage; } } + (NSUInteger)defaultScaleDownLimitBytes { return kDestImageLimitBytes; } + (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes { if (defaultScaleDownLimitBytes < kBytesPerPixel) { return; } kDestImageLimitBytes = defaultScaleDownLimitBytes; } #if SD_UIKIT || SD_WATCH // Convert an EXIF image orientation to an iOS one. + (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation { UIImageOrientation imageOrientation = UIImageOrientationUp; switch (exifOrientation) { case kCGImagePropertyOrientationUp: imageOrientation = UIImageOrientationUp; break; case kCGImagePropertyOrientationDown: imageOrientation = UIImageOrientationDown; break; case kCGImagePropertyOrientationLeft: imageOrientation = UIImageOrientationLeft; break; case kCGImagePropertyOrientationRight: imageOrientation = UIImageOrientationRight; break; case kCGImagePropertyOrientationUpMirrored: imageOrientation = UIImageOrientationUpMirrored; break; case kCGImagePropertyOrientationDownMirrored: imageOrientation = UIImageOrientationDownMirrored; break; case kCGImagePropertyOrientationLeftMirrored: imageOrientation = UIImageOrientationLeftMirrored; break; case kCGImagePropertyOrientationRightMirrored: imageOrientation = UIImageOrientationRightMirrored; break; default: break; } return imageOrientation; } // Convert an iOS orientation to an EXIF image orientation. + (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation { CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp; switch (imageOrientation) { case UIImageOrientationUp: exifOrientation = kCGImagePropertyOrientationUp; break; case UIImageOrientationDown: exifOrientation = kCGImagePropertyOrientationDown; break; case UIImageOrientationLeft: exifOrientation = kCGImagePropertyOrientationLeft; break; case UIImageOrientationRight: exifOrientation = kCGImagePropertyOrientationRight; break; case UIImageOrientationUpMirrored: exifOrientation = kCGImagePropertyOrientationUpMirrored; break; case UIImageOrientationDownMirrored: exifOrientation = kCGImagePropertyOrientationDownMirrored; break; case UIImageOrientationLeftMirrored: exifOrientation = kCGImagePropertyOrientationLeftMirrored; break; case UIImageOrientationRightMirrored: exifOrientation = kCGImagePropertyOrientationRightMirrored; break; default: break; } return exifOrientation; } #endif #pragma mark - Helper Function + (BOOL)shouldDecodeImage:(nullable UIImage *)image { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error if (image == nil) { return NO; } // Avoid extra decode if (image.sd_isDecoded) { return NO; } // do not decode animated images if (image.sd_isAnimated) { return NO; } // do not decode vector images if (image.sd_isVector) { return NO; } return YES; } + (BOOL)shouldScaleDownImage:(nonnull UIImage *)image limitBytes:(NSUInteger)bytes { BOOL shouldScaleDown = YES; CGImageRef sourceImageRef = image.CGImage; CGSize sourceResolution = CGSizeZero; sourceResolution.width = CGImageGetWidth(sourceImageRef); sourceResolution.height = CGImageGetHeight(sourceImageRef); float sourceTotalPixels = sourceResolution.width * sourceResolution.height; if (sourceTotalPixels <= 0) { return NO; } CGFloat destTotalPixels; if (bytes == 0) { bytes = [self defaultScaleDownLimitBytes]; } bytes = MAX(bytes, kBytesPerPixel); destTotalPixels = bytes / kBytesPerPixel; float imageScale = destTotalPixels / sourceTotalPixels; if (imageScale < 1) { shouldScaleDown = YES; } else { shouldScaleDown = NO; } return shouldScaleDown; } static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) { // Inspiration from @libfeihu // We need to calculate the proper transformation to make the image upright. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. CGAffineTransform transform = CGAffineTransformIdentity; switch (orientation) { case kCGImagePropertyOrientationDown: case kCGImagePropertyOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, size.width, size.height); transform = CGAffineTransformRotate(transform, M_PI); break; case kCGImagePropertyOrientationLeft: case kCGImagePropertyOrientationLeftMirrored: transform = CGAffineTransformTranslate(transform, size.width, 0); transform = CGAffineTransformRotate(transform, M_PI_2); break; case kCGImagePropertyOrientationRight: case kCGImagePropertyOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, 0, size.height); transform = CGAffineTransformRotate(transform, -M_PI_2); break; case kCGImagePropertyOrientationUp: case kCGImagePropertyOrientationUpMirrored: break; } switch (orientation) { case kCGImagePropertyOrientationUpMirrored: case kCGImagePropertyOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, size.width, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case kCGImagePropertyOrientationLeftMirrored: case kCGImagePropertyOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, size.height, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case kCGImagePropertyOrientationUp: case kCGImagePropertyOrientationDown: case kCGImagePropertyOrientationLeft: case kCGImagePropertyOrientationRight: break; } return transform; } #if SD_UIKIT || SD_WATCH static NSUInteger gcd(NSUInteger a, NSUInteger b) { NSUInteger c; while (a != 0) { c = a; a = b % a; b = c; } return b; } static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) { if (count == 0) { return 0; } NSUInteger result = values[0]; for (size_t i = 1; i < count; ++i) { result = gcd(values[i], result); } return result; } #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCodersManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageCoder.h" /** Global object holding the array of coders, so that we avoid passing them from object to object. Uses a priority queue behind scenes, which means the latest added coders have the highest priority. This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data. That way, users can add their custom coders while preserving our existing prebuilt ones Note: the `coders` getter will return the coders in their reversed order Example: - by default we internally set coders = `IOCoder`, `GIFCoder`, `APNGCoder` - calling `coders` will return `@[IOCoder, GIFCoder, APNGCoder]` - call `[addCoder:[MyCrazyCoder new]]` - calling `coders` now returns `@[IOCoder, GIFCoder, APNGCoder, MyCrazyCoder]` Coders ------ A coder must conform to the `SDImageCoder` protocol or even to `SDProgressiveImageCoder` if it supports progressive decoding Conformance is important because that way, they will implement `canDecodeFromData` or `canEncodeToFormat` Those methods are called on each coder in the array (using the priority order) until one of them returns YES. That means that coder can decode that data / encode to that format */ @interface SDImageCodersManager : NSObject /** Returns the global shared coders manager instance. */ @property (nonatomic, class, readonly, nonnull) SDImageCodersManager *sharedManager; /** All coders in coders manager. The coders array is a priority queue, which means the later added coder will have the highest priority */ @property (nonatomic, copy, nullable) NSArray> *coders; /** Add a new coder to the end of coders array. Which has the highest priority. @param coder coder */ - (void)addCoder:(nonnull id)coder; /** Remove a coder in the coders array. @param coder coder */ - (void)removeCoder:(nonnull id)coder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCodersManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCodersManager.h" #import "SDImageIOCoder.h" #import "SDImageGIFCoder.h" #import "SDImageAPNGCoder.h" #import "SDImageHEICCoder.h" #import "SDInternalMacros.h" @interface SDImageCodersManager () @property (nonatomic, strong, nonnull) NSMutableArray> *imageCoders; @end @implementation SDImageCodersManager { SD_LOCK_DECLARE(_codersLock); } + (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (instancetype)init { if (self = [super init]) { // initialize with default coders _imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]]; SD_LOCK_INIT(_codersLock); } return self; } - (NSArray> *)coders { SD_LOCK(_codersLock); NSArray> *coders = [_imageCoders copy]; SD_UNLOCK(_codersLock); return coders; } - (void)setCoders:(NSArray> *)coders { SD_LOCK(_codersLock); [_imageCoders removeAllObjects]; if (coders.count) { [_imageCoders addObjectsFromArray:coders]; } SD_UNLOCK(_codersLock); } #pragma mark - Coder IO operations - (void)addCoder:(nonnull id)coder { if (![coder conformsToProtocol:@protocol(SDImageCoder)]) { return; } SD_LOCK(_codersLock); [_imageCoders addObject:coder]; SD_UNLOCK(_codersLock); } - (void)removeCoder:(nonnull id)coder { if (![coder conformsToProtocol:@protocol(SDImageCoder)]) { return; } SD_LOCK(_codersLock); [_imageCoders removeObject:coder]; SD_UNLOCK(_codersLock); } #pragma mark - SDImageCoder - (BOOL)canDecodeFromData:(NSData *)data { NSArray> *coders = self.coders; for (id coder in coders.reverseObjectEnumerator) { if ([coder canDecodeFromData:data]) { return YES; } } return NO; } - (BOOL)canEncodeToFormat:(SDImageFormat)format { NSArray> *coders = self.coders; for (id coder in coders.reverseObjectEnumerator) { if ([coder canEncodeToFormat:format]) { return YES; } } return NO; } - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options { if (!data) { return nil; } UIImage *image; NSArray> *coders = self.coders; for (id coder in coders.reverseObjectEnumerator) { if ([coder canDecodeFromData:data]) { image = [coder decodedImageWithData:data options:options]; break; } } return image; } - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options { if (!image) { return nil; } NSArray> *coders = self.coders; for (id coder in coders.reverseObjectEnumerator) { if ([coder canEncodeToFormat:format]) { return [coder encodedDataWithImage:image format:format options:options]; } } return nil; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageFrame.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /** This class is used for creating animated images via `animatedImageWithFrames` in `SDImageCoderHelper`. @note If you need to specify animated images loop count, use `sd_imageLoopCount` property in `UIImage+Metadata.h`. */ @interface SDImageFrame : NSObject /** The image of current frame. You should not set an animated image. */ @property (nonatomic, strong, readonly, nonnull) UIImage *image; /** The duration of current frame to be displayed. The number is seconds but not milliseconds. You should not set this to zero. */ @property (nonatomic, readonly, assign) NSTimeInterval duration; /** Create a frame instance with specify image and duration @param image current frame's image @param duration current frame's duration @return frame instance */ + (instancetype _Nonnull)frameWithImage:(UIImage * _Nonnull)image duration:(NSTimeInterval)duration; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageFrame.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageFrame.h" @interface SDImageFrame () @property (nonatomic, strong, readwrite, nonnull) UIImage *image; @property (nonatomic, readwrite, assign) NSTimeInterval duration; @end @implementation SDImageFrame + (instancetype)frameWithImage:(UIImage *)image duration:(NSTimeInterval)duration { SDImageFrame *frame = [[SDImageFrame alloc] init]; frame.image = image; frame.duration = duration; return frame; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageGIFCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageIOAnimatedCoder.h" /** Built in coder using ImageIO that supports animated GIF encoding/decoding @note `SDImageIOCoder` supports GIF but only as static (will use the 1st frame). @note Use `SDImageGIFCoder` for fully animated GIFs. For `UIImageView`, it will produce animated `UIImage`(`NSImage` on macOS) for rendering. For `SDAnimatedImageView`, it will use `SDAnimatedImage` for rendering. @note The recommended approach for animated GIFs is using `SDAnimatedImage` with `SDAnimatedImageView`. It's more performant than `UIImageView` for GIF displaying(especially on memory usage) */ @interface SDImageGIFCoder : SDImageIOAnimatedCoder @property (nonatomic, class, readonly, nonnull) SDImageGIFCoder *sharedCoder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageGIFCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageGIFCoder.h" #if SD_MAC #import #else #import #endif @implementation SDImageGIFCoder + (instancetype)sharedCoder { static SDImageGIFCoder *coder; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coder = [[SDImageGIFCoder alloc] init]; }); return coder; } #pragma mark - Subclass Override + (SDImageFormat)imageFormat { return SDImageFormatGIF; } + (NSString *)imageUTType { return (__bridge NSString *)kUTTypeGIF; } + (NSString *)dictionaryProperty { return (__bridge NSString *)kCGImagePropertyGIFDictionary; } + (NSString *)unclampedDelayTimeProperty { return (__bridge NSString *)kCGImagePropertyGIFUnclampedDelayTime; } + (NSString *)delayTimeProperty { return (__bridge NSString *)kCGImagePropertyGIFDelayTime; } + (NSString *)loopCountProperty { return (__bridge NSString *)kCGImagePropertyGIFLoopCount; } + (NSUInteger)defaultLoopCount { return 1; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageGraphics.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import /** These following graphics context method are provided to easily write cross-platform(AppKit/UIKit) code. For UIKit, these methods just call the same method in `UIGraphics.h`. See the documentation for usage. For AppKit, these methods use `NSGraphicsContext` to create image context and match the behavior like UIKit. @note If you don't care bitmap format (ARGB8888) and just draw image, use `SDGraphicsImageRenderer` instead. It's more performant on RAM usage.` */ /// Returns the current graphics context. FOUNDATION_EXPORT CGContextRef __nullable SDGraphicsGetCurrentContext(void) CF_RETURNS_NOT_RETAINED; /// Creates a bitmap-based graphics context and makes it the current context. FOUNDATION_EXPORT void SDGraphicsBeginImageContext(CGSize size); /// Creates a bitmap-based graphics context with the specified options. FOUNDATION_EXPORT void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale); /// Removes the current bitmap-based graphics context from the top of the stack. FOUNDATION_EXPORT void SDGraphicsEndImageContext(void); /// Returns an image based on the contents of the current bitmap-based graphics context. FOUNDATION_EXPORT UIImage * __nullable SDGraphicsGetImageFromCurrentImageContext(void); ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageGraphics.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageGraphics.h" #import "NSImage+Compatibility.h" #import "objc/runtime.h" #if SD_MAC static void *kNSGraphicsContextScaleFactorKey; static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) { if (scale == 0) { // Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the device’s main screen if scale is 0. scale = [NSScreen mainScreen].backingScaleFactor; } size_t width = ceil(size.width * scale); size_t height = ceil(size.height * scale); if (width < 1 || height < 1) return NULL; //pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo); CGColorSpaceRelease(space); if (!context) { return NULL; } CGContextScaleCTM(context, scale, scale); return context; } #endif CGContextRef SDGraphicsGetCurrentContext(void) { #if SD_UIKIT || SD_WATCH return UIGraphicsGetCurrentContext(); #else return NSGraphicsContext.currentContext.CGContext; #endif } void SDGraphicsBeginImageContext(CGSize size) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContext(size); #else SDGraphicsBeginImageContextWithOptions(size, NO, 1.0); #endif } void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContextWithOptions(size, opaque, scale); #else CGContextRef context = SDCGContextCreateBitmapContext(size, opaque, scale); if (!context) { return; } NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]; objc_setAssociatedObject(graphicsContext, &kNSGraphicsContextScaleFactorKey, @(scale), OBJC_ASSOCIATION_RETAIN); CGContextRelease(context); [NSGraphicsContext saveGraphicsState]; NSGraphicsContext.currentContext = graphicsContext; #endif } void SDGraphicsEndImageContext(void) { #if SD_UIKIT || SD_WATCH UIGraphicsEndImageContext(); #else [NSGraphicsContext restoreGraphicsState]; #endif } UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { #if SD_UIKIT || SD_WATCH return UIGraphicsGetImageFromCurrentImageContext(); #else NSGraphicsContext *context = NSGraphicsContext.currentContext; CGContextRef contextRef = context.CGContext; if (!contextRef) { return nil; } CGImageRef imageRef = CGBitmapContextCreateImage(contextRef); if (!imageRef) { return nil; } CGFloat scale = 0; NSNumber *scaleFactor = objc_getAssociatedObject(context, &kNSGraphicsContextScaleFactorKey); if ([scaleFactor isKindOfClass:[NSNumber class]]) { scale = scaleFactor.doubleValue; } if (!scale) { // reset to the scale factor of the device’s main screen if scale is 0. scale = [NSScreen mainScreen].backingScaleFactor; } NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp]; CGImageRelease(imageRef); return image; #endif } ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageHEICCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageIOAnimatedCoder.h" /** This coder is used for HEIC (HEIF with HEVC container codec) image format. Image/IO provide the static HEIC (.heic) support in iOS 11/macOS 10.13/tvOS 11/watchOS 4+. Image/IO provide the animated HEIC (.heics) support in iOS 13/macOS 10.15/tvOS 13/watchOS 6+. See https://nokiatech.github.io/heif/technical.html for the standard. @note This coder is not in the default coder list for now, since HEIC animated image is really rare, and Apple's implementation still contains performance issues. You can enable if you need this. @note If you need to support lower firmware version for HEIF, you can have a try at https://github.com/SDWebImage/SDWebImageHEIFCoder */ API_AVAILABLE(ios(13.0), tvos(13.0), macos(10.15), watchos(6.0)) @interface SDImageHEICCoder : SDImageIOAnimatedCoder @property (nonatomic, class, readonly, nonnull) SDImageHEICCoder *sharedCoder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageHEICCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageHEICCoder.h" #import "SDImageIOAnimatedCoderInternal.h" // These constants are available from iOS 13+ and Xcode 11. This raw value is used for toolchain and firmware compatibility static NSString * kSDCGImagePropertyHEICSDictionary = @"{HEICS}"; static NSString * kSDCGImagePropertyHEICSLoopCount = @"LoopCount"; static NSString * kSDCGImagePropertyHEICSDelayTime = @"DelayTime"; static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTime"; @implementation SDImageHEICCoder + (void)initialize { if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) { // Use SDK instead of raw value kSDCGImagePropertyHEICSDictionary = (__bridge NSString *)kCGImagePropertyHEICSDictionary; kSDCGImagePropertyHEICSLoopCount = (__bridge NSString *)kCGImagePropertyHEICSLoopCount; kSDCGImagePropertyHEICSDelayTime = (__bridge NSString *)kCGImagePropertyHEICSDelayTime; kSDCGImagePropertyHEICSUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyHEICSUnclampedDelayTime; } } + (instancetype)sharedCoder { static SDImageHEICCoder *coder; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coder = [[SDImageHEICCoder alloc] init]; }); return coder; } #pragma mark - SDImageCoder - (BOOL)canDecodeFromData:(nullable NSData *)data { switch ([NSData sd_imageFormatForImageData:data]) { case SDImageFormatHEIC: // Check HEIC decoding compatibility return [self.class canDecodeFromFormat:SDImageFormatHEIC]; case SDImageFormatHEIF: // Check HEIF decoding compatibility return [self.class canDecodeFromFormat:SDImageFormatHEIF]; default: return NO; } } - (BOOL)canIncrementalDecodeFromData:(NSData *)data { return [self canDecodeFromData:data]; } - (BOOL)canEncodeToFormat:(SDImageFormat)format { switch (format) { case SDImageFormatHEIC: // Check HEIC encoding compatibility return [self.class canEncodeToFormat:SDImageFormatHEIC]; case SDImageFormatHEIF: // Check HEIF encoding compatibility return [self.class canEncodeToFormat:SDImageFormatHEIF]; default: return NO; } } #pragma mark - Subclass Override + (SDImageFormat)imageFormat { return SDImageFormatHEIC; } + (NSString *)imageUTType { return (__bridge NSString *)kSDUTTypeHEIC; } + (NSString *)dictionaryProperty { return kSDCGImagePropertyHEICSDictionary; } + (NSString *)unclampedDelayTimeProperty { return kSDCGImagePropertyHEICSUnclampedDelayTime; } + (NSString *)delayTimeProperty { return kSDCGImagePropertyHEICSDelayTime; } + (NSString *)loopCountProperty { return kSDCGImagePropertyHEICSLoopCount; } + (NSUInteger)defaultLoopCount { return 0; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageIOAnimatedCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import #import "SDImageCoder.h" /** This is the abstract class for all animated coder, which use the Image/IO API. You can not use this directly as real coders. A exception will be raised if you use this class. All of the properties need the subclass to implement and works as expected. For Image/IO, See Apple's documentation: https://developer.apple.com/documentation/imageio */ @interface SDImageIOAnimatedCoder : NSObject #pragma mark - Subclass Override /** The supported animated image format. Such as `SDImageFormatGIF`. @note Subclass override. */ @property (class, readonly) SDImageFormat imageFormat; /** The supported image format UTI Type. Such as `kUTTypeGIF`. This can be used for cases when we can not detect `SDImageFormat. Such as progressive decoding's hint format `kCGImageSourceTypeIdentifierHint`. @note Subclass override. */ @property (class, readonly, nonnull) NSString *imageUTType; /** The image container property key used in Image/IO API. Such as `kCGImagePropertyGIFDictionary`. @note Subclass override. */ @property (class, readonly, nonnull) NSString *dictionaryProperty; /** The image unclamped delay time property key used in Image/IO API. Such as `kCGImagePropertyGIFUnclampedDelayTime` @note Subclass override. */ @property (class, readonly, nonnull) NSString *unclampedDelayTimeProperty; /** The image delay time property key used in Image/IO API. Such as `kCGImagePropertyGIFDelayTime`. @note Subclass override. */ @property (class, readonly, nonnull) NSString *delayTimeProperty; /** The image loop count property key used in Image/IO API. Such as `kCGImagePropertyGIFLoopCount`. @note Subclass override. */ @property (class, readonly, nonnull) NSString *loopCountProperty; /** The default loop count when there are no any loop count information inside image container metadata. For example, for GIF format, the standard use 1 (play once). For APNG format, the standard use 0 (infinity loop). @note Subclass override. */ @property (class, readonly) NSUInteger defaultLoopCount; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageIOAnimatedCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageIOAnimatedCoder.h" #import "NSImage+Compatibility.h" #import "UIImage+Metadata.h" #import "NSData+ImageContentType.h" #import "SDImageCoderHelper.h" #import "SDAnimatedImageRep.h" #import "UIImage+ForceDecode.h" // Specify DPI for vector format in CGImageSource, like PDF static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI"; // Specify File Size for lossy format encoding, like JPEG static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; @interface SDImageIOCoderFrame : NSObject @property (nonatomic, assign) NSUInteger index; // Frame index (zero based) @property (nonatomic, assign) NSTimeInterval duration; // Frame duration in seconds @end @implementation SDImageIOCoderFrame @end @implementation SDImageIOAnimatedCoder { size_t _width, _height; CGImageSourceRef _imageSource; NSData *_imageData; CGFloat _scale; NSUInteger _loopCount; NSUInteger _frameCount; NSArray *_frames; BOOL _finished; BOOL _preserveAspectRatio; CGSize _thumbnailSize; } - (void)dealloc { if (_imageSource) { CFRelease(_imageSource); _imageSource = NULL; } #if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } - (void)didReceiveMemoryWarning:(NSNotification *)notification { if (_imageSource) { for (size_t i = 0; i < _frameCount; i++) { CGImageSourceRemoveCacheAtIndex(_imageSource, i); } } } #pragma mark - Subclass Override + (SDImageFormat)imageFormat { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)imageUTType { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)dictionaryProperty { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)unclampedDelayTimeProperty { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)delayTimeProperty { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)loopCountProperty { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSUInteger)defaultLoopCount { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)] userInfo:nil]; } #pragma mark - Utils + (BOOL)canDecodeFromFormat:(SDImageFormat)format { static dispatch_once_t onceToken; static NSSet *imageUTTypeSet; dispatch_once(&onceToken, ^{ NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageSourceCopyTypeIdentifiers(); imageUTTypeSet = [NSSet setWithArray:imageUTTypes]; }); CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) { // Can decode from target format return YES; } return NO; } + (BOOL)canEncodeToFormat:(SDImageFormat)format { static dispatch_once_t onceToken; static NSSet *imageUTTypeSet; dispatch_once(&onceToken, ^{ NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageDestinationCopyTypeIdentifiers(); imageUTTypeSet = [NSSet setWithArray:imageUTTypes]; }); CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) { // Can encode to target format return YES; } return NO; } + (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source { NSUInteger loopCount = self.defaultLoopCount; NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, NULL); NSDictionary *containerProperties = imageProperties[self.dictionaryProperty]; if (containerProperties) { NSNumber *containerLoopCount = containerProperties[self.loopCountProperty]; if (containerLoopCount != nil) { loopCount = containerLoopCount.unsignedIntegerValue; } } return loopCount; } + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { NSDictionary *options = @{ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES), (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage }; NSTimeInterval frameDuration = 0.1; CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options); if (!cfFrameProperties) { return frameDuration; } NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; NSDictionary *containerProperties = frameProperties[self.dictionaryProperty]; NSNumber *delayTimeUnclampedProp = containerProperties[self.unclampedDelayTimeProperty]; if (delayTimeUnclampedProp != nil) { frameDuration = [delayTimeUnclampedProp doubleValue]; } else { NSNumber *delayTimeProp = containerProperties[self.delayTimeProperty]; if (delayTimeProp != nil) { frameDuration = [delayTimeProp doubleValue]; } } // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify // a duration of <= 10 ms. See and // for more information. if (frameDuration < 0.011) { frameDuration = 0.1; } CFRelease(cfFrameProperties); return frameDuration; } + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options { // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :) // Parse the image properties NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options); NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue]; NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue]; CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue]; if (!exifOrientation) { exifOrientation = kCGImagePropertyOrientationUp; } CFStringRef uttype = CGImageSourceGetType(source); // Check vector format BOOL isVector = NO; if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) { isVector = YES; } NSMutableDictionary *decodingOptions; if (options) { decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options]; } else { decodingOptions = [NSMutableDictionary dictionary]; } CGImageRef imageRef; BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height); if (createFullImage) { if (isVector) { if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { // Provide the default pixel count for vector images, simply just use the screen size #if SD_WATCH thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size; #elif SD_UIKIT thumbnailSize = UIScreen.mainScreen.bounds.size; #elif SD_MAC thumbnailSize = NSScreen.mainScreen.frame.size; #endif } CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); NSUInteger DPIPerPixel = 2; NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel; decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI); } imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); } else { decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); CGFloat maxPixelSize; if (preserveAspectRatio) { CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height; if (pixelRatio > thumbnailRatio) { maxPixelSize = thumbnailSize.width; } else { maxPixelSize = thumbnailSize.height; } } else { maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); } decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize); decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES); imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); } if (!imageRef) { return nil; } // Thumbnail image post-process if (!createFullImage) { if (preserveAspectRatio) { // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice exifOrientation = kCGImagePropertyOrientationUp; } else { // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize]; CGImageRelease(imageRef); imageRef = scaledImageRef; } } #if SD_UIKIT || SD_WATCH UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation]; UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation]; #endif CGImageRelease(imageRef); return image; } #pragma mark - Decode - (BOOL)canDecodeFromData:(nullable NSData *)data { return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat); } - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options { if (!data) { return nil; } CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } CGSize thumbnailSize = CGSizeZero; NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; if (thumbnailSizeValue != nil) { #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif } BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } #if SD_MAC // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG) // Which decode frames in time and reduce memory usage if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); imageRep.size = size; NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; [animatedImage addRepresentation:imageRep]; return animatedImage; } #endif CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); if (!source) { return nil; } size_t count = CGImageSourceGetCount(source); UIImage *animatedImage; BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue]; if (decodeFirstFrame || count <= 1) { animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil]; } else { NSMutableArray *frames = [NSMutableArray array]; for (size_t i = 0; i < count; i++) { UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil]; if (!image) { continue; } NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source]; SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; [frames addObject:frame]; } NSUInteger loopCount = [self.class imageLoopCountWithSource:source]; animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames]; animatedImage.sd_imageLoopCount = loopCount; } animatedImage.sd_imageFormat = self.class.imageFormat; CFRelease(source); return animatedImage; } #pragma mark - Progressive Decode - (BOOL)canIncrementalDecodeFromData:(NSData *)data { return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat); } - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options { self = [super init]; if (self) { NSString *imageUTType = self.class.imageUTType; _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType}); CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } _scale = scale; CGSize thumbnailSize = CGSizeZero; NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; if (thumbnailSizeValue != nil) { #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif } _thumbnailSize = thumbnailSize; BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } _preserveAspectRatio = preserveAspectRatio; #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; } - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished { if (_finished) { return; } _imageData = data; _finished = finished; // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/ // Thanks to the author @Nyx0uf // Update the data source, we must pass ALL the data, not just the new bytes CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished); if (_width + _height == 0) { NSDictionary *options = @{ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES), (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage }; CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options); if (properties) { CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); if (val) CFNumberGetValue(val, kCFNumberLongType, &_height); val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); if (val) CFNumberGetValue(val, kCFNumberLongType, &_width); CFRelease(properties); } } // For animated image progressive decoding because the frame count and duration may be changed. [self scanAndCheckFramesValidWithImageSource:_imageSource]; } - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options { UIImage *image; if (_width + _height > 0) { // Create the image CGFloat scale = _scale; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil]; if (image) { image.sd_imageFormat = self.class.imageFormat; } } return image; } #pragma mark - Encode - (BOOL)canEncodeToFormat:(SDImageFormat)format { return (format == self.class.imageFormat); } - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options { if (!image) { return nil; } CGImageRef imageRef = image.CGImage; if (!imageRef) { // Earily return, supports CGImage only return nil; } if (format != self.class.imageFormat) { return nil; } NSMutableData *imageData = [NSMutableData data]; CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; NSArray *frames = [SDImageCoderHelper framesFromAnimatedImage:image]; // Create an image destination. Animated Image does not support EXIF image orientation TODO // The `CGImageDestinationCreateWithData` will log a warning when count is 0, use 1 instead. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count ?: 1, NULL); if (!imageDestination) { // Handle failure. return nil; } NSMutableDictionary *properties = [NSMutableDictionary dictionary]; // Encoding Options double compressionQuality = 1; if (options[SDImageCoderEncodeCompressionQuality]) { compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue]; } properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality); CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor]; if (backgroundColor) { properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor); } CGSize maxPixelSize = CGSizeZero; NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize]; if (maxPixelSizeValue != nil) { #if SD_MAC maxPixelSize = maxPixelSizeValue.sizeValue; #else maxPixelSize = maxPixelSizeValue.CGSizeValue; #endif } NSUInteger pixelWidth = CGImageGetWidth(imageRef); NSUInteger pixelHeight = CGImageGetHeight(imageRef); CGFloat finalPixelSize = 0; if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) { CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height; if (pixelRatio > maxPixelSizeRatio) { finalPixelSize = maxPixelSize.width; } else { finalPixelSize = maxPixelSize.height; } properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize); } NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue]; if (maxFileSize > 0) { properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize); // Remove the quality if we have file size limit properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil; } BOOL embedThumbnail = NO; if (options[SDImageCoderEncodeEmbedThumbnail]) { embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue]; } properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail); BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue]; if (encodeFirstFrame || frames.count == 0) { // for static single images CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties); } else { // for animated images NSUInteger loopCount = image.sd_imageLoopCount; NSDictionary *containerProperties = @{ self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)} }; // container level properties (applies for `CGImageDestinationSetProperties`, not individual frames) CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties); for (size_t i = 0; i < frames.count; i++) { SDImageFrame *frame = frames[i]; NSTimeInterval frameDuration = frame.duration; CGImageRef frameImageRef = frame.image.CGImage; properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)}; CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties); } } // Finalize the destination. if (CGImageDestinationFinalize(imageDestination) == NO) { // Handle failure. imageData = nil; } CFRelease(imageDestination); return [imageData copy]; } #pragma mark - SDAnimatedImageCoder - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options { if (!data) { return nil; } self = [super init]; if (self) { CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); if (!imageSource) { return nil; } BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource]; if (!framesValid) { CFRelease(imageSource); return nil; } CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } _scale = scale; CGSize thumbnailSize = CGSizeZero; NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; if (thumbnailSizeValue != nil) { #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif } _thumbnailSize = thumbnailSize; BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } _preserveAspectRatio = preserveAspectRatio; _imageSource = imageSource; _imageData = data; #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; } - (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource { if (!imageSource) { return NO; } NSUInteger frameCount = CGImageSourceGetCount(imageSource); NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource]; NSMutableArray *frames = [NSMutableArray array]; for (size_t i = 0; i < frameCount; i++) { SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init]; frame.index = i; frame.duration = [self.class frameDurationAtIndex:i source:imageSource]; [frames addObject:frame]; } _frameCount = frameCount; _loopCount = loopCount; _frames = [frames copy]; return YES; } - (NSData *)animatedImageData { return _imageData; } - (NSUInteger)animatedImageLoopCount { return _loopCount; } - (NSUInteger)animatedImageFrameCount { return _frameCount; } - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { if (index >= _frameCount) { return 0; } return _frames[index].duration; } - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { if (index >= _frameCount) { return nil; } // Animated Image should not use the CGContext solution to force decode. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961 NSDictionary *options = @{ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES), (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage }; UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:options]; if (!image) { return nil; } image.sd_imageFormat = self.class.imageFormat; image.sd_isDecoded = YES; return image; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageIOCoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageCoder.h" /** Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding. GIF Also supports static GIF (meaning will only handle the 1st frame). For a full GIF support, we recommend `SDAnimatedImageView` to keep both CPU and memory balanced. HEIC This coder also supports HEIC format because ImageIO supports it natively. But it depends on the system capabilities, so it won't work on all devices, see: https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf Decode(Software): !Simulator && (iOS 11 || tvOS 11 || macOS 10.13) Decode(Hardware): !Simulator && ((iOS 11 && A9Chip) || (macOS 10.13 && 6thGenerationIntelCPU)) Encode(Software): macOS 10.13 Encode(Hardware): !Simulator && ((iOS 11 && A10FusionChip) || (macOS 10.13 && 6thGenerationIntelCPU)) */ @interface SDImageIOCoder : NSObject @property (nonatomic, class, readonly, nonnull) SDImageIOCoder *sharedCoder; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageIOCoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageIOCoder.h" #import "SDImageCoderHelper.h" #import "NSImage+Compatibility.h" #import #import "UIImage+Metadata.h" #import "SDImageIOAnimatedCoderInternal.h" // Specify File Size for lossy format encoding, like JPEG static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; @implementation SDImageIOCoder { size_t _width, _height; CGImagePropertyOrientation _orientation; CGImageSourceRef _imageSource; CGFloat _scale; BOOL _finished; BOOL _preserveAspectRatio; CGSize _thumbnailSize; } - (void)dealloc { if (_imageSource) { CFRelease(_imageSource); _imageSource = NULL; } #if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } - (void)didReceiveMemoryWarning:(NSNotification *)notification { if (_imageSource) { CGImageSourceRemoveCacheAtIndex(_imageSource, 0); } } + (instancetype)sharedCoder { static SDImageIOCoder *coder; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ coder = [[SDImageIOCoder alloc] init]; }); return coder; } #pragma mark - Decode - (BOOL)canDecodeFromData:(nullable NSData *)data { return YES; } - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options { if (!data) { return nil; } CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1) ; } CGSize thumbnailSize = CGSizeZero; NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; if (thumbnailSizeValue != nil) { #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif } BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); if (!source) { return nil; } UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil]; CFRelease(source); if (!image) { return nil; } image.sd_imageFormat = [NSData sd_imageFormatForImageData:data]; return image; } #pragma mark - Progressive Decode - (BOOL)canIncrementalDecodeFromData:(NSData *)data { return [self canDecodeFromData:data]; } - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options { self = [super init]; if (self) { _imageSource = CGImageSourceCreateIncremental(NULL); CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } _scale = scale; CGSize thumbnailSize = CGSizeZero; NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; if (thumbnailSizeValue != nil) { #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif } _thumbnailSize = thumbnailSize; BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } _preserveAspectRatio = preserveAspectRatio; #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; } - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished { if (_finished) { return; } _finished = finished; // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/ // Thanks to the author @Nyx0uf // Update the data source, we must pass ALL the data, not just the new bytes CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished); if (_width + _height == 0) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL); if (properties) { NSInteger orientationValue = 1; CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); if (val) CFNumberGetValue(val, kCFNumberLongType, &_height); val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); if (val) CFNumberGetValue(val, kCFNumberLongType, &_width); val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue); CFRelease(properties); // When we draw to Core Graphics, we lose orientation information, // which means the image below born of initWithCGIImage will be // oriented incorrectly sometimes. (Unlike the image born of initWithData // in didCompleteWithError.) So save it here and pass it on later. _orientation = (CGImagePropertyOrientation)orientationValue; } } } - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options { UIImage *image; if (_width + _height > 0) { // Create the image CGFloat scale = _scale; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil]; if (image) { CFStringRef uttype = CGImageSourceGetType(_imageSource); image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype]; } } return image; } #pragma mark - Encode - (BOOL)canEncodeToFormat:(SDImageFormat)format { return YES; } - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options { if (!image) { return nil; } CGImageRef imageRef = image.CGImage; if (!imageRef) { // Earily return, supports CGImage only return nil; } if (format == SDImageFormatUndefined) { BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:imageRef]; if (hasAlpha) { format = SDImageFormatPNG; } else { format = SDImageFormatJPEG; } } NSMutableData *imageData = [NSMutableData data]; CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; // Create an image destination. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL); if (!imageDestination) { // Handle failure. return nil; } NSMutableDictionary *properties = [NSMutableDictionary dictionary]; #if SD_UIKIT || SD_WATCH CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation]; #else CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp; #endif properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation); // Encoding Options double compressionQuality = 1; if (options[SDImageCoderEncodeCompressionQuality]) { compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue]; } properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality); CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor]; if (backgroundColor) { properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor); } CGSize maxPixelSize = CGSizeZero; NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize]; if (maxPixelSizeValue != nil) { #if SD_MAC maxPixelSize = maxPixelSizeValue.sizeValue; #else maxPixelSize = maxPixelSizeValue.CGSizeValue; #endif } NSUInteger pixelWidth = CGImageGetWidth(imageRef); NSUInteger pixelHeight = CGImageGetHeight(imageRef); if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) { CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height; CGFloat finalPixelSize; if (pixelRatio > maxPixelSizeRatio) { finalPixelSize = maxPixelSize.width; } else { finalPixelSize = maxPixelSize.height; } properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize); } NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue]; if (maxFileSize > 0) { properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize); // Remove the quality if we have file size limit properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil; } BOOL embedThumbnail = NO; if (options[SDImageCoderEncodeEmbedThumbnail]) { embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue]; } properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail); // Add your image to the destination. CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties); // Finalize the destination. if (CGImageDestinationFinalize(imageDestination) == NO) { // Handle failure. imageData = nil; } CFRelease(imageDestination); return [imageData copy]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageLoader.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDWebImageOperation.h" #import "SDImageCoder.h" typedef void(^SDImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL); typedef void(^SDImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); #pragma mark - Context Options /** A `UIImage` instance from `SDWebImageManager` when you specify `SDWebImageRefreshCached` and image cache hit. This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. If the image from remote location does not change, you should call the completion with `SDWebImageErrorCacheNotModified` error. (UIImage) @note If you don't implement `SDWebImageRefreshCached` support, you do not need to care about this context option. */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCachedImage; #pragma mark - Helper method /** This is the built-in decoding process for image download from network or local file. @note If you want to implement your custom loader with `requestImageWithURL:options:context:progress:completed:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. @param imageData The image data from the network. Should not be nil @param imageURL The image URL from the input. Should not be nil @param options The options arg from the input @param context The context arg from the input @return The decoded image for current image data load from the network */ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context); /** This is the built-in decoding process for image progressive download from network. It's used when `SDWebImageProgressiveLoad` option is set. (It's not required when your loader does not support progressive image loading) @note If you want to implement your custom loader with `requestImageWithURL:options:context:progress:completed:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. @param imageData The image data from the network so far. Should not be nil @param imageURL The image URL from the input. Should not be nil @param finished Pass NO to specify the download process has not finished. Pass YES when all image data has finished. @param operation The loader operation associated with current progressive download. Why to provide this is because progressive decoding need to store the partial decoded context for each operation to avoid conflict. You should provide the operation from `loadImageWithURL:` method return value. @param options The options arg from the input @param context The context arg from the input @return The decoded progressive image for current image data load from the network */ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context); /** This function get the progressive decoder for current loading operation. If no progressive decoding is happended or decoder is not able to construct, return nil. @return The progressive decoder associated with the loading operation. */ FOUNDATION_EXPORT id _Nullable SDImageLoaderGetProgressiveCoder(id _Nonnull operation); /** This function set the progressive decoder for current loading operation. If no progressive decoding is happended, pass nil. @param operation The loading operation to associate the progerssive decoder. */ FOUNDATION_EXPORT void SDImageLoaderSetProgressiveCoder(id _Nonnull operation, id _Nullable progressiveCoder); #pragma mark - SDImageLoader /** This is the protocol to specify custom image load process. You can create your own class to conform this protocol and use as a image loader to load image from network or any available remote resources defined by yourself. If you want to implement custom loader for image download from network or local file, you just need to concentrate on image data download only. After the download finish, call `SDImageLoaderDecodeImageData` or `SDImageLoaderDecodeProgressiveImageData` to use the built-in decoding process and produce image (Remember to call in the global queue). And finally callback the completion block. If you directly get the image instance using some third-party SDKs, such as image directly from Photos framework. You can process the image data and image instance by yourself without that built-in decoding process. And finally callback the completion block. @note It's your responsibility to load the image in the desired global queue(to avoid block main queue). We do not dispatch these method call in a global queue but just from the call queue (For `SDWebImageManager`, it typically call from the main queue). */ @protocol SDImageLoader @required /** Whether current image loader supports to load the provide image URL. This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`. @param url The image URL to be loaded. @return YES to continue download, NO to stop download. */ - (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED("Use canRequestImageForURL:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); @optional /** Whether current image loader supports to load the provide image URL, with associated options and context. This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`. @param url The image URL to be loaded. @param options A mask to specify options to use for this request @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @return YES to continue download, NO to stop download. */ - (BOOL)canRequestImageForURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; @required /** Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance. @param url The URL represent the image. Note this may not be a HTTP URL @param options A mask to specify options to use for this request @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue @param completedBlock A block called when operation has been completed. @return An operation which allow the user to cancel the current request. */ - (nullable id)requestImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDImageLoaderCompletedBlock)completedBlock; /** Whether the error from image loader should be marked indeed un-recoverable or not. If this return YES, failed URL which does not using `SDWebImageRetryFailed` will be blocked into black list. Else not. @param url The URL represent the image. Note this may not be a HTTP URL @param error The URL's loading error, from previous `requestImageWithURL:options:context:progress:completed:` completedBlock's error. @return Whether to block this url or not. Return YES to mark this URL as failed. */ - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url error:(nonnull NSError *)error API_DEPRECATED("Use shouldBlockFailedURLWithURL:error:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); @optional /** Whether the error from image loader should be marked indeed un-recoverable or not, with associated options and context. If this return YES, failed URL which does not using `SDWebImageRetryFailed` will be blocked into black list. Else not. @param url The URL represent the image. Note this may not be a HTTP URL @param error The URL's loading error, from previous `requestImageWithURL:options:context:progress:completed:` completedBlock's error. @param options A mask to specify options to use for this request @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @return Whether to block this url or not. Return YES to mark this URL as failed. */ - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url error:(nonnull NSError *)error options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageLoader.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageLoader.h" #import "SDWebImageCacheKeyFilter.h" #import "SDImageCodersManager.h" #import "SDImageCoderHelper.h" #import "SDAnimatedImage.h" #import "UIImage+Metadata.h" #import "SDInternalMacros.h" #import "objc/runtime.h" SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage"; static void * SDImageLoaderProgressiveCoderKey = &SDImageLoaderProgressiveCoderKey; id SDImageLoaderGetProgressiveCoder(id operation) { NSCParameterAssert(operation); return objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey); } void SDImageLoaderSetProgressiveCoder(id operation, id progressiveCoder) { NSCParameterAssert(operation); objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) { NSCParameterAssert(imageData); NSCParameterAssert(imageURL); UIImage *image; id cacheKeyFilter = context[SDWebImageContextCacheKeyFilter]; NSString *cacheKey; if (cacheKeyFilter) { cacheKey = [cacheKeyFilter cacheKeyForURL:imageURL]; } else { cacheKey = imageURL.absoluteString; } BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio]; NSValue *thumbnailSizeValue; BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages); if (shouldScaleDown) { CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4; CGFloat dimension = ceil(sqrt(thumbnailPixels)); thumbnailSizeValue = @(CGSizeMake(dimension, dimension)); } if (context[SDWebImageContextImageThumbnailPixelSize]) { thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; } SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2]; mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame); mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale); mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue; mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue; mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; // Grab the image coder id imageCoder; if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { imageCoder = context[SDWebImageContextImageCoder]; } else { imageCoder = [SDImageCodersManager sharedManager]; } if (!decodeFirstFrame) { // check whether we should use `SDAnimatedImage` Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) { image = [[animatedImageClass alloc] initWithData:imageData scale:scale options:coderOptions]; if (image) { // Preload frames if supported if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) { [((id)image) preloadAllFrames]; } } else { // Check image class matching if (options & SDWebImageMatchAnimatedImageClass) { return nil; } } } } if (!image) { image = [imageCoder decodedImageWithData:imageData options:coderOptions]; } if (image) { BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage); if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) { // `SDAnimatedImage` do not decode shouldDecode = NO; } else if (image.sd_isAnimated) { // animated image do not decode shouldDecode = NO; } if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } } return image; } UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context) { NSCParameterAssert(imageData); NSCParameterAssert(imageURL); NSCParameterAssert(operation); UIImage *image; id cacheKeyFilter = context[SDWebImageContextCacheKeyFilter]; NSString *cacheKey; if (cacheKeyFilter) { cacheKey = [cacheKeyFilter cacheKeyForURL:imageURL]; } else { cacheKey = imageURL.absoluteString; } BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio]; NSValue *thumbnailSizeValue; BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages); if (shouldScaleDown) { CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4; CGFloat dimension = ceil(sqrt(thumbnailPixels)); thumbnailSizeValue = @(CGSizeMake(dimension, dimension)); } if (context[SDWebImageContextImageThumbnailPixelSize]) { thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; } SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2]; mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame); mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale); mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue; mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue; mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; // Grab the progressive image coder id progressiveCoder = SDImageLoaderGetProgressiveCoder(operation); if (!progressiveCoder) { id imageCoder = context[SDWebImageContextImageCoder]; // Check the progressive coder if provided if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions]; } else { // We need to create a new instance for progressive decoding to avoid conflicts for (id coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) { if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] && [((id)coder) canIncrementalDecodeFromData:imageData]) { progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions]; break; } } } SDImageLoaderSetProgressiveCoder(operation, progressiveCoder); } // If we can't find any progressive coder, disable progressive download if (!progressiveCoder) { return nil; } [progressiveCoder updateIncrementalData:imageData finished:finished]; if (!decodeFirstFrame) { // check whether we should use `SDAnimatedImage` Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [progressiveCoder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { image = [[animatedImageClass alloc] initWithAnimatedCoder:(id)progressiveCoder scale:scale]; if (image) { // Progressive decoding does not preload frames } else { // Check image class matching if (options & SDWebImageMatchAnimatedImageClass) { return nil; } } } } if (!image) { image = [progressiveCoder incrementalDecodedImageWithOptions:coderOptions]; } if (image) { BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage); if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) { // `SDAnimatedImage` do not decode shouldDecode = NO; } else if (image.sd_isAnimated) { // animated image do not decode shouldDecode = NO; } if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } // mark the image as progressive (completed one are not mark as progressive) image.sd_isIncremental = !finished; } return image; } ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageLoadersManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageLoader.h" /** A loaders manager to manage multiple loaders */ @interface SDImageLoadersManager : NSObject /** Returns the global shared loaders manager instance. By default we will set [`SDWebImageDownloader.sharedDownloader`] into the loaders array. */ @property (nonatomic, class, readonly, nonnull) SDImageLoadersManager *sharedManager; /** All image loaders in manager. The loaders array is a priority queue, which means the later added loader will have the highest priority */ @property (nonatomic, copy, nullable) NSArray>* loaders; /** Add a new image loader to the end of loaders array. Which has the highest priority. @param loader loader */ - (void)addLoader:(nonnull id)loader; /** Remove an image loader in the loaders array. @param loader loader */ - (void)removeLoader:(nonnull id)loader; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageLoadersManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageLoadersManager.h" #import "SDWebImageDownloader.h" #import "SDInternalMacros.h" @interface SDImageLoadersManager () @property (nonatomic, strong, nonnull) NSMutableArray> *imageLoaders; @end @implementation SDImageLoadersManager { SD_LOCK_DECLARE(_loadersLock); } + (SDImageLoadersManager *)sharedManager { static dispatch_once_t onceToken; static SDImageLoadersManager *manager; dispatch_once(&onceToken, ^{ manager = [[SDImageLoadersManager alloc] init]; }); return manager; } - (instancetype)init { self = [super init]; if (self) { // initialize with default image loaders _imageLoaders = [NSMutableArray arrayWithObject:[SDWebImageDownloader sharedDownloader]]; SD_LOCK_INIT(_loadersLock); } return self; } - (NSArray> *)loaders { SD_LOCK(_loadersLock); NSArray>* loaders = [_imageLoaders copy]; SD_UNLOCK(_loadersLock); return loaders; } - (void)setLoaders:(NSArray> *)loaders { SD_LOCK(_loadersLock); [_imageLoaders removeAllObjects]; if (loaders.count) { [_imageLoaders addObjectsFromArray:loaders]; } SD_UNLOCK(_loadersLock); } #pragma mark - Loader Property - (void)addLoader:(id)loader { if (![loader conformsToProtocol:@protocol(SDImageLoader)]) { return; } SD_LOCK(_loadersLock); [_imageLoaders addObject:loader]; SD_UNLOCK(_loadersLock); } - (void)removeLoader:(id)loader { if (![loader conformsToProtocol:@protocol(SDImageLoader)]) { return; } SD_LOCK(_loadersLock); [_imageLoaders removeObject:loader]; SD_UNLOCK(_loadersLock); } #pragma mark - SDImageLoader - (BOOL)canRequestImageForURL:(nullable NSURL *)url { NSArray> *loaders = self.loaders; for (id loader in loaders.reverseObjectEnumerator) { if ([loader canRequestImageForURL:url]) { return YES; } } return NO; } - (id)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock { if (!url) { return nil; } NSArray> *loaders = self.loaders; for (id loader in loaders.reverseObjectEnumerator) { if ([loader canRequestImageForURL:url]) { return [loader requestImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; } } return nil; } - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error { NSArray> *loaders = self.loaders; for (id loader in loaders.reverseObjectEnumerator) { if ([loader canRequestImageForURL:url]) { return [loader shouldBlockFailedURLWithURL:url error:error]; } } return NO; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageTransformer.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "UIImage+Transform.h" /** Return the transformed cache key which applied with specify transformerKey. @param key The original cache key @param transformerKey The transformer key from the transformer @return The transformed cache key */ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey); /** Return the thumbnailed cache key which applied with specify thumbnailSize and preserveAspectRatio control. @param key The original cache key @param thumbnailPixelSize The thumbnail pixel size @param preserveAspectRatio The preserve aspect ratio option @return The thumbnailed cache key @note If you have both transformer and thumbnail applied for image, call `SDThumbnailedKeyForKey` firstly and then with `SDTransformedKeyForKey`.` */ FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio); /** A transformer protocol to transform the image load from cache or from download. You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`). @note The transform process is called from a global queue in order to not to block the main queue. */ @protocol SDImageTransformer @required /** For each transformer, it must contains its cache key to used to store the image cache or query from the cache. This key will be appened after the original cache key generated by URL or from user. @return The cache key to appended after the original cache key. Should not be nil. */ @property (nonatomic, copy, readonly, nonnull) NSString *transformerKey; /** Transform the image to another image. @param image The image to be transformed @param key The cache key associated to the image. This arg is a hint for image source, not always useful and should be nullable. In the future we will remove this arg. @return The transformed image, or nil if transform failed */ - (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key API_DEPRECATED("The key arg will be removed in the future. Update your code and don't rely on that.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); @end #pragma mark - Pipeline /** Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one in order and generate the final image. @note Because transformers are lightweight, if you want to append or arrange transformers, create another pipeline transformer instead. This class is considered as immutable. */ @interface SDImagePipelineTransformer : NSObject /** All transformers in pipeline */ @property (nonatomic, copy, readonly, nonnull) NSArray> *transformers; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithTransformers:(nonnull NSArray> *)transformers; @end // There are some built-in transformers based on the `UIImage+Transformer` category to provide the common image geometry, image blending and image effect process. Those transform are useful for static image only but you can create your own to support animated image as well. // Because transformers are lightweight, these class are considered as immutable. #pragma mark - Image Geometry /** Image round corner transformer */ @interface SDImageRoundCornerTransformer: NSObject /** The radius of each corner oval. Values larger than half the rectangle's width or height are clamped appropriately to half the width or height. */ @property (nonatomic, assign, readonly) CGFloat cornerRadius; /** A bitmask value that identifies the corners that you want rounded. You can use this parameter to round only a subset of the corners of the rectangle. */ @property (nonatomic, assign, readonly) SDRectCorner corners; /** The inset border line width. Values larger than half the rectangle's width or height are clamped appropriately to half the width or height. */ @property (nonatomic, assign, readonly) CGFloat borderWidth; /** The border stroke color. nil means clear color. */ @property (nonatomic, strong, readonly, nullable) UIColor *borderColor; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor; @end /** Image resizing transformer */ @interface SDImageResizingTransformer : NSObject /** The new size to be resized, values should be positive. */ @property (nonatomic, assign, readonly) CGSize size; /** The scale mode for image content. */ @property (nonatomic, assign, readonly) SDImageScaleMode scaleMode; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; @end /** Image cropping transformer */ @interface SDImageCroppingTransformer : NSObject /** Image's inner rect. */ @property (nonatomic, assign, readonly) CGRect rect; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithRect:(CGRect)rect; @end /** Image flipping transformer */ @interface SDImageFlippingTransformer : NSObject /** YES to flip the image horizontally. ⇋ */ @property (nonatomic, assign, readonly) BOOL horizontal; /** YES to flip the image vertically. ⥯ */ @property (nonatomic, assign, readonly) BOOL vertical; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; @end /** Image rotation transformer */ @interface SDImageRotationTransformer : NSObject /** Rotated radians in counterclockwise.⟲ */ @property (nonatomic, assign, readonly) CGFloat angle; /** YES: new image's size is extend to fit all content. NO: image's size will not change, content may be clipped. */ @property (nonatomic, assign, readonly) BOOL fitSize; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; @end #pragma mark - Image Blending /** Image tint color transformer */ @interface SDImageTintTransformer : NSObject /** The tint color. */ @property (nonatomic, strong, readonly, nonnull) UIColor *tintColor; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithColor:(nonnull UIColor *)tintColor; @end #pragma mark - Image Effect /** Image blur effect transformer */ @interface SDImageBlurTransformer : NSObject /** The radius of the blur in points, 0 means no blur effect. */ @property (nonatomic, assign, readonly) CGFloat blurRadius; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithRadius:(CGFloat)blurRadius; @end #if SD_UIKIT || SD_MAC /** Core Image filter transformer */ @interface SDImageFilterTransformer: NSObject /** The CIFilter to be applied to the image. */ @property (nonatomic, strong, readonly, nonnull) CIFilter *filter; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)transformerWithFilter:(nonnull CIFilter *)filter; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDImageTransformer.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageTransformer.h" #import "UIColor+SDHexString.h" #if SD_UIKIT || SD_MAC #import #endif // Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDImageFlippingTransformer(1,0)-SDImageRotationTransformer(0.78539816339,1).png' static NSString * const SDImageTransformerKeySeparator = @"-"; NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey) { if (!key || !transformerKey) { return nil; } // Find the file extension NSURL *keyURL = [NSURL URLWithString:key]; NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension; if (ext.length > 0) { // For non-file URL if (keyURL && !keyURL.isFileURL) { // keep anything except path (like URL query) NSURLComponents *component = [NSURLComponents componentsWithURL:keyURL resolvingAgainstBaseURL:NO]; component.path = [[[component.path.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext]; return component.URL.absoluteString; } else { // file URL return [[[key.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext]; } } else { return [[key stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey]; } } NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio) { NSString *thumbnailKey = [NSString stringWithFormat:@"Thumbnail({%f,%f},%d)", thumbnailPixelSize.width, thumbnailPixelSize.height, preserveAspectRatio]; return SDTransformedKeyForKey(key, thumbnailKey); } @interface SDImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; @property (nonatomic, copy, readwrite) NSString *transformerKey; @end @implementation SDImagePipelineTransformer + (instancetype)transformerWithTransformers:(NSArray> *)transformers { SDImagePipelineTransformer *transformer = [SDImagePipelineTransformer new]; transformer.transformers = transformers; transformer.transformerKey = [[self class] cacheKeyForTransformers:transformers]; return transformer; } + (NSString *)cacheKeyForTransformers:(NSArray> *)transformers { if (transformers.count == 0) { return @""; } NSMutableArray *cacheKeys = [NSMutableArray arrayWithCapacity:transformers.count]; [transformers enumerateObjectsUsingBlock:^(id _Nonnull transformer, NSUInteger idx, BOOL * _Nonnull stop) { NSString *cacheKey = transformer.transformerKey; [cacheKeys addObject:cacheKey]; }]; return [cacheKeys componentsJoinedByString:SDImageTransformerKeySeparator]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } UIImage *transformedImage = image; for (id transformer in self.transformers) { transformedImage = [transformer transformedImageWithImage:transformedImage forKey:key]; } return transformedImage; } @end @interface SDImageRoundCornerTransformer () @property (nonatomic, assign) CGFloat cornerRadius; @property (nonatomic, assign) SDRectCorner corners; @property (nonatomic, assign) CGFloat borderWidth; @property (nonatomic, strong, nullable) UIColor *borderColor; @end @implementation SDImageRoundCornerTransformer + (instancetype)transformerWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor { SDImageRoundCornerTransformer *transformer = [SDImageRoundCornerTransformer new]; transformer.cornerRadius = cornerRadius; transformer.corners = corners; transformer.borderWidth = borderWidth; transformer.borderColor = borderColor; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageRoundCornerTransformer(%f,%lu,%f,%@)", self.cornerRadius, (unsigned long)self.corners, self.borderWidth, self.borderColor.sd_hexString]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_roundedCornerImageWithRadius:self.cornerRadius corners:self.corners borderWidth:self.borderWidth borderColor:self.borderColor]; } @end @interface SDImageResizingTransformer () @property (nonatomic, assign) CGSize size; @property (nonatomic, assign) SDImageScaleMode scaleMode; @end @implementation SDImageResizingTransformer + (instancetype)transformerWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { SDImageResizingTransformer *transformer = [SDImageResizingTransformer new]; transformer.size = size; transformer.scaleMode = scaleMode; return transformer; } - (NSString *)transformerKey { CGSize size = self.size; return [NSString stringWithFormat:@"SDImageResizingTransformer({%f,%f},%lu)", size.width, size.height, (unsigned long)self.scaleMode]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_resizedImageWithSize:self.size scaleMode:self.scaleMode]; } @end @interface SDImageCroppingTransformer () @property (nonatomic, assign) CGRect rect; @end @implementation SDImageCroppingTransformer + (instancetype)transformerWithRect:(CGRect)rect { SDImageCroppingTransformer *transformer = [SDImageCroppingTransformer new]; transformer.rect = rect; return transformer; } - (NSString *)transformerKey { CGRect rect = self.rect; return [NSString stringWithFormat:@"SDImageCroppingTransformer({%f,%f,%f,%f})", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_croppedImageWithRect:self.rect]; } @end @interface SDImageFlippingTransformer () @property (nonatomic, assign) BOOL horizontal; @property (nonatomic, assign) BOOL vertical; @end @implementation SDImageFlippingTransformer + (instancetype)transformerWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { SDImageFlippingTransformer *transformer = [SDImageFlippingTransformer new]; transformer.horizontal = horizontal; transformer.vertical = vertical; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageFlippingTransformer(%d,%d)", self.horizontal, self.vertical]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_flippedImageWithHorizontal:self.horizontal vertical:self.vertical]; } @end @interface SDImageRotationTransformer () @property (nonatomic, assign) CGFloat angle; @property (nonatomic, assign) BOOL fitSize; @end @implementation SDImageRotationTransformer + (instancetype)transformerWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { SDImageRotationTransformer *transformer = [SDImageRotationTransformer new]; transformer.angle = angle; transformer.fitSize = fitSize; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageRotationTransformer(%f,%d)", self.angle, self.fitSize]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_rotatedImageWithAngle:self.angle fitSize:self.fitSize]; } @end #pragma mark - Image Blending @interface SDImageTintTransformer () @property (nonatomic, strong, nonnull) UIColor *tintColor; @end @implementation SDImageTintTransformer + (instancetype)transformerWithColor:(UIColor *)tintColor { SDImageTintTransformer *transformer = [SDImageTintTransformer new]; transformer.tintColor = tintColor; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageTintTransformer(%@)", self.tintColor.sd_hexString]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_tintedImageWithColor:self.tintColor]; } @end #pragma mark - Image Effect @interface SDImageBlurTransformer () @property (nonatomic, assign) CGFloat blurRadius; @end @implementation SDImageBlurTransformer + (instancetype)transformerWithRadius:(CGFloat)blurRadius { SDImageBlurTransformer *transformer = [SDImageBlurTransformer new]; transformer.blurRadius = blurRadius; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageBlurTransformer(%f)", self.blurRadius]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_blurredImageWithRadius:self.blurRadius]; } @end #if SD_UIKIT || SD_MAC @interface SDImageFilterTransformer () @property (nonatomic, strong, nonnull) CIFilter *filter; @end @implementation SDImageFilterTransformer + (instancetype)transformerWithFilter:(CIFilter *)filter { SDImageFilterTransformer *transformer = [SDImageFilterTransformer new]; transformer.filter = filter; return transformer; } - (NSString *)transformerKey { return [NSString stringWithFormat:@"SDImageFilterTransformer(%@)", self.filter.name]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { if (!image) { return nil; } return [image sd_filteredImageWithFilter:self.filter]; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDMemoryCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" @class SDImageCacheConfig; /** A protocol to allow custom memory cache used in SDImageCache. */ @protocol SDMemoryCache @required /** Create a new memory cache instance with the specify cache config. You can check `maxMemoryCost` and `maxMemoryCount` used for memory cache. @param config The cache config to be used to create the cache. @return The new memory cache instance. */ - (nonnull instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config; /** Returns the value associated with a given key. @param key An object identifying the value. If nil, just return nil. @return The value associated with key, or nil if no value is associated with key. */ - (nullable id)objectForKey:(nonnull id)key; /** Sets the value of the specified key in the cache (0 cost). @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. @param key The key with which to associate the value. If nil, this method has no effect. @discussion Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it. */ - (void)setObject:(nullable id)object forKey:(nonnull id)key; /** Sets the value of the specified key in the cache, and associates the key-value pair with the specified cost. @param object The object to store in the cache. If nil, it calls `removeObjectForKey`. @param key The key with which to associate the value. If nil, this method has no effect. @param cost The cost with which to associate the key-value pair. @discussion Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it. */ - (void)setObject:(nullable id)object forKey:(nonnull id)key cost:(NSUInteger)cost; /** Removes the value of the specified key in the cache. @param key The key identifying the value to be removed. If nil, this method has no effect. */ - (void)removeObjectForKey:(nonnull id)key; /** Empties the cache immediately. */ - (void)removeAllObjects; @end /** A memory cache which auto purge the cache on memory warning and support weak cache. */ @interface SDMemoryCache : NSCache @property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDMemoryCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDMemoryCache.h" #import "SDImageCacheConfig.h" #import "UIImage+MemoryCacheCost.h" #import "SDInternalMacros.h" static void * SDMemoryCacheContext = &SDMemoryCacheContext; @interface SDMemoryCache () { #if SD_UIKIT SD_LOCK_DECLARE(_weakCacheLock); // a lock to keep the access to `weakCache` thread-safe #endif } @property (nonatomic, strong, nullable) SDImageCacheConfig *config; #if SD_UIKIT @property (nonatomic, strong, nonnull) NSMapTable *weakCache; // strong-weak cache #endif @end @implementation SDMemoryCache - (void)dealloc { [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) context:SDMemoryCacheContext]; [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) context:SDMemoryCacheContext]; #if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif self.delegate = nil; } - (instancetype)init { self = [super init]; if (self) { _config = [[SDImageCacheConfig alloc] init]; [self commonInit]; } return self; } - (instancetype)initWithConfig:(SDImageCacheConfig *)config { self = [super init]; if (self) { _config = config; [self commonInit]; } return self; } - (void)commonInit { SDImageCacheConfig *config = self.config; self.totalCostLimit = config.maxMemoryCost; self.countLimit = config.maxMemoryCount; [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext]; [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext]; #if SD_UIKIT self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; SD_LOCK_INIT(_weakCacheLock); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } // Current this seems no use on macOS (macOS use virtual memory and do not clear cache when memory warning). So we only override on iOS/tvOS platform. #if SD_UIKIT - (void)didReceiveMemoryWarning:(NSNotification *)notification { // Only remove cache, but keep weak cache [super removeAllObjects]; } // `setObject:forKey:` just call this with 0 cost. Override this is enough - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g { [super setObject:obj forKey:key cost:g]; if (!self.config.shouldUseWeakMemoryCache) { return; } if (key && obj) { // Store weak cache SD_LOCK(_weakCacheLock); [self.weakCache setObject:obj forKey:key]; SD_UNLOCK(_weakCacheLock); } } - (id)objectForKey:(id)key { id obj = [super objectForKey:key]; if (!self.config.shouldUseWeakMemoryCache) { return obj; } if (key && !obj) { // Check weak cache SD_LOCK(_weakCacheLock); obj = [self.weakCache objectForKey:key]; SD_UNLOCK(_weakCacheLock); if (obj) { // Sync cache NSUInteger cost = 0; if ([obj isKindOfClass:[UIImage class]]) { cost = [(UIImage *)obj sd_memoryCost]; } [super setObject:obj forKey:key cost:cost]; } } return obj; } - (void)removeObjectForKey:(id)key { [super removeObjectForKey:key]; if (!self.config.shouldUseWeakMemoryCache) { return; } if (key) { // Remove weak cache SD_LOCK(_weakCacheLock); [self.weakCache removeObjectForKey:key]; SD_UNLOCK(_weakCacheLock); } } - (void)removeAllObjects { [super removeAllObjects]; if (!self.config.shouldUseWeakMemoryCache) { return; } // Manually remove should also remove weak cache SD_LOCK(_weakCacheLock); [self.weakCache removeAllObjects]; SD_UNLOCK(_weakCacheLock); } #endif #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == SDMemoryCacheContext) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) { self.totalCostLimit = self.config.maxMemoryCost; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) { self.countLimit = self.config.maxMemoryCount; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheKeyFilter.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nonnull url); /** This is the protocol for cache key filter. We can use a block to specify the cache key filter. But Using protocol can make this extensible, and allow Swift user to use it easily instead of using `@convention(block)` to store a block into context options. */ @protocol SDWebImageCacheKeyFilter - (nullable NSString *)cacheKeyForURL:(nonnull NSURL *)url; @end /** A cache key filter class with block. */ @interface SDWebImageCacheKeyFilter : NSObject - (nonnull instancetype)initWithBlock:(nonnull SDWebImageCacheKeyFilterBlock)block; + (nonnull instancetype)cacheKeyFilterWithBlock:(nonnull SDWebImageCacheKeyFilterBlock)block; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheKeyFilter.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCacheKeyFilter.h" @interface SDWebImageCacheKeyFilter () @property (nonatomic, copy, nonnull) SDWebImageCacheKeyFilterBlock block; @end @implementation SDWebImageCacheKeyFilter - (instancetype)initWithBlock:(SDWebImageCacheKeyFilterBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)cacheKeyFilterWithBlock:(SDWebImageCacheKeyFilterBlock)block { SDWebImageCacheKeyFilter *cacheKeyFilter = [[SDWebImageCacheKeyFilter alloc] initWithBlock:block]; return cacheKeyFilter; } - (NSString *)cacheKeyForURL:(NSURL *)url { if (!self.block) { return nil; } return self.block(url); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheSerializer.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL); /** This is the protocol for cache serializer. We can use a block to specify the cache serializer. But Using protocol can make this extensible, and allow Swift user to use it easily instead of using `@convention(block)` to store a block into context options. */ @protocol SDWebImageCacheSerializer /// Provide the image data associated to the image and store to disk cache /// @param image The loaded image /// @param data The original loaded image data /// @param imageURL The image URL - (nullable NSData *)cacheDataWithImage:(nonnull UIImage *)image originalData:(nullable NSData *)data imageURL:(nullable NSURL *)imageURL; @end /** A cache serializer class with block. */ @interface SDWebImageCacheSerializer : NSObject - (nonnull instancetype)initWithBlock:(nonnull SDWebImageCacheSerializerBlock)block; + (nonnull instancetype)cacheSerializerWithBlock:(nonnull SDWebImageCacheSerializerBlock)block; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheSerializer.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCacheSerializer.h" @interface SDWebImageCacheSerializer () @property (nonatomic, copy, nonnull) SDWebImageCacheSerializerBlock block; @end @implementation SDWebImageCacheSerializer - (instancetype)initWithBlock:(SDWebImageCacheSerializerBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)cacheSerializerWithBlock:(SDWebImageCacheSerializerBlock)block { SDWebImageCacheSerializer *cacheSerializer = [[SDWebImageCacheSerializer alloc] initWithBlock:block]; return cacheSerializer; } - (NSData *)cacheDataWithImage:(UIImage *)image originalData:(NSData *)data imageURL:(nullable NSURL *)imageURL { if (!self.block) { return nil; } return self.block(image, data, imageURL); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCompat.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Jamie Pinkham * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #ifdef __OBJC_GC__ #error SDWebImage does not support Objective-C Garbage Collection #endif // Seems like TARGET_OS_MAC is always defined (on all platforms). // To determine if we are running on macOS, use TARGET_OS_OSX in Xcode 8 #if TARGET_OS_OSX #define SD_MAC 1 #else #define SD_MAC 0 #endif // iOS and tvOS are very similar, UIKit exists on both platforms // Note: watchOS also has UIKit, but it's very limited #if TARGET_OS_IOS || TARGET_OS_TV #define SD_UIKIT 1 #else #define SD_UIKIT 0 #endif #if TARGET_OS_IOS #define SD_IOS 1 #else #define SD_IOS 0 #endif #if TARGET_OS_TV #define SD_TV 1 #else #define SD_TV 0 #endif #if TARGET_OS_WATCH #define SD_WATCH 1 #else #define SD_WATCH 0 #endif #if SD_MAC #import #ifndef UIImage #define UIImage NSImage #endif #ifndef UIImageView #define UIImageView NSImageView #endif #ifndef UIView #define UIView NSView #endif #ifndef UIColor #define UIColor NSColor #endif #else #if SD_UIKIT #import #endif #if SD_WATCH #import #ifndef UIView #define UIView WKInterfaceObject #endif #ifndef UIImageView #define UIImageView WKInterfaceImage #endif #endif #endif #ifndef NS_ENUM #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type #endif #ifndef NS_OPTIONS #define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type #endif #ifndef dispatch_main_async_safe #define dispatch_main_async_safe(block)\ if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCompat.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if !__has_feature(objc_arc) #error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #endif #if !OS_OBJECT_USE_OBJC #error SDWebImage need ARC for dispatch object #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDefine.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" typedef void(^SDWebImageNoParamsBlock)(void); typedef NSString * SDWebImageContextOption NS_EXTENSIBLE_STRING_ENUM; typedef NSDictionary SDWebImageContext; typedef NSMutableDictionary SDWebImageMutableContext; #pragma mark - Image scale /** Return the image scale factor for the specify key, supports file name and url key. This is the built-in way to check the scale factor when we have no context about it. Because scale factor is not stored in image data (It's typically from filename). However, you can also provide custom scale factor as well, see `SDWebImageContextImageScaleFactor`. @param key The image cache key @return The scale factor for image */ FOUNDATION_EXPORT CGFloat SDImageScaleFactorForKey(NSString * _Nullable key); /** Scale the image with the scale factor for the specify key. If no need to scale, return the original image. This works for `UIImage`(UIKit) or `NSImage`(AppKit). And this function also preserve the associated value in `UIImage+Metadata.h`. @note This is actually a convenience function, which firstly call `SDImageScaleFactorForKey` and then call `SDScaledImageForScaleFactor`, kept for backward compatibility. @param key The image cache key @param image The image @return The scaled image */ FOUNDATION_EXPORT UIImage * _Nullable SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image); /** Scale the image with the scale factor. If no need to scale, return the original image. This works for `UIImage`(UIKit) or `NSImage`(AppKit). And this function also preserve the associated value in `UIImage+Metadata.h`. @param scale The image scale factor @param image The image @return The scaled image */ FOUNDATION_EXPORT UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage * _Nullable image); #pragma mark - WebCache Options /// WebCache options typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying. * This flag disable this blacklisting. */ SDWebImageRetryFailed = 1 << 0, /** * By default, image downloads are started during UI interactions, this flags disable this feature, * leading to delayed download on UIScrollView deceleration for instance. */ SDWebImageLowPriority = 1 << 1, /** * This flag enables progressive download, the image is displayed progressively during download as a browser would do. * By default, the image is only displayed once completely downloaded. */ SDWebImageProgressiveLoad = 1 << 2, /** * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed. * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation. * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics. * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image. * * Use this flag only if you can't make your URLs static with embedded cache busting parameter. */ SDWebImageRefreshCached = 1 << 3, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ SDWebImageContinueInBackground = 1 << 4, /** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageHandleCookies = 1 << 5, /** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageAllowInvalidSSLCertificates = 1 << 6, /** * By default, images are loaded in the order in which they were queued. This flag moves them to * the front of the queue. */ SDWebImageHighPriority = 1 << 7, /** * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */ SDWebImageDelayPlaceholder = 1 << 8, /** * We usually don't apply transform on animated images as most transformers could not manage animated images. * Use this flag to transform them anyway. */ SDWebImageTransformAnimatedImage = 1 << 9, /** * By default, image is added to the imageView after download. But in some cases, we want to * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance) * Use this flag if you want to manually set the image in the completion when success */ SDWebImageAvoidAutoSetImage = 1 << 10, /** * By default, images are decoded respecting their original size. * This flag will scale down the images to a size compatible with the constrained memory of devices. * To control the limit memory bytes, check `SDImageCoderHelper.defaultScaleDownLimitBytes` (Defaults to 60MB on iOS) * This will actually translate to use context option `.imageThumbnailPixelSize` from v5.5.0 (Defaults to (3966, 3966) on iOS). Previously does not. * This flags effect the progressive and animated images as well from v5.5.0. Previously does not. * @note If you need detail controls, it's better to use context option `imageThumbnailPixelSize` and `imagePreserveAspectRatio` instead. */ SDWebImageScaleDownLargeImages = 1 << 11, /** * By default, we do not query image data when the image is already cached in memory. This mask can force to query image data at the same time. However, this query is asynchronously unless you specify `SDWebImageQueryMemoryDataSync` */ SDWebImageQueryMemoryData = 1 << 12, /** * By default, when you only specify `SDWebImageQueryMemoryData`, we query the memory image data asynchronously. Combined this mask as well to query the memory image data synchronously. * @note Query data synchronously is not recommend, unless you want to ensure the image is loaded in the same runloop to avoid flashing during cell reusing. */ SDWebImageQueryMemoryDataSync = 1 << 13, /** * By default, when the memory cache miss, we query the disk cache asynchronously. This mask can force to query disk cache (when memory cache miss) synchronously. * @note These 3 query options can be combined together. For the full list about these masks combination, see wiki page. * @note Query data synchronously is not recommend, unless you want to ensure the image is loaded in the same runloop to avoid flashing during cell reusing. */ SDWebImageQueryDiskDataSync = 1 << 14, /** * By default, when the cache missed, the image is load from the loader. This flag can prevent this to load from cache only. */ SDWebImageFromCacheOnly = 1 << 15, /** * By default, we query the cache before the image is load from the loader. This flag can prevent this to load from loader only. */ SDWebImageFromLoaderOnly = 1 << 16, /** * By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image when the callback from manager is asynchronous (from network, or disk cache query) * This mask can force to apply view transition for any cases, like memory cache query, or sync disk cache query. */ SDWebImageForceTransition = 1 << 17, /** * By default, we will decode the image in the background during cache query and download from the network. This can help to improve performance because when rendering image on the screen, it need to be firstly decoded. But this happen on the main queue by Core Animation. * However, this process may increase the memory usage as well. If you are experiencing a issue due to excessive memory consumption, This flag can prevent decode the image. */ SDWebImageAvoidDecodeImage = 1 << 18, /** * By default, we decode the animated image. This flag can force decode the first frame only and produce the static image. */ SDWebImageDecodeFirstFrameOnly = 1 << 19, /** * By default, for `SDAnimatedImage`, we decode the animated image frame during rendering to reduce memory usage. However, you can specify to preload all frames into memory to reduce CPU usage when the animated image is shared by lots of imageViews. * This will actually trigger `preloadAllAnimatedImageFrames` in the background queue(Disk Cache & Download only). */ SDWebImagePreloadAllFrames = 1 << 20, /** * By default, when you use `SDWebImageContextAnimatedImageClass` context option (like using `SDAnimatedImageView` which designed to use `SDAnimatedImage`), we may still use `UIImage` when the memory cache hit, or image decoder is not available to produce one exactlly matching your custom class as a fallback solution. * Using this option, can ensure we always callback image with your provided class. If failed to produce one, a error with code `SDWebImageErrorBadImageData` will been used. * Note this options is not compatible with `SDWebImageDecodeFirstFrameOnly`, which always produce a UIImage/NSImage. */ SDWebImageMatchAnimatedImageClass = 1 << 21, /** * By default, when we load the image from network, the image will be written to the cache (memory and disk, controlled by your `storeCacheType` context option) * This maybe an asynchronously operation and the final `SDInternalCompletionBlock` callback does not guarantee the disk cache written is finished and may cause logic error. (For example, you modify the disk data just in completion block, however, the disk cache is not ready) * If you need to process with the disk cache in the completion block, you should use this option to ensure the disk cache already been written when callback. * Note if you use this when using the custom cache serializer, or using the transformer, we will also wait until the output image data written is finished. */ SDWebImageWaitStoreCache = 1 << 22, /** * We usually don't apply transform on vector images, because vector images supports dynamically changing to any size, rasterize to a fixed size will loss details. To modify vector images, you can process the vector data at runtime (such as modifying PDF tag / SVG element). * Use this flag to transform them anyway. */ SDWebImageTransformVectorImage = 1 << 23 }; #pragma mark - Context Options /** A String to be used as the operation key for view category to store the image load operation. This is used for view instance which supports different image loading process. If nil, will use the class name as operation key. (NSString *) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetImageOperationKey; /** A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager *) @deprecated Deprecated in the future. This context options can be replaced by other context option control like `.imageCache`, `.imageLoader`, `.imageTransformer` (See below), which already matches all the properties in SDWebImageManager. */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager API_DEPRECATED("Use individual context option like .imageCache, .imageLoader and .imageTransformer instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); /** A id instance which conforms to `SDImageCache` protocol. It's used to override the image manager's cache during the image loading pipeline. In other word, if you just want to specify a custom cache during image loading, you don't need to re-create a dummy SDWebImageManager instance with the cache. If not provided, use the image manager's cache (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCache; /** A id instance which conforms to `SDImageLoader` protocol. It's used to override the image manager's loader during the image loading pipeline. In other word, if you just want to specify a custom loader during image loading, you don't need to re-create a dummy SDWebImageManager instance with the loader. If not provided, use the image manager's cache (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageLoader; /** A id instance which conforms to `SDImageCoder` protocol. It's used to override the default image coder for image decoding(including progressive) and encoding during the image loading process. If you use this context option, we will not always use `SDImageCodersManager.shared` to loop through all registered coders and find the suitable one. Instead, we will arbitrarily use the exact provided coder without extra checking (We may not call `canDecodeFromData:`). @note This is only useful for cases which you can ensure the loading url matches your coder, or you find it's too hard to write a common coder which can used for generic usage. This will bind the loading url with the coder logic, which is not always a good design, but possible. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCoder; /** A id instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. If you pass NSNull, the transformer feature will be disabled. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTransformer; /** A CGFloat raw value which specify the image scale factor. The number should be greater than or equal to 1.0. If not provide or the number is invalid, we will use the cache key to specify the scale factor. (NSNumber) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor; /** A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format). Defaults to YES. (NSNumber) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImagePreserveAspectRatio; /** A CGSize raw value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.imagePreserveAspectRatio`) the value size. @note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both. Defaults to CGSizeZero, which means no thumbnail generation at all. (NSValue) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize; /** A SDImageCacheType raw value which specify the source of cache to query. Specify `SDImageCacheTypeDisk` to query from disk cache only; `SDImageCacheTypeMemory` to query from memory only. And `SDImageCacheTypeAll` to query from both memory cache and disk cache. Specify `SDImageCacheTypeNone` is invalid and totally ignore the cache query. If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextQueryCacheType; /** A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache. If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time. If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextStoreCacheType; /** The same behavior like `SDWebImageContextQueryCacheType`, but control the query cache type for the original image when you use image transformer feature. This allows the detail control of cache query for these two images. For example, if you want to query the transformed image from both memory/disk cache, query the original image from disk cache only, use `[.queryCacheType : .all, .originalQueryCacheType : .disk]` If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which query the original full image data from disk cache after transformed image cache miss. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber) @note Which means, if you set this value to not be `.none`, we will query the original image from cache, then do transform with transformer, instead of actual downloading, which can save bandwidth usage. */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalQueryCacheType; /** The same behavior like `SDWebImageContextStoreCacheType`, but control the store cache type for the original image when you use image transformer feature. This allows the detail control of cache storage for these two images. For example, if you want to store the transformed image into both memory/disk cache, store the original image into disk cache only, use `[.storeCacheType : .all, .originalStoreCacheType : .disk]` If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which store the original full image data into disk cache after storing the transformed image. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber) @note This only store the original image, if you want to use the original image without downloading in next query, specify `SDWebImageContextOriginalQueryCacheType` as well. */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalStoreCacheType; /** A id instance which conforms to `SDImageCache` protocol. It's used to control the cache for original image when using the transformer. If you provide one, the original image (full size image) will query and write from that cache instance instead, the transformed image will query and write from the default `SDWebImageContextImageCache` instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalImageCache; /** A Class object which the instance is a `UIImage/NSImage` subclass and adopt `SDAnimatedImage` protocol. We will call `initWithData:scale:options:` to create the instance (or `initWithAnimatedCoder:scale:` when using progressive download) . If the instance create failed, fallback to normal `UIImage/NSImage`. This can be used to improve animated images rendering performance (especially memory usage on big animated images) with `SDAnimatedImageView` (Class). */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextAnimatedImageClass; /** A id instance to modify the image download request. It's used for downloader to modify the original request from URL and options. If you provide one, it will ignore the `requestModifier` in downloader and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextDownloadRequestModifier; /** A id instance to modify the image download response. It's used for downloader to modify the original response from URL and options. If you provide one, it will ignore the `responseModifier` in downloader and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextDownloadResponseModifier; /** A id instance to decrypt the image download data. This can be used for image data decryption, such as Base64 encoded image. If you provide one, it will ignore the `decryptor` in downloader and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextDownloadDecryptor; /** A id instance to convert an URL into a cache key. It's used when manager need cache key to use image cache. If you provide one, it will ignore the `cacheKeyFilter` in manager and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCacheKeyFilter; /** A id instance to convert the decoded image, the source downloaded data, to the actual data. It's used for manager to store image to the disk cache. If you provide one, it will ignore the `cacheSerializer` in manager and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCacheSerializer; ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDefine.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDefine.h" #import "UIImage+Metadata.h" #import "NSImage+Compatibility.h" #import "SDAssociatedObject.h" #pragma mark - Image scale static inline NSArray * _Nonnull SDImageScaleFactors() { return @[@2, @3]; } inline CGFloat SDImageScaleFactorForKey(NSString * _Nullable key) { CGFloat scale = 1; if (!key) { return scale; } // Check if target OS support scale #if SD_WATCH if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) #elif SD_UIKIT if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) #elif SD_MAC if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)]) #endif { // a@2x.png -> 8 if (key.length >= 8) { // Fast check BOOL isURL = [key hasPrefix:@"http://"] || [key hasPrefix:@"https://"]; for (NSNumber *scaleFactor in SDImageScaleFactors()) { // @2x. for file name and normal url NSString *fileScale = [NSString stringWithFormat:@"@%@x.", scaleFactor]; if ([key containsString:fileScale]) { scale = scaleFactor.doubleValue; return scale; } if (isURL) { // %402x. for url encode NSString *urlScale = [NSString stringWithFormat:@"%%40%@x.", scaleFactor]; if ([key containsString:urlScale]) { scale = scaleFactor.doubleValue; return scale; } } } } } return scale; } inline UIImage * _Nullable SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) { if (!image) { return nil; } CGFloat scale = SDImageScaleFactorForKey(key); return SDScaledImageForScaleFactor(scale, image); } inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage * _Nullable image) { if (!image) { return nil; } if (scale <= 1) { return image; } if (scale == image.scale) { return image; } UIImage *scaledImage; if (image.sd_isAnimated) { UIImage *animatedImage; #if SD_UIKIT || SD_WATCH // `UIAnimatedImage` images share the same size and scale. NSMutableArray *scaledImages = [NSMutableArray array]; for (UIImage *tempImage in image.images) { UIImage *tempScaledImage = [[UIImage alloc] initWithCGImage:tempImage.CGImage scale:scale orientation:tempImage.imageOrientation]; [scaledImages addObject:tempScaledImage]; } animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration]; animatedImage.sd_imageLoopCount = image.sd_imageLoopCount; #else // Animated GIF for `NSImage` need to grab `NSBitmapImageRep`; NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height); NSImageRep *imageRep = [image bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapImageRep = (NSBitmapImageRep *)imageRep; } if (bitmapImageRep) { NSSize size = NSMakeSize(image.size.width / scale, image.size.height / scale); animatedImage = [[NSImage alloc] initWithSize:size]; bitmapImageRep.size = size; [animatedImage addRepresentation:bitmapImageRep]; } #endif scaledImage = animatedImage; } else { #if SD_UIKIT || SD_WATCH scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; #else scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:kCGImagePropertyOrientationUp]; #endif } SDImageCopyAssociatedObject(image, scaledImage); return scaledImage; } #pragma mark - Context option SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey"; SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager"; SDWebImageContextOption const SDWebImageContextImageCache = @"imageCache"; SDWebImageContextOption const SDWebImageContextImageLoader = @"imageLoader"; SDWebImageContextOption const SDWebImageContextImageCoder = @"imageCoder"; SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer"; SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor"; SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio"; SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize"; SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType"; SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType"; SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType"; SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType"; SDWebImageContextOption const SDWebImageContextOriginalImageCache = @"originalImageCache"; SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass"; SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier"; SDWebImageContextOption const SDWebImageContextDownloadResponseModifier = @"downloadResponseModifier"; SDWebImageContextOption const SDWebImageContextDownloadDecryptor = @"downloadDecryptor"; SDWebImageContextOption const SDWebImageContextCacheKeyFilter = @"cacheKeyFilter"; SDWebImageContextOption const SDWebImageContextCacheSerializer = @"cacheSerializer"; ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloader.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDWebImageOperation.h" #import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderRequestModifier.h" #import "SDWebImageDownloaderResponseModifier.h" #import "SDWebImageDownloaderDecryptor.h" #import "SDImageLoader.h" /// Downloader options typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { /** * Put the download in the low queue priority and task priority. */ SDWebImageDownloaderLowPriority = 1 << 0, /** * This flag enables progressive download, the image is displayed progressively during download as a browser would do. */ SDWebImageDownloaderProgressiveLoad = 1 << 1, /** * By default, request prevent the use of NSURLCache. With this flag, NSURLCache * is used with default policies. */ SDWebImageDownloaderUseNSURLCache = 1 << 2, /** * Call completion block with nil image/imageData if the image was read from NSURLCache * And the error code is `SDWebImageErrorCacheNotModified` * This flag should be combined with `SDWebImageDownloaderUseNSURLCache`. */ SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ SDWebImageDownloaderContinueInBackground = 1 << 4, /** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageDownloaderHandleCookies = 1 << 5, /** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, /** * Put the download in the high queue priority and task priority. */ SDWebImageDownloaderHighPriority = 1 << 7, /** * By default, images are decoded respecting their original size. On iOS, this flag will scale down the * images to a size compatible with the constrained memory of devices. * This flag take no effect if `SDWebImageDownloaderAvoidDecodeImage` is set. And it will be ignored if `SDWebImageDownloaderProgressiveLoad` is set. */ SDWebImageDownloaderScaleDownLargeImages = 1 << 8, /** * By default, we will decode the image in the background during cache query and download from the network. This can help to improve performance because when rendering image on the screen, it need to be firstly decoded. But this happen on the main queue by Core Animation. * However, this process may increase the memory usage as well. If you are experiencing a issue due to excessive memory consumption, This flag can prevent decode the image. */ SDWebImageDownloaderAvoidDecodeImage = 1 << 9, /** * By default, we decode the animated image. This flag can force decode the first frame only and produce the static image. */ SDWebImageDownloaderDecodeFirstFrameOnly = 1 << 10, /** * By default, for `SDAnimatedImage`, we decode the animated image frame during rendering to reduce memory usage. This flag actually trigger `preloadAllAnimatedImageFrames = YES` after image load from network */ SDWebImageDownloaderPreloadAllFrames = 1 << 11, /** * By default, when you use `SDWebImageContextAnimatedImageClass` context option (like using `SDAnimatedImageView` which designed to use `SDAnimatedImage`), we may still use `UIImage` when the memory cache hit, or image decoder is not available, to behave as a fallback solution. * Using this option, can ensure we always produce image with your provided class. If failed, a error with code `SDWebImageErrorBadImageData` will been used. * Note this options is not compatible with `SDWebImageDownloaderDecodeFirstFrameOnly`, which always produce a UIImage/NSImage. */ SDWebImageDownloaderMatchAnimatedImageClass = 1 << 12, }; FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStartNotification; FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadReceiveResponseNotification; FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStopNotification; FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadFinishNotification; typedef SDImageLoaderProgressBlock SDWebImageDownloaderProgressBlock; typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock; /** * A token associated with each download. Can be used to cancel a download */ @interface SDWebImageDownloadToken : NSObject /** Cancel the current download. */ - (void)cancel; /** The download's URL. */ @property (nonatomic, strong, nullable, readonly) NSURL *url; /** The download's request. */ @property (nonatomic, strong, nullable, readonly) NSURLRequest *request; /** The download's response. */ @property (nonatomic, strong, nullable, readonly) NSURLResponse *response; /** The download's metrics. This will be nil if download operation does not support metrics. */ @property (nonatomic, strong, nullable, readonly) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); @end /** * Asynchronous downloader dedicated and optimized for image loading. */ @interface SDWebImageDownloader : NSObject /** * Downloader Config object - storing all kind of settings. * Most config properties support dynamic changes during download, except something like `sessionConfiguration`, see `SDWebImageDownloaderConfig` for more detail. */ @property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config; /** * Set the request modifier to modify the original download request before image load. * This request modifier method will be called for each downloading image request. Return the original request means no modification. Return nil will cancel the download request. * Defaults to nil, means does not modify the original download request. * @note If you want to modify single request, consider using `SDWebImageContextDownloadRequestModifier` context option. */ @property (nonatomic, strong, nullable) id requestModifier; /** * Set the response modifier to modify the original download response during image load. * This request modifier method will be called for each downloading image response. Return the original response means no modification. Return nil will mark current download as cancelled. * Defaults to nil, means does not modify the original download response. * @note If you want to modify single response, consider using `SDWebImageContextDownloadResponseModifier` context option. */ @property (nonatomic, strong, nullable) id responseModifier; /** * Set the decryptor to decrypt the original download data before image decoding. This can be used for encrypted image data, like Base64. * This decryptor method will be called for each downloading image data. Return the original data means no modification. Return nil will mark this download failed. * Defaults to nil, means does not modify the original download data. * @note When using decryptor, progressive decoding will be disabled, to avoid data corrupt issue. * @note If you want to decrypt single download data, consider using `SDWebImageContextDownloadDecryptor` context option. */ @property (nonatomic, strong, nullable) id decryptor; /** * The configuration in use by the internal NSURLSession. If you want to provide a custom sessionConfiguration, use `SDWebImageDownloaderConfig.sessionConfiguration` and create a new downloader instance. @note This is immutable according to NSURLSession's documentation. Mutating this object directly has no effect. */ @property (nonatomic, readonly, nonnull) NSURLSessionConfiguration *sessionConfiguration; /** * Gets/Sets the download queue suspension state. */ @property (nonatomic, assign, getter=isSuspended) BOOL suspended; /** * Shows the current amount of downloads that still need to be downloaded */ @property (nonatomic, assign, readonly) NSUInteger currentDownloadCount; /** * Returns the global shared downloader instance. Which use the `SDWebImageDownloaderConfig.defaultDownloaderConfig` config. */ @property (nonatomic, class, readonly, nonnull) SDWebImageDownloader *sharedDownloader; /** Creates an instance of a downloader with specified downloader config. You can specify session configuration, timeout or operation class through downloader config. @param config The downloader config. If you specify nil, the `defaultDownloaderConfig` will be used. @return new instance of downloader class */ - (nonnull instancetype)initWithConfig:(nullable SDWebImageDownloaderConfig *)config NS_DESIGNATED_INITIALIZER; /** * Set a value for a HTTP header to be appended to each download HTTP request. * * @param value The value for the header field. Use `nil` value to remove the header field. * @param field The name of the header field to set. */ - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field; /** * Returns the value of the specified HTTP header field. * * @return The value associated with the header field field, or `nil` if there is no corresponding header field. */ - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field; /** * Creates a SDWebImageDownloader async downloader instance with a given URL * * The delegate will be informed when the image is finish downloaded or an error has happen. * * @see SDWebImageDownloaderDelegate * * @param url The URL to the image to download * @param completedBlock A block called once the download is completed. * If the download succeeded, the image parameter is set, in case of error, * error parameter is set with the error. The last parameter is always YES * if SDWebImageDownloaderProgressiveDownload isn't use. With the * SDWebImageDownloaderProgressiveDownload option, this block is called * repeatedly with the partial image object and the finished argument set to NO * before to be called a last time with the full image and finished argument * set to YES. In case of error, the finished argument is always YES. * * @return A token (SDWebImageDownloadToken) that can be used to cancel this operation */ - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; /** * Creates a SDWebImageDownloader async downloader instance with a given URL * * The delegate will be informed when the image is finish downloaded or an error has happen. * * @see SDWebImageDownloaderDelegate * * @param url The URL to the image to download * @param options The options to be used for this download * @param progressBlock A block called repeatedly while the image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called once the download is completed. * If the download succeeded, the image parameter is set, in case of error, * error parameter is set with the error. The last parameter is always YES * if SDWebImageDownloaderProgressiveLoad isn't use. With the * SDWebImageDownloaderProgressiveLoad option, this block is called * repeatedly with the partial image object and the finished argument set to NO * before to be called a last time with the full image and finished argument * set to YES. In case of error, the finished argument is always YES. * * @return A token (SDWebImageDownloadToken) that can be used to cancel this operation */ - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; /** * Creates a SDWebImageDownloader async downloader instance with a given URL * * The delegate will be informed when the image is finish downloaded or an error has happen. * * @see SDWebImageDownloaderDelegate * * @param url The URL to the image to download * @param options The options to be used for this download * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called repeatedly while the image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called once the download is completed. * * @return A token (SDWebImageDownloadToken) that can be used to cancel this operation */ - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; /** * Cancels all download operations in the queue */ - (void)cancelAllDownloads; /** * Invalidates the managed session, optionally canceling pending operations. * @note If you use custom downloader instead of the shared downloader, you need call this method when you do not use it to avoid memory leak * @param cancelPendingOperations Whether or not to cancel pending operations. * @note Calling this method on the shared downloader has no effect. */ - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations; @end /** SDWebImageDownloader is the built-in image loader conform to `SDImageLoader`. Which provide the HTTP/HTTPS/FTP download, or local file URL using NSURLSession. However, this downloader class itself also support customization for advanced users. You can specify `operationClass` in download config to custom download operation, See `SDWebImageDownloaderOperation`. If you want to provide some image loader which beyond network or local file, consider to create your own custom class conform to `SDImageLoader`. */ @interface SDWebImageDownloader (SDImageLoader) @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloader.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloader.h" #import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderOperation.h" #import "SDWebImageError.h" #import "SDInternalMacros.h" NSNotificationName const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; NSNotificationName const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification"; NSNotificationName const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; NSNotificationName const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; @interface SDWebImageDownloadToken () @property (nonatomic, strong, nullable, readwrite) NSURL *url; @property (nonatomic, strong, nullable, readwrite) NSURLRequest *request; @property (nonatomic, strong, nullable, readwrite) NSURLResponse *response; @property (nonatomic, strong, nullable, readwrite) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); @property (nonatomic, weak, nullable, readwrite) id downloadOperationCancelToken; @property (nonatomic, weak, nullable) NSOperation *downloadOperation; @property (nonatomic, assign, getter=isCancelled) BOOL cancelled; - (nonnull instancetype)init NS_UNAVAILABLE; + (nonnull instancetype)new NS_UNAVAILABLE; - (nonnull instancetype)initWithDownloadOperation:(nullable NSOperation *)downloadOperation; @end @interface SDWebImageDownloader () @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue; @property (strong, nonatomic, nonnull) NSMutableDictionary *> *URLOperations; @property (strong, nonatomic, nullable) NSMutableDictionary *HTTPHeaders; // The session in which data tasks will run @property (strong, nonatomic) NSURLSession *session; @end @implementation SDWebImageDownloader { SD_LOCK_DECLARE(_HTTPHeadersLock); // A lock to keep the access to `HTTPHeaders` thread-safe SD_LOCK_DECLARE(_operationsLock); // A lock to keep the access to `URLOperations` thread-safe } + (void)initialize { // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import if (NSClassFromString(@"SDNetworkActivityIndicator")) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")]; #pragma clang diagnostic pop // Remove observer in case it was previously added. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"startActivity") name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"stopActivity") name:SDWebImageDownloadStopNotification object:nil]; } } + (nonnull instancetype)sharedDownloader { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig]; } - (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config { self = [super init]; if (self) { if (!config) { config = SDWebImageDownloaderConfig.defaultDownloaderConfig; } _config = [config copy]; [_config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) options:0 context:SDWebImageDownloaderContext]; _downloadQueue = [NSOperationQueue new]; _downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads; _downloadQueue.name = @"com.hackemist.SDWebImageDownloader"; _URLOperations = [NSMutableDictionary new]; NSMutableDictionary *headerDictionary = [NSMutableDictionary dictionary]; NSString *userAgent = nil; #if SD_UIKIT // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]]; #elif SD_WATCH // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]]; #elif SD_MAC userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]; #endif if (userAgent) { if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSMutableString *mutableUserAgent = [userAgent mutableCopy]; if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { userAgent = mutableUserAgent; } } headerDictionary[@"User-Agent"] = userAgent; } headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8"; _HTTPHeaders = headerDictionary; SD_LOCK_INIT(_HTTPHeadersLock); SD_LOCK_INIT(_operationsLock); NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration; if (!sessionConfiguration) { sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; } /** * Create the session for this task * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate * method calls and completion handler calls. */ _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; } return self; } - (void)dealloc { [self.downloadQueue cancelAllOperations]; [self.config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) context:SDWebImageDownloaderContext]; // Invalide the URLSession after all operations been cancelled [self.session invalidateAndCancel]; self.session = nil; } - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations { if (self == [SDWebImageDownloader sharedDownloader]) { return; } if (cancelPendingOperations) { [self.session invalidateAndCancel]; } else { [self.session finishTasksAndInvalidate]; } } - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field { if (!field) { return; } SD_LOCK(_HTTPHeadersLock); [self.HTTPHeaders setValue:value forKey:field]; SD_UNLOCK(_HTTPHeadersLock); } - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field { if (!field) { return nil; } SD_LOCK(_HTTPHeadersLock); NSString *value = [self.HTTPHeaders objectForKey:field]; SD_UNLOCK(_HTTPHeadersLock); return value; } - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url completed:(SDWebImageDownloaderCompletedBlock)completedBlock { return [self downloadImageWithURL:url options:0 progress:nil completed:completedBlock]; } - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { return [self downloadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock]; } - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock { // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data. if (url == nil) { if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}]; completedBlock(nil, nil, error, YES); } return nil; } SD_LOCK(_operationsLock); id downloadOperationCancelToken; NSOperation *operation = [self.URLOperations objectForKey:url]; // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`. if (!operation || operation.isFinished || operation.isCancelled) { operation = [self createDownloaderOperationWithUrl:url options:options context:context]; if (!operation) { SD_UNLOCK(_operationsLock); if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}]; completedBlock(nil, nil, error, YES); } return nil; } @weakify(self); operation.completionBlock = ^{ @strongify(self); if (!self) { return; } SD_LOCK(self->_operationsLock); [self.URLOperations removeObjectForKey:url]; SD_UNLOCK(self->_operationsLock); }; self.URLOperations[url] = operation; // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers. downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; // Add operation to operation queue only after all configuration done according to Apple's doc. // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock. [self.downloadQueue addOperation:operation]; } else { // When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue) // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes. @synchronized (operation) { downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; } if (!operation.isExecuting) { if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } else { operation.queuePriority = NSOperationQueuePriorityNormal; } } } SD_UNLOCK(_operationsLock); SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation]; token.url = url; token.request = operation.request; token.downloadOperationCancelToken = downloadOperationCancelToken; return token; } - (nullable NSOperation *)createDownloaderOperationWithUrl:(nonnull NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context { NSTimeInterval timeoutInterval = self.config.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; } // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData; NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval]; mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies); mutableRequest.HTTPShouldUsePipelining = YES; SD_LOCK(_HTTPHeadersLock); mutableRequest.allHTTPHeaderFields = self.HTTPHeaders; SD_UNLOCK(_HTTPHeadersLock); // Context Option SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } // Request Modifier id requestModifier; if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) { requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier]; } else { requestModifier = self.requestModifier; } NSURLRequest *request; if (requestModifier) { NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]]; // If modified request is nil, early return if (!modifiedRequest) { return nil; } else { request = [modifiedRequest copy]; } } else { request = [mutableRequest copy]; } // Response Modifier id responseModifier; if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) { responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier]; } else { responseModifier = self.responseModifier; } if (responseModifier) { mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier; } // Decryptor id decryptor; if ([context valueForKey:SDWebImageContextDownloadDecryptor]) { decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor]; } else { decryptor = self.decryptor; } if (decryptor) { mutableContext[SDWebImageContextDownloadDecryptor] = decryptor; } context = [mutableContext copy]; // Operation Class Class operationClass = self.config.operationClass; if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) { // Custom operation class } else { operationClass = [SDWebImageDownloaderOperation class]; } NSOperation *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context]; if ([operation respondsToSelector:@selector(setCredential:)]) { if (self.config.urlCredential) { operation.credential = self.config.urlCredential; } else if (self.config.username && self.config.password) { operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession]; } } if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) { operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1); } if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // Emulate LIFO execution order by systematically, each previous adding operation can dependency the new operation // This can gurantee the new operation to be execulated firstly, even if when some operations finished, meanwhile you appending new operations // Just make last added operation dependents new operation can not solve this problem. See test case #test15DownloaderLIFOExecutionOrder for (NSOperation *pendingOperation in self.downloadQueue.operations) { [pendingOperation addDependency:operation]; } } return operation; } - (void)cancelAllDownloads { [self.downloadQueue cancelAllOperations]; } #pragma mark - Properties - (BOOL)isSuspended { return self.downloadQueue.isSuspended; } - (void)setSuspended:(BOOL)suspended { self.downloadQueue.suspended = suspended; } - (NSUInteger)currentDownloadCount { return self.downloadQueue.operationCount; } - (NSURLSessionConfiguration *)sessionConfiguration { return self.session.configuration; } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == SDWebImageDownloaderContext) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxConcurrentDownloads))]) { self.downloadQueue.maxConcurrentOperationCount = self.config.maxConcurrentDownloads; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark Helper methods - (NSOperation *)operationWithTask:(NSURLSessionTask *)task { NSOperation *returnOperation = nil; for (NSOperation *operation in self.downloadQueue.operations) { if ([operation respondsToSelector:@selector(dataTask)]) { // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes. NSURLSessionTask *operationTask; @synchronized (operation) { operationTask = operation.dataTask; } if (operationTask.taskIdentifier == task.taskIdentifier) { returnOperation = operation; break; } } } return returnOperation; } #pragma mark NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:dataTask]; if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) { [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; } else { if (completionHandler) { completionHandler(NSURLSessionResponseAllow); } } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:dataTask]; if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) { [dataOperation URLSession:session dataTask:dataTask didReceiveData:data]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:dataTask]; if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) { [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; } else { if (completionHandler) { completionHandler(proposedResponse); } } } #pragma mark NSURLSessionTaskDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:task]; if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { [dataOperation URLSession:session task:task didCompleteWithError:error]; } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:task]; if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) { [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler]; } else { if (completionHandler) { completionHandler(request); } } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:task]; if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) { [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; } else { if (completionHandler) { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) { // Identify the operation that runs this task and pass it the delegate method NSOperation *dataOperation = [self operationWithTask:task]; if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) { [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics]; } } @end @implementation SDWebImageDownloadToken - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadReceiveResponseNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadStopNotification object:nil]; } - (instancetype)initWithDownloadOperation:(NSOperation *)downloadOperation { self = [super init]; if (self) { _downloadOperation = downloadOperation; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidStop:) name:SDWebImageDownloadStopNotification object:downloadOperation]; } return self; } - (void)downloadDidReceiveResponse:(NSNotification *)notification { NSOperation *downloadOperation = notification.object; if (downloadOperation && downloadOperation == self.downloadOperation) { self.response = downloadOperation.response; } } - (void)downloadDidStop:(NSNotification *)notification { NSOperation *downloadOperation = notification.object; if (downloadOperation && downloadOperation == self.downloadOperation) { if ([downloadOperation respondsToSelector:@selector(metrics)]) { if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, watchOS 3.0, *)) { self.metrics = downloadOperation.metrics; } } } } - (void)cancel { @synchronized (self) { if (self.isCancelled) { return; } self.cancelled = YES; [self.downloadOperation cancel:self.downloadOperationCancelToken]; self.downloadOperationCancelToken = nil; } } @end @implementation SDWebImageDownloader (SDImageLoader) - (BOOL)canRequestImageForURL:(NSURL *)url { return [self canRequestImageForURL:url options:0 context:nil]; } - (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context { if (!url) { return NO; } // Always pass YES to let URLSession or custom download operation to determine return YES; } - (id)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock { UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage]; SDWebImageDownloaderOptions downloaderOptions = 0; if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad; if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage; if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly; if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames; if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass; if (cachedImage && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad; // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock]; } - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error { return [self shouldBlockFailedURLWithURL:url error:error options:0 context:nil]; } - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error options:(SDWebImageOptions)options context:(SDWebImageContext *)context { BOOL shouldBlockFailedURL; // Filter the error domain and check error codes if ([error.domain isEqualToString:SDWebImageErrorDomain]) { shouldBlockFailedURL = ( error.code == SDWebImageErrorInvalidURL || error.code == SDWebImageErrorBadImageData); } else if ([error.domain isEqualToString:NSURLErrorDomain]) { shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost && error.code != NSURLErrorNetworkConnectionLost); } else { shouldBlockFailedURL = NO; } return shouldBlockFailedURL; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderConfig.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// Operation execution order typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { /** * Default value. All download operations will execute in queue style (first-in-first-out). */ SDWebImageDownloaderFIFOExecutionOrder, /** * All download operations will execute in stack style (last-in-first-out). */ SDWebImageDownloaderLIFOExecutionOrder }; /** The class contains all the config for image downloader @note This class conform to NSCopying, make sure to add the property in `copyWithZone:` as well. */ @interface SDWebImageDownloaderConfig : NSObject /** Gets the default downloader config used for shared instance or initialization when it does not provide any downloader config. Such as `SDWebImageDownloader.sharedDownloader`. @note You can modify the property on default downloader config, which can be used for later created downloader instance. The already created downloader instance does not get affected. */ @property (nonatomic, class, readonly, nonnull) SDWebImageDownloaderConfig *defaultDownloaderConfig; /** * The maximum number of concurrent downloads. * Defaults to 6. */ @property (nonatomic, assign) NSInteger maxConcurrentDownloads; /** * The timeout value (in seconds) for each download operation. * Defaults to 15.0. */ @property (nonatomic, assign) NSTimeInterval downloadTimeout; /** * The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value. However, the final finish download progress callback does not get effected. * The value should be 0.0-1.0. * @note If you're using progressive decoding feature, this will also effect the image refresh rate. * @note This value may enhance the performance if you don't want progress callback too frequently. * Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately. */ @property (nonatomic, assign) double minimumProgressInterval; /** * The custom session configuration in use by NSURLSession. If you don't provide one, we will use `defaultSessionConfiguration` instead. * Defatuls to nil. * @note This property does not support dynamic changes, means it's immutable after the downloader instance initialized. */ @property (nonatomic, strong, nullable) NSURLSessionConfiguration *sessionConfiguration; /** * Gets/Sets a subclass of `SDWebImageDownloaderOperation` as the default * `NSOperation` to be used each time SDWebImage constructs a request * operation to download an image. * Defaults to nil. * @note Passing `NSOperation` to set as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`. */ @property (nonatomic, assign, nullable) Class operationClass; /** * Changes download operations execution order. * Defaults to `SDWebImageDownloaderFIFOExecutionOrder`. */ @property (nonatomic, assign) SDWebImageDownloaderExecutionOrder executionOrder; /** * Set the default URL credential to be set for request operations. * Defaults to nil. */ @property (nonatomic, copy, nullable) NSURLCredential *urlCredential; /** * Set username using for HTTP Basic authentication. * Defaults to nil. */ @property (nonatomic, copy, nullable) NSString *username; /** * Set password using for HTTP Basic authentication. * Defaults to nil. */ @property (nonatomic, copy, nullable) NSString *password; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderConfig.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderConfig.h" static SDWebImageDownloaderConfig * _defaultDownloaderConfig; @implementation SDWebImageDownloaderConfig + (SDWebImageDownloaderConfig *)defaultDownloaderConfig { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _defaultDownloaderConfig = [SDWebImageDownloaderConfig new]; }); return _defaultDownloaderConfig; } - (instancetype)init { self = [super init]; if (self) { _maxConcurrentDownloads = 6; _downloadTimeout = 15.0; _executionOrder = SDWebImageDownloaderFIFOExecutionOrder; } return self; } - (id)copyWithZone:(NSZone *)zone { SDWebImageDownloaderConfig *config = [[[self class] allocWithZone:zone] init]; config.maxConcurrentDownloads = self.maxConcurrentDownloads; config.downloadTimeout = self.downloadTimeout; config.minimumProgressInterval = self.minimumProgressInterval; config.sessionConfiguration = [self.sessionConfiguration copyWithZone:zone]; config.operationClass = self.operationClass; config.executionOrder = self.executionOrder; config.urlCredential = self.urlCredential; config.username = self.username; config.password = self.password; return config; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderDecryptor.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NSData * _Nullable (^SDWebImageDownloaderDecryptorBlock)(NSData * _Nonnull data, NSURLResponse * _Nullable response); /** This is the protocol for downloader decryptor. Which decrypt the original encrypted data before decoding. Note progressive decoding is not compatible for decryptor. We can use a block to specify the downloader decryptor. But Using protocol can make this extensible, and allow Swift user to use it easily instead of using `@convention(block)` to store a block into context options. */ @protocol SDWebImageDownloaderDecryptor /// Decrypt the original download data and return a new data. You can use this to decrypt the data using your preferred algorithm. /// @param data The original download data /// @param response The URL response for data. If you modify the original URL response via response modifier, the modified version will be here. This arg is nullable. /// @note If nil is returned, the image download will be marked as failed with error `SDWebImageErrorBadImageData` - (nullable NSData *)decryptedDataWithData:(nonnull NSData *)data response:(nullable NSURLResponse *)response; @end /** A downloader response modifier class with block. */ @interface SDWebImageDownloaderDecryptor : NSObject /// Create the data decryptor with block /// @param block A block to control decrypt logic - (nonnull instancetype)initWithBlock:(nonnull SDWebImageDownloaderDecryptorBlock)block; /// Create the data decryptor with block /// @param block A block to control decrypt logic + (nonnull instancetype)decryptorWithBlock:(nonnull SDWebImageDownloaderDecryptorBlock)block; @end /// Convenience way to create decryptor for common data encryption. @interface SDWebImageDownloaderDecryptor (Conveniences) /// Base64 Encoded image data decryptor @property (class, readonly, nonnull) SDWebImageDownloaderDecryptor *base64Decryptor; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderDecryptor.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderDecryptor.h" @interface SDWebImageDownloaderDecryptor () @property (nonatomic, copy, nonnull) SDWebImageDownloaderDecryptorBlock block; @end @implementation SDWebImageDownloaderDecryptor - (instancetype)initWithBlock:(SDWebImageDownloaderDecryptorBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)decryptorWithBlock:(SDWebImageDownloaderDecryptorBlock)block { SDWebImageDownloaderDecryptor *decryptor = [[SDWebImageDownloaderDecryptor alloc] initWithBlock:block]; return decryptor; } - (nullable NSData *)decryptedDataWithData:(nonnull NSData *)data response:(nullable NSURLResponse *)response { if (!self.block) { return nil; } return self.block(data, response); } @end @implementation SDWebImageDownloaderDecryptor (Conveniences) + (SDWebImageDownloaderDecryptor *)base64Decryptor { static SDWebImageDownloaderDecryptor *decryptor; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ decryptor = [SDWebImageDownloaderDecryptor decryptorWithBlock:^NSData * _Nullable(NSData * _Nonnull data, NSURLResponse * _Nullable response) { NSData *modifiedData = [[NSData alloc] initWithBase64EncodedData:data options:NSDataBase64DecodingIgnoreUnknownCharacters]; return modifiedData; }]; }); return decryptor; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageDownloader.h" #import "SDWebImageOperation.h" /** Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol For the description about these methods, see `SDWebImageDownloaderOperation` @note If your custom operation class does not use `NSURLSession` at all, do not implement the optional methods and session delegate methods. */ @protocol SDWebImageDownloaderOperation @required - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options; - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context; - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; - (BOOL)cancel:(nullable id)token; @property (strong, nonatomic, readonly, nullable) NSURLRequest *request; @property (strong, nonatomic, readonly, nullable) NSURLResponse *response; @optional @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; @property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); @property (strong, nonatomic, nullable) NSURLCredential *credential; @property (assign, nonatomic) double minimumProgressInterval; @end /** The download operation class for SDWebImageDownloader. */ @interface SDWebImageDownloaderOperation : NSOperation /** * The request used by the operation's task. */ @property (strong, nonatomic, readonly, nullable) NSURLRequest *request; /** * The response returned by the operation's task. */ @property (strong, nonatomic, readonly, nullable) NSURLResponse *response; /** * The operation's task */ @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; /** * The collected metrics from `-URLSession:task:didFinishCollectingMetrics:`. * This can be used to collect the network metrics like download duration, DNS lookup duration, SSL handshake duration, etc. See Apple's documentation: https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics */ @property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); /** * The credential used for authentication challenges in `-URLSession:task:didReceiveChallenge:completionHandler:`. * * This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. */ @property (strong, nonatomic, nullable) NSURLCredential *credential; /** * The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value. However, the final finish download progress callback does not get effected. * The value should be 0.0-1.0. * @note If you're using progressive decoding feature, this will also effect the image refresh rate. * @note This value may enhance the performance if you don't want progress callback too frequently. * Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately. */ @property (assign, nonatomic) double minimumProgressInterval; /** * The options for the receiver. */ @property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options; /** * The context for the receiver. */ @property (copy, nonatomic, readonly, nullable) SDWebImageContext *context; /** * Initializes a `SDWebImageDownloaderOperation` object * * @see SDWebImageDownloaderOperation * * @param request the URL request * @param session the URL session in which this operation will run * @param options downloader options * * @return the initialized instance */ - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options; /** * Initializes a `SDWebImageDownloaderOperation` object * * @see SDWebImageDownloaderOperation * * @param request the URL request * @param session the URL session in which this operation will run * @param options downloader options * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * * @return the initialized instance */ - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context NS_DESIGNATED_INITIALIZER; /** * Adds handlers for progress and completion. Returns a token that can be passed to -cancel: to cancel this set of * callbacks. * * @param progressBlock the block executed when a new chunk of data arrives. * @note the progress block is executed on a background queue * @param completedBlock the block executed when the download is done. * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue * * @return the token to use to cancel this set of handlers */ - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; /** * Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled. * * @param token the token representing a set of callbacks to cancel * * @return YES if the operation was stopped because this was the last token to be canceled. NO otherwise. */ - (BOOL)cancel:(nullable id)token; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderOperation.h" #import "SDWebImageError.h" #import "SDInternalMacros.h" #import "SDWebImageDownloaderResponseModifier.h" #import "SDWebImageDownloaderDecryptor.h" static NSString *const kProgressCallbackKey = @"progress"; static NSString *const kCompletedCallbackKey = @"completed"; typedef NSMutableDictionary SDCallbacksDictionary; @interface SDWebImageDownloaderOperation () @property (strong, nonatomic, nonnull) NSMutableArray *callbackBlocks; @property (assign, nonatomic, readwrite) SDWebImageDownloaderOptions options; @property (copy, nonatomic, readwrite, nullable) SDWebImageContext *context; @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (strong, nonatomic, nullable) NSMutableData *imageData; @property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse` @property (assign, nonatomic) NSUInteger expectedSize; // may be 0 @property (assign, nonatomic) NSUInteger receivedSize; @property (strong, nonatomic, nullable, readwrite) NSURLResponse *response; @property (strong, nonatomic, nullable) NSError *responseError; @property (assign, nonatomic) double previousProgress; // previous progress percent @property (strong, nonatomic, nullable) id responseModifier; // modify original URLResponse @property (strong, nonatomic, nullable) id decryptor; // decrypt image data // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run // the task associated with this operation @property (weak, nonatomic, nullable) NSURLSession *unownedSession; // This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one @property (strong, nonatomic, nullable) NSURLSession *ownedSession; @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask; @property (strong, nonatomic, readwrite, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); @property (strong, nonatomic, nonnull) NSOperationQueue *coderQueue; // the serial operation queue to do image decoding #if SD_UIKIT @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; #endif @end @implementation SDWebImageDownloaderOperation @synthesize executing = _executing; @synthesize finished = _finished; - (nonnull instancetype)init { return [self initWithRequest:nil inSession:nil options:0]; } - (instancetype)initWithRequest:(NSURLRequest *)request inSession:(NSURLSession *)session options:(SDWebImageDownloaderOptions)options { return [self initWithRequest:request inSession:session options:options context:nil]; } - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context { if ((self = [super init])) { _request = [request copy]; _options = options; _context = [context copy]; _callbackBlocks = [NSMutableArray new]; _responseModifier = context[SDWebImageContextDownloadResponseModifier]; _decryptor = context[SDWebImageContextDownloadDecryptor]; _executing = NO; _finished = NO; _expectedSize = 0; _unownedSession = session; _coderQueue = [NSOperationQueue new]; _coderQueue.maxConcurrentOperationCount = 1; #if SD_UIKIT _backgroundTaskId = UIBackgroundTaskInvalid; #endif } return self; } - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock { SDCallbacksDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; @synchronized (self) { [self.callbackBlocks addObject:callbacks]; } return callbacks; } - (nullable NSArray *)callbacksForKey:(NSString *)key { NSMutableArray *callbacks; @synchronized (self) { callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy]; } // We need to remove [NSNull null] because there might not always be a progress block for each callback [callbacks removeObjectIdenticalTo:[NSNull null]]; return [callbacks copy]; // strip mutability here } - (BOOL)cancel:(nullable id)token { if (!token) return NO; BOOL shouldCancel = NO; @synchronized (self) { NSMutableArray *tempCallbackBlocks = [self.callbackBlocks mutableCopy]; [tempCallbackBlocks removeObjectIdenticalTo:token]; if (tempCallbackBlocks.count == 0) { shouldCancel = YES; } } if (shouldCancel) { // Cancel operation running and callback last token's completion block [self cancel]; } else { // Only callback this token's completion block @synchronized (self) { [self.callbackBlocks removeObjectIdenticalTo:token]; } SDWebImageDownloaderCompletedBlock completedBlock = [token valueForKey:kCompletedCallbackKey]; dispatch_main_async_safe(^{ if (completedBlock) { completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}], YES); } }); } return shouldCancel; } - (void)start { @synchronized (self) { if (self.isCancelled) { if (!self.isFinished) self.finished = YES; // Operation cancelled by user before sending the request [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]]; [self reset]; return; } #if SD_UIKIT Class UIApplicationClass = NSClassFromString(@"UIApplication"); BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)]; if (hasApplication && [self shouldContinueWhenAppEntersBackground]) { __weak typeof(self) wself = self; UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)]; self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{ [wself cancel]; }]; } #endif NSURLSession *session = self.unownedSession; if (!session) { NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfig.timeoutIntervalForRequest = 15; /** * Create the session for this task * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate * method calls and completion handler calls. */ session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; self.ownedSession = session; } if (self.options & SDWebImageDownloaderIgnoreCachedResponse) { // Grab the cached data for later check NSURLCache *URLCache = session.configuration.URLCache; if (!URLCache) { URLCache = [NSURLCache sharedURLCache]; } NSCachedURLResponse *cachedResponse; // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483 @synchronized (URLCache) { cachedResponse = [URLCache cachedResponseForRequest:self.request]; } if (cachedResponse) { self.cachedData = cachedResponse.data; } } if (!session.delegate) { // Session been invalid and has no delegate at all [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Session delegate is nil and invalid"}]]; [self reset]; return; } self.dataTask = [session dataTaskWithRequest:self.request]; self.executing = YES; } if (self.dataTask) { if (self.options & SDWebImageDownloaderHighPriority) { self.dataTask.priority = NSURLSessionTaskPriorityHigh; self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive; } else if (self.options & SDWebImageDownloaderLowPriority) { self.dataTask.priority = NSURLSessionTaskPriorityLow; self.coderQueue.qualityOfService = NSQualityOfServiceBackground; } else { self.dataTask.priority = NSURLSessionTaskPriorityDefault; self.coderQueue.qualityOfService = NSQualityOfServiceDefault; } [self.dataTask resume]; for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, NSURLResponseUnknownLength, self.request.URL); } __block typeof(self) strongSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf]; }); } else { if (!self.isFinished) self.finished = YES; [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]]; [self reset]; } } - (void)cancel { @synchronized (self) { [self cancelInternal]; } } - (void)cancelInternal { if (self.isFinished) return; [super cancel]; __block typeof(self) strongSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf]; }); if (self.dataTask) { // Cancel the URLSession, `URLSession:task:didCompleteWithError:` delegate callback will be ignored [self.dataTask cancel]; self.dataTask = nil; } // NSOperation disallow setFinished=YES **before** operation's start method been called // We check for the initialized status, which is isExecuting == NO && isFinished = NO // Ony update for non-intialized status, which is !(isExecuting == NO && isFinished = NO), or if (self.isExecuting || self.isFinished) {...} if (self.isExecuting || self.isFinished) { if (self.isExecuting) self.executing = NO; if (!self.isFinished) self.finished = YES; } // Operation cancelled by user during sending the request [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}]]; [self reset]; } - (void)done { self.finished = YES; self.executing = NO; [self reset]; } - (void)reset { @synchronized (self) { [self.callbackBlocks removeAllObjects]; self.dataTask = nil; if (self.ownedSession) { [self.ownedSession invalidateAndCancel]; self.ownedSession = nil; } #if SD_UIKIT if (self.backgroundTaskId != UIBackgroundTaskInvalid) { // If backgroundTaskId != UIBackgroundTaskInvalid, sharedApplication is always exist UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)]; [app endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; } #endif } } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (BOOL)isConcurrent { return YES; } #pragma mark NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; // Check response modifier, if return nil, will marked as cancelled. BOOL valid = YES; if (self.responseModifier && response) { response = [self.responseModifier modifiedResponseWithResponse:response]; if (!response) { valid = NO; self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response is nil"}]; } } NSInteger expected = (NSInteger)response.expectedContentLength; expected = expected > 0 ? expected : 0; self.expectedSize = expected; self.response = response; NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200; // Status code should between [200,400) BOOL statusCodeValid = statusCode >= 200 && statusCode < 400; if (!statusCodeValid) { valid = NO; self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response status code is not in 200-400", SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}]; } //'304 Not Modified' is an exceptional one //URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check if (statusCode == 304 && !self.cachedData) { valid = NO; self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Download response status code is 304 not modified and ignored"}]; } if (valid) { for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, expected, self.request.URL); } } else { // Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle disposition = NSURLSessionResponseCancel; } __block typeof(self) strongSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf]; }); if (completionHandler) { completionHandler(disposition); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { if (!self.imageData) { self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize]; } [self.imageData appendData:data]; self.receivedSize = self.imageData.length; if (self.expectedSize == 0) { // Unknown expectedSize, immediately call progressBlock and return for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(self.receivedSize, self.expectedSize, self.request.URL); } return; } // Get the finish status BOOL finished = (self.receivedSize >= self.expectedSize); // Get the current progress double currentProgress = (double)self.receivedSize / (double)self.expectedSize; double previousProgress = self.previousProgress; double progressInterval = currentProgress - previousProgress; // Check if we need callback progress if (!finished && (progressInterval < self.minimumProgressInterval)) { return; } self.previousProgress = currentProgress; // Using data decryptor will disable the progressive decoding, since there are no support for progressive decrypt BOOL supportProgressive = (self.options & SDWebImageDownloaderProgressiveLoad) && !self.decryptor; // Progressive decoding Only decode partial image, full image in `URLSession:task:didCompleteWithError:` if (supportProgressive && !finished) { // Get the image data NSData *imageData = [self.imageData copy]; // keep maximum one progressive decode process during download if (self.coderQueue.operationCount == 0) { // NSOperation have autoreleasepool, don't need to create extra one @weakify(self); [self.coderQueue addOperationWithBlock:^{ @strongify(self); if (!self) { return; } UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, NO, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); if (image) { // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding. [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO]; } }]; } } for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(self.receivedSize, self.expectedSize, self.request.URL); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { NSCachedURLResponse *cachedResponse = proposedResponse; if (!(self.options & SDWebImageDownloaderUseNSURLCache)) { // Prevents caching of responses cachedResponse = nil; } if (completionHandler) { completionHandler(cachedResponse); } } #pragma mark NSURLSessionTaskDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // If we already cancel the operation or anything mark the operation finished, don't callback twice if (self.isFinished) return; @synchronized(self) { self.dataTask = nil; __block typeof(self) strongSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf]; if (!error) { [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:strongSelf]; } }); } // make sure to call `[self done]` to mark operation as finished if (error) { // custom error instead of URLSession error if (self.responseError) { error = self.responseError; } [self callCompletionBlocksWithError:error]; [self done]; } else { if ([self callbacksForKey:kCompletedCallbackKey].count > 0) { NSData *imageData = self.imageData; self.imageData = nil; // data decryptor if (imageData && self.decryptor) { imageData = [self.decryptor decryptedDataWithData:imageData response:self.response]; } if (imageData) { /** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`, * then we should check if the cached data is equal to image data */ if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) { self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image is not modified and ignored"}]; // call completion block with not modified error [self callCompletionBlocksWithError:self.responseError]; [self done]; } else { // decode the image in coder queue, cancel all previous decoding process [self.coderQueue cancelAllOperations]; @weakify(self); [self.coderQueue addOperationWithBlock:^{ @strongify(self); if (!self) { return; } // check if we already use progressive decoding, use that to produce faster decoding id progressiveCoder = SDImageLoaderGetProgressiveCoder(self); UIImage *image; if (progressiveCoder) { image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, YES, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); } else { image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); } CGSize imageSize = image.size; if (imageSize.width == 0 || imageSize.height == 0) { NSString *description = image == nil ? @"Downloaded image decode failed" : @"Downloaded image has 0 pixels"; [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : description}]]; } else { [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES]; } [self done]; }]; } } else { [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]]; [self done]; } } else { [self done]; } } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } else { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; disposition = NSURLSessionAuthChallengeUseCredential; } } else { if (challenge.previousFailureCount == 0) { if (self.credential) { credential = self.credential; disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } if (completionHandler) { completionHandler(disposition, credential); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) { self.metrics = metrics; } #pragma mark Helper methods + (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions { SDWebImageOptions options = 0; if (downloadOptions & SDWebImageDownloaderScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages; if (downloadOptions & SDWebImageDownloaderDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly; if (downloadOptions & SDWebImageDownloaderPreloadAllFrames) options |= SDWebImagePreloadAllFrames; if (downloadOptions & SDWebImageDownloaderAvoidDecodeImage) options |= SDWebImageAvoidDecodeImage; if (downloadOptions & SDWebImageDownloaderMatchAnimatedImageClass) options |= SDWebImageMatchAnimatedImageClass; return options; } - (BOOL)shouldContinueWhenAppEntersBackground { return SD_OPTIONS_CONTAINS(self.options, SDWebImageDownloaderContinueInBackground); } - (void)callCompletionBlocksWithError:(nullable NSError *)error { [self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES]; } - (void)callCompletionBlocksWithImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData error:(nullable NSError *)error finished:(BOOL)finished { NSArray *completionBlocks = [self callbacksForKey:kCompletedCallbackKey]; dispatch_main_async_safe(^{ for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) { completedBlock(image, imageData, error, finished); } }); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderRequestModifier.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NSURLRequest * _Nullable (^SDWebImageDownloaderRequestModifierBlock)(NSURLRequest * _Nonnull request); /** This is the protocol for downloader request modifier. We can use a block to specify the downloader request modifier. But Using protocol can make this extensible, and allow Swift user to use it easily instead of using `@convention(block)` to store a block into context options. */ @protocol SDWebImageDownloaderRequestModifier /// Modify the original URL request and return a new one instead. You can modify the HTTP header, cachePolicy, etc for this URL. /// @param request The original URL request for image loading /// @note If return nil, the URL request will be cancelled. - (nullable NSURLRequest *)modifiedRequestWithRequest:(nonnull NSURLRequest *)request; @end /** A downloader request modifier class with block. */ @interface SDWebImageDownloaderRequestModifier : NSObject /// Create the request modifier with block /// @param block A block to control modifier logic - (nonnull instancetype)initWithBlock:(nonnull SDWebImageDownloaderRequestModifierBlock)block; /// Create the request modifier with block /// @param block A block to control modifier logic + (nonnull instancetype)requestModifierWithBlock:(nonnull SDWebImageDownloaderRequestModifierBlock)block; @end /** A convenient request modifier to provide the HTTP request including HTTP Method, Headers and Body. */ @interface SDWebImageDownloaderRequestModifier (Conveniences) /// Create the request modifier with HTTP Method. /// @param method HTTP Method, nil means to GET. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithMethod:(nullable NSString *)method; /// Create the request modifier with HTTP Headers. /// @param headers HTTP Headers. Case insensitive according to HTTP/1.1(HTTP/2) standard. The headers will override the same fields from original request. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithHeaders:(nullable NSDictionary *)headers; /// Create the request modifier with HTTP Body. /// @param body HTTP Body. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithBody:(nullable NSData *)body; /// Create the request modifier with HTTP Method, Headers and Body. /// @param method HTTP Method, nil means to GET. /// @param headers HTTP Headers. Case insensitive according to HTTP/1.1(HTTP/2) standard. The headers will override the same fields from original request. /// @param body HTTP Body. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithMethod:(nullable NSString *)method headers:(nullable NSDictionary *)headers body:(nullable NSData *)body; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderRequestModifier.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderRequestModifier.h" @interface SDWebImageDownloaderRequestModifier () @property (nonatomic, copy, nonnull) SDWebImageDownloaderRequestModifierBlock block; @end @implementation SDWebImageDownloaderRequestModifier - (instancetype)initWithBlock:(SDWebImageDownloaderRequestModifierBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)requestModifierWithBlock:(SDWebImageDownloaderRequestModifierBlock)block { SDWebImageDownloaderRequestModifier *requestModifier = [[SDWebImageDownloaderRequestModifier alloc] initWithBlock:block]; return requestModifier; } - (NSURLRequest *)modifiedRequestWithRequest:(NSURLRequest *)request { if (!self.block) { return nil; } return self.block(request); } @end @implementation SDWebImageDownloaderRequestModifier (Conveniences) - (instancetype)initWithMethod:(NSString *)method { return [self initWithMethod:method headers:nil body:nil]; } - (instancetype)initWithHeaders:(NSDictionary *)headers { return [self initWithMethod:nil headers:headers body:nil]; } - (instancetype)initWithBody:(NSData *)body { return [self initWithMethod:nil headers:nil body:body]; } - (instancetype)initWithMethod:(NSString *)method headers:(NSDictionary *)headers body:(NSData *)body { method = method ? [method copy] : @"GET"; headers = [headers copy]; body = [body copy]; return [self initWithBlock:^NSURLRequest * _Nullable(NSURLRequest * _Nonnull request) { NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPMethod = method; mutableRequest.HTTPBody = body; for (NSString *header in headers) { NSString *value = headers[header]; [mutableRequest setValue:value forHTTPHeaderField:header]; } return [mutableRequest copy]; }]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderResponseModifier.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NSURLResponse * _Nullable (^SDWebImageDownloaderResponseModifierBlock)(NSURLResponse * _Nonnull response); /** This is the protocol for downloader response modifier. We can use a block to specify the downloader response modifier. But Using protocol can make this extensible, and allow Swift user to use it easily instead of using `@convention(block)` to store a block into context options. */ @protocol SDWebImageDownloaderResponseModifier /// Modify the original URL response and return a new response. You can use this to check MIME-Type, mock server response, etc. /// @param response The original URL response, note for HTTP request it's actually a `NSHTTPURLResponse` instance /// @note If nil is returned, the image download will marked as cancelled with error `SDWebImageErrorInvalidDownloadResponse` - (nullable NSURLResponse *)modifiedResponseWithResponse:(nonnull NSURLResponse *)response; @end /** A downloader response modifier class with block. */ @interface SDWebImageDownloaderResponseModifier : NSObject /// Create the response modifier with block /// @param block A block to control modifier logic - (nonnull instancetype)initWithBlock:(nonnull SDWebImageDownloaderResponseModifierBlock)block; /// Create the response modifier with block /// @param block A block to control modifier logic + (nonnull instancetype)responseModifierWithBlock:(nonnull SDWebImageDownloaderResponseModifierBlock)block; @end /** A convenient response modifier to provide the HTTP response including HTTP Status Code, Version and Headers. */ @interface SDWebImageDownloaderResponseModifier (Conveniences) /// Create the response modifier with HTTP Status code. /// @param statusCode HTTP Status Code. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithStatusCode:(NSInteger)statusCode; /// Create the response modifier with HTTP Version. Status code defaults to 200. /// @param version HTTP Version, nil means "HTTP/1.1". /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithVersion:(nullable NSString *)version; /// Create the response modifier with HTTP Headers. Status code defaults to 200. /// @param headers HTTP Headers. Case insensitive according to HTTP/1.1(HTTP/2) standard. The headers will override the same fields from original response. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithHeaders:(nullable NSDictionary *)headers; /// Create the response modifier with HTTP Status Code, Version and Headers. /// @param statusCode HTTP Status Code. /// @param version HTTP Version, nil means "HTTP/1.1". /// @param headers HTTP Headers. Case insensitive according to HTTP/1.1(HTTP/2) standard. The headers will override the same fields from original response. /// @note This is for convenience, if you need code to control the logic, use block API instead. - (nonnull instancetype)initWithStatusCode:(NSInteger)statusCode version:(nullable NSString *)version headers:(nullable NSDictionary *)headers; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderResponseModifier.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderResponseModifier.h" @interface SDWebImageDownloaderResponseModifier () @property (nonatomic, copy, nonnull) SDWebImageDownloaderResponseModifierBlock block; @end @implementation SDWebImageDownloaderResponseModifier - (instancetype)initWithBlock:(SDWebImageDownloaderResponseModifierBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)responseModifierWithBlock:(SDWebImageDownloaderResponseModifierBlock)block { SDWebImageDownloaderResponseModifier *responseModifier = [[SDWebImageDownloaderResponseModifier alloc] initWithBlock:block]; return responseModifier; } - (nullable NSURLResponse *)modifiedResponseWithResponse:(nonnull NSURLResponse *)response { if (!self.block) { return nil; } return self.block(response); } @end @implementation SDWebImageDownloaderResponseModifier (Conveniences) - (instancetype)initWithStatusCode:(NSInteger)statusCode { return [self initWithStatusCode:statusCode version:nil headers:nil]; } - (instancetype)initWithVersion:(NSString *)version { return [self initWithStatusCode:200 version:version headers:nil]; } - (instancetype)initWithHeaders:(NSDictionary *)headers { return [self initWithStatusCode:200 version:nil headers:headers]; } - (instancetype)initWithStatusCode:(NSInteger)statusCode version:(NSString *)version headers:(NSDictionary *)headers { version = version ? [version copy] : @"HTTP/1.1"; headers = [headers copy]; return [self initWithBlock:^NSURLResponse * _Nullable(NSURLResponse * _Nonnull response) { if (![response isKindOfClass:NSHTTPURLResponse.class]) { return response; } NSMutableDictionary *mutableHeaders = [((NSHTTPURLResponse *)response).allHeaderFields mutableCopy]; for (NSString *header in headers) { NSString *value = headers[header]; mutableHeaders[header] = value; } NSHTTPURLResponse *httpResponse = [[NSHTTPURLResponse alloc] initWithURL:response.URL statusCode:statusCode HTTPVersion:version headerFields:[mutableHeaders copy]]; return httpResponse; }]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageError.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Jamie Pinkham * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" FOUNDATION_EXPORT NSErrorDomain const _Nonnull SDWebImageErrorDomain; /// The HTTP status code for invalid download response (NSNumber *) FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey; /// SDWebImage error domain and codes typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) { SDWebImageErrorInvalidURL = 1000, // The URL is invalid, such as nil URL or corrupted URL SDWebImageErrorBadImageData = 1001, // The image data can not be decoded to image, or the image data is empty SDWebImageErrorCacheNotModified = 1002, // The remote location specify that the cached image is not modified, such as the HTTP response 304 code. It's useful for `SDWebImageRefreshCached` SDWebImageErrorBlackListed = 1003, // The URL is blacklisted because of unrecoverable failure marked by downloader (such as 404), you can use `.retryFailed` option to avoid this SDWebImageErrorInvalidDownloadOperation = 2000, // The image download operation is invalid, such as nil operation or unexpected error occur when operation initialized SDWebImageErrorInvalidDownloadStatusCode = 2001, // The image download response a invalid status code. You can check the status code in error's userInfo under `SDWebImageErrorDownloadStatusCodeKey` SDWebImageErrorCancelled = 2002, // The image loading operation is cancelled before finished, during either async disk cache query, or waiting before actual network request. For actual network request error, check `NSURLErrorDomain` error domain and code. SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as failed. }; ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageError.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Jamie Pinkham * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageError.h" NSErrorDomain const _Nonnull SDWebImageErrorDomain = @"SDWebImageErrorDomain"; NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey = @"SDWebImageErrorDownloadStatusCodeKey"; ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageIndicator.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_UIKIT || SD_MAC /** A protocol to custom the indicator during the image loading. All of these methods are called from main queue. */ @protocol SDWebImageIndicator @required /** The view associate to the indicator. @return The indicator view */ @property (nonatomic, strong, readonly, nonnull) UIView *indicatorView; /** Start the animating for indicator. */ - (void)startAnimatingIndicator; /** Stop the animating for indicator. */ - (void)stopAnimatingIndicator; @optional /** Update the loading progress (0-1.0) for indicator. Optional @param progress The progress, value between 0 and 1.0 */ - (void)updateIndicatorProgress:(double)progress; @end #pragma mark - Activity Indicator /** Activity indicator class. for UIKit(macOS), it use a `UIActivityIndicatorView`. for AppKit(macOS), it use a `NSProgressIndicator` with the spinning style. */ @interface SDWebImageActivityIndicator : NSObject #if SD_UIKIT @property (nonatomic, strong, readonly, nonnull) UIActivityIndicatorView *indicatorView; #else @property (nonatomic, strong, readonly, nonnull) NSProgressIndicator *indicatorView; #endif @end /** Convenience way to use activity indicator. */ @interface SDWebImageActivityIndicator (Conveniences) /// These indicator use the fixed color without dark mode support /// gray-style activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *grayIndicator; /// large gray-style activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *grayLargeIndicator; /// white-style activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *whiteIndicator; /// large white-style activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *whiteLargeIndicator; /// These indicator use the system style, supports dark mode if available (iOS 13+/macOS 10.14+) /// large activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *largeIndicator; /// medium activity indicator @property (nonatomic, class, nonnull, readonly) SDWebImageActivityIndicator *mediumIndicator; @end #pragma mark - Progress Indicator /** Progress indicator class. for UIKit(macOS), it use a `UIProgressView`. for AppKit(macOS), it use a `NSProgressIndicator` with the bar style. */ @interface SDWebImageProgressIndicator : NSObject #if SD_UIKIT @property (nonatomic, strong, readonly, nonnull) UIProgressView *indicatorView; #else @property (nonatomic, strong, readonly, nonnull) NSProgressIndicator *indicatorView; #endif @end /** Convenience way to create progress indicator. Remember to specify the indicator width or use layout constraint if need. */ @interface SDWebImageProgressIndicator (Conveniences) /// default-style progress indicator @property (nonatomic, class, nonnull, readonly) SDWebImageProgressIndicator *defaultIndicator; /// bar-style progress indicator @property (nonatomic, class, nonnull, readonly) SDWebImageProgressIndicator *barIndicator API_UNAVAILABLE(macos, tvos); @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageIndicator.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageIndicator.h" #if SD_UIKIT || SD_MAC #if SD_MAC #import #endif #pragma mark - Activity Indicator @interface SDWebImageActivityIndicator () #if SD_UIKIT @property (nonatomic, strong, readwrite, nonnull) UIActivityIndicatorView *indicatorView; #else @property (nonatomic, strong, readwrite, nonnull) NSProgressIndicator *indicatorView; #endif @end @implementation SDWebImageActivityIndicator - (instancetype)init { self = [super init]; if (self) { [self commonInit]; } return self; } #if SD_UIKIT #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)commonInit { self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; self.indicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; } #pragma clang diagnostic pop #endif #if SD_MAC - (void)commonInit { self.indicatorView = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; self.indicatorView.style = NSProgressIndicatorStyleSpinning; self.indicatorView.controlSize = NSControlSizeSmall; [self.indicatorView sizeToFit]; self.indicatorView.autoresizingMask = NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin; } #endif - (void)startAnimatingIndicator { #if SD_UIKIT [self.indicatorView startAnimating]; #else [self.indicatorView startAnimation:nil]; #endif self.indicatorView.hidden = NO; } - (void)stopAnimatingIndicator { #if SD_UIKIT [self.indicatorView stopAnimating]; #else [self.indicatorView stopAnimation:nil]; #endif self.indicatorView.hidden = YES; } @end @implementation SDWebImageActivityIndicator (Conveniences) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" + (SDWebImageActivityIndicator *)grayIndicator { SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new]; #if SD_UIKIT #if SD_IOS indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; #else indicator.indicatorView.color = [UIColor colorWithWhite:0 alpha:0.45]; // Color from `UIActivityIndicatorViewStyleGray` #endif #else indicator.indicatorView.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; // Disable dark mode support #endif return indicator; } + (SDWebImageActivityIndicator *)grayLargeIndicator { SDWebImageActivityIndicator *indicator = SDWebImageActivityIndicator.grayIndicator; #if SD_UIKIT UIColor *grayColor = indicator.indicatorView.color; indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; indicator.indicatorView.color = grayColor; #else indicator.indicatorView.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; // Disable dark mode support indicator.indicatorView.controlSize = NSControlSizeRegular; #endif [indicator.indicatorView sizeToFit]; return indicator; } + (SDWebImageActivityIndicator *)whiteIndicator { SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new]; #if SD_UIKIT indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite; #else indicator.indicatorView.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; // Disable dark mode support CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"]; [lighten setDefaults]; [lighten setValue:@(1) forKey:kCIInputBrightnessKey]; indicator.indicatorView.contentFilters = @[lighten]; #endif return indicator; } + (SDWebImageActivityIndicator *)whiteLargeIndicator { SDWebImageActivityIndicator *indicator = SDWebImageActivityIndicator.whiteIndicator; #if SD_UIKIT indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; #else indicator.indicatorView.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; // Disable dark mode support indicator.indicatorView.controlSize = NSControlSizeRegular; [indicator.indicatorView sizeToFit]; #endif return indicator; } + (SDWebImageActivityIndicator *)largeIndicator { SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new]; #if SD_UIKIT if (@available(iOS 13.0, tvOS 13.0, *)) { indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleLarge; } else { indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; } #else indicator.indicatorView.controlSize = NSControlSizeRegular; [indicator.indicatorView sizeToFit]; #endif return indicator; } + (SDWebImageActivityIndicator *)mediumIndicator { SDWebImageActivityIndicator *indicator = [SDWebImageActivityIndicator new]; #if SD_UIKIT if (@available(iOS 13.0, tvOS 13.0, *)) { indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; } else { indicator.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite; } #else indicator.indicatorView.controlSize = NSControlSizeSmall; [indicator.indicatorView sizeToFit]; #endif return indicator; } #pragma clang diagnostic pop @end #pragma mark - Progress Indicator @interface SDWebImageProgressIndicator () #if SD_UIKIT @property (nonatomic, strong, readwrite, nonnull) UIProgressView *indicatorView; #else @property (nonatomic, strong, readwrite, nonnull) NSProgressIndicator *indicatorView; #endif @end @implementation SDWebImageProgressIndicator - (instancetype)init { self = [super init]; if (self) { [self commonInit]; } return self; } #if SD_UIKIT - (void)commonInit { self.indicatorView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; self.indicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; } #endif #if SD_MAC - (void)commonInit { self.indicatorView = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 160, 0)]; // Width from `UIProgressView` default width self.indicatorView.style = NSProgressIndicatorStyleBar; self.indicatorView.controlSize = NSControlSizeSmall; [self.indicatorView sizeToFit]; self.indicatorView.autoresizingMask = NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin; } #endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" - (void)startAnimatingIndicator { self.indicatorView.hidden = NO; #if SD_UIKIT if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) { // Ignore NSProgress } else { self.indicatorView.progress = 0; } #else self.indicatorView.indeterminate = YES; self.indicatorView.doubleValue = 0; [self.indicatorView startAnimation:nil]; #endif } - (void)stopAnimatingIndicator { self.indicatorView.hidden = YES; #if SD_UIKIT if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) { // Ignore NSProgress } else { self.indicatorView.progress = 1; } #else self.indicatorView.indeterminate = NO; self.indicatorView.doubleValue = 100; [self.indicatorView stopAnimation:nil]; #endif } - (void)updateIndicatorProgress:(double)progress { #if SD_UIKIT if ([self.indicatorView respondsToSelector:@selector(observedProgress)] && self.indicatorView.observedProgress) { // Ignore NSProgress } else { [self.indicatorView setProgress:progress animated:YES]; } #else self.indicatorView.indeterminate = progress > 0 ? NO : YES; self.indicatorView.doubleValue = progress * 100; #endif } #pragma clang diagnostic pop @end @implementation SDWebImageProgressIndicator (Conveniences) + (SDWebImageProgressIndicator *)defaultIndicator { SDWebImageProgressIndicator *indicator = [SDWebImageProgressIndicator new]; return indicator; } #if SD_IOS + (SDWebImageProgressIndicator *)barIndicator { SDWebImageProgressIndicator *indicator = [SDWebImageProgressIndicator new]; indicator.indicatorView.progressViewStyle = UIProgressViewStyleBar; return indicator; } #endif @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageOperation.h" #import "SDImageCacheDefine.h" #import "SDImageLoader.h" #import "SDImageTransformer.h" #import "SDWebImageCacheKeyFilter.h" #import "SDWebImageCacheSerializer.h" #import "SDWebImageOptionsProcessor.h" typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL); /** A combined operation representing the cache and loader operation. You can use it to cancel the load process. */ @interface SDWebImageCombinedOperation : NSObject /** Cancel the current operation, including cache and loader process */ - (void)cancel; /** The cache operation from the image cache query */ @property (strong, nonatomic, nullable, readonly) id cacheOperation; /** The loader operation from the image loader (such as download operation) */ @property (strong, nonatomic, nullable, readonly) id loaderOperation; @end @class SDWebImageManager; /** The manager delegate protocol. */ @protocol SDWebImageManagerDelegate @optional /** * Controls which image should be downloaded when the image is not found in the cache. * * @param imageManager The current `SDWebImageManager` * @param imageURL The url of the image to be downloaded * * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. */ - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nonnull NSURL *)imageURL; /** * Controls the complicated logic to mark as failed URLs when download error occur. * If the delegate implement this method, we will not use the built-in way to mark URL as failed based on error code; @param imageManager The current `SDWebImageManager` @param imageURL The url of the image @param error The download error for the url @return Whether to block this url or not. Return YES to mark this URL as failed. */ - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error; @end /** * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). * You can use this class directly to benefit from web image downloading with caching in another context than * a UIView. * * Here is a simple example of how to use SDWebImageManager: * * @code SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager loadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; * @endcode */ @interface SDWebImageManager : NSObject /** * The delegate for manager. Defaults to nil. */ @property (weak, nonatomic, nullable) id delegate; /** * The image cache used by manager to query image cache. */ @property (strong, nonatomic, readonly, nonnull) id imageCache; /** * The image loader used by manager to load image. */ @property (strong, nonatomic, readonly, nonnull) id imageLoader; /** The image transformer for manager. It's used for image transform after the image load finished and store the transformed image to cache, see `SDImageTransformer`. Defaults to nil, which means no transform is applied. @note This will affect all the load requests for this manager if you provide. However, you can pass `SDWebImageContextImageTransformer` in context arg to explicitly use that transformer instead. */ @property (strong, nonatomic, nullable) id transformer; /** * The cache filter is used to convert an URL into a cache key each time SDWebImageManager need cache key to use image cache. * * The following example sets a filter in the application delegate that will remove any query-string from the * URL before to use it as a cache key: * * @code SDWebImageManager.sharedManager.cacheKeyFilter =[SDWebImageCacheKeyFilter cacheKeyFilterWithBlock:^NSString * _Nullable(NSURL * _Nonnull url) { url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; return [url absoluteString]; }]; * @endcode */ @property (nonatomic, strong, nullable) id cacheKeyFilter; /** * The cache serializer is used to convert the decoded image, the source downloaded data, to the actual data used for storing to the disk cache. If you return nil, means to generate the data from the image instance, see `SDImageCache`. * For example, if you are using WebP images and facing the slow decoding time issue when later retrieving from disk cache again. You can try to encode the decoded image to JPEG/PNG format to disk cache instead of source downloaded data. * @note The `image` arg is nonnull, but when you also provide an image transformer and the image is transformed, the `data` arg may be nil, take attention to this case. * @note This method is called from a global queue in order to not to block the main thread. * @code SDWebImageManager.sharedManager.cacheSerializer = [SDWebImageCacheSerializer cacheSerializerWithBlock:^NSData * _Nullable(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL) { SDImageFormat format = [NSData sd_imageFormatForImageData:data]; switch (format) { case SDImageFormatWebP: return image.images ? data : nil; default: return data; } }]; * @endcode * The default value is nil. Means we just store the source downloaded data to disk cache. */ @property (nonatomic, strong, nullable) id cacheSerializer; /** The options processor is used, to have a global control for all the image request options and context option for current manager. @note If you use `transformer`, `cacheKeyFilter` or `cacheSerializer` property of manager, the input context option already apply those properties before passed. This options processor is a better replacement for those property in common usage. For example, you can control the global options, based on the URL or original context option like the below code. @code SDWebImageManager.sharedManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) { // Only do animation on `SDAnimatedImageView` if (!context[SDWebImageContextAnimatedImageClass]) { options |= SDWebImageDecodeFirstFrameOnly; } // Do not force decode for png url if ([url.lastPathComponent isEqualToString:@"png"]) { options |= SDWebImageAvoidDecodeImage; } // Always use screen scale factor SDWebImageMutableContext *mutableContext = [NSDictionary dictionaryWithDictionary:context]; mutableContext[SDWebImageContextImageScaleFactor] = @(UIScreen.mainScreen.scale); context = [mutableContext copy]; return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context]; }]; @endcode */ @property (nonatomic, strong, nullable) id optionsProcessor; /** * Check one or more operations running */ @property (nonatomic, assign, readonly, getter=isRunning) BOOL running; /** The default image cache when the manager which is created with no arguments. Such as shared manager or init. Defaults to nil. Means using `SDImageCache.sharedImageCache` */ @property (nonatomic, class, nullable) id defaultImageCache; /** The default image loader for manager which is created with no arguments. Such as shared manager or init. Defaults to nil. Means using `SDWebImageDownloader.sharedDownloader` */ @property (nonatomic, class, nullable) id defaultImageLoader; /** * Returns global shared manager instance. */ @property (nonatomic, class, readonly, nonnull) SDWebImageManager *sharedManager; /** * Allows to specify instance of cache and image loader used with image manager. * @return new instance of `SDWebImageManager` with specified cache and loader. */ - (nonnull instancetype)initWithCache:(nonnull id)cache loader:(nonnull id)loader NS_DESIGNATED_INITIALIZER; /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. * * @param url The URL to the image * @param options A mask to specify options to use for this request * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. * * This parameter is required. * * This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter. * In case of error the image parameter is nil and the third parameter may contain an NSError. * * The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * or from the memory cache or from the network. * * The fifth parameter is set to NO when the SDWebImageProgressiveLoad option is used and the image is * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * block is called a last time with the full image and the last parameter set to YES. * * The last parameter is the original image URL * * @return Returns an instance of SDWebImageCombinedOperation, which you can cancel the loading process. */ - (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlock; /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. * * @param url The URL to the image * @param options A mask to specify options to use for this request * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. * * @return Returns an instance of SDWebImageCombinedOperation, which you can cancel the loading process. */ - (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlock; /** * Cancel all current operations */ - (void)cancelAll; /** * Remove the specify URL from failed black list. * @param url The failed URL. */ - (void)removeFailedURL:(nonnull NSURL *)url; /** * Remove all the URL from failed black list. */ - (void)removeAllFailedURLs; /** * Return the cache key for a given URL, does not considerate transformer or thumbnail. * @note This method does not have context option, only use the url and manager level cacheKeyFilter to generate the cache key. */ - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url; /** * Return the cache key for a given URL and context option. * @note The context option like `.thumbnailPixelSize` and `.imageTransformer` will effect the generated cache key, using this if you have those context associated. */ - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageManager.h" #import "SDImageCache.h" #import "SDWebImageDownloader.h" #import "UIImage+Metadata.h" #import "SDAssociatedObject.h" #import "SDWebImageError.h" #import "SDInternalMacros.h" static id _defaultImageCache; static id _defaultImageLoader; @interface SDWebImageCombinedOperation () @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (strong, nonatomic, readwrite, nullable) id loaderOperation; @property (strong, nonatomic, readwrite, nullable) id cacheOperation; @property (weak, nonatomic, nullable) SDWebImageManager *manager; @end @interface SDWebImageManager () { SD_LOCK_DECLARE(_failedURLsLock); // a lock to keep the access to `failedURLs` thread-safe SD_LOCK_DECLARE(_runningOperationsLock); // a lock to keep the access to `runningOperations` thread-safe } @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; @property (strong, nonatomic, readwrite, nonnull) id imageLoader; @property (strong, nonatomic, nonnull) NSMutableSet *failedURLs; @property (strong, nonatomic, nonnull) NSMutableSet *runningOperations; @end @implementation SDWebImageManager + (id)defaultImageCache { return _defaultImageCache; } + (void)setDefaultImageCache:(id)defaultImageCache { if (defaultImageCache && ![defaultImageCache conformsToProtocol:@protocol(SDImageCache)]) { return; } _defaultImageCache = defaultImageCache; } + (id)defaultImageLoader { return _defaultImageLoader; } + (void)setDefaultImageLoader:(id)defaultImageLoader { if (defaultImageLoader && ![defaultImageLoader conformsToProtocol:@protocol(SDImageLoader)]) { return; } _defaultImageLoader = defaultImageLoader; } + (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { id cache = [[self class] defaultImageCache]; if (!cache) { cache = [SDImageCache sharedImageCache]; } id loader = [[self class] defaultImageLoader]; if (!loader) { loader = [SDWebImageDownloader sharedDownloader]; } return [self initWithCache:cache loader:loader]; } - (nonnull instancetype)initWithCache:(nonnull id)cache loader:(nonnull id)loader { if ((self = [super init])) { _imageCache = cache; _imageLoader = loader; _failedURLs = [NSMutableSet new]; SD_LOCK_INIT(_failedURLsLock); _runningOperations = [NSMutableSet new]; SD_LOCK_INIT(_runningOperationsLock); } return self; } - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url { if (!url) { return @""; } NSString *key; // Cache Key Filter id cacheKeyFilter = self.cacheKeyFilter; if (cacheKeyFilter) { key = [cacheKeyFilter cacheKeyForURL:url]; } else { key = url.absoluteString; } return key; } - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context { if (!url) { return @""; } NSString *key; // Cache Key Filter id cacheKeyFilter = self.cacheKeyFilter; if (context[SDWebImageContextCacheKeyFilter]) { cacheKeyFilter = context[SDWebImageContextCacheKeyFilter]; } if (cacheKeyFilter) { key = [cacheKeyFilter cacheKeyForURL:url]; } else { key = url.absoluteString; } // Thumbnail Key Appending NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; if (thumbnailSizeValue != nil) { CGSize thumbnailSize = CGSizeZero; #if SD_MAC thumbnailSize = thumbnailSizeValue.sizeValue; #else thumbnailSize = thumbnailSizeValue.CGSizeValue; #endif BOOL preserveAspectRatio = YES; NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio]; if (preserveAspectRatioValue != nil) { preserveAspectRatio = preserveAspectRatioValue.boolValue; } key = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio); } // Transformer Key Appending id transformer = self.transformer; if (context[SDWebImageContextImageTransformer]) { transformer = context[SDWebImageContextImageTransformer]; if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { transformer = nil; } } if (transformer) { key = SDTransformedKeyForKey(key, transformer.transformerKey); } return key; } - (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock { return [self loadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock]; } - (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlock { // Invoking this method without a completedBlock is pointless NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } // Prevents app crashing on argument type error like sending NSNull instead of NSURL if (![url isKindOfClass:NSURL.class]) { url = nil; } SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; operation.manager = self; BOOL isFailedUrl = NO; if (url) { SD_LOCK(_failedURLsLock); isFailedUrl = [self.failedURLs containsObject:url]; SD_UNLOCK(_failedURLsLock); } if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil"; NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL; [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url]; return operation; } SD_LOCK(_runningOperationsLock); [self.runningOperations addObject:operation]; SD_UNLOCK(_runningOperationsLock); // Preprocess the options and context arg to decide the final the result for manager SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context]; // Start the entry to load image from cache [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock]; return operation; } - (void)cancelAll { SD_LOCK(_runningOperationsLock); NSSet *copiedOperations = [self.runningOperations copy]; SD_UNLOCK(_runningOperationsLock); [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array } - (BOOL)isRunning { BOOL isRunning = NO; SD_LOCK(_runningOperationsLock); isRunning = (self.runningOperations.count > 0); SD_UNLOCK(_runningOperationsLock); return isRunning; } - (void)removeFailedURL:(NSURL *)url { if (!url) { return; } SD_LOCK(_failedURLsLock); [self.failedURLs removeObject:url]; SD_UNLOCK(_failedURLsLock); } - (void)removeAllFailedURLs { SD_LOCK(_failedURLsLock); [self.failedURLs removeAllObjects]; SD_UNLOCK(_failedURLsLock); } #pragma mark - Private // Query normal cache process - (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use id imageCache; if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache]; } else { imageCache = self.imageCache; } // Get the query cache type SDImageCacheType queryCacheType = SDImageCacheTypeAll; if (context[SDWebImageContextQueryCacheType]) { queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue]; } // Check whether we should query cache BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); if (shouldQueryCache) { NSString *key = [self cacheKeyForURL:url context:context]; @weakify(operation); operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; [self safelyRemoveOperationFromRunning:operation]; return; } else if (context[SDWebImageContextImageTransformer] && !cachedImage) { // Have a chance to query original cache instead of downloading [self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock]; return; } // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock]; }]; } else { // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock]; } } // Query original cache process - (void)callOriginalCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use, choose standalone original cache firstly id imageCache; if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextOriginalImageCache]; } else { // if no standalone cache available, use default cache if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache]; } else { imageCache = self.imageCache; } } // Get the original query cache type SDImageCacheType originalQueryCacheType = SDImageCacheTypeDisk; if (context[SDWebImageContextOriginalQueryCacheType]) { originalQueryCacheType = [context[SDWebImageContextOriginalQueryCacheType] integerValue]; } // Check whether we should query original cache BOOL shouldQueryOriginalCache = (originalQueryCacheType != SDImageCacheTypeNone); if (shouldQueryOriginalCache) { // Disable transformer for original cache key generation SDWebImageMutableContext *tempContext = [context mutableCopy]; tempContext[SDWebImageContextImageTransformer] = [NSNull null]; NSString *key = [self cacheKeyForURL:url context:tempContext]; @weakify(operation); operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:originalQueryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) { @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url]; [self safelyRemoveOperationFromRunning:operation]; return; } else if (context[SDWebImageContextImageTransformer] && !cachedImage) { // Original image cache miss. Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock]; return; } // Use the store cache process instead of downloading, and ignore .refreshCached option for now [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:cachedImage downloadedData:cachedData finished:YES progress:progressBlock completed:completedBlock]; [self safelyRemoveOperationFromRunning:operation]; }]; } else { // Continue download process [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:originalQueryCacheType progress:progressBlock completed:completedBlock]; } } // Download process - (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context cachedImage:(nullable UIImage *)cachedImage cachedData:(nullable NSData *)cachedData cacheType:(SDImageCacheType)cacheType progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image loader to use id imageLoader; if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { imageLoader = context[SDWebImageContextImageLoader]; } else { imageLoader = self.imageLoader; } // Check whether we should download image from network BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly); shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached); shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) { shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context]; } else { shouldDownload &= [imageLoader canRequestImageForURL:url]; } if (shouldDownload) { if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image. SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage; context = [mutableContext copy]; } @weakify(operation); operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { @strongify(operation); if (!operation || operation.isCancelled) { // Image combined operation cancelled by user [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url]; } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) { // Download operation cancelled by user before sending the request, don't block failed URL [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url]; } else if (error) { [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url]; BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context]; if (shouldBlockFailedURL) { SD_LOCK(self->_failedURLsLock); [self.failedURLs addObject:url]; SD_UNLOCK(self->_failedURLsLock); } } else { if ((options & SDWebImageRetryFailed)) { SD_LOCK(self->_failedURLsLock); [self.failedURLs removeObject:url]; SD_UNLOCK(self->_failedURLsLock); } // Continue store cache process [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock]; } if (finished) { [self safelyRemoveOperationFromRunning:operation]; } }]; } else if (cachedImage) { [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; } else { // Image not in cache and download disallowed by delegate [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; } } // Store cache process - (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context downloadedImage:(nullable UIImage *)downloadedImage downloadedData:(nullable NSData *)downloadedData finished:(BOOL)finished progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use, choose standalone original cache firstly id imageCache; if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextOriginalImageCache]; } else { // if no standalone cache available, use default cache if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache]; } else { imageCache = self.imageCache; } } // the target image store cache type SDImageCacheType storeCacheType = SDImageCacheTypeAll; if (context[SDWebImageContextStoreCacheType]) { storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue]; } // the original store image cache type SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk; if (context[SDWebImageContextOriginalStoreCacheType]) { originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue]; } // Disable transformer for original cache key generation SDWebImageMutableContext *tempContext = [context mutableCopy]; tempContext[SDWebImageContextImageTransformer] = [NSNull null]; NSString *key = [self cacheKeyForURL:url context:tempContext]; id transformer = context[SDWebImageContextImageTransformer]; if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { transformer = nil; } id cacheSerializer = context[SDWebImageContextCacheSerializer]; BOOL shouldTransformImage = downloadedImage && transformer; shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage)); BOOL shouldCacheOriginal = downloadedImage && finished; // if available, store original image to cache if (shouldCacheOriginal) { // normally use the store cache type, but if target image is transformed, use original store cache type instead SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType; if (cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @autoreleasepool { NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url]; [self storeImage:downloadedImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType options:options context:context completion:^{ // Continue transform process [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock]; }]; } }); } else { [self storeImage:downloadedImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType options:options context:context completion:^{ // Continue transform process [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock]; }]; } } else { // Continue transform process [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock]; } } // Transform process - (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context originalImage:(nullable UIImage *)originalImage originalData:(nullable NSData *)originalData finished:(BOOL)finished progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use id imageCache; if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache]; } else { imageCache = self.imageCache; } // the target image store cache type SDImageCacheType storeCacheType = SDImageCacheTypeAll; if (context[SDWebImageContextStoreCacheType]) { storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue]; } // transformed cache key NSString *key = [self cacheKeyForURL:url context:context]; id transformer = context[SDWebImageContextImageTransformer]; if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { transformer = nil; } id cacheSerializer = context[SDWebImageContextCacheSerializer]; BOOL shouldTransformImage = originalImage && transformer; shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage)); // if available, store transformed image to cache if (shouldTransformImage) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @autoreleasepool { UIImage *transformedImage = [transformer transformedImageWithImage:originalImage forKey:key]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:originalImage]; NSData *cacheData; // pass nil if the image was transformed, so we can recalculate the data from the image if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) { cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : originalData) imageURL:url]; } else { cacheData = (imageWasTransformed ? nil : originalData); } [self storeImage:transformedImage imageData:cacheData forKey:key imageCache:imageCache cacheType:storeCacheType options:options context:context completion:^{ [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; }]; } else { [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } } }); } else { [self callCompletionBlockForOperation:operation completion:completedBlock image:originalImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } } #pragma mark - Helper - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation { if (!operation) { return; } SD_LOCK(_runningOperationsLock); [self.runningOperations removeObject:operation]; SD_UNLOCK(_runningOperationsLock); } - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)data forKey:(nullable NSString *)key imageCache:(nonnull id)imageCache cacheType:(SDImageCacheType)cacheType options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDWebImageNoParamsBlock)completion { BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); // Check whether we should wait the store cache finished. If not, callback immediately [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{ if (waitStoreCache) { if (completion) { completion(); } } }]; if (!waitStoreCache) { if (completion) { completion(); } } } - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation completion:(nullable SDInternalCompletionBlock)completionBlock error:(nullable NSError *)error url:(nullable NSURL *)url { [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; } - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation completion:(nullable SDInternalCompletionBlock)completionBlock image:(nullable UIImage *)image data:(nullable NSData *)data error:(nullable NSError *)error cacheType:(SDImageCacheType)cacheType finished:(BOOL)finished url:(nullable NSURL *)url { dispatch_main_async_safe(^{ if (completionBlock) { completionBlock(image, data, error, cacheType, finished, url); } }); } - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url error:(nonnull NSError *)error options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { id imageLoader; if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { imageLoader = context[SDWebImageContextImageLoader]; } else { imageLoader = self.imageLoader; } // Check whether we should block failed url BOOL shouldBlockFailedURL; if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) { shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error]; } else { if ([imageLoader respondsToSelector:@selector(shouldBlockFailedURLWithURL:error:options:context:)]) { shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error options:options context:context]; } else { shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error]; } } return shouldBlockFailedURL; } - (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context { SDWebImageOptionsResult *result; SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary]; // Image Transformer from manager if (!context[SDWebImageContextImageTransformer]) { id transformer = self.transformer; [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer]; } // Cache key filter from manager if (!context[SDWebImageContextCacheKeyFilter]) { id cacheKeyFilter = self.cacheKeyFilter; [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter]; } // Cache serializer from manager if (!context[SDWebImageContextCacheSerializer]) { id cacheSerializer = self.cacheSerializer; [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer]; } if (mutableContext.count > 0) { if (context) { [mutableContext addEntriesFromDictionary:context]; } context = [mutableContext copy]; } // Apply options processor if (self.optionsProcessor) { result = [self.optionsProcessor processedResultForURL:url options:options context:context]; } if (!result) { // Use default options result result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context]; } return result; } @end @implementation SDWebImageCombinedOperation - (void)cancel { @synchronized(self) { if (self.isCancelled) { return; } self.cancelled = YES; if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } if (self.loaderOperation) { [self.loaderOperation cancel]; self.loaderOperation = nil; } [self.manager safelyRemoveOperationFromRunning:self]; } } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import /// A protocol represents cancelable operation. @protocol SDWebImageOperation - (void)cancel; @end /// NSOperation conform to `SDWebImageOperation`. @interface NSOperation (SDWebImageOperation) @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageOperation.h" /// NSOperation conform to `SDWebImageOperation`. @implementation NSOperation (SDWebImageOperation) @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageOptionsProcessor.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" @class SDWebImageOptionsResult; typedef SDWebImageOptionsResult * _Nullable(^SDWebImageOptionsProcessorBlock)(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context); /** The options result contains both options and context. */ @interface SDWebImageOptionsResult : NSObject /** WebCache options. */ @property (nonatomic, assign, readonly) SDWebImageOptions options; /** Context options. */ @property (nonatomic, copy, readonly, nullable) SDWebImageContext *context; /** Create a new options result. @param options options @param context context @return The options result contains both options and context. */ - (nonnull instancetype)initWithOptions:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; @end /** This is the protocol for options processor. Options processor can be used, to control the final result for individual image request's `SDWebImageOptions` and `SDWebImageContext` Implements the protocol to have a global control for each indivadual image request's option. */ @protocol SDWebImageOptionsProcessor /** Return the processed options result for specify image URL, with its options and context @param url The URL to the image @param options A mask to specify options to use for this request @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. @return The processed result, contains both options and context */ - (nullable SDWebImageOptionsResult *)processedResultForURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; @end /** A options processor class with block. */ @interface SDWebImageOptionsProcessor : NSObject - (nonnull instancetype)initWithBlock:(nonnull SDWebImageOptionsProcessorBlock)block; + (nonnull instancetype)optionsProcessorWithBlock:(nonnull SDWebImageOptionsProcessorBlock)block; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageOptionsProcessor.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageOptionsProcessor.h" @interface SDWebImageOptionsResult () @property (nonatomic, assign) SDWebImageOptions options; @property (nonatomic, copy, nullable) SDWebImageContext *context; @end @implementation SDWebImageOptionsResult - (instancetype)initWithOptions:(SDWebImageOptions)options context:(SDWebImageContext *)context { self = [super init]; if (self) { self.options = options; self.context = context; } return self; } @end @interface SDWebImageOptionsProcessor () @property (nonatomic, copy, nonnull) SDWebImageOptionsProcessorBlock block; @end @implementation SDWebImageOptionsProcessor - (instancetype)initWithBlock:(SDWebImageOptionsProcessorBlock)block { self = [super init]; if (self) { self.block = block; } return self; } + (instancetype)optionsProcessorWithBlock:(SDWebImageOptionsProcessorBlock)block { SDWebImageOptionsProcessor *optionsProcessor = [[SDWebImageOptionsProcessor alloc] initWithBlock:block]; return optionsProcessor; } - (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context { if (!self.block) { return nil; } return self.block(url, options, context); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImagePrefetcher.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageManager.h" @class SDWebImagePrefetcher; /** A token represents a list of URLs, can be used to cancel the download. */ @interface SDWebImagePrefetchToken : NSObject /** * Cancel the current prefetching. */ - (void)cancel; /** list of URLs of current prefetching. */ @property (nonatomic, copy, readonly, nullable) NSArray *urls; @end /** The prefetcher delegate protocol */ @protocol SDWebImagePrefetcherDelegate @optional /** * Called when an image was prefetched. Which means it's called when one URL from any of prefetching finished. * * @param imagePrefetcher The current image prefetcher * @param imageURL The image url that was prefetched * @param finishedCount The total number of images that were prefetched (successful or not) * @param totalCount The total number of images that were to be prefetched */ - (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(nullable NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount; /** * Called when all images are prefetched. Which means it's called when all URLs from all of prefetching finished. * @param imagePrefetcher The current image prefetcher * @param totalCount The total number of images that were prefetched (whether successful or not) * @param skippedCount The total number of images that were skipped */ - (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount; @end typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls); typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls); /** * Prefetch some URLs in the cache for future use. Images are downloaded in low priority. */ @interface SDWebImagePrefetcher : NSObject /** * The web image manager used by prefetcher to prefetch images. * @note You can specify a standalone manager and downloader with custom configuration suitable for image prefetching. Such as `currentDownloadCount` or `downloadTimeout`. */ @property (strong, nonatomic, readonly, nonnull) SDWebImageManager *manager; /** * Maximum number of URLs to prefetch at the same time. Defaults to 3. */ @property (nonatomic, assign) NSUInteger maxConcurrentPrefetchCount; /** * The options for prefetcher. Defaults to SDWebImageLowPriority. */ @property (nonatomic, assign) SDWebImageOptions options; /** * The context for prefetcher. Defaults to nil. */ @property (nonatomic, copy, nullable) SDWebImageContext *context; /** * Queue options for prefetcher when call the progressBlock, completionBlock and delegate methods. Defaults to Main Queue. * @note The call is asynchronously to avoid blocking target queue. * @note The delegate queue should be set before any prefetching start and may not be changed during prefetching to avoid thread-safe problem. */ @property (strong, nonatomic, nonnull) dispatch_queue_t delegateQueue; /** * The delegate for the prefetcher. Defaults to nil. */ @property (weak, nonatomic, nullable) id delegate; /** * Returns the global shared image prefetcher instance. It use a standalone manager which is different from shared manager. */ @property (nonatomic, class, readonly, nonnull) SDWebImagePrefetcher *sharedImagePrefetcher; /** * Allows you to instantiate a prefetcher with any arbitrary image manager. */ - (nonnull instancetype)initWithImageManager:(nonnull SDWebImageManager *)manager NS_DESIGNATED_INITIALIZER; /** * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching. It based on the image manager so the image may from the cache and network according to the `options` property. * Prefetching is separate to each other, which means the progressBlock and completionBlock you provide is bind to the prefetching for the list of urls. * Attention that call this will not cancel previous fetched urls. You should keep the token return by this to cancel or cancel all the prefetch. * * @param urls list of URLs to prefetch * @return the token to cancel the current prefetching. */ - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray *)urls; /** * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching. It based on the image manager so the image may from the cache and network according to the `options` property. * Prefetching is separate to each other, which means the progressBlock and completionBlock you provide is bind to the prefetching for the list of urls. * Attention that call this will not cancel previous fetched urls. You should keep the token return by this to cancel or cancel all the prefetch. * * @param urls list of URLs to prefetch * @param progressBlock block to be called when progress updates; * first parameter is the number of completed (successful or not) requests, * second parameter is the total number of images originally requested to be prefetched * @param completionBlock block to be called when the current prefetching is completed * first param is the number of completed (successful or not) requests, * second parameter is the number of skipped requests * @return the token to cancel the current prefetching. */ - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock; /** * Remove and cancel all the prefeching for the prefetcher. */ - (void)cancelPrefetching; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImagePrefetcher.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImagePrefetcher.h" #import "SDAsyncBlockOperation.h" #import "SDInternalMacros.h" #import @interface SDWebImagePrefetchToken () { @public // Though current implementation, `SDWebImageManager` completion block is always on main queue. But however, there is no guarantee in docs. And we may introduce config to specify custom queue in the future. // These value are just used as incrementing counter, keep thread-safe using memory_order_relaxed for performance. atomic_ulong _skippedCount; atomic_ulong _finishedCount; atomic_flag _isAllFinished; unsigned long _totalCount; // Used to ensure NSPointerArray thread safe SD_LOCK_DECLARE(_prefetchOperationsLock); SD_LOCK_DECLARE(_loadOperationsLock); } @property (nonatomic, copy, readwrite) NSArray *urls; @property (nonatomic, strong) NSPointerArray *loadOperations; @property (nonatomic, strong) NSPointerArray *prefetchOperations; @property (nonatomic, weak) SDWebImagePrefetcher *prefetcher; @property (nonatomic, copy, nullable) SDWebImagePrefetcherCompletionBlock completionBlock; @property (nonatomic, copy, nullable) SDWebImagePrefetcherProgressBlock progressBlock; @end @interface SDWebImagePrefetcher () @property (strong, nonatomic, nonnull) SDWebImageManager *manager; @property (strong, atomic, nonnull) NSMutableSet *runningTokens; @property (strong, nonatomic, nonnull) NSOperationQueue *prefetchQueue; @end @implementation SDWebImagePrefetcher + (nonnull instancetype)sharedImagePrefetcher { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { return [self initWithImageManager:[SDWebImageManager new]]; } - (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager { if ((self = [super init])) { _manager = manager; _runningTokens = [NSMutableSet set]; _options = SDWebImageLowPriority; _delegateQueue = dispatch_get_main_queue(); _prefetchQueue = [NSOperationQueue new]; self.maxConcurrentPrefetchCount = 3; } return self; } - (void)setMaxConcurrentPrefetchCount:(NSUInteger)maxConcurrentPrefetchCount { self.prefetchQueue.maxConcurrentOperationCount = maxConcurrentPrefetchCount; } - (NSUInteger)maxConcurrentPrefetchCount { return self.prefetchQueue.maxConcurrentOperationCount; } #pragma mark - Prefetch - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray *)urls { return [self prefetchURLs:urls progress:nil completed:nil]; } - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock { if (!urls || urls.count == 0) { if (completionBlock) { completionBlock(0, 0); } return nil; } SDWebImagePrefetchToken *token = [SDWebImagePrefetchToken new]; token.prefetcher = self; token.urls = urls; token->_skippedCount = 0; token->_finishedCount = 0; token->_totalCount = token.urls.count; atomic_flag_clear(&(token->_isAllFinished)); token.loadOperations = [NSPointerArray weakObjectsPointerArray]; token.prefetchOperations = [NSPointerArray weakObjectsPointerArray]; token.progressBlock = progressBlock; token.completionBlock = completionBlock; [self addRunningToken:token]; [self startPrefetchWithToken:token]; return token; } - (void)startPrefetchWithToken:(SDWebImagePrefetchToken * _Nonnull)token { for (NSURL *url in token.urls) { @autoreleasepool { @weakify(self); SDAsyncBlockOperation *prefetchOperation = [SDAsyncBlockOperation blockOperationWithBlock:^(SDAsyncBlockOperation * _Nonnull asyncOperation) { @strongify(self); if (!self || asyncOperation.isCancelled) { return; } id operation = [self.manager loadImageWithURL:url options:self.options context:self.context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { @strongify(self); if (!self) { return; } if (!finished) { return; } atomic_fetch_add_explicit(&(token->_finishedCount), 1, memory_order_relaxed); if (error) { // Add last failed atomic_fetch_add_explicit(&(token->_skippedCount), 1, memory_order_relaxed); } // Current operation finished [self callProgressBlockForToken:token imageURL:imageURL]; if (atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed) == token->_totalCount) { // All finished if (!atomic_flag_test_and_set_explicit(&(token->_isAllFinished), memory_order_relaxed)) { [self callCompletionBlockForToken:token]; [self removeRunningToken:token]; } } [asyncOperation complete]; }]; NSAssert(operation != nil, @"Operation should not be nil, [SDWebImageManager loadImageWithURL:options:context:progress:completed:] break prefetch logic"); SD_LOCK(token->_loadOperationsLock); [token.loadOperations addPointer:(__bridge void *)operation]; SD_UNLOCK(token->_loadOperationsLock); }]; SD_LOCK(token->_prefetchOperationsLock); [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation]; SD_UNLOCK(token->_prefetchOperationsLock); [self.prefetchQueue addOperation:prefetchOperation]; } } } #pragma mark - Cancel - (void)cancelPrefetching { @synchronized(self.runningTokens) { NSSet *copiedTokens = [self.runningTokens copy]; [copiedTokens makeObjectsPerformSelector:@selector(cancel)]; [self.runningTokens removeAllObjects]; } } - (void)callProgressBlockForToken:(SDWebImagePrefetchToken *)token imageURL:(NSURL *)url { if (!token) { return; } BOOL shouldCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]; NSUInteger tokenFinishedCount = [self tokenFinishedCount]; NSUInteger tokenTotalCount = [self tokenTotalCount]; NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed); NSUInteger totalCount = token->_totalCount; dispatch_async(self.delegateQueue, ^{ if (shouldCallDelegate) { [self.delegate imagePrefetcher:self didPrefetchURL:url finishedCount:tokenFinishedCount totalCount:tokenTotalCount]; } if (token.progressBlock) { token.progressBlock(finishedCount, totalCount); } }); } - (void)callCompletionBlockForToken:(SDWebImagePrefetchToken *)token { if (!token) { return; } BOOL shoulCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)] && ([self countOfRunningTokens] == 1); // last one NSUInteger tokenTotalCount = [self tokenTotalCount]; NSUInteger tokenSkippedCount = [self tokenSkippedCount]; NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed); NSUInteger skippedCount = atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed); dispatch_async(self.delegateQueue, ^{ if (shoulCallDelegate) { [self.delegate imagePrefetcher:self didFinishWithTotalCount:tokenTotalCount skippedCount:tokenSkippedCount]; } if (token.completionBlock) { token.completionBlock(finishedCount, skippedCount); } }); } #pragma mark - Helper - (NSUInteger)tokenTotalCount { NSUInteger tokenTotalCount = 0; @synchronized (self.runningTokens) { for (SDWebImagePrefetchToken *token in self.runningTokens) { tokenTotalCount += token->_totalCount; } } return tokenTotalCount; } - (NSUInteger)tokenSkippedCount { NSUInteger tokenSkippedCount = 0; @synchronized (self.runningTokens) { for (SDWebImagePrefetchToken *token in self.runningTokens) { tokenSkippedCount += atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed); } } return tokenSkippedCount; } - (NSUInteger)tokenFinishedCount { NSUInteger tokenFinishedCount = 0; @synchronized (self.runningTokens) { for (SDWebImagePrefetchToken *token in self.runningTokens) { tokenFinishedCount += atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed); } } return tokenFinishedCount; } - (void)addRunningToken:(SDWebImagePrefetchToken *)token { if (!token) { return; } @synchronized (self.runningTokens) { [self.runningTokens addObject:token]; } } - (void)removeRunningToken:(SDWebImagePrefetchToken *)token { if (!token) { return; } @synchronized (self.runningTokens) { [self.runningTokens removeObject:token]; } } - (NSUInteger)countOfRunningTokens { NSUInteger count = 0; @synchronized (self.runningTokens) { count = self.runningTokens.count; } return count; } @end @implementation SDWebImagePrefetchToken - (instancetype)init { self = [super init]; if (self) { SD_LOCK_INIT(_prefetchOperationsLock); SD_LOCK_INIT(_loadOperationsLock); } return self; } - (void)cancel { SD_LOCK(_prefetchOperationsLock); [self.prefetchOperations compact]; for (id operation in self.prefetchOperations) { id strongOperation = operation; if (strongOperation) { [strongOperation cancel]; } } self.prefetchOperations.count = 0; SD_UNLOCK(_prefetchOperationsLock); SD_LOCK(_loadOperationsLock); [self.loadOperations compact]; for (id operation in self.loadOperations) { id strongOperation = operation; if (strongOperation) { [strongOperation cancel]; } } self.loadOperations.count = 0; SD_UNLOCK(_loadOperationsLock); self.completionBlock = nil; self.progressBlock = nil; [self.prefetcher removeRunningToken:self]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageTransition.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_UIKIT || SD_MAC #import "SDImageCache.h" #if SD_UIKIT typedef UIViewAnimationOptions SDWebImageAnimationOptions; #else typedef NS_OPTIONS(NSUInteger, SDWebImageAnimationOptions) { SDWebImageAnimationOptionAllowsImplicitAnimation = 1 << 0, // specify `allowsImplicitAnimation` for the `NSAnimationContext` SDWebImageAnimationOptionCurveEaseInOut = 0 << 16, // default SDWebImageAnimationOptionCurveEaseIn = 1 << 16, SDWebImageAnimationOptionCurveEaseOut = 2 << 16, SDWebImageAnimationOptionCurveLinear = 3 << 16, SDWebImageAnimationOptionTransitionNone = 0 << 20, // default SDWebImageAnimationOptionTransitionFlipFromLeft = 1 << 20, SDWebImageAnimationOptionTransitionFlipFromRight = 2 << 20, SDWebImageAnimationOptionTransitionCurlUp = 3 << 20, SDWebImageAnimationOptionTransitionCurlDown = 4 << 20, SDWebImageAnimationOptionTransitionCrossDissolve = 5 << 20, SDWebImageAnimationOptionTransitionFlipFromTop = 6 << 20, SDWebImageAnimationOptionTransitionFlipFromBottom = 7 << 20, }; #endif typedef void (^SDWebImageTransitionPreparesBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL); typedef void (^SDWebImageTransitionAnimationsBlock)(__kindof UIView * _Nonnull view, UIImage * _Nullable image); typedef void (^SDWebImageTransitionCompletionBlock)(BOOL finished); /** This class is used to provide a transition animation after the view category load image finished. Use this on `sd_imageTransition` in UIView+WebCache.h for UIKit(iOS & tvOS), we use `+[UIView transitionWithView:duration:options:animations:completion]` for transition animation. for AppKit(macOS), we use `+[NSAnimationContext runAnimationGroup:completionHandler:]` for transition animation. You can call `+[NSAnimationContext currentContext]` to grab the context during animations block. @note These transition are provided for basic usage. If you need complicated animation, consider to directly use Core Animation or use `SDWebImageAvoidAutoSetImage` and implement your own after image load finished. */ @interface SDWebImageTransition : NSObject /** By default, we set the image to the view at the beginning of the animations. You can disable this and provide custom set image process */ @property (nonatomic, assign) BOOL avoidAutoSetImage; /** The duration of the transition animation, measured in seconds. Defaults to 0.5. */ @property (nonatomic, assign) NSTimeInterval duration; /** The timing function used for all animations within this transition animation (macOS). */ @property (nonatomic, strong, nullable) CAMediaTimingFunction *timingFunction API_UNAVAILABLE(ios, tvos, watchos) API_DEPRECATED("Use SDWebImageAnimationOptions instead, or grab NSAnimationContext.currentContext and modify the timingFunction", macos(10.10, 10.10)); /** A mask of options indicating how you want to perform the animations. */ @property (nonatomic, assign) SDWebImageAnimationOptions animationOptions; /** A block object to be executed before the animation sequence starts. */ @property (nonatomic, copy, nullable) SDWebImageTransitionPreparesBlock prepares; /** A block object that contains the changes you want to make to the specified view. */ @property (nonatomic, copy, nullable) SDWebImageTransitionAnimationsBlock animations; /** A block object to be executed when the animation sequence ends. */ @property (nonatomic, copy, nullable) SDWebImageTransitionCompletionBlock completion; @end /** Convenience way to create transition. Remember to specify the duration if needed. for UIKit, these transition just use the correspond `animationOptions`. By default we enable `UIViewAnimationOptionAllowUserInteraction` to allow user interaction during transition. for AppKit, these transition use Core Animation in `animations`. So your view must be layer-backed. Set `wantsLayer = YES` before you apply it. */ @interface SDWebImageTransition (Conveniences) /// Fade-in transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *fadeTransition; /// Flip from left transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromLeftTransition; /// Flip from right transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromRightTransition; /// Flip from top transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromTopTransition; /// Flip from bottom transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *flipFromBottomTransition; /// Curl up transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlUpTransition; /// Curl down transition. @property (nonatomic, class, nonnull, readonly) SDWebImageTransition *curlDownTransition; /// Fade-in transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)fadeTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(fade(duration:)); /// Flip from left transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(flipFromLeft(duration:)); /// Flip from right transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)flipFromRightTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(flipFromRight(duration:)); /// Flip from top transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)flipFromTopTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(flipFromTop(duration:)); /// Flip from bottom transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(flipFromBottom(duration:)); /// Curl up transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)curlUpTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(curlUp(duration:)); /// Curl down transition with duration. /// @param duration transition duration, use ease-in-out + (nonnull instancetype)curlDownTransitionWithDuration:(NSTimeInterval)duration NS_SWIFT_NAME(curlDown(duration:)); @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageTransition.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageTransition.h" #if SD_UIKIT || SD_MAC #if SD_MAC #import "SDWebImageTransitionInternal.h" #import "SDInternalMacros.h" CAMediaTimingFunction * SDTimingFunctionFromAnimationOptions(SDWebImageAnimationOptions options) { if (SD_OPTIONS_CONTAINS(SDWebImageAnimationOptionCurveLinear, options)) { return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; } else if (SD_OPTIONS_CONTAINS(SDWebImageAnimationOptionCurveEaseIn, options)) { return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; } else if (SD_OPTIONS_CONTAINS(SDWebImageAnimationOptionCurveEaseOut, options)) { return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; } else if (SD_OPTIONS_CONTAINS(SDWebImageAnimationOptionCurveEaseInOut, options)) { return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; } else { return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; } } CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions options) { if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionCrossDissolve)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionFade; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionFlipFromLeft)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionPush; trans.subtype = kCATransitionFromLeft; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionFlipFromRight)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionPush; trans.subtype = kCATransitionFromRight; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionFlipFromTop)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionPush; trans.subtype = kCATransitionFromTop; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionFlipFromBottom)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionPush; trans.subtype = kCATransitionFromBottom; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionCurlUp)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionReveal; trans.subtype = kCATransitionFromTop; return trans; } else if (SD_OPTIONS_CONTAINS(options, SDWebImageAnimationOptionTransitionCurlDown)) { CATransition *trans = [CATransition animation]; trans.type = kCATransitionReveal; trans.subtype = kCATransitionFromBottom; return trans; } else { return nil; } } #endif @implementation SDWebImageTransition - (instancetype)init { self = [super init]; if (self) { self.duration = 0.5; } return self; } @end @implementation SDWebImageTransition (Conveniences) + (SDWebImageTransition *)fadeTransition { return [self fadeTransitionWithDuration:0.5]; } + (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionCrossDissolve; #endif return transition; } + (SDWebImageTransition *)flipFromLeftTransition { return [self flipFromLeftTransitionWithDuration:0.5]; } + (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionFlipFromLeft; #endif return transition; } + (SDWebImageTransition *)flipFromRightTransition { return [self flipFromRightTransitionWithDuration:0.5]; } + (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionFlipFromRight; #endif return transition; } + (SDWebImageTransition *)flipFromTopTransition { return [self flipFromTopTransitionWithDuration:0.5]; } + (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionFlipFromTop; #endif return transition; } + (SDWebImageTransition *)flipFromBottomTransition { return [self flipFromBottomTransitionWithDuration:0.5]; } + (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionFlipFromBottom; #endif return transition; } + (SDWebImageTransition *)curlUpTransition { return [self curlUpTransitionWithDuration:0.5]; } + (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionCurlUp; #endif return transition; } + (SDWebImageTransition *)curlDownTransition { return [self curlDownTransitionWithDuration:0.5]; } + (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration { SDWebImageTransition *transition = [SDWebImageTransition new]; transition.duration = duration; #if SD_UIKIT transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction; #else transition.animationOptions = SDWebImageAnimationOptionTransitionCurlDown; #endif transition.duration = duration; return transition; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIButton+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_UIKIT #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with UIButton. */ @interface UIButton (WebCache) #pragma mark - Image /** * Get the current image URL. */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentImageURL; /** * Get the image URL for a control state. * * @param state Which state you want to know the URL for. The values are described in UIControlState. */ - (nullable NSURL *)sd_imageURLForState:(UIControlState)state; /** * Set the button `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the button `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; #pragma mark - Background Image /** * Get the current background image URL. */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentBackgroundImageURL; /** * Get the background image URL for a control state. * * @param state Which state you want to know the URL for. The values are described in UIControlState. */ - (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state; /** * Set the button `backgroundImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state NS_REFINED_FOR_SWIFT; /** * Set the button `backgroundImage` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the button `backgroundImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the button `backgroundImage` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the button `backgroundImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `backgroundImage` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the button `backgroundImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `backgroundImage` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the button `backgroundImage` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; #pragma mark - Cancel /** * Cancel the current image download */ - (void)sd_cancelImageLoadForState:(UIControlState)state; /** * Cancel the current backgroundImage download */ - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIButton+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIButton+WebCache.h" #if SD_UIKIT #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" static char imageURLStorageKey; typedef NSMutableDictionary SDStateImageURLDictionary; static inline NSString * imageURLKeyForState(UIControlState state) { return [NSString stringWithFormat:@"image_%lu", (unsigned long)state]; } static inline NSString * backgroundImageURLKeyForState(UIControlState state) { return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state]; } static inline NSString * imageOperationKeyForState(UIControlState state) { return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state]; } static inline NSString * backgroundImageOperationKeyForState(UIControlState state) { return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state]; } @implementation UIButton (WebCache) #pragma mark - Image - (nullable NSURL *)sd_currentImageURL { NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)]; if (!url) { url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)]; } return url; } - (nullable NSURL *)sd_imageURLForState:(UIControlState)state { return self.sd_imageURLStorage[imageURLKeyForState(state)]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { if (!url) { [self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)]; } else { self.sd_imageURLStorage[imageURLKeyForState(state)] = url; } SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextSetImageOperationKey] = imageOperationKeyForState(state); @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:mutableContext setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { @strongify(self); [self setImage:image forState:state]; } progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } #pragma mark - Background Image - (nullable NSURL *)sd_currentBackgroundImageURL { NSURL *url = self.sd_imageURLStorage[backgroundImageURLKeyForState(self.state)]; if (!url) { url = self.sd_imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)]; } return url; } - (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state { return self.sd_imageURLStorage[backgroundImageURLKeyForState(state)]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { if (!url) { [self.sd_imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)]; } else { self.sd_imageURLStorage[backgroundImageURLKeyForState(state)] = url; } SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextSetImageOperationKey] = backgroundImageOperationKeyForState(state); @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:mutableContext setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { @strongify(self); [self setBackgroundImage:image forState:state]; } progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } #pragma mark - Cancel - (void)sd_cancelImageLoadForState:(UIControlState)state { [self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)]; } - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state { [self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)]; } #pragma mark - Private - (SDStateImageURLDictionary *)sd_imageURLStorage { SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey); if (!storage) { storage = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return storage; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+ExtendedCacheData.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Fabrice Aneche * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" @interface UIImage (ExtendedCacheData) /** Read and Write the extended object and bind it to the image. Which can hold some extra metadata like Image's scale factor, URL rich link, date, etc. The extended object should conforms to NSCoding, which we use `NSKeyedArchiver` and `NSKeyedUnarchiver` to archive it to data, and write to disk cache. @note The disk cache preserve both of the data and extended data with the same cache key. For manual query, use the `SDDiskCache` protocol method `extendedDataForKey:` instead. @note You can specify arbitrary object conforms to NSCoding (NSObject protocol here is used to support object using `NS_ROOT_CLASS`, which is not NSObject subclass). If you load image from disk cache, you should check the extended object class to avoid corrupted data. @warning This object don't need to implements NSSecureCoding (but it's recommended), because we allows arbitrary class. */ @property (nonatomic, strong, nullable) id sd_extendedObject; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+ExtendedCacheData.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Fabrice Aneche * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+ExtendedCacheData.h" #import @implementation UIImage (ExtendedCacheData) - (id)sd_extendedObject { return objc_getAssociatedObject(self, @selector(sd_extendedObject)); } - (void)setSd_extendedObject:(id)sd_extendedObject { objc_setAssociatedObject(self, @selector(sd_extendedObject), sd_extendedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+ForceDecode.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" /** UIImage category about force decode feature (avoid Image/IO's lazy decoding during rendering behavior). */ @interface UIImage (ForceDecode) /** A bool value indicating whether the image has already been decoded. This can help to avoid extra force decode. */ @property (nonatomic, assign) BOOL sd_isDecoded; /** Decode the provided image. This is useful if you want to force decode the image before rendering to improve performance. @param image The image to be decoded @return The decoded image */ + (nullable UIImage *)sd_decodedImageWithImage:(nullable UIImage *)image; /** Decode and scale down the provided image @param image The image to be decoded @return The decoded and scaled down image */ + (nullable UIImage *)sd_decodedAndScaledDownImageWithImage:(nullable UIImage *)image; /** Decode and scale down the provided image with limit bytes @param image The image to be decoded @param bytes The limit bytes size. Provide 0 to use the build-in limit. @return The decoded and scaled down image */ + (nullable UIImage *)sd_decodedAndScaledDownImageWithImage:(nullable UIImage *)image limitBytes:(NSUInteger)bytes; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+ForceDecode.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+ForceDecode.h" #import "SDImageCoderHelper.h" #import "objc/runtime.h" @implementation UIImage (ForceDecode) - (BOOL)sd_isDecoded { NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isDecoded)); return value.boolValue; } - (void)setSd_isDecoded:(BOOL)sd_isDecoded { objc_setAssociatedObject(self, @selector(sd_isDecoded), @(sd_isDecoded), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + (nullable UIImage *)sd_decodedImageWithImage:(nullable UIImage *)image { if (!image) { return nil; } return [SDImageCoderHelper decodedImageWithImage:image]; } + (nullable UIImage *)sd_decodedAndScaledDownImageWithImage:(nullable UIImage *)image { return [self sd_decodedAndScaledDownImageWithImage:image limitBytes:0]; } + (nullable UIImage *)sd_decodedAndScaledDownImageWithImage:(nullable UIImage *)image limitBytes:(NSUInteger)bytes { if (!image) { return nil; } return [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:bytes]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+GIF.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Laurin Brandner * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" /** This category is just use as a convenience method. For more detail control, use methods in `UIImage+MultiFormat.h` or directly use `SDImageCoder`. */ @interface UIImage (GIF) /** Creates an animated UIImage from an NSData. This will create animated image if the data is Animated GIF. And will create a static image is the data is Static GIF. @param data The GIF data @return The created image */ + (nullable UIImage *)sd_imageWithGIFData:(nullable NSData *)data; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+GIF.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Laurin Brandner * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+GIF.h" #import "SDImageGIFCoder.h" @implementation UIImage (GIF) + (nullable UIImage *)sd_imageWithGIFData:(nullable NSData *)data { if (!data) { return nil; } return [[SDImageGIFCoder sharedCoder] decodedImageWithData:data options:0]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+MemoryCacheCost.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" /** UIImage category for memory cache cost. */ @interface UIImage (MemoryCacheCost) /** The memory cache cost for specify image used by image cache. The cost function is the bytes size held in memory. If you set some associated object to `UIImage`, you can set the custom value to indicate the memory cost. For `UIImage`, this method return the single frame bytes size when `image.images` is nil for static image. Return full frame bytes size when `image.images` is not nil for animated image. For `NSImage`, this method return the single frame bytes size because `NSImage` does not store all frames in memory. @note Note that because of the limitations of category this property can get out of sync if you create another instance with CGImage or other methods. @note For custom animated class conforms to `SDAnimatedImage`, you can override this getter method in your subclass to return a more proper value instead, which representing the current frame's total bytes. */ @property (assign, nonatomic) NSUInteger sd_memoryCost; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+MemoryCacheCost.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+MemoryCacheCost.h" #import "objc/runtime.h" #import "NSImage+Compatibility.h" FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) { CGImageRef imageRef = image.CGImage; if (!imageRef) { return 0; } NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef); NSUInteger frameCount; #if SD_MAC frameCount = 1; #elif SD_UIKIT || SD_WATCH frameCount = image.images.count > 0 ? image.images.count : 1; #endif NSUInteger cost = bytesPerFrame * frameCount; return cost; } @implementation UIImage (MemoryCacheCost) - (NSUInteger)sd_memoryCost { NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost)); NSUInteger memoryCost; if (value != nil) { memoryCost = [value unsignedIntegerValue]; } else { memoryCost = SDMemoryCacheCostForImage(self); } return memoryCost; } - (void)setSd_memoryCost:(NSUInteger)sd_memoryCost { objc_setAssociatedObject(self, @selector(sd_memoryCost), @(sd_memoryCost), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+Metadata.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "NSData+ImageContentType.h" /** UIImage category for image metadata, including animation, loop count, format, incremental, etc. */ @interface UIImage (Metadata) /** * UIKit: * For static image format, this value is always 0. * For animated image format, 0 means infinite looping. * Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods. * AppKit: * NSImage currently only support animated via GIF imageRep unlike UIImage. * The getter of this property will get the loop count from GIF imageRep * The setter of this property will set the loop count from GIF imageRep */ @property (nonatomic, assign) NSUInteger sd_imageLoopCount; /** * UIKit: * Check the `images` array property. * AppKit: * NSImage currently only support animated via GIF imageRep unlike UIImage. It will check the imageRep's frame count. */ @property (nonatomic, assign, readonly) BOOL sd_isAnimated; /** * UIKit: * Check the `isSymbolImage` property. Also check the system PDF(iOS 11+) && SVG(iOS 13+) support. * AppKit: * NSImage supports PDF && SVG && EPS imageRep, check the imageRep class. */ @property (nonatomic, assign, readonly) BOOL sd_isVector; /** * The image format represent the original compressed image data format. * If you don't manually specify a format, this information is retrieve from CGImage using `CGImageGetUTType`, which may return nil for non-CG based image. At this time it will return `SDImageFormatUndefined` as default value. * @note Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods. */ @property (nonatomic, assign) SDImageFormat sd_imageFormat; /** A bool value indicating whether the image is during incremental decoding and may not contains full pixels. */ @property (nonatomic, assign) BOOL sd_isIncremental; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+Metadata.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+Metadata.h" #import "NSImage+Compatibility.h" #import "SDInternalMacros.h" #import "objc/runtime.h" @implementation UIImage (Metadata) #if SD_UIKIT || SD_WATCH - (NSUInteger)sd_imageLoopCount { NSUInteger imageLoopCount = 0; NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageLoopCount)); if ([value isKindOfClass:[NSNumber class]]) { imageLoopCount = value.unsignedIntegerValue; } return imageLoopCount; } - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { NSNumber *value = @(sd_imageLoopCount); objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)sd_isAnimated { return (self.images != nil); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - (BOOL)sd_isVector { if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { // Xcode 11 supports symbol image, keep Xcode 10 compatible currently SEL SymbolSelector = NSSelectorFromString(@"isSymbolImage"); if ([self respondsToSelector:SymbolSelector] && [self performSelector:SymbolSelector]) { return YES; } // SVG SEL SVGSelector = SD_SEL_SPI(CGSVGDocument); if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector]) { return YES; } } if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { // PDF SEL PDFSelector = SD_SEL_SPI(CGPDFPage); if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector]) { return YES; } } return NO; } #pragma clang diagnostic pop #else - (NSUInteger)sd_imageLoopCount { NSUInteger imageLoopCount = 0; NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapImageRep = (NSBitmapImageRep *)imageRep; } if (bitmapImageRep) { imageLoopCount = [[bitmapImageRep valueForProperty:NSImageLoopCount] unsignedIntegerValue]; } return imageLoopCount; } - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapImageRep = (NSBitmapImageRep *)imageRep; } if (bitmapImageRep) { [bitmapImageRep setProperty:NSImageLoopCount withValue:@(sd_imageLoopCount)]; } } - (BOOL)sd_isAnimated { BOOL isAnimated = NO; NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapImageRep = (NSBitmapImageRep *)imageRep; } if (bitmapImageRep) { NSUInteger frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue]; isAnimated = frameCount > 1 ? YES : NO; } return isAnimated; } - (BOOL)sd_isVector { NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; if ([imageRep isKindOfClass:[NSPDFImageRep class]]) { return YES; } if ([imageRep isKindOfClass:[NSEPSImageRep class]]) { return YES; } if ([NSStringFromClass(imageRep.class) hasSuffix:@"NSSVGImageRep"]) { return YES; } return NO; } #endif - (SDImageFormat)sd_imageFormat { SDImageFormat imageFormat = SDImageFormatUndefined; NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageFormat)); if ([value isKindOfClass:[NSNumber class]]) { imageFormat = value.integerValue; return imageFormat; } // Check CGImage's UTType, may return nil for non-Image/IO based image if (@available(iOS 9.0, tvOS 9.0, macOS 10.11, watchOS 2.0, *)) { CFStringRef uttype = CGImageGetUTType(self.CGImage); imageFormat = [NSData sd_imageFormatFromUTType:uttype]; } return imageFormat; } - (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat { objc_setAssociatedObject(self, @selector(sd_imageFormat), @(sd_imageFormat), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)setSd_isIncremental:(BOOL)sd_isIncremental { objc_setAssociatedObject(self, @selector(sd_isIncremental), @(sd_isIncremental), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)sd_isIncremental { NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isIncremental)); return value.boolValue; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+MultiFormat.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "NSData+ImageContentType.h" /** UIImage category for convenient image format decoding/encoding. */ @interface UIImage (MultiFormat) #pragma mark - Decode /** Create and decode a image with the specify image data @param data The image data @return The created image */ + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data; /** Create and decode a image with the specify image data and scale @param data The image data @param scale The image scale factor. Should be greater than or equal to 1.0. @return The created image */ + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale; /** Create and decode a image with the specify image data and scale, allow specify animate/static control @param data The image data @param scale The image scale factor. Should be greater than or equal to 1.0. @param firstFrameOnly Even if the image data is animated image format, decode the first frame only as static image. @return The created image */ + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly; #pragma mark - Encode /** Encode the current image to the data, the image format is unspecified @note If the receiver is `SDAnimatedImage`, this will return the animated image data if available. No more extra encoding process. @return The encoded data. If can't encode, return nil */ - (nullable NSData *)sd_imageData; /** Encode the current image to data with the specify image format @param imageFormat The specify image format @return The encoded data. If can't encode, return nil */ - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat NS_SWIFT_NAME(sd_imageData(as:)); /** Encode the current image to data with the specify image format and compression quality @param imageFormat The specify image format @param compressionQuality The quality of the resulting image data. Value between 0.0-1.0. Some coders may not support compression quality. @return The encoded data. If can't encode, return nil */ - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality NS_SWIFT_NAME(sd_imageData(as:compressionQuality:)); /** Encode the current image to data with the specify image format and compression quality, allow specify animate/static control @param imageFormat The specify image format @param compressionQuality The quality of the resulting image data. Value between 0.0-1.0. Some coders may not support compression quality. @param firstFrameOnly Even if the image is animated image, encode the first frame only as static image. @return The encoded data. If can't encode, return nil */ - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly NS_SWIFT_NAME(sd_imageData(as:compressionQuality:firstFrameOnly:)); @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+MultiFormat.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+MultiFormat.h" #import "SDImageCodersManager.h" @implementation UIImage (MultiFormat) + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { return [self sd_imageWithData:data scale:1]; } + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale { return [self sd_imageWithData:data scale:scale firstFrameOnly:NO]; } + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly { if (!data) { return nil; } SDImageCoderOptions *options = @{SDImageCoderDecodeScaleFactor : @(MAX(scale, 1)), SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}; return [[SDImageCodersManager sharedManager] decodedImageWithData:data options:options]; } - (nullable NSData *)sd_imageData { return [self sd_imageDataAsFormat:SDImageFormatUndefined]; } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat { return [self sd_imageDataAsFormat:imageFormat compressionQuality:1]; } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality { return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO]; } - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly { SDImageCoderOptions *options = @{SDImageCoderEncodeCompressionQuality : @(compressionQuality), SDImageCoderEncodeFirstFrameOnly : @(firstFrameOnly)}; return [[SDImageCodersManager sharedManager] encodedDataWithImage:self format:imageFormat options:options]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+Transform.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" typedef NS_ENUM(NSUInteger, SDImageScaleMode) { SDImageScaleModeFill = 0, SDImageScaleModeAspectFit = 1, SDImageScaleModeAspectFill = 2 }; #if SD_UIKIT || SD_WATCH typedef UIRectCorner SDRectCorner; #else typedef NS_OPTIONS(NSUInteger, SDRectCorner) { SDRectCornerTopLeft = 1 << 0, SDRectCornerTopRight = 1 << 1, SDRectCornerBottomLeft = 1 << 2, SDRectCornerBottomRight = 1 << 3, SDRectCornerAllCorners = ~0UL }; #endif /** Provide some common method for `UIImage`. Image process is based on Core Graphics and vImage. */ @interface UIImage (Transform) #pragma mark - Image Geometry /** Returns a new image which is resized from this image. You can specify a larger or smaller size than the image size. The image content will be changed with the scale mode. @param size The new size to be resized, values should be positive. @param scaleMode The scale mode for image content. @return The new image with the given size. */ - (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; /** Returns a new image which is cropped from this image. @param rect Image's inner rect. @return The new image with the cropping rect. */ - (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect; /** Rounds a new image with a given corner radius and corners. @param cornerRadius The radius of each corner oval. Values larger than half the rectangle's width or height are clamped appropriately to half the width or height. @param corners A bitmask value that identifies the corners that you want rounded. You can use this parameter to round only a subset of the corners of the rectangle. @param borderWidth The inset border line width. Values larger than half the rectangle's width or height are clamped appropriately to half the width or height. @param borderColor The border stroke color. nil means clear color. @return The new image with the round corner. */ - (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor; /** Returns a new rotated image (relative to the center). @param angle Rotated radians in counterclockwise.⟲ @param fitSize YES: new image's size is extend to fit all content. NO: image's size will not change, content may be clipped. @return The new image with the rotation. */ - (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; /** Returns a new horizontally(vertically) flipped image. @param horizontal YES to flip the image horizontally. ⇋ @param vertical YES to flip the image vertically. ⥯ @return The new image with the flipping. */ - (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; #pragma mark - Image Blending /** Return a tinted image with the given color. This actually use alpha blending of current image and the tint color. @param tintColor The tint color. @return The new image with the tint color. */ - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; /** Return the pixel color at specify position. The point is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. @note The point's x/y should not be smaller than 0, or greater than or equal to width/height. @note The overhead of object creation means this method is best suited for infrequent color sampling. For heavy image processing, grab the raw bitmap data and process yourself. @param point The position of pixel @return The color for specify pixel, or nil if any error occur */ - (nullable UIColor *)sd_colorAtPoint:(CGPoint)point; /** Return the pixel color array with specify rectangle. The rect is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. @note The rect's width/height should not be smaller than or equal to 0. The minX/minY should not be smaller than 0. The maxX/maxY should not be greater than width/height. Attention this limit is different from `sd_colorAtPoint:` (point: (0, 0) like rect: (0, 0, 1, 1)) @note The overhead of object creation means this method is best suited for infrequent color sampling. For heavy image processing, grab the raw bitmap data and process yourself. @param rect The rectangle of pixels @return The color array for specify pixels, or nil if any error occur */ - (nullable NSArray *)sd_colorsWithRect:(CGRect)rect; #pragma mark - Image Effect /** Return a new image applied a blur effect. @param blurRadius The radius of the blur in points, 0 means no blur effect. @return The new image with blur effect, or nil if an error occurs (e.g. no enough memory). */ - (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius; #if SD_UIKIT || SD_MAC /** Return a new image applied a CIFilter. @param filter The CIFilter to be applied to the image. @return The new image with the CIFilter, or nil if an error occurs (e.g. no enough memory). */ - (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter; #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+Transform.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImage+Transform.h" #import "NSImage+Compatibility.h" #import "SDImageGraphics.h" #import "SDGraphicsImageRenderer.h" #import "NSBezierPath+SDRoundedCorners.h" #import #if SD_UIKIT || SD_MAC #import #endif static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) { rect = CGRectStandardize(rect); size.width = size.width < 0 ? -size.width : size.width; size.height = size.height < 0 ? -size.height : size.height; CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); switch (scaleMode) { case SDImageScaleModeAspectFit: case SDImageScaleModeAspectFill: { if (rect.size.width < 0.01 || rect.size.height < 0.01 || size.width < 0.01 || size.height < 0.01) { rect.origin = center; rect.size = CGSizeZero; } else { CGFloat scale; if (scaleMode == SDImageScaleModeAspectFit) { if (size.width / size.height < rect.size.width / rect.size.height) { scale = rect.size.height / size.height; } else { scale = rect.size.width / size.width; } } else { if (size.width / size.height < rect.size.width / rect.size.height) { scale = rect.size.width / size.width; } else { scale = rect.size.height / size.height; } } size.width *= scale; size.height *= scale; rect.size = size; rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); } } break; case SDImageScaleModeFill: default: { rect = rect; } } return rect; } static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) { // Get alpha info, byteOrder info CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask; CGFloat r = 0, g = 0, b = 0, a = 1; BOOL byteOrderNormal = NO; switch (byteOrderInfo) { case kCGBitmapByteOrderDefault: { byteOrderNormal = YES; } break; case kCGBitmapByteOrder32Little: { } break; case kCGBitmapByteOrder32Big: { byteOrderNormal = YES; } break; default: break; } switch (alphaInfo) { case kCGImageAlphaPremultipliedFirst: case kCGImageAlphaFirst: { if (byteOrderNormal) { // ARGB8888 a = pixel[0] / 255.0; r = pixel[1] / 255.0; g = pixel[2] / 255.0; b = pixel[3] / 255.0; } else { // BGRA8888 b = pixel[0] / 255.0; g = pixel[1] / 255.0; r = pixel[2] / 255.0; a = pixel[3] / 255.0; } } break; case kCGImageAlphaPremultipliedLast: case kCGImageAlphaLast: { if (byteOrderNormal) { // RGBA8888 r = pixel[0] / 255.0; g = pixel[1] / 255.0; b = pixel[2] / 255.0; a = pixel[3] / 255.0; } else { // ABGR8888 a = pixel[0] / 255.0; b = pixel[1] / 255.0; g = pixel[2] / 255.0; r = pixel[3] / 255.0; } } break; case kCGImageAlphaNone: { if (byteOrderNormal) { // RGB r = pixel[0] / 255.0; g = pixel[1] / 255.0; b = pixel[2] / 255.0; } else { // BGR b = pixel[0] / 255.0; g = pixel[1] / 255.0; r = pixel[2] / 255.0; } } break; case kCGImageAlphaNoneSkipLast: { if (byteOrderNormal) { // RGBX r = pixel[0] / 255.0; g = pixel[1] / 255.0; b = pixel[2] / 255.0; } else { // XBGR b = pixel[1] / 255.0; g = pixel[2] / 255.0; r = pixel[3] / 255.0; } } break; case kCGImageAlphaNoneSkipFirst: { if (byteOrderNormal) { // XRGB r = pixel[1] / 255.0; g = pixel[2] / 255.0; b = pixel[3] / 255.0; } else { // BGRX b = pixel[0] / 255.0; g = pixel[1] / 255.0; r = pixel[2] / 255.0; } } break; case kCGImageAlphaOnly: { // A a = pixel[0] / 255.0; } break; default: break; } return [UIColor colorWithRed:r green:g blue:b alpha:a]; } #if SD_UIKIT || SD_MAC // Create-Rule, caller should call CGImageRelease static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ciImage) { CGImageRef imageRef = NULL; if (@available(iOS 10, macOS 10.12, tvOS 10, *)) { imageRef = ciImage.CGImage; } if (!imageRef) { CIContext *context = [CIContext context]; imageRef = [context createCGImage:ciImage fromRect:ciImage.extent]; } else { CGImageRetain(imageRef); } return imageRef; } #endif @implementation UIImage (Transform) - (void)sd_drawInRect:(CGRect)rect context:(CGContextRef)context scaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips { CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode); if (drawRect.size.width == 0 || drawRect.size.height == 0) return; if (clips) { if (context) { CGContextSaveGState(context); CGContextAddRect(context, rect); CGContextClip(context); [self drawInRect:drawRect]; CGContextRestoreGState(context); } } else { [self drawInRect:drawRect]; } } - (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { if (size.width <= 0 || size.height <= 0) return nil; SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; format.scale = self.scale; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) context:context scaleMode:scaleMode clipsToBounds:NO]; }]; return image; } - (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect { rect.origin.x *= self.scale; rect.origin.y *= self.scale; rect.size.width *= self.scale; rect.size.height *= self.scale; if (rect.size.width <= 0 || rect.size.height <= 0) return nil; #if SD_UIKIT || SD_MAC // CIImage shortcut if (self.CIImage) { CGRect croppingRect = CGRectMake(rect.origin.x, self.size.height - CGRectGetMaxY(rect), rect.size.width, rect.size.height); CIImage *ciImage = [self.CIImage imageByCroppingToRect:croppingRect]; #if SD_UIKIT UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif return image; } #endif CGImageRef imageRef = self.CGImage; if (!imageRef) { return nil; } CGImageRef croppedImageRef = CGImageCreateWithImageInRect(imageRef, rect); if (!croppedImageRef) { return nil; } #if SD_UIKIT || SD_WATCH UIImage *image = [UIImage imageWithCGImage:croppedImageRef scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCGImage:croppedImageRef scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif CGImageRelease(croppedImageRef); return image; } - (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor { SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; format.scale = self.scale; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format]; UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); CGFloat minSize = MIN(self.size.width, self.size.height); if (borderWidth < minSize / 2) { #if SD_UIKIT || SD_WATCH UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)]; #else NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; #endif [path closePath]; CGContextSaveGState(context); [path addClip]; [self drawInRect:rect]; CGContextRestoreGState(context); } if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) { CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale; CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset); CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0; #if SD_UIKIT || SD_WATCH UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)]; #else NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; #endif [path closePath]; path.lineWidth = borderWidth; [borderColor setStroke]; [path stroke]; } }]; return image; } - (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { size_t width = self.size.width; size_t height = self.size.height; CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height), fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity); #if SD_UIKIT || SD_MAC // CIImage shortcut if (self.CIImage) { CIImage *ciImage = self.CIImage; if (fitSize) { CGAffineTransform transform = CGAffineTransformMakeRotation(angle); ciImage = [ciImage imageByApplyingTransform:transform]; } else { CIFilter *filter = [CIFilter filterWithName:@"CIStraightenFilter"]; [filter setValue:ciImage forKey:kCIInputImageKey]; [filter setValue:@(angle) forKey:kCIInputAngleKey]; ciImage = filter.outputImage; } #if SD_UIKIT || SD_WATCH UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif return image; } #endif SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; format.scale = self.scale; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format]; UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { CGContextSetShouldAntialias(context, true); CGContextSetAllowsAntialiasing(context, true); CGContextSetInterpolationQuality(context, kCGInterpolationHigh); CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5)); #if SD_UIKIT || SD_WATCH // Use UIKit coordinate system counterclockwise (⟲) CGContextRotateCTM(context, -angle); #else CGContextRotateCTM(context, angle); #endif [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)]; }]; return image; } - (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { size_t width = self.size.width; size_t height = self.size.height; #if SD_UIKIT || SD_MAC // CIImage shortcut if (self.CIImage) { CGAffineTransform transform = CGAffineTransformIdentity; // Use UIKit coordinate system if (horizontal) { CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0); transform = CGAffineTransformConcat(transform, flipHorizontal); } if (vertical) { CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height); transform = CGAffineTransformConcat(transform, flipVertical); } CIImage *ciImage = [self.CIImage imageByApplyingTransform:transform]; #if SD_UIKIT UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif return image; } #endif SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; format.scale = self.scale; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format]; UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { // Use UIKit coordinate system if (horizontal) { CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0); CGContextConcatCTM(context, flipHorizontal); } if (vertical) { CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height); CGContextConcatCTM(context, flipVertical); } [self drawInRect:CGRectMake(0, 0, width, height)]; }]; return image; } #pragma mark - Image Blending - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor { BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__; if (!hasTint) { return self; } #if SD_UIKIT || SD_MAC // CIImage shortcut if (self.CIImage) { CIImage *ciImage = self.CIImage; CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]]; colorImage = [colorImage imageByCroppingToRect:ciImage.extent]; CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"]; [filter setValue:colorImage forKey:kCIInputImageKey]; [filter setValue:ciImage forKey:kCIInputBackgroundImageKey]; ciImage = filter.outputImage; #if SD_UIKIT UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif return image; } #endif CGSize size = self.size; CGRect rect = { CGPointZero, size }; CGFloat scale = self.scale; // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing CGBlendMode blendMode = kCGBlendModeSourceAtop; SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; format.scale = scale; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { [self drawInRect:rect]; CGContextSetBlendMode(context, blendMode); CGContextSetFillColorWithColor(context, tintColor.CGColor); CGContextFillRect(context, rect); }]; return image; } - (nullable UIColor *)sd_colorAtPoint:(CGPoint)point { CGImageRef imageRef = NULL; // CIImage compatible #if SD_UIKIT || SD_MAC if (self.CIImage) { imageRef = SDCreateCGImageFromCIImage(self.CIImage); } #endif if (!imageRef) { imageRef = self.CGImage; CGImageRetain(imageRef); } if (!imageRef) { return nil; } // Check point CGFloat width = CGImageGetWidth(imageRef); CGFloat height = CGImageGetHeight(imageRef); if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) { CGImageRelease(imageRef); return nil; } // Get pixels CGDataProviderRef provider = CGImageGetDataProvider(imageRef); if (!provider) { CGImageRelease(imageRef); return nil; } CFDataRef data = CGDataProviderCopyData(provider); if (!data) { CGImageRelease(imageRef); return nil; } // Get pixel at point size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4); if (CFDataGetLength(data) < range.location + range.length) { CFRelease(data); CGImageRelease(imageRef); return nil; } Pixel_8888 pixel = {0}; CFDataGetBytes(data, range, pixel); CFRelease(data); CGImageRelease(imageRef); // Convert to color return SDGetColorFromPixel(pixel, bitmapInfo); } - (nullable NSArray *)sd_colorsWithRect:(CGRect)rect { CGImageRef imageRef = NULL; // CIImage compatible #if SD_UIKIT || SD_MAC if (self.CIImage) { imageRef = SDCreateCGImageFromCIImage(self.CIImage); } #endif if (!imageRef) { imageRef = self.CGImage; CGImageRetain(imageRef); } if (!imageRef) { return nil; } // Check rect CGFloat width = CGImageGetWidth(imageRef); CGFloat height = CGImageGetHeight(imageRef); if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) { CGImageRelease(imageRef); return nil; } // Get pixels CGDataProviderRef provider = CGImageGetDataProvider(imageRef); if (!provider) { CGImageRelease(imageRef); return nil; } CFDataRef data = CGDataProviderCopyData(provider); if (!data) { CGImageRelease(imageRef); return nil; } // Get pixels with rect size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); size_t start = bytesPerRow * CGRectGetMinY(rect) + components * CGRectGetMinX(rect); size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect); if (CFDataGetLength(data) < (CFIndex)end) { CFRelease(data); CGImageRelease(imageRef); return nil; } const UInt8 *pixels = CFDataGetBytePtr(data); size_t row = CGRectGetMinY(rect); size_t col = CGRectGetMaxX(rect); // Convert to color CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); NSMutableArray *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)]; for (size_t index = start; index < end; index += 4) { if (index >= row * bytesPerRow + col * components) { // Index beyond the end of current row, go next row row++; index = row * bytesPerRow + CGRectGetMinX(rect) * components; index -= 4; continue; } Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]}; UIColor *color = SDGetColorFromPixel(pixel, bitmapInfo); [colors addObject:color]; } CFRelease(data); CGImageRelease(imageRef); return [colors copy]; } #pragma mark - Image Effect // We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIGaussianBlur`. For other blur effect, use any filter in `CICategoryBlur` - (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius { if (self.size.width < 1 || self.size.height < 1) { return nil; } BOOL hasBlur = blurRadius > __FLT_EPSILON__; if (!hasBlur) { return self; } CGFloat scale = self.scale; CGFloat inputRadius = blurRadius * scale; #if SD_UIKIT || SD_MAC if (self.CIImage) { CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"]; [filter setValue:self.CIImage forKey:kCIInputImageKey]; [filter setValue:@(inputRadius) forKey:kCIInputRadiusKey]; CIImage *ciImage = filter.outputImage; ciImage = [ciImage imageByCroppingToRect:CGRectMake(0, 0, self.size.width, self.size.height)]; #if SD_UIKIT UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif return image; } #endif CGImageRef imageRef = self.CGImage; //convert to BGRA if it isn't if (CGImageGetBitsPerPixel(imageRef) != 32 || CGImageGetBitsPerComponent(imageRef) != 8 || !((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) { SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; imageRef = SDGraphicsGetImageFromCurrentImageContext().CGImage; SDGraphicsEndImageContext(); } vImage_Buffer effect = {}, scratch = {}; vImage_Buffer *input = NULL, *output = NULL; vImage_CGImageFormat format = { .bitsPerComponent = 8, .bitsPerPixel = 32, .colorSpace = NULL, .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer. .version = 0, .decode = NULL, .renderingIntent = kCGRenderingIntentDefault }; vImage_Error err; err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImageNoFlags); if (err != kvImageNoError) { NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self); return nil; } err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags); if (err != kvImageNoError) { NSLog(@"UIImage+Transform error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self); return nil; } input = &effect; output = &scratch; if (hasBlur) { // A description of how to compute the box kernel width from the Gaussian // radius (aka standard deviation) appears in the SVG spec: // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement // // For larger values of 's' (s >= 2.0), an approximation can be used: Three // successive box-blurs build a piece-wise quadratic convolution kernel, which // approximates the Gaussian kernel to within roughly 3%. // // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) // // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. // if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0; uint32_t radius = floor(inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5); radius |= 1; // force radius to be odd so that the three box-blur methodology works. int iterations; if (blurRadius * scale < 0.5) iterations = 1; else if (blurRadius * scale < 1.5) iterations = 2; else iterations = 3; NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); void *temp = malloc(tempSize); for (int i = 0; i < iterations; i++) { vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend); vImage_Buffer *tmp = input; input = output; output = tmp; } free(temp); } CGImageRef effectCGImage = NULL; effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoAllocate, NULL); if (effectCGImage == NULL) { effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL); free(input->data); } free(output->data); #if SD_UIKIT || SD_WATCH UIImage *outputImage = [UIImage imageWithCGImage:effectCGImage scale:self.scale orientation:self.imageOrientation]; #else UIImage *outputImage = [[UIImage alloc] initWithCGImage:effectCGImage scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif CGImageRelease(effectCGImage); return outputImage; } #if SD_UIKIT || SD_MAC - (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter { CIImage *inputImage; if (self.CIImage) { inputImage = self.CIImage; } else { CGImageRef imageRef = self.CGImage; if (!imageRef) { return nil; } inputImage = [CIImage imageWithCGImage:imageRef]; } if (!inputImage) return nil; CIContext *context = [CIContext context]; [filter setValue:inputImage forKey:kCIInputImageKey]; CIImage *outputImage = filter.outputImage; if (!outputImage) return nil; CGImageRef imageRef = [context createCGImage:outputImage fromRect:outputImage.extent]; if (!imageRef) return nil; #if SD_UIKIT UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; #else UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp]; #endif CGImageRelease(imageRef); return image; } #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImageView+HighlightedWebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_UIKIT #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with UIImageView for highlighted state. */ @interface UIImageView (HighlightedWebCache) /** * Set the imageView `highlightedImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the imageView `highlightedImage` with an `url`, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the imageView `highlightedImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `highlightedImage` with an `url`, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImageView+HighlightedWebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImageView+HighlightedWebCache.h" #if SD_UIKIT #import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" static NSString * const SDHighlightedImageOperationKey = @"UIImageViewImageOperationHighlighted"; @implementation UIImageView (HighlightedWebCache) - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil]; } - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil]; } - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setHighlightedImageWithURL:url options:options context:context progress:nil completed:nil]; } - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock]; } - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock]; } - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { @weakify(self); SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } mutableContext[SDWebImageContextSetImageOperationKey] = SDHighlightedImageOperationKey; [self sd_internalSetImageWithURL:url placeholderImage:nil options:options context:mutableContext setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { @strongify(self); self.highlightedImage = image; } progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImageView+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageManager.h" /** * Usage with a UITableViewCell sub-class: * * @code #import ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @"MyIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]; } // Here we use the provided sd_setImageWithURL:placeholderImage: method to load the web image // Ensure you use a placeholder image otherwise cells will be initialized with no image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"]]; cell.textLabel.text = @"My Text"; return cell; } * @endcode */ /** * Integrates SDWebImage async downloading and caching of remote images with UIImageView. */ @interface UIImageView (WebCache) /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder, custom options and context. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIImageView+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImageView+WebCache.h" #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" #import "UIView+WebCache.h" @implementation UIImageView (WebCache) - (void)sd_setImageWithURL:(nullable NSURL *)url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil]; } - (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock]; } - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options context:context setImageBlock:nil progress:progressBlock completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, imageURL); } }]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIView+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDWebImageManager.h" #import "SDWebImageTransition.h" #import "SDWebImageIndicator.h" /** The value specify that the image progress unit count cannot be determined because the progressBlock is not been called. */ FOUNDATION_EXPORT const int64_t SDWebImageProgressUnitCountUnknown; /* 1LL */ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL); /** Integrates SDWebImage async downloading and caching of remote images with UIView subclass. */ @interface UIView (WebCache) /** * Get the current image URL. * * @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly. */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL; /** * Get the current image operation key. Operation key is used to identify the different queries for one view instance (like UIButton). * See more about this in `SDWebImageContextSetImageOperationKey`. * If you cancel current image load, the key will be set to nil. * @note You can use method `UIView+WebCacheOperation` to investigate different queries' operation. */ @property (nonatomic, strong, readonly, nullable) NSString *sd_latestOperationKey; /** * The current image loading progress associated to the view. The unit count is the received size and excepted size of download. * The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue). * @note You can use Key-Value Observing on the progress, but you should take care that the change to progress is from a background queue during download(the same as progressBlock). If you want to using KVO and update the UI, make sure to dispatch on the main queue. And it's recommend to use some KVO libs like KVOController because it's more safe and easy to use. * @note The getter will create a progress instance if the value is nil. But by default, we don't create one. If you need to use Key-Value Observing, you must trigger the getter or set a custom progress instance before the loading start. The default value is nil. * @note Note that because of the limitations of categories this property can get out of sync if you update the progress directly. */ @property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress; /** * Set the imageView `image` with an `url` and optionally a placeholder image. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. * @param setImageBlock Block used for custom set image code. If not provide, use the built-in set image code (supports `UIImageView/NSImageView` and `UIButton/NSButton` currently) * @param progressBlock A block called while image is downloading * @note the progress block is executed on a background queue * @param completedBlock A block called when operation has been completed. * This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter. * In case of error the image parameter is nil and the third parameter may contain an NSError. * * The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * or from the memory cache or from the network. * * The fifth parameter normally is always YES. However, if you provide SDWebImageAvoidAutoSetImage with SDWebImageProgressiveLoad options to enable progressive downloading and set the image yourself. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * block is called a last time with the full image and the last parameter set to YES. * * The last parameter is the original image URL */ - (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock; /** * Cancel the current image load */ - (void)sd_cancelCurrentImageLoad; #if SD_UIKIT || SD_MAC #pragma mark - Image Transition /** The image transition when image load finished. See `SDWebImageTransition`. If you specify nil, do not do transition. Defaults to nil. */ @property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition; #pragma mark - Image Indicator /** The image indicator during the image loading. If you do not need indicator, specify nil. Defaults to nil The setter will remove the old indicator view and add new indicator view to current view's subview. @note Because this is UI related, you should access only from the main queue. */ @property (nonatomic, strong, nullable) id sd_imageIndicator; #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIView+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIView+WebCache.h" #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" #import "SDWebImageError.h" #import "SDInternalMacros.h" #import "SDWebImageTransitionInternal.h" const int64_t SDWebImageProgressUnitCountUnknown = 1LL; @implementation UIView (WebCache) - (nullable NSURL *)sd_imageURL { return objc_getAssociatedObject(self, @selector(sd_imageURL)); } - (void)setSd_imageURL:(NSURL * _Nullable)sd_imageURL { objc_setAssociatedObject(self, @selector(sd_imageURL), sd_imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (nullable NSString *)sd_latestOperationKey { return objc_getAssociatedObject(self, @selector(sd_latestOperationKey)); } - (void)setSd_latestOperationKey:(NSString * _Nullable)sd_latestOperationKey { objc_setAssociatedObject(self, @selector(sd_latestOperationKey), sd_latestOperationKey, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSProgress *)sd_imageProgress { NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress)); if (!progress) { progress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; self.sd_imageProgress = progress; } return progress; } - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress { objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { if (context) { // copy to avoid mutable object context = [context copy]; } else { context = [NSDictionary dictionary]; } NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey]; if (!validOperationKey) { // pass through the operation key to downstream, which can used for tracing operation or image view class validOperationKey = NSStringFromClass([self class]); SDWebImageMutableContext *mutableContext = [context mutableCopy]; mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey; context = [mutableContext copy]; } self.sd_latestOperationKey = validOperationKey; [self sd_cancelImageLoadOperationWithKey:validOperationKey]; self.sd_imageURL = url; if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url]; }); } if (url) { // reset the progress NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress)); if (imageProgress) { imageProgress.totalUnitCount = 0; imageProgress.completedUnitCount = 0; } #if SD_UIKIT || SD_MAC // check and start image indicator [self sd_startImageIndicator]; id imageIndicator = self.sd_imageIndicator; #endif SDWebImageManager *manager = context[SDWebImageContextCustomManager]; if (!manager) { manager = [SDWebImageManager sharedManager]; } else { // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager) SDWebImageMutableContext *mutableContext = [context mutableCopy]; mutableContext[SDWebImageContextCustomManager] = nil; context = [mutableContext copy]; } SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { if (imageProgress) { imageProgress.totalUnitCount = expectedSize; imageProgress.completedUnitCount = receivedSize; } #if SD_UIKIT || SD_MAC if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) { double progress = 0; if (expectedSize != 0) { progress = (double)receivedSize / expectedSize; } progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0 dispatch_async(dispatch_get_main_queue(), ^{ [imageIndicator updateIndicatorProgress:progress]; }); } #endif if (progressBlock) { progressBlock(receivedSize, expectedSize, targetURL); } }; @weakify(self); id operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { @strongify(self); if (!self) { return; } // if the progress not been updated, mark it to complete state if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) { imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown; imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown; } #if SD_UIKIT || SD_MAC // check and stop image indicator if (finished) { [self sd_stopImageIndicator]; } #endif BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage); BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) || (!image && !(options & SDWebImageDelayPlaceholder))); SDWebImageNoParamsBlock callCompletedBlockClosure = ^{ if (!self) { return; } if (!shouldNotSetImage) { [self sd_setNeedsLayout]; } if (completedBlock && shouldCallCompletedBlock) { completedBlock(image, data, error, cacheType, finished, url); } }; // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set // OR // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set if (shouldNotSetImage) { dispatch_main_async_safe(callCompletedBlockClosure); return; } UIImage *targetImage = nil; NSData *targetData = nil; if (image) { // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set targetImage = image; targetData = data; } else if (options & SDWebImageDelayPlaceholder) { // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set targetImage = placeholder; targetData = nil; } #if SD_UIKIT || SD_MAC // check whether we should use the image transition SDWebImageTransition *transition = nil; BOOL shouldUseTransition = NO; if (options & SDWebImageForceTransition) { // Always shouldUseTransition = YES; } else if (cacheType == SDImageCacheTypeNone) { // From network shouldUseTransition = YES; } else { // From disk (and, user don't use sync query) if (cacheType == SDImageCacheTypeMemory) { shouldUseTransition = NO; } else if (cacheType == SDImageCacheTypeDisk) { if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) { shouldUseTransition = NO; } else { shouldUseTransition = YES; } } else { // Not valid cache type, fallback shouldUseTransition = NO; } } if (finished && shouldUseTransition) { transition = self.sd_imageTransition; } #endif dispatch_main_async_safe(^{ #if SD_UIKIT || SD_MAC [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL]; #else [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL]; #endif callCompletedBlockClosure(); }); }]; [self sd_setImageLoadOperation:operation forKey:validOperationKey]; } else { #if SD_UIKIT || SD_MAC [self sd_stopImageIndicator]; #endif dispatch_main_async_safe(^{ if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}]; completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url); } }); } } - (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:self.sd_latestOperationKey]; self.sd_latestOperationKey = nil; } - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL { #if SD_UIKIT || SD_MAC [self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:cacheType imageURL:imageURL]; #else // watchOS does not support view transition. Simplify the logic if (setImageBlock) { setImageBlock(image, imageData, cacheType, imageURL); } else if ([self isKindOfClass:[UIImageView class]]) { UIImageView *imageView = (UIImageView *)self; [imageView setImage:image]; } #endif } #if SD_UIKIT || SD_MAC - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL { UIView *view = self; SDSetImageBlock finalSetImageBlock; if (setImageBlock) { finalSetImageBlock = setImageBlock; } else if ([view isKindOfClass:[UIImageView class]]) { UIImageView *imageView = (UIImageView *)view; finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) { imageView.image = setImage; }; } #if SD_UIKIT else if ([view isKindOfClass:[UIButton class]]) { UIButton *button = (UIButton *)view; finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) { [button setImage:setImage forState:UIControlStateNormal]; }; } #endif #if SD_MAC else if ([view isKindOfClass:[NSButton class]]) { NSButton *button = (NSButton *)view; finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) { button.image = setImage; }; } #endif if (transition) { NSString *originalOperationKey = view.sd_latestOperationKey; #if SD_UIKIT [UIView transitionWithView:view duration:0 options:0 animations:^{ if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } // 0 duration to let UIKit render placeholder and prepares block if (transition.prepares) { transition.prepares(view, image, imageData, cacheType, imageURL); } } completion:^(BOOL finished) { [UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{ if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } if (finalSetImageBlock && !transition.avoidAutoSetImage) { finalSetImageBlock(image, imageData, cacheType, imageURL); } if (transition.animations) { transition.animations(view, image); } } completion:^(BOOL finished) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } if (transition.completion) { transition.completion(finished); } }]; }]; #elif SD_MAC [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } // 0 duration to let AppKit render placeholder and prepares block prepareContext.duration = 0; if (transition.prepares) { transition.prepares(view, image, imageData, cacheType, imageURL); } } completionHandler:^{ [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } context.duration = transition.duration; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CAMediaTimingFunction *timingFunction = transition.timingFunction; #pragma clang diagnostic pop if (!timingFunction) { timingFunction = SDTimingFunctionFromAnimationOptions(transition.animationOptions); } context.timingFunction = timingFunction; context.allowsImplicitAnimation = SD_OPTIONS_CONTAINS(transition.animationOptions, SDWebImageAnimationOptionAllowsImplicitAnimation); if (finalSetImageBlock && !transition.avoidAutoSetImage) { finalSetImageBlock(image, imageData, cacheType, imageURL); } CATransition *trans = SDTransitionFromAnimationOptions(transition.animationOptions); if (trans) { [view.layer addAnimation:trans forKey:kCATransition]; } if (transition.animations) { transition.animations(view, image); } } completionHandler:^{ if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) { return; } if (transition.completion) { transition.completion(YES); } }]; }]; #endif } else { if (finalSetImageBlock) { finalSetImageBlock(image, imageData, cacheType, imageURL); } } } #endif - (void)sd_setNeedsLayout { #if SD_UIKIT [self setNeedsLayout]; #elif SD_MAC [self setNeedsLayout:YES]; #elif SD_WATCH // Do nothing because WatchKit automatically layout the view after property change #endif } #if SD_UIKIT || SD_MAC #pragma mark - Image Transition - (SDWebImageTransition *)sd_imageTransition { return objc_getAssociatedObject(self, @selector(sd_imageTransition)); } - (void)setSd_imageTransition:(SDWebImageTransition *)sd_imageTransition { objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - Indicator - (id)sd_imageIndicator { return objc_getAssociatedObject(self, @selector(sd_imageIndicator)); } - (void)setSd_imageIndicator:(id)sd_imageIndicator { // Remove the old indicator view id previousIndicator = self.sd_imageIndicator; [previousIndicator.indicatorView removeFromSuperview]; objc_setAssociatedObject(self, @selector(sd_imageIndicator), sd_imageIndicator, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // Add the new indicator view UIView *view = sd_imageIndicator.indicatorView; if (CGRectEqualToRect(view.frame, CGRectZero)) { view.frame = self.bounds; } // Center the indicator view #if SD_MAC [view setFrameOrigin:CGPointMake(round((NSWidth(self.bounds) - NSWidth(view.frame)) / 2), round((NSHeight(self.bounds) - NSHeight(view.frame)) / 2))]; #else view.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); #endif view.hidden = NO; [self addSubview:view]; } - (void)sd_startImageIndicator { id imageIndicator = self.sd_imageIndicator; if (!imageIndicator) { return; } dispatch_main_async_safe(^{ [imageIndicator startAnimatingIndicator]; }); } - (void)sd_stopImageIndicator { id imageIndicator = self.sd_imageIndicator; if (!imageIndicator) { return; } dispatch_main_async_safe(^{ [imageIndicator stopAnimatingIndicator]; }); } #endif @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIView+WebCacheOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageOperation.h" /** These methods are used to support canceling for UIView image loading, it's designed to be used internal but not external. All the stored operations are weak, so it will be dealloced after image loading finished. If you need to store operations, use your own class to keep a strong reference for them. */ @interface UIView (WebCacheOperation) /** * Get the image load operation for key * * @param key key for identifying the operations * @return the image load operation */ - (nullable id)sd_imageLoadOperationForKey:(nullable NSString *)key; /** * Set the image load operation (storage in a UIView based weak map table) * * @param operation the operation * @param key key for storing the operation */ - (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key; /** * Cancel all operations for the current UIView and key * * @param key key for identifying the operations */ - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key; /** * Just remove the operations corresponding to the current UIView and key without cancelling them * * @param key key for identifying the operations */ - (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Core/UIView+WebCacheOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIView+WebCacheOperation.h" #import "objc/runtime.h" static char loadOperationKey; // key is strong, value is weak because operation instance is retained by SDWebImageManager's runningOperations property // we should use lock to keep thread-safe because these method may not be accessed from main queue typedef NSMapTable> SDOperationsDictionary; @implementation UIView (WebCacheOperation) - (SDOperationsDictionary *)sd_operationDictionary { @synchronized(self) { SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); if (operations) { return operations; } operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; } } - (nullable id)sd_imageLoadOperationForKey:(nullable NSString *)key { id operation; if (key) { SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; @synchronized (self) { operation = [operationDictionary objectForKey:key]; } } return operation; } - (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key { if (key) { [self sd_cancelImageLoadOperationWithKey:key]; if (operation) { SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; @synchronized (self) { [operationDictionary setObject:operation forKey:key]; } } } } - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key { if (key) { // Cancel in progress downloader from queue SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; id operation; @synchronized (self) { operation = [operationDictionary objectForKey:key]; } if (operation) { if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { [operation cancel]; } @synchronized (self) { [operationDictionary removeObjectForKey:key]; } } } } - (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key { if (key) { SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; @synchronized (self) { [operationDictionary removeObjectForKey:key]; } } } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/NSBezierPath+SDRoundedCorners.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_MAC #import "UIImage+Transform.h" @interface NSBezierPath (SDRoundedCorners) /** Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`. */ + (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/NSBezierPath+SDRoundedCorners.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "NSBezierPath+SDRoundedCorners.h" #if SD_MAC @implementation NSBezierPath (SDRoundedCorners) + (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { NSBezierPath *path = [NSBezierPath bezierPath]; CGFloat maxCorner = MIN(NSWidth(rect), NSHeight(rect)) / 2; CGFloat topLeftRadius = MIN(maxCorner, (corners & SDRectCornerTopLeft) ? cornerRadius : 0); CGFloat topRightRadius = MIN(maxCorner, (corners & SDRectCornerTopRight) ? cornerRadius : 0); CGFloat bottomLeftRadius = MIN(maxCorner, (corners & SDRectCornerBottomLeft) ? cornerRadius : 0); CGFloat bottomRightRadius = MIN(maxCorner, (corners & SDRectCornerBottomRight) ? cornerRadius : 0); NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect)); NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); [path moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))]; [path appendBezierPathWithArcFromPoint:topLeft toPoint:bottomLeft radius:topLeftRadius]; [path appendBezierPathWithArcFromPoint:bottomLeft toPoint:bottomRight radius:bottomLeftRadius]; [path appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:bottomRightRadius]; [path appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:topRightRadius]; [path closePath]; return path; } @end #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDAssociatedObject.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" /// Copy the associated object from source image to target image. The associated object including all the category read/write properties. /// @param source source /// @param target target FOUNDATION_EXPORT void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target); ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDAssociatedObject.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAssociatedObject.h" #import "UIImage+Metadata.h" #import "UIImage+ExtendedCacheData.h" #import "UIImage+MemoryCacheCost.h" #import "UIImage+ForceDecode.h" void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target) { if (!source || !target) { return; } // Image Metadata target.sd_isIncremental = source.sd_isIncremental; target.sd_imageLoopCount = source.sd_imageLoopCount; target.sd_imageFormat = source.sd_imageFormat; // Force Decode target.sd_isDecoded = source.sd_isDecoded; // Extended Cache Data target.sd_extendedObject = source.sd_extendedObject; } ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDAsyncBlockOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" @class SDAsyncBlockOperation; typedef void (^SDAsyncBlock)(SDAsyncBlockOperation * __nonnull asyncOperation); /// A async block operation, success after you call `completer` (not like `NSBlockOperation` which is for sync block, success on return) @interface SDAsyncBlockOperation : NSOperation - (nonnull instancetype)initWithBlock:(nonnull SDAsyncBlock)block; + (nonnull instancetype)blockOperationWithBlock:(nonnull SDAsyncBlock)block; - (void)complete; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDAsyncBlockOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDAsyncBlockOperation.h" @interface SDAsyncBlockOperation () @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (nonatomic, copy, nonnull) SDAsyncBlock executionBlock; @end @implementation SDAsyncBlockOperation @synthesize executing = _executing; @synthesize finished = _finished; - (nonnull instancetype)initWithBlock:(nonnull SDAsyncBlock)block { self = [super init]; if (self) { self.executionBlock = block; } return self; } + (nonnull instancetype)blockOperationWithBlock:(nonnull SDAsyncBlock)block { SDAsyncBlockOperation *operation = [[SDAsyncBlockOperation alloc] initWithBlock:block]; return operation; } - (void)start { @synchronized (self) { if (self.isCancelled) { self.finished = YES; return; } self.finished = NO; self.executing = YES; if (self.executionBlock) { self.executionBlock(self); } else { self.executing = NO; self.finished = YES; } } } - (void)cancel { @synchronized (self) { [super cancel]; if (self.isExecuting) { self.executing = NO; self.finished = YES; } } } - (void)complete { @synchronized (self) { if (self.isExecuting) { self.finished = YES; self.executing = NO; } } } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (BOOL)isConcurrent { return YES; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDDeviceHelper.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// Device information helper methods @interface SDDeviceHelper : NSObject + (NSUInteger)totalMemory; + (NSUInteger)freeMemory; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDDeviceHelper.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDDeviceHelper.h" #import @implementation SDDeviceHelper + (NSUInteger)totalMemory { return (NSUInteger)[[NSProcessInfo processInfo] physicalMemory]; } + (NSUInteger)freeMemory { mach_port_t host_port = mach_host_self(); mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return 0; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); if (kern != KERN_SUCCESS) return 0; return vm_stat.free_count * page_size; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDDisplayLink.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// Cross-platform display link wrapper. Do not retain the target /// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS @interface SDDisplayLink : NSObject @property (readonly, nonatomic, weak, nullable) id target; @property (readonly, nonatomic, assign, nonnull) SEL selector; @property (readonly, nonatomic) CFTimeInterval duration; @property (readonly, nonatomic) BOOL isRunning; + (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel; - (void)addToRunLoop:(nonnull NSRunLoop *)runloop forMode:(nonnull NSRunLoopMode)mode; - (void)removeFromRunLoop:(nonnull NSRunLoop *)runloop forMode:(nonnull NSRunLoopMode)mode; - (void)start; - (void)stop; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDDisplayLink.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDDisplayLink.h" #import "SDWeakProxy.h" #if SD_MAC #import #elif SD_IOS || SD_TV #import #endif #if SD_MAC static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext); #endif #define kSDDisplayLinkInterval 1.0 / 60 @interface SDDisplayLink () #if SD_MAC @property (nonatomic, assign) CVDisplayLinkRef displayLink; @property (nonatomic, assign) CVTimeStamp outputTime; @property (nonatomic, copy) NSRunLoopMode runloopMode; #elif SD_IOS || SD_TV @property (nonatomic, strong) CADisplayLink *displayLink; #else @property (nonatomic, strong) NSTimer *displayLink; @property (nonatomic, strong) NSRunLoop *runloop; @property (nonatomic, copy) NSRunLoopMode runloopMode; @property (nonatomic, assign) NSTimeInterval currentFireDate; #endif @end @implementation SDDisplayLink - (void)dealloc { #if SD_MAC if (_displayLink) { CVDisplayLinkRelease(_displayLink); _displayLink = NULL; } #elif SD_IOS || SD_TV [_displayLink invalidate]; _displayLink = nil; #else [_displayLink invalidate]; _displayLink = nil; #endif } - (instancetype)initWithTarget:(id)target selector:(SEL)sel { self = [super init]; if (self) { _target = target; _selector = sel; #if SD_MAC CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void *)self); #elif SD_IOS || SD_TV SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; _displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayLinkDidRefresh:)]; #else SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; _displayLink = [NSTimer timerWithTimeInterval:kSDDisplayLinkInterval target:weakProxy selector:@selector(displayLinkDidRefresh:) userInfo:nil repeats:YES]; #endif } return self; } + (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel { SDDisplayLink *displayLink = [[SDDisplayLink alloc] initWithTarget:target selector:sel]; return displayLink; } - (CFTimeInterval)duration { #if SD_MAC CVTimeStamp outputTime = self.outputTime; NSTimeInterval duration = 0; double periodPerSecond = (double)outputTime.videoTimeScale * outputTime.rateScalar; if (periodPerSecond > 0) { duration = (double)outputTime.videoRefreshPeriod / periodPerSecond; } #elif SD_IOS || SD_TV #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSTimeInterval duration = self.displayLink.duration * self.displayLink.frameInterval; #pragma clang diagnostic pop #else NSTimeInterval duration = 0; if (self.displayLink.isValid && self.currentFireDate != 0) { NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink); duration = nextFireDate - self.currentFireDate; } #endif if (duration == 0) { duration = kSDDisplayLinkInterval; } return duration; } - (BOOL)isRunning { #if SD_MAC return CVDisplayLinkIsRunning(self.displayLink); #elif SD_IOS || SD_TV return !self.displayLink.isPaused; #else return self.displayLink.isValid; #endif } - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode { if (!runloop || !mode) { return; } #if SD_MAC self.runloopMode = mode; #elif SD_IOS || SD_TV [self.displayLink addToRunLoop:runloop forMode:mode]; #else self.runloop = runloop; self.runloopMode = mode; CFRunLoopMode cfMode; if ([mode isEqualToString:NSDefaultRunLoopMode]) { cfMode = kCFRunLoopDefaultMode; } else if ([mode isEqualToString:NSRunLoopCommonModes]) { cfMode = kCFRunLoopCommonModes; } else { cfMode = (__bridge CFStringRef)mode; } CFRunLoopAddTimer(runloop.getCFRunLoop, (__bridge CFRunLoopTimerRef)self.displayLink, cfMode); #endif } - (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode { if (!runloop || !mode) { return; } #if SD_MAC self.runloopMode = nil; #elif SD_IOS || SD_TV [self.displayLink removeFromRunLoop:runloop forMode:mode]; #else self.runloop = nil; self.runloopMode = nil; CFRunLoopMode cfMode; if ([mode isEqualToString:NSDefaultRunLoopMode]) { cfMode = kCFRunLoopDefaultMode; } else if ([mode isEqualToString:NSRunLoopCommonModes]) { cfMode = kCFRunLoopCommonModes; } else { cfMode = (__bridge CFStringRef)mode; } CFRunLoopRemoveTimer(runloop.getCFRunLoop, (__bridge CFRunLoopTimerRef)self.displayLink, cfMode); #endif } - (void)start { #if SD_MAC CVDisplayLinkStart(self.displayLink); #elif SD_IOS || SD_TV self.displayLink.paused = NO; #else if (self.displayLink.isValid) { [self.displayLink fire]; } else { SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; self.displayLink = [NSTimer timerWithTimeInterval:kSDDisplayLinkInterval target:weakProxy selector:@selector(displayLinkDidRefresh:) userInfo:nil repeats:YES]; [self addToRunLoop:self.runloop forMode:self.runloopMode]; } #endif } - (void)stop { #if SD_MAC CVDisplayLinkStop(self.displayLink); #elif SD_IOS || SD_TV self.displayLink.paused = YES; #else [self.displayLink invalidate]; #endif } - (void)displayLinkDidRefresh:(id)displayLink { #if SD_MAC // CVDisplayLink does not use runloop, but we can provide similar behavior for modes // May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel) NSString *runloopMode = self.runloopMode; if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) { return; } #endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [_target performSelector:_selector withObject:self]; #pragma clang diagnostic pop #if SD_WATCH self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink); #endif } @end #if SD_MAC static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { // CVDisplayLink callback is not on main queue SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext; if (inOutputTime) { object.outputTime = *inOutputTime; } __weak SDDisplayLink *weakObject = object; dispatch_async(dispatch_get_main_queue(), ^{ [weakObject displayLinkDidRefresh:(__bridge id)(displayLink)]; }); return kCVReturnSuccess; } #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDFileAttributeHelper.h ================================================ // // This file is from https://gist.github.com/zydeco/6292773 // // Created by Jesús A. Álvarez on 2008-12-17. // Copyright 2008-2009 namedfork.net. All rights reserved. // #import /// File Extended Attribute (xattr) helper methods @interface SDFileAttributeHelper : NSObject + (nullable NSArray *)extendedAttributeNamesAtPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err; + (BOOL)hasExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err; + (nullable NSData *)extendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err; + (BOOL)setExtendedAttribute:(nonnull NSString *)name value:(nonnull NSData *)value atPath:(nonnull NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError * _Nullable * _Nullable)err; + (BOOL)removeExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDFileAttributeHelper.m ================================================ // // This file is from https://gist.github.com/zydeco/6292773 // // Created by Jesús A. Álvarez on 2008-12-17. // Copyright 2008-2009 namedfork.net. All rights reserved. // #import "SDFileAttributeHelper.h" #import @implementation SDFileAttributeHelper + (NSArray*)extendedAttributeNamesAtPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err { int flags = follow? 0 : XATTR_NOFOLLOW; // get size of name list ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags); if (nameBuffLen == -1) { if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo: @{ @"error": [NSString stringWithUTF8String:strerror(errno)], @"function": @"listxattr", @":path": path, @":traverseLink": @(follow) } ]; return nil; } else if (nameBuffLen == 0) return @[]; // get name list NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen]; listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags); // convert to array NSMutableArray * names = [NSMutableArray arrayWithCapacity:5]; char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen; for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName)) [names addObject:[NSString stringWithUTF8String:nextName]]; return names.copy; } + (BOOL)hasExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err { int flags = follow? 0 : XATTR_NOFOLLOW; // get size of name list ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags); if (nameBuffLen == -1) { if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo: @{ @"error": [NSString stringWithUTF8String:strerror(errno)], @"function": @"listxattr", @":path": path, @":traverseLink": @(follow) } ]; return NO; } else if (nameBuffLen == 0) return NO; // get name list NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen]; listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags); // find our name char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen; for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName)) if (strcmp(nextName, [name UTF8String]) == 0) return YES; return NO; } + (NSData *)extendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err { int flags = follow? 0 : XATTR_NOFOLLOW; // get length ssize_t attrLen = getxattr([path fileSystemRepresentation], [name UTF8String], NULL, 0, 0, flags); if (attrLen == -1) { if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo: @{ @"error": [NSString stringWithUTF8String:strerror(errno)], @"function": @"getxattr", @":name": name, @":path": path, @":traverseLink": @(follow) } ]; return nil; } // get attribute data NSMutableData *attrData = [NSMutableData dataWithLength:attrLen]; getxattr([path fileSystemRepresentation], [name UTF8String], [attrData mutableBytes], attrLen, 0, flags); return attrData; } + (BOOL)setExtendedAttribute:(NSString *)name value:(NSData *)value atPath:(NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError **)err { int flags = (follow? 0 : XATTR_NOFOLLOW) | (overwrite? 0 : XATTR_CREATE); if (0 == setxattr([path fileSystemRepresentation], [name UTF8String], [value bytes], [value length], 0, flags)) return YES; // error if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo: @{ @"error": [NSString stringWithUTF8String:strerror(errno)], @"function": @"setxattr", @":name": name, @":value.length": @(value.length), @":path": path, @":traverseLink": @(follow), @":overwrite": @(overwrite) } ]; return NO; } + (BOOL)removeExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err { int flags = (follow? 0 : XATTR_NOFOLLOW); if (0 == removexattr([path fileSystemRepresentation], [name UTF8String], flags)) return YES; // error if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo: @{ @"error": [NSString stringWithUTF8String:strerror(errno)], @"function": @"removexattr", @":name": name, @":path": path, @":traverseLink": @(follow) } ]; return NO; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDImageAssetManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// A Image-Asset manager to work like UIKit/AppKit's image cache behavior /// Apple parse the Asset Catalog compiled file(`Assets.car`) by CoreUI.framework, however it's a private framework and there are no other ways to directly get the data. So we just process the normal bundle files :) @interface SDImageAssetManager : NSObject @property (nonatomic, strong, nonnull) NSMapTable *imageTable; + (nonnull instancetype)sharedAssetManager; - (nullable NSString *)getPathForName:(nonnull NSString *)name bundle:(nonnull NSBundle *)bundle preferredScale:(nonnull CGFloat *)scale; - (nullable UIImage *)imageForName:(nonnull NSString *)name; - (void)storeImage:(nonnull UIImage *)image forName:(nonnull NSString *)name; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDImageAssetManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageAssetManager.h" #import "SDInternalMacros.h" static NSArray *SDBundlePreferredScales() { static NSArray *scales; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ #if SD_WATCH CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; #elif SD_UIKIT CGFloat screenScale = [UIScreen mainScreen].scale; #elif SD_MAC CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; #endif if (screenScale <= 1) { scales = @[@1,@2,@3]; } else if (screenScale <= 2) { scales = @[@2,@3,@1]; } else { scales = @[@3,@2,@1]; } }); return scales; } @implementation SDImageAssetManager { SD_LOCK_DECLARE(_lock); } + (instancetype)sharedAssetManager { static dispatch_once_t onceToken; static SDImageAssetManager *assetManager; dispatch_once(&onceToken, ^{ assetManager = [[SDImageAssetManager alloc] init]; }); return assetManager; } - (instancetype)init { self = [super init]; if (self) { NSPointerFunctionsOptions valueOptions; #if SD_MAC // Apple says that NSImage use a weak reference to value valueOptions = NSPointerFunctionsWeakMemory; #else // Apple says that UIImage use a strong reference to value valueOptions = NSPointerFunctionsStrongMemory; #endif _imageTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn valueOptions:valueOptions]; SD_LOCK_INIT(_lock); #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; } - (void)dealloc { #if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } - (void)didReceiveMemoryWarning:(NSNotification *)notification { SD_LOCK(_lock); [self.imageTable removeAllObjects]; SD_UNLOCK(_lock); } - (NSString *)getPathForName:(NSString *)name bundle:(NSBundle *)bundle preferredScale:(CGFloat *)scale { NSParameterAssert(name); NSParameterAssert(bundle); NSString *path; if (name.length == 0) { return path; } if ([name hasSuffix:@"/"]) { return path; } NSString *extension = name.pathExtension; if (extension.length == 0) { // If no extension, follow Apple's doc, check PNG format extension = @"png"; } name = [name stringByDeletingPathExtension]; CGFloat providedScale = *scale; NSArray *scales = SDBundlePreferredScales(); // Check if file name contains scale for (size_t i = 0; i < scales.count; i++) { NSNumber *scaleValue = scales[i]; if ([name hasSuffix:[NSString stringWithFormat:@"@%@x", scaleValue]]) { path = [bundle pathForResource:name ofType:extension]; if (path) { *scale = scaleValue.doubleValue; // override return path; } } } // Search with provided scale first if (providedScale != 0) { NSString *scaledName = [name stringByAppendingFormat:@"@%@x", @(providedScale)]; path = [bundle pathForResource:scaledName ofType:extension]; if (path) { return path; } } // Search with preferred scale for (size_t i = 0; i < scales.count; i++) { NSNumber *scaleValue = scales[i]; if (scaleValue.doubleValue == providedScale) { // Ignore provided scale continue; } NSString *scaledName = [name stringByAppendingFormat:@"@%@x", scaleValue]; path = [bundle pathForResource:scaledName ofType:extension]; if (path) { *scale = scaleValue.doubleValue; // override return path; } } // Search without scale path = [bundle pathForResource:name ofType:extension]; return path; } - (UIImage *)imageForName:(NSString *)name { NSParameterAssert(name); UIImage *image; SD_LOCK(_lock); image = [self.imageTable objectForKey:name]; SD_UNLOCK(_lock); return image; } - (void)storeImage:(UIImage *)image forName:(NSString *)name { NSParameterAssert(image); NSParameterAssert(name); SD_LOCK(_lock); [self.imageTable setObject:image forKey:name]; SD_UNLOCK(_lock); } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDImageCachesManagerOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// This is used for operation management, but not for operation queue execute @interface SDImageCachesManagerOperation : NSOperation @property (nonatomic, assign, readonly) NSUInteger pendingCount; - (void)beginWithTotalCount:(NSUInteger)totalCount; - (void)completeOne; - (void)done; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDImageCachesManagerOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCachesManagerOperation.h" #import "SDInternalMacros.h" @implementation SDImageCachesManagerOperation { SD_LOCK_DECLARE(_pendingCountLock); } @synthesize executing = _executing; @synthesize finished = _finished; @synthesize cancelled = _cancelled; @synthesize pendingCount = _pendingCount; - (instancetype)init { if (self = [super init]) { SD_LOCK_INIT(_pendingCountLock); _pendingCount = 0; } return self; } - (void)beginWithTotalCount:(NSUInteger)totalCount { self.executing = YES; self.finished = NO; _pendingCount = totalCount; } - (NSUInteger)pendingCount { SD_LOCK(_pendingCountLock); NSUInteger pendingCount = _pendingCount; SD_UNLOCK(_pendingCountLock); return pendingCount; } - (void)completeOne { SD_LOCK(_pendingCountLock); _pendingCount = _pendingCount > 0 ? _pendingCount - 1 : 0; SD_UNLOCK(_pendingCountLock); } - (void)cancel { self.cancelled = YES; [self reset]; } - (void)done { self.finished = YES; self.executing = NO; [self reset]; } - (void)reset { SD_LOCK(_pendingCountLock); _pendingCount = 0; SD_UNLOCK(_pendingCountLock); } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (void)setCancelled:(BOOL)cancelled { [self willChangeValueForKey:@"isCancelled"]; _cancelled = cancelled; [self didChangeValueForKey:@"isCancelled"]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDImageIOAnimatedCoder.h" // AVFileTypeHEIC/AVFileTypeHEIF is defined in AVFoundation via iOS 11, we use this without import AVFoundation #define kSDUTTypeHEIC ((__bridge CFStringRef)@"public.heic") #define kSDUTTypeHEIF ((__bridge CFStringRef)@"public.heif") // HEIC Sequence (Animated Image) #define kSDUTTypeHEICS ((__bridge CFStringRef)@"public.heics") // kUTTypeWebP seems not defined in public UTI framework, Apple use the hardcode string, we define them :) #define kSDUTTypeWebP ((__bridge CFStringRef)@"org.webmproject.webp") @interface SDImageIOAnimatedCoder () + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source; + (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source; + (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(nullable NSDictionary *)options; + (BOOL)canEncodeToFormat:(SDImageFormat)format; + (BOOL)canDecodeFromFormat:(SDImageFormat)format; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDInternalMacros.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import #import #import "SDmetamacros.h" #define SD_USE_OS_UNFAIR_LOCK TARGET_OS_MACCATALYST ||\ (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0) ||\ (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12) ||\ (__TV_OS_VERSION_MIN_REQUIRED >= __TVOS_10_0) ||\ (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_3_0) #ifndef SD_LOCK_DECLARE #if SD_USE_OS_UNFAIR_LOCK #define SD_LOCK_DECLARE(lock) os_unfair_lock lock #else #define SD_LOCK_DECLARE(lock) os_unfair_lock lock API_AVAILABLE(ios(10.0), tvos(10), watchos(3), macos(10.12)); \ OSSpinLock lock##_deprecated; #endif #endif #ifndef SD_LOCK_DECLARE_STATIC #if SD_USE_OS_UNFAIR_LOCK #define SD_LOCK_DECLARE_STATIC(lock) static os_unfair_lock lock #else #define SD_LOCK_DECLARE_STATIC(lock) static os_unfair_lock lock API_AVAILABLE(ios(10.0), tvos(10), watchos(3), macos(10.12)); \ static OSSpinLock lock##_deprecated; #endif #endif #ifndef SD_LOCK_INIT #if SD_USE_OS_UNFAIR_LOCK #define SD_LOCK_INIT(lock) lock = OS_UNFAIR_LOCK_INIT #else #define SD_LOCK_INIT(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) lock = OS_UNFAIR_LOCK_INIT; \ else lock##_deprecated = OS_SPINLOCK_INIT; #endif #endif #ifndef SD_LOCK #if SD_USE_OS_UNFAIR_LOCK #define SD_LOCK(lock) os_unfair_lock_lock(&lock) #else #define SD_LOCK(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) os_unfair_lock_lock(&lock); \ else OSSpinLockLock(&lock##_deprecated); #endif #endif #ifndef SD_UNLOCK #if SD_USE_OS_UNFAIR_LOCK #define SD_UNLOCK(lock) os_unfair_lock_unlock(&lock) #else #define SD_UNLOCK(lock) if (@available(iOS 10, tvOS 10, watchOS 3, macOS 10.12, *)) os_unfair_lock_unlock(&lock); \ else OSSpinLockUnlock(&lock##_deprecated); #endif #endif #ifndef SD_OPTIONS_CONTAINS #define SD_OPTIONS_CONTAINS(options, value) (((options) & (value)) == (value)) #endif #ifndef SD_CSTRING #define SD_CSTRING(str) #str #endif #ifndef SD_NSSTRING #define SD_NSSTRING(str) @(SD_CSTRING(str)) #endif #ifndef SD_SEL_SPI #define SD_SEL_SPI(name) NSSelectorFromString([NSString stringWithFormat:@"_%@", SD_NSSTRING(name)]) #endif #ifndef weakify #define weakify(...) \ sd_keywordify \ metamacro_foreach_cxt(sd_weakify_,, __weak, __VA_ARGS__) #endif #ifndef strongify #define strongify(...) \ sd_keywordify \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ metamacro_foreach(sd_strongify_,, __VA_ARGS__) \ _Pragma("clang diagnostic pop") #endif #define sd_weakify_(INDEX, CONTEXT, VAR) \ CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR); #define sd_strongify_(INDEX, VAR) \ __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_); #if DEBUG #define sd_keywordify autoreleasepool {} #else #define sd_keywordify try {} @catch (...) {} #endif #ifndef onExit #define onExit \ sd_keywordify \ __strong sd_cleanupBlock_t metamacro_concat(sd_exitBlock_, __LINE__) __attribute__((cleanup(sd_executeCleanupBlock), unused)) = ^ #endif typedef void (^sd_cleanupBlock_t)(void); #if defined(__cplusplus) extern "C" { #endif void sd_executeCleanupBlock (__strong sd_cleanupBlock_t *block); #if defined(__cplusplus) } #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDInternalMacros.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDInternalMacros.h" void sd_executeCleanupBlock (__strong sd_cleanupBlock_t *block) { (*block)(); } ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDWeakProxy.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" /// A weak proxy which forward all the message to the target @interface SDWeakProxy : NSProxy @property (nonatomic, weak, readonly, nullable) id target; - (nonnull instancetype)initWithTarget:(nonnull id)target; + (nonnull instancetype)proxyWithTarget:(nonnull id)target; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDWeakProxy.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWeakProxy.h" @implementation SDWeakProxy - (instancetype)initWithTarget:(id)target { _target = target; return self; } + (instancetype)proxyWithTarget:(id)target { return [[SDWeakProxy alloc] initWithTarget:target]; } - (id)forwardingTargetForSelector:(SEL)selector { return _target; } - (void)forwardInvocation:(NSInvocation *)invocation { void *null = NULL; [invocation setReturnValue:&null]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { return [NSObject instanceMethodSignatureForSelector:@selector(init)]; } - (BOOL)respondsToSelector:(SEL)aSelector { return [_target respondsToSelector:aSelector]; } - (BOOL)isEqual:(id)object { return [_target isEqual:object]; } - (NSUInteger)hash { return [_target hash]; } - (Class)superclass { return [_target superclass]; } - (Class)class { return [_target class]; } - (BOOL)isKindOfClass:(Class)aClass { return [_target isKindOfClass:aClass]; } - (BOOL)isMemberOfClass:(Class)aClass { return [_target isMemberOfClass:aClass]; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { return [_target conformsToProtocol:aProtocol]; } - (BOOL)isProxy { return YES; } - (NSString *)description { return [_target description]; } - (NSString *)debugDescription { return [_target debugDescription]; } @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDWebImageTransitionInternal.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #if SD_MAC #import /// Helper method for Core Animation transition FOUNDATION_EXPORT CAMediaTimingFunction * _Nullable SDTimingFunctionFromAnimationOptions(SDWebImageAnimationOptions options); FOUNDATION_EXPORT CATransition * _Nullable SDTransitionFromAnimationOptions(SDWebImageAnimationOptions options); #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/SDmetamacros.h ================================================ /** * Macros for metaprogramming * ExtendedC * * Copyright (C) 2012 Justin Spahr-Summers * Released under the MIT license */ #ifndef EXTC_METAMACROS_H #define EXTC_METAMACROS_H /** * Executes one or more expressions (which may have a void type, such as a call * to a function that returns no value) and always returns true. */ #define metamacro_exprify(...) \ ((__VA_ARGS__), true) /** * Returns a string representation of VALUE after full macro expansion. */ #define metamacro_stringify(VALUE) \ metamacro_stringify_(VALUE) /** * Returns A and B concatenated after full macro expansion. */ #define metamacro_concat(A, B) \ metamacro_concat_(A, B) /** * Returns the Nth variadic argument (starting from zero). At least * N + 1 variadic arguments must be given. N must be between zero and twenty, * inclusive. */ #define metamacro_at(N, ...) \ metamacro_concat(metamacro_at, N)(__VA_ARGS__) /** * Returns the number of arguments (up to twenty) provided to the macro. At * least one argument must be provided. * * Inspired by P99: http://p99.gforge.inria.fr */ #define metamacro_argcount(...) \ metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) /** * Identical to #metamacro_foreach_cxt, except that no CONTEXT argument is * given. Only the index and current argument will thus be passed to MACRO. */ #define metamacro_foreach(MACRO, SEP, ...) \ metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__) /** * For each consecutive variadic argument (up to twenty), MACRO is passed the * zero-based index of the current argument, CONTEXT, and then the argument * itself. The results of adjoining invocations of MACRO are then separated by * SEP. * * Inspired by P99: http://p99.gforge.inria.fr */ #define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \ metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) /** * Identical to #metamacro_foreach_cxt. This can be used when the former would * fail due to recursive macro expansion. */ #define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \ metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) /** * In consecutive order, appends each variadic argument (up to twenty) onto * BASE. The resulting concatenations are then separated by SEP. * * This is primarily useful to manipulate a list of macro invocations into instead * invoking a different, possibly related macro. */ #define metamacro_foreach_concat(BASE, SEP, ...) \ metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__) /** * Iterates COUNT times, each time invoking MACRO with the current index * (starting at zero) and CONTEXT. The results of adjoining invocations of MACRO * are then separated by SEP. * * COUNT must be an integer between zero and twenty, inclusive. */ #define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \ metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT) /** * Returns the first argument given. At least one argument must be provided. * * This is useful when implementing a variadic macro, where you may have only * one variadic argument, but no way to retrieve it (for example, because \c ... * always needs to match at least one argument). * * @code #define varmacro(...) \ metamacro_head(__VA_ARGS__) * @endcode */ #define metamacro_head(...) \ metamacro_head_(__VA_ARGS__, 0) /** * Returns every argument except the first. At least two arguments must be * provided. */ #define metamacro_tail(...) \ metamacro_tail_(__VA_ARGS__) /** * Returns the first N (up to twenty) variadic arguments as a new argument list. * At least N variadic arguments must be provided. */ #define metamacro_take(N, ...) \ metamacro_concat(metamacro_take, N)(__VA_ARGS__) /** * Removes the first N (up to twenty) variadic arguments from the given argument * list. At least N variadic arguments must be provided. */ #define metamacro_drop(N, ...) \ metamacro_concat(metamacro_drop, N)(__VA_ARGS__) /** * Decrements VAL, which must be a number between zero and twenty, inclusive. * * This is primarily useful when dealing with indexes and counts in * metaprogramming. */ #define metamacro_dec(VAL) \ metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) /** * Increments VAL, which must be a number between zero and twenty, inclusive. * * This is primarily useful when dealing with indexes and counts in * metaprogramming. */ #define metamacro_inc(VAL) \ metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) /** * If A is equal to B, the next argument list is expanded; otherwise, the * argument list after that is expanded. A and B must be numbers between zero * and twenty, inclusive. Additionally, B must be greater than or equal to A. * * @code // expands to true metamacro_if_eq(0, 0)(true)(false) // expands to false metamacro_if_eq(0, 1)(true)(false) * @endcode * * This is primarily useful when dealing with indexes and counts in * metaprogramming. */ #define metamacro_if_eq(A, B) \ metamacro_concat(metamacro_if_eq, A)(B) /** * Identical to #metamacro_if_eq. This can be used when the former would fail * due to recursive macro expansion. */ #define metamacro_if_eq_recursive(A, B) \ metamacro_concat(metamacro_if_eq_recursive, A)(B) /** * Returns 1 if N is an even number, or 0 otherwise. N must be between zero and * twenty, inclusive. * * For the purposes of this test, zero is considered even. */ #define metamacro_is_even(N) \ metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) /** * Returns the logical NOT of B, which must be the number zero or one. */ #define metamacro_not(B) \ metamacro_at(B, 1, 0) // IMPLEMENTATION DETAILS FOLLOW! // Do not write code that depends on anything below this line. #define metamacro_stringify_(VALUE) # VALUE #define metamacro_concat_(A, B) A ## B #define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG) #define metamacro_head_(FIRST, ...) FIRST #define metamacro_tail_(FIRST, ...) __VA_ARGS__ #define metamacro_consume_(...) #define metamacro_expand_(...) __VA_ARGS__ // implemented from scratch so that metamacro_concat() doesn't end up nesting #define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG) #define metamacro_foreach_concat_iter_(BASE, ARG) BASE ## ARG // metamacro_at expansions #define metamacro_at0(...) metamacro_head(__VA_ARGS__) #define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__) #define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__) #define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__) #define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__) #define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__) #define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__) #define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__) #define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__) #define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__) #define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__) #define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__) #define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__) #define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__) #define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__) #define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__) #define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__) #define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__) #define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__) #define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__) #define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__) // metamacro_foreach_cxt expansions #define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT) #define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) #define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \ SEP \ MACRO(1, CONTEXT, _1) #define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ SEP \ MACRO(2, CONTEXT, _2) #define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ SEP \ MACRO(3, CONTEXT, _3) #define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ SEP \ MACRO(4, CONTEXT, _4) #define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ SEP \ MACRO(5, CONTEXT, _5) #define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ SEP \ MACRO(6, CONTEXT, _6) #define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ SEP \ MACRO(7, CONTEXT, _7) #define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ SEP \ MACRO(8, CONTEXT, _8) #define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ SEP \ MACRO(9, CONTEXT, _9) #define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ SEP \ MACRO(10, CONTEXT, _10) #define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ SEP \ MACRO(11, CONTEXT, _11) #define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ SEP \ MACRO(12, CONTEXT, _12) #define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ SEP \ MACRO(13, CONTEXT, _13) #define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ SEP \ MACRO(14, CONTEXT, _14) #define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ SEP \ MACRO(15, CONTEXT, _15) #define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ SEP \ MACRO(16, CONTEXT, _16) #define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ SEP \ MACRO(17, CONTEXT, _17) #define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ SEP \ MACRO(18, CONTEXT, _18) #define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ SEP \ MACRO(19, CONTEXT, _19) // metamacro_foreach_cxt_recursive expansions #define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT) #define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) #define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) \ SEP \ MACRO(1, CONTEXT, _1) #define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ SEP \ MACRO(2, CONTEXT, _2) #define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ SEP \ MACRO(3, CONTEXT, _3) #define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ SEP \ MACRO(4, CONTEXT, _4) #define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ SEP \ MACRO(5, CONTEXT, _5) #define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ SEP \ MACRO(6, CONTEXT, _6) #define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ SEP \ MACRO(7, CONTEXT, _7) #define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ SEP \ MACRO(8, CONTEXT, _8) #define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ SEP \ MACRO(9, CONTEXT, _9) #define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ SEP \ MACRO(10, CONTEXT, _10) #define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ SEP \ MACRO(11, CONTEXT, _11) #define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ SEP \ MACRO(12, CONTEXT, _12) #define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ SEP \ MACRO(13, CONTEXT, _13) #define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ SEP \ MACRO(14, CONTEXT, _14) #define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ SEP \ MACRO(15, CONTEXT, _15) #define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ SEP \ MACRO(16, CONTEXT, _16) #define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ SEP \ MACRO(17, CONTEXT, _17) #define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ SEP \ MACRO(18, CONTEXT, _18) #define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ SEP \ MACRO(19, CONTEXT, _19) // metamacro_for_cxt expansions #define metamacro_for_cxt0(MACRO, SEP, CONTEXT) #define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT) #define metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ metamacro_for_cxt1(MACRO, SEP, CONTEXT) \ SEP \ MACRO(1, CONTEXT) #define metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ SEP \ MACRO(2, CONTEXT) #define metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ SEP \ MACRO(3, CONTEXT) #define metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ SEP \ MACRO(4, CONTEXT) #define metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ SEP \ MACRO(5, CONTEXT) #define metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ SEP \ MACRO(6, CONTEXT) #define metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ SEP \ MACRO(7, CONTEXT) #define metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ SEP \ MACRO(8, CONTEXT) #define metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ SEP \ MACRO(9, CONTEXT) #define metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ SEP \ MACRO(10, CONTEXT) #define metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ SEP \ MACRO(11, CONTEXT) #define metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ SEP \ MACRO(12, CONTEXT) #define metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ SEP \ MACRO(13, CONTEXT) #define metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ SEP \ MACRO(14, CONTEXT) #define metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ SEP \ MACRO(15, CONTEXT) #define metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ SEP \ MACRO(16, CONTEXT) #define metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ SEP \ MACRO(17, CONTEXT) #define metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ SEP \ MACRO(18, CONTEXT) #define metamacro_for_cxt20(MACRO, SEP, CONTEXT) \ metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ SEP \ MACRO(19, CONTEXT) // metamacro_if_eq expansions #define metamacro_if_eq0(VALUE) \ metamacro_concat(metamacro_if_eq0_, VALUE) #define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_ #define metamacro_if_eq0_1(...) metamacro_expand_ #define metamacro_if_eq0_2(...) metamacro_expand_ #define metamacro_if_eq0_3(...) metamacro_expand_ #define metamacro_if_eq0_4(...) metamacro_expand_ #define metamacro_if_eq0_5(...) metamacro_expand_ #define metamacro_if_eq0_6(...) metamacro_expand_ #define metamacro_if_eq0_7(...) metamacro_expand_ #define metamacro_if_eq0_8(...) metamacro_expand_ #define metamacro_if_eq0_9(...) metamacro_expand_ #define metamacro_if_eq0_10(...) metamacro_expand_ #define metamacro_if_eq0_11(...) metamacro_expand_ #define metamacro_if_eq0_12(...) metamacro_expand_ #define metamacro_if_eq0_13(...) metamacro_expand_ #define metamacro_if_eq0_14(...) metamacro_expand_ #define metamacro_if_eq0_15(...) metamacro_expand_ #define metamacro_if_eq0_16(...) metamacro_expand_ #define metamacro_if_eq0_17(...) metamacro_expand_ #define metamacro_if_eq0_18(...) metamacro_expand_ #define metamacro_if_eq0_19(...) metamacro_expand_ #define metamacro_if_eq0_20(...) metamacro_expand_ #define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE)) #define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE)) #define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE)) #define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE)) #define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE)) #define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE)) #define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE)) #define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE)) #define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE)) #define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE)) #define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE)) #define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE)) #define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE)) #define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE)) #define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE)) #define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE)) #define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE)) #define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE)) #define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE)) #define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE)) // metamacro_if_eq_recursive expansions #define metamacro_if_eq_recursive0(VALUE) \ metamacro_concat(metamacro_if_eq_recursive0_, VALUE) #define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_ #define metamacro_if_eq_recursive0_1(...) metamacro_expand_ #define metamacro_if_eq_recursive0_2(...) metamacro_expand_ #define metamacro_if_eq_recursive0_3(...) metamacro_expand_ #define metamacro_if_eq_recursive0_4(...) metamacro_expand_ #define metamacro_if_eq_recursive0_5(...) metamacro_expand_ #define metamacro_if_eq_recursive0_6(...) metamacro_expand_ #define metamacro_if_eq_recursive0_7(...) metamacro_expand_ #define metamacro_if_eq_recursive0_8(...) metamacro_expand_ #define metamacro_if_eq_recursive0_9(...) metamacro_expand_ #define metamacro_if_eq_recursive0_10(...) metamacro_expand_ #define metamacro_if_eq_recursive0_11(...) metamacro_expand_ #define metamacro_if_eq_recursive0_12(...) metamacro_expand_ #define metamacro_if_eq_recursive0_13(...) metamacro_expand_ #define metamacro_if_eq_recursive0_14(...) metamacro_expand_ #define metamacro_if_eq_recursive0_15(...) metamacro_expand_ #define metamacro_if_eq_recursive0_16(...) metamacro_expand_ #define metamacro_if_eq_recursive0_17(...) metamacro_expand_ #define metamacro_if_eq_recursive0_18(...) metamacro_expand_ #define metamacro_if_eq_recursive0_19(...) metamacro_expand_ #define metamacro_if_eq_recursive0_20(...) metamacro_expand_ #define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE)) #define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE)) // metamacro_take expansions #define metamacro_take0(...) #define metamacro_take1(...) metamacro_head(__VA_ARGS__) #define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__)) #define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__)) #define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__)) #define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__)) #define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__)) #define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__)) #define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__)) #define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__)) #define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__)) #define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__)) #define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__)) #define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__)) #define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__)) #define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__)) #define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__)) #define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__)) #define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__)) #define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__)) #define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__)) // metamacro_drop expansions #define metamacro_drop0(...) __VA_ARGS__ #define metamacro_drop1(...) metamacro_tail(__VA_ARGS__) #define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__)) #define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__)) #define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__)) #define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__)) #define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__)) #define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__)) #define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__)) #define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__)) #define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__)) #define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__)) #define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__)) #define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__)) #define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__)) #define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__)) #define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__)) #define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__)) #define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__)) #define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__)) #define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__)) #endif ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/UIColor+SDHexString.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" @interface UIColor (SDHexString) /** Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`. */ @property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; @end ================================================ FILE: Pods/SDWebImage/SDWebImage/Private/UIColor+SDHexString.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIColor+SDHexString.h" @implementation UIColor (SDHexString) - (NSString *)sd_hexString { CGFloat red, green, blue, alpha; #if SD_UIKIT if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { [self getWhite:&red alpha:&alpha]; green = red; blue = red; } #else @try { [self getRed:&red green:&green blue:&blue alpha:&alpha]; } @catch (NSException *exception) { [self getWhite:&red alpha:&alpha]; green = red; blue = red; } #endif red = roundf(red * 255.f); green = roundf(green * 255.f); blue = roundf(blue * 255.f); alpha = roundf(alpha * 255.f); uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); return [NSString stringWithFormat:@"#%08x", hex]; } @end ================================================ FILE: Pods/SDWebImage/WebImage/SDWebImage.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Florent Vilmart * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import //! Project version number for SDWebImage. FOUNDATION_EXPORT double SDWebImageVersionNumber; //! Project version string for SDWebImage. FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import // Mac #if __has_include() #import #endif #if __has_include() #import #endif #if __has_include() #import #endif // MapKit #if __has_include() #import #endif ================================================ FILE: Pods/Target Support Files/Appirater/Appirater-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 2.3.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/Appirater/Appirater-dummy.m ================================================ #import @interface PodsDummy_Appirater : NSObject @end @implementation PodsDummy_Appirater @end ================================================ FILE: Pods/Target Support Files/Appirater/Appirater-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: Pods/Target Support Files/Appirater/Appirater-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "Appirater.h" #import "AppiraterDelegate.h" FOUNDATION_EXPORT double AppiraterVersionNumber; FOUNDATION_EXPORT const unsigned char AppiraterVersionString[]; ================================================ FILE: Pods/Target Support Files/Appirater/Appirater.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Appirater GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" -framework "SystemConfiguration" -weak_framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Appirater PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/Appirater/Appirater.modulemap ================================================ framework module Appirater { umbrella header "Appirater-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/Appirater/Appirater.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Appirater GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" -framework "SystemConfiguration" -weak_framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Appirater PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/Appirater/ResourceBundle-Appirater-Appirater-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType BNDL CFBundleShortVersionString 2.3.1 CFBundleSignature ???? CFBundleVersion 1 NSPrincipalClass ================================================ FILE: Pods/Target Support Files/Firebase/Firebase.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public" OTHER_LDFLAGS = $(inherited) -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Firebase PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/Firebase/Firebase.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Firebase FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Firebase" "${PODS_ROOT}/Headers/Public" OTHER_LDFLAGS = $(inherited) -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Firebase PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks-input-files.xcfilelist ================================================ ${PODS_ROOT}/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks.sh ${PODS_ROOT}/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework ================================================ FILE: Pods/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks-output-files.xcfilelist ================================================ ${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport/FirebaseAnalytics.framework ================================================ FILE: Pods/Target Support Files/FirebaseAnalytics/FirebaseAnalytics-xcframeworks.sh ================================================ #!/bin/sh set -e set -u set -o pipefail function on_error { echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" } trap 'on_error $LINENO' ERR # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") copy_dir() { local source="$1" local destination="$2" # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" \"${source}*\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}"/* "${destination}" } SELECT_SLICE_RETVAL="" select_slice() { local paths=("$@") # Locate the correct slice of the .xcframework for the current architectures local target_path="" # Split archs on space so we can find a slice that has all the needed archs local target_archs=$(echo $ARCHS | tr " " "\n") local target_variant="" if [[ "$PLATFORM_NAME" == *"simulator" ]]; then target_variant="simulator" fi if [[ ! -z ${EFFECTIVE_PLATFORM_NAME+x} && "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then target_variant="maccatalyst" fi for i in ${!paths[@]}; do local matched_all_archs="1" for target_arch in $target_archs do if ! [[ "${paths[$i]}" == *"$target_variant"* ]]; then matched_all_archs="0" break fi # Verifies that the path contains the variant string (simulator or maccatalyst) if the variant is set. if [[ -z "$target_variant" && ("${paths[$i]}" == *"simulator"* || "${paths[$i]}" == *"maccatalyst"*) ]]; then matched_all_archs="0" break fi # This regex matches all possible variants of the arch in the folder name: # Let's say the folder name is: ios-armv7_armv7s_arm64_arm64e/CoconutLib.framework # We match the following: -armv7_, _armv7s_, _arm64_ and _arm64e/. # If we have a specific variant: ios-i386_x86_64-simulator/CoconutLib.framework # We match the following: -i386_ and _x86_64- # When the .xcframework wraps a static library, the folder name does not include # any .framework. In that case, the folder name can be: ios-arm64_armv7 # We also match _armv7$ to handle that case. local target_arch_regex="[_\-]${target_arch}([\/_\-]|$)" if ! [[ "${paths[$i]}" =~ $target_arch_regex ]]; then matched_all_archs="0" break fi done if [[ "$matched_all_archs" == "1" ]]; then # Found a matching slice echo "Selected xcframework slice ${paths[$i]}" SELECT_SLICE_RETVAL=${paths[$i]} break fi done } install_xcframework() { local basepath="$1" local name="$2" local package_type="$3" local paths=("${@:4}") # Locate the correct slice of the .xcframework for the current architectures select_slice "${paths[@]}" local target_path="$SELECT_SLICE_RETVAL" if [[ -z "$target_path" ]]; then echo "warning: [CP] Unable to find matching .xcframework slice in '${paths[@]}' for the current build architectures ($ARCHS)." return fi local source="$basepath/$target_path" local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}" if [ ! -d "$destination" ]; then mkdir -p "$destination" fi copy_dir "$source/" "$destination" echo "Copied $source to $destination" } install_xcframework "${PODS_ROOT}/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework" "FirebaseAnalytics/AdIdSupport" "framework" "ios-arm64_armv7" "ios-arm64_i386_x86_64-simulator" ================================================ FILE: Pods/Target Support Files/FirebaseAnalytics/FirebaseAnalytics.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAnalytics FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseAnalytics PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseAnalytics/FirebaseAnalytics.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAnalytics FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseAnalytics PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 8.6.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore-dummy.m ================================================ #import @interface PodsDummy_FirebaseCore : NSObject @end @implementation PodsDummy_FirebaseCore @end ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FIRApp.h" #import "FIRConfiguration.h" #import "FirebaseCore.h" #import "FIRLoggerLevel.h" #import "FIROptions.h" #import "FIRVersion.h" FOUNDATION_EXPORT double FirebaseCoreVersionNumber; FOUNDATION_EXPORT const unsigned char FirebaseCoreVersionString[]; ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 Firebase_VERSION=8.6.1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_CFLAGS = $(inherited) -fno-autolink OTHER_LDFLAGS = $(inherited) -framework "FirebaseCoreDiagnostics" -framework "Foundation" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseCore PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore.modulemap ================================================ framework module FirebaseCore { umbrella header "FirebaseCore-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/FirebaseCore/FirebaseCore.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 Firebase_VERSION=8.6.1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_CFLAGS = $(inherited) -fno-autolink OTHER_LDFLAGS = $(inherited) -framework "FirebaseCoreDiagnostics" -framework "Foundation" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseCore PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 8.6.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-dummy.m ================================================ #import @interface PodsDummy_FirebaseCoreDiagnostics : NSObject @end @implementation PodsDummy_FirebaseCoreDiagnostics @end ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FIRCoreDiagnostics.h" FOUNDATION_EXPORT double FirebaseCoreDiagnosticsVersionNumber; FOUNDATION_EXPORT const unsigned char FirebaseCoreDiagnosticsVersionString[]; ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.debug.xcconfig ================================================ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 GCC_TREAT_WARNINGS_AS_ERRORS = YES HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -framework "CoreTelephony" -framework "Foundation" -framework "GoogleDataTransport" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "nanopb" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseCoreDiagnostics PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.modulemap ================================================ framework module FirebaseCoreDiagnostics { umbrella header "FirebaseCoreDiagnostics-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.release.xcconfig ================================================ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 GCC_TREAT_WARNINGS_AS_ERRORS = YES HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -framework "CoreTelephony" -framework "Foundation" -framework "GoogleDataTransport" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "nanopb" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseCoreDiagnostics PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 8.6.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations-dummy.m ================================================ #import @interface PodsDummy_FirebaseInstallations : NSObject @end @implementation PodsDummy_FirebaseInstallations @end ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FirebaseInstallations.h" #import "FIRInstallations.h" #import "FIRInstallationsAuthTokenResult.h" #import "FIRInstallationsErrors.h" FOUNDATION_EXPORT double FirebaseInstallationsVersionNumber; FOUNDATION_EXPORT const unsigned char FirebaseInstallationsVersionString[]; ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -framework "FBLPromises" -framework "FirebaseCore" -framework "Foundation" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseInstallations PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations.modulemap ================================================ framework module FirebaseInstallations { umbrella header "FirebaseInstallations-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/FirebaseInstallations/FirebaseInstallations.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -framework "FBLPromises" -framework "FirebaseCore" -framework "Foundation" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FirebaseInstallations PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks-input-files.xcfilelist ================================================ ${PODS_ROOT}/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks.sh ${PODS_ROOT}/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework ================================================ FILE: Pods/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks-output-files.xcfilelist ================================================ ${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport/GoogleAppMeasurement.framework ================================================ FILE: Pods/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement-xcframeworks.sh ================================================ #!/bin/sh set -e set -u set -o pipefail function on_error { echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" } trap 'on_error $LINENO' ERR # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") copy_dir() { local source="$1" local destination="$2" # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" \"${source}*\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}"/* "${destination}" } SELECT_SLICE_RETVAL="" select_slice() { local paths=("$@") # Locate the correct slice of the .xcframework for the current architectures local target_path="" # Split archs on space so we can find a slice that has all the needed archs local target_archs=$(echo $ARCHS | tr " " "\n") local target_variant="" if [[ "$PLATFORM_NAME" == *"simulator" ]]; then target_variant="simulator" fi if [[ ! -z ${EFFECTIVE_PLATFORM_NAME+x} && "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then target_variant="maccatalyst" fi for i in ${!paths[@]}; do local matched_all_archs="1" for target_arch in $target_archs do if ! [[ "${paths[$i]}" == *"$target_variant"* ]]; then matched_all_archs="0" break fi # Verifies that the path contains the variant string (simulator or maccatalyst) if the variant is set. if [[ -z "$target_variant" && ("${paths[$i]}" == *"simulator"* || "${paths[$i]}" == *"maccatalyst"*) ]]; then matched_all_archs="0" break fi # This regex matches all possible variants of the arch in the folder name: # Let's say the folder name is: ios-armv7_armv7s_arm64_arm64e/CoconutLib.framework # We match the following: -armv7_, _armv7s_, _arm64_ and _arm64e/. # If we have a specific variant: ios-i386_x86_64-simulator/CoconutLib.framework # We match the following: -i386_ and _x86_64- # When the .xcframework wraps a static library, the folder name does not include # any .framework. In that case, the folder name can be: ios-arm64_armv7 # We also match _armv7$ to handle that case. local target_arch_regex="[_\-]${target_arch}([\/_\-]|$)" if ! [[ "${paths[$i]}" =~ $target_arch_regex ]]; then matched_all_archs="0" break fi done if [[ "$matched_all_archs" == "1" ]]; then # Found a matching slice echo "Selected xcframework slice ${paths[$i]}" SELECT_SLICE_RETVAL=${paths[$i]} break fi done } install_xcframework() { local basepath="$1" local name="$2" local package_type="$3" local paths=("${@:4}") # Locate the correct slice of the .xcframework for the current architectures select_slice "${paths[@]}" local target_path="$SELECT_SLICE_RETVAL" if [[ -z "$target_path" ]]; then echo "warning: [CP] Unable to find matching .xcframework slice in '${paths[@]}' for the current build architectures ($ARCHS)." return fi local source="$basepath/$target_path" local destination="${PODS_XCFRAMEWORKS_BUILD_DIR}/${name}" if [ ! -d "$destination" ]; then mkdir -p "$destination" fi copy_dir "$source/" "$destination" echo "Copied $source to $destination" } install_xcframework "${PODS_ROOT}/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework" "GoogleAppMeasurement/AdIdSupport" "framework" "ios-arm64_armv7" "ios-arm64_i386_x86_64-simulator" ================================================ FILE: Pods/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleAppMeasurement FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleAppMeasurement PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleAppMeasurement/GoogleAppMeasurement.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleAppMeasurement FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"z" -framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleAppMeasurement PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 9.1.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport-dummy.m ================================================ #import @interface PodsDummy_GoogleDataTransport : NSObject @end @implementation PodsDummy_GoogleDataTransport @end ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "GDTCORClock.h" #import "GDTCORConsoleLogger.h" #import "GDTCOREndpoints.h" #import "GDTCOREvent.h" #import "GDTCOREventDataObject.h" #import "GDTCOREventTransformer.h" #import "GDTCORTargets.h" #import "GDTCORTransport.h" #import "GoogleDataTransport.h" FOUNDATION_EXPORT double GoogleDataTransportVersionNumber; FOUNDATION_EXPORT const unsigned char GoogleDataTransportVersionString[]; ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport.debug.xcconfig ================================================ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 GDTCOR_VERSION=9.1.0 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}/" OTHER_LDFLAGS = $(inherited) -l"z" -framework "CoreTelephony" -framework "FBLPromises" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "nanopb" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleDataTransport PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport.modulemap ================================================ framework module GoogleDataTransport { umbrella header "GoogleDataTransport-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/GoogleDataTransport/GoogleDataTransport.release.xcconfig ================================================ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 GDTCOR_VERSION=9.1.0 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}/" OTHER_LDFLAGS = $(inherited) -l"z" -framework "CoreTelephony" -framework "FBLPromises" -framework "GoogleUtilities" -framework "Security" -framework "SystemConfiguration" -framework "nanopb" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleDataTransport PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 7.5.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities-dummy.m ================================================ #import @interface PodsDummy_GoogleUtilities : NSObject @end @implementation PodsDummy_GoogleUtilities @end ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "GULAppDelegateSwizzler.h" #import "GULApplication.h" #import "GULSceneDelegateSwizzler.h" #import "GULAppEnvironmentUtil.h" #import "GULHeartbeatDateStorable.h" #import "GULHeartbeatDateStorage.h" #import "GULHeartbeatDateStorageUserDefaults.h" #import "GULKeychainStorage.h" #import "GULKeychainUtils.h" #import "GULSecureCoding.h" #import "GULURLSessionDataResponse.h" #import "NSURLSession+GULPromises.h" #import "GULLogger.h" #import "GULLoggerLevel.h" #import "GULOriginalIMPConvenienceMacros.h" #import "GULSwizzler.h" #import "GULNSData+zlib.h" #import "GULMutableDictionary.h" #import "GULNetwork.h" #import "GULNetworkConstants.h" #import "GULNetworkLoggerProtocol.h" #import "GULNetworkMessageCode.h" #import "GULNetworkURLSession.h" #import "GULReachabilityChecker.h" #import "GULUserDefaults.h" FOUNDATION_EXPORT double GoogleUtilitiesVersionNumber; FOUNDATION_EXPORT const unsigned char GoogleUtilitiesVersionString[]; ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -l"z" -framework "FBLPromises" -framework "Security" -framework "SystemConfiguration" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleUtilities PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities.modulemap ================================================ framework module GoogleUtilities { umbrella header "GoogleUtilities-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/GoogleUtilities/GoogleUtilities.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" GCC_C_LANGUAGE_STANDARD = c99 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}" OTHER_LDFLAGS = $(inherited) -l"z" -framework "FBLPromises" -framework "Security" -framework "SystemConfiguration" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleUtilities PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## Appirater Copyright 2017. Arash Payan. This library is distributed under the terms of the MIT/X11. ## Firebase Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## FirebaseAnalytics Copyright 2021 Google ## FirebaseCore Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## FirebaseCoreDiagnostics Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## FirebaseInstallations Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## GoogleAppMeasurement Copyright 2021 Google ## GoogleDataTransport Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## GoogleUtilities Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================================================ The following copyright from Landon J. Fuller applies to the isAppEncrypted function in Environment/third_party/GULAppEnvironmentUtil.m. Copyright (c) 2017 Landon J. Fuller All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Comment from iPhone Dev Wiki Crack Prevention: App Store binaries are signed by both their developer and Apple. This encrypts the binary so that decryption keys are needed in order to make the binary readable. When iOS executes the binary, the decryption keys are used to decrypt the binary into a readable state where it is then loaded into memory and executed. iOS can tell the encryption status of a binary via the cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero value then the binary is encrypted. 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into a new binary file, resigning, and repackaging. This will only work on jailbroken devices as codesignature validation has been removed. Resigning takes place because while the codesignature doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have AppSync or similar to disable codesignature checks. More information at Landon Fuller's blog ## PromisesObjC Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ## SDWebImage Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## nanopb Copyright (c) 2011 Petteri Aimonen This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Generated by CocoaPods - https://cocoapods.org ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText Copyright 2017. Arash Payan. This library is distributed under the terms of the MIT/X11. License MIT Title Appirater Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title Firebase Type PSGroupSpecifier FooterText Copyright 2021 Google License Copyright Title FirebaseAnalytics Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title FirebaseCore Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title FirebaseCoreDiagnostics Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title FirebaseInstallations Type PSGroupSpecifier FooterText Copyright 2021 Google License Copyright Title GoogleAppMeasurement Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title GoogleDataTransport Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================================================ The following copyright from Landon J. Fuller applies to the isAppEncrypted function in Environment/third_party/GULAppEnvironmentUtil.m. Copyright (c) 2017 Landon J. Fuller <landon@landonf.org> All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Comment from <a href="http://iphonedevwiki.net/index.php/Crack_prevention">iPhone Dev Wiki Crack Prevention</a>: App Store binaries are signed by both their developer and Apple. This encrypts the binary so that decryption keys are needed in order to make the binary readable. When iOS executes the binary, the decryption keys are used to decrypt the binary into a readable state where it is then loaded into memory and executed. iOS can tell the encryption status of a binary via the cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero value then the binary is encrypted. 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into a new binary file, resigning, and repackaging. This will only work on jailbroken devices as codesignature validation has been removed. Resigning takes place because while the codesignature doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have AppSync or similar to disable codesignature checks. More information at <a href="http://landonf.org/2009/02/index.html">Landon Fuller's blog</a> License Apache Title GoogleUtilities Type PSGroupSpecifier FooterText Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. License Apache Title PromisesObjC Type PSGroupSpecifier FooterText Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SDWebImage Type PSGroupSpecifier FooterText Copyright (c) 2011 Petteri Aimonen <jpa at nanopb.mail.kapsi.fi> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. License zlib Title nanopb Type PSGroupSpecifier FooterText Generated by CocoaPods - https://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-dummy.m ================================================ #import @interface PodsDummy_Pods_Spotify : NSObject @end @implementation PodsDummy_Pods_Spotify @end ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-Debug-input-files.xcfilelist ================================================ ${PODS_ROOT}/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks.sh ${BUILT_PRODUCTS_DIR}/Appirater/Appirater.framework ${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework ${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework ${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework ${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework ${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework ${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework ${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework ${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-Debug-output-files.xcfilelist ================================================ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Appirater.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-Release-input-files.xcfilelist ================================================ ${PODS_ROOT}/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks.sh ${BUILT_PRODUCTS_DIR}/Appirater/Appirater.framework ${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework ${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework ${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework ${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework ${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework ${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework ${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework ${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-Release-output-files.xcfilelist ================================================ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Appirater.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks.sh ================================================ #!/bin/sh set -e set -u set -o pipefail function on_error { echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" } trap 'on_error $LINENO' ERR if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy # frameworks to, so exit 0 (signalling the script phase was successful). exit 0 fi echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" BCSYMBOLMAP_DIR="BCSymbolMaps" # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") # Copies and strips a vendored framework install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do echo "Installing $f" install_bcsymbolmap "$f" "$destination" rm "$f" done rmdir "${source}/${BCSYMBOLMAP_DIR}" fi # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" elif [ -L "${binary}" ]; then echo "Destination binary is symlinked..." dirname="$(dirname "${binary}")" binary="${dirname}/$(readlink "${binary}")" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Copies and strips a vendored dSYM install_dsym() { local source="$1" warn_missing_arch=${2:-true} if [ -r "$source" ]; then # Copy the dSYM into the targets temp dir. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" local basename basename="$(basename -s .dSYM "$source")" binary_name="$(ls "$source/Contents/Resources/DWARF")" binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" # Strip invalid architectures from the dSYM. if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then strip_invalid_archs "$binary" "$warn_missing_arch" fi if [[ $STRIP_BINARY_RETVAL == 0 ]]; then # Move the stripped file into its final destination. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" else # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. mkdir -p "${DWARF_DSYM_FOLDER_PATH}" touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" fi fi } # Used as a return value for each invocation of `strip_invalid_archs` function. STRIP_BINARY_RETVAL=0 # Strip invalid architectures strip_invalid_archs() { binary="$1" warn_missing_arch=${2:-true} # Get architectures for current target binary binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" # Intersect them with the architectures we are building for intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" # If there are no archs supported by this binary then warn the user if [[ -z "$intersected_archs" ]]; then if [[ "$warn_missing_arch" == "true" ]]; then echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." fi STRIP_BINARY_RETVAL=1 return fi stripped="" for arch in $binary_archs; do if ! [[ "${ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi STRIP_BINARY_RETVAL=0 } # Copies the bcsymbolmap files of a vendored framework install_bcsymbolmap() { local bcsymbolmap_path="$1" local destination="${BUILT_PRODUCTS_DIR}" echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identity echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then code_sign_cmd="$code_sign_cmd &" fi echo "$code_sign_cmd" eval "$code_sign_cmd" fi } if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Appirater/Appirater.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework" install_framework "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework" install_framework "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework" install_framework "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Appirater/Appirater.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework" install_framework "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework" install_framework "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework" install_framework "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework" install_framework "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework" fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait fi ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double Pods_SpotifyVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_SpotifyVersionString[]; ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Appirater" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Appirater/Appirater.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Firebase" $(inherited) ${PODS_ROOT}/Firebase/CoreOnly/Sources "${PODS_TARGET_SRCROOT}/Sources/FBLPromises/include" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"z" -framework "Appirater" -framework "CFNetwork" -framework "CoreTelephony" -framework "FBLPromises" -framework "FirebaseAnalytics" -framework "FirebaseCore" -framework "FirebaseCoreDiagnostics" -framework "FirebaseInstallations" -framework "Foundation" -framework "GoogleAppMeasurement" -framework "GoogleDataTransport" -framework "GoogleUtilities" -framework "ImageIO" -framework "SDWebImage" -framework "Security" -framework "StoreKit" -framework "SystemConfiguration" -framework "UIKit" -framework "nanopb" -weak_framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify.modulemap ================================================ framework module Pods_Spotify { umbrella header "Pods-Spotify-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/Pods-Spotify/Pods-Spotify.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Appirater" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb" "${PODS_ROOT}/FirebaseAnalytics/Frameworks" "${PODS_ROOT}/GoogleAppMeasurement/Frameworks" "${PODS_XCFRAMEWORKS_BUILD_DIR}/FirebaseAnalytics/AdIdSupport" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleAppMeasurement/AdIdSupport" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Appirater/Appirater.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Firebase" $(inherited) ${PODS_ROOT}/Firebase/CoreOnly/Sources "${PODS_TARGET_SRCROOT}/Sources/FBLPromises/include" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"z" -framework "Appirater" -framework "CFNetwork" -framework "CoreTelephony" -framework "FBLPromises" -framework "FirebaseAnalytics" -framework "FirebaseCore" -framework "FirebaseCoreDiagnostics" -framework "FirebaseInstallations" -framework "Foundation" -framework "GoogleAppMeasurement" -framework "GoogleDataTransport" -framework "GoogleUtilities" -framework "ImageIO" -framework "SDWebImage" -framework "Security" -framework "StoreKit" -framework "SystemConfiguration" -framework "UIKit" -framework "nanopb" -weak_framework "StoreKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 2.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC-dummy.m ================================================ #import @interface PodsDummy_PromisesObjC : NSObject @end @implementation PodsDummy_PromisesObjC @end ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "FBLPromise+All.h" #import "FBLPromise+Always.h" #import "FBLPromise+Any.h" #import "FBLPromise+Async.h" #import "FBLPromise+Await.h" #import "FBLPromise+Catch.h" #import "FBLPromise+Delay.h" #import "FBLPromise+Do.h" #import "FBLPromise+Race.h" #import "FBLPromise+Recover.h" #import "FBLPromise+Reduce.h" #import "FBLPromise+Retry.h" #import "FBLPromise+Testing.h" #import "FBLPromise+Then.h" #import "FBLPromise+Timeout.h" #import "FBLPromise+Validate.h" #import "FBLPromise+Wrap.h" #import "FBLPromise.h" #import "FBLPromiseError.h" #import "FBLPromises.h" FOUNDATION_EXPORT double FBLPromisesVersionNumber; FOUNDATION_EXPORT const unsigned char FBLPromisesVersionString[]; ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}/Sources/FBLPromises/include" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/PromisesObjC PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC.modulemap ================================================ framework module FBLPromises { umbrella header "PromisesObjC-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/PromisesObjC/PromisesObjC.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_TARGET_SRCROOT}/Sources/FBLPromises/include" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/PromisesObjC PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.11.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage-dummy.m ================================================ #import @interface PodsDummy_SDWebImage : NSObject @end @implementation PodsDummy_SDWebImage @end ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "NSButton+WebCache.h" #import "NSData+ImageContentType.h" #import "NSImage+Compatibility.h" #import "SDAnimatedImage.h" #import "SDAnimatedImagePlayer.h" #import "SDAnimatedImageRep.h" #import "SDAnimatedImageView+WebCache.h" #import "SDAnimatedImageView.h" #import "SDDiskCache.h" #import "SDGraphicsImageRenderer.h" #import "SDImageAPNGCoder.h" #import "SDImageAWebPCoder.h" #import "SDImageCache.h" #import "SDImageCacheConfig.h" #import "SDImageCacheDefine.h" #import "SDImageCachesManager.h" #import "SDImageCoder.h" #import "SDImageCoderHelper.h" #import "SDImageCodersManager.h" #import "SDImageFrame.h" #import "SDImageGIFCoder.h" #import "SDImageGraphics.h" #import "SDImageHEICCoder.h" #import "SDImageIOAnimatedCoder.h" #import "SDImageIOCoder.h" #import "SDImageLoader.h" #import "SDImageLoadersManager.h" #import "SDImageTransformer.h" #import "SDMemoryCache.h" #import "SDWebImageCacheKeyFilter.h" #import "SDWebImageCacheSerializer.h" #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDWebImageDownloader.h" #import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderDecryptor.h" #import "SDWebImageDownloaderOperation.h" #import "SDWebImageDownloaderRequestModifier.h" #import "SDWebImageDownloaderResponseModifier.h" #import "SDWebImageError.h" #import "SDWebImageIndicator.h" #import "SDWebImageManager.h" #import "SDWebImageOperation.h" #import "SDWebImageOptionsProcessor.h" #import "SDWebImagePrefetcher.h" #import "SDWebImageTransition.h" #import "UIButton+WebCache.h" #import "UIImage+ExtendedCacheData.h" #import "UIImage+ForceDecode.h" #import "UIImage+GIF.h" #import "UIImage+MemoryCacheCost.h" #import "UIImage+Metadata.h" #import "UIImage+MultiFormat.h" #import "UIImage+Transform.h" #import "UIImageView+HighlightedWebCache.h" #import "UIImageView+WebCache.h" #import "UIView+WebCache.h" #import "UIView+WebCacheOperation.h" #import "SDWebImage.h" FOUNDATION_EXPORT double SDWebImageVersionNumber; FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[]; ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "ImageIO" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SUPPORTS_MACCATALYST = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage.modulemap ================================================ framework module SDWebImage { umbrella header "SDWebImage-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/SDWebImage/SDWebImage.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "ImageIO" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SUPPORTS_MACCATALYST = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/nanopb/nanopb-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 2.30908.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Pods/Target Support Files/nanopb/nanopb-dummy.m ================================================ #import @interface PodsDummy_nanopb : NSObject @end @implementation PodsDummy_nanopb @end ================================================ FILE: Pods/Target Support Files/nanopb/nanopb-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: Pods/Target Support Files/nanopb/nanopb-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "pb.h" #import "pb_common.h" #import "pb_decode.h" #import "pb_encode.h" #import "pb.h" #import "pb_decode.h" #import "pb_common.h" #import "pb.h" #import "pb_encode.h" #import "pb_common.h" FOUNDATION_EXPORT double nanopbVersionNumber; FOUNDATION_EXPORT const unsigned char nanopbVersionString[]; ================================================ FILE: Pods/Target Support Files/nanopb/nanopb.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/nanopb GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/nanopb PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/Target Support Files/nanopb/nanopb.modulemap ================================================ framework module nanopb { umbrella header "nanopb-umbrella.h" export * module * { export * } } ================================================ FILE: Pods/Target Support Files/nanopb/nanopb.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/nanopb GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 $(inherited) PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/nanopb PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: Pods/nanopb/LICENSE.txt ================================================ Copyright (c) 2011 Petteri Aimonen This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: Pods/nanopb/README.md ================================================ Nanopb - Protocol Buffers for Embedded Systems ============================================== [![Build Status](https://travis-ci.org/nanopb/nanopb.svg?branch=master)](https://travis-ci.org/nanopb/nanopb) Nanopb is a small code-size Protocol Buffers implementation in ansi C. It is especially suitable for use in microcontrollers, but fits any memory restricted system. * **Homepage:** https://jpa.kapsi.fi/nanopb/ * **Documentation:** https://jpa.kapsi.fi/nanopb/docs/ * **Downloads:** https://jpa.kapsi.fi/nanopb/download/ * **Forum:** https://groups.google.com/forum/#!forum/nanopb Using the nanopb library ------------------------ To use the nanopb library, you need to do two things: 1. Compile your .proto files for nanopb, using `protoc`. 2. Include *pb_encode.c*, *pb_decode.c* and *pb_common.c* in your project. The easiest way to get started is to study the project in "examples/simple". It contains a Makefile, which should work directly under most Linux systems. However, for any other kind of build system, see the manual steps in README.txt in that folder. Using the Protocol Buffers compiler (protoc) -------------------------------------------- The nanopb generator is implemented as a plugin for the Google's own `protoc` compiler. This has the advantage that there is no need to reimplement the basic parsing of .proto files. However, it does mean that you need the Google's protobuf library in order to run the generator. If you have downloaded a binary package for nanopb (either Windows, Linux or Mac OS X version), the `protoc` binary is included in the 'generator-bin' folder. In this case, you are ready to go. Simply run this command: generator-bin/protoc --nanopb_out=. myprotocol.proto However, if you are using a git checkout or a plain source distribution, you need to provide your own version of `protoc` and the Google's protobuf library. On Linux, the necessary packages are `protobuf-compiler` and `python-protobuf`. On Windows, you can either build Google's protobuf library from source or use one of the binary distributions of it. In either case, if you use a separate `protoc`, you need to manually give the path to nanopb generator: protoc --plugin=protoc-gen-nanopb=nanopb/generator/protoc-gen-nanopb ... Running the tests ----------------- If you want to perform further development of the nanopb core, or to verify its functionality using your compiler and platform, you'll want to run the test suite. The build rules for the test suite are implemented using Scons, so you need to have that installed (ex: `sudo apt install scons` on Ubuntu). To run the tests: cd tests scons This will show the progress of various test cases. If the output does not end in an error, the test cases were successful. Note: Mac OS X by default aliases 'clang' as 'gcc', while not actually supporting the same command line options as gcc does. To run tests on Mac OS X, use: "scons CC=clang CXX=clang". Same way can be used to run tests with different compilers on any platform. ================================================ FILE: Pods/nanopb/pb.h ================================================ /* Common parts of the nanopb library. Most of these are quite low-level * stuff. For the high-level interface, see pb_encode.h and pb_decode.h. */ #ifndef PB_H_INCLUDED #define PB_H_INCLUDED /***************************************************************** * Nanopb compilation time options. You can change these here by * * uncommenting the lines, or on the compiler command line. * *****************************************************************/ /* Enable support for dynamically allocated fields */ /* #define PB_ENABLE_MALLOC 1 */ /* Define this if your CPU / compiler combination does not support * unaligned memory access to packed structures. */ /* #define PB_NO_PACKED_STRUCTS 1 */ /* Increase the number of required fields that are tracked. * A compiler warning will tell if you need this. */ /* #define PB_MAX_REQUIRED_FIELDS 256 */ /* Add support for tag numbers > 255 and fields larger than 255 bytes. */ /* #define PB_FIELD_16BIT 1 */ /* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */ /* #define PB_FIELD_32BIT 1 */ /* Disable support for error messages in order to save some code space. */ /* #define PB_NO_ERRMSG 1 */ /* Disable support for custom streams (support only memory buffers). */ /* #define PB_BUFFER_ONLY 1 */ /* Switch back to the old-style callback function signature. * This was the default until nanopb-0.2.1. */ /* #define PB_OLD_CALLBACK_STYLE */ /* Don't encode scalar arrays as packed. This is only to be used when * the decoder on the receiving side cannot process packed scalar arrays. * Such example is older protobuf.js. */ /* #define PB_ENCODE_ARRAYS_UNPACKED 1 */ /****************************************************************** * You usually don't need to change anything below this line. * * Feel free to look around and use the defined macros, though. * ******************************************************************/ /* Version of the nanopb library. Just in case you want to check it in * your own program. */ #define NANOPB_VERSION nanopb-0.3.9.8 /* Include all the system headers needed by nanopb. You will need the * definitions of the following: * - strlen, memcpy, memset functions * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t * - size_t * - bool * * If you don't have the standard header files, you can instead provide * a custom header that defines or includes all this. In that case, * define PB_SYSTEM_HEADER to the path of this file. */ #ifdef PB_SYSTEM_HEADER #include PB_SYSTEM_HEADER #else #include #include #include #include #ifdef PB_ENABLE_MALLOC #include #endif #endif /* Macro for defining packed structures (compiler dependent). * This just reduces memory requirements, but is not required. */ #if defined(PB_NO_PACKED_STRUCTS) /* Disable struct packing */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed #elif defined(__GNUC__) || defined(__clang__) /* For GCC and clang */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed __attribute__((packed)) #elif defined(__ICCARM__) || defined(__CC_ARM) /* For IAR ARM and Keil MDK-ARM compilers */ # define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)") # define PB_PACKED_STRUCT_END _Pragma("pack(pop)") # define pb_packed #elif defined(_MSC_VER) && (_MSC_VER >= 1500) /* For Microsoft Visual C++ */ # define PB_PACKED_STRUCT_START __pragma(pack(push, 1)) # define PB_PACKED_STRUCT_END __pragma(pack(pop)) # define pb_packed #else /* Unknown compiler */ # define PB_PACKED_STRUCT_START # define PB_PACKED_STRUCT_END # define pb_packed #endif /* Handly macro for suppressing unreferenced-parameter compiler warnings. */ #ifndef PB_UNUSED #define PB_UNUSED(x) (void)(x) #endif /* Compile-time assertion, used for checking compatible compilation options. * If this does not work properly on your compiler, use * #define PB_NO_STATIC_ASSERT to disable it. * * But before doing that, check carefully the error message / place where it * comes from to see if the error has a real cause. Unfortunately the error * message is not always very clear to read, but you can see the reason better * in the place where the PB_STATIC_ASSERT macro was called. */ #ifndef PB_NO_STATIC_ASSERT #ifndef PB_STATIC_ASSERT #define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1]; #define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) #define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##LINE##COUNTER #endif #else #define PB_STATIC_ASSERT(COND,MSG) #endif /* Number of required fields to keep track of. */ #ifndef PB_MAX_REQUIRED_FIELDS #define PB_MAX_REQUIRED_FIELDS 64 #endif #if PB_MAX_REQUIRED_FIELDS < 64 #error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64). #endif /* List of possible field types. These are used in the autogenerated code. * Least-significant 4 bits tell the scalar type * Most-significant 4 bits specify repeated/required/packed etc. */ typedef uint_least8_t pb_type_t; /**** Field data types ****/ /* Numeric types */ #define PB_LTYPE_BOOL 0x00 /* bool */ #define PB_LTYPE_VARINT 0x01 /* int32, int64, enum, bool */ #define PB_LTYPE_UVARINT 0x02 /* uint32, uint64 */ #define PB_LTYPE_SVARINT 0x03 /* sint32, sint64 */ #define PB_LTYPE_FIXED32 0x04 /* fixed32, sfixed32, float */ #define PB_LTYPE_FIXED64 0x05 /* fixed64, sfixed64, double */ /* Marker for last packable field type. */ #define PB_LTYPE_LAST_PACKABLE 0x05 /* Byte array with pre-allocated buffer. * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ #define PB_LTYPE_BYTES 0x06 /* String with pre-allocated buffer. * data_size is the maximum length. */ #define PB_LTYPE_STRING 0x07 /* Submessage * submsg_fields is pointer to field descriptions */ #define PB_LTYPE_SUBMESSAGE 0x08 /* Extension pseudo-field * The field contains a pointer to pb_extension_t */ #define PB_LTYPE_EXTENSION 0x09 /* Byte array with inline, pre-allocated byffer. * data_size is the length of the inline, allocated buffer. * This differs from PB_LTYPE_BYTES by defining the element as * pb_byte_t[data_size] rather than pb_bytes_array_t. */ #define PB_LTYPE_FIXED_LENGTH_BYTES 0x0A /* Number of declared LTYPES */ #define PB_LTYPES_COUNT 0x0B #define PB_LTYPE_MASK 0x0F /**** Field repetition rules ****/ #define PB_HTYPE_REQUIRED 0x00 #define PB_HTYPE_OPTIONAL 0x10 #define PB_HTYPE_REPEATED 0x20 #define PB_HTYPE_ONEOF 0x30 #define PB_HTYPE_MASK 0x30 /**** Field allocation types ****/ #define PB_ATYPE_STATIC 0x00 #define PB_ATYPE_POINTER 0x80 #define PB_ATYPE_CALLBACK 0x40 #define PB_ATYPE_MASK 0xC0 #define PB_ATYPE(x) ((x) & PB_ATYPE_MASK) #define PB_HTYPE(x) ((x) & PB_HTYPE_MASK) #define PB_LTYPE(x) ((x) & PB_LTYPE_MASK) /* Data type used for storing sizes of struct fields * and array counts. */ #if defined(PB_FIELD_32BIT) typedef uint32_t pb_size_t; typedef int32_t pb_ssize_t; #elif defined(PB_FIELD_16BIT) typedef uint_least16_t pb_size_t; typedef int_least16_t pb_ssize_t; #else typedef uint_least8_t pb_size_t; typedef int_least8_t pb_ssize_t; #endif #define PB_SIZE_MAX ((pb_size_t)-1) /* Data type for storing encoded data and other byte streams. * This typedef exists to support platforms where uint8_t does not exist. * You can regard it as equivalent on uint8_t on other platforms. */ typedef uint_least8_t pb_byte_t; /* This structure is used in auto-generated constants * to specify struct fields. * You can change field sizes if you need structures * larger than 256 bytes or field tags larger than 256. * The compiler should complain if your .proto has such * structures. Fix that by defining PB_FIELD_16BIT or * PB_FIELD_32BIT. */ PB_PACKED_STRUCT_START typedef struct pb_field_s pb_field_t; struct pb_field_s { pb_size_t tag; pb_type_t type; pb_size_t data_offset; /* Offset of field data, relative to previous field. */ pb_ssize_t size_offset; /* Offset of array size or has-boolean, relative to data */ pb_size_t data_size; /* Data size in bytes for a single item */ pb_size_t array_size; /* Maximum number of entries in array */ /* Field definitions for submessage * OR default value for all other non-array, non-callback types * If null, then field will zeroed. */ const void *ptr; } pb_packed; PB_PACKED_STRUCT_END /* Make sure that the standard integer types are of the expected sizes. * Otherwise fixed32/fixed64 fields can break. * * If you get errors here, it probably means that your stdint.h is not * correct for your platform. */ #ifndef PB_WITHOUT_64BIT PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE) PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE) #endif /* This structure is used for 'bytes' arrays. * It has the number of bytes in the beginning, and after that an array. * Note that actual structs used will have a different length of bytes array. */ #define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; } #define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes)) struct pb_bytes_array_s { pb_size_t size; pb_byte_t bytes[1]; }; typedef struct pb_bytes_array_s pb_bytes_array_t; /* This structure is used for giving the callback function. * It is stored in the message structure and filled in by the method that * calls pb_decode. * * The decoding callback will be given a limited-length stream * If the wire type was string, the length is the length of the string. * If the wire type was a varint/fixed32/fixed64, the length is the length * of the actual value. * The function may be called multiple times (especially for repeated types, * but also otherwise if the message happens to contain the field multiple * times.) * * The encoding callback will receive the actual output stream. * It should write all the data in one call, including the field tag and * wire type. It can write multiple fields. * * The callback can be null if you want to skip a field. */ typedef struct pb_istream_s pb_istream_t; typedef struct pb_ostream_s pb_ostream_t; typedef struct pb_callback_s pb_callback_t; struct pb_callback_s { #ifdef PB_OLD_CALLBACK_STYLE /* Deprecated since nanopb-0.2.1 */ union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg); } funcs; #else /* New function signature, which allows modifying arg contents in callback. */ union { bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg); bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); } funcs; #endif /* Free arg for use by callback */ void *arg; }; /* Wire types. Library user needs these only in encoder callbacks. */ typedef enum { PB_WT_VARINT = 0, PB_WT_64BIT = 1, PB_WT_STRING = 2, PB_WT_32BIT = 5 } pb_wire_type_t; /* Structure for defining the handling of unknown/extension fields. * Usually the pb_extension_type_t structure is automatically generated, * while the pb_extension_t structure is created by the user. However, * if you want to catch all unknown fields, you can also create a custom * pb_extension_type_t with your own callback. */ typedef struct pb_extension_type_s pb_extension_type_t; typedef struct pb_extension_s pb_extension_t; struct pb_extension_type_s { /* Called for each unknown field in the message. * If you handle the field, read off all of its data and return true. * If you do not handle the field, do not read anything and return true. * If you run into an error, return false. * Set to NULL for default handler. */ bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); /* Called once after all regular fields have been encoded. * If you have something to write, do so and return true. * If you do not have anything to write, just return true. * If you run into an error, return false. * Set to NULL for default handler. */ bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension); /* Free field for use by the callback. */ const void *arg; }; struct pb_extension_s { /* Type describing the extension field. Usually you'll initialize * this to a pointer to the automatically generated structure. */ const pb_extension_type_t *type; /* Destination for the decoded data. This must match the datatype * of the extension field. */ void *dest; /* Pointer to the next extension handler, or NULL. * If this extension does not match a field, the next handler is * automatically called. */ pb_extension_t *next; /* The decoder sets this to true if the extension was found. * Ignored for encoding. */ bool found; }; /* Memory allocation functions to use. You can define pb_realloc and * pb_free to custom functions if you want. */ #ifdef PB_ENABLE_MALLOC # ifndef pb_realloc # define pb_realloc(ptr, size) realloc(ptr, size) # endif # ifndef pb_free # define pb_free(ptr) free(ptr) # endif #endif /* This is used to inform about need to regenerate .pb.h/.pb.c files. */ #define PB_PROTO_HEADER_VERSION 30 /* These macros are used to declare pb_field_t's in the constant array. */ /* Size of a structure member, in bytes. */ #define pb_membersize(st, m) (sizeof ((st*)0)->m) /* Number of entries in an array. */ #define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) /* Delta from start of one member to the start of another member. */ #define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) /* Marks the end of the field list */ #define PB_LAST_FIELD {0,(pb_type_t) 0,0,0,0,0,0} /* Macros for filling in the data_offset field */ /* data_offset for first field in a message */ #define PB_DATAOFFSET_FIRST(st, m1, m2) (offsetof(st, m1)) /* data_offset for subsequent fields */ #define PB_DATAOFFSET_OTHER(st, m1, m2) (offsetof(st, m1) - offsetof(st, m2) - pb_membersize(st, m2)) /* data offset for subsequent fields inside an union (oneof) */ #define PB_DATAOFFSET_UNION(st, m1, m2) (PB_SIZE_MAX) /* Choose first/other based on m1 == m2 (deprecated, remains for backwards compatibility) */ #define PB_DATAOFFSET_CHOOSE(st, m1, m2) (int)(offsetof(st, m1) == offsetof(st, m2) \ ? PB_DATAOFFSET_FIRST(st, m1, m2) \ : PB_DATAOFFSET_OTHER(st, m1, m2)) /* Required fields are the simplest. They just have delta (padding) from * previous field end, and the size of the field. Pointer is used for * submessages and default values. */ #define PB_REQUIRED_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Optional fields add the delta to the has_ variable. */ #define PB_OPTIONAL_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \ fd, \ pb_delta(st, has_ ## m, m), \ pb_membersize(st, m), 0, ptr} #define PB_SINGULAR_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Repeated fields have a _count field and also the maximum number of entries. */ #define PB_REPEATED_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | ltype, \ fd, \ pb_delta(st, m ## _count, m), \ pb_membersize(st, m[0]), \ pb_arraysize(st, m), ptr} /* Allocated fields carry the size of the actual data, not the pointer */ #define PB_REQUIRED_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Optional fields don't need a has_ variable, as information would be redundant */ #define PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Same as optional fields*/ #define PB_SINGULAR_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m[0]), 0, ptr} /* Repeated fields have a _count field and a pointer to array of pointers */ #define PB_REPEATED_POINTER(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_REPEATED | ltype, \ fd, pb_delta(st, m ## _count, m), \ pb_membersize(st, m[0]), 0, ptr} /* Callbacks are much like required fields except with special datatype. */ #define PB_REQUIRED_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REQUIRED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_SINGULAR_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} #define PB_REPEATED_CALLBACK(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REPEATED | ltype, \ fd, 0, pb_membersize(st, m), 0, ptr} /* Optional extensions don't have the has_ field, as that would be redundant. * Furthermore, the combination of OPTIONAL without has_ field is used * for indicating proto3 style fields. Extensions exist in proto2 mode only, * so they should be encoded according to proto2 rules. To avoid the conflict, * extensions are marked as REQUIRED instead. */ #define PB_OPTEXT_STATIC(tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \ 0, \ 0, \ pb_membersize(st, m), 0, ptr} #define PB_OPTEXT_POINTER(tag, st, m, fd, ltype, ptr) \ PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr) #define PB_OPTEXT_CALLBACK(tag, st, m, fd, ltype, ptr) \ PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) /* The mapping from protobuf types to LTYPEs is done using these macros. */ #define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL #define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES #define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT #define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT #define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT #define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT #define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE #define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32 #define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT #define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT #define PB_LTYPE_MAP_STRING PB_LTYPE_STRING #define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT #define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT #define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION #define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES /* This is the actual macro used in field descriptions. * It takes these arguments: * - Field tag number * - Field type: BOOL, BYTES, DOUBLE, ENUM, UENUM, FIXED32, FIXED64, * FLOAT, INT32, INT64, MESSAGE, SFIXED32, SFIXED64 * SINT32, SINT64, STRING, UINT32, UINT64 or EXTENSION * - Field rules: REQUIRED, OPTIONAL or REPEATED * - Allocation: STATIC, CALLBACK or POINTER * - Placement: FIRST or OTHER, depending on if this is the first field in structure. * - Message name * - Field name * - Previous field name (or field name again for first field) * - Pointer to default value or submsg fields. */ #define PB_FIELD(tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ ## rules ## _ ## allocation(tag, message, field, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) /* Field description for repeated static fixed count fields.*/ #define PB_REPEATED_FIXED_COUNT(tag, type, placement, message, field, prevfield, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | PB_LTYPE_MAP_ ## type, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ 0, \ pb_membersize(message, field[0]), \ pb_arraysize(message, field), ptr} /* Field description for oneof fields. This requires taking into account the * union name also, that's why a separate set of macros is needed. */ #define PB_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, u.m), \ pb_membersize(st, u.m), 0, ptr} #define PB_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, u.m), \ pb_membersize(st, u.m[0]), 0, ptr} #define PB_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ONEOF_ ## allocation(union_name, tag, message, field, \ PB_DATAOFFSET_ ## placement(message, union_name.field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) #define PB_ANONYMOUS_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, m), \ pb_membersize(st, m), 0, ptr} #define PB_ANONYMOUS_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \ fd, pb_delta(st, which_ ## u, m), \ pb_membersize(st, m[0]), 0, ptr} #define PB_ANONYMOUS_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \ PB_ANONYMOUS_ONEOF_ ## allocation(union_name, tag, message, field, \ PB_DATAOFFSET_ ## placement(message, field, prevfield), \ PB_LTYPE_MAP_ ## type, ptr) /* These macros are used for giving out error messages. * They are mostly a debugging aid; the main error information * is the true/false return value from functions. * Some code space can be saved by disabling the error * messages if not used. * * PB_SET_ERROR() sets the error message if none has been set yet. * msg must be a constant string literal. * PB_GET_ERROR() always returns a pointer to a string. * PB_RETURN_ERROR() sets the error and returns false from current * function. */ #ifdef PB_NO_ERRMSG #define PB_SET_ERROR(stream, msg) PB_UNUSED(stream) #define PB_GET_ERROR(stream) "(errmsg disabled)" #else #define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg)) #define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)") #endif #define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false #endif ================================================ FILE: Pods/nanopb/pb_common.c ================================================ /* pb_common.c: Common support functions for pb_encode.c and pb_decode.c. * * 2014 Petteri Aimonen */ #include "pb_common.h" bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct) { iter->start = fields; iter->pos = fields; iter->required_field_index = 0; iter->dest_struct = dest_struct; iter->pData = (char*)dest_struct + iter->pos->data_offset; iter->pSize = (char*)iter->pData + iter->pos->size_offset; return (iter->pos->tag != 0); } bool pb_field_iter_next(pb_field_iter_t *iter) { const pb_field_t *prev_field = iter->pos; if (prev_field->tag == 0) { /* Handle empty message types, where the first field is already the terminator. * In other cases, the iter->pos never points to the terminator. */ return false; } iter->pos++; if (iter->pos->tag == 0) { /* Wrapped back to beginning, reinitialize */ (void)pb_field_iter_begin(iter, iter->start, iter->dest_struct); return false; } else { /* Increment the pointers based on previous field size */ size_t prev_size = prev_field->data_size; if (PB_HTYPE(prev_field->type) == PB_HTYPE_ONEOF && PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF && iter->pos->data_offset == PB_SIZE_MAX) { /* Don't advance pointers inside unions */ return true; } else if (PB_ATYPE(prev_field->type) == PB_ATYPE_STATIC && PB_HTYPE(prev_field->type) == PB_HTYPE_REPEATED) { /* In static arrays, the data_size tells the size of a single entry and * array_size is the number of entries */ prev_size *= prev_field->array_size; } else if (PB_ATYPE(prev_field->type) == PB_ATYPE_POINTER) { /* Pointer fields always have a constant size in the main structure. * The data_size only applies to the dynamically allocated area. */ prev_size = sizeof(void*); } if (PB_HTYPE(prev_field->type) == PB_HTYPE_REQUIRED) { /* Count the required fields, in order to check their presence in the * decoder. */ iter->required_field_index++; } iter->pData = (char*)iter->pData + prev_size + iter->pos->data_offset; iter->pSize = (char*)iter->pData + iter->pos->size_offset; return true; } } bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag) { const pb_field_t *start = iter->pos; do { if (iter->pos->tag == tag && PB_LTYPE(iter->pos->type) != PB_LTYPE_EXTENSION) { /* Found the wanted field */ return true; } (void)pb_field_iter_next(iter); } while (iter->pos != start); /* Searched all the way back to start, and found nothing. */ return false; } ================================================ FILE: Pods/nanopb/pb_common.h ================================================ /* pb_common.h: Common support functions for pb_encode.c and pb_decode.c. * These functions are rarely needed by applications directly. */ #ifndef PB_COMMON_H_INCLUDED #define PB_COMMON_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Iterator for pb_field_t list */ struct pb_field_iter_s { const pb_field_t *start; /* Start of the pb_field_t array */ const pb_field_t *pos; /* Current position of the iterator */ unsigned required_field_index; /* Zero-based index that counts only the required fields */ void *dest_struct; /* Pointer to start of the structure */ void *pData; /* Pointer to current field value */ void *pSize; /* Pointer to count/has field */ }; typedef struct pb_field_iter_s pb_field_iter_t; /* Initialize the field iterator structure to beginning. * Returns false if the message type is empty. */ bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct); /* Advance the iterator to the next field. * Returns false when the iterator wraps back to the first field. */ bool pb_field_iter_next(pb_field_iter_t *iter); /* Advance the iterator until it points at a field with the given tag. * Returns false if no such field exists. */ bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: Pods/nanopb/pb_decode.c ================================================ /* pb_decode.c -- decode a protobuf using minimal resources * * 2011 Petteri Aimonen */ /* Use the GCC warn_unused_result attribute to check that all return values * are propagated correctly. On other compilers and gcc before 3.4.0 just * ignore the annotation. */ #if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) #define checkreturn #else #define checkreturn __attribute__((warn_unused_result)) #endif #include "pb.h" #include "pb_decode.h" #include "pb_common.h" /************************************** * Declarations internal to this file * **************************************/ typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn; static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size); static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter); static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension); static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter); static bool checkreturn find_extension_field(pb_field_iter_t *iter); static void pb_field_set_to_default(pb_field_iter_t *iter); static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct); static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_skip_varint(pb_istream_t *stream); static bool checkreturn pb_skip_string(pb_istream_t *stream); #ifdef PB_ENABLE_MALLOC static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size); static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter); static void pb_release_single_field(const pb_field_iter_t *iter); #endif #ifdef PB_WITHOUT_64BIT #define pb_int64_t int32_t #define pb_uint64_t uint32_t #else #define pb_int64_t int64_t #define pb_uint64_t uint64_t #endif /* --- Function pointers to field decoders --- * Order in the array must match pb_action_t LTYPE numbering. */ static const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = { &pb_dec_bool, &pb_dec_varint, &pb_dec_uvarint, &pb_dec_svarint, &pb_dec_fixed32, &pb_dec_fixed64, &pb_dec_bytes, &pb_dec_string, &pb_dec_submessage, NULL, /* extensions */ &pb_dec_fixed_length_bytes }; /******************************* * pb_istream_t implementation * *******************************/ static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) { size_t i; const pb_byte_t *source = (const pb_byte_t*)stream->state; stream->state = (pb_byte_t*)stream->state + count; if (buf != NULL) { for (i = 0; i < count; i++) buf[i] = source[i]; } return true; } bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) { if (count == 0) return true; #ifndef PB_BUFFER_ONLY if (buf == NULL && stream->callback != buf_read) { /* Skip input bytes */ pb_byte_t tmp[16]; while (count > 16) { if (!pb_read(stream, tmp, 16)) return false; count -= 16; } return pb_read(stream, tmp, count); } #endif if (stream->bytes_left < count) PB_RETURN_ERROR(stream, "end-of-stream"); #ifndef PB_BUFFER_ONLY if (!stream->callback(stream, buf, count)) PB_RETURN_ERROR(stream, "io error"); #else if (!buf_read(stream, buf, count)) return false; #endif stream->bytes_left -= count; return true; } /* Read a single byte from input stream. buf may not be NULL. * This is an optimization for the varint decoding. */ static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf) { if (stream->bytes_left == 0) PB_RETURN_ERROR(stream, "end-of-stream"); #ifndef PB_BUFFER_ONLY if (!stream->callback(stream, buf, 1)) PB_RETURN_ERROR(stream, "io error"); #else *buf = *(const pb_byte_t*)stream->state; stream->state = (pb_byte_t*)stream->state + 1; #endif stream->bytes_left--; return true; } pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize) { pb_istream_t stream; /* Cast away the const from buf without a compiler error. We are * careful to use it only in a const manner in the callbacks. */ union { void *state; const void *c_state; } state; #ifdef PB_BUFFER_ONLY stream.callback = NULL; #else stream.callback = &buf_read; #endif state.c_state = buf; stream.state = state.state; stream.bytes_left = bufsize; #ifndef PB_NO_ERRMSG stream.errmsg = NULL; #endif return stream; } /******************** * Helper functions * ********************/ static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof) { pb_byte_t byte; uint32_t result; if (!pb_readbyte(stream, &byte)) { if (stream->bytes_left == 0) { if (eof) { *eof = true; } } return false; } if ((byte & 0x80) == 0) { /* Quick case, 1 byte value */ result = byte; } else { /* Multibyte case */ uint_fast8_t bitpos = 7; result = byte & 0x7F; do { if (!pb_readbyte(stream, &byte)) return false; if (bitpos >= 32) { /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */ uint8_t sign_extension = (bitpos < 63) ? 0xFF : 0x01; if ((byte & 0x7F) != 0x00 && ((result >> 31) == 0 || byte != sign_extension)) { PB_RETURN_ERROR(stream, "varint overflow"); } } else { result |= (uint32_t)(byte & 0x7F) << bitpos; } bitpos = (uint_fast8_t)(bitpos + 7); } while (byte & 0x80); if (bitpos == 35 && (byte & 0x70) != 0) { /* The last byte was at bitpos=28, so only bottom 4 bits fit. */ PB_RETURN_ERROR(stream, "varint overflow"); } } *dest = result; return true; } bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) { return pb_decode_varint32_eof(stream, dest, NULL); } #ifndef PB_WITHOUT_64BIT bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest) { pb_byte_t byte; uint_fast8_t bitpos = 0; uint64_t result = 0; do { if (bitpos >= 64) PB_RETURN_ERROR(stream, "varint overflow"); if (!pb_readbyte(stream, &byte)) return false; result |= (uint64_t)(byte & 0x7F) << bitpos; bitpos = (uint_fast8_t)(bitpos + 7); } while (byte & 0x80); *dest = result; return true; } #endif bool checkreturn pb_skip_varint(pb_istream_t *stream) { pb_byte_t byte; do { if (!pb_read(stream, &byte, 1)) return false; } while (byte & 0x80); return true; } bool checkreturn pb_skip_string(pb_istream_t *stream) { uint32_t length; if (!pb_decode_varint32(stream, &length)) return false; return pb_read(stream, NULL, length); } bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof) { uint32_t temp; *eof = false; *wire_type = (pb_wire_type_t) 0; *tag = 0; if (!pb_decode_varint32_eof(stream, &temp, eof)) { return false; } if (temp == 0) { *eof = true; /* Special feature: allow 0-terminated messages. */ return false; } *tag = temp >> 3; *wire_type = (pb_wire_type_t)(temp & 7); return true; } bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type) { switch (wire_type) { case PB_WT_VARINT: return pb_skip_varint(stream); case PB_WT_64BIT: return pb_read(stream, NULL, 8); case PB_WT_STRING: return pb_skip_string(stream); case PB_WT_32BIT: return pb_read(stream, NULL, 4); default: PB_RETURN_ERROR(stream, "invalid wire_type"); } } /* Read a raw value to buffer, for the purpose of passing it to callback as * a substream. Size is maximum size on call, and actual size on return. */ static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size) { size_t max_size = *size; switch (wire_type) { case PB_WT_VARINT: *size = 0; do { (*size)++; if (*size > max_size) return false; if (!pb_read(stream, buf, 1)) return false; } while (*buf++ & 0x80); return true; case PB_WT_64BIT: *size = 8; return pb_read(stream, buf, 8); case PB_WT_32BIT: *size = 4; return pb_read(stream, buf, 4); case PB_WT_STRING: /* Calling read_raw_value with a PB_WT_STRING is an error. * Explicitly handle this case and fallthrough to default to avoid * compiler warnings. */ default: PB_RETURN_ERROR(stream, "invalid wire_type"); } } /* Decode string length from stream and return a substream with limited length. * Remember to close the substream using pb_close_string_substream(). */ bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream) { uint32_t size; if (!pb_decode_varint32(stream, &size)) return false; *substream = *stream; if (substream->bytes_left < size) PB_RETURN_ERROR(stream, "parent stream too short"); substream->bytes_left = size; stream->bytes_left -= size; return true; } bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream) { if (substream->bytes_left) { if (!pb_read(substream, NULL, substream->bytes_left)) return false; } stream->state = substream->state; #ifndef PB_NO_ERRMSG stream->errmsg = substream->errmsg; #endif return true; } /************************* * Decode a single field * *************************/ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) { pb_type_t type; pb_decoder_t func; type = iter->pos->type; func = PB_DECODERS[PB_LTYPE(type)]; switch (PB_HTYPE(type)) { case PB_HTYPE_REQUIRED: return func(stream, iter->pos, iter->pData); case PB_HTYPE_OPTIONAL: if (iter->pSize != iter->pData) *(bool*)iter->pSize = true; return func(stream, iter->pos, iter->pData); case PB_HTYPE_REPEATED: if (wire_type == PB_WT_STRING && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) { /* Packed array */ bool status = true; pb_size_t *size = (pb_size_t*)iter->pSize; pb_istream_t substream; if (!pb_make_string_substream(stream, &substream)) return false; while (substream.bytes_left > 0 && *size < iter->pos->array_size) { void *pItem = (char*)iter->pData + iter->pos->data_size * (*size); if (!func(&substream, iter->pos, pItem)) { status = false; break; } (*size)++; } if (substream.bytes_left != 0) PB_RETURN_ERROR(stream, "array overflow"); if (!pb_close_string_substream(stream, &substream)) return false; return status; } else { /* Repeated field */ pb_size_t *size = (pb_size_t*)iter->pSize; char *pItem = (char*)iter->pData + iter->pos->data_size * (*size); if ((*size)++ >= iter->pos->array_size) PB_RETURN_ERROR(stream, "array overflow"); return func(stream, iter->pos, pItem); } case PB_HTYPE_ONEOF: if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && *(pb_size_t*)iter->pSize != iter->pos->tag) { /* We memset to zero so that any callbacks are set to NULL. * This is because the callbacks might otherwise have values * from some other union field. */ memset(iter->pData, 0, iter->pos->data_size); pb_message_set_to_defaults((const pb_field_t*)iter->pos->ptr, iter->pData); } *(pb_size_t*)iter->pSize = iter->pos->tag; return func(stream, iter->pos, iter->pData); default: PB_RETURN_ERROR(stream, "invalid field type"); } } #ifdef PB_ENABLE_MALLOC /* Allocate storage for the field and store the pointer at iter->pData. * array_size is the number of entries to reserve in an array. * Zero size is not allowed, use pb_free() for releasing. */ static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size) { void *ptr = *(void**)pData; if (data_size == 0 || array_size == 0) PB_RETURN_ERROR(stream, "invalid size"); #ifdef __AVR__ /* Workaround for AVR libc bug 53284: http://savannah.nongnu.org/bugs/?53284 * Realloc to size of 1 byte can cause corruption of the malloc structures. */ if (data_size == 1 && array_size == 1) { data_size = 2; } #endif /* Check for multiplication overflows. * This code avoids the costly division if the sizes are small enough. * Multiplication is safe as long as only half of bits are set * in either multiplicand. */ { const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4); if (data_size >= check_limit || array_size >= check_limit) { const size_t size_max = (size_t)-1; if (size_max / array_size < data_size) { PB_RETURN_ERROR(stream, "size too large"); } } } /* Allocate new or expand previous allocation */ /* Note: on failure the old pointer will remain in the structure, * the message must be freed by caller also on error return. */ ptr = pb_realloc(ptr, array_size * data_size); if (ptr == NULL) PB_RETURN_ERROR(stream, "realloc failed"); *(void**)pData = ptr; return true; } /* Clear a newly allocated item in case it contains a pointer, or is a submessage. */ static void initialize_pointer_field(void *pItem, pb_field_iter_t *iter) { if (PB_LTYPE(iter->pos->type) == PB_LTYPE_STRING || PB_LTYPE(iter->pos->type) == PB_LTYPE_BYTES) { *(void**)pItem = NULL; } else if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE) { /* We memset to zero so that any callbacks are set to NULL. * Then set any default values. */ memset(pItem, 0, iter->pos->data_size); pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, pItem); } } #endif static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) { #ifndef PB_ENABLE_MALLOC PB_UNUSED(wire_type); PB_UNUSED(iter); PB_RETURN_ERROR(stream, "no malloc support"); #else pb_type_t type; pb_decoder_t func; type = iter->pos->type; func = PB_DECODERS[PB_LTYPE(type)]; switch (PB_HTYPE(type)) { case PB_HTYPE_REQUIRED: case PB_HTYPE_OPTIONAL: case PB_HTYPE_ONEOF: if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && *(void**)iter->pData != NULL) { /* Duplicate field, have to release the old allocation first. */ pb_release_single_field(iter); } if (PB_HTYPE(type) == PB_HTYPE_ONEOF) { *(pb_size_t*)iter->pSize = iter->pos->tag; } if (PB_LTYPE(type) == PB_LTYPE_STRING || PB_LTYPE(type) == PB_LTYPE_BYTES) { return func(stream, iter->pos, iter->pData); } else { if (!allocate_field(stream, iter->pData, iter->pos->data_size, 1)) return false; initialize_pointer_field(*(void**)iter->pData, iter); return func(stream, iter->pos, *(void**)iter->pData); } case PB_HTYPE_REPEATED: if (wire_type == PB_WT_STRING && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) { /* Packed array, multiple items come in at once. */ bool status = true; pb_size_t *size = (pb_size_t*)iter->pSize; size_t allocated_size = *size; void *pItem; pb_istream_t substream; if (!pb_make_string_substream(stream, &substream)) return false; while (substream.bytes_left) { if (*size == PB_SIZE_MAX) { #ifndef PB_NO_ERRMSG stream->errmsg = "too many array entries"; #endif status = false; break; } if ((size_t)*size + 1 > allocated_size) { /* Allocate more storage. This tries to guess the * number of remaining entries. Round the division * upwards. */ size_t remain = (substream.bytes_left - 1) / iter->pos->data_size + 1; if (remain < PB_SIZE_MAX - allocated_size) allocated_size += remain; else allocated_size += 1; if (!allocate_field(&substream, iter->pData, iter->pos->data_size, allocated_size)) { status = false; break; } } /* Decode the array entry */ pItem = *(char**)iter->pData + iter->pos->data_size * (*size); initialize_pointer_field(pItem, iter); if (!func(&substream, iter->pos, pItem)) { status = false; break; } (*size)++; } if (!pb_close_string_substream(stream, &substream)) return false; return status; } else { /* Normal repeated field, i.e. only one item at a time. */ pb_size_t *size = (pb_size_t*)iter->pSize; void *pItem; if (*size == PB_SIZE_MAX) PB_RETURN_ERROR(stream, "too many array entries"); if (!allocate_field(stream, iter->pData, iter->pos->data_size, (size_t)(*size + 1))) return false; pItem = *(char**)iter->pData + iter->pos->data_size * (*size); (*size)++; initialize_pointer_field(pItem, iter); return func(stream, iter->pos, pItem); } default: PB_RETURN_ERROR(stream, "invalid field type"); } #endif } static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) { pb_callback_t *pCallback = (pb_callback_t*)iter->pData; #ifdef PB_OLD_CALLBACK_STYLE void *arg; #else void **arg; #endif if (pCallback == NULL || pCallback->funcs.decode == NULL) return pb_skip_field(stream, wire_type); #ifdef PB_OLD_CALLBACK_STYLE arg = pCallback->arg; #else arg = &(pCallback->arg); #endif if (wire_type == PB_WT_STRING) { pb_istream_t substream; if (!pb_make_string_substream(stream, &substream)) return false; do { if (!pCallback->funcs.decode(&substream, iter->pos, arg)) PB_RETURN_ERROR(stream, "callback failed"); } while (substream.bytes_left); if (!pb_close_string_substream(stream, &substream)) return false; return true; } else { /* Copy the single scalar value to stack. * This is required so that we can limit the stream length, * which in turn allows to use same callback for packed and * not-packed fields. */ pb_istream_t substream; pb_byte_t buffer[10]; size_t size = sizeof(buffer); if (!read_raw_value(stream, wire_type, buffer, &size)) return false; substream = pb_istream_from_buffer(buffer, size); return pCallback->funcs.decode(&substream, iter->pos, arg); } } static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter) { #ifdef PB_ENABLE_MALLOC /* When decoding an oneof field, check if there is old data that must be * released first. */ if (PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF) { if (!pb_release_union_field(stream, iter)) return false; } #endif switch (PB_ATYPE(iter->pos->type)) { case PB_ATYPE_STATIC: return decode_static_field(stream, wire_type, iter); case PB_ATYPE_POINTER: return decode_pointer_field(stream, wire_type, iter); case PB_ATYPE_CALLBACK: return decode_callback_field(stream, wire_type, iter); default: PB_RETURN_ERROR(stream, "invalid field type"); } } static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension) { /* Fake a field iterator for the extension field. * It is not actually safe to advance this iterator, but decode_field * will not even try to. */ const pb_field_t *field = (const pb_field_t*)extension->type->arg; (void)pb_field_iter_begin(iter, field, extension->dest); iter->pData = extension->dest; iter->pSize = &extension->found; if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) { /* For pointer extensions, the pointer is stored directly * in the extension structure. This avoids having an extra * indirection. */ iter->pData = &extension->dest; } } /* Default handler for extension fields. Expects a pb_field_t structure * in extension->type->arg. */ static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type) { const pb_field_t *field = (const pb_field_t*)extension->type->arg; pb_field_iter_t iter; if (field->tag != tag) return true; iter_from_extension(&iter, extension); extension->found = true; return decode_field(stream, wire_type, &iter); } /* Try to decode an unknown field as an extension field. Tries each extension * decoder in turn, until one of them handles the field or loop ends. */ static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter) { pb_extension_t *extension = *(pb_extension_t* const *)iter->pData; size_t pos = stream->bytes_left; while (extension != NULL && pos == stream->bytes_left) { bool status; if (extension->type->decode) status = extension->type->decode(stream, extension, tag, wire_type); else status = default_extension_decoder(stream, extension, tag, wire_type); if (!status) return false; extension = extension->next; } return true; } /* Step through the iterator until an extension field is found or until all * entries have been checked. There can be only one extension field per * message. Returns false if no extension field is found. */ static bool checkreturn find_extension_field(pb_field_iter_t *iter) { const pb_field_t *start = iter->pos; do { if (PB_LTYPE(iter->pos->type) == PB_LTYPE_EXTENSION) return true; (void)pb_field_iter_next(iter); } while (iter->pos != start); return false; } /* Initialize message fields to default values, recursively */ static void pb_field_set_to_default(pb_field_iter_t *iter) { pb_type_t type; type = iter->pos->type; if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) { pb_extension_t *ext = *(pb_extension_t* const *)iter->pData; while (ext != NULL) { pb_field_iter_t ext_iter; ext->found = false; iter_from_extension(&ext_iter, ext); pb_field_set_to_default(&ext_iter); ext = ext->next; } } else if (PB_ATYPE(type) == PB_ATYPE_STATIC) { bool init_data = true; if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && iter->pSize != iter->pData) { /* Set has_field to false. Still initialize the optional field * itself also. */ *(bool*)iter->pSize = false; } else if (PB_HTYPE(type) == PB_HTYPE_REPEATED || PB_HTYPE(type) == PB_HTYPE_ONEOF) { /* REPEATED: Set array count to 0, no need to initialize contents. ONEOF: Set which_field to 0. */ *(pb_size_t*)iter->pSize = 0; init_data = false; } if (init_data) { if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE) { /* Initialize submessage to defaults */ pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, iter->pData); } else if (iter->pos->ptr != NULL) { /* Initialize to default value */ memcpy(iter->pData, iter->pos->ptr, iter->pos->data_size); } else { /* Initialize to zeros */ memset(iter->pData, 0, iter->pos->data_size); } } } else if (PB_ATYPE(type) == PB_ATYPE_POINTER) { /* Initialize the pointer to NULL. */ *(void**)iter->pData = NULL; /* Initialize array count to 0. */ if (PB_HTYPE(type) == PB_HTYPE_REPEATED || PB_HTYPE(type) == PB_HTYPE_ONEOF) { *(pb_size_t*)iter->pSize = 0; } } else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) { /* Don't overwrite callback */ } } static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct) { pb_field_iter_t iter; if (!pb_field_iter_begin(&iter, fields, dest_struct)) return; /* Empty message type */ do { pb_field_set_to_default(&iter); } while (pb_field_iter_next(&iter)); } /********************* * Decode all fields * *********************/ bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) { uint32_t fields_seen[(PB_MAX_REQUIRED_FIELDS + 31) / 32] = {0, 0}; const uint32_t allbits = ~(uint32_t)0; uint32_t extension_range_start = 0; pb_field_iter_t iter; /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed * count field. This can only handle _one_ repeated fixed count field that * is unpacked and unordered among other (non repeated fixed count) fields. */ const pb_field_t *fixed_count_field = NULL; pb_size_t fixed_count_size = 0; /* Return value ignored, as empty message types will be correctly handled by * pb_field_iter_find() anyway. */ (void)pb_field_iter_begin(&iter, fields, dest_struct); while (stream->bytes_left) { uint32_t tag; pb_wire_type_t wire_type; bool eof; if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) { if (eof) break; else return false; } if (!pb_field_iter_find(&iter, tag)) { /* No match found, check if it matches an extension. */ if (tag >= extension_range_start) { if (!find_extension_field(&iter)) extension_range_start = (uint32_t)-1; else extension_range_start = iter.pos->tag; if (tag >= extension_range_start) { size_t pos = stream->bytes_left; if (!decode_extension(stream, tag, wire_type, &iter)) return false; if (pos != stream->bytes_left) { /* The field was handled */ continue; } } } /* No match found, skip data */ if (!pb_skip_field(stream, wire_type)) return false; continue; } /* If a repeated fixed count field was found, get size from * 'fixed_count_field' as there is no counter contained in the struct. */ if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REPEATED && iter.pSize == iter.pData) { if (fixed_count_field != iter.pos) { /* If the new fixed count field does not match the previous one, * check that the previous one is NULL or that it finished * receiving all the expected data. */ if (fixed_count_field != NULL && fixed_count_size != fixed_count_field->array_size) { PB_RETURN_ERROR(stream, "wrong size for fixed count field"); } fixed_count_field = iter.pos; fixed_count_size = 0; } iter.pSize = &fixed_count_size; } if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REQUIRED && iter.required_field_index < PB_MAX_REQUIRED_FIELDS) { uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31)); fields_seen[iter.required_field_index >> 5] |= tmp; } if (!decode_field(stream, wire_type, &iter)) return false; } /* Check that all elements of the last decoded fixed count field were present. */ if (fixed_count_field != NULL && fixed_count_size != fixed_count_field->array_size) { PB_RETURN_ERROR(stream, "wrong size for fixed count field"); } /* Check that all required fields were present. */ { /* First figure out the number of required fields by * seeking to the end of the field array. Usually we * are already close to end after decoding. */ unsigned req_field_count; pb_type_t last_type; unsigned i; do { req_field_count = iter.required_field_index; last_type = iter.pos->type; } while (pb_field_iter_next(&iter)); /* Fixup if last field was also required. */ if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.pos->tag != 0) req_field_count++; if (req_field_count > PB_MAX_REQUIRED_FIELDS) req_field_count = PB_MAX_REQUIRED_FIELDS; if (req_field_count > 0) { /* Check the whole words */ for (i = 0; i < (req_field_count >> 5); i++) { if (fields_seen[i] != allbits) PB_RETURN_ERROR(stream, "missing required field"); } /* Check the remaining bits (if any) */ if ((req_field_count & 31) != 0) { if (fields_seen[req_field_count >> 5] != (allbits >> (32 - (req_field_count & 31)))) { PB_RETURN_ERROR(stream, "missing required field"); } } } } return true; } bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) { bool status; pb_message_set_to_defaults(fields, dest_struct); status = pb_decode_noinit(stream, fields, dest_struct); #ifdef PB_ENABLE_MALLOC if (!status) pb_release(fields, dest_struct); #endif return status; } bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) { pb_istream_t substream; bool status; if (!pb_make_string_substream(stream, &substream)) return false; status = pb_decode_noinit(&substream, fields, dest_struct); if (!pb_close_string_substream(stream, &substream)) return false; return status; } bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) { pb_istream_t substream; bool status; if (!pb_make_string_substream(stream, &substream)) return false; status = pb_decode(&substream, fields, dest_struct); if (!pb_close_string_substream(stream, &substream)) return false; return status; } bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct) { /* This behaviour will be separated in nanopb-0.4.0, see issue #278. */ return pb_decode(stream, fields, dest_struct); } #ifdef PB_ENABLE_MALLOC /* Given an oneof field, if there has already been a field inside this oneof, * release it before overwriting with a different one. */ static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter) { pb_size_t old_tag = *(pb_size_t*)iter->pSize; /* Previous which_ value */ pb_size_t new_tag = iter->pos->tag; /* New which_ value */ if (old_tag == 0) return true; /* Ok, no old data in union */ if (old_tag == new_tag) return true; /* Ok, old data is of same type => merge */ /* Release old data. The find can fail if the message struct contains * invalid data. */ if (!pb_field_iter_find(iter, old_tag)) PB_RETURN_ERROR(stream, "invalid union tag"); pb_release_single_field(iter); /* Restore iterator to where it should be. * This shouldn't fail unless the pb_field_t structure is corrupted. */ if (!pb_field_iter_find(iter, new_tag)) PB_RETURN_ERROR(stream, "iterator error"); if (PB_ATYPE(iter->pos->type) == PB_ATYPE_POINTER) { /* Initialize the pointer to NULL to make sure it is valid * even in case of error return. */ *(void**)iter->pData = NULL; } return true; } static void pb_release_single_field(const pb_field_iter_t *iter) { pb_type_t type; type = iter->pos->type; if (PB_HTYPE(type) == PB_HTYPE_ONEOF) { if (*(pb_size_t*)iter->pSize != iter->pos->tag) return; /* This is not the current field in the union */ } /* Release anything contained inside an extension or submsg. * This has to be done even if the submsg itself is statically * allocated. */ if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) { /* Release fields from all extensions in the linked list */ pb_extension_t *ext = *(pb_extension_t**)iter->pData; while (ext != NULL) { pb_field_iter_t ext_iter; iter_from_extension(&ext_iter, ext); pb_release_single_field(&ext_iter); ext = ext->next; } } else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && PB_ATYPE(type) != PB_ATYPE_CALLBACK) { /* Release fields in submessage or submsg array */ void *pItem = iter->pData; pb_size_t count = 1; if (PB_ATYPE(type) == PB_ATYPE_POINTER) { pItem = *(void**)iter->pData; } if (PB_HTYPE(type) == PB_HTYPE_REPEATED) { if (PB_ATYPE(type) == PB_ATYPE_STATIC && iter->pSize == iter->pData) { /* No _count field so use size of the array */ count = iter->pos->array_size; } else { count = *(pb_size_t*)iter->pSize; } if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > iter->pos->array_size) { /* Protect against corrupted _count fields */ count = iter->pos->array_size; } } if (pItem) { while (count--) { pb_release((const pb_field_t*)iter->pos->ptr, pItem); pItem = (char*)pItem + iter->pos->data_size; } } } if (PB_ATYPE(type) == PB_ATYPE_POINTER) { if (PB_HTYPE(type) == PB_HTYPE_REPEATED && (PB_LTYPE(type) == PB_LTYPE_STRING || PB_LTYPE(type) == PB_LTYPE_BYTES)) { /* Release entries in repeated string or bytes array */ void **pItem = *(void***)iter->pData; pb_size_t count = *(pb_size_t*)iter->pSize; while (count--) { pb_free(*pItem); *pItem++ = NULL; } } if (PB_HTYPE(type) == PB_HTYPE_REPEATED) { /* We are going to release the array, so set the size to 0 */ *(pb_size_t*)iter->pSize = 0; } /* Release main item */ pb_free(*(void**)iter->pData); *(void**)iter->pData = NULL; } } void pb_release(const pb_field_t fields[], void *dest_struct) { pb_field_iter_t iter; if (!dest_struct) return; /* Ignore NULL pointers, similar to free() */ if (!pb_field_iter_begin(&iter, fields, dest_struct)) return; /* Empty message type */ do { pb_release_single_field(&iter); } while (pb_field_iter_next(&iter)); } #endif /* Field decoders */ bool pb_decode_bool(pb_istream_t *stream, bool *dest) { return pb_dec_bool(stream, NULL, (void*)dest); } bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) { pb_uint64_t value; if (!pb_decode_varint(stream, &value)) return false; if (value & 1) *dest = (pb_int64_t)(~(value >> 1)); else *dest = (pb_int64_t)(value >> 1); return true; } bool pb_decode_fixed32(pb_istream_t *stream, void *dest) { pb_byte_t bytes[4]; if (!pb_read(stream, bytes, 4)) return false; *(uint32_t*)dest = ((uint32_t)bytes[0] << 0) | ((uint32_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24); return true; } #ifndef PB_WITHOUT_64BIT bool pb_decode_fixed64(pb_istream_t *stream, void *dest) { pb_byte_t bytes[8]; if (!pb_read(stream, bytes, 8)) return false; *(uint64_t*)dest = ((uint64_t)bytes[0] << 0) | ((uint64_t)bytes[1] << 8) | ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) | ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) | ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56); return true; } #endif static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest) { uint32_t value; PB_UNUSED(field); if (!pb_decode_varint32(stream, &value)) return false; *(bool*)dest = (value != 0); return true; } static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest) { pb_uint64_t value; pb_int64_t svalue; pb_int64_t clamped; if (!pb_decode_varint(stream, &value)) return false; /* See issue 97: Google's C++ protobuf allows negative varint values to * be cast as int32_t, instead of the int64_t that should be used when * encoding. Previous nanopb versions had a bug in encoding. In order to * not break decoding of such messages, we cast <=32 bit fields to * int32_t first to get the sign correct. */ if (field->data_size == sizeof(pb_int64_t)) svalue = (pb_int64_t)value; else svalue = (int32_t)value; /* Cast to the proper field size, while checking for overflows */ if (field->data_size == sizeof(pb_int64_t)) clamped = *(pb_int64_t*)dest = svalue; else if (field->data_size == sizeof(int32_t)) clamped = *(int32_t*)dest = (int32_t)svalue; else if (field->data_size == sizeof(int_least16_t)) clamped = *(int_least16_t*)dest = (int_least16_t)svalue; else if (field->data_size == sizeof(int_least8_t)) clamped = *(int_least8_t*)dest = (int_least8_t)svalue; else PB_RETURN_ERROR(stream, "invalid data_size"); if (clamped != svalue) PB_RETURN_ERROR(stream, "integer too large"); return true; } static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest) { pb_uint64_t value, clamped; if (!pb_decode_varint(stream, &value)) return false; /* Cast to the proper field size, while checking for overflows */ if (field->data_size == sizeof(pb_uint64_t)) clamped = *(pb_uint64_t*)dest = value; else if (field->data_size == sizeof(uint32_t)) clamped = *(uint32_t*)dest = (uint32_t)value; else if (field->data_size == sizeof(uint_least16_t)) clamped = *(uint_least16_t*)dest = (uint_least16_t)value; else if (field->data_size == sizeof(uint_least8_t)) clamped = *(uint_least8_t*)dest = (uint_least8_t)value; else PB_RETURN_ERROR(stream, "invalid data_size"); if (clamped != value) PB_RETURN_ERROR(stream, "integer too large"); return true; } static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest) { pb_int64_t value, clamped; if (!pb_decode_svarint(stream, &value)) return false; /* Cast to the proper field size, while checking for overflows */ if (field->data_size == sizeof(pb_int64_t)) clamped = *(pb_int64_t*)dest = value; else if (field->data_size == sizeof(int32_t)) clamped = *(int32_t*)dest = (int32_t)value; else if (field->data_size == sizeof(int_least16_t)) clamped = *(int_least16_t*)dest = (int_least16_t)value; else if (field->data_size == sizeof(int_least8_t)) clamped = *(int_least8_t*)dest = (int_least8_t)value; else PB_RETURN_ERROR(stream, "invalid data_size"); if (clamped != value) PB_RETURN_ERROR(stream, "integer too large"); return true; } static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest) { PB_UNUSED(field); return pb_decode_fixed32(stream, dest); } static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest) { PB_UNUSED(field); #ifndef PB_WITHOUT_64BIT return pb_decode_fixed64(stream, dest); #else PB_UNUSED(dest); PB_RETURN_ERROR(stream, "no 64bit support"); #endif } static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) { uint32_t size; size_t alloc_size; pb_bytes_array_t *bdest; if (!pb_decode_varint32(stream, &size)) return false; if (size > PB_SIZE_MAX) PB_RETURN_ERROR(stream, "bytes overflow"); alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size); if (size > alloc_size) PB_RETURN_ERROR(stream, "size too large"); if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) { #ifndef PB_ENABLE_MALLOC PB_RETURN_ERROR(stream, "no malloc support"); #else if (stream->bytes_left < size) PB_RETURN_ERROR(stream, "end-of-stream"); if (!allocate_field(stream, dest, alloc_size, 1)) return false; bdest = *(pb_bytes_array_t**)dest; #endif } else { if (alloc_size > field->data_size) PB_RETURN_ERROR(stream, "bytes overflow"); bdest = (pb_bytes_array_t*)dest; } bdest->size = (pb_size_t)size; return pb_read(stream, bdest->bytes, size); } static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest) { uint32_t size; size_t alloc_size; bool status; if (!pb_decode_varint32(stream, &size)) return false; /* Space for null terminator */ alloc_size = size + 1; if (alloc_size < size) PB_RETURN_ERROR(stream, "size too large"); if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) { #ifndef PB_ENABLE_MALLOC PB_RETURN_ERROR(stream, "no malloc support"); #else if (stream->bytes_left < size) PB_RETURN_ERROR(stream, "end-of-stream"); if (!allocate_field(stream, dest, alloc_size, 1)) return false; dest = *(void**)dest; #endif } else { if (alloc_size > field->data_size) PB_RETURN_ERROR(stream, "string overflow"); } status = pb_read(stream, (pb_byte_t*)dest, size); *((pb_byte_t*)dest + size) = 0; return status; } static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest) { bool status; pb_istream_t substream; const pb_field_t* submsg_fields = (const pb_field_t*)field->ptr; if (!pb_make_string_substream(stream, &substream)) return false; if (field->ptr == NULL) PB_RETURN_ERROR(stream, "invalid field descriptor"); /* New array entries need to be initialized, while required and optional * submessages have already been initialized in the top-level pb_decode. */ if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) status = pb_decode(&substream, submsg_fields, dest); else status = pb_decode_noinit(&substream, submsg_fields, dest); if (!pb_close_string_substream(stream, &substream)) return false; return status; } static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest) { uint32_t size; if (!pb_decode_varint32(stream, &size)) return false; if (size > PB_SIZE_MAX) PB_RETURN_ERROR(stream, "bytes overflow"); if (size == 0) { /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */ memset(dest, 0, field->data_size); return true; } if (size != field->data_size) PB_RETURN_ERROR(stream, "incorrect fixed length bytes size"); return pb_read(stream, (pb_byte_t*)dest, field->data_size); } ================================================ FILE: Pods/nanopb/pb_decode.h ================================================ /* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. * The main function is pb_decode. You also need an input stream, and the * field descriptions created by nanopb_generator.py. */ #ifndef PB_DECODE_H_INCLUDED #define PB_DECODE_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Structure for defining custom input streams. You will need to provide * a callback function to read the bytes from your storage, which can be * for example a file or a network socket. * * The callback must conform to these rules: * * 1) Return false on IO errors. This will cause decoding to abort. * 2) You can use state to store your own data (e.g. buffer pointer), * and rely on pb_read to verify that no-body reads past bytes_left. * 3) Your callback may be used with substreams, in which case bytes_left * is different than from the main stream. Don't use bytes_left to compute * any pointers. */ struct pb_istream_s { #ifdef PB_BUFFER_ONLY /* Callback pointer is not used in buffer-only configuration. * Having an int pointer here allows binary compatibility but * gives an error if someone tries to assign callback function. */ int *callback; #else bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count); #endif void *state; /* Free field for use by callback implementation */ size_t bytes_left; #ifndef PB_NO_ERRMSG const char *errmsg; #endif }; /*************************** * Main decoding functions * ***************************/ /* Decode a single protocol buffers message from input stream into a C structure. * Returns true on success, false on any failure. * The actual struct pointed to by dest must match the description in fields. * Callback fields of the destination structure must be initialized by caller. * All other fields will be initialized by this function. * * Example usage: * MyMessage msg = {}; * uint8_t buffer[64]; * pb_istream_t stream; * * // ... read some data into buffer ... * * stream = pb_istream_from_buffer(buffer, count); * pb_decode(&stream, MyMessage_fields, &msg); */ bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except does not initialize the destination structure * to default values. This is slightly faster if you need no default values * and just do memset(struct, 0, sizeof(struct)) yourself. * * This can also be used for 'merging' two messages, i.e. update only the * fields that exist in the new message. * * Note: If this function returns with an error, it will not release any * dynamically allocated fields. You will need to call pb_release() yourself. */ bool pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except expects the stream to start with the message size * encoded as varint. Corresponds to parseDelimitedFrom() in Google's * protobuf API. */ bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode_delimited, except that it does not initialize the destination structure. * See pb_decode_noinit */ bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); /* Same as pb_decode, except allows the message to be terminated with a null byte. * NOTE: Until nanopb-0.4.0, pb_decode() also allows null-termination. This behaviour * is not supported in most other protobuf implementations, so pb_decode_delimited() * is a better option for compatibility. */ bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct); #ifdef PB_ENABLE_MALLOC /* Release any allocated pointer fields. If you use dynamic allocation, you should * call this for any successfully decoded message when you are done with it. If * pb_decode() returns with an error, the message is already released. */ void pb_release(const pb_field_t fields[], void *dest_struct); #endif /************************************** * Functions for manipulating streams * **************************************/ /* Create an input stream for reading from a memory buffer. * * Alternatively, you can use a custom stream that reads directly from e.g. * a file or a network socket. */ pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize); /* Function to read from a pb_istream_t. You can use this if you need to * read some custom header data, or to read data in field callbacks. */ bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); /************************************************ * Helper functions for writing field callbacks * ************************************************/ /* Decode the tag for the next field in the stream. Gives the wire type and * field tag. At end of the message, returns false and sets eof to true. */ bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof); /* Skip the field payload data, given the wire type. */ bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); /* Decode an integer in the varint format. This works for enum, int32, * int64, uint32 and uint64 field types. */ #ifndef PB_WITHOUT_64BIT bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); #else #define pb_decode_varint pb_decode_varint32 #endif /* Decode an integer in the varint format. This works for enum, int32, * and uint32 field types. */ bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); /* Decode a bool value in varint format. */ bool pb_decode_bool(pb_istream_t *stream, bool *dest); /* Decode an integer in the zig-zagged svarint format. This works for sint32 * and sint64. */ #ifndef PB_WITHOUT_64BIT bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); #else bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest); #endif /* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to * a 4-byte wide C variable. */ bool pb_decode_fixed32(pb_istream_t *stream, void *dest); #ifndef PB_WITHOUT_64BIT /* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to * a 8-byte wide C variable. */ bool pb_decode_fixed64(pb_istream_t *stream, void *dest); #endif /* Make a limited-length substream for reading a PB_WT_STRING field. */ bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream); bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: Pods/nanopb/pb_encode.c ================================================ /* pb_encode.c -- encode a protobuf using minimal resources * * 2011 Petteri Aimonen */ #include "pb.h" #include "pb_encode.h" #include "pb_common.h" /* Use the GCC warn_unused_result attribute to check that all return values * are propagated correctly. On other compilers and gcc before 3.4.0 just * ignore the annotation. */ #if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) #define checkreturn #else #define checkreturn __attribute__((warn_unused_result)) #endif /************************************** * Declarations internal to this file * **************************************/ typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src) checkreturn; static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count, pb_encoder_t func); static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData); static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData); static void *pb_const_cast(const void *p); static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src); #ifdef PB_WITHOUT_64BIT #define pb_int64_t int32_t #define pb_uint64_t uint32_t static bool checkreturn pb_encode_negative_varint(pb_ostream_t *stream, pb_uint64_t value); #else #define pb_int64_t int64_t #define pb_uint64_t uint64_t #endif /* --- Function pointers to field encoders --- * Order in the array must match pb_action_t LTYPE numbering. */ static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = { &pb_enc_bool, &pb_enc_varint, &pb_enc_uvarint, &pb_enc_svarint, &pb_enc_fixed32, &pb_enc_fixed64, &pb_enc_bytes, &pb_enc_string, &pb_enc_submessage, NULL, /* extensions */ &pb_enc_fixed_length_bytes }; /******************************* * pb_ostream_t implementation * *******************************/ static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) { size_t i; pb_byte_t *dest = (pb_byte_t*)stream->state; stream->state = dest + count; for (i = 0; i < count; i++) dest[i] = buf[i]; return true; } pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) { pb_ostream_t stream; #ifdef PB_BUFFER_ONLY stream.callback = (void*)1; /* Just a marker value */ #else stream.callback = &buf_write; #endif stream.state = buf; stream.max_size = bufsize; stream.bytes_written = 0; #ifndef PB_NO_ERRMSG stream.errmsg = NULL; #endif return stream; } bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) { if (count > 0 && stream->callback != NULL) { if (stream->bytes_written + count < stream->bytes_written || stream->bytes_written + count > stream->max_size) { PB_RETURN_ERROR(stream, "stream full"); } #ifdef PB_BUFFER_ONLY if (!buf_write(stream, buf, count)) PB_RETURN_ERROR(stream, "io error"); #else if (!stream->callback(stream, buf, count)) PB_RETURN_ERROR(stream, "io error"); #endif } stream->bytes_written += count; return true; } /************************* * Encode a single field * *************************/ /* Read a bool value without causing undefined behavior even if the value * is invalid. See issue #434 and * https://stackoverflow.com/questions/27661768/weird-results-for-conditional */ static bool safe_read_bool(const void *pSize) { const char *p = (const char *)pSize; size_t i; for (i = 0; i < sizeof(bool); i++) { if (p[i] != 0) return true; } return false; } /* Encode a static array. Handles the size calculations and possible packing. */ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count, pb_encoder_t func) { size_t i; const void *p; #ifndef PB_ENCODE_ARRAYS_UNPACKED size_t size; #endif if (count == 0) return true; if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) PB_RETURN_ERROR(stream, "array max size exceeded"); #ifndef PB_ENCODE_ARRAYS_UNPACKED /* We always pack arrays if the datatype allows it. */ if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) { if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) return false; /* Determine the total size of packed array. */ if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) { size = 4 * count; } else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) { size = 8 * count; } else { pb_ostream_t sizestream = PB_OSTREAM_SIZING; p = pData; for (i = 0; i < count; i++) { if (!func(&sizestream, field, p)) return false; p = (const char*)p + field->data_size; } size = sizestream.bytes_written; } if (!pb_encode_varint(stream, (pb_uint64_t)size)) return false; if (stream->callback == NULL) return pb_write(stream, NULL, size); /* Just sizing.. */ /* Write the data */ p = pData; for (i = 0; i < count; i++) { if (!func(stream, field, p)) return false; p = (const char*)p + field->data_size; } } else #endif { p = pData; for (i = 0; i < count; i++) { if (!pb_encode_tag_for_field(stream, field)) return false; /* Normally the data is stored directly in the array entries, but * for pointer-type string and bytes fields, the array entries are * actually pointers themselves also. So we have to dereference once * more to get to the actual data. */ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && (PB_LTYPE(field->type) == PB_LTYPE_STRING || PB_LTYPE(field->type) == PB_LTYPE_BYTES)) { if (!func(stream, field, *(const void* const*)p)) return false; } else { if (!func(stream, field, p)) return false; } p = (const char*)p + field->data_size; } } return true; } /* In proto3, all fields are optional and are only encoded if their value is "non-zero". * This function implements the check for the zero value. */ static bool pb_check_proto3_default_value(const pb_field_t *field, const void *pData) { pb_type_t type = field->type; const void *pSize = (const char*)pData + field->size_offset; if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) { /* Required proto2 fields inside proto3 submessage, pretty rare case */ return false; } else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) { /* Repeated fields inside proto3 submessage: present if count != 0 */ if (field->size_offset != 0) return *(const pb_size_t*)pSize == 0; else if (PB_ATYPE(type) == PB_ATYPE_STATIC) return false; /* Fixed length array */ } else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) { /* Oneof fields */ return *(const pb_size_t*)pSize == 0; } else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->size_offset != 0) { /* Proto2 optional fields inside proto3 submessage */ return safe_read_bool(pSize) == false; } /* Rest is proto3 singular fields */ if (PB_ATYPE(type) == PB_ATYPE_STATIC) { if (PB_LTYPE(type) == PB_LTYPE_BYTES) { const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)pData; return bytes->size == 0; } else if (PB_LTYPE(type) == PB_LTYPE_STRING) { return *(const char*)pData == '\0'; } else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) { /* Fixed length bytes is only empty if its length is fixed * as 0. Which would be pretty strange, but we can check * it anyway. */ return field->data_size == 0; } else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE) { /* Check all fields in the submessage to find if any of them * are non-zero. The comparison cannot be done byte-per-byte * because the C struct may contain padding bytes that must * be skipped. */ pb_field_iter_t iter; if (pb_field_iter_begin(&iter, (const pb_field_t*)field->ptr, pb_const_cast(pData))) { do { if (!pb_check_proto3_default_value(iter.pos, iter.pData)) { return false; } } while (pb_field_iter_next(&iter)); } return true; } } /* Compares pointers to NULL in case of FT_POINTER */ if (PB_ATYPE(type) == PB_ATYPE_POINTER && PB_LTYPE(type) > PB_LTYPE_LAST_PACKABLE) { return !*(const void**)((uintptr_t)pData); } { /* Catch-all branch that does byte-per-byte comparison for zero value. * * This is for all pointer fields, and for static PB_LTYPE_VARINT, * UVARINT, SVARINT, FIXED32, FIXED64, EXTENSION fields, and also * callback fields. These all have integer or pointer value which * can be compared with 0. */ pb_size_t i; const char *p = (const char*)pData; for (i = 0; i < field->data_size; i++) { if (p[i] != 0) { return false; } } return true; } } /* Encode a field with static or pointer allocation, i.e. one whose data * is available to the encoder directly. */ static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData) { pb_encoder_t func; bool implicit_has; const void *pSize = &implicit_has; func = PB_ENCODERS[PB_LTYPE(field->type)]; if (field->size_offset) { /* Static optional, repeated or oneof field */ pSize = (const char*)pData + field->size_offset; } else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) { /* Proto3 style field, optional but without explicit has_ field. */ implicit_has = !pb_check_proto3_default_value(field, pData); } else { /* Required field, always present */ implicit_has = true; } if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) { /* pData is a pointer to the field, which contains pointer to * the data. If the 2nd pointer is NULL, it is interpreted as if * the has_field was false. */ pData = *(const void* const*)pData; implicit_has = (pData != NULL); } switch (PB_HTYPE(field->type)) { case PB_HTYPE_REQUIRED: if (!pData) PB_RETURN_ERROR(stream, "missing required field"); if (!pb_encode_tag_for_field(stream, field)) return false; if (!func(stream, field, pData)) return false; break; case PB_HTYPE_OPTIONAL: if (safe_read_bool(pSize)) { if (!pb_encode_tag_for_field(stream, field)) return false; if (!func(stream, field, pData)) return false; } break; case PB_HTYPE_REPEATED: { pb_size_t count; if (field->size_offset != 0) { count = *(const pb_size_t*)pSize; } else { count = field->array_size; } if (!encode_array(stream, field, pData, count, func)) return false; break; } case PB_HTYPE_ONEOF: if (*(const pb_size_t*)pSize == field->tag) { if (!pb_encode_tag_for_field(stream, field)) return false; if (!func(stream, field, pData)) return false; } break; default: PB_RETURN_ERROR(stream, "invalid field type"); } return true; } /* Encode a field with callback semantics. This means that a user function is * called to provide and encode the actual data. */ static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData) { const pb_callback_t *callback = (const pb_callback_t*)pData; #ifdef PB_OLD_CALLBACK_STYLE const void *arg = callback->arg; #else void * const *arg = &(callback->arg); #endif if (callback->funcs.encode != NULL) { if (!callback->funcs.encode(stream, field, arg)) PB_RETURN_ERROR(stream, "callback error"); } return true; } /* Encode a single field of any callback or static type. */ static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData) { switch (PB_ATYPE(field->type)) { case PB_ATYPE_STATIC: case PB_ATYPE_POINTER: return encode_basic_field(stream, field, pData); case PB_ATYPE_CALLBACK: return encode_callback_field(stream, field, pData); default: PB_RETURN_ERROR(stream, "invalid field type"); } } /* Default handler for extension fields. Expects to have a pb_field_t * pointer in the extension->type->arg field. */ static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension) { const pb_field_t *field = (const pb_field_t*)extension->type->arg; if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) { /* For pointer extensions, the pointer is stored directly * in the extension structure. This avoids having an extra * indirection. */ return encode_field(stream, field, &extension->dest); } else { return encode_field(stream, field, extension->dest); } } /* Walk through all the registered extensions and give them a chance * to encode themselves. */ static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData) { const pb_extension_t *extension = *(const pb_extension_t* const *)pData; PB_UNUSED(field); while (extension) { bool status; if (extension->type->encode) status = extension->type->encode(stream, extension); else status = default_extension_encoder(stream, extension); if (!status) return false; extension = extension->next; } return true; } /********************* * Encode all fields * *********************/ static void *pb_const_cast(const void *p) { /* Note: this casts away const, in order to use the common field iterator * logic for both encoding and decoding. */ union { void *p1; const void *p2; } t; t.p2 = p; return t.p1; } bool checkreturn pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) { pb_field_iter_t iter; if (!pb_field_iter_begin(&iter, fields, pb_const_cast(src_struct))) return true; /* Empty message type */ do { if (PB_LTYPE(iter.pos->type) == PB_LTYPE_EXTENSION) { /* Special case for the extension field placeholder */ if (!encode_extension_field(stream, iter.pos, iter.pData)) return false; } else { /* Regular field */ if (!encode_field(stream, iter.pos, iter.pData)) return false; } } while (pb_field_iter_next(&iter)); return true; } bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) { return pb_encode_submessage(stream, fields, src_struct); } bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) { const pb_byte_t zero = 0; if (!pb_encode(stream, fields, src_struct)) return false; return pb_write(stream, &zero, 1); } bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct) { pb_ostream_t stream = PB_OSTREAM_SIZING; if (!pb_encode(&stream, fields, src_struct)) return false; *size = stream.bytes_written; return true; } /******************** * Helper functions * ********************/ #ifdef PB_WITHOUT_64BIT bool checkreturn pb_encode_negative_varint(pb_ostream_t *stream, pb_uint64_t value) { pb_byte_t buffer[10]; size_t i = 0; size_t compensation = 32;/* we need to compensate 32 bits all set to 1 */ while (value) { buffer[i] = (pb_byte_t)((value & 0x7F) | 0x80); value >>= 7; if (compensation) { /* re-set all the compensation bits we can or need */ size_t bits = compensation > 7 ? 7 : compensation; value ^= (pb_uint64_t)((0xFFu >> (8 - bits)) << 25); /* set the number of bits needed on the lowest of the most significant 7 bits */ compensation -= bits; } i++; } buffer[i - 1] &= 0x7F; /* Unset top bit on last byte */ return pb_write(stream, buffer, i); } #endif bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) { pb_byte_t buffer[10]; size_t i = 0; if (value <= 0x7F) { pb_byte_t v = (pb_byte_t)value; return pb_write(stream, &v, 1); } while (value) { buffer[i] = (pb_byte_t)((value & 0x7F) | 0x80); value >>= 7; i++; } buffer[i-1] &= 0x7F; /* Unset top bit on last byte */ return pb_write(stream, buffer, i); } bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) { pb_uint64_t zigzagged; if (value < 0) zigzagged = ~((pb_uint64_t)value << 1); else zigzagged = (pb_uint64_t)value << 1; return pb_encode_varint(stream, zigzagged); } bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) { uint32_t val = *(const uint32_t*)value; pb_byte_t bytes[4]; bytes[0] = (pb_byte_t)(val & 0xFF); bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); return pb_write(stream, bytes, 4); } #ifndef PB_WITHOUT_64BIT bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) { uint64_t val = *(const uint64_t*)value; pb_byte_t bytes[8]; bytes[0] = (pb_byte_t)(val & 0xFF); bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); return pb_write(stream, bytes, 8); } #endif bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) { pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; return pb_encode_varint(stream, tag); } bool checkreturn pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field) { pb_wire_type_t wiretype; switch (PB_LTYPE(field->type)) { case PB_LTYPE_BOOL: case PB_LTYPE_VARINT: case PB_LTYPE_UVARINT: case PB_LTYPE_SVARINT: wiretype = PB_WT_VARINT; break; case PB_LTYPE_FIXED32: wiretype = PB_WT_32BIT; break; case PB_LTYPE_FIXED64: wiretype = PB_WT_64BIT; break; case PB_LTYPE_BYTES: case PB_LTYPE_STRING: case PB_LTYPE_SUBMESSAGE: case PB_LTYPE_FIXED_LENGTH_BYTES: wiretype = PB_WT_STRING; break; default: PB_RETURN_ERROR(stream, "invalid field type"); } return pb_encode_tag(stream, wiretype, field->tag); } bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) { if (!pb_encode_varint(stream, (pb_uint64_t)size)) return false; return pb_write(stream, buffer, size); } bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct) { /* First calculate the message size using a non-writing substream. */ pb_ostream_t substream = PB_OSTREAM_SIZING; size_t size; bool status; if (!pb_encode(&substream, fields, src_struct)) { #ifndef PB_NO_ERRMSG stream->errmsg = substream.errmsg; #endif return false; } size = substream.bytes_written; if (!pb_encode_varint(stream, (pb_uint64_t)size)) return false; if (stream->callback == NULL) return pb_write(stream, NULL, size); /* Just sizing */ if (stream->bytes_written + size > stream->max_size) PB_RETURN_ERROR(stream, "stream full"); /* Use a substream to verify that a callback doesn't write more than * what it did the first time. */ substream.callback = stream->callback; substream.state = stream->state; substream.max_size = size; substream.bytes_written = 0; #ifndef PB_NO_ERRMSG substream.errmsg = NULL; #endif status = pb_encode(&substream, fields, src_struct); stream->bytes_written += substream.bytes_written; stream->state = substream.state; #ifndef PB_NO_ERRMSG stream->errmsg = substream.errmsg; #endif if (substream.bytes_written != size) PB_RETURN_ERROR(stream, "submsg size changed"); return status; } /* Field encoders */ static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_t *field, const void *src) { uint32_t value = safe_read_bool(src) ? 1 : 0; PB_UNUSED(field); return pb_encode_varint(stream, value); } static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src) { pb_int64_t value = 0; if (field->data_size == sizeof(int_least8_t)) value = *(const int_least8_t*)src; else if (field->data_size == sizeof(int_least16_t)) value = *(const int_least16_t*)src; else if (field->data_size == sizeof(int32_t)) value = *(const int32_t*)src; else if (field->data_size == sizeof(pb_int64_t)) value = *(const pb_int64_t*)src; else PB_RETURN_ERROR(stream, "invalid data_size"); #ifdef PB_WITHOUT_64BIT if (value < 0) return pb_encode_negative_varint(stream, (pb_uint64_t)value); else #endif return pb_encode_varint(stream, (pb_uint64_t)value); } static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src) { pb_uint64_t value = 0; if (field->data_size == sizeof(uint_least8_t)) value = *(const uint_least8_t*)src; else if (field->data_size == sizeof(uint_least16_t)) value = *(const uint_least16_t*)src; else if (field->data_size == sizeof(uint32_t)) value = *(const uint32_t*)src; else if (field->data_size == sizeof(pb_uint64_t)) value = *(const pb_uint64_t*)src; else PB_RETURN_ERROR(stream, "invalid data_size"); return pb_encode_varint(stream, value); } static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src) { pb_int64_t value = 0; if (field->data_size == sizeof(int_least8_t)) value = *(const int_least8_t*)src; else if (field->data_size == sizeof(int_least16_t)) value = *(const int_least16_t*)src; else if (field->data_size == sizeof(int32_t)) value = *(const int32_t*)src; else if (field->data_size == sizeof(pb_int64_t)) value = *(const pb_int64_t*)src; else PB_RETURN_ERROR(stream, "invalid data_size"); return pb_encode_svarint(stream, value); } static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src) { PB_UNUSED(field); #ifndef PB_WITHOUT_64BIT return pb_encode_fixed64(stream, src); #else PB_UNUSED(src); PB_RETURN_ERROR(stream, "no 64bit support"); #endif } static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src) { PB_UNUSED(field); return pb_encode_fixed32(stream, src); } static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src) { const pb_bytes_array_t *bytes = NULL; size_t allocsize; bytes = (const pb_bytes_array_t*)src; if (src == NULL) { /* Treat null pointer as an empty bytes field */ return pb_encode_string(stream, NULL, 0); } allocsize = PB_BYTES_ARRAY_T_ALLOCSIZE(bytes->size); if (allocsize < bytes->size || (PB_ATYPE(field->type) == PB_ATYPE_STATIC && allocsize > field->data_size)) { PB_RETURN_ERROR(stream, "bytes size exceeded"); } return pb_encode_string(stream, bytes->bytes, bytes->size); } static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src) { size_t size = 0; size_t max_size = field->data_size; const char *p = (const char*)src; if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) max_size = (size_t)-1; if (src == NULL) { size = 0; /* Treat null pointer as an empty string */ } else { /* strnlen() is not always available, so just use a loop */ while (size < max_size && *p != '\0') { size++; p++; } } return pb_encode_string(stream, (const pb_byte_t*)src, size); } static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src) { if (field->ptr == NULL) PB_RETURN_ERROR(stream, "invalid field descriptor"); return pb_encode_submessage(stream, (const pb_field_t*)field->ptr, src); } static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src) { return pb_encode_string(stream, (const pb_byte_t*)src, field->data_size); } ================================================ FILE: Pods/nanopb/pb_encode.h ================================================ /* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. * The main function is pb_encode. You also need an output stream, and the * field descriptions created by nanopb_generator.py. */ #ifndef PB_ENCODE_H_INCLUDED #define PB_ENCODE_H_INCLUDED #include "pb.h" #ifdef __cplusplus extern "C" { #endif /* Structure for defining custom output streams. You will need to provide * a callback function to write the bytes to your storage, which can be * for example a file or a network socket. * * The callback must conform to these rules: * * 1) Return false on IO errors. This will cause encoding to abort. * 2) You can use state to store your own data (e.g. buffer pointer). * 3) pb_write will update bytes_written after your callback runs. * 4) Substreams will modify max_size and bytes_written. Don't use them * to calculate any pointers. */ struct pb_ostream_s { #ifdef PB_BUFFER_ONLY /* Callback pointer is not used in buffer-only configuration. * Having an int pointer here allows binary compatibility but * gives an error if someone tries to assign callback function. * Also, NULL pointer marks a 'sizing stream' that does not * write anything. */ int *callback; #else bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); #endif void *state; /* Free field for use by callback implementation. */ size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */ size_t bytes_written; /* Number of bytes written so far. */ #ifndef PB_NO_ERRMSG const char *errmsg; #endif }; /*************************** * Main encoding functions * ***************************/ /* Encode a single protocol buffers message from C structure into a stream. * Returns true on success, false on any failure. * The actual struct pointed to by src_struct must match the description in fields. * All required fields in the struct are assumed to have been filled in. * * Example usage: * MyMessage msg = {}; * uint8_t buffer[64]; * pb_ostream_t stream; * * msg.field1 = 42; * stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); * pb_encode(&stream, MyMessage_fields, &msg); */ bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Same as pb_encode, but prepends the length of the message as a varint. * Corresponds to writeDelimitedTo() in Google's protobuf API. */ bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Same as pb_encode, but appends a null byte to the message for termination. * NOTE: This behaviour is not supported in most other protobuf implementations, so pb_encode_delimited() * is a better option for compatibility. */ bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); /* Encode the message to get the size of the encoded data, but do not store * the data. */ bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct); /************************************** * Functions for manipulating streams * **************************************/ /* Create an output stream for writing into a memory buffer. * The number of bytes written can be found in stream.bytes_written after * encoding the message. * * Alternatively, you can use a custom stream that writes directly to e.g. * a file or a network socket. */ pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); /* Pseudo-stream for measuring the size of a message without actually storing * the encoded data. * * Example usage: * MyMessage msg = {}; * pb_ostream_t stream = PB_OSTREAM_SIZING; * pb_encode(&stream, MyMessage_fields, &msg); * printf("Message size is %d\n", stream.bytes_written); */ #ifndef PB_NO_ERRMSG #define PB_OSTREAM_SIZING {0,0,0,0,0} #else #define PB_OSTREAM_SIZING {0,0,0,0} #endif /* Function to write into a pb_ostream_t stream. You can use this if you need * to append or prepend some custom headers to the message. */ bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); /************************************************ * Helper functions for writing field callbacks * ************************************************/ /* Encode field header based on type and field number defined in the field * structure. Call this from the callback before writing out field contents. */ bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field); /* Encode field header by manually specifying wire type. You need to use this * if you want to write out packed arrays from a callback field. */ bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); /* Encode an integer in the varint format. * This works for bool, enum, int32, int64, uint32 and uint64 field types. */ #ifndef PB_WITHOUT_64BIT bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); #else bool pb_encode_varint(pb_ostream_t *stream, uint32_t value); #endif /* Encode an integer in the zig-zagged svarint format. * This works for sint32 and sint64. */ #ifndef PB_WITHOUT_64BIT bool pb_encode_svarint(pb_ostream_t *stream, int64_t value); #else bool pb_encode_svarint(pb_ostream_t *stream, int32_t value); #endif /* Encode a string or bytes type field. For strings, pass strlen(s) as size. */ bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); /* Encode a fixed32, sfixed32 or float value. * You need to pass a pointer to a 4-byte wide C variable. */ bool pb_encode_fixed32(pb_ostream_t *stream, const void *value); #ifndef PB_WITHOUT_64BIT /* Encode a fixed64, sfixed64 or double value. * You need to pass a pointer to a 8-byte wide C variable. */ bool pb_encode_fixed64(pb_ostream_t *stream, const void *value); #endif /* Encode a submessage field. * You need to pass the pb_field_t array and pointer to struct, just like * with pb_encode(). This internally encodes the submessage twice, first to * calculate message size and then to actually write it out. */ bool pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: README.md ================================================ # Spotify Client (iOS - Swift 5 - 2021) Full featured Spotify like app written in Swift 5 with MVVM architecture. ![Spotify Client iOS Academy](https://raw.githubusercontent.com/AfrazCodes/Spotify-iOS/master/screenshots.png) ## Features - Official Spotify API Use - Playlists, Playlist Creation, - Browse & Recommended - Search Songs, Albums, Artists, More - Playback and Playlists Playback - Save Albums - Sign In/Sign Out (OAUTH 2.0) - View Your Profile - Browse Categories - Categorical Playlists ## Notes This code is free to use. Note that I have removed the Client Id and Client Secret from the AuthManager that I originally developed this with. You can obtain your own credentials from the Spotify Developer website. ================================================ FILE: Spotify/Controllers/Core/HomeViewController.swift ================================================ // // ViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit enum BrowseSectionType { case newReleases(viewModels: [NewReleasesCellViewModel]) // 1 case featuredPlaylists(viewModels: [FeaturedPlaylistCellViewModel]) // 2 case recommendedTracks(viewModels: [RecommendedTrackCellViewModel]) // 3 var title: String { switch self { case .newReleases: return "New Released Albums" case .featuredPlaylists: return "Featured Playlists" case .recommendedTracks: return "Recommended" } } } class HomeViewController: UIViewController { private var newAlbums: [Album] = [] private var playlists: [Playlist] = [] private var tracks: [AudioTrack] = [] private var collectionView: UICollectionView = UICollectionView( frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout { sectionIndex, _ -> NSCollectionLayoutSection? in return HomeViewController.createSectionLayout(section: sectionIndex) } ) private let spinner: UIActivityIndicatorView = { let spinner = UIActivityIndicatorView() spinner.tintColor = .label spinner.hidesWhenStopped = true return spinner }() private var sections = [BrowseSectionType]() override func viewDidLoad() { super.viewDidLoad() title = "Browse" view.backgroundColor = .systemBackground navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "gear"), style: .done, target: self, action: #selector(didTapSettings) ) configureCollectionView() view.addSubview(spinner) fetchData() addLongTapGesture() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } private func addLongTapGesture() { let gesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:))) collectionView.isUserInteractionEnabled = true collectionView.addGestureRecognizer(gesture) } @objc func didLongPress(_ gesture: UILongPressGestureRecognizer) { guard gesture.state == .began else { return } let touchPoint = gesture.location(in: collectionView) print("point: \(touchPoint)") guard let indexPath = collectionView.indexPathForItem(at: touchPoint), indexPath.section == 2 else { return } let model = tracks[indexPath.row] let actionSheet = UIAlertController( title: model.name, message: "Would you like to add this to a playlist?", preferredStyle: .actionSheet ) actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) actionSheet.addAction(UIAlertAction(title: "Add to Playlist", style: .default, handler: { [weak self] _ in DispatchQueue.main.async { let vc = LibraryPlaylistsViewController() vc.selectionHandler = { playlist in APICaller.shared.addTrackToPlaylist( track: model, playlist: playlist ) { success in print("Added to playlist success: \(success)") } } vc.title = "Select Playlist" self?.present(UINavigationController(rootViewController: vc), animated: true, completion: nil) } })) present(actionSheet, animated: true) } private func configureCollectionView() { view.addSubview(collectionView) collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") collectionView.register(NewReleaseCollectionViewCell.self, forCellWithReuseIdentifier: NewReleaseCollectionViewCell.identifier) collectionView.register(FeaturedPlaylistCollectionViewCell.self, forCellWithReuseIdentifier: FeaturedPlaylistCollectionViewCell.identifier) collectionView.register(RecommendedTrackCollectionViewCell.self, forCellWithReuseIdentifier: RecommendedTrackCollectionViewCell.identifier) collectionView.register( TitleHeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: TitleHeaderCollectionReusableView.identifier ) collectionView.dataSource = self collectionView.delegate = self collectionView.backgroundColor = .systemBackground } private func fetchData() { let group = DispatchGroup() group.enter() group.enter() group.enter() var newReleases: NewReleasesResponse? var featuredPlaylist: FeaturedPlaylistsResponse? var recommendations: RecommendationsResponse? // New Releases APICaller.shared.getNewReleases { result in defer { group.leave() } switch result { case .success(let model): newReleases = model case .failure(let error): print(error.localizedDescription) } } // Featured Playlists APICaller.shared.getFeaturedFlaylists { result in defer { group.leave() } switch result { case .success(let model): featuredPlaylist = model case .failure(let error): print(error.localizedDescription) } } // Recommended Tracks APICaller.shared.gerRecommendedGenres { result in switch result { case .success(let model): let genres = model.genres var seeds = Set() while seeds.count < 5 { if let random = genres.randomElement() { seeds.insert(random) } } APICaller.shared.getRecommendations(genres: seeds) { recommendedResult in defer { group.leave() } switch recommendedResult { case .success(let model): recommendations = model case .failure(let error): print(error.localizedDescription) } } case .failure(let error): print(error.localizedDescription) } } group.notify(queue: .main) { guard let newAlbums = newReleases?.albums.items, let playlists = featuredPlaylist?.playlists.items, let tracks = recommendations?.tracks else { fatalError("Models are nil") } self.configureModels( newAlbums: newAlbums, playlists: playlists, tracks: tracks ) } } private func configureModels( newAlbums: [Album], playlists: [Playlist], tracks: [AudioTrack] ) { self.newAlbums = newAlbums self.playlists = playlists self.tracks = tracks sections.append(.newReleases(viewModels: newAlbums.compactMap({ return NewReleasesCellViewModel( name: $0.name, artworkURL: URL(string: $0.images.first?.url ?? ""), numberOfTracks: $0.total_tracks, artistName: $0.artists.first?.name ?? "-" ) }))) sections.append(.featuredPlaylists(viewModels: playlists.compactMap({ return FeaturedPlaylistCellViewModel( name: $0.name, artworkURL: URL(string: $0.images.first?.url ?? ""), creatorName: $0.owner.display_name ) }))) sections.append(.recommendedTracks(viewModels: tracks.compactMap({ return RecommendedTrackCellViewModel( name: $0.name, artistName: $0.artists.first?.name ?? "-", artworkURL: URL(string: $0.album?.images.first?.url ?? "") ) }))) collectionView.reloadData() } @objc func didTapSettings() { let vc = SettingsViewController() vc.title = "Settings" vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } } extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let type = sections[section] switch type { case .newReleases(let viewModels): return viewModels.count case .featuredPlaylists(let viewModels): return viewModels.count case .recommendedTracks(let viewModels): return viewModels.count } } func numberOfSections(in collectionView: UICollectionView) -> Int { return sections.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let type = sections[indexPath.section] switch type { case .newReleases(let viewModels): guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: NewReleaseCollectionViewCell.identifier, for: indexPath ) as? NewReleaseCollectionViewCell else { return UICollectionViewCell() } let viewModel = viewModels[indexPath.row] cell.configure(with: viewModel) return cell case .featuredPlaylists(let viewModels): guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: FeaturedPlaylistCollectionViewCell.identifier, for: indexPath ) as? FeaturedPlaylistCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: viewModels[indexPath.row]) return cell case .recommendedTracks(let viewModels): guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: RecommendedTrackCollectionViewCell.identifier, for: indexPath ) as? RecommendedTrackCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: viewModels[indexPath.row]) return cell } } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) HapticsManager.shared.vibrateForSelection() let section = sections[indexPath.section] switch section { case .featuredPlaylists: let playlist = playlists[indexPath.row] let vc = PlaylistViewController(playlist: playlist) vc.title = playlist.name vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) case .newReleases: let album = newAlbums[indexPath.row] let vc = AlbumViewController(album: album) vc.title = album.name vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) case .recommendedTracks: let track = tracks[indexPath.row] PlaybackPresenter.shared.startPlayback(from: self, track: track) } } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard let header = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: TitleHeaderCollectionReusableView.identifier, for: indexPath ) as? TitleHeaderCollectionReusableView, kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() } let section = indexPath.section let title = sections[section].title header.configure(with: title) return header } static func createSectionLayout(section: Int) -> NSCollectionLayoutSection { let supplementaryViews = [ NSCollectionLayoutBoundarySupplementaryItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(50) ), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top ) ] switch section { case 0: // Item let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) // Vertical group in horizontal group let verticalGroup = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(390) ), subitem: item, count: 3 ) let horizontalGroup = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(390) ), subitem: verticalGroup, count: 1 ) // Section let section = NSCollectionLayoutSection(group: horizontalGroup) section.orthogonalScrollingBehavior = .groupPaging section.boundarySupplementaryItems = supplementaryViews return section case 1: // Item let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .absolute(200), heightDimension: .absolute(200) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) let verticalGroup = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .absolute(200), heightDimension: .absolute(400) ), subitem: item, count: 2 ) let horizontalGroup = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize( widthDimension: .absolute(200), heightDimension: .absolute(400) ), subitem: verticalGroup, count: 1 ) // Section let section = NSCollectionLayoutSection(group: horizontalGroup) section.orthogonalScrollingBehavior = .continuous section.boundarySupplementaryItems = supplementaryViews return section case 2: // Item let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) let group = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(80) ), subitem: item, count: 1 ) let section = NSCollectionLayoutSection(group: group) section.boundarySupplementaryItems = supplementaryViews return section default: // Item let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) let group = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(390) ), subitem: item, count: 1 ) let section = NSCollectionLayoutSection(group: group) section.boundarySupplementaryItems = supplementaryViews return section } } } ================================================ FILE: Spotify/Controllers/Core/LibraryViewController.swift ================================================ // // LibraryViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class LibraryViewController: UIViewController { private let playlistsVC = LibraryPlaylistsViewController() private let albumsVC = LibraryAlbumsViewController() private let scrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.isPagingEnabled = true return scrollView }() private let toggleView = LibraryToggleView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground view.addSubview(toggleView) toggleView.delegate = self view.addSubview(scrollView) scrollView.contentSize = CGSize(width: view.width*2, height: scrollView.height) scrollView.delegate = self addChildren() updateBarButtons() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() scrollView.frame = CGRect( x: 0, y: view.safeAreaInsets.top+55, width: view.width, height: view.height-view.safeAreaInsets.top-view.safeAreaInsets.bottom-55 ) toggleView.frame = CGRect( x: 0, y: view.safeAreaInsets.top, width: 200, height: 55 ) } private func updateBarButtons() { switch toggleView.state { case .playlist: navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAdd)) case .album: navigationItem.rightBarButtonItem = nil } } @objc private func didTapAdd() { playlistsVC.showCreatePlaylistAlert() } private func addChildren() { addChild(playlistsVC) scrollView.addSubview(playlistsVC.view) playlistsVC.view.frame = CGRect(x: 0, y: 0, width: scrollView.width, height: scrollView.height) playlistsVC.didMove(toParent: self) addChild(albumsVC) scrollView.addSubview(albumsVC.view) albumsVC.view.frame = CGRect(x: view.width, y: 0, width: scrollView.width, height: scrollView.height) albumsVC.didMove(toParent: self) } } extension LibraryViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.contentOffset.x >= (view.width-100) { toggleView.update(for: .album) updateBarButtons() } else { toggleView.update(for: .playlist) updateBarButtons() } } } extension LibraryViewController: LibraryToggleViewDelegate { func libraryToggleViewDidTapPlaylists(_ toggleView: LibraryToggleView) { scrollView.setContentOffset(.zero, animated: true) updateBarButtons() } func libraryToggleViewDidTapAlbums(_ toggleView: LibraryToggleView) { scrollView.setContentOffset(CGPoint(x: view.width, y: 0), animated: true) updateBarButtons() } } ================================================ FILE: Spotify/Controllers/Core/SearchViewController.swift ================================================ // // SearchViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import SafariServices import UIKit class SearchViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate { let searchController: UISearchController = { let vc = UISearchController(searchResultsController: SearchResultsViewController()) vc.searchBar.placeholder = "Songs, Artists, Albums" vc.searchBar.searchBarStyle = .minimal vc.definesPresentationContext = true return vc }() private let collectionView: UICollectionView = UICollectionView( frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout(sectionProvider: { _, _ -> NSCollectionLayoutSection? in let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))) item.contentInsets = NSDirectionalEdgeInsets( top: 2, leading: 7, bottom: 2, trailing: 7 ) let group = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(150)), subitem: item, count: 2 ) group.contentInsets = NSDirectionalEdgeInsets( top: 10, leading: 0, bottom: 10, trailing: 0 ) return NSCollectionLayoutSection(group: group) }) ) private var categories = [Category]() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground searchController.searchResultsUpdater = self searchController.searchBar.delegate = self navigationItem.searchController = searchController view.addSubview(collectionView) collectionView.register(CategoryCollectionViewCell.self, forCellWithReuseIdentifier: CategoryCollectionViewCell.identifier) collectionView.delegate = self collectionView.dataSource = self collectionView.backgroundColor = .systemBackground APICaller.shared.getCategories { [weak self] result in DispatchQueue.main.async { switch result { case .success(let categories): self?.categories = categories self?.collectionView.reloadData() case .failure(let error): print(error.localizedDescription) } } } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { guard let resultsController = searchController.searchResultsController as? SearchResultsViewController, let query = searchBar.text, !query.trimmingCharacters(in: .whitespaces).isEmpty else { return } resultsController.delegate = self APICaller.shared.search(with: query) { result in DispatchQueue.main.async { switch result { case .success(let results): resultsController.update(with: results) case .failure(let error): print(error.localizedDescription) } } } } func updateSearchResults(for searchController: UISearchController) { } } extension SearchViewController: SearchResultsViewControllerDelegate { func didTapResult(_ result: SearchResult) { switch result { case .artist(let model): guard let url = URL(string: model.external_urls["spotify"] ?? "") else { return } let vc = SFSafariViewController(url: url) present(vc, animated: true) case .album(let model): let vc = AlbumViewController(album: model) vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) case .track(let model): PlaybackPresenter.shared.startPlayback( from: self, track: model ) case .playlist(let model): let vc = PlaylistViewController(playlist: model) vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } } } extension SearchViewController: UICollectionViewDelegate, UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return categories.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: CategoryCollectionViewCell.identifier, for: indexPath ) as? CategoryCollectionViewCell else { return UICollectionViewCell() } let category = categories[indexPath.row] cell.configure( with: CategoryCollectionViewCellViewModel( title: category.name, artworkURL: URL(string: category.icons.first?.url ?? "") ) ) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) HapticsManager.shared.vibrateForSelection() let category = categories[indexPath.row] let vc = CategoryViewController(category: category) vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } } ================================================ FILE: Spotify/Controllers/Core/TabBarViewController.swift ================================================ // // TabBarViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class TabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() let vc1 = HomeViewController() let vc2 = SearchViewController() let vc3 = LibraryViewController() vc1.title = "Browse" vc2.title = "Search" vc3.title = "Library" vc1.navigationItem.largeTitleDisplayMode = .always vc2.navigationItem.largeTitleDisplayMode = .always vc3.navigationItem.largeTitleDisplayMode = .always let nav1 = UINavigationController(rootViewController: vc1) let nav2 = UINavigationController(rootViewController: vc2) let nav3 = UINavigationController(rootViewController: vc3) nav1.navigationBar.tintColor = .label nav2.navigationBar.tintColor = .label nav3.navigationBar.tintColor = .label nav1.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 1) nav2.tabBarItem = UITabBarItem(title: "Search", image: UIImage(systemName: "magnifyingglass"), tag: 1) nav3.tabBarItem = UITabBarItem(title: "Library", image: UIImage(systemName: "music.note.list"), tag: 1) nav1.navigationBar.prefersLargeTitles = true nav2.navigationBar.prefersLargeTitles = true nav3.navigationBar.prefersLargeTitles = true setViewControllers([nav1, nav2, nav3], animated: false) } } ================================================ FILE: Spotify/Controllers/Other/AlbumViewController.swift ================================================ // // AlbumViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import UIKit class AlbumViewController: UIViewController { private let collectionView = UICollectionView( frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout(sectionProvider: { _, _ -> NSCollectionLayoutSection? in let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 1, leading: 2, bottom: 1, trailing: 2) let group = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(60) ), subitem: item, count: 1 ) let section = NSCollectionLayoutSection(group: group) section.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem( layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top ) ] return section }) ) private var viewModels = [AlbumCollectionViewCellViewModel]() private var tracks = [AudioTrack]() private let album: Album init(album: Album) { self.album = album super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError() } override func viewDidLoad() { super.viewDidLoad() title = album.name view.backgroundColor = .systemBackground view.addSubview(collectionView) collectionView.register( AlbumTrackCollectionViewCell.self, forCellWithReuseIdentifier: AlbumTrackCollectionViewCell.identifier ) collectionView.register( PlaylistHeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: PlaylistHeaderCollectionReusableView.identifier ) collectionView.backgroundColor = .systemBackground collectionView.delegate = self collectionView.dataSource = self fetchData() navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(didTapActions)) } @objc func didTapActions() { let actionSheet = UIAlertController(title: album.name, message: "Actions", preferredStyle: .actionSheet) actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) actionSheet.addAction(UIAlertAction(title: "Save Album", style: .default, handler: { [weak self] _ in guard let strongSelf = self else { return } APICaller.shared.saveAlbum(album: strongSelf.album) { success in if success { HapticsManager.shared.vibrate(for: .success) NotificationCenter.default.post(name: .albumSavedNotification, object: nil) } else { HapticsManager.shared.vibrate(for: .error) } } })) present(actionSheet, animated: true) } func fetchData() { APICaller.shared.getAlbumDetails(for: album) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let model): self?.tracks = model.tracks.items self?.viewModels = model.tracks.items.compactMap({ AlbumCollectionViewCellViewModel( name: $0.name, artistName: $0.artists.first?.name ?? "-" ) }) self?.collectionView.reloadData() case .failure(let error): print(error.localizedDescription) } } } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } } extension AlbumViewController: UICollectionViewDelegate, UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return viewModels.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: AlbumTrackCollectionViewCell.identifier, for: indexPath ) as? AlbumTrackCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: viewModels[indexPath.row]) return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard let header = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: PlaylistHeaderCollectionReusableView.identifier, for: indexPath ) as? PlaylistHeaderCollectionReusableView, kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() } let headerViewModel = PlaylistHeaderViewViewModel( name: album.name, ownerName: album.artists.first?.name, description: "Release Date: \(String.formattedDate(string: album.release_date))", artworkURL: URL(string: album.images.first?.url ?? "") ) header.configure(with: headerViewModel) header.delegate = self return header } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) var track = tracks[indexPath.row] track.album = self.album PlaybackPresenter.shared.startPlayback(from: self, track: track) } } extension AlbumViewController: PlaylistHeaderCollectionReusableViewDelegate { func playlistHeaderCollectionReusableViewDidTapPlayAll(_ header: PlaylistHeaderCollectionReusableView) { let tracksWithAlbum: [AudioTrack] = tracks.compactMap({ var track = $0 track.album = self.album return track }) PlaybackPresenter.shared.startPlayback(from: self, tracks: tracksWithAlbum) } } ================================================ FILE: Spotify/Controllers/Other/AuthViewController.swift ================================================ // // AuthViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit import WebKit class AuthViewController: UIViewController, WKNavigationDelegate { private let webView: WKWebView = { let prefs = WKWebpagePreferences() prefs.allowsContentJavaScript = true let config = WKWebViewConfiguration() config.defaultWebpagePreferences = prefs let webView = WKWebView(frame: .zero, configuration: config) return webView }() public var completionHandler: ((Bool) -> Void)? override func viewDidLoad() { super.viewDidLoad() title = "Sign In" view.backgroundColor = .systemBackground webView.navigationDelegate = self view.addSubview(webView) guard let url = AuthManager.shared.signInURL else { return } webView.load(URLRequest(url: url)) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() webView.frame = view.bounds } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { guard let url = webView.url else { return } // Exchange the code for access token guard let code = URLComponents(string: url.absoluteString)?.queryItems?.first(where: { $0.name == "code" })?.value else { return } webView.isHidden = true AuthManager.shared.exchangeCodeForToken(code: code) { [weak self] success in DispatchQueue.main.async { self?.navigationController?.popToRootViewController(animated: true) self?.completionHandler?(success) } } } } ================================================ FILE: Spotify/Controllers/Other/CategoryViewController.swift ================================================ // // CategoryViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import UIKit class CategoryViewController: UIViewController { let category: Category private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout(sectionProvider: { _, _ -> NSCollectionLayoutSection? in let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))) item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5) let group = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(250) ), subitem: item, count: 2 ) group.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5) return NSCollectionLayoutSection(group: group) })) // MARK: - Init init(category: Category) { self.category = category super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError() } private var playlists = [Playlist]() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() title = category.name view.addSubview(collectionView) view.backgroundColor = .systemBackground collectionView.backgroundColor = .systemBackground collectionView.register( FeaturedPlaylistCollectionViewCell.self, forCellWithReuseIdentifier: FeaturedPlaylistCollectionViewCell.identifier ) collectionView.delegate = self collectionView.dataSource = self APICaller.shared.getCategoryPlaylists(category: category) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let playlists): self?.playlists = playlists self?.collectionView.reloadData() case .failure(let error): print(error.localizedDescription) } } } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } } extension CategoryViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return playlists.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: FeaturedPlaylistCollectionViewCell.identifier, for: indexPath ) as? FeaturedPlaylistCollectionViewCell else { return UICollectionViewCell() } let playlist = playlists[indexPath.row] cell.configure(with: FeaturedPlaylistCellViewModel( name: playlist.name, artworkURL: URL(string: playlist.images.first?.url ?? ""), creatorName: playlist.owner.display_name ) ) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) let vc = PlaylistViewController(playlist: playlists[indexPath.row]) vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } } ================================================ FILE: Spotify/Controllers/Other/Library/LibraryAlbumsViewController.swift ================================================ // // LibraryAlbumsViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import UIKit class LibraryAlbumsViewController: UIViewController { var albums = [Album]() private let noAlbumsView = ActionLabelView() private let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.register( SearchResultSubtitleTableViewCell.self, forCellReuseIdentifier: SearchResultSubtitleTableViewCell.identfier) tableView.isHidden = true return tableView }() private var observer: NSObjectProtocol? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) setUpNoAlbumsView() fetchData() observer = NotificationCenter.default.addObserver( forName: .albumSavedNotification, object: nil, queue: .main, using: { [weak self] _ in self?.fetchData() } ) } @objc func didTapClose() { dismiss(animated: true, completion: nil) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() noAlbumsView.frame = CGRect(x: (view.width-150)/2, y: (view.height-150)/2, width: 150, height: 150) tableView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height) } private func setUpNoAlbumsView() { view.addSubview(noAlbumsView) noAlbumsView.delegate = self noAlbumsView.configure( with: ActionLabelViewViewModel( text: "You have not saved any albums yet.", actionTitle: "Browse" ) ) } private func fetchData() { albums.removeAll() APICaller.shared.getCurrentUserAlbums { [weak self] result in DispatchQueue.main.async { switch result { case .success(let albums): self?.albums = albums self?.updateUI() case .failure(let error): print(error.localizedDescription) } } } } private func updateUI() { if albums.isEmpty { // Show label noAlbumsView.isHidden = false tableView.isHidden = true } else { // Show table tableView.reloadData() noAlbumsView.isHidden = true tableView.isHidden = false } } } extension LibraryAlbumsViewController: ActionLabelViewDelegate { func actionLabelViewDidTapButton(_ actionView: ActionLabelView) { tabBarController?.selectedIndex = 0 } } extension LibraryAlbumsViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return albums.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultSubtitleTableViewCell.identfier, for: indexPath ) as? SearchResultSubtitleTableViewCell else { return UITableViewCell() } let album = albums[indexPath.row] cell.configure( with: SearchResultSubtitleTableViewCellViewModel( title: album.name, subtitle: album.artists.first?.name ?? "-", imageURL: URL(string: album.images.first?.url ?? "") ) ) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) HapticsManager.shared.vibrateForSelection() let album = albums[indexPath.row] let vc = AlbumViewController(album: album) vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 70 } } ================================================ FILE: Spotify/Controllers/Other/Library/LibraryPlaylistsViewController.swift ================================================ // // LibraryPlaylistsViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import UIKit class LibraryPlaylistsViewController: UIViewController { var playlists = [Playlist]() public var selectionHandler: ((Playlist) -> Void)? private let noPlaylistsView = ActionLabelView() private let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.register( SearchResultSubtitleTableViewCell.self, forCellReuseIdentifier: SearchResultSubtitleTableViewCell.identfier) tableView.isHidden = true return tableView }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) setUpNoPlaylistsView() fetchData() if selectionHandler != nil { navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(didTapClose)) } } @objc func didTapClose() { dismiss(animated: true, completion: nil) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() noPlaylistsView.frame = CGRect(x: 0, y: 0, width: 150, height: 150) noPlaylistsView.center = view.center tableView.frame = view.bounds } private func setUpNoPlaylistsView() { view.addSubview(noPlaylistsView) noPlaylistsView.delegate = self noPlaylistsView.configure( with: ActionLabelViewViewModel( text: "You don't have any playlists yet.", actionTitle: "Create" ) ) } private func fetchData() { APICaller.shared.getCurrentUserPlaylists { [weak self] result in DispatchQueue.main.async { switch result { case .success(let playlists): self?.playlists = playlists self?.updateUI() case .failure(let error): print(error.localizedDescription) } } } } private func updateUI() { if playlists.isEmpty { // Show label noPlaylistsView.isHidden = false tableView.isHidden = true } else { // Show table tableView.reloadData() noPlaylistsView.isHidden = true tableView.isHidden = false } } public func showCreatePlaylistAlert() { let alert = UIAlertController( title: "New Playlists", message: "Enter playlist name.", preferredStyle: .alert ) alert.addTextField { textField in textField.placeholder = "Playlist..." } alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "Create", style: .default, handler: { _ in guard let field = alert.textFields?.first, let text = field.text, !text.trimmingCharacters(in: .whitespaces).isEmpty else { return } APICaller.shared.createPlaylist(with: text) { [weak self] success in if success { HapticsManager.shared.vibrate(for: .success) // Refresh list of playlists self?.fetchData() } else { HapticsManager.shared.vibrate(for: .error) print("Failed to create playlist") } } })) present(alert, animated: true) } } extension LibraryPlaylistsViewController: ActionLabelViewDelegate { func actionLabelViewDidTapButton(_ actionView: ActionLabelView) { showCreatePlaylistAlert() } } extension LibraryPlaylistsViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return playlists.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultSubtitleTableViewCell.identfier, for: indexPath ) as? SearchResultSubtitleTableViewCell else { return UITableViewCell() } let playlist = playlists[indexPath.row] cell.configure( with: SearchResultSubtitleTableViewCellViewModel( title: playlist.name, subtitle: playlist.owner.display_name, imageURL: URL(string: playlist.images.first?.url ?? "") ) ) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) HapticsManager.shared.vibrateForSelection() let playlist = playlists[indexPath.row] guard selectionHandler == nil else { selectionHandler?(playlist) dismiss(animated: true, completion: nil) return } let vc = PlaylistViewController(playlist: playlist) vc.navigationItem.largeTitleDisplayMode = .never vc.isOwner = true navigationController?.pushViewController(vc, animated: true) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 70 } } ================================================ FILE: Spotify/Controllers/Other/PlayerViewController.swift ================================================ // // PlayerViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit import SDWebImage protocol PlayerViewControllerDelegate: AnyObject { func didTapPlayPause() func didTapForward() func didTapBackward() func didSlideSlider(_ value: Float) } class PlayerViewController: UIViewController { weak var dataSource: PlayerDataSource? weak var delegate: PlayerViewControllerDelegate? private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill return imageView }() private let controlsView = PlayerControlsView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground view.addSubview(imageView) view.addSubview(controlsView) controlsView.delegate = self configureBarButtons() configure() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() imageView.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width: view.width, height: view.width) controlsView.frame = CGRect( x: 10, y: imageView.bottom+10, width: view.width-20, height: view.height-imageView.height-view.safeAreaInsets.top-view.safeAreaInsets.bottom-15 ) } private func configure() { imageView.sd_setImage(with: dataSource?.imageURL, completed: nil) controlsView.configure( with: PlayerControlsViewViewModel( title: dataSource?.songName, subtitle: dataSource?.subtitle ) ) } private func configureBarButtons() { navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(didTapClose)) navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(didTapAction)) } @objc private func didTapClose() { dismiss(animated: true, completion: nil) } @objc private func didTapAction() { // Actions } func refreshUI() { configure() } } extension PlayerViewController: PlayerControlsViewDelegate { func playerControlsViewDidTapPlayPauseButton(_ playerControlsView: PlayerControlsView) { delegate?.didTapPlayPause() } func playerControlsViewDidTapForwardButton(_ playerControlsView: PlayerControlsView) { delegate?.didTapForward() } func playerControlsViewDidTapBackwardsButton(_ playerControlsView: PlayerControlsView) { delegate?.didTapBackward() } func playerControlsView(_ playerControlsView: PlayerControlsView, didSlideSlider value: Float) { delegate?.didSlideSlider(value) } } ================================================ FILE: Spotify/Controllers/Other/PlaylistViewController.swift ================================================ // // PlaylistViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class PlaylistViewController: UIViewController { private let playlist: Playlist public var isOwner = false private let collectionView = UICollectionView( frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout(sectionProvider: { _, _ -> NSCollectionLayoutSection? in let item = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0) ) ) item.contentInsets = NSDirectionalEdgeInsets(top: 1, leading: 2, bottom: 1, trailing: 2) let group = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .absolute(60) ), subitem: item, count: 1 ) let section = NSCollectionLayoutSection(group: group) section.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem( layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top ) ] return section }) ) init(playlist: Playlist) { self.playlist = playlist super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError() } private var viewModels = [RecommendedTrackCellViewModel]() private var tracks = [AudioTrack]() override func viewDidLoad() { super.viewDidLoad() title = playlist.name view.backgroundColor = .systemBackground view.addSubview(collectionView) collectionView.register( RecommendedTrackCollectionViewCell.self, forCellWithReuseIdentifier: RecommendedTrackCollectionViewCell.identifier ) collectionView.register( PlaylistHeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: PlaylistHeaderCollectionReusableView.identifier ) collectionView.backgroundColor = .systemBackground collectionView.delegate = self collectionView.dataSource = self APICaller.shared.getPlaylistDetails(for: playlist) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let model): self?.tracks = model.tracks.items.compactMap({ $0.track }) self?.viewModels = model.tracks.items.compactMap({ RecommendedTrackCellViewModel( name: $0.track.name, artistName: $0.track.artists.first?.name ?? "-", artworkURL: URL(string: $0.track.album?.images.first?.url ?? "") ) }) self?.collectionView.reloadData() case .failure(let error): print(error.localizedDescription) } } } navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .action, target: self, action: #selector(didTapShare) ) let gesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:))) collectionView.addGestureRecognizer(gesture) } @objc func didLongPress(_ gesture: UILongPressGestureRecognizer) { guard gesture.state == .began else { return } let touchPoint = gesture.location(in: collectionView) guard let indexPath = collectionView.indexPathForItem(at: touchPoint) else { return } let trackToDelete = tracks[indexPath.row] let actionSheet = UIAlertController( title: trackToDelete.name, message: "Would you like to remove this from the playlist?", preferredStyle: .actionSheet ) actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) actionSheet.addAction( UIAlertAction( title: "Remove", style: .destructive, handler: { [weak self] _ in guard let strongSelf = self else { return } APICaller.shared.removeTrackFromPlaylist(track: trackToDelete, playlist: strongSelf.playlist) { success in DispatchQueue.main.async { if success { strongSelf.tracks.remove(at: indexPath.row) strongSelf.viewModels.remove(at: indexPath.row) strongSelf.collectionView.reloadData() } else { print("Failed to remove") } } } } ) ) present(actionSheet, animated: true, completion: nil) } @objc private func didTapShare() { guard let url = URL(string: playlist.external_urls["spotify"] ?? "") else { return } let vc = UIActivityViewController( activityItems: [url], applicationActivities: [] ) vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem present(vc, animated: true) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } } extension PlaylistViewController: UICollectionViewDelegate, UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return viewModels.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: RecommendedTrackCollectionViewCell.identifier, for: indexPath ) as? RecommendedTrackCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: viewModels[indexPath.row]) return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard let header = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: PlaylistHeaderCollectionReusableView.identifier, for: indexPath ) as? PlaylistHeaderCollectionReusableView, kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() } let headerViewModel = PlaylistHeaderViewViewModel( name: playlist.name, ownerName: playlist.owner.display_name, description: playlist.description, artworkURL: URL(string: playlist.images.first?.url ?? "") ) header.configure(with: headerViewModel) header.delegate = self return header } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) let index = indexPath.row let track = tracks[index] PlaybackPresenter.shared.startPlayback(from: self, track: track) } } extension PlaylistViewController: PlaylistHeaderCollectionReusableViewDelegate { func playlistHeaderCollectionReusableViewDidTapPlayAll(_ header: PlaylistHeaderCollectionReusableView) { PlaybackPresenter.shared.startPlayback( from: self, tracks: tracks ) } } ================================================ FILE: Spotify/Controllers/Other/ProfileViewController.swift ================================================ // // ProfileViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import SDWebImage import UIKit class ProfileViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { private let tableView: UITableView = { let tableView = UITableView() tableView.isHidden = true tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") return tableView }() private var models = [String]() override func viewDidLoad() { super.viewDidLoad() title = "Profile" tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) fetchProfile() view.backgroundColor = .systemBackground } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() tableView.frame = view.bounds } private func fetchProfile() { APICaller.shared.getCurrentUserProfile { [weak self] result in DispatchQueue.main.async { switch result { case .success(let model): self?.updateUI(with: model) case .failure(let error): print("Profile Error: \(error.localizedDescription)") self?.failedToGetProfile() } } } } private func updateUI(with model: UserProfile) { tableView.isHidden = false // configure table models models.append("Full Name: \(model.display_name)") models.append("Email Address: \(model.email)") models.append("User ID: \(model.id)") models.append("Plan: \(model.product)") createTableHeader(with: model.images.first?.url) tableView.reloadData() } private func createTableHeader(with string: String?) { guard let urlString = string, let url = URL(string: urlString) else { return } let headerView = UIView(frame: CGRect(x: 0, y: 0, width: view.width, height: view.width/1.5)) let imageSize: CGFloat = headerView.height/2 let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: imageSize, height: imageSize)) headerView.addSubview(imageView) imageView.center = headerView.center imageView.contentMode = .scaleAspectFill imageView.sd_setImage(with: url, completed: nil) imageView.layer.masksToBounds = true imageView.layer.cornerRadius = imageSize/2 tableView.tableHeaderView = headerView } private func failedToGetProfile() { let label = UILabel(frame: .zero) label.text = "Failed to load profile." label.sizeToFit() label.textColor = .secondaryLabel view.addSubview(label) label.center = view.center } // MARK: - TableView func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = models[indexPath.row] cell.selectionStyle = .none return cell } } ================================================ FILE: Spotify/Controllers/Other/SearchResultsViewController.swift ================================================ // // SearchResultsViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit struct SearchSection { let title: String let results: [SearchResult] } protocol SearchResultsViewControllerDelegate: AnyObject { func didTapResult(_ result: SearchResult) } class SearchResultsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { weak var delegate: SearchResultsViewControllerDelegate? private var sections: [SearchSection] = [] private let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.backgroundColor = .systemBackground tableView.register(SearchResultDefaultTableViewCell.self, forCellReuseIdentifier: SearchResultDefaultTableViewCell.identfier) tableView.register(SearchResultSubtitleTableViewCell.self, forCellReuseIdentifier: SearchResultSubtitleTableViewCell.identfier) tableView.isHidden = true return tableView }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .clear view.addSubview(tableView) tableView.delegate = self tableView.dataSource = self } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() tableView.frame = view.bounds } func update(with results: [SearchResult]) { let artists = results.filter({ switch $0 { case .artist: return true default: return false } }) let albums = results.filter({ switch $0 { case .album: return true default: return false } }) let tracks = results.filter({ switch $0 { case .track: return true default: return false } }) let playlists = results.filter({ switch $0 { case .playlist: return true default: return false } }) self.sections = [ SearchSection(title: "Songs", results: tracks), SearchSection(title: "Artists", results: artists), SearchSection(title: "Playlists", results: playlists), SearchSection(title: "Albums", results: albums) ] tableView.reloadData() tableView.isHidden = results.isEmpty } func numberOfSections(in tableView: UITableView) -> Int { return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections[section].results.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let result = sections[indexPath.section].results[indexPath.row] switch result { case .artist(let artist): guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultDefaultTableViewCell.identfier, for: indexPath ) as? SearchResultDefaultTableViewCell else { return UITableViewCell() } let viewModel = SearchResultDefaultTableViewCellViewModel( title: artist.name, imageURL: URL(string: artist.images?.first?.url ?? "") ) cell.configure(with: viewModel) return cell case .album(let album): guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultSubtitleTableViewCell.identfier, for: indexPath ) as? SearchResultSubtitleTableViewCell else { return UITableViewCell() } let viewModel = SearchResultSubtitleTableViewCellViewModel( title: album.name, subtitle: album.artists.first?.name ?? "", imageURL: URL(string: album.images.first?.url ?? "") ) cell.configure(with: viewModel) return cell case .track(let track): guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultSubtitleTableViewCell.identfier, for: indexPath ) as? SearchResultSubtitleTableViewCell else { return UITableViewCell() } let viewModel = SearchResultSubtitleTableViewCellViewModel( title: track.name, subtitle: track.artists.first?.name ?? "-", imageURL: URL(string: track.album?.images.first?.url ?? "") ) cell.configure(with: viewModel) return cell case .playlist(let playlist): guard let cell = tableView.dequeueReusableCell( withIdentifier: SearchResultSubtitleTableViewCell.identfier, for: indexPath ) as? SearchResultSubtitleTableViewCell else { return UITableViewCell() } let viewModel = SearchResultSubtitleTableViewCellViewModel( title: playlist.name, subtitle: playlist.owner.display_name, imageURL: URL(string: playlist.images.first?.url ?? "") ) cell.configure(with: viewModel) return cell } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let result = sections[indexPath.section].results[indexPath.row] delegate?.didTapResult(result) } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section].title } } ================================================ FILE: Spotify/Controllers/Other/SettingsViewController.swift ================================================ // // SettingsViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class SettingsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") return tableView }() private var sections = [Section]() override func viewDidLoad() { super.viewDidLoad() configureModels() title = "Settings" view.backgroundColor = .systemBackground view.addSubview(tableView) tableView.dataSource = self tableView.delegate = self } private func configureModels() { sections.append(Section(title: "Profile", options: [Option(title: "View Your Profile", handler: { [weak self] in DispatchQueue.main.async { self?.viewProfile() } })])) sections.append(Section(title: "Account", options: [Option(title: "Sign Out", handler: { [weak self] in DispatchQueue.main.async { self?.signOutTapped() } })])) } private func signOutTapped() { let alert = UIAlertController(title: "Sign Out", message: "Are you sure?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "Sign Out", style: .destructive, handler: { _ in AuthManager.shared.signOut { [weak self] signedOut in if signedOut { DispatchQueue.main.async { let navVC = UINavigationController(rootViewController: WelcomeViewController()) navVC.navigationBar.prefersLargeTitles = true navVC.viewControllers.first?.navigationItem.largeTitleDisplayMode = .always navVC.modalPresentationStyle = .fullScreen self?.present(navVC, animated: true, completion: { self?.navigationController?.popToRootViewController(animated: false) }) } } } })) present(alert, animated: true) } private func viewProfile() { let vc = ProfileViewController() vc.title = "Profile" vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() tableView.frame = view.bounds } // MARK: - TableView func numberOfSections(in tableView: UITableView) -> Int { return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections[section].options.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = sections[indexPath.section].options[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = model.title return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // Call handler for cll let model = sections[indexPath.section].options[indexPath.row] model.handler() } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { let model = sections[section] return model.title } } ================================================ FILE: Spotify/Controllers/Other/WelcomeViewController.swift ================================================ // // WelcomeViewController.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class WelcomeViewController: UIViewController { private let signInButton: UIButton = { let button = UIButton() button.backgroundColor = .white button.setTitle("Sign In with Spotify", for: .normal) button.setTitleColor(.black, for: .normal) return button }() private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.image = UIImage(named: "albums_background") return imageView }() private let overlayView: UIView = { let view = UIView() view.backgroundColor = .black view.alpha = 0.7 return view }() private let logoImageView: UIImageView = { let imageView = UIImageView(image: UIImage(named: "logo")) imageView.contentMode = .scaleAspectFit return imageView }() private let label: UILabel = { let label = UILabel() label.numberOfLines = 0 label.textAlignment = .center label.textColor = .white label.font = .systemFont(ofSize: 32, weight: .semibold) label.text = "Listen to Millions\nof Songs on\nthe go." return label }() override func viewDidLoad() { super.viewDidLoad() title = "Spotify" view.addSubview(imageView) view.addSubview(overlayView) view.backgroundColor = .blue view.addSubview(signInButton) signInButton.addTarget(self, action: #selector(didTapSignIn), for: .touchUpInside) view.addSubview(label) view.addSubview(logoImageView) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() imageView.frame = view.bounds overlayView.frame = view.bounds signInButton.frame = CGRect( x: 20, y: view.height-50-view.safeAreaInsets.bottom, width: view.width-40, height: 50 ) logoImageView.frame = CGRect(x: (view.width-120)/2, y: (view.height-350)/2, width: 120, height: 120) label.frame = CGRect(x: 30, y: logoImageView.bottom+30, width: view.width-60, height: 150) } @objc func didTapSignIn() { let vc = AuthViewController() vc.completionHandler = { [weak self] success in DispatchQueue.main.async { self?.handleSignIn(success: success) } } vc.navigationItem.largeTitleDisplayMode = .never navigationController?.pushViewController(vc, animated: true) } private func handleSignIn(success: Bool) { // Log user in or yell at them for error guard success else { let alert = UIAlertController(title: "Oops", message: "Something went wrong when signing in.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)) present(alert, animated: true) return } let mainAppTabBarVC = TabBarViewController() mainAppTabBarVC.modalPresentationStyle = .fullScreen present(mainAppTabBarVC, animated: true) } } ================================================ FILE: Spotify/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Spotify/Managers/APICaller.swift ================================================ // // APICaller.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation final class APICaller { static let shared = APICaller() private init() {} struct Constants { static let baseAPIURL = "https://api.spotify.com/v1" } enum APIError: Error { case faileedToGetData } // MARK: - Albums public func getAlbumDetails(for album: Album, completion: @escaping (Result) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/albums/" + album.id), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(AlbumDetailsResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } public func getCurrentUserAlbums(completion: @escaping (Result<[Album], Error>) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/me/albums"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(LibraryAlbumsResponse.self, from: data) completion(.success(result.items.compactMap({ $0.album }))) } catch { completion(.failure(error)) } } task.resume() } } public func saveAlbum(album: Album, completion: @escaping (Bool) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/me/albums?ids=\(album.id)"), type: .PUT ) { baseRequest in var request = baseRequest request.setValue("application/json", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let code = (response as? HTTPURLResponse)?.statusCode, error == nil else { completion(false) return } print(code) completion(code == 200) } task.resume() } } // MARK: - Playlists public func getPlaylistDetails(for playlist: Playlist, completion: @escaping (Result) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/playlists/" + playlist.id), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(PlaylistDetailsResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } public func getCurrentUserPlaylists(completion: @escaping (Result<[Playlist], Error>) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/me/playlists/?limit=50"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(LibraryPlaylistsResponse.self, from: data) completion(.success(result.items)) } catch { print(error) completion(.failure(error)) } } task.resume() } } public func createPlaylist(with name: String, completion: @escaping (Bool) -> Void) { getCurrentUserProfile { [weak self] result in switch result { case .success(let profile): let urlString = Constants.baseAPIURL + "/users/\(profile.id)/playlists" print(urlString) self?.createRequest(with: URL(string: urlString), type: .POST) { baseRequest in var request = baseRequest let json = [ "name": name ] request.httpBody = try? JSONSerialization.data(withJSONObject: json, options: .fragmentsAllowed) print("Starting creation...") let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(false) return } do { let result = try JSONSerialization.jsonObject(with: data, options: .allowFragments) if let response = result as? [String: Any], response["id"] as? String != nil { completion(true) } else { completion(false) } } catch { print(error.localizedDescription) completion(false) } } task.resume() } case .failure(let error): print(error.localizedDescription) } } } public func addTrackToPlaylist( track: AudioTrack, playlist: Playlist, completion: @escaping (Bool) -> Void ) { createRequest( with: URL(string: Constants.baseAPIURL + "/playlists/\(playlist.id)/tracks"), type: .POST ) { baseRequest in var request = baseRequest let json = [ "uris": [ "spotify:track:\(track.id)" ] ] print(json) request.httpBody = try? JSONSerialization.data(withJSONObject: json, options: .fragmentsAllowed) request.setValue("application/json", forHTTPHeaderField: "Content-Type") print("Adding...") let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else{ completion(false) return } do { let result = try JSONSerialization.jsonObject(with: data, options: .allowFragments) print(result) if let response = result as? [String: Any], response["snapshot_id"] as? String != nil { completion(true) } else { completion(false) } } catch { completion(false) } } task.resume() } } public func removeTrackFromPlaylist( track: AudioTrack, playlist: Playlist, completion: @escaping (Bool) -> Void ) { createRequest( with: URL(string: Constants.baseAPIURL + "/playlists/\(playlist.id)/tracks"), type: .DELETE ) { baseRequest in var request = baseRequest let json: [String: Any] = [ "tracks": [ [ "uri": "spotify:track:\(track.id)" ] ] ] request.httpBody = try? JSONSerialization.data(withJSONObject: json, options: .fragmentsAllowed) request.setValue("application/json", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else{ completion(false) return } do { let result = try JSONSerialization.jsonObject(with: data, options: .allowFragments) if let response = result as? [String: Any], response["snapshot_id"] as? String != nil { completion(true) } else { completion(false) } } catch { completion(false) } } task.resume() } } // MARK: - Profile public func getCurrentUserProfile(completion: @escaping (Result) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/me"), type: .GET ) { baseRequest in let task = URLSession.shared.dataTask(with: baseRequest) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(UserProfile.self, from: data) completion(.success(result)) } catch { print(error.localizedDescription) completion(.failure(error)) } } task.resume() } } // MARK: - Browse public func getNewReleases(completion: @escaping ((Result)) -> Void) { createRequest(with: URL(string: Constants.baseAPIURL + "/browse/new-releases?limit=50"), type: .GET) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(NewReleasesResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } public func getFeaturedFlaylists(completion: @escaping ((Result) -> Void)) { createRequest( with: URL(string: Constants.baseAPIURL + "/browse/featured-playlists?limit=20"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(FeaturedPlaylistsResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } public func getRecommendations(genres: Set, completion: @escaping ((Result) -> Void)) { let seeds = genres.joined(separator: ",") createRequest( with: URL(string: Constants.baseAPIURL + "/recommendations?limit=40&seed_genres=\(seeds)"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(RecommendationsResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } public func gerRecommendedGenres(completion: @escaping ((Result) -> Void)) { createRequest( with: URL(string: Constants.baseAPIURL + "/recommendations/available-genre-seeds"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(RecommendedGenresResponse.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } } task.resume() } } // MARK: - Category public func getCategories(completion: @escaping (Result<[Category], Error>) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/browse/categories?limit=50"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else{ completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(AllCategoriesResponse.self, from: data) completion(.success(result.categories.items)) } catch { completion(.failure(error)) } } task.resume() } } public func getCategoryPlaylists(category: Category, completion: @escaping (Result<[Playlist], Error>) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL + "/browse/categories/\(category.id)/playlists?limit=50"), type: .GET ) { request in let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else{ completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(CategoryPlaylistsResponse.self, from: data) let playlists = result.playlists.items completion(.success(playlists)) } catch { completion(.failure(error)) } } task.resume() } } // MARK: - Search public func search(with query: String, completion: @escaping (Result<[SearchResult], Error>) -> Void) { createRequest( with: URL(string: Constants.baseAPIURL+"/search?limit=10&type=album,artist,playlist,track&q=\(query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"), type: .GET ) { request in print(request.url?.absoluteString ?? "none") let task = URLSession.shared.dataTask(with: request) { data, _, error in guard let data = data, error == nil else { completion(.failure(APIError.faileedToGetData)) return } do { let result = try JSONDecoder().decode(SearchResultsResponse.self, from: data) var searchResults: [SearchResult] = [] searchResults.append(contentsOf: result.tracks.items.compactMap({ .track(model: $0) })) searchResults.append(contentsOf: result.albums.items.compactMap({ .album(model: $0) })) searchResults.append(contentsOf: result.artists.items.compactMap({ .artist(model: $0) })) searchResults.append(contentsOf: result.playlists.items.compactMap({ .playlist(model: $0) })) completion(.success(searchResults)) } catch { completion(.failure(error)) } } task.resume() } } // MARK: - Private enum HTTPMethod: String { case GET case PUT case POST case DELETE } private func createRequest( with url: URL?, type: HTTPMethod, completion: @escaping (URLRequest) -> Void ) { AuthManager.shared.withValidToken { token in guard let apiURL = url else { return } var request = URLRequest(url: apiURL) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.httpMethod = type.rawValue request.timeoutInterval = 30 completion(request) } } } ================================================ FILE: Spotify/Managers/AuthManager.swift ================================================ // // AuthManager.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation final class AuthManager { static let shared = AuthManager() private var refreshingToken = false struct Constants { static let clientID = "ADD_YOUR_CLIENT_ID_HERE" static let clientSecret = "ADD_YOUR_CLIENT_SECRET_HERE" static let tokenAPIURL = "https://accounts.spotify.com/api/token" static let redirectURI = "https://www.iosacademy.io" static let scopes = "user-read-private%20playlist-modify-public%20playlist-read-private%20playlist-modify-private%20user-follow-read%20user-library-modify%20user-library-read%20user-read-email" } private init() {} public var signInURL: URL? { let base = "https://accounts.spotify.com/authorize" let string = "\(base)?response_type=code&client_id=\(Constants.clientID)&scope=\(Constants.scopes)&redirect_uri=\(Constants.redirectURI)&show_dialog=TRUE" return URL(string: string) } var isSignedIn: Bool { return accessToken != nil } private var accessToken: String? { return UserDefaults.standard.string(forKey: "access_token") } private var refreshToken: String? { return UserDefaults.standard.string(forKey: "refresh_token") } private var tokenExpirationDate: Date? { return UserDefaults.standard.object(forKey: "expirationDate") as? Date } private var shouldRefreshToken: Bool { guard let expirationDate = tokenExpirationDate else { return false } let currentDate = Date() let fiveMinutes: TimeInterval = 300 return currentDate.addingTimeInterval(fiveMinutes) >= expirationDate } public func exchangeCodeForToken( code: String, completion: @escaping ((Bool) -> Void) ) { // Get Token guard let url = URL(string: Constants.tokenAPIURL) else { return } var components = URLComponents() components.queryItems = [ URLQueryItem(name: "grant_type", value: "authorization_code"), URLQueryItem(name: "code", value: code), URLQueryItem(name: "redirect_uri", value: Constants.redirectURI), ] var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded ", forHTTPHeaderField: "Content-Type") request.httpBody = components.query?.data(using: .utf8) let basicToken = Constants.clientID+":"+Constants.clientSecret let data = basicToken.data(using: .utf8) guard let base64String = data?.base64EncodedString() else { print("Failure to get base64") completion(false) return } request.setValue("Basic \(base64String)", forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in guard let data = data, error == nil else { completion(false) return } do { let result = try JSONDecoder().decode(AuthResponse.self, from: data) self?.cacheToken(result: result) completion(true) } catch { print(error.localizedDescription) completion(false) } } task.resume() } private var onRefreshBlocks = [((String) -> Void)]() /// Supplies valid token to be used with API Calls public func withValidToken(completion: @escaping (String) -> Void) { guard !refreshingToken else { // Append the compleiton onRefreshBlocks.append(completion) return } if shouldRefreshToken { // Refresh refreshIfNeeded { [weak self] success in if let token = self?.accessToken, success { completion(token) } } } else if let token = accessToken { completion(token) } } public func refreshIfNeeded(completion: ((Bool) -> Void)?) { guard !refreshingToken else { return } guard shouldRefreshToken else { completion?(true) return } guard let refreshToken = self.refreshToken else{ return } // Refresh the token guard let url = URL(string: Constants.tokenAPIURL) else { return } refreshingToken = true var components = URLComponents() components.queryItems = [ URLQueryItem(name: "grant_type", value: "refresh_token"), URLQueryItem(name: "refresh_token", value: refreshToken), ] var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded ", forHTTPHeaderField: "Content-Type") request.httpBody = components.query?.data(using: .utf8) let basicToken = Constants.clientID+":"+Constants.clientSecret let data = basicToken.data(using: .utf8) guard let base64String = data?.base64EncodedString() else { print("Failure to get base64") completion?(false) return } request.setValue("Basic \(base64String)", forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in self?.refreshingToken = false guard let data = data, error == nil else { completion?(false) return } do { let result = try JSONDecoder().decode(AuthResponse.self, from: data) self?.onRefreshBlocks.forEach { $0(result.access_token) } self?.onRefreshBlocks.removeAll() self?.cacheToken(result: result) completion?(true) } catch { print(error.localizedDescription) completion?(false) } } task.resume() } private func cacheToken(result: AuthResponse) { UserDefaults.standard.setValue(result.access_token, forKey: "access_token") if let refresh_token = result.refresh_token { UserDefaults.standard.setValue(refresh_token, forKey: "refresh_token") } UserDefaults.standard.setValue(Date().addingTimeInterval(TimeInterval(result.expires_in)), forKey: "expirationDate") } public func signOut(completion: (Bool) -> Void) { UserDefaults.standard.setValue(nil, forKey: "access_token") UserDefaults.standard.setValue(nil, forKey: "refresh_token") UserDefaults.standard.setValue(nil, forKey: "expirationDate") completion(true) } } ================================================ FILE: Spotify/Managers/HapticsManager.swift ================================================ // // HapticsManager.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation import UIKit final class HapticsManager { static let shared = HapticsManager() private init() {} public func vibrateForSelection() { DispatchQueue.main.async { let generator = UISelectionFeedbackGenerator() generator.prepare() generator.selectionChanged() } } public func vibrate(for type: UINotificationFeedbackGenerator.FeedbackType) { DispatchQueue.main.async { let generator = UINotificationFeedbackGenerator() generator.prepare() generator.notificationOccurred(type) } } } ================================================ FILE: Spotify/Models/APIImage.swift ================================================ // // UserImage.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct APIImage: Codable { let url: String } ================================================ FILE: Spotify/Models/AlbumDetailsResponse.swift ================================================ // // AlbumDetailsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct AlbumDetailsResponse: Codable { let album_type: String let artists: [Artist] let available_markets: [String] let external_urls: [String: String] let id: String let images: [APIImage] let label: String let name: String let tracks: TracksResponse } struct TracksResponse: Codable { let items: [AudioTrack] } ================================================ FILE: Spotify/Models/AllCategoriesResponse.swift ================================================ // // AllCategoriesResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation struct AllCategoriesResponse: Codable { let categories: Categories } struct Categories: Codable { let items: [Category] } struct Category: Codable { let id: String let name: String let icons: [APIImage] } ================================================ FILE: Spotify/Models/Artist.swift ================================================ // // Artist.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation struct Artist: Codable { let id: String let name: String let type: String let images: [APIImage]? let external_urls: [String: String] } ================================================ FILE: Spotify/Models/AudioTrack.swift ================================================ // // AudioTrack.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation struct AudioTrack: Codable { var album: Album? let artists: [Artist] let available_markets: [String] let disc_number: Int let duration_ms: Int let explicit: Bool let external_urls: [String: String] let id: String let name: String let preview_url: String? } ================================================ FILE: Spotify/Models/AuthResponse.swift ================================================ // // AuthResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation struct AuthResponse: Codable { let access_token: String let expires_in: Int let refresh_token: String? let scope: String let token_type: String } ================================================ FILE: Spotify/Models/FeaturedPlaylistsResponse.swift ================================================ // // FeaturedPlaylistsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct FeaturedPlaylistsResponse: Codable { let playlists: PlaylistResponse } struct CategoryPlaylistsResponse: Codable { let playlists: PlaylistResponse } struct PlaylistResponse: Codable { let items: [Playlist] } struct User: Codable { let display_name: String let external_urls: [String: String] let id: String } ================================================ FILE: Spotify/Models/LibraryAlbumsResponse.swift ================================================ // // LibraryAlbumsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import Foundation struct LibraryAlbumsResponse: Codable { let items: [SavedAlbum] } struct SavedAlbum: Codable { let added_at: String let album: Album } ================================================ FILE: Spotify/Models/LibraryPlaylistsResponse.swift ================================================ // // LibraryPlaylistsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import Foundation struct LibraryPlaylistsResponse: Codable { let items: [Playlist] } ================================================ FILE: Spotify/Models/NewReleasesResponse.swift ================================================ // // NewReleasesResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct NewReleasesResponse: Codable { let albums: AlbumsResponse } struct AlbumsResponse: Codable { let items: [Album] } struct Album: Codable { let album_type: String let available_markets: [String] let id: String var images: [APIImage] let name: String let release_date: String let total_tracks: Int let artists: [Artist] } ================================================ FILE: Spotify/Models/Playlist.swift ================================================ // // Playlist.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation struct Playlist: Codable { let description: String let external_urls: [String: String] let id: String let images: [APIImage] let name: String let owner: User } ================================================ FILE: Spotify/Models/PlaylistDetailsResponse.swift ================================================ // // PlaylistDetailsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct PlaylistDetailsResponse: Codable { let description: String let external_urls: [String: String] let id: String let images: [APIImage] let name: String let tracks: PlaylistTracksResponse } struct PlaylistTracksResponse: Codable { let items: [PlaylistItem] } struct PlaylistItem: Codable { let track: AudioTrack } ================================================ FILE: Spotify/Models/RecommendationsResponse.swift ================================================ // // RecommendationsResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct RecommendationsResponse: Codable { let tracks: [AudioTrack] } ================================================ FILE: Spotify/Models/RecommendedGenresResponse.swift ================================================ // // RecommendedGenresResponse.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct RecommendedGenresResponse: Codable { let genres: [String] } ================================================ FILE: Spotify/Models/SearchResult.swift ================================================ // // SearchResult.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation enum SearchResult { case artist(model: Artist) case album(model: Album) case track(model: AudioTrack) case playlist(model: Playlist) } ================================================ FILE: Spotify/Models/SearchResultResponse.swift ================================================ // // SearchResult.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation struct SearchResultsResponse: Codable { let albums: SearchAlbumResponse let artists: SearchArtistsResponse let playlists: SearchPlaylistsResponse let tracks: SearchTrackssResponse } struct SearchAlbumResponse: Codable { let items: [Album] } struct SearchArtistsResponse: Codable { let items: [Artist] } struct SearchPlaylistsResponse: Codable { let items: [Playlist] } struct SearchTrackssResponse: Codable { let items: [AudioTrack] } ================================================ FILE: Spotify/Models/SettingsModels.swift ================================================ // // SettingsModels.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct Section { let title: String let options: [Option] } struct Option { let title: String let handler: () -> Void } ================================================ FILE: Spotify/Models/UserProfile.swift ================================================ // // UserProfile.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation struct UserProfile: Codable { let country: String let display_name: String let email: String let explicit_content: [String: Bool] let external_urls: [String: String] let id: String let product: String let images: [APIImage] } ================================================ FILE: Spotify/Presenter/PlaybackPresenter.swift ================================================ // // PlaybackPresenter.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import AVFoundation import Foundation import UIKit protocol PlayerDataSource: AnyObject { var songName: String? { get } var subtitle: String? { get } var imageURL: URL? { get } } final class PlaybackPresenter { static let shared = PlaybackPresenter() private var track: AudioTrack? private var tracks = [AudioTrack]() var index = 0 var currentTrack: AudioTrack? { if let track = track, tracks.isEmpty { return track } else if let player = self.playerQueue, !tracks.isEmpty { return tracks[index] } return nil } var playerVC: PlayerViewController? var player: AVPlayer? var playerQueue: AVQueuePlayer? func startPlayback( from viewController: UIViewController, track: AudioTrack ) { guard let url = URL(string: track.preview_url ?? "") else { return } player = AVPlayer(url: url) player?.volume = 0.5 self.track = track self.tracks = [] let vc = PlayerViewController() vc.title = track.name vc.dataSource = self vc.delegate = self viewController.present(UINavigationController(rootViewController: vc), animated: true) { [weak self] in self?.player?.play() } self.playerVC = vc } func startPlayback( from viewController: UIViewController, tracks: [AudioTrack] ) { self.tracks = tracks self.track = nil self.playerQueue = AVQueuePlayer(items: tracks.compactMap({ guard let url = URL(string: $0.preview_url ?? "") else { return nil } return AVPlayerItem(url: url) })) self.playerQueue?.volume = 0.5 self.playerQueue?.play() let vc = PlayerViewController() vc.dataSource = self vc.delegate = self viewController.present(UINavigationController(rootViewController: vc), animated: true, completion: nil) self.playerVC = vc } } extension PlaybackPresenter: PlayerViewControllerDelegate { func didTapPlayPause() { if let player = player { if player.timeControlStatus == .playing { player.pause() } else if player.timeControlStatus == .paused { player.play() } } else if let player = playerQueue { if player.timeControlStatus == .playing { player.pause() } else if player.timeControlStatus == .paused { player.play() } } } func didTapForward() { if tracks.isEmpty { // Not playlist or album player?.pause() } else if let player = playerQueue { player.advanceToNextItem() index += 1 print(index) playerVC?.refreshUI() } } func didTapBackward() { if tracks.isEmpty { // Not playlist or album player?.pause() player?.play() } else if let firstItem = playerQueue?.items().first { playerQueue?.pause() playerQueue?.removeAllItems() playerQueue = AVQueuePlayer(items: [firstItem]) playerQueue?.play() playerQueue?.volume = 0.5 } } func didSlideSlider(_ value: Float) { player?.volume = value } } extension PlaybackPresenter: PlayerDataSource { var songName: String? { return currentTrack?.name } var subtitle: String? { return currentTrack?.artists.first?.name } var imageURL: URL? { return URL(string: currentTrack?.album?.images.first?.url ?? "") } } ================================================ FILE: Spotify/Resources/AppDelegate.swift ================================================ // // AppDelegate.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) if AuthManager.shared.isSignedIn { AuthManager.shared.refreshIfNeeded(completion: nil) window.rootViewController = TabBarViewController() } else { let navVC = UINavigationController(rootViewController: WelcomeViewController()) navVC.navigationBar.prefersLargeTitles = true navVC.viewControllers.first?.navigationItem.largeTitleDisplayMode = .always window.rootViewController = navVC } window.makeKeyAndVisible() self.window = window return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } ================================================ FILE: Spotify/Resources/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Spotify/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "notification-icon@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "filename" : "notification-icon@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "filename" : "icon-small.png", "idiom" : "iphone", "scale" : "1x", "size" : "29x29" }, { "filename" : "icon-small@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-small@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "filename" : "icon-40@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "filename" : "icon.png", "idiom" : "iphone", "scale" : "1x", "size" : "57x57" }, { "filename" : "icon@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "57x57" }, { "filename" : "icon-60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "filename" : "icon-60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "filename" : "notification-icon~ipad.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "filename" : "notification-icon~ipad@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon-small.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "filename" : "icon-small@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "filename" : "icon-40@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-small-50.png", "idiom" : "ipad", "scale" : "1x", "size" : "50x50" }, { "filename" : "icon-small-50@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "50x50" }, { "filename" : "icon-72.png", "idiom" : "ipad", "scale" : "1x", "size" : "72x72" }, { "filename" : "icon-72@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "72x72" }, { "filename" : "icon-76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "filename" : "icon-76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "filename" : "icon-83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "filename" : "ios-marketing.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Spotify/Resources/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Spotify/Resources/Assets.xcassets/albums_background.imageset/Contents.json ================================================ { "images" : [ { "filename" : "albums.jpg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Spotify/Resources/Assets.xcassets/logo.imageset/Contents.json ================================================ { "images" : [ { "filename" : "logo.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Spotify/Resources/Extensions.swift ================================================ // // Extensions.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import Foundation import UIKit extension UIView { var width: CGFloat { return frame.size.width } var height: CGFloat { return frame.size.height } var left: CGFloat { return frame.origin.x } var right: CGFloat { return left + width } var top: CGFloat { return frame.origin.y } var bottom: CGFloat { return top + height } } extension DateFormatter { static let dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "YYYY-MM-dd" return dateFormatter }() static let displayDateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium return dateFormatter }() } extension String { static func formattedDate(string: String) -> String { guard let date = DateFormatter.dateFormatter.date(from: string) else { return string } return DateFormatter.displayDateFormatter.string(from: date) } } extension Notification.Name { static let albumSavedNotification = Notification.Name("albumSavedNotification") } ================================================ FILE: Spotify/Resources/SceneDelegate.swift ================================================ // // SceneDelegate.swift // Spotify // // Created by Afraz Siddiqui on 2/14/21. // import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) if AuthManager.shared.isSignedIn { window.rootViewController = TabBarViewController() } else { let navVC = UINavigationController(rootViewController: WelcomeViewController()) navVC.navigationBar.prefersLargeTitles = true navVC.viewControllers.first?.navigationItem.largeTitleDisplayMode = .always window.rootViewController = navVC } window.makeKeyAndVisible() self.window = window } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } } ================================================ FILE: Spotify/ViewModels/AlbumCollectionViewCellViewModel.swift ================================================ // // AlbumCollectionViewCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation struct AlbumCollectionViewCellViewModel { let name: String let artistName: String } ================================================ FILE: Spotify/ViewModels/CategoryCollectionViewCellViewModel.swift ================================================ // // CategoryCollectionViewCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation struct CategoryCollectionViewCellViewModel { let title: String let artworkURL: URL? } ================================================ FILE: Spotify/ViewModels/FeaturedPlaylistCellViewModel.swift ================================================ // // FeaturedPlaylistCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct FeaturedPlaylistCellViewModel { let name: String let artworkURL: URL? let creatorName: String } ================================================ FILE: Spotify/ViewModels/NewReleasesCellViewModel.swift ================================================ // // NewReleasesCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct NewReleasesCellViewModel { let name: String let artworkURL: URL? let numberOfTracks: Int let artistName: String } ================================================ FILE: Spotify/ViewModels/PlaylistHeaderViewViewModel.swift ================================================ // // PlaylistHeaderViewViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/18/21. // import Foundation struct PlaylistHeaderViewViewModel { let name: String? let ownerName: String? let description: String? let artworkURL: URL? } ================================================ FILE: Spotify/ViewModels/RecommendedTrackCellViewModel.swift ================================================ // // RecommendedTrackCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import Foundation struct RecommendedTrackCellViewModel { let name: String let artistName: String let artworkURL: URL? } ================================================ FILE: Spotify/ViewModels/SearchResultDefaultTableViewCellViewModel.swift ================================================ // // SearchResultDefaultTableViewCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import Foundation struct SearchResultDefaultTableViewCellViewModel { let title: String let imageURL: URL? } ================================================ FILE: Spotify/ViewModels/SearchResultSubtitleTableViewCellViewModel.swift ================================================ // // SearchResultSubtitleTableViewCellViewModel.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import Foundation struct SearchResultSubtitleTableViewCellViewModel { let title: String let subtitle: String let imageURL: URL? } ================================================ FILE: Spotify/Views/ActionLabelView.swift ================================================ // // ActionLabelView.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import UIKit struct ActionLabelViewViewModel { let text: String let actionTitle: String } protocol ActionLabelViewDelegate: AnyObject { func actionLabelViewDidTapButton(_ actionView: ActionLabelView) } class ActionLabelView: UIView { weak var delegate: ActionLabelViewDelegate? private let label: UILabel = { let label = UILabel() label.textAlignment = .center label.numberOfLines = 0 label.textColor = .secondaryLabel return label }() private let button: UIButton = { let button = UIButton() button.setTitleColor(.link, for: .normal) return button }() override init(frame: CGRect) { super.init(frame: frame) clipsToBounds = true isHidden = true addSubview(button) addSubview(label) button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) } required init?(coder: NSCoder) { fatalError() } @objc func didTapButton() { delegate?.actionLabelViewDidTapButton(self) } override func layoutSubviews() { super.layoutSubviews() button.frame = CGRect(x: 0, y: height-40, width: width, height: 40) label.frame = CGRect(x: 0, y: 0, width: width, height: height-45) } func configure(with viewModel: ActionLabelViewViewModel) { label.text = viewModel.text button.setTitle(viewModel.actionTitle, for: .normal) } } ================================================ FILE: Spotify/Views/AlbumTrackCollectionViewCell.swift ================================================ // // AlbumTrackCollectionViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import Foundation import UIKit class AlbumTrackCollectionViewCell: UICollectionViewCell { static let identifier = "AlbumTrackCollectionViewCell" private let trackNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 18, weight: .regular) return label }() private let artistNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 15, weight: .thin) return label }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .secondarySystemBackground contentView.backgroundColor = .secondarySystemBackground contentView.addSubview(trackNameLabel) contentView.addSubview(artistNameLabel) contentView.clipsToBounds = true } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() trackNameLabel.frame = CGRect( x: 10, y: 0, width: contentView.width-15, height: contentView.height/2 ) artistNameLabel.frame = CGRect( x: 10, y: contentView.height/2, width: contentView.width-15, height: contentView.height/2 ) } override func prepareForReuse() { super.prepareForReuse() trackNameLabel.text = nil artistNameLabel.text = nil } func configure(with viewModel: AlbumCollectionViewCellViewModel) { trackNameLabel.text = viewModel.name artistNameLabel.text = viewModel.artistName } } ================================================ FILE: Spotify/Views/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Spotify/Views/Browse/FeaturedPlaylistCollectionViewCell.swift ================================================ // // FeaturedPlaylistCollectionViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import UIKit class FeaturedPlaylistCollectionViewCell: UICollectionViewCell { static let identifier = "FeaturedPlaylistCollectionViewCell" private let playlistCoverImageView: UIImageView = { let imageView = UIImageView() imageView.layer.masksToBounds = true imageView.layer.cornerRadius = 4 imageView.image = UIImage(systemName: "photo") imageView.contentMode = .scaleAspectFill return imageView }() private let playlistNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.textAlignment = .center label.font = .systemFont(ofSize: 18, weight: .regular) return label }() private let creatorNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.textAlignment = .center label.font = .systemFont(ofSize: 15, weight: .thin) return label }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(playlistCoverImageView) contentView.addSubview(playlistNameLabel) contentView.addSubview(creatorNameLabel) contentView.clipsToBounds = true } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() creatorNameLabel.frame = CGRect( x: 3, y: contentView.height-30, width: contentView.width-6, height: 30 ) playlistNameLabel.frame = CGRect( x: 3, y: contentView.height-60, width: contentView.width-6, height: 30 ) let imageSize = contentView.height-70 playlistCoverImageView.frame = CGRect( x: (contentView.width-imageSize)/2, y: 3, width: imageSize, height: imageSize ) } override func prepareForReuse() { super.prepareForReuse() playlistNameLabel.text = nil playlistCoverImageView.image = nil creatorNameLabel.text = nil } func configure(with viewModel: FeaturedPlaylistCellViewModel) { playlistNameLabel.text = viewModel.name playlistCoverImageView.sd_setImage(with: viewModel.artworkURL, completed: nil) creatorNameLabel.text = viewModel.creatorName } } ================================================ FILE: Spotify/Views/Browse/NewReleaseCollectionViewCell.swift ================================================ // // NewReleaseCollectionViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import UIKit import SDWebImage class NewReleaseCollectionViewCell: UICollectionViewCell { static let identifier = "NewReleaseCollectionViewCell" private let albumCoverImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(systemName: "photo") imageView.contentMode = .scaleAspectFill return imageView }() private let albumNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 20, weight: .semibold) return label }() private let numberOfTracksLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 18, weight: .thin) label.numberOfLines = 0 return label }() private let artistNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 18, weight: .light) return label }() override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .secondarySystemBackground contentView.addSubview(albumCoverImageView) contentView.addSubview(albumNameLabel) contentView.addSubview(artistNameLabel) contentView.clipsToBounds = true contentView.addSubview(numberOfTracksLabel) } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() let imageSize: CGFloat = contentView.height-10 let albumLabelSize = albumNameLabel.sizeThatFits( CGSize( width: contentView.width-imageSize-10, height: contentView.height-10 ) ) artistNameLabel.sizeToFit() numberOfTracksLabel.sizeToFit() // Image albumCoverImageView.frame = CGRect(x: 5, y: 5, width: imageSize, height: imageSize) // Album name label let albumLabelHeight = min(60, albumLabelSize.height) albumNameLabel.frame = CGRect( x: albumCoverImageView.right+10, y: 5, width: albumLabelSize.width, height: albumLabelHeight ) artistNameLabel.frame = CGRect( x: albumCoverImageView.right+10, y: albumNameLabel.bottom, width: contentView.width - albumCoverImageView.right-10, height: 30 ) numberOfTracksLabel.frame = CGRect( x: albumCoverImageView.right+10, y: contentView.bottom-44, width: numberOfTracksLabel.width, height: 44 ) } override func prepareForReuse() { super.prepareForReuse() albumNameLabel.text = nil artistNameLabel.text = nil numberOfTracksLabel.text = nil albumCoverImageView.image = nil } func configure(with viewModel: NewReleasesCellViewModel) { albumNameLabel.text = viewModel.name artistNameLabel.text = viewModel.artistName numberOfTracksLabel.text = "Tracks: \(viewModel.numberOfTracks)" albumCoverImageView.sd_setImage(with: viewModel.artworkURL, completed: nil) } } ================================================ FILE: Spotify/Views/Browse/RecommendedTrackCollectionViewCell.swift ================================================ // // RecommendedTrackCollectionViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/15/21. // import UIKit class RecommendedTrackCollectionViewCell: UICollectionViewCell { static let identifier = "RecommendedTrackCollectionViewCell" private let albumCoverImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(systemName: "photo") imageView.contentMode = .scaleAspectFill return imageView }() private let trackNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 18, weight: .regular) return label }() private let artistNameLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = .systemFont(ofSize: 15, weight: .thin) return label }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .secondarySystemBackground contentView.backgroundColor = .secondarySystemBackground contentView.addSubview(albumCoverImageView) contentView.addSubview(trackNameLabel) contentView.addSubview(artistNameLabel) contentView.clipsToBounds = true } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() albumCoverImageView.frame = CGRect(x: 5, y: 2, width: contentView.height-4, height: contentView.height-4) trackNameLabel.frame = CGRect( x: albumCoverImageView.right+10, y: 0, width: contentView.width-albumCoverImageView.right-15, height: contentView.height/2 ) artistNameLabel.frame = CGRect( x: albumCoverImageView.right+10, y: contentView.height/2, width: contentView.width-albumCoverImageView.right-15, height: contentView.height/2 ) } override func prepareForReuse() { super.prepareForReuse() trackNameLabel.text = nil albumCoverImageView.image = nil artistNameLabel.text = nil } func configure(with viewModel: RecommendedTrackCellViewModel) { trackNameLabel.text = viewModel.name albumCoverImageView.sd_setImage(with: viewModel.artworkURL, completed: nil) artistNameLabel.text = viewModel.artistName } } ================================================ FILE: Spotify/Views/GenreCollectionViewCell.swift ================================================ // // CategoryCollectionViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import UIKit import SDWebImage class CategoryCollectionViewCell: UICollectionViewCell { static let identifier = "CategoryCollectionViewCell" private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageView.tintColor = .white imageView.image = UIImage(systemName: "music.quarternote.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .regular)) return imageView }() private let label: UILabel = { let label = UILabel() label.textColor = .white label.font = .systemFont(ofSize: 22, weight: .semibold) return label }() private let colors: [UIColor] = [ .systemPink, .systemBlue, .systemPurple, .systemOrange, .systemGreen, .systemRed, .systemYellow, .darkGray, .systemTeal ] override init(frame: CGRect) { super.init(frame: frame) contentView.layer.cornerRadius = 8 contentView.layer.masksToBounds = true contentView.addSubview(label) contentView.addSubview(imageView) } required init?(coder: NSCoder) { fatalError() } override func prepareForReuse() { super.prepareForReuse() label.text = nil imageView.image = UIImage(systemName: "music.quarternote.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .regular)) } override func layoutSubviews() { super.layoutSubviews() label.frame = CGRect(x: 10, y: contentView.height/2, width: contentView.width-20, height: contentView.height/2) imageView.frame = CGRect(x: contentView.width/2, y: 10, width: contentView.width/2, height: contentView.height/2) } func configure(with viewModel: CategoryCollectionViewCellViewModel) { label.text = viewModel.title imageView.sd_setImage(with: viewModel.artworkURL, completed: nil) contentView.backgroundColor = colors.randomElement() } } ================================================ FILE: Spotify/Views/LibraryToggleView.swift ================================================ // // LibraryToggleView.swift // Spotify // // Created by Afraz Siddiqui on 2/21/21. // import UIKit protocol LibraryToggleViewDelegate: AnyObject { func libraryToggleViewDidTapPlaylists(_ toggleView: LibraryToggleView) func libraryToggleViewDidTapAlbums(_ toggleView: LibraryToggleView) } class LibraryToggleView: UIView { enum State { case playlist case album } var state: State = .playlist weak var delegate: LibraryToggleViewDelegate? private let playlistButton: UIButton = { let button = UIButton() button.setTitleColor(.label, for: .normal) button.setTitle("Playlists", for: .normal) return button }() private let albumsButton: UIButton = { let button = UIButton() button.setTitleColor(.label, for: .normal) button.setTitle("Albums", for: .normal) return button }() private let indicatorView: UIView = { let view = UIView() view.backgroundColor = .systemGreen view.layer.masksToBounds = true view.layer.cornerRadius = 4 return view }() override init(frame: CGRect) { super.init(frame: frame) addSubview(playlistButton) addSubview(albumsButton) addSubview(indicatorView) playlistButton.addTarget(self, action: #selector(didTapPlaylists), for: .touchUpInside) albumsButton.addTarget(self, action: #selector(didTapAlbums), for: .touchUpInside) } required init?(coder: NSCoder) { fatalError() } @objc private func didTapPlaylists() { state = .playlist UIView.animate(withDuration: 0.2) { self.layoutIndicator() } delegate?.libraryToggleViewDidTapPlaylists(self) } @objc private func didTapAlbums() { state = .album UIView.animate(withDuration: 0.2) { self.layoutIndicator() } delegate?.libraryToggleViewDidTapAlbums(self) } override func layoutSubviews() { super.layoutSubviews() playlistButton.frame = CGRect(x: 0, y: 0, width: 100, height: 40) albumsButton.frame = CGRect(x: playlistButton.right, y: 0, width: 100, height: 40) layoutIndicator() } func layoutIndicator() { switch state { case .playlist: indicatorView.frame = CGRect( x: 0, y: playlistButton.bottom, width: 100, height: 3 ) case .album: indicatorView.frame = CGRect( x: 100, y: playlistButton.bottom, width: 100, height: 3 ) } } func update(for state: State) { self.state = state UIView.animate(withDuration: 0.2) { self.layoutIndicator() } } } ================================================ FILE: Spotify/Views/PlayerControlsView.swift ================================================ // // PlayerControlsView.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import Foundation import UIKit protocol PlayerControlsViewDelegate: AnyObject { func playerControlsViewDidTapPlayPauseButton(_ playerControlsView: PlayerControlsView) func playerControlsViewDidTapForwardButton(_ playerControlsView: PlayerControlsView) func playerControlsViewDidTapBackwardsButton(_ playerControlsView: PlayerControlsView) func playerControlsView(_ playerControlsView: PlayerControlsView, didSlideSlider value: Float) } struct PlayerControlsViewViewModel { let title: String? let subtitle: String? } final class PlayerControlsView: UIView { private var isPlaying = true weak var delegate: PlayerControlsViewDelegate? private let volumeSlider: UISlider = { let slider = UISlider() slider.value = 0.5 return slider }() private let nameLabel: UILabel = { let label = UILabel() label.text = "This Is My Song" label.numberOfLines = 1 label.font = .systemFont(ofSize: 20, weight: .semibold) return label }() private let subtitleLabel: UILabel = { let label = UILabel() label.text = "Drake (feat. Some Other Artist)" label.numberOfLines = 1 label.font = .systemFont(ofSize: 18, weight: .regular) label.textColor = .secondaryLabel return label }() private let backButton: UIButton = { let button = UIButton() button.tintColor = .label let image = UIImage(systemName: "backward.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular)) button.setImage(image, for: .normal) return button }() private let nextButton: UIButton = { let button = UIButton() button.tintColor = .label let image = UIImage(systemName: "forward.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular)) button.setImage(image, for: .normal) return button }() private let playPauseButton: UIButton = { let button = UIButton() button.tintColor = .label let image = UIImage(systemName: "pause", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular)) button.setImage(image, for: .normal) return button }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear addSubview(nameLabel) addSubview(subtitleLabel) addSubview(volumeSlider) volumeSlider.addTarget(self, action: #selector(didSlideSlider(_:)), for: .valueChanged) addSubview(backButton) addSubview(nextButton) addSubview(playPauseButton) backButton.addTarget(self, action: #selector(didTapBack), for: .touchUpInside) nextButton.addTarget(self, action: #selector(didTapNext), for: .touchUpInside) playPauseButton.addTarget(self, action: #selector(didTapPlayPause), for: .touchUpInside) clipsToBounds = true } required init?(coder: NSCoder) { fatalError() } @objc func didSlideSlider(_ slider: UISlider) { let value = slider.value delegate?.playerControlsView(self, didSlideSlider: value) } @objc private func didTapBack() { delegate?.playerControlsViewDidTapBackwardsButton(self) } @objc private func didTapNext() { delegate?.playerControlsViewDidTapForwardButton(self) } @objc private func didTapPlayPause() { self.isPlaying = !isPlaying delegate?.playerControlsViewDidTapPlayPauseButton(self) // Update icon let pause = UIImage(systemName: "pause", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular)) let play = UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular)) playPauseButton.setImage(isPlaying ? pause : play, for: .normal) } override func layoutSubviews() { super.layoutSubviews() nameLabel.frame = CGRect(x: 0, y: 0, width: width, height: 50) subtitleLabel.frame = CGRect(x: 0, y: nameLabel.bottom+10, width: width, height: 50) volumeSlider.frame = CGRect(x: 10, y: subtitleLabel.bottom+20, width: width-20, height: 44) let buttonSize: CGFloat = 60 playPauseButton.frame = CGRect(x: (width - buttonSize)/2, y: volumeSlider.bottom + 30, width: buttonSize, height: buttonSize) backButton.frame = CGRect(x: playPauseButton.left-80-buttonSize, y: playPauseButton.top, width: buttonSize, height: buttonSize) nextButton.frame = CGRect(x: playPauseButton.right+80, y: playPauseButton.top, width: buttonSize, height: buttonSize) } func configure(with viewModel: PlayerControlsViewViewModel) { nameLabel.text = viewModel.title subtitleLabel.text = viewModel.subtitle } } ================================================ FILE: Spotify/Views/PlaylistHeaderCollectionReusableView.swift ================================================ // // PlaylistHeaderCollectionReusableView.swift // Spotify // // Created by Afraz Siddiqui on 2/18/21. // import SDWebImage import UIKit protocol PlaylistHeaderCollectionReusableViewDelegate: AnyObject { func playlistHeaderCollectionReusableViewDidTapPlayAll(_ header: PlaylistHeaderCollectionReusableView) } final class PlaylistHeaderCollectionReusableView: UICollectionReusableView { static let identifier = "PlaylistHeaderCollectionReusableView" weak var delegate: PlaylistHeaderCollectionReusableViewDelegate? private let nameLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 22, weight: .semibold) return label }() private let descriptionLabel: UILabel = { let label = UILabel() label.textColor = .secondaryLabel label.font = .systemFont(ofSize: 18, weight: .regular) label.numberOfLines = 0 return label }() private let ownerLabel: UILabel = { let label = UILabel() label.textColor = .secondaryLabel label.font = .systemFont(ofSize: 18, weight: .light) return label }() private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.image = UIImage(systemName: "photo") return imageView }() private let playAllButton: UIButton = { let button = UIButton() button.backgroundColor = .systemGreen let image = UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .regular)) button.setImage(image, for: .normal) button.tintColor = .white button.layer.cornerRadius = 30 button.layer.masksToBounds = true return button }() // MARK: - Init override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .systemBackground addSubview(imageView) addSubview(nameLabel) addSubview(descriptionLabel) addSubview(ownerLabel) addSubview(playAllButton) playAllButton.addTarget(self, action: #selector(didTapPlayAll), for: .touchUpInside) } required init?(coder: NSCoder) { fatalError() } @objc private func didTapPlayAll() { delegate?.playlistHeaderCollectionReusableViewDidTapPlayAll(self) } override func layoutSubviews() { super.layoutSubviews() let imageSize: CGFloat = height/1.8 imageView.frame = CGRect(x: (width-imageSize)/2, y: 20, width: imageSize, height: imageSize) nameLabel.frame = CGRect(x: 10, y: imageView.bottom, width: width-20, height: 44) descriptionLabel.frame = CGRect(x: 10, y: nameLabel.bottom, width: width-20, height: 44) ownerLabel.frame = CGRect(x: 10, y: descriptionLabel.bottom, width: width-20, height: 44) playAllButton.frame = CGRect(x: width-80, y: height-80, width: 60, height: 60) } func configure(with viewModel: PlaylistHeaderViewViewModel) { nameLabel.text = viewModel.name ownerLabel.text = viewModel.ownerName descriptionLabel.text = viewModel.description imageView.sd_setImage(with: viewModel.artworkURL, placeholderImage: UIImage(systemName: "photo"), completed: nil) } } ================================================ FILE: Spotify/Views/SearchResultCells/SearchResultDefaultTableViewCell.swift ================================================ // // SearchResultDefaultTableViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import UIKit import SDWebImage class SearchResultDefaultTableViewCell: UITableViewCell { static let identfier = "SearchResultDefaultTableViewCell" private let label: UILabel = { let label = UILabel() label.numberOfLines = 1 return label }() private let iconImageViewe: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill return imageView }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(label) contentView.addSubview(iconImageViewe) contentView.clipsToBounds = true accessoryType = .disclosureIndicator } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() let imageSize: CGFloat = contentView.height-10 iconImageViewe.frame = CGRect( x: 10, y: 5, width: imageSize, height: imageSize ) iconImageViewe.layer.cornerRadius = imageSize/2 iconImageViewe.layer.masksToBounds = true label.frame = CGRect(x: iconImageViewe.right+10, y: 0, width: contentView.width-iconImageViewe.right-15, height: contentView.height) } override func prepareForReuse() { super.prepareForReuse() iconImageViewe.image = nil label.text = nil } func configure(with viewModel: SearchResultDefaultTableViewCellViewModel) { label.text = viewModel.title iconImageViewe.sd_setImage(with: viewModel.imageURL, completed: nil) } } ================================================ FILE: Spotify/Views/SearchResultCells/SearchResultSubtitleTableViewCell.swift ================================================ // // SearchResultSubtitleTableViewCell.swift // Spotify // // Created by Afraz Siddiqui on 2/20/21. // import UIKit import SDWebImage class SearchResultSubtitleTableViewCell: UITableViewCell { static let identfier = "SearchResultSubtitleTableViewCell" private let label: UILabel = { let label = UILabel() label.numberOfLines = 1 return label }() private let subtitleLabel: UILabel = { let label = UILabel() label.textColor = .secondaryLabel label.numberOfLines = 1 return label }() private let iconImageViewe: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill return imageView }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(label) contentView.addSubview(subtitleLabel) contentView.addSubview(iconImageViewe) contentView.clipsToBounds = true accessoryType = .disclosureIndicator } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() let imageSize: CGFloat = contentView.height-10 iconImageViewe.frame = CGRect( x: 10, y: 5, width: imageSize, height: imageSize ) let labelHeight = contentView.height/2 label.frame = CGRect( x: iconImageViewe.right+10, y: 0, width: contentView.width-iconImageViewe.right-15, height: labelHeight ) subtitleLabel.frame = CGRect( x: iconImageViewe.right+10, y: label.bottom, width: contentView.width-iconImageViewe.right-15, height: labelHeight ) } override func prepareForReuse() { super.prepareForReuse() iconImageViewe.image = nil label.text = nil subtitleLabel.text = nil } func configure(with viewModel: SearchResultSubtitleTableViewCellViewModel) { label.text = viewModel.title subtitleLabel.text = viewModel.subtitle iconImageViewe.sd_setImage(with: viewModel.imageURL, placeholderImage: UIImage(systemName: "photo"), completed: nil) } } ================================================ FILE: Spotify/Views/TitleHeaderCollectionReusableView.swift ================================================ // // TitleHeaderCollectionReusableView.swift // Spotify // // Created by Afraz Siddiqui on 2/19/21. // import UIKit class TitleHeaderCollectionReusableView: UICollectionReusableView { static let identifier = "TitleHeaderCollectionReusableView" private let label: UILabel = { let label = UILabel() label.textColor = .label label.numberOfLines = 1 label.font = .systemFont(ofSize: 22, weight: .regular) return label }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .systemBackground addSubview(label) } required init?(coder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() label.frame = CGRect(x: 15, y: 0, width: width-30, height: height) } func configure(with title: String) { label.text = title } } ================================================ FILE: Spotify.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXBuildFile section */ 8305D6E626E172CE006C162D /* SpotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8305D6E526E172CE006C162D /* SpotifyTests.swift */; }; 8309BFC325E0555500767482 /* AllCategoriesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8309BFC225E0555500767482 /* AllCategoriesResponse.swift */; }; 8309BFC625E0582300767482 /* CategoryCollectionViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8309BFC525E0582300767482 /* CategoryCollectionViewCellViewModel.swift */; }; 8309BFC925E058F700767482 /* CategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8309BFC825E058F700767482 /* CategoryViewController.swift */; }; 8309C02C25E082E300767482 /* SearchResultResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8309C02B25E082E300767482 /* SearchResultResponse.swift */; }; 8309C03025E083DF00767482 /* SearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8309C02F25E083DF00767482 /* SearchResult.swift */; }; 830A09E925E1ABBF00D37663 /* SearchResultDefaultTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09E825E1ABBF00D37663 /* SearchResultDefaultTableViewCell.swift */; }; 830A09EC25E1ACEA00D37663 /* SearchResultDefaultTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09EB25E1ACEA00D37663 /* SearchResultDefaultTableViewCellViewModel.swift */; }; 830A09EF25E1AEE400D37663 /* SearchResultSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09EE25E1AEE400D37663 /* SearchResultSubtitleTableViewCell.swift */; }; 830A09F225E1AF8B00D37663 /* SearchResultSubtitleTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09F125E1AF8B00D37663 /* SearchResultSubtitleTableViewCellViewModel.swift */; }; 830A09F625E1B2D000D37663 /* PlaybackPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09F525E1B2D000D37663 /* PlaybackPresenter.swift */; }; 830A09FA25E1B72E00D37663 /* PlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830A09F925E1B72E00D37663 /* PlayerControlsView.swift */; }; 8325981C25E01BA600A091F6 /* TitleHeaderCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8325981B25E01BA600A091F6 /* TitleHeaderCollectionReusableView.swift */; }; 8325981F25E01F4900A091F6 /* AlbumTrackCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8325981E25E01F4900A091F6 /* AlbumTrackCollectionViewCell.swift */; }; 8325982225E01FF300A091F6 /* AlbumCollectionViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8325982125E01FF300A091F6 /* AlbumCollectionViewCellViewModel.swift */; }; 8325982525E0294D00A091F6 /* GenreCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8325982425E0294D00A091F6 /* GenreCollectionViewCell.swift */; }; 834DA84325DAAD36003CF18B /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA84225DAAD36003CF18B /* SettingsModels.swift */; }; 834DA85525DAD4A7003CF18B /* NewReleasesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA85425DAD4A7003CF18B /* NewReleasesResponse.swift */; }; 834DA85825DAD574003CF18B /* APIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA85725DAD574003CF18B /* APIImage.swift */; }; 834DA85C25DAD73A003CF18B /* FeaturedPlaylistsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA85B25DAD73A003CF18B /* FeaturedPlaylistsResponse.swift */; }; 834DA85F25DADA39003CF18B /* RecommendedGenresResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA85E25DADA39003CF18B /* RecommendedGenresResponse.swift */; }; 834DA86225DADD41003CF18B /* RecommendationsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA86125DADD41003CF18B /* RecommendationsResponse.swift */; }; 834DA86625DAE8DE003CF18B /* NewReleaseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA86525DAE8DE003CF18B /* NewReleaseCollectionViewCell.swift */; }; 834DA86A25DAE8F1003CF18B /* FeaturedPlaylistCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA86925DAE8F1003CF18B /* FeaturedPlaylistCollectionViewCell.swift */; }; 834DA86D25DAE908003CF18B /* RecommendedTrackCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA86C25DAE908003CF18B /* RecommendedTrackCollectionViewCell.swift */; }; 834DA87025DB0BED003CF18B /* NewReleasesCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA86F25DB0BED003CF18B /* NewReleasesCellViewModel.swift */; }; 834DA87325DB1904003CF18B /* FeaturedPlaylistCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA87225DB1904003CF18B /* FeaturedPlaylistCellViewModel.swift */; }; 834DA87625DB1942003CF18B /* RecommendedTrackCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA87525DB1942003CF18B /* RecommendedTrackCellViewModel.swift */; }; 834DA87925DB2FF7003CF18B /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA87825DB2FF7003CF18B /* AlbumViewController.swift */; }; 834DA87C25DB3235003CF18B /* AlbumDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA87B25DB3235003CF18B /* AlbumDetailsResponse.swift */; }; 834DA87F25DB3528003CF18B /* PlaylistDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DA87E25DB3528003CF18B /* PlaylistDetailsResponse.swift */; }; 83962E3925D96D4400A0F09E /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E3825D96D4400A0F09E /* TabBarViewController.swift */; }; 83962E3D25D96D6200A0F09E /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E3C25D96D6200A0F09E /* SearchViewController.swift */; }; 83962E4025D96D7000A0F09E /* LibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E3F25D96D7000A0F09E /* LibraryViewController.swift */; }; 83962E4625D96D9400A0F09E /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E4525D96D9400A0F09E /* AuthViewController.swift */; }; 83962E4A25D96D9F00A0F09E /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E4925D96D9F00A0F09E /* WelcomeViewController.swift */; }; 83962E4D25D96DB000A0F09E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E4C25D96DB000A0F09E /* SettingsViewController.swift */; }; 83962E5025D96DBB00A0F09E /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E4F25D96DBB00A0F09E /* ProfileViewController.swift */; }; 83962E5325D96DC500A0F09E /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E5225D96DC500A0F09E /* PlayerViewController.swift */; }; 83962E5625D96DD300A0F09E /* PlaylistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E5525D96DD300A0F09E /* PlaylistViewController.swift */; }; 83962E5925D96DE600A0F09E /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E5825D96DE600A0F09E /* SearchResultsViewController.swift */; }; 83962E5C25D96E0400A0F09E /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E5B25D96E0400A0F09E /* AuthManager.swift */; }; 83962E5F25D96E1000A0F09E /* APICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E5E25D96E1000A0F09E /* APICaller.swift */; }; 83962E6225D96E1E00A0F09E /* HapticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E6125D96E1E00A0F09E /* HapticsManager.swift */; }; 83962E6525D96E3500A0F09E /* Playlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E6425D96E3500A0F09E /* Playlist.swift */; }; 83962E6825D96E4100A0F09E /* AudioTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E6725D96E4100A0F09E /* AudioTrack.swift */; }; 83962E6B25D96E4E00A0F09E /* Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E6A25D96E4E00A0F09E /* Artist.swift */; }; 83962E6E25D96E5800A0F09E /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83962E6D25D96E5800A0F09E /* UserProfile.swift */; }; 839BA20625D96C1500BA56A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839BA20525D96C1500BA56A5 /* AppDelegate.swift */; }; 839BA20825D96C1500BA56A5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839BA20725D96C1500BA56A5 /* SceneDelegate.swift */; }; 839BA20A25D96C1500BA56A5 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839BA20925D96C1500BA56A5 /* HomeViewController.swift */; }; 839BA20F25D96C1600BA56A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 839BA20E25D96C1600BA56A5 /* Assets.xcassets */; }; 839BA21225D96C1600BA56A5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 839BA21025D96C1600BA56A5 /* LaunchScreen.storyboard */; }; 83C02EA125DEA7D5009DC56A /* PlaylistHeaderCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C02EA025DEA7D5009DC56A /* PlaylistHeaderCollectionReusableView.swift */; }; 83C02EA425DEA97A009DC56A /* PlaylistHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C02EA325DEA97A009DC56A /* PlaylistHeaderViewViewModel.swift */; }; 83DAECA225D9751500A55E5A /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DAECA125D9751500A55E5A /* Extensions.swift */; }; 83DAECA525D97DC300A55E5A /* AuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DAECA425D97DC300A55E5A /* AuthResponse.swift */; }; 83E7A16225E2BBE7007EF414 /* LibraryPlaylistsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A16125E2BBE7007EF414 /* LibraryPlaylistsViewController.swift */; }; 83E7A16625E2BC00007EF414 /* LibraryAlbumsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A16525E2BC00007EF414 /* LibraryAlbumsViewController.swift */; }; 83E7A16925E2BE0F007EF414 /* LibraryToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A16825E2BE0F007EF414 /* LibraryToggleView.swift */; }; 83E7A16C25E2C4C3007EF414 /* LibraryPlaylistsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A16B25E2C4C3007EF414 /* LibraryPlaylistsResponse.swift */; }; 83E7A16F25E2C5C0007EF414 /* ActionLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A16E25E2C5C0007EF414 /* ActionLabelView.swift */; }; 83E7A17225E3022F007EF414 /* LibraryAlbumsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7A17125E3022F007EF414 /* LibraryAlbumsResponse.swift */; }; B155E499983AD7C0E9CC2978 /* Pods_Spotify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B58F92C4DA5A3AFC0EF74F2 /* Pods_Spotify.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 8305D6E726E172CE006C162D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 839BA1FA25D96C1500BA56A5 /* Project object */; proxyType = 1; remoteGlobalIDString = 839BA20125D96C1500BA56A5; remoteInfo = Spotify; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 26823DE37DC5DC6B3B050E92 /* Pods-Spotify.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spotify.release.xcconfig"; path = "Target Support Files/Pods-Spotify/Pods-Spotify.release.xcconfig"; sourceTree = ""; }; 8305D6E326E172CE006C162D /* SpotifyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SpotifyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8305D6E526E172CE006C162D /* SpotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyTests.swift; sourceTree = ""; }; 8309BFC225E0555500767482 /* AllCategoriesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllCategoriesResponse.swift; sourceTree = ""; }; 8309BFC525E0582300767482 /* CategoryCollectionViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryCollectionViewCellViewModel.swift; sourceTree = ""; }; 8309BFC825E058F700767482 /* CategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryViewController.swift; sourceTree = ""; }; 8309C02B25E082E300767482 /* SearchResultResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultResponse.swift; sourceTree = ""; }; 8309C02F25E083DF00767482 /* SearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResult.swift; sourceTree = ""; }; 830A09E825E1ABBF00D37663 /* SearchResultDefaultTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultDefaultTableViewCell.swift; sourceTree = ""; }; 830A09EB25E1ACEA00D37663 /* SearchResultDefaultTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultDefaultTableViewCellViewModel.swift; sourceTree = ""; }; 830A09EE25E1AEE400D37663 /* SearchResultSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSubtitleTableViewCell.swift; sourceTree = ""; }; 830A09F125E1AF8B00D37663 /* SearchResultSubtitleTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSubtitleTableViewCellViewModel.swift; sourceTree = ""; }; 830A09F525E1B2D000D37663 /* PlaybackPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackPresenter.swift; sourceTree = ""; }; 830A09F925E1B72E00D37663 /* PlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsView.swift; sourceTree = ""; }; 8325981B25E01BA600A091F6 /* TitleHeaderCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleHeaderCollectionReusableView.swift; sourceTree = ""; }; 8325981E25E01F4900A091F6 /* AlbumTrackCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTrackCollectionViewCell.swift; sourceTree = ""; }; 8325982125E01FF300A091F6 /* AlbumCollectionViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumCollectionViewCellViewModel.swift; sourceTree = ""; }; 8325982425E0294D00A091F6 /* GenreCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenreCollectionViewCell.swift; sourceTree = ""; }; 834DA84225DAAD36003CF18B /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = ""; }; 834DA85425DAD4A7003CF18B /* NewReleasesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewReleasesResponse.swift; sourceTree = ""; }; 834DA85725DAD574003CF18B /* APIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIImage.swift; sourceTree = ""; }; 834DA85B25DAD73A003CF18B /* FeaturedPlaylistsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedPlaylistsResponse.swift; sourceTree = ""; }; 834DA85E25DADA39003CF18B /* RecommendedGenresResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedGenresResponse.swift; sourceTree = ""; }; 834DA86125DADD41003CF18B /* RecommendationsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendationsResponse.swift; sourceTree = ""; }; 834DA86525DAE8DE003CF18B /* NewReleaseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewReleaseCollectionViewCell.swift; sourceTree = ""; }; 834DA86925DAE8F1003CF18B /* FeaturedPlaylistCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedPlaylistCollectionViewCell.swift; sourceTree = ""; }; 834DA86C25DAE908003CF18B /* RecommendedTrackCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedTrackCollectionViewCell.swift; sourceTree = ""; }; 834DA86F25DB0BED003CF18B /* NewReleasesCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewReleasesCellViewModel.swift; sourceTree = ""; }; 834DA87225DB1904003CF18B /* FeaturedPlaylistCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedPlaylistCellViewModel.swift; sourceTree = ""; }; 834DA87525DB1942003CF18B /* RecommendedTrackCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedTrackCellViewModel.swift; sourceTree = ""; }; 834DA87825DB2FF7003CF18B /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = ""; }; 834DA87B25DB3235003CF18B /* AlbumDetailsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailsResponse.swift; sourceTree = ""; }; 834DA87E25DB3528003CF18B /* PlaylistDetailsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistDetailsResponse.swift; sourceTree = ""; }; 83776ED7A5A9E57291488C82 /* Pods-Spotify.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Spotify.debug.xcconfig"; path = "Target Support Files/Pods-Spotify/Pods-Spotify.debug.xcconfig"; sourceTree = ""; }; 83962E3825D96D4400A0F09E /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = ""; }; 83962E3C25D96D6200A0F09E /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; 83962E3F25D96D7000A0F09E /* LibraryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewController.swift; sourceTree = ""; }; 83962E4525D96D9400A0F09E /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 83962E4925D96D9F00A0F09E /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 83962E4C25D96DB000A0F09E /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 83962E4F25D96DBB00A0F09E /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 83962E5225D96DC500A0F09E /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = ""; }; 83962E5525D96DD300A0F09E /* PlaylistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistViewController.swift; sourceTree = ""; }; 83962E5825D96DE600A0F09E /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = ""; }; 83962E5B25D96E0400A0F09E /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 83962E5E25D96E1000A0F09E /* APICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICaller.swift; sourceTree = ""; }; 83962E6125D96E1E00A0F09E /* HapticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsManager.swift; sourceTree = ""; }; 83962E6425D96E3500A0F09E /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = ""; }; 83962E6725D96E4100A0F09E /* AudioTrack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioTrack.swift; sourceTree = ""; }; 83962E6A25D96E4E00A0F09E /* Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Artist.swift; sourceTree = ""; }; 83962E6D25D96E5800A0F09E /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 839BA20225D96C1500BA56A5 /* Spotify.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spotify.app; sourceTree = BUILT_PRODUCTS_DIR; }; 839BA20525D96C1500BA56A5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 839BA20725D96C1500BA56A5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 839BA20925D96C1500BA56A5 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 839BA20E25D96C1600BA56A5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 839BA21125D96C1600BA56A5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 839BA21325D96C1600BA56A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 83C02EA025DEA7D5009DC56A /* PlaylistHeaderCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistHeaderCollectionReusableView.swift; sourceTree = ""; }; 83C02EA325DEA97A009DC56A /* PlaylistHeaderViewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistHeaderViewViewModel.swift; sourceTree = ""; }; 83DAECA125D9751500A55E5A /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 83DAECA425D97DC300A55E5A /* AuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthResponse.swift; sourceTree = ""; }; 83E7A16125E2BBE7007EF414 /* LibraryPlaylistsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPlaylistsViewController.swift; sourceTree = ""; }; 83E7A16525E2BC00007EF414 /* LibraryAlbumsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryAlbumsViewController.swift; sourceTree = ""; }; 83E7A16825E2BE0F007EF414 /* LibraryToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryToggleView.swift; sourceTree = ""; }; 83E7A16B25E2C4C3007EF414 /* LibraryPlaylistsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPlaylistsResponse.swift; sourceTree = ""; }; 83E7A16E25E2C5C0007EF414 /* ActionLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionLabelView.swift; sourceTree = ""; }; 83E7A17125E3022F007EF414 /* LibraryAlbumsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryAlbumsResponse.swift; sourceTree = ""; }; 9B58F92C4DA5A3AFC0EF74F2 /* Pods_Spotify.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Spotify.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8305D6E026E172CE006C162D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 839BA1FF25D96C1500BA56A5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B155E499983AD7C0E9CC2978 /* Pods_Spotify.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 55C258CBA1FF639EF844CC45 /* Frameworks */ = { isa = PBXGroup; children = ( 9B58F92C4DA5A3AFC0EF74F2 /* Pods_Spotify.framework */, ); name = Frameworks; sourceTree = ""; }; 8305D6E426E172CE006C162D /* SpotifyTests */ = { isa = PBXGroup; children = ( 8305D6E526E172CE006C162D /* SpotifyTests.swift */, ); path = SpotifyTests; sourceTree = ""; }; 830A09E725E1AB9E00D37663 /* SearchResultCells */ = { isa = PBXGroup; children = ( 830A09E825E1ABBF00D37663 /* SearchResultDefaultTableViewCell.swift */, 830A09EE25E1AEE400D37663 /* SearchResultSubtitleTableViewCell.swift */, ); path = SearchResultCells; sourceTree = ""; }; 830A09F425E1B2BD00D37663 /* Presenter */ = { isa = PBXGroup; children = ( 830A09F525E1B2D000D37663 /* PlaybackPresenter.swift */, ); path = Presenter; sourceTree = ""; }; 834DA86425DAE8CC003CF18B /* Browse */ = { isa = PBXGroup; children = ( 834DA86525DAE8DE003CF18B /* NewReleaseCollectionViewCell.swift */, 834DA86925DAE8F1003CF18B /* FeaturedPlaylistCollectionViewCell.swift */, 834DA86C25DAE908003CF18B /* RecommendedTrackCollectionViewCell.swift */, ); path = Browse; sourceTree = ""; }; 83962E4225D96D7600A0F09E /* Core */ = { isa = PBXGroup; children = ( 83962E3825D96D4400A0F09E /* TabBarViewController.swift */, 839BA20925D96C1500BA56A5 /* HomeViewController.swift */, 83962E3C25D96D6200A0F09E /* SearchViewController.swift */, 83962E3F25D96D7000A0F09E /* LibraryViewController.swift */, ); path = Core; sourceTree = ""; }; 83962E4425D96D8000A0F09E /* Other */ = { isa = PBXGroup; children = ( 83E7A16025E2BBD3007EF414 /* Library */, 83962E4525D96D9400A0F09E /* AuthViewController.swift */, 83962E4925D96D9F00A0F09E /* WelcomeViewController.swift */, 83962E4C25D96DB000A0F09E /* SettingsViewController.swift */, 83962E4F25D96DBB00A0F09E /* ProfileViewController.swift */, 83962E5225D96DC500A0F09E /* PlayerViewController.swift */, 83962E5525D96DD300A0F09E /* PlaylistViewController.swift */, 83962E5825D96DE600A0F09E /* SearchResultsViewController.swift */, 834DA87825DB2FF7003CF18B /* AlbumViewController.swift */, 8309BFC825E058F700767482 /* CategoryViewController.swift */, ); path = Other; sourceTree = ""; }; 839BA1F925D96C1500BA56A5 = { isa = PBXGroup; children = ( 839BA20425D96C1500BA56A5 /* Spotify */, 8305D6E426E172CE006C162D /* SpotifyTests */, 839BA20325D96C1500BA56A5 /* Products */, 8E19A93F9CD9274F548B6CAA /* Pods */, 55C258CBA1FF639EF844CC45 /* Frameworks */, ); sourceTree = ""; }; 839BA20325D96C1500BA56A5 /* Products */ = { isa = PBXGroup; children = ( 839BA20225D96C1500BA56A5 /* Spotify.app */, 8305D6E326E172CE006C162D /* SpotifyTests.xctest */, ); name = Products; sourceTree = ""; }; 839BA20425D96C1500BA56A5 /* Spotify */ = { isa = PBXGroup; children = ( 830A09F425E1B2BD00D37663 /* Presenter */, 839BA21F25D96C5400BA56A5 /* Resources */, 839BA21E25D96C4C00BA56A5 /* Managers */, 839BA21D25D96C4600BA56A5 /* ViewModels */, 839BA21C25D96C4200BA56A5 /* Models */, 839BA21B25D96C3D00BA56A5 /* Views */, 839BA21A25D96C3600BA56A5 /* Controllers */, 839BA21325D96C1600BA56A5 /* Info.plist */, ); path = Spotify; sourceTree = ""; }; 839BA21A25D96C3600BA56A5 /* Controllers */ = { isa = PBXGroup; children = ( 83962E4425D96D8000A0F09E /* Other */, 83962E4225D96D7600A0F09E /* Core */, ); path = Controllers; sourceTree = ""; }; 839BA21B25D96C3D00BA56A5 /* Views */ = { isa = PBXGroup; children = ( 830A09E725E1AB9E00D37663 /* SearchResultCells */, 834DA86425DAE8CC003CF18B /* Browse */, 839BA21025D96C1600BA56A5 /* LaunchScreen.storyboard */, 83C02EA025DEA7D5009DC56A /* PlaylistHeaderCollectionReusableView.swift */, 8325981B25E01BA600A091F6 /* TitleHeaderCollectionReusableView.swift */, 8325981E25E01F4900A091F6 /* AlbumTrackCollectionViewCell.swift */, 8325982425E0294D00A091F6 /* GenreCollectionViewCell.swift */, 830A09F925E1B72E00D37663 /* PlayerControlsView.swift */, 83E7A16825E2BE0F007EF414 /* LibraryToggleView.swift */, 83E7A16E25E2C5C0007EF414 /* ActionLabelView.swift */, ); path = Views; sourceTree = ""; }; 839BA21C25D96C4200BA56A5 /* Models */ = { isa = PBXGroup; children = ( 83962E6425D96E3500A0F09E /* Playlist.swift */, 83962E6725D96E4100A0F09E /* AudioTrack.swift */, 83962E6A25D96E4E00A0F09E /* Artist.swift */, 83962E6D25D96E5800A0F09E /* UserProfile.swift */, 83DAECA425D97DC300A55E5A /* AuthResponse.swift */, 834DA84225DAAD36003CF18B /* SettingsModels.swift */, 834DA85425DAD4A7003CF18B /* NewReleasesResponse.swift */, 834DA85725DAD574003CF18B /* APIImage.swift */, 834DA85B25DAD73A003CF18B /* FeaturedPlaylistsResponse.swift */, 834DA85E25DADA39003CF18B /* RecommendedGenresResponse.swift */, 834DA86125DADD41003CF18B /* RecommendationsResponse.swift */, 834DA87B25DB3235003CF18B /* AlbumDetailsResponse.swift */, 834DA87E25DB3528003CF18B /* PlaylistDetailsResponse.swift */, 8309BFC225E0555500767482 /* AllCategoriesResponse.swift */, 8309C02B25E082E300767482 /* SearchResultResponse.swift */, 8309C02F25E083DF00767482 /* SearchResult.swift */, 83E7A16B25E2C4C3007EF414 /* LibraryPlaylistsResponse.swift */, 83E7A17125E3022F007EF414 /* LibraryAlbumsResponse.swift */, ); path = Models; sourceTree = ""; }; 839BA21D25D96C4600BA56A5 /* ViewModels */ = { isa = PBXGroup; children = ( 834DA86F25DB0BED003CF18B /* NewReleasesCellViewModel.swift */, 834DA87225DB1904003CF18B /* FeaturedPlaylistCellViewModel.swift */, 834DA87525DB1942003CF18B /* RecommendedTrackCellViewModel.swift */, 83C02EA325DEA97A009DC56A /* PlaylistHeaderViewViewModel.swift */, 8325982125E01FF300A091F6 /* AlbumCollectionViewCellViewModel.swift */, 8309BFC525E0582300767482 /* CategoryCollectionViewCellViewModel.swift */, 830A09EB25E1ACEA00D37663 /* SearchResultDefaultTableViewCellViewModel.swift */, 830A09F125E1AF8B00D37663 /* SearchResultSubtitleTableViewCellViewModel.swift */, ); path = ViewModels; sourceTree = ""; }; 839BA21E25D96C4C00BA56A5 /* Managers */ = { isa = PBXGroup; children = ( 83962E5B25D96E0400A0F09E /* AuthManager.swift */, 83962E5E25D96E1000A0F09E /* APICaller.swift */, 83962E6125D96E1E00A0F09E /* HapticsManager.swift */, ); path = Managers; sourceTree = ""; }; 839BA21F25D96C5400BA56A5 /* Resources */ = { isa = PBXGroup; children = ( 839BA20E25D96C1600BA56A5 /* Assets.xcassets */, 839BA20525D96C1500BA56A5 /* AppDelegate.swift */, 839BA20725D96C1500BA56A5 /* SceneDelegate.swift */, 83DAECA125D9751500A55E5A /* Extensions.swift */, ); path = Resources; sourceTree = ""; }; 83E7A16025E2BBD3007EF414 /* Library */ = { isa = PBXGroup; children = ( 83E7A16125E2BBE7007EF414 /* LibraryPlaylistsViewController.swift */, 83E7A16525E2BC00007EF414 /* LibraryAlbumsViewController.swift */, ); path = Library; sourceTree = ""; }; 8E19A93F9CD9274F548B6CAA /* Pods */ = { isa = PBXGroup; children = ( 83776ED7A5A9E57291488C82 /* Pods-Spotify.debug.xcconfig */, 26823DE37DC5DC6B3B050E92 /* Pods-Spotify.release.xcconfig */, ); path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8305D6E226E172CE006C162D /* SpotifyTests */ = { isa = PBXNativeTarget; buildConfigurationList = 8305D6EB26E172CE006C162D /* Build configuration list for PBXNativeTarget "SpotifyTests" */; buildPhases = ( 8305D6DF26E172CE006C162D /* Sources */, 8305D6E026E172CE006C162D /* Frameworks */, 8305D6E126E172CE006C162D /* Resources */, ); buildRules = ( ); dependencies = ( 8305D6E826E172CE006C162D /* PBXTargetDependency */, ); name = SpotifyTests; productName = SpotifyTests; productReference = 8305D6E326E172CE006C162D /* SpotifyTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 839BA20125D96C1500BA56A5 /* Spotify */ = { isa = PBXNativeTarget; buildConfigurationList = 839BA21625D96C1600BA56A5 /* Build configuration list for PBXNativeTarget "Spotify" */; buildPhases = ( 19E7EAA90164C6F7BB5DDBC8 /* [CP] Check Pods Manifest.lock */, 839BA1FE25D96C1500BA56A5 /* Sources */, 839BA1FF25D96C1500BA56A5 /* Frameworks */, 839BA20025D96C1500BA56A5 /* Resources */, 7A1EE8C8D3B5091BF3FE3F35 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Spotify; productName = Spotify; productReference = 839BA20225D96C1500BA56A5 /* Spotify.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 839BA1FA25D96C1500BA56A5 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1240; TargetAttributes = { 8305D6E226E172CE006C162D = { CreatedOnToolsVersion = 13.0; TestTargetID = 839BA20125D96C1500BA56A5; }; 839BA20125D96C1500BA56A5 = { CreatedOnToolsVersion = 12.4; }; }; }; buildConfigurationList = 839BA1FD25D96C1500BA56A5 /* Build configuration list for PBXProject "Spotify" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 839BA1F925D96C1500BA56A5; productRefGroup = 839BA20325D96C1500BA56A5 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 839BA20125D96C1500BA56A5 /* Spotify */, 8305D6E226E172CE006C162D /* SpotifyTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8305D6E126E172CE006C162D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 839BA20025D96C1500BA56A5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 839BA21225D96C1600BA56A5 /* LaunchScreen.storyboard in Resources */, 839BA20F25D96C1600BA56A5 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 19E7EAA90164C6F7BB5DDBC8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Spotify-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 7A1EE8C8D3B5091BF3FE3F35 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Spotify/Pods-Spotify-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8305D6DF26E172CE006C162D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8305D6E626E172CE006C162D /* SpotifyTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 839BA1FE25D96C1500BA56A5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 83962E3D25D96D6200A0F09E /* SearchViewController.swift in Sources */, 834DA87F25DB3528003CF18B /* PlaylistDetailsResponse.swift in Sources */, 83962E6525D96E3500A0F09E /* Playlist.swift in Sources */, 83962E5C25D96E0400A0F09E /* AuthManager.swift in Sources */, 83E7A16925E2BE0F007EF414 /* LibraryToggleView.swift in Sources */, 834DA87025DB0BED003CF18B /* NewReleasesCellViewModel.swift in Sources */, 8325982225E01FF300A091F6 /* AlbumCollectionViewCellViewModel.swift in Sources */, 834DA87625DB1942003CF18B /* RecommendedTrackCellViewModel.swift in Sources */, 83E7A16F25E2C5C0007EF414 /* ActionLabelView.swift in Sources */, 83962E5925D96DE600A0F09E /* SearchResultsViewController.swift in Sources */, 83E7A17225E3022F007EF414 /* LibraryAlbumsResponse.swift in Sources */, 8309BFC925E058F700767482 /* CategoryViewController.swift in Sources */, 8309BFC325E0555500767482 /* AllCategoriesResponse.swift in Sources */, 8325981C25E01BA600A091F6 /* TitleHeaderCollectionReusableView.swift in Sources */, 834DA87925DB2FF7003CF18B /* AlbumViewController.swift in Sources */, 8309C02C25E082E300767482 /* SearchResultResponse.swift in Sources */, 83C02EA425DEA97A009DC56A /* PlaylistHeaderViewViewModel.swift in Sources */, 834DA87325DB1904003CF18B /* FeaturedPlaylistCellViewModel.swift in Sources */, 830A09F225E1AF8B00D37663 /* SearchResultSubtitleTableViewCellViewModel.swift in Sources */, 83962E4A25D96D9F00A0F09E /* WelcomeViewController.swift in Sources */, 83962E5025D96DBB00A0F09E /* ProfileViewController.swift in Sources */, 83962E3925D96D4400A0F09E /* TabBarViewController.swift in Sources */, 830A09E925E1ABBF00D37663 /* SearchResultDefaultTableViewCell.swift in Sources */, 83962E6225D96E1E00A0F09E /* HapticsManager.swift in Sources */, 834DA86D25DAE908003CF18B /* RecommendedTrackCollectionViewCell.swift in Sources */, 83DAECA525D97DC300A55E5A /* AuthResponse.swift in Sources */, 830A09FA25E1B72E00D37663 /* PlayerControlsView.swift in Sources */, 8325982525E0294D00A091F6 /* GenreCollectionViewCell.swift in Sources */, 834DA85525DAD4A7003CF18B /* NewReleasesResponse.swift in Sources */, 830A09EF25E1AEE400D37663 /* SearchResultSubtitleTableViewCell.swift in Sources */, 834DA85825DAD574003CF18B /* APIImage.swift in Sources */, 83E7A16625E2BC00007EF414 /* LibraryAlbumsViewController.swift in Sources */, 83E7A16C25E2C4C3007EF414 /* LibraryPlaylistsResponse.swift in Sources */, 8325981F25E01F4900A091F6 /* AlbumTrackCollectionViewCell.swift in Sources */, 834DA86225DADD41003CF18B /* RecommendationsResponse.swift in Sources */, 83962E6825D96E4100A0F09E /* AudioTrack.swift in Sources */, 839BA20A25D96C1500BA56A5 /* HomeViewController.swift in Sources */, 830A09EC25E1ACEA00D37663 /* SearchResultDefaultTableViewCellViewModel.swift in Sources */, 834DA86625DAE8DE003CF18B /* NewReleaseCollectionViewCell.swift in Sources */, 83962E4625D96D9400A0F09E /* AuthViewController.swift in Sources */, 834DA86A25DAE8F1003CF18B /* FeaturedPlaylistCollectionViewCell.swift in Sources */, 839BA20625D96C1500BA56A5 /* AppDelegate.swift in Sources */, 83962E6E25D96E5800A0F09E /* UserProfile.swift in Sources */, 83DAECA225D9751500A55E5A /* Extensions.swift in Sources */, 8309C03025E083DF00767482 /* SearchResult.swift in Sources */, 83C02EA125DEA7D5009DC56A /* PlaylistHeaderCollectionReusableView.swift in Sources */, 830A09F625E1B2D000D37663 /* PlaybackPresenter.swift in Sources */, 83962E4025D96D7000A0F09E /* LibraryViewController.swift in Sources */, 83962E6B25D96E4E00A0F09E /* Artist.swift in Sources */, 834DA84325DAAD36003CF18B /* SettingsModels.swift in Sources */, 83962E5625D96DD300A0F09E /* PlaylistViewController.swift in Sources */, 839BA20825D96C1500BA56A5 /* SceneDelegate.swift in Sources */, 83E7A16225E2BBE7007EF414 /* LibraryPlaylistsViewController.swift in Sources */, 83962E5325D96DC500A0F09E /* PlayerViewController.swift in Sources */, 834DA87C25DB3235003CF18B /* AlbumDetailsResponse.swift in Sources */, 83962E5F25D96E1000A0F09E /* APICaller.swift in Sources */, 834DA85C25DAD73A003CF18B /* FeaturedPlaylistsResponse.swift in Sources */, 83962E4D25D96DB000A0F09E /* SettingsViewController.swift in Sources */, 8309BFC625E0582300767482 /* CategoryCollectionViewCellViewModel.swift in Sources */, 834DA85F25DADA39003CF18B /* RecommendedGenresResponse.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 8305D6E826E172CE006C162D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 839BA20125D96C1500BA56A5 /* Spotify */; targetProxy = 8305D6E726E172CE006C162D /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 839BA21025D96C1600BA56A5 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 839BA21125D96C1600BA56A5 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 8305D6E926E172CE006C162D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 3798Z36XLV; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.iosacademy.SpotifyTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Spotify.app/Spotify"; }; name = Debug; }; 8305D6EA26E172CE006C162D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 3798Z36XLV; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = io.iosacademy.SpotifyTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Spotify.app/Spotify"; }; name = Release; }; 839BA21425D96C1600BA56A5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 839BA21525D96C1600BA56A5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 839BA21725D96C1600BA56A5 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 83776ED7A5A9E57291488C82 /* Pods-Spotify.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3798Z36XLV; INFOPLIST_FILE = Spotify/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = io.iosacademy.Spotify; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 839BA21825D96C1600BA56A5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 26823DE37DC5DC6B3B050E92 /* Pods-Spotify.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3798Z36XLV; INFOPLIST_FILE = Spotify/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = io.iosacademy.Spotify; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 8305D6EB26E172CE006C162D /* Build configuration list for PBXNativeTarget "SpotifyTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 8305D6E926E172CE006C162D /* Debug */, 8305D6EA26E172CE006C162D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 839BA1FD25D96C1500BA56A5 /* Build configuration list for PBXProject "Spotify" */ = { isa = XCConfigurationList; buildConfigurations = ( 839BA21425D96C1600BA56A5 /* Debug */, 839BA21525D96C1600BA56A5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 839BA21625D96C1600BA56A5 /* Build configuration list for PBXNativeTarget "Spotify" */ = { isa = XCConfigurationList; buildConfigurations = ( 839BA21725D96C1600BA56A5 /* Debug */, 839BA21825D96C1600BA56A5 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 839BA1FA25D96C1500BA56A5 /* Project object */; } ================================================ FILE: Spotify.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Spotify.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Spotify.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: Spotify.xcodeproj/xcuserdata/afrazsiddiqui.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Spotify.xcscheme_^#shared#^_ orderHint 14 ================================================ FILE: Spotify.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Spotify.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Spotify.xcworkspace/xcuserdata/afrazsiddiqui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: SpotifyTests/SpotifyTests.swift ================================================ // // SpotifyTests.swift // SpotifyTests // // Created by Afraz Siddiqui on 9/2/21. // @testable import Spotify import XCTest class SpotifyTests: XCTestCase { func testNewReleasesCellViewModel() { let viewModel = NewReleasesCellViewModel( name: "Album Title", artworkURL: nil, numberOfTracks: 12, artistName: "Drake" ) XCTAssertNotNil(viewModel) XCTAssertEqual(viewModel.name, "Album Title") XCTAssertNil(viewModel.artworkURL) XCTAssertEqual(viewModel.numberOfTracks, 12) XCTAssertEqual(viewModel.artistName, "Drake") } func testFeaturedPlaylistCellViewModel() { let viewModel = FeaturedPlaylistCellViewModel( name: "Summer Vibes", artworkURL: nil, creatorName: "All American" ) XCTAssertEqual(viewModel.name, "Summer Vibes") XCTAssertEqual(viewModel.creatorName, "All American") XCTAssertNil(viewModel.artworkURL) } func testMath() { XCTAssertEqual(7+2, 9) XCTAssertEqual(12 - 2, 10) } } ================================================ FILE: codemagic.yaml ================================================ # Customize the codemagic.yaml file according to your project and commit it to the root of your repository # Check out https://docs.codemagic.io/getting-started/yaml/ for more information workflows: spotify-workflow: name: Spotify Pipeline scripts: - name: iOS test script: | xcode-project run-tests \ --workspace Spotify.xcworkspace \ --scheme Spotify publishing: email: recipients: - hello@iosacademy.io ================================================ FILE: codemagic.yml ================================================ # Customize the codemagic.yaml file according to your project and commit it to the root of your repository # Check out https://docs.codemagic.io/getting-started/yaml/ for more information workflows: spotify-workflow: name: Spotify Pipeline scripts: - name: iOS test script: | xcode-project run-tests \ --workspace Spotify.xcworkspace \ --scheme Spotify \ --device "iPhone 11" publishing: email: recipients: - hello@iosacademy.io