Skip to content

Commit acbfd28

Browse files
authored
Swizzling (#364)
* Swizzling • Ensures that UNNotificationCenter setDelegate: will never get called twice, which is useful if other SDK's are swizzling the same method incorrectly in a way that invites race conditions * Add Test • Adds a test to make sure that calling UNUserNotificationCenter +setDelegate: does not swizzle the same object twice. * Add Comment • Adds a comment to explain what is happening in the registerAsUNNotificationCenterDelegate() method which looks a bit confusing
1 parent 45ab228 commit acbfd28

File tree

6 files changed

+86
-0
lines changed

6 files changed

+86
-0
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
CAABF34B205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; };
154154
CAABF34C205B157B0042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; };
155155
CAABF34D205B157B0042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; };
156+
CAB411AE208931EE005A70D1 /* DummyNotificationCenterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CAB411AD208931EE005A70D1 /* DummyNotificationCenterDelegate.m */; };
156157
CAEA1C66202BB3C600FBFE9E /* OSEmailSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = CA810FCF202BA97300A60FED /* OSEmailSubscription.h */; };
157158
/* End PBXBuildFile section */
158159

@@ -292,6 +293,8 @@
292293
CAA4ED0020646762005BD59B /* BadgeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BadgeTests.m; sourceTree = "<group>"; };
293294
CAABF349205B15780042F8E5 /* OneSignalExtensionBadgeHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalExtensionBadgeHandler.h; sourceTree = "<group>"; };
294295
CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalExtensionBadgeHandler.m; sourceTree = "<group>"; };
296+
CAB411AC208931EE005A70D1 /* DummyNotificationCenterDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DummyNotificationCenterDelegate.h; sourceTree = "<group>"; };
297+
CAB411AD208931EE005A70D1 /* DummyNotificationCenterDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DummyNotificationCenterDelegate.m; sourceTree = "<group>"; };
295298
/* End PBXFileReference section */
296299

297300
/* Begin PBXFrameworksBuildPhase section */
@@ -427,6 +430,8 @@
427430
4529DED41FA823B900CEAB1D /* TestHelperFunctions.m */,
428431
4529DEF41FA8460C00CEAB1D /* UnitTestAppDelegate.h */,
429432
4529DEF51FA8460C00CEAB1D /* UnitTestAppDelegate.m */,
433+
CAB411AC208931EE005A70D1 /* DummyNotificationCenterDelegate.h */,
434+
CAB411AD208931EE005A70D1 /* DummyNotificationCenterDelegate.m */,
430435
);
431436
path = UnitTests;
432437
sourceTree = "<group>";
@@ -836,6 +841,7 @@
836841
4529DEE11FA82AB300CEAB1D /* NSBundleOverrider.m in Sources */,
837842
912412441E73342200E41FD7 /* UNUserNotificationCenter+OneSignal.m in Sources */,
838843
9124123C1E73342200E41FD7 /* OneSignalWebView.m in Sources */,
844+
CAB411AE208931EE005A70D1 /* DummyNotificationCenterDelegate.m in Sources */,
839845
4529DEF01FA8433500CEAB1D /* NSLocaleOverrider.m in Sources */,
840846
CA08FC751FE99B00004C445F /* OneSignalClient.m in Sources */,
841847
9129C6BA1E89E59B009CB6A0 /* OSPermission.m in Sources */,

iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,14 @@ +(NSString*)randomStringWithLength:(int)length {
540540

541541
+ (void)registerAsUNNotificationCenterDelegate {
542542
let curNotifCenter = [UNUserNotificationCenter currentNotificationCenter];
543+
544+
/*
545+
Sets the OneSignal shared instance as a delegate of UNUserNotificationCenter
546+
OneSignal does not implement the delegate methods, we simply set it as a delegate
547+
in order to swizzle the UNUserNotificationCenter methods in case the developer
548+
does not set a UNUserNotificationCenter delegate themselves
549+
*/
550+
543551
if (!curNotifCenter.delegate)
544552
curNotifCenter.delegate = (id)[self sharedInstance];
545553
}

iOS_SDK/OneSignalSDK/Source/UNUserNotificationCenter+OneSignal.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ @implementation OneSignalUNUserNotificationCenter
6868
// But rather in one of the subclasses
6969
static NSArray* delegateUNSubclasses = nil;
7070

71+
//ensures setDelegate: swizzles will never get executed twice for the same delegate object
72+
//captures a weak reference to avoid retain cycles
73+
__weak static id previousDelegate;
74+
7175
+ (void)swizzleSelectors {
7276
injectToProperClass(@selector(setOneSignalUNDelegate:), @selector(setDelegate:), @[], [OneSignalUNUserNotificationCenter class], [UNUserNotificationCenter class]);
7377

@@ -120,6 +124,13 @@ - (void)onesignalGetNotificationSettingsWithCompletionHandler:(void(^)(UNNotific
120124
// - Selector will be called once if developer does not set a UNUserNotificationCenter delegate.
121125
// - Selector will be called a 2nd time if the developer does set one.
122126
- (void) setOneSignalUNDelegate:(id)delegate {
127+
if (previousDelegate == delegate) {
128+
[self setOneSignalUNDelegate:delegate];
129+
return;
130+
}
131+
132+
previousDelegate = delegate;
133+
123134
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"OneSignalUNUserNotificationCenter setOneSignalUNDelegate Fired!"];
124135

125136
delegateUNClass = getClassWithProtocolInHierarchy([delegate class], @protocol(UNUserNotificationCenterDelegate));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// DummyNotificationCenterDelegate.h
3+
// UnitTests
4+
//
5+
// Created by Brad Hesse on 4/19/18.
6+
// Copyright © 2018 Hiptic. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
#import <UserNotifications/UserNotifications.h>
11+
12+
@interface DummyNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
13+
14+
@end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// DummyNotificationCenterDelegate.m
3+
// UnitTests
4+
//
5+
// Created by Brad Hesse on 4/19/18.
6+
// Copyright © 2018 Hiptic. All rights reserved.
7+
//
8+
9+
#import "DummyNotificationCenterDelegate.h"
10+
11+
@implementation DummyNotificationCenterDelegate
12+
13+
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
14+
15+
}
16+
17+
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
18+
19+
}
20+
21+
@end

iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
#import "OneSignalClientOverrider.h"
8080
#import "OneSignalCommonDefines.h"
8181

82+
#import "DummyNotificationCenterDelegate.h"
83+
8284
@interface OneSignalHelper (TestHelper)
8385
+ (NSString*)downloadMediaAndSaveInBundle:(NSString*)urlString;
8486
@end
@@ -1850,4 +1852,28 @@ - (void)testHandlingMediaUrlExtensions {
18501852
XCTAssertNotNil(cacheName);
18511853
}
18521854

1855+
//tests to make sure that UNNotificationCenter setDelegate: duplicate calls don't double-swizzle for the same object
1856+
- (void)testSwizzling {
1857+
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
1858+
1859+
DummyNotificationCenterDelegate *delegate = [[DummyNotificationCenterDelegate alloc] init];
1860+
1861+
IMP original = class_getMethodImplementation([delegate class], @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:));
1862+
1863+
center.delegate = delegate;
1864+
1865+
IMP swizzled = class_getMethodImplementation([delegate class], @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:));
1866+
1867+
XCTAssertNotEqual(original, swizzled);
1868+
1869+
//calling setDelegate: a second time on the same object should not re-exchange method implementations
1870+
//thus the new method implementation should still be the same, swizzled == newSwizzled should be true
1871+
center.delegate = delegate;
1872+
1873+
IMP newSwizzled = class_getMethodImplementation([delegate class], @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:));
1874+
1875+
XCTAssertNotEqual(original, newSwizzled);
1876+
XCTAssertEqual(swizzled, newSwizzled);
1877+
}
1878+
18531879
@end

0 commit comments

Comments
 (0)