From ad1401fc4de723aed54f9773594ddc94558ee821 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 24 Jan 2022 12:49:55 -0800 Subject: [PATCH 01/12] add GULMulticastAppDelegate class and SwiftUISample test app --- .gitignore | 2 + GoogleMulticastAppDelegate.podspec | 72 ++++ .../Apps/SwiftUISample/Podfile | 17 + .../SwiftUISample.xcodeproj/project.pbxproj | 368 ++++++++++++++++++ .../xcschemes/SwiftUISample.xcscheme | 84 ++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 98 +++++ .../Assets.xcassets/Contents.json | 6 + .../SwiftUISample/ContentView.swift | 76 ++++ .../SwiftUISample/SwiftUISample/Info.plist | 21 + .../SwiftUISample/MulticastAppDelegate.swift | 144 +++++++ .../Preview Assets.xcassets/Contents.json | 6 + .../SwiftUISample/SwiftUISample.entitlements | 8 + .../SwiftUISample/SwiftUISampleApp.swift | 103 +++++ .../Sources/GULMulticastAppDelegate.m | 96 +++++ .../GoogleUtilities/GULMulticastAppDelegate.h | 35 ++ 16 files changed, 1147 insertions(+) create mode 100644 GoogleMulticastAppDelegate.podspec create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements create mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift create mode 100644 GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m create mode 100644 GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h diff --git a/.gitignore b/.gitignore index 592d6c4a..c1adfae2 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ firebase-ios-sdk/ #scripts link scripts + +GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/GoogleService-Info.plist diff --git a/GoogleMulticastAppDelegate.podspec b/GoogleMulticastAppDelegate.podspec new file mode 100644 index 00000000..5addbfe6 --- /dev/null +++ b/GoogleMulticastAppDelegate.podspec @@ -0,0 +1,72 @@ +Pod::Spec.new do |s| + # TODO: Is `GoogleMulticastAppDelegate` name fine? + s.name = 'GoogleMulticastAppDelegate' + s.version = '7.5.0' + s.summary = 'GoogleMulticastAppDelegate' + + s.description = <<-DESC + GoogleMulticastAppDelegate + DESC + + s.homepage = 'https://github.com/google/GoogleUtilities' + s.license = { :type => 'Apache', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + s.source = { + :git => 'https://github.com/google/GoogleUtilities.git', + :tag => 'MulticastAppDelegate-' + s.version.to_s + } + + ios_deployment_target = '9.0' + osx_deployment_target = '10.12' + tvos_deployment_target = '10.0' + watchos_deployment_target = '6.0' + + s.ios.deployment_target = ios_deployment_target + # s.osx.deployment_target = osx_deployment_target + # s.tvos.deployment_target = tvos_deployment_target + # s.watchos.deployment_target = watchos_deployment_target + + s.swift_versions = ['5.0', '5.2'] + + s.cocoapods_version = '>= 1.4.0' + s.prefix_header_file = false + + s.pod_target_xcconfig = { + 'GCC_C_LANGUAGE_STANDARD' => 'c99', + 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', + } + + base_dir = "GoogleMulticastAppDelegate/" + s.source_files = [ + base_dir + 'Sources/**/*.[hm]', + ] + + # s.test_spec 'unit' do |unit_tests| + # unit_tests.scheme = { :code_coverage => true } + # unit_tests.platforms = { + # :ios => ios_deployment_target, + # :osx => osx_deployment_target, + # :tvos => tvos_deployment_target + # } + # unit_tests.source_files = [ + # base_dir + 'Tests/Unit/**/*.[mh]', + # ] + # unit_tests.requires_app_host = true + # unit_tests.dependency 'OCMock' + # end + + # s.test_spec 'unit-swift' do |unit_tests_swift| + # unit_tests_swift.scheme = { :code_coverage => true } + # unit_tests_swift.platforms = { + # :ios => ios_deployment_target, + # :osx => osx_deployment_target, + # :tvos => tvos_deployment_target + # } + # unit_tests_swift.source_files = [ + # base_dir + 'Tests/Unit/**/*.swift', + # ] + + # unit_tests_swift.requires_app_host = true + # end +end diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile b/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile new file mode 100644 index 00000000..d64c8ba1 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile @@ -0,0 +1,17 @@ +use_frameworks! + +source 'https://github.com/firebase/SpecsDev.git' +source 'https://github.com/firebase/SpecsStaging.git' +source 'https://cdn.cocoapods.org/' + +target 'SwiftUISample' do + platform :ios, '15.0' + + pod 'FirebaseAuth' + pod 'FirebaseCore' + pod 'FirebaseMessaging' + pod 'FirebaseStorage' + pod 'GoogleMulticastAppDelegate', :path => '../../../' + pod 'GoogleUtilities', :path => '../../../' + +end diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj new file mode 100644 index 00000000..8444f4ce --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj @@ -0,0 +1,368 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 51B0F0E02798DEE0003C703E /* SwiftUISampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */; }; + 51B0F0E22798DEE0003C703E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0F0E12798DEE0003C703E /* ContentView.swift */; }; + 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E32798DEE2003C703E /* Assets.xcassets */; }; + 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUISample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUISampleApp.swift; sourceTree = ""; }; + 51B0F0E12798DEE0003C703E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 51B0F0E32798DEE2003C703E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 51B0F0ED2798E127003C703E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUISample.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 51B0F0D92798DEE0003C703E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 51B0F0D32798DEE0003C703E = { + isa = PBXGroup; + children = ( + 51B0F0DE2798DEE0003C703E /* SwiftUISample */, + 51B0F0DD2798DEE0003C703E /* Products */, + A4E8028DB1B31DF418090DC0 /* Pods */, + ); + sourceTree = ""; + }; + 51B0F0DD2798DEE0003C703E /* Products */ = { + isa = PBXGroup; + children = ( + 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */, + ); + name = Products; + sourceTree = ""; + }; + 51B0F0DE2798DEE0003C703E /* SwiftUISample */ = { + isa = PBXGroup; + children = ( + 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */, + 51B0F0ED2798E127003C703E /* Info.plist */, + 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */, + 51B0F0E12798DEE0003C703E /* ContentView.swift */, + 51B0F0E32798DEE2003C703E /* Assets.xcassets */, + 51B0F0E52798DEE2003C703E /* Preview Content */, + ); + path = SwiftUISample; + sourceTree = ""; + }; + 51B0F0E52798DEE2003C703E /* Preview Content */ = { + isa = PBXGroup; + children = ( + 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + A4E8028DB1B31DF418090DC0 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 51B0F0DB2798DEE0003C703E /* SwiftUISample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 51B0F0EA2798DEE2003C703E /* Build configuration list for PBXNativeTarget "SwiftUISample" */; + buildPhases = ( + 51B0F0D82798DEE0003C703E /* Sources */, + 51B0F0D92798DEE0003C703E /* Frameworks */, + 51B0F0DA2798DEE0003C703E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftUISample; + productName = SwiftUISample; + productReference = 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 51B0F0D42798DEE0003C703E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1300; + LastUpgradeCheck = 1300; + TargetAttributes = { + 51B0F0DB2798DEE0003C703E = { + CreatedOnToolsVersion = 13.0; + }; + }; + }; + buildConfigurationList = 51B0F0D72798DEE0003C703E /* Build configuration list for PBXProject "SwiftUISample" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 51B0F0D32798DEE0003C703E; + productRefGroup = 51B0F0DD2798DEE0003C703E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 51B0F0DB2798DEE0003C703E /* SwiftUISample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 51B0F0DA2798DEE0003C703E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */, + 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 51B0F0D82798DEE0003C703E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 51B0F0E22798DEE0003C703E /* ContentView.swift in Sources */, + 51B0F0E02798DEE0003C703E /* SwiftUISampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 51B0F0E82798DEE2003C703E /* 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++17"; + 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 = 15.0; + 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; + }; + 51B0F0E92798DEE2003C703E /* 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++17"; + 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 = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 51B0F0EB2798DEE2003C703E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUISample/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 51B0F0EC2798DEE2003C703E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUISample/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 51B0F0D72798DEE0003C703E /* Build configuration list for PBXProject "SwiftUISample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 51B0F0E82798DEE2003C703E /* Debug */, + 51B0F0E92798DEE2003C703E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 51B0F0EA2798DEE2003C703E /* Build configuration list for PBXNativeTarget "SwiftUISample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 51B0F0EB2798DEE2003C703E /* Debug */, + 51B0F0EC2798DEE2003C703E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 51B0F0D42798DEE0003C703E /* Project object */; +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme new file mode 100644 index 00000000..7662ffa4 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift new file mode 100644 index 00000000..e6fe6616 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift @@ -0,0 +1,76 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import FirebaseAuth +import FirebaseStorage + +extension Image { + func data(url:URL) -> Self { + if let data = try? Data(contentsOf: url) { + return Image(uiImage: UIImage(data: data)!) + .resizable() + } + return self.resizable() + } +} + +struct ContentView: View { + @State private var imageURL = URL(string: "local") + + var body: some View { + VStack{ + + Image(systemName: "person.fill") + .data(url: self.imageURL!).onAppear(perform: loadImageFromFirebase) + Text("Hello, world!") + .padding() + Button(action: signOut) { + HStack { + Image(systemName: "arrow.clockwise.circle.fill") + Text("Sign out") + .fontWeight(.semibold) + } + } + }.onAppear(perform: loadImageFromFirebase) + } + + func loadImageFromFirebase() { + let storageRef = Storage.storage().reference(withPath: "sparky.png") + storageRef.downloadURL { (url, error) in + if error != nil { + return + } + self.imageURL = url! + } + } + + func signOut() { + do + { + try Auth.auth().signOut() + loadImageFromFirebase() + } + catch let error as NSError + { + print(error.localizedDescription) + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist new file mode 100644 index 00000000..643393a9 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist @@ -0,0 +1,21 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.449451107265-hsa0p0enomki8tfffrc6toflb2qfndm3 + + + + UIBackgroundModes + + remote-notification + + + diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift new file mode 100644 index 00000000..41ae44fd --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift @@ -0,0 +1,144 @@ +// +// MulticastAppDelegate.swift +// SwiftUISample +// +// Created by Charlotte Liang on 1/20/22. +// + +// limitations under the License. +import UIKit + +private typealias Application = UIApplication +private typealias ApplicationDelegate = UIApplicationDelegate + +@objc(GULMulticastAppDelegateProtocol) +public protocol MulticastAppDelegateProtocol: NSObjectProtocol { + typealias Delegate = UIApplicationDelegate + + func addInterceptor(_ interceptor: Delegate) + func removeInterceptor(_ interceptor: Delegate) +} + +@objc(GULMulticastAppDelegate) +open class MulticastAppDelegate: NSObject, MulticastAppDelegateProtocol { + public var appDelegate: MulticastAppDelegateProtocol.Delegate? + private var interceptors: [MulticastAppDelegateProtocol.Delegate] = [] + private var allInterceptors: [MulticastAppDelegateProtocol.Delegate] { + guard let appDelegate = appDelegate else { + return interceptors + } + + var allInterceptors = [appDelegate] + allInterceptors.append(contentsOf: interceptors) + return allInterceptors + } + + override public init() { + super.init() + } + + public init(appDelegate: MulticastAppDelegateProtocol.Delegate) { + super.init() + self.appDelegate = appDelegate + } + + @objc + public func addInterceptor(_ interceptor: Delegate) { + interceptors.append(interceptor) + } + + @objc + public func removeInterceptor(_ interceptor: Delegate) { + interceptors = interceptors.filter { $0 !== interceptor } + } + + // Forward all unknown messages to the original app delegate. + override public func responds(to aSelector: Selector!) -> Bool { + if type(of: self).instancesRespond(to: aSelector) { + return true + } + + return appDelegate?.responds(to: aSelector) ?? false + } + + override open func forwardingTarget(for aSelector: Selector) -> Any? { + return appDelegate + } +} + +// MARK: - Multicast App Delegate detection +extension MulticastAppDelegate { + /// Returns an instance of app delegate if it conforms to `MulticastAppDelegateProtocol` + @objc + public class func installedMulticastDelegate() -> MulticastAppDelegateProtocol? { + guard let appDelegate = Application.shared.delegate else { + return nil + } + + if let multicastDelegate = appDelegate as? MulticastAppDelegateProtocol { + return multicastDelegate + } + + // SwiftUI `UIApplicationDelegateAdaptor` doesn't allow easily check if the actual app delegate confirms to a protocol. But any method call is eventually forwarded to the original app delegate instance, so it can be used as a type-safe workaround. + if appDelegate.responds(to: #selector(gulMulticastDelegate)) { + return appDelegate.perform(#selector(gulMulticastDelegate)) + .takeRetainedValue() as? MulticastAppDelegateProtocol + } + + return nil + } + + /// The method is used to test if calls to `Application.shared.delegate` are forwarded to a `MulticastAppDelegate` subclass. + @objc + public func gulMulticastDelegate() -> MulticastAppDelegateProtocol { + return self + } +} + +extension MulticastAppDelegate: MulticastAppDelegateProtocol.Delegate { + // MARK: - Open URL + public func application(_ app: UIApplication, open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + var result = false + + for interceptor in allInterceptors { + result = result || interceptor.application?(app, open: url, options: options) ?? false + } + + return result + } + + // MARK: - APNS methods + public func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + for interceptor in allInterceptors { + interceptor.application?( + application, + didRegisterForRemoteNotificationsWithDeviceToken: deviceToken + ) + } + } + + public func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + for interceptor in allInterceptors { + interceptor.application?(application, didFailToRegisterForRemoteNotificationsWithError: error) + } + } + + public func application(_ application: UIApplication, + didReceiveRemoteNotification notification: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) + -> Void) { + for interceptor in allInterceptors { + // TODO: Make sure completionHandler is called once. + interceptor.application?( + application, + didReceiveRemoteNotification: notification, + fetchCompletionHandler: completionHandler + ) + } + } +} + +extension MulticastAppDelegate {} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift new file mode 100644 index 00000000..ea1ec18b --- /dev/null +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift @@ -0,0 +1,103 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import UIKit + +import FirebaseCore +import FirebaseAuth +import FirebaseMessaging +import GoogleUtilities +import GoogleMulticastAppDelegate +import nanopb + + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + +// func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { +// +// } +// +// func application(_ application: UIApplication, +// didReceiveRemoteNotification notification: [AnyHashable : Any], +// fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { +// // This notification is not auth related, developer should handle it. +// } +// +// // For iOS 9+ +// func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { +// +// // URL not auth related, developer should handle it. +// return true +// } + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]? = nil) -> Bool { + FirebaseApp.configure() + + // Request permissions for push notifications + let center = UNUserNotificationCenter.current() + center.delegate = self + center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + if error != nil { + print("Failed requesting notification permission: ", error ?? "") + } + } + application.registerForRemoteNotifications() + + PhoneAuthProvider.provider() + .verifyPhoneNumber("+16505551234", uiDelegate: nil) { verificationID, error in + if let error = error { + print(error) + return + } + // Sign in using the verificationID and the code sent to the user + // ... + UserDefaults.standard.set(verificationID, forKey: "authVerificationID") + let verificationID = UserDefaults.standard.string(forKey: "authVerificationID") + self.signin(verificationID: verificationID ?? "") + } + + return true + } + + func signin(verificationID: String) { + + let credential = PhoneAuthProvider.provider().credential( + withVerificationID: verificationID, + verificationCode: "654321" + ) + Auth.auth().signIn(with: credential) { authResult, error in + if let error = error { + print(error.localizedDescription) + return + } + } + } +} +@main +struct SwiftUISampleApp: App { + @UIApplicationDelegateAdaptor(GULMulticastAppDelegate.self) var delegate + + init() { + self.delegate.appDelegate = AppDelegate() + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m new file mode 100644 index 00000000..888eeb2c --- /dev/null +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -0,0 +1,96 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" + + +@interface GULMulticastAppDelegate () { + id _appDelegate; + NSMutableArray* _interceptors; +} +@end + +@implementation GULMulticastAppDelegate + +- (instancetype)init { + self = [super init]; + if (self) { + _interceptors = [[NSMutableArray alloc] init]; + } + return self; +} + +-(void)addInterceptorWithDelegate:(id)interceptor { + [_interceptors addObject:interceptor]; +} + +-(void)removeInterceptorWithDelegate:(id)interceptor { + [_interceptors removeObject:interceptor]; +} + +-(void)setAppDelegate:(id)appDelegate { + _appDelegate = appDelegate; + [self addInterceptorWithDelegate:appDelegate]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + if ([[self class] instancesRespondToSelector:aSelector]) { + return YES; + } + return _appDelegate? [_appDelegate respondsToSelector:aSelector] : NO ; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector { + return _appDelegate; +} + + +#pragma mark - Open URL +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + BOOL result = NO; + for (id interceptor in _interceptors) { + result = result || [interceptor application:app openURL:url options:options]; + } + return result; +} + +#pragma mark - APNS methods + +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + for (id interceptor in _interceptors) { + [interceptor application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } +} + +#if TARGET_OS_IOS || TARGET_OS_TV +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + for (id interceptor in _interceptors) { + [interceptor application:application didFailToRegisterForRemoteNotificationsWithError:error]; + } +} + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + for (id interceptor in _interceptors) { + [interceptor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; + } +} +#endif + +@end diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h new file mode 100644 index 00000000..ae0d036a --- /dev/null +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -0,0 +1,35 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol GULMulticastAppDelegateProtocol + +- (void)addInterceptorWithDelegate:(id) interceptor; + +- (void)removeInterceptorWithDelegate:(id) interceptor; + +@end + +@interface GULMulticastAppDelegate : NSObject + +@property (nonatomic, readwrite) id appDelegate; +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END + From 038688c0c7f44bb600f364e18a92b4a0b369ecaa Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 24 Jan 2022 13:29:17 -0800 Subject: [PATCH 02/12] simplify the code --- .../SwiftUISample.xcodeproj/project.pbxproj | 16 ++++++---- .../SwiftUISample/SwiftUISampleApp.swift | 3 +- .../Sources/GULMulticastAppDelegate.m | 29 +++++++++++++------ .../GoogleUtilities/GULMulticastAppDelegate.h | 11 ++----- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj index 8444f4ce..76809515 100644 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 51B0F0E22798DEE0003C703E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0F0E12798DEE0003C703E /* ContentView.swift */; }; 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E32798DEE2003C703E /* Assets.xcassets */; }; 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */; }; + 51B0F0F4279F48B0003C703E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -21,6 +22,7 @@ 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 51B0F0ED2798E127003C703E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUISample.entitlements; sourceTree = ""; }; + 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,6 +56,7 @@ 51B0F0DE2798DEE0003C703E /* SwiftUISample */ = { isa = PBXGroup; children = ( + 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */, 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */, 51B0F0ED2798E127003C703E /* Info.plist */, 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */, @@ -139,6 +142,7 @@ files = ( 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */, 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */, + 51B0F0F4279F48B0003C703E /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -279,11 +283,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EQHXZ8M8AV; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SwiftUISample/Info.plist; @@ -300,7 +304,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "Firebase iOS App Extensions Dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -313,11 +317,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = EQHXZ8M8AV; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SwiftUISample/Info.plist; @@ -334,7 +338,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "Firebase iOS App Extensions Dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift index ea1ec18b..5ceab4cc 100644 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift +++ b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift @@ -25,6 +25,7 @@ import nanopb class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { +// The following implementations should not be needed for Messaging/Auth to function. // func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // // } @@ -92,7 +93,7 @@ struct SwiftUISampleApp: App { @UIApplicationDelegateAdaptor(GULMulticastAppDelegate.self) var delegate init() { - self.delegate.appDelegate = AppDelegate() + self.delegate.addInterceptor(with: AppDelegate()) } var body: some Scene { diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 888eeb2c..a3d1e355 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -14,9 +14,15 @@ #import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" +@protocol GULMulticastAppDelegateProtocol -@interface GULMulticastAppDelegate () { - id _appDelegate; +- (void)addInterceptorWithDelegate:(id) interceptor; + +- (void)removeInterceptorWithDelegate:(id) interceptor; + +@end + +@interface GULMulticastAppDelegate () { NSMutableArray* _interceptors; } @end @@ -39,20 +45,25 @@ -(void)removeInterceptorWithDelegate:(id)interceptor { [_interceptors removeObject:interceptor]; } --(void)setAppDelegate:(id)appDelegate { - _appDelegate = appDelegate; - [self addInterceptorWithDelegate:appDelegate]; -} - - (BOOL)respondsToSelector:(SEL)aSelector { if ([[self class] instancesRespondToSelector:aSelector]) { return YES; } - return _appDelegate? [_appDelegate respondsToSelector:aSelector] : NO ; + for (id interceptor in _interceptors) { + if (interceptor && [interceptor respondsToSelector:aSelector]) { + return YES; + } + } + return NO; } - (id)forwardingTargetForSelector:(SEL)aSelector { - return _appDelegate; + for (id interceptor in _interceptors) { + if (interceptor && [interceptor respondsToSelector:aSelector]) { + return interceptor; + } + } + return nil; } diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index ae0d036a..964848d3 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -16,17 +16,10 @@ NS_ASSUME_NONNULL_BEGIN -@protocol GULMulticastAppDelegateProtocol +@interface GULMulticastAppDelegate : NSObject -- (void)addInterceptorWithDelegate:(id) interceptor; +-(void)addInterceptorWithDelegate:(id)delegate; -- (void)removeInterceptorWithDelegate:(id) interceptor; - -@end - -@interface GULMulticastAppDelegate : NSObject - -@property (nonatomic, readwrite) id appDelegate; - (instancetype)init NS_UNAVAILABLE; @end From 253da8adc5da5ec3ed8aed7ccae9b69bd470edf0 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 24 Jan 2022 22:55:06 -0800 Subject: [PATCH 03/12] make it multiplatform --- .../Sources/GULMulticastAppDelegate.m | 37 +++++++++++-------- .../GoogleUtilities/GULMulticastAppDelegate.h | 37 ++++++++++++++++++- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index a3d1e355..03fd58e0 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -16,9 +16,9 @@ @protocol GULMulticastAppDelegateProtocol -- (void)addInterceptorWithDelegate:(id) interceptor; +- (void)addInterceptorWithDelegate:(id) interceptor; -- (void)removeInterceptorWithDelegate:(id) interceptor; +- (void)removeInterceptorWithDelegate:(id) interceptor; @end @@ -37,11 +37,11 @@ - (instancetype)init { return self; } --(void)addInterceptorWithDelegate:(id)interceptor { +-(void)addInterceptorWithDelegate:(id)interceptor { [_interceptors addObject:interceptor]; } --(void)removeInterceptorWithDelegate:(id)interceptor { +-(void)removeInterceptorWithDelegate:(id)interceptor { [_interceptors removeObject:interceptor]; } @@ -49,7 +49,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector { if ([[self class] instancesRespondToSelector:aSelector]) { return YES; } - for (id interceptor in _interceptors) { + for (id interceptor in _interceptors) { if (interceptor && [interceptor respondsToSelector:aSelector]) { return YES; } @@ -58,7 +58,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector { } - (id)forwardingTargetForSelector:(SEL)aSelector { - for (id interceptor in _interceptors) { + for (id interceptor in _interceptors) { if (interceptor && [interceptor respondsToSelector:aSelector]) { return interceptor; } @@ -66,9 +66,9 @@ - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; } - +#if !TARGET_OS_WATCH #pragma mark - Open URL -- (BOOL)application:(UIApplication *)app +- (BOOL)application:(GULApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { BOOL result = NO; @@ -79,26 +79,33 @@ - (BOOL)application:(UIApplication *)app } #pragma mark - APNS methods - -- (void)application:(UIApplication *)application +- (void)application:(GULApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - for (id interceptor in _interceptors) { + for (id interceptor in _interceptors) { [interceptor application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } } +#else // !TARGET_OS_WATCH +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + for (id interceptor in _interceptors) { + [interceptor didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } +} +#endif // !TARGET_OS_WATCH + #if TARGET_OS_IOS || TARGET_OS_TV -- (void)application:(UIApplication *)application +- (void)application:(GULApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - for (id interceptor in _interceptors) { + for (id interceptor in _interceptors) { [interceptor application:application didFailToRegisterForRemoteNotificationsWithError:error]; } } -- (void)application:(UIApplication *)application +- (void)application:(GULApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - for (id interceptor in _interceptors) { + for (id interceptor in _interceptors) { [interceptor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } } diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index 964848d3..500229d0 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -11,14 +11,47 @@ // WITHOUT WARRANTIES 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 + NS_ASSUME_NONNULL_BEGIN -@interface GULMulticastAppDelegate : NSObject +@interface GULMulticastAppDelegate : NSObject --(void)addInterceptorWithDelegate:(id)delegate; +-(void)addInterceptorWithDelegate:(id)delegate; - (instancetype)init NS_UNAVAILABLE; From 7c6dbc903f2182d25c1181d1c71c818207b30381 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Wed, 26 Jan 2022 16:33:24 -0800 Subject: [PATCH 04/12] adjust the multicast app delegate to work with auth and messaging --- .../Sources/GULMulticastAppDelegate.m | 24 ++++++++++++++----- .../GoogleUtilities/GULMulticastAppDelegate.h | 13 +++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 03fd58e0..3a8b9437 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -14,13 +14,7 @@ #import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" -@protocol GULMulticastAppDelegateProtocol -- (void)addInterceptorWithDelegate:(id) interceptor; - -- (void)removeInterceptorWithDelegate:(id) interceptor; - -@end @interface GULMulticastAppDelegate () { NSMutableArray* _interceptors; @@ -37,6 +31,24 @@ - (instancetype)init { return self; } ++ (id)multicastDelegate { + + id appDelegate = [UIApplication sharedApplication].delegate; + if (appDelegate && [appDelegate respondsToSelector:@selector(getMulticastDelegate)]) { + + id multicastDelegate = [appDelegate performSelector:@selector(getMulticastDelegate)]; + CFRetain((__bridge CFTypeRef)(multicastDelegate)); + return multicastDelegate; + } + return nil; +} + + +- (id)getMulticastDelegate { + return self; +} + + -(void)addInterceptorWithDelegate:(id)interceptor { [_interceptors addObject:interceptor]; } diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index 500229d0..1f017e96 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -49,12 +49,19 @@ static NSString *const kGULApplicationClassName = @"WKExtension"; NS_ASSUME_NONNULL_BEGIN -@interface GULMulticastAppDelegate : NSObject +@protocol GULMulticastAppDelegateProtocol + +- (void)addInterceptorWithDelegate:(id) interceptor; + +- (void)removeInterceptorWithDelegate:(id) interceptor; --(void)addInterceptorWithDelegate:(id)delegate; +@end + +@interface GULMulticastAppDelegate : NSObject -- (instancetype)init NS_UNAVAILABLE; +- (void)addInterceptorWithDelegate:(id)delegate; ++ (id)multicastDelegate; @end NS_ASSUME_NONNULL_END From 769da7fe1b0197b1b5ce8f59a677c2b3333d5ada Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Wed, 26 Jan 2022 16:47:45 -0800 Subject: [PATCH 05/12] remove the sample app --- .../Apps/SwiftUISample/Podfile | 17 - .../SwiftUISample.xcodeproj/project.pbxproj | 372 ----- .../xcschemes/SwiftUISample.xcscheme | 84 -- .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 98 -- .../Assets.xcassets/Contents.json | 6 - .../SwiftUISample/ContentView.swift | 76 - .../SwiftUISample/SwiftUISample/Info.plist | 21 - .../SwiftUISample/MulticastAppDelegate.swift | 144 -- .../Preview Assets.xcassets/Contents.json | 6 - .../SwiftUISample/SwiftUISample.entitlements | 8 - .../SwiftUISample/SwiftUISampleApp.swift | 104 -- .../Tests/Unit/GULMulticastAppDelegateTest.m | 1250 +++++++++++++++++ 13 files changed, 1250 insertions(+), 947 deletions(-) delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements delete mode 100644 GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift create mode 100644 GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile b/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile deleted file mode 100644 index d64c8ba1..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/Podfile +++ /dev/null @@ -1,17 +0,0 @@ -use_frameworks! - -source 'https://github.com/firebase/SpecsDev.git' -source 'https://github.com/firebase/SpecsStaging.git' -source 'https://cdn.cocoapods.org/' - -target 'SwiftUISample' do - platform :ios, '15.0' - - pod 'FirebaseAuth' - pod 'FirebaseCore' - pod 'FirebaseMessaging' - pod 'FirebaseStorage' - pod 'GoogleMulticastAppDelegate', :path => '../../../' - pod 'GoogleUtilities', :path => '../../../' - -end diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj deleted file mode 100644 index 76809515..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,372 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - 51B0F0E02798DEE0003C703E /* SwiftUISampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */; }; - 51B0F0E22798DEE0003C703E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0F0E12798DEE0003C703E /* ContentView.swift */; }; - 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E32798DEE2003C703E /* Assets.xcassets */; }; - 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */; }; - 51B0F0F4279F48B0003C703E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUISample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUISampleApp.swift; sourceTree = ""; }; - 51B0F0E12798DEE0003C703E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 51B0F0E32798DEE2003C703E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 51B0F0ED2798E127003C703E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUISample.entitlements; sourceTree = ""; }; - 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 51B0F0D92798DEE0003C703E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 51B0F0D32798DEE0003C703E = { - isa = PBXGroup; - children = ( - 51B0F0DE2798DEE0003C703E /* SwiftUISample */, - 51B0F0DD2798DEE0003C703E /* Products */, - A4E8028DB1B31DF418090DC0 /* Pods */, - ); - sourceTree = ""; - }; - 51B0F0DD2798DEE0003C703E /* Products */ = { - isa = PBXGroup; - children = ( - 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */, - ); - name = Products; - sourceTree = ""; - }; - 51B0F0DE2798DEE0003C703E /* SwiftUISample */ = { - isa = PBXGroup; - children = ( - 51B0F0F3279F48B0003C703E /* GoogleService-Info.plist */, - 51B0F0EE2798E131003C703E /* SwiftUISample.entitlements */, - 51B0F0ED2798E127003C703E /* Info.plist */, - 51B0F0DF2798DEE0003C703E /* SwiftUISampleApp.swift */, - 51B0F0E12798DEE0003C703E /* ContentView.swift */, - 51B0F0E32798DEE2003C703E /* Assets.xcassets */, - 51B0F0E52798DEE2003C703E /* Preview Content */, - ); - path = SwiftUISample; - sourceTree = ""; - }; - 51B0F0E52798DEE2003C703E /* Preview Content */ = { - isa = PBXGroup; - children = ( - 51B0F0E62798DEE2003C703E /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - A4E8028DB1B31DF418090DC0 /* Pods */ = { - isa = PBXGroup; - children = ( - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 51B0F0DB2798DEE0003C703E /* SwiftUISample */ = { - isa = PBXNativeTarget; - buildConfigurationList = 51B0F0EA2798DEE2003C703E /* Build configuration list for PBXNativeTarget "SwiftUISample" */; - buildPhases = ( - 51B0F0D82798DEE0003C703E /* Sources */, - 51B0F0D92798DEE0003C703E /* Frameworks */, - 51B0F0DA2798DEE0003C703E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SwiftUISample; - productName = SwiftUISample; - productReference = 51B0F0DC2798DEE0003C703E /* SwiftUISample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 51B0F0D42798DEE0003C703E /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1300; - LastUpgradeCheck = 1300; - TargetAttributes = { - 51B0F0DB2798DEE0003C703E = { - CreatedOnToolsVersion = 13.0; - }; - }; - }; - buildConfigurationList = 51B0F0D72798DEE0003C703E /* Build configuration list for PBXProject "SwiftUISample" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 51B0F0D32798DEE0003C703E; - productRefGroup = 51B0F0DD2798DEE0003C703E /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 51B0F0DB2798DEE0003C703E /* SwiftUISample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 51B0F0DA2798DEE0003C703E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 51B0F0E72798DEE2003C703E /* Preview Assets.xcassets in Resources */, - 51B0F0E42798DEE2003C703E /* Assets.xcassets in Resources */, - 51B0F0F4279F48B0003C703E /* GoogleService-Info.plist in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 51B0F0D82798DEE0003C703E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 51B0F0E22798DEE0003C703E /* ContentView.swift in Sources */, - 51B0F0E02798DEE0003C703E /* SwiftUISampleApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 51B0F0E82798DEE2003C703E /* 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++17"; - 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 = 15.0; - 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; - }; - 51B0F0E92798DEE2003C703E /* 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++17"; - 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 = 15.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 51B0F0EB2798DEE2003C703E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; - DEVELOPMENT_TEAM = EQHXZ8M8AV; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SwiftUISample/Info.plist; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Firebase iOS App Extensions Dev"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 51B0F0EC2798DEE2003C703E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SwiftUISample/SwiftUISample.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"SwiftUISample/Preview Content\""; - DEVELOPMENT_TEAM = EQHXZ8M8AV; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SwiftUISample/Info.plist; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.extensions.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Firebase iOS App Extensions Dev"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 51B0F0D72798DEE0003C703E /* Build configuration list for PBXProject "SwiftUISample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51B0F0E82798DEE2003C703E /* Debug */, - 51B0F0E92798DEE2003C703E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 51B0F0EA2798DEE2003C703E /* Build configuration list for PBXNativeTarget "SwiftUISample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51B0F0EB2798DEE2003C703E /* Debug */, - 51B0F0EC2798DEE2003C703E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 51B0F0D42798DEE0003C703E /* Project object */; -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme deleted file mode 100644 index 7662ffa4..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample.xcodeproj/xcshareddata/xcschemes/SwiftUISample.xcscheme +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb878970..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9221b9bb..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift deleted file mode 100644 index e6fe6616..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/ContentView.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import SwiftUI -import FirebaseAuth -import FirebaseStorage - -extension Image { - func data(url:URL) -> Self { - if let data = try? Data(contentsOf: url) { - return Image(uiImage: UIImage(data: data)!) - .resizable() - } - return self.resizable() - } -} - -struct ContentView: View { - @State private var imageURL = URL(string: "local") - - var body: some View { - VStack{ - - Image(systemName: "person.fill") - .data(url: self.imageURL!).onAppear(perform: loadImageFromFirebase) - Text("Hello, world!") - .padding() - Button(action: signOut) { - HStack { - Image(systemName: "arrow.clockwise.circle.fill") - Text("Sign out") - .fontWeight(.semibold) - } - } - }.onAppear(perform: loadImageFromFirebase) - } - - func loadImageFromFirebase() { - let storageRef = Storage.storage().reference(withPath: "sparky.png") - storageRef.downloadURL { (url, error) in - if error != nil { - return - } - self.imageURL = url! - } - } - - func signOut() { - do - { - try Auth.auth().signOut() - loadImageFromFirebase() - } - catch let error as NSError - { - print(error.localizedDescription) - } - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist deleted file mode 100644 index 643393a9..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Info.plist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - com.googleusercontent.apps.449451107265-hsa0p0enomki8tfffrc6toflb2qfndm3 - - - - UIBackgroundModes - - remote-notification - - - diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift deleted file mode 100644 index 41ae44fd..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/MulticastAppDelegate.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// MulticastAppDelegate.swift -// SwiftUISample -// -// Created by Charlotte Liang on 1/20/22. -// - -// limitations under the License. -import UIKit - -private typealias Application = UIApplication -private typealias ApplicationDelegate = UIApplicationDelegate - -@objc(GULMulticastAppDelegateProtocol) -public protocol MulticastAppDelegateProtocol: NSObjectProtocol { - typealias Delegate = UIApplicationDelegate - - func addInterceptor(_ interceptor: Delegate) - func removeInterceptor(_ interceptor: Delegate) -} - -@objc(GULMulticastAppDelegate) -open class MulticastAppDelegate: NSObject, MulticastAppDelegateProtocol { - public var appDelegate: MulticastAppDelegateProtocol.Delegate? - private var interceptors: [MulticastAppDelegateProtocol.Delegate] = [] - private var allInterceptors: [MulticastAppDelegateProtocol.Delegate] { - guard let appDelegate = appDelegate else { - return interceptors - } - - var allInterceptors = [appDelegate] - allInterceptors.append(contentsOf: interceptors) - return allInterceptors - } - - override public init() { - super.init() - } - - public init(appDelegate: MulticastAppDelegateProtocol.Delegate) { - super.init() - self.appDelegate = appDelegate - } - - @objc - public func addInterceptor(_ interceptor: Delegate) { - interceptors.append(interceptor) - } - - @objc - public func removeInterceptor(_ interceptor: Delegate) { - interceptors = interceptors.filter { $0 !== interceptor } - } - - // Forward all unknown messages to the original app delegate. - override public func responds(to aSelector: Selector!) -> Bool { - if type(of: self).instancesRespond(to: aSelector) { - return true - } - - return appDelegate?.responds(to: aSelector) ?? false - } - - override open func forwardingTarget(for aSelector: Selector) -> Any? { - return appDelegate - } -} - -// MARK: - Multicast App Delegate detection -extension MulticastAppDelegate { - /// Returns an instance of app delegate if it conforms to `MulticastAppDelegateProtocol` - @objc - public class func installedMulticastDelegate() -> MulticastAppDelegateProtocol? { - guard let appDelegate = Application.shared.delegate else { - return nil - } - - if let multicastDelegate = appDelegate as? MulticastAppDelegateProtocol { - return multicastDelegate - } - - // SwiftUI `UIApplicationDelegateAdaptor` doesn't allow easily check if the actual app delegate confirms to a protocol. But any method call is eventually forwarded to the original app delegate instance, so it can be used as a type-safe workaround. - if appDelegate.responds(to: #selector(gulMulticastDelegate)) { - return appDelegate.perform(#selector(gulMulticastDelegate)) - .takeRetainedValue() as? MulticastAppDelegateProtocol - } - - return nil - } - - /// The method is used to test if calls to `Application.shared.delegate` are forwarded to a `MulticastAppDelegate` subclass. - @objc - public func gulMulticastDelegate() -> MulticastAppDelegateProtocol { - return self - } -} - -extension MulticastAppDelegate: MulticastAppDelegateProtocol.Delegate { - // MARK: - Open URL - public func application(_ app: UIApplication, open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - var result = false - - for interceptor in allInterceptors { - result = result || interceptor.application?(app, open: url, options: options) ?? false - } - - return result - } - - // MARK: - APNS methods - public func application(_ application: UIApplication, - didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - for interceptor in allInterceptors { - interceptor.application?( - application, - didRegisterForRemoteNotificationsWithDeviceToken: deviceToken - ) - } - } - - public func application(_ application: UIApplication, - didFailToRegisterForRemoteNotificationsWithError error: Error) { - for interceptor in allInterceptors { - interceptor.application?(application, didFailToRegisterForRemoteNotificationsWithError: error) - } - } - - public func application(_ application: UIApplication, - didReceiveRemoteNotification notification: [AnyHashable: Any], - fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) - -> Void) { - for interceptor in allInterceptors { - // TODO: Make sure completionHandler is called once. - interceptor.application?( - application, - didReceiveRemoteNotification: notification, - fetchCompletionHandler: completionHandler - ) - } - } -} - -extension MulticastAppDelegate {} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements deleted file mode 100644 index 903def2a..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISample.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - aps-environment - development - - diff --git a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift b/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift deleted file mode 100644 index 5ceab4cc..00000000 --- a/GoogleMulticastAppDelegate/Apps/SwiftUISample/SwiftUISample/SwiftUISampleApp.swift +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import SwiftUI -import UIKit - -import FirebaseCore -import FirebaseAuth -import FirebaseMessaging -import GoogleUtilities -import GoogleMulticastAppDelegate -import nanopb - - -class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { - -// The following implementations should not be needed for Messaging/Auth to function. -// func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { -// -// } -// -// func application(_ application: UIApplication, -// didReceiveRemoteNotification notification: [AnyHashable : Any], -// fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { -// // This notification is not auth related, developer should handle it. -// } -// -// // For iOS 9+ -// func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { -// -// // URL not auth related, developer should handle it. -// return true -// } - - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication - .LaunchOptionsKey: Any]? = nil) -> Bool { - FirebaseApp.configure() - - // Request permissions for push notifications - let center = UNUserNotificationCenter.current() - center.delegate = self - center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in - if error != nil { - print("Failed requesting notification permission: ", error ?? "") - } - } - application.registerForRemoteNotifications() - - PhoneAuthProvider.provider() - .verifyPhoneNumber("+16505551234", uiDelegate: nil) { verificationID, error in - if let error = error { - print(error) - return - } - // Sign in using the verificationID and the code sent to the user - // ... - UserDefaults.standard.set(verificationID, forKey: "authVerificationID") - let verificationID = UserDefaults.standard.string(forKey: "authVerificationID") - self.signin(verificationID: verificationID ?? "") - } - - return true - } - - func signin(verificationID: String) { - - let credential = PhoneAuthProvider.provider().credential( - withVerificationID: verificationID, - verificationCode: "654321" - ) - Auth.auth().signIn(with: credential) { authResult, error in - if let error = error { - print(error.localizedDescription) - return - } - } - } -} -@main -struct SwiftUISampleApp: App { - @UIApplicationDelegateAdaptor(GULMulticastAppDelegate.self) var delegate - - init() { - self.delegate.addInterceptor(with: AppDelegate()) - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m b/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m new file mode 100644 index 00000000..e4a96e9f --- /dev/null +++ b/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m @@ -0,0 +1,1250 @@ +// 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 "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" + +#import +#import +#import +#import "OCMock.h" + +#if (defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) +#define SDK_HAS_USERACTIVITY 1 +#endif + +/** Plist key that allows Firebase developers to disable App Delegate Proxying. Source of truth is + * the GULAppDelegateSwizzler class. + */ +static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey = + @"FirebaseAppDelegateProxyEnabled"; + +/** Plist key that allows non-Firebase developers to disable App Delegate Proxying. Source of truth + * is the GULAppDelegateSwizzler class. + */ +static NSString *const kGULGoogleAppDelegateProxyEnabledPlistKey = + @"GoogleUtilitiesAppDelegateProxyEnabled"; + +#pragma mark - GULTestAppDelegate + +/** This class conforms to the application delegate protocol and is there to be able to test the + * App Delegate Swizzler's behavior. + */ +@interface GULTestAppDelegate : NSObject, UIApplicationDelegate { + @public // Because we want to access the ivars from outside the class like obj->ivar for testing. + /** YES if the application:openURL:options: was called on an instance, NO otherwise. */ + BOOL _isOpenURLOptionsMethodCalled; + + /** Contains the backgroundSessionID that was passed to the + * application:handleEventsForBackgroundURLSession:completionHandler: method. + */ + NSString *_backgroundSessionID; + + /** YES if init was called. Used for memory layout testing after isa swizzling. */ + BOOL _isInitialized; + + /** An arbitrary number. Used for memory layout testing after isa swizzling. */ + int _arbitraryNumber; +} + +/** A URL property that is set by the app delegate methods, which is then used to verify if the app + * delegate methods were properly called. + */ +@property(nonatomic, strong) NSURL *url; +@property(nonatomic, strong) NSDictionary *openURLOptions; +@property(nonatomic, strong) NSString *openURLSourceApplication; + +@property(nonatomic, strong) NSUserActivity *userActivity; + +@property(nonatomic, strong) NSData *remoteNotificationsDeviceToken; +@property(nonatomic, strong) NSError *failToRegisterForRemoteNotificationsError; +@property(nonatomic, strong) NSDictionary *remoteNotification; + +#if TARGET_OS_IOS || TARGET_OS_TV +@property(nonatomic, copy) void (^remoteNotificationCompletionHandler)(UIBackgroundFetchResult); +#endif // TARGET_OS_IOS || TARGET_OS_TV + +/** + * The application is set each time a GULApplicationDelegate method is called + */ +@property(nonatomic, weak) GULApplication *application; + +@end + +@implementation GULTestAppDelegate + +// TODO: The static BOOLs below being accurate is dependent on the runtime loading +// GULTestAppDelegate before GULAppDelegateSwizzlerTest. It works, but it might be a good idea to +// figure a way to make this more deterministic. + +/** YES if GULTestAppDelegate responds to application:openURL:options:, NO otherwise. */ +static BOOL gRespondsToOpenURLHandler_iOS9; + +/** YES if GULTestAppDelegate responds to application:continueUserActivity:restorationHandler:, NO + * otherwise. + */ +static BOOL gRespondsToContinueUserActivity; + +/** YES if GULTestAppDelegate responds to + * application:handleEventsForBackgroundURLSession:completionHandler:, NO otherwise. + */ +static BOOL gRespondsToHandleBackgroundSession; + ++ (void)load { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + + gRespondsToOpenURLHandler_iOS9 = + [self instancesRespondToSelector:@selector(application:openURL:options:)]; + gRespondsToHandleBackgroundSession = + [self instancesRespondToSelector:@selector(application: + handleEventsForBackgroundURLSession:completionHandler:)]; + gRespondsToContinueUserActivity = [self + instancesRespondToSelector:@selector(application:continueUserActivity:restorationHandler:)]; +#pragma clang diagnostic pop +} + +- (instancetype)init { + self = [super init]; + if (self) { + _isOpenURLOptionsMethodCalled = NO; + _isInitialized = YES; + _arbitraryNumber = 123456789; + _backgroundSessionID = @"randomSessionID"; + _url = nil; + } + return self; +} + +- (BOOL)application:(GULApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + self.application = app; + self.url = url; + self.openURLOptions = options; + _isOpenURLOptionsMethodCalled = YES; + return NO; +} + +- (BOOL)application:(GULApplication *)application + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray> *__nullable + restorableObjects))restorationHandler { + self.application = application; + self.userActivity = userActivity; + return NO; +} + +- (void)application:(GULApplication *)application + handleEventsForBackgroundURLSession:(nonnull NSString *)identifier + completionHandler:(nonnull void (^)(void))completionHandler { + self.application = application; + _backgroundSessionID = identifier; +} + +- (void)application:(GULApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + self.application = application; + self.remoteNotificationsDeviceToken = deviceToken; +} + +- (void)application:(GULApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + self.application = application; + self.failToRegisterForRemoteNotificationsError = error; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +- (void)application:(GULApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { + self.application = application; + self.remoteNotification = userInfo; +} +#pragma clang diagnostic pop + +#if TARGET_OS_IOS || TARGET_OS_TV + +- (void)application:(GULApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + self.application = application; + self.remoteNotification = userInfo; + self.remoteNotificationCompletionHandler = completionHandler; +} + +#endif // TARGET_OS_IOS || TARGET_OS_TV + +// These are methods to test whether changing the class still maintains behavior that the app +// delegate proxy shouldn't have modified. + +- (NSString *)someArbitraryMethod { + return @"blabla"; +} + ++ (int)someNumber { + return 890; +} + +@end + +@interface GULEmptyTestAppDelegate : NSObject +@end + +@implementation GULEmptyTestAppDelegate +@end + +#pragma mark - Interceptor class + +/** This is a class used to test whether interceptors work with the App Delegate Swizzler. */ +@interface GULTestInterceptorAppDelegate : NSObject + +/** URL sent to application:openURL:options:. */ +@property(nonatomic, copy) NSURL *URLForIOS9; + +/** The NSUserActivity sent to application:continueUserActivity:restorationHandler:. */ +@property(nonatomic, copy) NSUserActivity *userActivity; + +@end + +@implementation GULTestInterceptorAppDelegate + +- (BOOL)application:(GULApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + _URLForIOS9 = [url copy]; + return YES; +} + +#if SDK_HAS_USERACTIVITY + +- (BOOL)application:(GULApplication *)application + continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray> *__nullable + restorableObjects))restorationHandler { + _userActivity = userActivity; + return YES; +} + +#endif // SDK_HAS_USERACTIVITY + +@end + +@interface GULAppDelegateSwizzlerTest : XCTestCase +@property(nonatomic, strong) id mockSharedApplication; +@end + +@implementation GULAppDelegateSwizzlerTest + +- (void)setUp { + [super setUp]; + self.mockSharedApplication = OCMClassMock([GULApplication class]); + OCMStub([self.mockSharedApplication sharedApplication]).andReturn(self.mockSharedApplication); +} + +- (void)tearDown { + [GULAppDelegateSwizzler clearInterceptors]; + [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; + self.mockSharedApplication = nil; + [super tearDown]; +} + +- (void)testNotAppDelegateIsNotSwizzled { + NSObject *notAppDelegate = [[NSObject alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(notAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject"); +} + +/** Tests proxying an object that responds to application delegate protocol and makes sure that + * it is isa swizzled and that the object after proxying responds to the expected methods + * and doesn't have its ivars modified. + */ +- (void)testProxyAppDelegate { + GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + size_t sizeBefore = class_getInstanceSize([GULTestAppDelegate class]); + + Class realAppDelegateClassBefore = [realAppDelegate class]; + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + XCTAssertTrue([realAppDelegate isKindOfClass:[GULTestAppDelegate class]]); + + NSString *newClassName = NSStringFromClass([realAppDelegate class]); + XCTAssertTrue([newClassName hasPrefix:@"GUL_"]); + // It is no longer GULTestAppDelegate class instance. + XCTAssertFalse([realAppDelegate isMemberOfClass:[GULTestAppDelegate class]]); + + size_t sizeAfter = class_getInstanceSize([realAppDelegate class]); + + // Class size must stay the same. + XCTAssertEqual(sizeBefore, sizeAfter); + + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + continueUserActivity:restorationHandler:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); +#if TARGET_OS_IOS || TARGET_OS_TV + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + handleEventsForBackgroundURLSession:completionHandler:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +#endif // TARGET_OS_IOS || TARGET_OS_TV + + // Make sure that the class has changed. + XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); + + // Make sure that the ivars are not changed in memory as the subclass is created. Directly + // accessing the ivars should not crash. + XCTAssertEqual(realAppDelegate->_arbitraryNumber, 123456789); + XCTAssertEqual(realAppDelegate->_isInitialized, 1); + XCTAssertFalse(realAppDelegate->_isOpenURLOptionsMethodCalled); + XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, @"randomSessionID"); +} + +- (void)testProxyEmptyAppDelegate { + GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]); + + Class realAppDelegateClassBefore = [realAppDelegate class]; + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]); + + NSString *newClassName = NSStringFromClass([realAppDelegate class]); + XCTAssertTrue([newClassName hasPrefix:@"GUL_"]); + // It is no longer GULTestAppDelegate class instance. + XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]); + + size_t sizeAfter = class_getInstanceSize([realAppDelegate class]); + + // Class size must stay the same. + XCTAssertEqual(sizeBefore, sizeAfter); + + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + continueUserActivity:restorationHandler:)]); + // Remote notifications methods should be added only by + // -proxyOriginalDelegateIncludingAPNSMethods + XCTAssertFalse([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertFalse([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); +#if TARGET_OS_IOS || TARGET_OS_TV + // The implementation should not be added if there is no original implementation + XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + handleEventsForBackgroundURLSession:completionHandler:)]); + XCTAssertFalse([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +#endif // TARGET_OS_IOS || TARGET_OS_TV + + // Make sure that the class has changed. + XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); +} + +- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate { + GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]); + + Class realAppDelegateClassBefore = [realAppDelegate class]; + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]); + + NSString *newClassName = NSStringFromClass([realAppDelegate class]); + XCTAssertTrue([newClassName hasPrefix:@"GUL_"]); + // It is no longer GULTestAppDelegate class instance. + XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]); + + size_t sizeAfter = class_getInstanceSize([realAppDelegate class]); + + // Class size must stay the same. + XCTAssertEqual(sizeBefore, sizeAfter); + + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + continueUserActivity:restorationHandler:)]); + + // Remote notifications methods should be added only by + // -proxyOriginalDelegateIncludingAPNSMethods + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); + +#if TARGET_OS_IOS || TARGET_OS_TV + // The implementation should not be added if there is no original implementation + XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); + + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + handleEventsForBackgroundURLSession:completionHandler:)]); + + // The implementation should not be added if there is no original implementation + XCTAssertFalse([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + +#endif // TARGET_OS_IOS || TARGET_OS_TV + + // Make sure that the class has changed. + XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); +} + +- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegateAfterInitialProxy { + GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]); + + Class realAppDelegateClassBefore = [realAppDelegate class]; + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]); + + NSString *newClassName = NSStringFromClass([realAppDelegate class]); + XCTAssertTrue([newClassName hasPrefix:@"GUL_"]); + // It is no longer GULTestAppDelegate class instance. + XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]); + + size_t sizeAfter = class_getInstanceSize([realAppDelegate class]); + + // Class size must stay the same. + XCTAssertEqual(sizeBefore, sizeAfter); + + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + continueUserActivity:restorationHandler:)]); + // Proxy remote notifications methods + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); +#if TARGET_OS_IOS || TARGET_OS_TV + // The implementation should not be added if there is no original implementation + XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + handleEventsForBackgroundURLSession:completionHandler:)]); + + // The implementation should not be added if there is no original implementation + XCTAssertFalse([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +#endif // TARGET_OS_IOS || TARGET_OS_TV + + // Make sure that the class has changed. + XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); +} + +#if SDK_HAS_USERACTIVITY +- (void)testHandleBackgroundSessionMethod { + GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + GULApplication *currentApplication = [GULApplication sharedApplication]; + NSString *sessionID = @"123"; + void (^nilHandler)(void) = nil; + [realAppDelegate application:currentApplication + handleEventsForBackgroundURLSession:sessionID + completionHandler:nilHandler]; + + // Intentionally access the ivars directly. It should be set to the session ID as the real method + // is called. + XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, sessionID); +} +#endif // SDK_HAS_USERACTIVITY + +/** Tests registering and unregistering invalid interceptors. */ +- (void)testInvalidInterceptor { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows([GULAppDelegateSwizzler registerAppDelegateInterceptor:nil], + @"Should not register nil interceptor"); +#pragma clang diagnostic pop + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0); + + // Try to register some random object that does not conform to application delegate. + NSObject *randomObject = [[NSObject alloc] init]; + + XCTAssertThrows([GULAppDelegateSwizzler + registerAppDelegateInterceptor:(id)randomObject], + @"Should not register interceptor that does not conform to %@Delegate", + kGULApplicationClassName); + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0); + + GULTestInterceptorAppDelegate *interceptorAppDelegate = + [[GULTestInterceptorAppDelegate alloc] init]; + GULAppDelegateInterceptorID interceptorID = + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate]; + XCTAssertNotNil(interceptorID); + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1); + + // Register the same object. Should not change the number of objects. + XCTAssertNotNil([GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate]); + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1); + + XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@""], + @"Should not unregister empty interceptor ID"); + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1); + + // Try to unregister an empty string. Should not remove anything. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:nil], + @"Should not unregister nil interceptorID"); + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1); + + // Try to unregister a random string. Should not remove anything. + [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@"random ID"]; + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1); + + // Unregister the right one. + [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:interceptorID]; + XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0); +} + +/** Tests that the description of appDelegate object doesn't change even after proxying it. */ +- (void)testDescription { + GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + Class classBefore = [realAppDelegate class]; + NSString *descriptionBefore = [realAppDelegate description]; + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + Class classAfter = [realAppDelegate class]; + NSString *descriptionAfter = [realAppDelegate description]; + + NSString *descriptionString = + [NSString stringWithFormat:@"", realAppDelegate]; + + // The description must be the same even though the class has changed. + XCTAssertEqualObjects(descriptionBefore, descriptionAfter); + XCTAssertNotEqualObjects(classAfter, classBefore); + XCTAssertEqualObjects(descriptionAfter, descriptionString); +} + +/** Tests that methods that are not overridden by the App Delegate Proxy still work as expected. */ +- (void)testNotOverriddenMethods { + GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate); + + // Create the proxy. + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + // Make sure that original class instance method still works. + XCTAssertEqualObjects([realAppDelegate someArbitraryMethod], @"blabla"); + + // Make sure that the new subclass inherits the original class method. + XCTAssertEqual([[realAppDelegate class] someNumber], 890); + + // Make sure that the original class still works. + XCTAssertEqual([GULTestAppDelegate someNumber], 890); +} + +#if !SWIFT_PACKAGE +// TODO: Investigate why this test fails in Swift PM builds. + +/** Tests that if the app delegate changes after it has been proxied, the App Delegate Swizzler + * handles it correctly. + */ +- (void)testAppDelegateInstance { + // The test logic involves using KVC on the UIApplication.delegate property. This does not really + // work well with OCMPartialMock([GULApplication sharedApplication]) and triggers issue + // https://github.com/erikdoe/ocmock/issues/346. + // Let's stop mocking the shared application for this particular test. + [self.mockSharedApplication stopMocking]; + self.mockSharedApplication = nil; + + GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init]; + + [GULApplication sharedApplication].delegate = realAppDelegate; + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + XCTAssertEqualObjects([GULAppDelegateSwizzler originalDelegate], realAppDelegate); + + GULTestInterceptorAppDelegate *anotherAppDelegate = [[GULTestInterceptorAppDelegate alloc] init]; + XCTAssertNotEqualObjects(realAppDelegate, anotherAppDelegate); + + [GULApplication sharedApplication].delegate = anotherAppDelegate; + // Make sure that the new delegate is swizzled out and set correctly. + XCTAssertNil([GULAppDelegateSwizzler originalDelegate]); + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + // Swizzling of an updated app delegate is not supported so far. + XCTAssertNil([GULAppDelegateSwizzler originalDelegate]); +} +#endif + +#pragma mark - Tests the behaviour with interceptors + +#if TARGET_OS_IOS || TARGET_OS_TV +/** Tests that application:openURL:options: is invoked on the interceptor if it exists. */ +- (void)testApplicationOpenURLOptionsIsInvokedOnInterceptors { + if (@available(iOS 10, *)) { + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + + NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; + NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + // Check that original implementation was called with proper parameters + XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]); + XCTAssertEqual(testAppDelegate.url, testURL); + } +} + +/** Tests that the result of application:openURL:options: from all interceptors is ORed. */ +- (void)testResultOfApplicationOpenURLOptionsIsORed { + if (@available(iOS 10, *)) { + NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"]; + NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"}; + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that the original app delegate returns NO. + XCTAssertFalse(shouldOpen); + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that if the only interceptor returns NO, the value is still NO. + XCTAssertFalse(shouldOpen); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(YES); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY]) + .andReturn(NO); + shouldOpen = [testAppDelegate application:[GULApplication sharedApplication] + openURL:testURL + options:testOpenURLOptions]; + // Verify that if one of the two interceptors returns YES, the value is YES. + XCTAssertTrue(shouldOpen); + } +} +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#if TARGET_OS_IOS || TARGET_OS_TV +/** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the + * interceptors if it exists. + */ +- (void)testApplicationHandleEventsForBackgroundURLSessionIsInvokedOnInterceptors { + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY + handleEventsForBackgroundURLSession:OCMOCK_ANY + completionHandler:OCMOCK_ANY]); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY + handleEventsForBackgroundURLSession:OCMOCK_ANY + completionHandler:OCMOCK_ANY]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + NSString *backgroundSessionID = @"testBackgroundSessionID"; + [testAppDelegate application:[GULApplication sharedApplication] + handleEventsForBackgroundURLSession:backgroundSessionID + completionHandler:^{ + }]; + + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + // Check that original implementation was called with proper parameters + XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]); + XCTAssertEqual(testAppDelegate->_backgroundSessionID, backgroundSessionID); +} +#endif // TARGET_OS_IOS || TARGET_OS_TV + +/** Tests that application:continueUserActivity:restorationHandler: is invoked on the interceptors + * if it exists. + */ +- (void)testApplicationContinueUserActivityRestorationHandlerIsInvokedOnInterceptors { + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY + continueUserActivity:OCMOCK_ANY + restorationHandler:OCMOCK_ANY]) + .andReturn(NO); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY + continueUserActivity:OCMOCK_ANY + restorationHandler:OCMOCK_ANY]) + .andReturn(NO); + + NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"]; + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:[GULApplication sharedApplication] + continueUserActivity:testUserActivity + restorationHandler:^(NSArray *restorableObjects){ + }]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + // Check that original implementation was called with proper parameters + XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]); + XCTAssertEqual(testAppDelegate.userActivity, testUserActivity); +} + +/** Tests that the results of application:continueUserActivity:restorationHandler: from the + * interceptors are ORed. + */ +- (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed { + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"]; + + BOOL shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication] + continueUserActivity:testUserActivity + restorationHandler:^(NSArray *restorableObjects){ + }]; + // Verify that it is NO when there are no interceptors. + XCTAssertFalse(shouldContinueUserActivity); + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:OCMOCK_ANY + continueUserActivity:OCMOCK_ANY + restorationHandler:OCMOCK_ANY]) + .andReturn(NO); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication] + continueUserActivity:testUserActivity + restorationHandler:^(NSArray *restorableObjects){ + }]; + // Verify that it is NO when the only interceptor returns a NO. + XCTAssertFalse(shouldContinueUserActivity); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:OCMOCK_ANY + continueUserActivity:OCMOCK_ANY + restorationHandler:OCMOCK_ANY]) + .andReturn(YES); + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + OCMExpect([interceptor application:OCMOCK_ANY + continueUserActivity:OCMOCK_ANY + restorationHandler:OCMOCK_ANY]) + .andReturn(NO); + shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication] + continueUserActivity:testUserActivity + restorationHandler:^(NSArray *restorableObjects){ + }]; + + // The result is YES if one of the interceptors returns YES. + XCTAssertTrue(shouldContinueUserActivity); +} + +- (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSData *deviceToken = [NSData data]; + GULApplication *application = [GULApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken); +} + +- (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSError *error = [NSError errorWithDomain:@"test" code:-1 userInfo:nil]; + GULApplication *application = [GULApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + GULApplication *application = [GULApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didReceiveRemoteNotification:notification]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); +} +#pragma clang diagnostic pop + +#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + GULApplication *application = [GULApplication sharedApplication]; + void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { + }; + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:[OCMArg isNotNil]]); + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:[OCMArg isNotNil]]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); +} + +- (void)verifyCompletionCalledForObserverResult:(UIBackgroundFetchResult)observerResult1 + anotherObserverResult:(UIBackgroundFetchResult)observerResult2 + swizzledResult:(UIBackgroundFetchResult)swizzledResult + expectedResult:(UIBackgroundFetchResult)expectedResult { + NSDictionary *notification = @{}; + GULApplication *application = [GULApplication sharedApplication]; + + XCTestExpectation *completionExpectation = + [[XCTestExpectation alloc] initWithDescription:@"Completion called once"]; + + void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { + XCTAssertEqual(result, expectedResult); + [completionExpectation fulfill]; + }; + + void (^onDidReceiveRemoteNotification1)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil; + [invocation getArgument:(void *)(&localCompletionHandler) atIndex:4]; + XCTAssertNotNil(localCompletionHandler); + localCompletionHandler(observerResult1); + }; + + id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:[OCMArg isNotNil]]) + .andDo(onDidReceiveRemoteNotification1); + + void (^onDidReceiveRemoteNotification2)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil; + [invocation getArgument:(void *)(&localCompletionHandler) atIndex:4]; + XCTAssertNotNil(localCompletionHandler); + localCompletionHandler(observerResult2); + }; + + id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate)); + OCMExpect([interceptor2 application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:[OCMArg isNotNil]]) + .andDo(onDidReceiveRemoteNotification2); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate); + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]; + testAppDelegate.remoteNotificationCompletionHandler(swizzledResult); + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + [self waitForExpectations:@[ completionExpectation ] timeout:0.1]; +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData + anotherObserverResult:UIBackgroundFetchResultNoData + swizzledResult:UIBackgroundFetchResultNoData + expectedResult:UIBackgroundFetchResultNoData]; +} + +- (void) + testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleFailedState { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultFailed + anotherObserverResult:UIBackgroundFetchResultFailed + swizzledResult:UIBackgroundFetchResultFailed + expectedResult:UIBackgroundFetchResultFailed]; +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_NoData { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData + anotherObserverResult:UIBackgroundFetchResultFailed + swizzledResult:UIBackgroundFetchResultFailed + expectedResult:UIBackgroundFetchResultNoData]; +} +- (void) + testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersFailed { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData + anotherObserverResult:UIBackgroundFetchResultFailed + swizzledResult:UIBackgroundFetchResultFailed + expectedResult:UIBackgroundFetchResultNewData]; +} + +- (void) + testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoData { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData + anotherObserverResult:UIBackgroundFetchResultNoData + swizzledResult:UIBackgroundFetchResultNoData + expectedResult:UIBackgroundFetchResultNewData]; +} + +- (void) + testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoDataFailed { + [self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData + anotherObserverResult:UIBackgroundFetchResultNoData + swizzledResult:UIBackgroundFetchResultFailed + expectedResult:UIBackgroundFetchResultNewData]; +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded { + // The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler: + // implementation + GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init]; + OCMStub([self.mockSharedApplication delegate]).andReturn(legacyDelegate); + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +} +#endif // TARGET_OS_IOS || TARGET_OS_TV + +#pragma mark - Tests to test that Plist flag is honored + +/** Tests that app delegate proxy is enabled when there is no Info.plist dictionary. */ +- (void)testAppProxyPlistFlag_NoFlag { + // No keys anywhere. If there is no key, the default should be enabled. + NSDictionary *mainDictionary = nil; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that app delegate proxy is enabled when there is neither the Firebase nor the non-Firebase + * Info.plist key present. + */ +- (void)testAppProxyPlistFlag_NoAppDelegateProxyKey { + // No app delegate disable key. If there is no key, the default should be enabled. + NSDictionary *mainDictionary = @{@"randomKey" : @"randomValue"}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that app delegate proxy is enabled when the Firebase plist is explicitly set to YES and + * the Google flag is not present. */ +- (void)testAppProxyPlistFlag_FirebaseEnabled { + // Set proxy enabled to YES. + NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that app delegate proxy is enabled when the Google plist is explicitly set to YES and the + * Firebase flag is not present. */ +- (void)testAppProxyPlistFlag_GoogleEnabled { + // Set proxy enabled to YES. + NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES)}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is enabled when the Firebase flag has the wrong type of value + * and the Google flag is not present. */ +- (void)testAppProxyPlist_WrongFirebaseDisableFlagValueType { + // Set proxy enabled to "NO" - a string. + NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @"NO"}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is enabled when the Google flag has the wrong type of value + * and the Firebase flag is not present. */ +- (void)testAppProxyPlist_WrongGoogleDisableFlagValueType { + // Set proxy enabled to "NO" - a string. + NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @"NO"}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is disabled when the Firebase flag is set to NO and the Google + * flag is not present. */ +- (void)testAppProxyPlist_FirebaseDisableFlag { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase + * flag is not present. */ +- (void)testAppProxyPlist_GoogleDisableFlag { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO)}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase + * flag is set to YES. */ +- (void)testAppProxyPlist_GoogleDisableFlagFirebaseEnableFlag { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{ + kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO), + kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES) + }; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase + * flag is set to YES. */ +- (void)testAppProxyPlist_FirebaseDisableFlagGoogleEnableFlag { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{ + kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES), + kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO) + }; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase + * flag is set to NO. */ +- (void)testAppProxyPlist_FirebaseDisableFlagGoogleDisableFlag { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{ + kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO), + kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO) + }; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary]; + + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + [mainBundleMock stopMocking]; +} + +/** Tests that the App Delegate is not proxied when it is disabled. */ +- (void)testAppDelegateIsNotProxiedWhenDisabled { + // Set proxy enabled to NO. + NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)}; + id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + [[[mainBundleMock stub] andReturn:mainDictionary] infoDictionary]; + XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + + id originalAppDelegate = OCMProtocolMock(@protocol(GULApplicationDelegate)); + Class originalAppDelegateClass = [originalAppDelegate class]; + XCTAssertNotNil(originalAppDelegate); + OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate); + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + XCTAssertEqualObjects([originalAppDelegate class], originalAppDelegateClass); + + [mainBundleMock stopMocking]; +} + +// TODO(tejasd): There is some weirdness that happens (at least when running this locally on Xcode) +// where the actual app delegate is nilled out in one of these tests, causing the tests to fail. +// Disabling this test seems to fix the problem. + +/** Tests that the App Delegate is proxied when it is enabled. */ +- (void)testAppDelegateIsProxiedWhenEnabled { + // App Delegate Proxying is enabled by default. + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + + id originalAppDelegate = [[GULTestAppDelegate alloc] init]; + Class originalAppDelegateClass = [originalAppDelegate class]; + XCTAssertNotNil(originalAppDelegate); + OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate); + + [GULAppDelegateSwizzler proxyOriginalDelegate]; + XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass); +} + +- (void)testAppDelegateIsProxiedIncludingAPNSMethodsWhenEnabled { + // App Delegate Proxying is enabled by default. + XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]); + + id originalAppDelegate = [[GULTestAppDelegate alloc] init]; + Class originalAppDelegateClass = [originalAppDelegate class]; + XCTAssertNotNil(originalAppDelegate); + OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate); + + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass); +} + +@end From 05e0b8b86c90139a14e547b665cfcce3bf521566 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Thu, 27 Jan 2022 21:09:20 -0800 Subject: [PATCH 06/12] adjust the init method in multicast --- .../Sources/GULMulticastAppDelegate.m | 34 +++++++++++++++++-- .../GoogleUtilities/GULMulticastAppDelegate.h | 8 ++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 3a8b9437..9107a76d 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -14,10 +14,23 @@ #import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" +#if TARGET_OS_IOS || TARGET_OS_TV + +static NSString *const kGULApplicationClassName = @"UIApplication"; + +#elif TARGET_OS_OSX + +static NSString *const kGULApplicationClassName = @"NSApplication"; + +#elif TARGET_OS_WATCH +static NSString *const kGULApplicationClassName = @"WKExtension"; + +#endif @interface GULMulticastAppDelegate () { NSMutableArray* _interceptors; + id _appDelegate; } @end @@ -31,11 +44,26 @@ - (instancetype)init { return self; } +- (instancetype)initWithAppDelegate:(id)delegate { + self = [super init]; + if (self) { + _interceptors = [[NSMutableArray alloc] init]; + [_interceptors addObject:delegate]; + _appDelegate = delegate; + } + return self; +} + + (id)multicastDelegate { - id appDelegate = [UIApplication sharedApplication].delegate; + if (!appDelegate) { + return nil; + } + if ([appDelegate conformsToProtocol:@protocol(GULMulticastAppDelegateProtocol)]) { + id multicastAppDelegate = appDelegate; + return multicastAppDelegate; + } if (appDelegate && [appDelegate respondsToSelector:@selector(getMulticastDelegate)]) { - id multicastDelegate = [appDelegate performSelector:@selector(getMulticastDelegate)]; CFRetain((__bridge CFTypeRef)(multicastDelegate)); return multicastDelegate; @@ -51,6 +79,8 @@ - (instancetype)init { -(void)addInterceptorWithDelegate:(id)interceptor { [_interceptors addObject:interceptor]; + _appDelegate = interceptor; + } -(void)removeInterceptorWithDelegate:(id)interceptor { diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index 1f017e96..48e7506d 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -22,8 +22,6 @@ #define GULApplicationDelegate UIApplicationDelegate #define GULUserActivityRestoring UIUserActivityRestoring -static NSString *const kGULApplicationClassName = @"UIApplication"; - #elif TARGET_OS_OSX #import @@ -32,8 +30,6 @@ static NSString *const kGULApplicationClassName = @"UIApplication"; #define GULApplicationDelegate NSApplicationDelegate #define GULUserActivityRestoring NSUserActivityRestoring -static NSString *const kGULApplicationClassName = @"NSApplication"; - #elif TARGET_OS_WATCH #import @@ -43,8 +39,6 @@ static NSString *const kGULApplicationClassName = @"NSApplication"; #define GULApplicationDelegate WKExtensionDelegate #define GULUserActivityRestoring NSUserActivityRestoring -static NSString *const kGULApplicationClassName = @"WKExtension"; - #endif NS_ASSUME_NONNULL_BEGIN @@ -59,6 +53,8 @@ NS_ASSUME_NONNULL_BEGIN @interface GULMulticastAppDelegate : NSObject +- (instancetype)initWithAppDelegate:(id)delegate; + - (void)addInterceptorWithDelegate:(id)delegate; + (id)multicastDelegate; From cda176f8a4b45474ed68d06966d61035036fd09b Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Fri, 28 Jan 2022 13:53:48 -0800 Subject: [PATCH 07/12] fix the issue that callback must implement to avoid crash --- .../Sources/GULMulticastAppDelegate.m | 58 +++++++++++-------- .../GoogleUtilities/GULMulticastAppDelegate.h | 9 +-- .../Tests/Unit/GULMulticastAppDelegateTest.m | 3 +- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 9107a76d..82b8d205 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -24,13 +24,13 @@ #elif TARGET_OS_WATCH -static NSString *const kGULApplicationClassName = @"WKExtension"; +static NSString* const kGULApplicationClassName = @"WKExtension"; #endif -@interface GULMulticastAppDelegate () { - NSMutableArray* _interceptors; - id _appDelegate; +@interface GULMulticastAppDelegate () { + NSMutableArray *_interceptors; + id _defaultAppDelegate; } @end @@ -49,7 +49,7 @@ - (instancetype)initWithAppDelegate:(id)delegate { if (self) { _interceptors = [[NSMutableArray alloc] init]; [_interceptors addObject:delegate]; - _appDelegate = delegate; + _defaultAppDelegate = delegate; } return self; } @@ -60,30 +60,28 @@ - (instancetype)initWithAppDelegate:(id)delegate { return nil; } if ([appDelegate conformsToProtocol:@protocol(GULMulticastAppDelegateProtocol)]) { - id multicastAppDelegate = appDelegate; + id multicastAppDelegate = + (id)appDelegate; return multicastAppDelegate; } if (appDelegate && [appDelegate respondsToSelector:@selector(getMulticastDelegate)]) { - id multicastDelegate = [appDelegate performSelector:@selector(getMulticastDelegate)]; + id multicastDelegate = + [appDelegate performSelector:@selector(getMulticastDelegate)]; CFRetain((__bridge CFTypeRef)(multicastDelegate)); return multicastDelegate; } return nil; } - - (id)getMulticastDelegate { return self; } - --(void)addInterceptorWithDelegate:(id)interceptor { +- (void)addInterceptorWithDelegate:(id)interceptor { [_interceptors addObject:interceptor]; - _appDelegate = interceptor; - } --(void)removeInterceptorWithDelegate:(id)interceptor { +- (void)removeInterceptorWithDelegate:(id)interceptor { [_interceptors removeObject:interceptor]; } @@ -100,12 +98,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector { } - (id)forwardingTargetForSelector:(SEL)aSelector { - for (id interceptor in _interceptors) { - if (interceptor && [interceptor respondsToSelector:aSelector]) { - return interceptor; - } - } - return nil; + return _defaultAppDelegate; } #if !TARGET_OS_WATCH @@ -124,23 +117,33 @@ - (BOOL)application:(GULApplication *)app - (void)application:(GULApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { for (id interceptor in _interceptors) { - [interceptor application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + if ([interceptor respondsToSelector:@selector(application: + didRegisterForRemoteNotificationsWithDeviceToken:)]) { + [interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } } } -#else // !TARGET_OS_WATCH +#else // !TARGET_OS_WATCH - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { for (id interceptor in _interceptors) { - [interceptor didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + if ([interceptor + respondsToSelector:@selector(didRegisterForRemoteNotificationsWithDeviceToken)]) { + [interceptor didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } } } -#endif // !TARGET_OS_WATCH +#endif // !TARGET_OS_WATCH #if TARGET_OS_IOS || TARGET_OS_TV - (void)application:(GULApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { for (id interceptor in _interceptors) { - [interceptor application:application didFailToRegisterForRemoteNotificationsWithError:error]; + if ([interceptor respondsToSelector:@selector(application: + didFailToRegisterForRemoteNotificationsWithError:)]) { + [interceptor application:application didFailToRegisterForRemoteNotificationsWithError:error]; + } } } @@ -148,7 +151,12 @@ - (void)application:(GULApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { for (id interceptor in _interceptors) { - [interceptor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; + if ([interceptor respondsToSelector:@selector + (application:didReceiveRemoteNotification:fetchCompletionHandler:)]) { + [interceptor application:application + didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; + } } } #endif diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index 48e7506d..efc87fd2 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -45,13 +45,15 @@ NS_ASSUME_NONNULL_BEGIN @protocol GULMulticastAppDelegateProtocol -- (void)addInterceptorWithDelegate:(id) interceptor; +- (void)addInterceptorWithDelegate:(id)interceptor; -- (void)removeInterceptorWithDelegate:(id) interceptor; +- (void)removeInterceptorWithDelegate:(id)interceptor; @end -@interface GULMulticastAppDelegate : NSObject +@interface GULMulticastAppDelegate : NSObject + +@property(nonatomic, copy) id defaultAppDelegate; - (instancetype)initWithAppDelegate:(id)delegate; @@ -61,4 +63,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - diff --git a/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m b/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m index e4a96e9f..c2eca5ce 100644 --- a/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m +++ b/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m @@ -40,7 +40,8 @@ /** This class conforms to the application delegate protocol and is there to be able to test the * App Delegate Swizzler's behavior. */ -@interface GULTestAppDelegate : NSObject, UIApplicationDelegate { +@interface GULTestAppDelegate : NSObject +, UIApplicationDelegate { @public // Because we want to access the ivars from outside the class like obj->ivar for testing. /** YES if the application:openURL:options: was called on an instance, NO otherwise. */ BOOL _isOpenURLOptionsMethodCalled; From afebcb3dab5e45d3594ff06616b208f3ab0e96c8 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Fri, 28 Jan 2022 16:13:01 -0800 Subject: [PATCH 08/12] start depending on AppDelegateSwizzler --- GoogleMulticastAppDelegate.podspec | 4 ++- .../Sources/GULMulticastAppDelegate.m | 19 ++++--------- .../GoogleUtilities/GULMulticastAppDelegate.h | 27 +------------------ 3 files changed, 9 insertions(+), 41 deletions(-) diff --git a/GoogleMulticastAppDelegate.podspec b/GoogleMulticastAppDelegate.podspec index 5addbfe6..3751db2c 100644 --- a/GoogleMulticastAppDelegate.podspec +++ b/GoogleMulticastAppDelegate.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| # TODO: Is `GoogleMulticastAppDelegate` name fine? s.name = 'GoogleMulticastAppDelegate' - s.version = '7.5.0' + s.version = '7.7.0' s.summary = 'GoogleMulticastAppDelegate' s.description = <<-DESC @@ -42,6 +42,8 @@ Pod::Spec.new do |s| base_dir + 'Sources/**/*.[hm]', ] + s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 7.7' + # s.test_spec 'unit' do |unit_tests| # unit_tests.scheme = { :code_coverage => true } # unit_tests.platforms = { diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 82b8d205..781cc955 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -14,20 +14,6 @@ #import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" -#if TARGET_OS_IOS || TARGET_OS_TV - -static NSString *const kGULApplicationClassName = @"UIApplication"; - -#elif TARGET_OS_OSX - -static NSString *const kGULApplicationClassName = @"NSApplication"; - -#elif TARGET_OS_WATCH - -static NSString* const kGULApplicationClassName = @"WKExtension"; - -#endif - @interface GULMulticastAppDelegate () { NSMutableArray *_interceptors; id _defaultAppDelegate; @@ -97,6 +83,11 @@ - (BOOL)respondsToSelector:(SEL)aSelector { return NO; } +- (void)setDefaultAppDelegate:(id)defaultAppDelegate { + [_interceptors addObject:defaultAppDelegate]; + _defaultAppDelegate = defaultAppDelegate; +} + - (id)forwardingTargetForSelector:(SEL)aSelector { return _defaultAppDelegate; } diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index efc87fd2..38b7721d 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -14,32 +14,7 @@ #import -#if TARGET_OS_IOS || TARGET_OS_TV - -#import - -#define GULApplication UIApplication -#define GULApplicationDelegate UIApplicationDelegate -#define GULUserActivityRestoring UIUserActivityRestoring - -#elif TARGET_OS_OSX - -#import - -#define GULApplication NSApplication -#define GULApplicationDelegate NSApplicationDelegate -#define GULUserActivityRestoring NSUserActivityRestoring - -#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 - -#endif +#import NS_ASSUME_NONNULL_BEGIN From b3b013b317ec62292facf221e52627952d983a7f Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 31 Jan 2022 11:54:33 -0800 Subject: [PATCH 09/12] remove swift version --- GoogleMulticastAppDelegate.podspec | 3 --- 1 file changed, 3 deletions(-) diff --git a/GoogleMulticastAppDelegate.podspec b/GoogleMulticastAppDelegate.podspec index 3751db2c..1c104046 100644 --- a/GoogleMulticastAppDelegate.podspec +++ b/GoogleMulticastAppDelegate.podspec @@ -1,5 +1,4 @@ Pod::Spec.new do |s| - # TODO: Is `GoogleMulticastAppDelegate` name fine? s.name = 'GoogleMulticastAppDelegate' s.version = '7.7.0' s.summary = 'GoogleMulticastAppDelegate' @@ -27,8 +26,6 @@ Pod::Spec.new do |s| # s.tvos.deployment_target = tvos_deployment_target # s.watchos.deployment_target = watchos_deployment_target - s.swift_versions = ['5.0', '5.2'] - s.cocoapods_version = '>= 1.4.0' s.prefix_header_file = false From 812295fd5dc067e8ba121264caf8e81a52c2a2d5 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 31 Jan 2022 13:16:20 -0800 Subject: [PATCH 10/12] rename the podspec and fix a few places based on the comments --- ...podspec => GoogleUtilitiesMulticastAppDelegate.podspec | 8 ++++---- .../Sources/GULMulticastAppDelegate.m | 3 +-- .../Public/GoogleUtilities/GULMulticastAppDelegate.h | 2 +- .../Tests/Unit/GULMulticastAppDelegateTest.m | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) rename GoogleMulticastAppDelegate.podspec => GoogleUtilitiesMulticastAppDelegate.podspec (90%) rename {GoogleMulticastAppDelegate => GoogleUtilitiesMulticastAppDelegate}/Sources/GULMulticastAppDelegate.m (98%) rename {GoogleMulticastAppDelegate => GoogleUtilitiesMulticastAppDelegate}/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h (93%) rename {GoogleMulticastAppDelegate => GoogleUtilitiesMulticastAppDelegate}/Tests/Unit/GULMulticastAppDelegateTest.m (99%) diff --git a/GoogleMulticastAppDelegate.podspec b/GoogleUtilitiesMulticastAppDelegate.podspec similarity index 90% rename from GoogleMulticastAppDelegate.podspec rename to GoogleUtilitiesMulticastAppDelegate.podspec index 1c104046..6d1a74d8 100644 --- a/GoogleMulticastAppDelegate.podspec +++ b/GoogleUtilitiesMulticastAppDelegate.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |s| - s.name = 'GoogleMulticastAppDelegate' + s.name = 'GoogleUtilitiesMulticastAppDelegate' s.version = '7.7.0' - s.summary = 'GoogleMulticastAppDelegate' + s.summary = 'GoogleUtilitiesMulticastAppDelegate' s.description = <<-DESC - GoogleMulticastAppDelegate + GoogleUtilitiesMulticastAppDelegate DESC s.homepage = 'https://github.com/google/GoogleUtilities' @@ -34,7 +34,7 @@ Pod::Spec.new do |s| 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', } - base_dir = "GoogleMulticastAppDelegate/" + base_dir = "GoogleUtilitiesMulticastAppDelegate/" s.source_files = [ base_dir + 'Sources/**/*.[hm]', ] diff --git a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m similarity index 98% rename from GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m rename to GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 781cc955..01ca3acf 100644 --- a/GoogleMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -33,8 +33,7 @@ - (instancetype)init { - (instancetype)initWithAppDelegate:(id)delegate { self = [super init]; if (self) { - _interceptors = [[NSMutableArray alloc] init]; - [_interceptors addObject:delegate]; + _interceptors = [NSMutableArray arrayWithObject:delegate]; _defaultAppDelegate = delegate; } return self; diff --git a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h similarity index 93% rename from GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h rename to GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index 38b7721d..b28f16f0 100644 --- a/GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @interface GULMulticastAppDelegate : NSObject -@property(nonatomic, copy) id defaultAppDelegate; +@property(nonatomic, weak, copy) id defaultAppDelegate; - (instancetype)initWithAppDelegate:(id)delegate; diff --git a/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m b/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m similarity index 99% rename from GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m rename to GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m index c2eca5ce..3ec60788 100644 --- a/GoogleMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m +++ b/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From e8fee5b458dc1036605de8d5b108c449baae5e5e Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 31 Jan 2022 13:34:16 -0800 Subject: [PATCH 11/12] fix a few api names --- GoogleUtilitiesMulticastAppDelegate.podspec | 2 +- .../Sources/GULMulticastAppDelegate.m | 8 ++++---- .../Public/GoogleUtilities/GULMulticastAppDelegate.h | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/GoogleUtilitiesMulticastAppDelegate.podspec b/GoogleUtilitiesMulticastAppDelegate.podspec index 6d1a74d8..22245a42 100644 --- a/GoogleUtilitiesMulticastAppDelegate.podspec +++ b/GoogleUtilitiesMulticastAppDelegate.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/google/GoogleUtilities.git', - :tag => 'MulticastAppDelegate-' + s.version.to_s + :tag => 'GoogleUtilitiesMulticastAppDelegate-' + s.version.to_s } ios_deployment_target = '9.0' diff --git a/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m b/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m index 01ca3acf..08208e15 100644 --- a/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m +++ b/GoogleUtilitiesMulticastAppDelegate/Sources/GULMulticastAppDelegate.m @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" +#import "GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" @interface GULMulticastAppDelegate () { NSMutableArray *_interceptors; @@ -49,7 +49,7 @@ - (instancetype)initWithAppDelegate:(id)delegate { (id)appDelegate; return multicastAppDelegate; } - if (appDelegate && [appDelegate respondsToSelector:@selector(getMulticastDelegate)]) { + if ([appDelegate respondsToSelector:@selector(getMulticastDelegate)]) { id multicastDelegate = [appDelegate performSelector:@selector(getMulticastDelegate)]; CFRetain((__bridge CFTypeRef)(multicastDelegate)); @@ -62,11 +62,11 @@ - (instancetype)initWithAppDelegate:(id)delegate { return self; } -- (void)addInterceptorWithDelegate:(id)interceptor { +- (void)addInterceptorWithInterceptor:(id)interceptor { [_interceptors addObject:interceptor]; } -- (void)removeInterceptorWithDelegate:(id)interceptor { +- (void)removeInterceptorWithInterceptor:(id)interceptor { [_interceptors removeObject:interceptor]; } diff --git a/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h b/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h index b28f16f0..e59e8a40 100644 --- a/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h +++ b/GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h @@ -20,19 +20,19 @@ NS_ASSUME_NONNULL_BEGIN @protocol GULMulticastAppDelegateProtocol -- (void)addInterceptorWithDelegate:(id)interceptor; +- (void)addInterceptorWithInterceptor:(id)interceptor; -- (void)removeInterceptorWithDelegate:(id)interceptor; +- (void)removeInterceptorWithInterceptor:(id)interceptor; @end @interface GULMulticastAppDelegate : NSObject -@property(nonatomic, weak, copy) id defaultAppDelegate; +@property(nonatomic, copy) id defaultAppDelegate; - (instancetype)initWithAppDelegate:(id)delegate; -- (void)addInterceptorWithDelegate:(id)delegate; +- (void)addInterceptorWithInterceptor:(id)delegate; + (id)multicastDelegate; @end From 1158a7d2eb01234aaeeac84fc3c1656b93d7e831 Mon Sep 17 00:00:00 2001 From: Charlotte Liang Date: Mon, 31 Jan 2022 14:05:04 -0800 Subject: [PATCH 12/12] fix test breakage --- .../Tests/Unit/GULMulticastAppDelegateTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m b/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m index 3ec60788..28007d98 100644 --- a/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m +++ b/GoogleUtilitiesMulticastAppDelegate/Tests/Unit/GULMulticastAppDelegateTest.m @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "GoogleMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" +#import "GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h" #import #import