| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h"
- #import <objc/runtime.h>
- #import <GoogleUtilities/GULAppDelegateSwizzler.h>
- #import "Firebase/Messaging/FIRMessagingConstants.h"
- #import "Firebase/Messaging/FIRMessagingLogger.h"
- #import "Firebase/Messaging/FIRMessagingUtilities.h"
- #import "Firebase/Messaging/FIRMessaging_Private.h"
- static void *UserNotificationObserverContext = &UserNotificationObserverContext;
- static NSString *kUserNotificationWillPresentSelectorString =
- @"userNotificationCenter:willPresentNotification:withCompletionHandler:";
- static NSString *kUserNotificationDidReceiveResponseSelectorString =
- @"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:";
- @interface FIRMessagingRemoteNotificationsProxy () <GULApplicationDelegate>
- @property(strong, nonatomic) NSMutableDictionary<NSString *, NSValue *> *originalAppDelegateImps;
- @property(strong, nonatomic) NSMutableDictionary<NSString *, NSArray *> *swizzledSelectorsByClass;
- @property(nonatomic) BOOL didSwizzleMethods;
- @property(nonatomic) BOOL hasSwizzledUserNotificationDelegate;
- @property(nonatomic) BOOL isObservingUserNotificationDelegateChanges;
- @property(strong, nonatomic) id userNotificationCenter;
- @property(strong, nonatomic) id currentUserNotificationCenterDelegate;
- @property(strong, nonatomic) GULAppDelegateInterceptorID appDelegateInterceptorID;
- @end
- @implementation FIRMessagingRemoteNotificationsProxy
- + (BOOL)canSwizzleMethods {
- return [GULAppDelegateSwizzler isAppDelegateProxyEnabled];
- }
- + (instancetype)sharedProxy {
- static FIRMessagingRemoteNotificationsProxy *proxy;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init];
- });
- return proxy;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _originalAppDelegateImps = [[NSMutableDictionary alloc] init];
- _swizzledSelectorsByClass = [[NSMutableDictionary alloc] init];
- }
- return self;
- }
- - (void)dealloc {
- [self unswizzleAllMethods];
- self.swizzledSelectorsByClass = nil;
- [self.originalAppDelegateImps removeAllObjects];
- self.originalAppDelegateImps = nil;
- [self removeUserNotificationCenterDelegateObserver];
- }
- - (void)swizzleMethodsIfPossible {
- // Already swizzled.
- if (self.didSwizzleMethods) {
- return;
- }
- [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
- self.appDelegateInterceptorID = [GULAppDelegateSwizzler registerAppDelegateInterceptor:self];
- // Add KVO listener on [UNUserNotificationCenter currentNotificationCenter]'s delegate property
- Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
- if (notificationCenterClass) {
- // We are linked against iOS 10 SDK or above
- id notificationCenter = FIRMessagingPropertyNameFromObject(
- notificationCenterClass, @"currentNotificationCenter", notificationCenterClass);
- if (notificationCenter) {
- [self listenForDelegateChangesInUserNotificationCenter:notificationCenter];
- }
- }
- self.didSwizzleMethods = YES;
- }
- - (void)unswizzleAllMethods {
- if (self.appDelegateInterceptorID) {
- [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:self.appDelegateInterceptorID];
- }
- for (NSString *className in self.swizzledSelectorsByClass) {
- Class klass = NSClassFromString(className);
- NSArray *selectorStrings = self.swizzledSelectorsByClass[className];
- for (NSString *selectorString in selectorStrings) {
- SEL selector = NSSelectorFromString(selectorString);
- [self unswizzleSelector:selector inClass:klass];
- }
- }
- [self.swizzledSelectorsByClass removeAllObjects];
- }
- - (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter {
- Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
- if (![notificationCenter isKindOfClass:notificationCenterClass]) {
- return;
- }
- id delegate = FIRMessagingPropertyNameFromObject(notificationCenter, @"delegate", nil);
- Protocol *delegateProtocol = NSProtocolFromString(@"UNUserNotificationCenterDelegate");
- if ([delegate conformsToProtocol:delegateProtocol]) {
- // Swizzle this object now, if available
- [self swizzleUserNotificationCenterDelegate:delegate];
- }
- // Add KVO observer for "delegate" keyPath for future changes
- [self addDelegateObserverToUserNotificationCenter:notificationCenter];
- }
- #pragma mark - UNNotificationCenter Swizzling
- - (void)swizzleUserNotificationCenterDelegate:(id _Nonnull)delegate {
- if (self.currentUserNotificationCenterDelegate == delegate) {
- // Via pointer-check, compare if we have already swizzled this item.
- return;
- }
- Protocol *userNotificationCenterProtocol =
- NSProtocolFromString(@"UNUserNotificationCenterDelegate");
- if ([delegate conformsToProtocol:userNotificationCenterProtocol]) {
- SEL willPresentNotificationSelector =
- NSSelectorFromString(kUserNotificationWillPresentSelectorString);
- // Swizzle the optional method
- // "userNotificationCenter:willPresentNotification:withCompletionHandler:", if it is
- // implemented. Do not swizzle otherwise, as an implementation *will* be created, which will
- // fool iOS into thinking that this method is implemented, and therefore not send notifications
- // to the fallback method in the app delegate
- // "application:didReceiveRemoteNotification:fetchCompletionHandler:".
- if ([delegate respondsToSelector:willPresentNotificationSelector]) {
- [self swizzleSelector:willPresentNotificationSelector
- inClass:[delegate class]
- withImplementation:(IMP)FCMSwizzleWillPresentNotificationWithHandler
- inProtocol:userNotificationCenterProtocol];
- }
- SEL didReceiveNotificationResponseSelector =
- NSSelectorFromString(kUserNotificationDidReceiveResponseSelectorString);
- if ([delegate respondsToSelector:didReceiveNotificationResponseSelector]) {
- [self swizzleSelector:didReceiveNotificationResponseSelector
- inClass:[delegate class]
- withImplementation:(IMP)FCMSwizzleDidReceiveNotificationResponseWithHandler
- inProtocol:userNotificationCenterProtocol];
- }
- self.currentUserNotificationCenterDelegate = delegate;
- self.hasSwizzledUserNotificationDelegate = YES;
- }
- }
- - (void)unswizzleUserNotificationCenterDelegate:(id _Nonnull)delegate {
- if (self.currentUserNotificationCenterDelegate != delegate) {
- // We aren't swizzling this delegate, so don't do anything.
- return;
- }
- SEL willPresentNotificationSelector =
- NSSelectorFromString(kUserNotificationWillPresentSelectorString);
- // Call unswizzle methods, even if the method was not implemented (it will fail gracefully).
- [self unswizzleSelector:willPresentNotificationSelector
- inClass:[self.currentUserNotificationCenterDelegate class]];
- SEL didReceiveNotificationResponseSelector =
- NSSelectorFromString(kUserNotificationDidReceiveResponseSelectorString);
- [self unswizzleSelector:didReceiveNotificationResponseSelector
- inClass:[self.currentUserNotificationCenterDelegate class]];
- self.currentUserNotificationCenterDelegate = nil;
- self.hasSwizzledUserNotificationDelegate = NO;
- }
- #pragma mark - KVO for UNUserNotificationCenter
- - (void)addDelegateObserverToUserNotificationCenter:(id)userNotificationCenter {
- [self removeUserNotificationCenterDelegateObserver];
- @try {
- [userNotificationCenter addObserver:self
- forKeyPath:NSStringFromSelector(@selector(delegate))
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:UserNotificationObserverContext];
- self.userNotificationCenter = userNotificationCenter;
- self.isObservingUserNotificationDelegateChanges = YES;
- } @catch (NSException *exception) {
- FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxy000,
- @"Encountered exception trying to add a KVO observer for "
- @"UNUserNotificationCenter's 'delegate' property: %@",
- exception);
- } @finally {
- }
- }
- - (void)removeUserNotificationCenterDelegateObserver {
- if (!self.userNotificationCenter) {
- return;
- }
- @try {
- [self.userNotificationCenter removeObserver:self
- forKeyPath:NSStringFromSelector(@selector(delegate))
- context:UserNotificationObserverContext];
- self.userNotificationCenter = nil;
- self.isObservingUserNotificationDelegateChanges = NO;
- } @catch (NSException *exception) {
- FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxy001,
- @"Encountered exception trying to remove a KVO observer for "
- @"UNUserNotificationCenter's 'delegate' property: %@",
- exception);
- } @finally {
- }
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary<NSKeyValueChangeKey, id> *)change
- context:(void *)context {
- if (context == UserNotificationObserverContext) {
- if ([keyPath isEqualToString:NSStringFromSelector(@selector(delegate))]) {
- id oldDelegate = change[NSKeyValueChangeOldKey];
- if (oldDelegate && oldDelegate != [NSNull null]) {
- [self unswizzleUserNotificationCenterDelegate:oldDelegate];
- }
- id newDelegate = change[NSKeyValueChangeNewKey];
- if (newDelegate && newDelegate != [NSNull null]) {
- [self swizzleUserNotificationCenterDelegate:newDelegate];
- }
- }
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- }
- #pragma mark - NSProxy methods
- - (void)saveOriginalImplementation:(IMP)imp forSelector:(SEL)selector {
- if (imp && selector) {
- NSValue *IMPValue = [NSValue valueWithPointer:imp];
- NSString *selectorString = NSStringFromSelector(selector);
- self.originalAppDelegateImps[selectorString] = IMPValue;
- }
- }
- - (IMP)originalImplementationForSelector:(SEL)selector {
- NSString *selectorString = NSStringFromSelector(selector);
- NSValue *implementationValue = self.originalAppDelegateImps[selectorString];
- if (!implementationValue) {
- return nil;
- }
- IMP imp;
- [implementationValue getValue:&imp];
- return imp;
- }
- - (void)trackSwizzledSelector:(SEL)selector ofClass:(Class)klass {
- NSString *className = NSStringFromClass(klass);
- NSString *selectorString = NSStringFromSelector(selector);
- NSArray *selectors = self.swizzledSelectorsByClass[selectorString];
- if (selectors) {
- selectors = [selectors arrayByAddingObject:selectorString];
- } else {
- selectors = @[ selectorString ];
- }
- self.swizzledSelectorsByClass[className] = selectors;
- }
- - (void)removeImplementationForSelector:(SEL)selector {
- NSString *selectorString = NSStringFromSelector(selector);
- [self.originalAppDelegateImps removeObjectForKey:selectorString];
- }
- - (void)swizzleSelector:(SEL)originalSelector
- inClass:(Class)klass
- withImplementation:(IMP)swizzledImplementation
- inProtocol:(Protocol *)protocol {
- Method originalMethod = class_getInstanceMethod(klass, originalSelector);
- if (originalMethod) {
- // This class implements this method, so replace the original implementation
- // with our new implementation and save the old implementation.
- IMP originalMethodImplementation =
- method_setImplementation(originalMethod, swizzledImplementation);
- IMP nonexistantMethodImplementation = [self nonExistantMethodImplementationForClass:klass];
- if (originalMethodImplementation &&
- originalMethodImplementation != nonexistantMethodImplementation &&
- originalMethodImplementation != swizzledImplementation) {
- [self saveOriginalImplementation:originalMethodImplementation forSelector:originalSelector];
- }
- } else {
- // The class doesn't have this method, so add our swizzled implementation as the
- // original implementation of the original method.
- struct objc_method_description methodDescription =
- protocol_getMethodDescription(protocol, originalSelector, NO, YES);
- BOOL methodAdded =
- class_addMethod(klass, originalSelector, swizzledImplementation, methodDescription.types);
- if (!methodAdded) {
- FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyMethodNotAdded,
- @"Could not add method for %@ to class %@",
- NSStringFromSelector(originalSelector), NSStringFromClass(klass));
- }
- }
- [self trackSwizzledSelector:originalSelector ofClass:klass];
- }
- - (void)unswizzleSelector:(SEL)selector inClass:(Class)klass {
- Method swizzledMethod = class_getInstanceMethod(klass, selector);
- if (!swizzledMethod) {
- // This class doesn't seem to have this selector as an instance method? Bail out.
- return;
- }
- IMP originalImp = [self originalImplementationForSelector:selector];
- if (originalImp) {
- // Restore the original implementation as the current implementation
- method_setImplementation(swizzledMethod, originalImp);
- [self removeImplementationForSelector:selector];
- } else {
- // This class originally did not have an implementation for this selector.
- // We can't actually remove methods in Objective C 2.0, but we could set
- // its method to something non-existent. This should give us the same
- // behavior as if the method was not implemented.
- // See: http://stackoverflow.com/a/8276527/9849
- IMP nonExistantMethodImplementation = [self nonExistantMethodImplementationForClass:klass];
- method_setImplementation(swizzledMethod, nonExistantMethodImplementation);
- }
- }
- #pragma mark - Reflection Helpers
- // This is useful to generate from a stable, "known missing" selector, as the IMP can be compared
- // in case we are setting an implementation for a class that was previously "unswizzled" into a
- // non-existant implementation.
- - (IMP)nonExistantMethodImplementationForClass:(Class)klass {
- SEL nonExistantSelector = NSSelectorFromString(@"aNonExistantMethod");
- IMP nonExistantMethodImplementation = class_getMethodImplementation(klass, nonExistantSelector);
- return nonExistantMethodImplementation;
- }
- // A safe, non-leaky way return a property object by its name
- id FIRMessagingPropertyNameFromObject(id object, NSString *propertyName, Class klass) {
- SEL selector = NSSelectorFromString(propertyName);
- if (![object respondsToSelector:selector]) {
- return nil;
- }
- if (!klass) {
- klass = [NSObject class];
- }
- // Suppress clang warning about leaks in performSelector
- // The alternative way to perform this is to invoke
- // the method as a block (see http://stackoverflow.com/a/20058585),
- // but this approach sometimes returns incomplete objects.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- id property = [object performSelector:selector];
- #pragma clang diagnostic pop
- if (![property isKindOfClass:klass]) {
- return nil;
- }
- return property;
- }
- #pragma mark - GULApplicationDelegate
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-implementations"
- - (void)application:(GULApplication *)application
- didReceiveRemoteNotification:(NSDictionary *)userInfo {
- [[FIRMessaging messaging] appDidReceiveMessage:userInfo];
- }
- #pragma clang diagnostic pop
- #if TARGET_OS_IOS || TARGET_OS_TV
- - (void)application:(UIApplication *)application
- didReceiveRemoteNotification:(NSDictionary *)userInfo
- fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
- [[FIRMessaging messaging] appDidReceiveMessage:userInfo];
- }
- - (void)application:(UIApplication *)application
- didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
- // Log the fact that we failed to register for remote notifications
- FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed,
- @"Error in "
- @"application:didFailToRegisterForRemoteNotificationsWithError: %@",
- error.localizedDescription);
- }
- #endif
- - (void)application:(GULApplication *)application
- didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
- [FIRMessaging messaging].APNSToken = deviceToken;
- }
- #pragma mark - Swizzled Methods
- /**
- * Swizzle the notification handler for iOS 10+ devices.
- * Signature of original handler is as below:
- * - (void)userNotificationCenter:(UNUserNotificationCenter *)center
- * willPresentNotification:(UNNotification *)notification
- * withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
- * In order to make FCM SDK compile and compatible with iOS SDKs before iOS 10, hide the
- * parameter types from the swizzling implementation.
- */
- static void FCMSwizzleWillPresentNotificationWithHandler(
- id self, SEL cmd, id center, id notification, void (^handler)(NSUInteger)) {
- FIRMessagingRemoteNotificationsProxy *proxy = [FIRMessagingRemoteNotificationsProxy sharedProxy];
- IMP originalImp = [proxy originalImplementationForSelector:cmd];
- void (^callOriginalMethodIfAvailable)(void) = ^{
- if (originalImp) {
- ((void (*)(id, SEL, id, id, void (^)(NSUInteger)))originalImp)(self, cmd, center,
- notification, handler);
- }
- return;
- };
- Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
- Class notificationClass = NSClassFromString(@"UNNotification");
- if (!notificationCenterClass || !notificationClass) {
- // Can't find UserNotifications framework. Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- }
- if (!center || ![center isKindOfClass:[notificationCenterClass class]]) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- if (!notification || ![notification isKindOfClass:[notificationClass class]]) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- if (!handler) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- // Attempt to access the user info
- id notificationUserInfo = FIRMessagingUserInfoFromNotification(notification);
- if (!notificationUserInfo) {
- // Could not access notification.request.content.userInfo.
- callOriginalMethodIfAvailable();
- return;
- }
- [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo];
- // Execute the original implementation.
- callOriginalMethodIfAvailable();
- }
- /**
- * Swizzle the notification handler for iOS 10+ devices.
- * Signature of original handler is as below:
- * - (void)userNotificationCenter:(UNUserNotificationCenter *)center
- * didReceiveNotificationResponse:(UNNotificationResponse *)response
- * withCompletionHandler:(void (^)(void))completionHandler
- * In order to make FCM SDK compile and compatible with iOS SDKs before iOS 10, hide the
- * parameter types from the swizzling implementation.
- */
- static void FCMSwizzleDidReceiveNotificationResponseWithHandler(
- id self, SEL cmd, id center, id response, void (^handler)(void)) {
- FIRMessagingRemoteNotificationsProxy *proxy = [FIRMessagingRemoteNotificationsProxy sharedProxy];
- IMP originalImp = [proxy originalImplementationForSelector:cmd];
- void (^callOriginalMethodIfAvailable)(void) = ^{
- if (originalImp) {
- ((void (*)(id, SEL, id, id, void (^)(void)))originalImp)(self, cmd, center, response,
- handler);
- }
- return;
- };
- Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
- Class responseClass = NSClassFromString(@"UNNotificationResponse");
- if (!center || ![center isKindOfClass:[notificationCenterClass class]]) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- if (!response || ![response isKindOfClass:[responseClass class]]) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- if (!handler) {
- // Invalid parameter type from the original method.
- // Do not swizzle, just execute the original method.
- callOriginalMethodIfAvailable();
- return;
- }
- // Try to access the response.notification property
- SEL notificationSelector = NSSelectorFromString(@"notification");
- if (![response respondsToSelector:notificationSelector]) {
- // Cannot access the .notification property.
- callOriginalMethodIfAvailable();
- return;
- }
- id notificationClass = NSClassFromString(@"UNNotification");
- id notification =
- FIRMessagingPropertyNameFromObject(response, @"notification", notificationClass);
- // With a notification object, use the common code to reach deep into notification
- // (notification.request.content.userInfo)
- id notificationUserInfo = FIRMessagingUserInfoFromNotification(notification);
- if (!notificationUserInfo) {
- // Could not access notification.request.content.userInfo.
- callOriginalMethodIfAvailable();
- return;
- }
- [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo];
- // Execute the original implementation.
- callOriginalMethodIfAvailable();
- }
- static id FIRMessagingUserInfoFromNotification(id notification) {
- // Select the userInfo field from UNNotification.request.content.userInfo.
- SEL requestSelector = NSSelectorFromString(@"request");
- if (![notification respondsToSelector:requestSelector]) {
- // Cannot access the request property.
- return nil;
- }
- Class requestClass = NSClassFromString(@"UNNotificationRequest");
- id notificationRequest =
- FIRMessagingPropertyNameFromObject(notification, @"request", requestClass);
- SEL notificationContentSelector = NSSelectorFromString(@"content");
- if (!notificationRequest ||
- ![notificationRequest respondsToSelector:notificationContentSelector]) {
- // Cannot access the content property.
- return nil;
- }
- Class contentClass = NSClassFromString(@"UNNotificationContent");
- id notificationContent =
- FIRMessagingPropertyNameFromObject(notificationRequest, @"content", contentClass);
- SEL notificationUserInfoSelector = NSSelectorFromString(@"userInfo");
- if (!notificationContent ||
- ![notificationContent respondsToSelector:notificationUserInfoSelector]) {
- // Cannot access the userInfo property.
- return nil;
- }
- id notificationUserInfo =
- FIRMessagingPropertyNameFromObject(notificationContent, @"userInfo", [NSDictionary class]);
- if (!notificationUserInfo) {
- // This is not the expected notification handler.
- return nil;
- }
- return notificationUserInfo;
- }
- @end
|