Skip to content

Commit d679f93

Browse files
committed
Do not swizzle a subclass of an already swizzled class
Adding unit test for swizzling subclass Adding subClass swizzling check to UNUserNotificationCenter Don't swizzle subclass if we have already swizzled its super
1 parent 01e6e63 commit d679f93

File tree

6 files changed

+180
-6
lines changed

6 files changed

+180
-6
lines changed

iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UIApplicationDelegate+OneSignalNotifications.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#import "OneSignalSelectorHelpers.h"
3535
#import "SwizzlingForwarder.h"
3636
#import "OSNotificationsManager.h"
37+
#import <objc/runtime.h>
3738

3839
// This class hooks into the UIApplicationDelegate selectors to receive iOS 9 and older events.
3940
// - UNUserNotificationCenter is used for iOS 10
@@ -58,7 +59,7 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
5859

5960
Class delegateClass = [delegate class];
6061

61-
if (delegate == nil || [swizzledClasses containsObject:delegateClass]) {
62+
if (delegate == nil || [OneSignalNotificationsAppDelegate swizzledClassInHeirarchy:delegateClass]) {
6263
[self setOneSignalDelegate:delegate];
6364
return;
6465
}
@@ -93,6 +94,22 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
9394
[self setOneSignalDelegate:delegate];
9495
}
9596

97+
+ (BOOL)swizzledClassInHeirarchy:(Class)delegateClass {
98+
if ([swizzledClasses containsObject:delegateClass]) {
99+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@", NSStringFromClass(delegateClass)]];
100+
return true;
101+
}
102+
Class superClass = class_getSuperclass(delegateClass);
103+
while(superClass) {
104+
if ([swizzledClasses containsObject:superClass]) {
105+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@ in super class: %@", NSStringFromClass(delegateClass), NSStringFromClass(superClass)]];
106+
return true;
107+
}
108+
superClass = class_getSuperclass(superClass);
109+
}
110+
return false;
111+
}
112+
96113
- (void)oneSignalDidRegisterForRemoteNotifications:(UIApplication*)app deviceToken:(NSData*)inDeviceToken {
97114
[OneSignalNotificationsAppDelegate traceCall:@"oneSignalDidRegisterForRemoteNotifications:deviceToken:"];
98115

iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#import "UIApplicationDelegate+OneSignalNotifications.h"
3434
#import "OSNotificationsManager.h"
3535
#import <OneSignalCore/OneSignalCore.h>
36+
#import <objc/runtime.h>
3637
#pragma clang diagnostic push
3738
#pragma clang diagnostic ignored "-Wundeclared-selector"
3839

@@ -182,7 +183,7 @@ - (void) setOneSignalUNDelegate:(id)delegate {
182183

183184
Class delegateClass = [delegate class];
184185

185-
if (delegate == nil || [swizzledClasses containsObject:delegateClass]) {
186+
if (delegate == nil || [OneSignalNotificationsUNUserNotificationCenter swizzledClassInHeirarchy:delegateClass]) {
186187
[self setOneSignalUNDelegate:delegate];
187188
return;
188189
}
@@ -196,6 +197,22 @@ - (void) setOneSignalUNDelegate:(id)delegate {
196197
[self setOneSignalUNDelegate:delegate];
197198
}
198199

200+
+ (BOOL)swizzledClassInHeirarchy:(Class)delegateClass {
201+
if ([swizzledClasses containsObject:delegateClass]) {
202+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@", NSStringFromClass(delegateClass)]];
203+
return true;
204+
}
205+
Class superClass = class_getSuperclass(delegateClass);
206+
while(superClass) {
207+
if ([swizzledClasses containsObject:superClass]) {
208+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@ in super class: %@", NSStringFromClass(delegateClass), NSStringFromClass(superClass)]];
209+
return true;
210+
}
211+
superClass = class_getSuperclass(superClass);
212+
}
213+
return false;
214+
}
215+
199216
+ (void)swizzleSelectorsOnDelegate:(id)delegate {
200217
Class delegateUNClass = [delegate class];
201218
injectSelector(

iOS_SDK/OneSignalSDK/Source/UIApplicationDelegate+OneSignal.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#import "OneSignalTracker.h"
3535
#import "OneSignalSelectorHelpers.h"
3636
#import "SwizzlingForwarder.h"
37+
#import <objc/runtime.h>
3738

3839
@interface OneSignal (UN_extra)
3940
+ (NSString*) appId;
@@ -61,7 +62,7 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
6162

6263
Class delegateClass = [delegate class];
6364

64-
if (delegate == nil || [swizzledClasses containsObject:delegateClass]) {
65+
if (delegate == nil || [OneSignalAppDelegate swizzledClassInHeirarchy:delegateClass]) {
6566
[self setOneSignalDelegate:delegate];
6667
return;
6768
}
@@ -79,6 +80,22 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
7980
[self setOneSignalDelegate:delegate];
8081
}
8182

83+
+ (BOOL)swizzledClassInHeirarchy:(Class)delegateClass {
84+
if ([swizzledClasses containsObject:delegateClass]) {
85+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@", NSStringFromClass(delegateClass)]];
86+
return true;
87+
}
88+
Class superClass = class_getSuperclass(delegateClass);
89+
while(superClass) {
90+
if ([swizzledClasses containsObject:superClass]) {
91+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@ in super class: %@", NSStringFromClass(delegateClass), NSStringFromClass(superClass)]];
92+
return true;
93+
}
94+
superClass = class_getSuperclass(superClass);
95+
}
96+
return false;
97+
}
98+
8299
-(void)oneSignalApplicationWillTerminate:(UIApplication *)application {
83100
[OneSignalAppDelegate traceCall:@"oneSignalApplicationWillTerminate:"];
84101

iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingIntegrationTests.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ of this software and associated documentation files (the "Software"), to deal
2626
*/
2727

2828
#import <XCTest/XCTest.h>
29-
#import "OneSignal.h"
29+
#import "OneSignalFramework.h"
3030
#import "OneSignalUserDefaults.h"
3131
#import "OneSignalHelper.h"
3232
#import "OSInAppMessageInternal.h"

iOS_SDK/OneSignalSDK/UnitTests/OneSignalUNUserNotificationCenterSwizzlingTest.m

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ @interface UNUserNotificationCenterDelegateForInfiniteLoopWithAnotherSwizzlerTes
130130
@end
131131
@implementation UNUserNotificationCenterDelegateForInfiniteLoopWithAnotherSwizzlerTest
132132
@end
133-
@interface OtherUNNotificationLibraryASwizzler : NSObject
133+
@interface OtherUNNotificationLibraryASwizzler : UIResponder<UNUserNotificationCenterDelegate>
134134
+(void)swizzleUNUserNotificationCenterDelegate;
135135
+(BOOL)selectorCalled;
136136
@end
@@ -157,7 +157,33 @@ -(void)userNotificationCenterLibraryA:(UNUserNotificationCenter *)center willPre
157157
[self userNotificationCenterLibraryA:center willPresentNotification:notification withCompletionHandler:completionHandler];
158158
}
159159
@end
160+
@interface OtherUNNotificationLibraryBSubClassSwizzler : OtherUNNotificationLibraryASwizzler
161+
+(void)swizzleUNUserNotificationCenterDelegate;
162+
+(BOOL)selectorCalled;
163+
@end
164+
@implementation OtherUNNotificationLibraryBSubClassSwizzler
160165

166+
+(BOOL)selectorCalled {
167+
return selectorCalled;
168+
}
169+
170+
+(void)swizzleUNUserNotificationCenterDelegate
171+
{
172+
swizzleExistingSelector(
173+
[UNUserNotificationCenter.currentNotificationCenter.delegate class],
174+
@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:),
175+
[self class],
176+
@selector(userNotificationCenterLibraryB:willPresentNotification:withCompletionHandler:)
177+
);
178+
}
179+
-(void)userNotificationCenterLibraryB:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
180+
{
181+
selectorCalled = true;
182+
// Standard basic swizzling forwarder another library may have.
183+
if ([self respondsToSelector:@selector(userNotificationCenterLibraryA:willPresentNotification:withCompletionHandler:)])
184+
[self userNotificationCenterLibraryB:center willPresentNotification:notification withCompletionHandler:completionHandler];
185+
}
186+
@end
161187

162188

163189
@interface OneSignalUNUserNotificationCenterSwizzlingTest : XCTestCase
@@ -319,6 +345,41 @@ - (void)testDoubleSwizzleInfiniteLoop {
319345
[localOrignalDelegate userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter willPresentNotification:[self createBasiciOSNotification] withCompletionHandler:^(UNNotificationPresentationOptions options) {}];
320346
}
321347

348+
- (void)testNotificationCenterSubClassIsNotSwizzledTwice {
349+
// 1. Create a new delegate and assign it
350+
id myDelegate = [UNUserNotificationCenterDelegateForInfiniteLoopTest new];
351+
UNUserNotificationCenter.currentNotificationCenter.delegate = myDelegate;
352+
353+
// 2. Create another Library's app delegate and assign it then swizzle
354+
id thierDelegate = [OtherUNNotificationLibraryASwizzler new];
355+
UNUserNotificationCenter.currentNotificationCenter.delegate = thierDelegate;
356+
[OtherUNNotificationLibraryASwizzler swizzleUNUserNotificationCenterDelegate];
357+
358+
// 3. Create another Library's app delegate subclass and assign it then swizzle
359+
id thierDelegateSubClass = [OtherUNNotificationLibraryBSubClassSwizzler new];
360+
UNUserNotificationCenter.currentNotificationCenter.delegate = thierDelegateSubClass;
361+
[OtherUNNotificationLibraryBSubClassSwizzler swizzleUNUserNotificationCenterDelegate];
362+
363+
// 4. Call something to confirm we don't get stuck in an infinite call loop
364+
id<UNUserNotificationCenterDelegate> delegate =
365+
UNUserNotificationCenter.currentNotificationCenter.delegate;
366+
[delegate
367+
userNotificationCenter:UNUserNotificationCenter.currentNotificationCenter
368+
willPresentNotification:[self createBasiciOSNotification]
369+
withCompletionHandler:^(UNNotificationPresentationOptions options) {}
370+
];
371+
372+
// 5. Ensure OneSignal's selector is called.
373+
XCTAssertEqual([OneSignalUNUserNotificationCenterOverrider
374+
callCountForSelector:@"onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:"], 1);
375+
376+
// 6. Ensure other library selector is still called too.
377+
XCTAssertTrue([OtherUNNotificationLibraryASwizzler selectorCalled]);
378+
379+
// 7. Ensure other library subclass selector is still called too.
380+
XCTAssertTrue([OtherUNNotificationLibraryBSubClassSwizzler selectorCalled]);
381+
}
382+
322383
- (void)testCompatibleWithOtherSwizzlerWhenSwapingBetweenNil {
323384
// 1. Create a new delegate and assign it
324385
id myAppDelegate = [UNUserNotificationCenterDelegateForInfiniteLoopWithAnotherSwizzlerTest new];

iOS_SDK/OneSignalSDK/UnitTests/UIApplicationDelegateSwizzlingTests.m

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#import "TestHelperFunctions.h"
88
#import "OneSignalAppDelegateOverrider.h"
99

10+
#define ONESIGNALApplicationDelegate UIApplicationDelegate
11+
1012
@interface AppDelegateForAddsMissingSelectorsTest : UIResponder<UIApplicationDelegate>
1113
@end
1214
@implementation AppDelegateForAddsMissingSelectorsTest
@@ -90,7 +92,8 @@ @interface AppDelegateForInfiniteLoopWithAnotherSwizzlerTest : UIResponder<UIApp
9092
@end
9193
@implementation AppDelegateForInfiniteLoopWithAnotherSwizzlerTest
9294
@end
93-
@interface OtherLibraryASwizzler : NSObject
95+
96+
@interface OtherLibraryASwizzler : UIResponder<ONESIGNALApplicationDelegate>
9497
+(void)swizzleAppDelegate;
9598
+(BOOL)selectorCalled;
9699
@end
@@ -109,6 +112,7 @@ +(void)swizzleAppDelegate
109112
@selector(applicationWillTerminateLibraryA:)
110113
);
111114
}
115+
112116
- (void)applicationWillTerminateLibraryA:(UIApplication *)application
113117
{
114118
selectorCalled = true;
@@ -118,6 +122,33 @@ - (void)applicationWillTerminateLibraryA:(UIApplication *)application
118122
}
119123
@end
120124

125+
126+
@interface OtherLibraryBSwizzlerSubClass : OtherLibraryASwizzler
127+
@end
128+
@implementation OtherLibraryBSwizzlerSubClass
129+
+(BOOL)selectorCalled {
130+
return selectorCalled;
131+
}
132+
133+
+(void)swizzleAppDelegate
134+
{
135+
swizzleExistingSelector(
136+
[UIApplication.sharedApplication.delegate class],
137+
@selector(applicationWillTerminate:),
138+
[self class],
139+
@selector(applicationWillTerminateLibraryB:)
140+
);
141+
}
142+
143+
- (void)applicationWillTerminateLibraryB:(UIApplication *)application
144+
{
145+
selectorCalled = true;
146+
// Standard basic swizzling forwarder another library may have.
147+
if ([self respondsToSelector:@selector(applicationWillTerminateLibraryB:)])
148+
[self applicationWillTerminateLibraryB:application];
149+
}
150+
@end
151+
121152
@interface AppDelegateForExistingSelectorsTest : UIResponder<UIApplicationDelegate> {
122153
@public NSMutableDictionary *selectorCallsDict;
123154
}
@@ -408,6 +439,36 @@ - (void)testDoubleSwizzleInfiniteLoop {
408439
[localOrignalDelegate applicationWillTerminate:UIApplication.sharedApplication];
409440
}
410441

442+
- (void)testAppDelegateSubClassIsNotSwizzledTwice {
443+
// 1. Create a new delegate and assign it
444+
id myAppDelegate = [AppDelegateForInfiniteLoopWithAnotherSwizzlerTest new];
445+
UIApplication.sharedApplication.delegate = myAppDelegate;
446+
447+
// 2. Create another Library's app delegate and assign it then swizzle
448+
id thierAppDelegate = [OtherLibraryASwizzler new];
449+
UIApplication.sharedApplication.delegate = thierAppDelegate;
450+
[OtherLibraryASwizzler swizzleAppDelegate];
451+
452+
// 3. Create another Library's app delegate subclass and assign it then swizzle
453+
id thierAppDelegateSubClass = [OtherLibraryBSwizzlerSubClass new];
454+
UIApplication.sharedApplication.delegate = thierAppDelegateSubClass;
455+
[OtherLibraryBSwizzlerSubClass swizzleAppDelegate];
456+
457+
// 4. Call something to confirm we don't get stuck in an infinite call loop
458+
id<UIApplicationDelegate> delegate = UIApplication.sharedApplication.delegate;
459+
[delegate applicationWillTerminate:UIApplication.sharedApplication];
460+
461+
// 5. Ensure OneSignal's selector is called.
462+
XCTAssertEqual([OneSignalAppDelegateOverrider
463+
callCountForSelector:@"oneSignalApplicationWillTerminate:"], 1);
464+
465+
// 6. Ensure other library selector is still called too.
466+
XCTAssertTrue([OtherLibraryASwizzler selectorCalled]);
467+
468+
// 7. Ensure other library subclass selector is still called too.
469+
XCTAssertTrue([OtherLibraryBSwizzlerSubClass selectorCalled]);
470+
}
471+
411472
- (void)testCompatibleWithOtherSwizzlerWhenSwapingBetweenNil {
412473
// 1. Create a new delegate and assign it
413474
id myAppDelegate = [AppDelegateForInfiniteLoopWithAnotherSwizzlerTest new];
@@ -579,4 +640,5 @@ - (void)testAppDelegateInheritsFromBaseWhereBothHaveSelectorsButSuperIsNotCalled
579640
XCTAssertFalse(myAppDelegate.selectorCalledOnParent);
580641
XCTAssertEqual([OneSignalAppDelegateOverrider callCountForSelector:@"oneSignalReceiveRemoteNotification:UserInfo:fetchCompletionHandler:"], 1);
581642
}
643+
582644
@end

0 commit comments

Comments
 (0)