Skip to content

Commit 789b339

Browse files
authored
Merge pull request #1284 from OneSignal/Fix/swizzling_subclass_of_already_swizzled_class
Fix swizzling subclass of already swizzled class
2 parents a7a6142 + c692754 commit 789b339

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed

iOS_SDK/OneSignalSDK/Source/UIApplicationDelegate+OneSignal.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#import "OneSignalHelper.h"
3939
#import "OSMessagingController.h"
4040
#import "SwizzlingForwarder.h"
41+
#import <objc/runtime.h>
4142

4243
@interface OneSignal (UN_extra)
4344
+ (void) didRegisterForRemoteNotifications:(UIApplication*)app deviceToken:(NSData*)inDeviceToken;
@@ -75,7 +76,7 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
7576

7677
Class delegateClass = [delegate class];
7778

78-
if (delegate == nil || [swizzledClasses containsObject:delegateClass]) {
79+
if (delegate == nil || [OneSignalAppDelegate swizzledClassInHeirarchy:delegateClass]) {
7980
[self setOneSignalDelegate:delegate];
8081
return;
8182
}
@@ -120,6 +121,22 @@ - (void) setOneSignalDelegate:(id<UIApplicationDelegate>)delegate {
120121
[self setOneSignalDelegate:delegate];
121122
}
122123

124+
+ (BOOL)swizzledClassInHeirarchy:(Class)delegateClass {
125+
if ([swizzledClasses containsObject:delegateClass]) {
126+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@", NSStringFromClass(delegateClass)]];
127+
return true;
128+
}
129+
Class superClass = class_getSuperclass(delegateClass);
130+
while(superClass) {
131+
if ([swizzledClasses containsObject:superClass]) {
132+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@ in super class: %@", NSStringFromClass(delegateClass), NSStringFromClass(superClass)]];
133+
return true;
134+
}
135+
superClass = class_getSuperclass(superClass);
136+
}
137+
return false;
138+
}
139+
123140
+ (void)swizzlePreiOS10Methods:(Class)delegateClass {
124141
if ([OneSignalHelper isIOSVersionGreaterThanOrEqual:@"10.0"])
125142
return;

iOS_SDK/OneSignalSDK/Source/UNUserNotificationCenter+OneSignal.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#import "UIApplicationDelegate+OneSignal.h"
3838
#import "OneSignalCommonDefines.h"
3939
#import "SwizzlingForwarder.h"
40+
#import <objc/runtime.h>
4041
#pragma clang diagnostic push
4142
#pragma clang diagnostic ignored "-Wundeclared-selector"
4243

@@ -192,7 +193,7 @@ - (void) setOneSignalUNDelegate:(id)delegate {
192193

193194
Class delegateClass = [delegate class];
194195

195-
if (delegate == nil || [swizzledClasses containsObject:delegateClass]) {
196+
if (delegate == nil || [OneSignalUNUserNotificationCenter swizzledClassInHeirarchy:delegateClass]) {
196197
[self setOneSignalUNDelegate:delegate];
197198
return;
198199
}
@@ -206,6 +207,22 @@ - (void) setOneSignalUNDelegate:(id)delegate {
206207
[self setOneSignalUNDelegate:delegate];
207208
}
208209

210+
+ (BOOL)swizzledClassInHeirarchy:(Class)delegateClass {
211+
if ([swizzledClasses containsObject:delegateClass]) {
212+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@", NSStringFromClass(delegateClass)]];
213+
return true;
214+
}
215+
Class superClass = class_getSuperclass(delegateClass);
216+
while(superClass) {
217+
if ([swizzledClasses containsObject:superClass]) {
218+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OneSignal already swizzled %@ in super class: %@", NSStringFromClass(delegateClass), NSStringFromClass(superClass)]];
219+
return true;
220+
}
221+
superClass = class_getSuperclass(superClass);
222+
}
223+
return false;
224+
}
225+
209226
+ (void)swizzleSelectorsOnDelegate:(id)delegate {
210227
Class delegateUNClass = [delegate class];
211228
injectSelector(

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];
@@ -624,4 +685,5 @@ - (void)testAppDelegateInheritsFromBaseWhereBothHaveSelectorsButSuperIsNotCalled
624685
XCTAssertFalse(myAppDelegate.selectorCalledOnParent);
625686
XCTAssertEqual([OneSignalAppDelegateOverrider callCountForSelector:@"oneSignalReceiveRemoteNotification:UserInfo:fetchCompletionHandler:"], 1);
626687
}
688+
627689
@end

0 commit comments

Comments
 (0)