Skip to content

Commit 2879e22

Browse files
authored
Merge pull request #1097 from OneSignal/swizzle_forward_target_usernotificationcenter
Use Forward Target Swizzling check for UNUserNotificationCenterDelegate methods
2 parents 0d4bc5c + 7cb0c50 commit 2879e22

File tree

2 files changed

+265
-14
lines changed

2 files changed

+265
-14
lines changed

iOS_SDK/OneSignalSDK/Source/UNUserNotificationCenter+OneSignal.m

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
#import "OneSignalSelectorHelpers.h"
3737
#import "UIApplicationDelegate+OneSignal.h"
3838
#import "OneSignalCommonDefines.h"
39-
39+
#import "SwizzlingForwarder.h"
4040
#pragma clang diagnostic push
4141
#pragma clang diagnostic ignored "-Wundeclared-selector"
4242

@@ -205,21 +205,31 @@ + (void)swizzleSelectorsOnDelegate:(id)delegate {
205205
@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), delegateUNSubclasses, [OneSignalUNUserNotificationCenter class], delegateUNClass);
206206
}
207207

208-
+ (void)forwardNotificationWithCenter:(UNUserNotificationCenter *)center
208+
+ (BOOL)forwardNotificationWithCenter:(UNUserNotificationCenter *)center
209209
notification:(UNNotification *)notification
210210
OneSignalCenter:(id)instance
211211
completionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
212-
// Call orginal selector if one was set.
213-
if ([instance respondsToSelector:@selector(onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:)])
214-
[instance onesignalUserNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler];
215-
// Or call a legacy AppDelegate selector
216-
else {
212+
SwizzlingForwarder *forwarder = [[SwizzlingForwarder alloc]
213+
initWithTarget:instance
214+
withYourSelector:@selector(
215+
onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:
216+
)
217+
withOriginalSelector:@selector(
218+
userNotificationCenter:willPresentNotification:withCompletionHandler:
219+
)
220+
];
221+
if (forwarder.hasReceiver) {
222+
[forwarder invokeWithArgs:@[center, notification, completionHandler]];
223+
return true;
224+
} else {
225+
// call a legacy AppDelegate selector
217226
[OneSignalUNUserNotificationCenter callLegacyAppDeletegateSelector:notification
218227
isTextReply:false
219228
actionIdentifier:nil
220229
userText:nil
221230
fromPresentNotification:true
222231
withCompletionHandler:^() {}];
232+
return false;
223233
}
224234
}
225235

@@ -232,8 +242,8 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center
232242

233243
// return if the user has not granted privacy permissions or if not a OneSignal payload
234244
if ([OSPrivacyConsentController shouldLogMissingPrivacyConsentErrorWithMethodName:nil] || ![OneSignalHelper isOneSignalPayload:notification.request.content.userInfo]) {
235-
[OneSignalUNUserNotificationCenter forwardNotificationWithCenter:center notification:notification OneSignalCenter:self completionHandler:completionHandler];
236-
if (![self respondsToSelector:@selector(onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:)]) {
245+
BOOL hasReceiver = [OneSignalUNUserNotificationCenter forwardNotificationWithCenter:center notification:notification OneSignalCenter:self completionHandler:completionHandler];
246+
if (!hasReceiver) {
237247
completionHandler(7);
238248
}
239249
return;
@@ -278,10 +288,20 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center
278288
withCompletionHandler:(void(^)())completionHandler {
279289
// return if the user has not granted privacy permissions or if not a OneSignal payload
280290
if ([OSPrivacyConsentController shouldLogMissingPrivacyConsentErrorWithMethodName:nil] || ![OneSignalHelper isOneSignalPayload:response.notification.request.content.userInfo]) {
281-
if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)])
282-
[self onesignalUserNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
283-
else
291+
SwizzlingForwarder *forwarder = [[SwizzlingForwarder alloc]
292+
initWithTarget:self
293+
withYourSelector:@selector(
294+
onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
295+
)
296+
withOriginalSelector:@selector(
297+
userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
298+
)
299+
];
300+
if (forwarder.hasReceiver) {
301+
[forwarder invokeWithArgs:@[center, response, completionHandler]];
302+
} else {
284303
completionHandler();
304+
}
285305
return;
286306
}
287307

@@ -290,8 +310,18 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center
290310
[OneSignalUNUserNotificationCenter processiOS10Open:response];
291311

292312
// Call orginal selector if one was set.
293-
if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)])
294-
[self onesignalUserNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
313+
SwizzlingForwarder *forwarder = [[SwizzlingForwarder alloc]
314+
initWithTarget:self
315+
withYourSelector:@selector(
316+
onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
317+
)
318+
withOriginalSelector:@selector(
319+
userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
320+
)
321+
];
322+
if (forwarder.hasReceiver) {
323+
[forwarder invokeWithArgs:@[center, response, completionHandler]];
324+
}
295325
// Or call a legacy AppDelegate selector
296326
// - If not a dismiss event as their isn't a iOS 9 selector for it.
297327
else if (![OneSignalUNUserNotificationCenter isDismissEvent:response]) {

iOS_SDK/OneSignalSDK/UnitTests/OneSignalUNUserNotificationCenterSwizzlingTest.m

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,98 @@ -(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotif
3030
}
3131
@end
3232

33+
@interface UNUserNotificationCenterDelegateForwardingTargetForSelectorTest : UIResponder<UNUserNotificationCenterDelegate>
34+
@end
35+
@implementation UNUserNotificationCenterDelegateForwardingTargetForSelectorTest {
36+
id forwardingInstance;
37+
}
38+
- (instancetype)initForwardingTarget:(id)forwardingTarget {
39+
self = [super init];
40+
forwardingInstance = forwardingTarget;
41+
return self;
42+
}
43+
44+
- (id)forwardingTargetForSelector:(SEL)selector {
45+
return forwardingInstance;
46+
}
47+
@end
48+
49+
@interface UNUserNotificationCenterDelegateForwardReceiver : UIResponder<UNUserNotificationCenterDelegate> {
50+
@public NSMutableDictionary *selectorCallsDict;
51+
}
52+
@end
53+
@implementation UNUserNotificationCenterDelegateForwardReceiver
54+
55+
- (instancetype)init {
56+
self = [super init];
57+
selectorCallsDict = [NSMutableDictionary new];
58+
return self;
59+
}
60+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
61+
SEL thisSelector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
62+
[selectorCallsDict
63+
setObject:@(true)
64+
forKey:NSStringFromSelector(thisSelector)
65+
];
66+
}
67+
68+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
69+
SEL thisSelector = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
70+
[selectorCallsDict
71+
setObject:@(true)
72+
forKey:NSStringFromSelector(thisSelector)
73+
];
74+
}
75+
76+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification {
77+
SEL thisSelector = @selector(userNotificationCenter:openSettingsForNotification:);
78+
[selectorCallsDict
79+
setObject:@(true)
80+
forKey:NSStringFromSelector(thisSelector)
81+
];
82+
}
83+
@end
84+
85+
@interface UNUserNotificationCenterDelegateForExistingSelectorsTest : UIResponder<UNUserNotificationCenterDelegate> {
86+
@public NSMutableDictionary *selectorCallsDict;
87+
}
88+
@end
89+
@implementation UNUserNotificationCenterDelegateForExistingSelectorsTest
90+
91+
- (instancetype)init {
92+
self = [super init];
93+
selectorCallsDict = [NSMutableDictionary new];
94+
return self;
95+
}
96+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
97+
SEL thisSelector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
98+
[selectorCallsDict
99+
setObject:@(true)
100+
forKey:NSStringFromSelector(thisSelector)
101+
];
102+
}
103+
104+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
105+
SEL thisSelector = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
106+
[selectorCallsDict
107+
setObject:@(true)
108+
forKey:NSStringFromSelector(thisSelector)
109+
];
110+
}
111+
112+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification {
113+
SEL thisSelector = @selector(userNotificationCenter:openSettingsForNotification:);
114+
[selectorCallsDict
115+
setObject:@(true)
116+
forKey:NSStringFromSelector(thisSelector)
117+
];
118+
}
119+
@end
120+
121+
@interface UNUserNotificationCenterDelegateForInfiniteLoopTest : UIResponder<UNUserNotificationCenterDelegate>
122+
@end
123+
@implementation UNUserNotificationCenterDelegateForInfiniteLoopTest
124+
@end
33125

34126
@interface OneSignalUNUserNotificationCenterSwizzlingTest : XCTestCase
35127
@end
@@ -50,6 +142,37 @@ - (void)tearDown {
50142
[OneSignalUNUserNotificationCenterHelper restoreDelegateAsOneSignal];
51143
}
52144

145+
- (UNNotificationResponse *)createBasiciOSNotificationResponse {
146+
id userInfo = @{@"custom":
147+
@{ @"i": @"b2f7f966-d8cc-11e4-bed1-df8f05be55ba" }
148+
};
149+
150+
return [UnitTestCommonMethods createBasiciOSNotificationResponseWithPayload:userInfo];
151+
}
152+
153+
- (UNNotification *)createNonOneSignaliOSNotification {
154+
id userInfo = @{@"aps": @{
155+
@"mutable-content": @1,
156+
@"alert": @"Message Body"
157+
}
158+
};
159+
160+
return [UnitTestCommonMethods createBasiciOSNotificationWithPayload:userInfo];
161+
}
162+
163+
- (UNNotification *)createBasiciOSNotification {
164+
id userInfo = @{@"aps": @{
165+
@"mutable-content": @1,
166+
@"alert": @"Message Body"
167+
},
168+
@"os_data": @{
169+
@"i": @"b2f7f966-d8cc-11e4-bed1-df8f05be55ba",
170+
@"buttons": @[@{@"i": @"id1", @"n": @"text1"}],
171+
}};
172+
173+
return [UnitTestCommonMethods createBasiciOSNotificationWithPayload:userInfo];
174+
}
175+
53176
// Tests to make sure that UNNotificationCenter setDelegate: duplicate calls don't double-swizzle for the same object
54177
- (void)testAUNUserNotificationCenterDelegateAssigningDoesSwizzle {
55178
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
@@ -93,4 +216,102 @@ - (void)testUNUserNotificationCenterDelegateAssignedBeforeOneSignal {
93216
XCTAssertNotEqual(originalDummyImp, swizzledDummyImp);
94217
}
95218

219+
- (void)testForwardingTargetForSelector {
220+
UNUserNotificationCenterDelegateForwardReceiver *receiver = [UNUserNotificationCenterDelegateForwardReceiver new];
221+
id myNotifCenterDelegate = [[UNUserNotificationCenterDelegateForwardingTargetForSelectorTest alloc]
222+
initForwardingTarget:receiver];
223+
UNUserNotificationCenter.currentNotificationCenter.delegate = myNotifCenterDelegate;
224+
id<UNUserNotificationCenterDelegate> notifCenterDelegate = UNUserNotificationCenter.currentNotificationCenter.delegate;
225+
226+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
227+
willPresentNotification:[self createBasiciOSNotification]
228+
withCompletionHandler:^(UNNotificationPresentationOptions options) {}];
229+
XCTAssertTrue([receiver->selectorCallsDict
230+
objectForKey:NSStringFromSelector(
231+
@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)
232+
)
233+
]);
234+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
235+
didReceiveNotificationResponse:[self createBasiciOSNotificationResponse]
236+
withCompletionHandler:^{}];
237+
XCTAssertTrue([receiver->selectorCallsDict
238+
objectForKey:NSStringFromSelector(
239+
@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)
240+
)
241+
]);
242+
if (@available(iOS 12.0, *)) {
243+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
244+
openSettingsForNotification:[self createBasiciOSNotification]];
245+
}
246+
XCTAssertTrue([receiver->selectorCallsDict
247+
objectForKey:NSStringFromSelector(
248+
@selector(userNotificationCenter:openSettingsForNotification:)
249+
)
250+
]);
251+
}
252+
253+
- (void)testForwardingTargetForNonOneSignalNotification {
254+
UNUserNotificationCenterDelegateForwardReceiver *receiver = [UNUserNotificationCenterDelegateForwardReceiver new];
255+
id myNotifCenterDelegate = [[UNUserNotificationCenterDelegateForwardingTargetForSelectorTest alloc]
256+
initForwardingTarget:receiver];
257+
UNUserNotificationCenter.currentNotificationCenter.delegate = myNotifCenterDelegate;
258+
id<UNUserNotificationCenterDelegate> notifCenterDelegate = UNUserNotificationCenter.currentNotificationCenter.delegate;
259+
260+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
261+
willPresentNotification:[self createNonOneSignaliOSNotification]
262+
withCompletionHandler:^(UNNotificationPresentationOptions options) {}];
263+
XCTAssertTrue([receiver->selectorCallsDict
264+
objectForKey:NSStringFromSelector(
265+
@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)
266+
)
267+
]);
268+
}
269+
270+
- (void)testDoubleSwizzleInfiniteLoop {
271+
// 1. Save original delegate
272+
id<UNUserNotificationCenterDelegate> localOrignalDelegate = UNUserNotificationCenter.currentNotificationCenter.delegate;
273+
274+
// 2. Create a new delegate and assign it
275+
id myDelegate = [UNUserNotificationCenterDelegateForInfiniteLoopTest new];
276+
UNUserNotificationCenter.currentNotificationCenter.delegate = myDelegate;
277+
278+
// 3. Put the original delegate back
279+
UNUserNotificationCenter.currentNotificationCenter.delegate = localOrignalDelegate;
280+
281+
// 4. Call something to confirm we don't get stuck in an infinite call loop
282+
[localOrignalDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter willPresentNotification:[self createBasiciOSNotification] withCompletionHandler:^(UNNotificationPresentationOptions options) {}];
283+
}
284+
285+
- (void)testSwizzleExistingSelectors {
286+
UNUserNotificationCenterDelegateForExistingSelectorsTest* myNotifCenterDelegate = [UNUserNotificationCenterDelegateForExistingSelectorsTest new];
287+
UNUserNotificationCenter.currentNotificationCenter.delegate = myNotifCenterDelegate;
288+
id<UNUserNotificationCenterDelegate> notifCenterDelegate = UNUserNotificationCenter.currentNotificationCenter.delegate;
289+
290+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
291+
willPresentNotification:[self createBasiciOSNotification]
292+
withCompletionHandler:^(UNNotificationPresentationOptions options) {}];
293+
XCTAssertTrue([myNotifCenterDelegate->selectorCallsDict
294+
objectForKey:NSStringFromSelector(
295+
@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)
296+
)
297+
]);
298+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
299+
didReceiveNotificationResponse:[self createBasiciOSNotificationResponse]
300+
withCompletionHandler:^{}];
301+
XCTAssertTrue([myNotifCenterDelegate->selectorCallsDict
302+
objectForKey:NSStringFromSelector(
303+
@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)
304+
)
305+
]);
306+
if (@available(iOS 12.0, *)) {
307+
[notifCenterDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
308+
openSettingsForNotification:[self createBasiciOSNotification]];
309+
}
310+
XCTAssertTrue([myNotifCenterDelegate->selectorCallsDict
311+
objectForKey:NSStringFromSelector(
312+
@selector(userNotificationCenter:openSettingsForNotification:)
313+
)
314+
]);
315+
}
316+
96317
@end

0 commit comments

Comments
 (0)