Skip to content

Commit 85423d0

Browse files
authored
Merge pull request #225 from apptentive/branch_5.2.3
Release 5.2.3
2 parents 978b403 + d6f1cbf commit 85423d0

24 files changed

+312
-36
lines changed

Apptentive/Apptentive.xcodeproj/project.pbxproj

+17-6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@
7878
01798C021EAF94FE00633164 /* ApptentivePayloadSender.h in Headers */ = {isa = PBXBuildFile; fileRef = 01798C001EAF94FD00633164 /* ApptentivePayloadSender.h */; };
7979
01798C031EAF94FE00633164 /* ApptentivePayloadSender.m in Sources */ = {isa = PBXBuildFile; fileRef = 01798C011EAF94FD00633164 /* ApptentivePayloadSender.m */; };
8080
017E54ED1F3B860E00EA9F81 /* ApptentiveJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 017E54EC1F3B860E00EA9F81 /* ApptentiveJSONSerializationTests.m */; };
81+
018E1CE021936B1400E58F33 /* ApptentiveEngagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018E1CDF21936B1300E58F33 /* ApptentiveEngagementTests.swift */; };
82+
018E1CE321936B6600E58F33 /* conversation-4.archive in Resources */ = {isa = PBXBuildFile; fileRef = 018E1CE121936B6600E58F33 /* conversation-4.archive */; };
83+
018E1CE421936B6600E58F33 /* conversation-5.archive in Resources */ = {isa = PBXBuildFile; fileRef = 018E1CE221936B6600E58F33 /* conversation-5.archive */; };
8184
018FAFDF1FC4A9C6007C52FE /* ApptentiveAndClause.h in Headers */ = {isa = PBXBuildFile; fileRef = 018FAFDD1FC4A9C6007C52FE /* ApptentiveAndClause.h */; };
8285
018FAFE01FC4A9C6007C52FE /* ApptentiveAndClause.m in Sources */ = {isa = PBXBuildFile; fileRef = 018FAFDE1FC4A9C6007C52FE /* ApptentiveAndClause.m */; };
8386
018FAFE31FC4AC41007C52FE /* ApptentiveOrClause.h in Headers */ = {isa = PBXBuildFile; fileRef = 018FAFE11FC4AC41007C52FE /* ApptentiveOrClause.h */; };
@@ -512,6 +515,9 @@
512515
01798C001EAF94FD00633164 /* ApptentivePayloadSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApptentivePayloadSender.h; sourceTree = "<group>"; };
513516
01798C011EAF94FD00633164 /* ApptentivePayloadSender.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApptentivePayloadSender.m; sourceTree = "<group>"; };
514517
017E54EC1F3B860E00EA9F81 /* ApptentiveJSONSerializationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApptentiveJSONSerializationTests.m; sourceTree = "<group>"; };
518+
018E1CDF21936B1300E58F33 /* ApptentiveEngagementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApptentiveEngagementTests.swift; sourceTree = "<group>"; };
519+
018E1CE121936B6600E58F33 /* conversation-4.archive */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "conversation-4.archive"; sourceTree = "<group>"; };
520+
018E1CE221936B6600E58F33 /* conversation-5.archive */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "conversation-5.archive"; sourceTree = "<group>"; };
515521
018FAFDD1FC4A9C6007C52FE /* ApptentiveAndClause.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApptentiveAndClause.h; sourceTree = "<group>"; };
516522
018FAFDE1FC4A9C6007C52FE /* ApptentiveAndClause.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ApptentiveAndClause.m; sourceTree = "<group>"; };
517523
018FAFE11FC4AC41007C52FE /* ApptentiveOrClause.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApptentiveOrClause.h; sourceTree = "<group>"; };
@@ -1024,6 +1030,7 @@
10241030
01A2CF9E1E49062800C2103A /* ApptentiveTests */ = {
10251031
isa = PBXGroup;
10261032
children = (
1033+
01917D431E5E0B7400B37D82 /* ApptentiveTests-Bridging-Header.h */,
10271034
01201AD21FC637BD00EB3593 /* CodePointAndInteractionTests.m */,
10281035
EF4EABA22040A8C5003318C9 /* utils */,
10291036
01A2D20F1E4946D500C2103A /* data */,
@@ -1041,7 +1048,6 @@
10411048
01A2D2071E4946D500C2103A /* ApptentiveMigrationTests.m */,
10421049
01A2D2091E4946D500C2103A /* ApptentiveStyleSheetTests.m */,
10431050
01A2D20A1E4946D500C2103A /* ApptentiveSurveyTests.m */,
1044-
01917D431E5E0B7400B37D82 /* ApptentiveTests-Bridging-Header.h */,
10451051
01A2D20B1E4946D500C2103A /* ApptentiveUtilitiesTests.m */,
10461052
01917D441E5E0B7400B37D82 /* ConversationManagerTests.swift */,
10471053
01A2D20D1E4946D500C2103A /* CriteriaTests.h */,
@@ -1053,6 +1059,7 @@
10531059
0123005E20531698000EC3C3 /* ClauseTests.m */,
10541060
EF4EAB99203F9821003318C9 /* AppptentiveAsyncLogWriterTests.swift */,
10551061
012ED92B2072FABE003D87F3 /* RetryPolicyTests.swift */,
1062+
018E1CDF21936B1300E58F33 /* ApptentiveEngagementTests.swift */,
10561063
);
10571064
path = ApptentiveTests;
10581065
sourceTree = "<group>";
@@ -1645,6 +1652,8 @@
16451652
01A2D20F1E4946D500C2103A /* data */ = {
16461653
isa = PBXGroup;
16471654
children = (
1655+
018E1CE121936B6600E58F33 /* conversation-4.archive */,
1656+
018E1CE221936B6600E58F33 /* conversation-5.archive */,
16481657
EFF4D2A51EC37F0A00FD4EFE /* containers */,
16491658
01A2D2161E4946D500C2103A /* criteria */,
16501659
01A2D2101E4946D500C2103A /* ATDataModelv1.sqlite */,
@@ -2081,6 +2090,8 @@
20812090
01A2D2551E4946D600C2103A /* testCornerCasesThatShouldBeFalse.json in Resources */,
20822091
01A2D2531E4946D600C2103A /* testCodePointInvokesVersion.json in Resources */,
20832092
01A2D25F1E4946D600C2103A /* testOperatorGreaterThanOrEqual.json in Resources */,
2093+
018E1CE321936B6600E58F33 /* conversation-4.archive in Resources */,
2094+
018E1CE421936B6600E58F33 /* conversation-5.archive in Resources */,
20842095
01A2D25B1E4946D600C2103A /* testOperatorContains.json in Resources */,
20852096
01A2D24E1E4946D600C2103A /* ATDataModelv3.sqlite in Resources */,
20862097
01A2D26A1E4946D600C2103A /* testJsonDiffing.1.new.json in Resources */,
@@ -2280,6 +2291,7 @@
22802291
EFF4D2AC1EC39EC000FD4EFE /* ApptentiveAppDataContainer.m in Sources */,
22812292
01A2D2481E4946D600C2103A /* ApptentiveSurveyTests.m in Sources */,
22822293
01A2D29B1E4963A500C2103A /* ATDataModel v3 to v4.xcmappingmodel in Sources */,
2294+
018E1CE021936B1400E58F33 /* ApptentiveEngagementTests.swift in Sources */,
22832295
0174772F1EA92D7D00A0A949 /* PayloadTests.swift in Sources */,
22842296
01201AD31FC637BE00EB3593 /* CodePointAndInteractionTests.m in Sources */,
22852297
01A2D2451E4946D600C2103A /* ApptentiveMigrationTests.m in Sources */,
@@ -2382,7 +2394,7 @@
23822394
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
23832395
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
23842396
COPY_PHASE_STRIP = NO;
2385-
CURRENT_PROJECT_VERSION = 20;
2397+
CURRENT_PROJECT_VERSION = 23;
23862398
DEBUG_INFORMATION_FORMAT = dwarf;
23872399
ENABLE_STRICT_OBJC_MSGSEND = YES;
23882400
ENABLE_TESTABILITY = YES;
@@ -2440,7 +2452,7 @@
24402452
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
24412453
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
24422454
COPY_PHASE_STRIP = NO;
2443-
CURRENT_PROJECT_VERSION = 20;
2455+
CURRENT_PROJECT_VERSION = 23;
24442456
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
24452457
ENABLE_NS_ASSERTIONS = NO;
24462458
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -2472,7 +2484,7 @@
24722484
DEFINES_MODULE = YES;
24732485
DEVELOPMENT_TEAM = 86WML2UN43;
24742486
DYLIB_COMPATIBILITY_VERSION = 1;
2475-
DYLIB_CURRENT_VERSION = 20;
2487+
DYLIB_CURRENT_VERSION = 23;
24762488
DYLIB_INSTALL_NAME_BASE = "@rpath";
24772489
GCC_PREFIX_HEADER = "Apptentive/Misc/ApptentiveConnect-Prefix.pch";
24782490
GCC_PREPROCESSOR_DEFINITIONS = "APPTENTIVE_DEBUG=1";
@@ -2494,7 +2506,7 @@
24942506
DEFINES_MODULE = YES;
24952507
DEVELOPMENT_TEAM = 86WML2UN43;
24962508
DYLIB_COMPATIBILITY_VERSION = 1;
2497-
DYLIB_CURRENT_VERSION = 20;
2509+
DYLIB_CURRENT_VERSION = 23;
24982510
DYLIB_INSTALL_NAME_BASE = "@rpath";
24992511
GCC_PREFIX_HEADER = "Apptentive/Misc/ApptentiveConnect-Prefix.pch";
25002512
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
@@ -2538,7 +2550,6 @@
25382550
};
25392551
name = Release;
25402552
};
2541-
25422553
/* End XCBuildConfiguration section */
25432554

25442555
/* Begin XCConfigurationList section */

Apptentive/Apptentive/Apptentive.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ FOUNDATION_EXPORT double ApptentiveVersionNumber;
2020
FOUNDATION_EXPORT const unsigned char ApptentiveVersionString[];
2121

2222
/** The version number of the Apptentive SDK. */
23-
#define kApptentiveVersionString @"5.2.2"
23+
#define kApptentiveVersionString @"5.2.3"
2424

2525
/** The version number of the Apptentive API platform. */
2626
#define kApptentiveAPIVersionString @"9"

Apptentive/Apptentive/Engagement/Model/ApptentiveConversationManager.m

+7
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,8 @@ - (BOOL)updateActiveConversation:(ApptentiveConversation *)conversation withResp
821821

822822
[self saveConversation:self.activeConversation];
823823

824+
self.messageManager.conversation = self.activeConversation;
825+
824826
[self handleConversationStateChange:self.activeConversation];
825827

826828
[self updateManifestIfNeeded];
@@ -854,10 +856,15 @@ - (BOOL)updateLegacyConversation:(ApptentiveConversation *)conversation withResp
854856
mutableConversation.state = ApptentiveConversationStateAnonymous;
855857
}
856858

859+
[self.messageManager stop];
860+
857861
self.activeConversation = mutableConversation;
858862
self.activeConversation.delegate = self;
859863

860864
[self saveConversation:self.activeConversation];
865+
866+
self.messageManager.conversation = self.activeConversation;
867+
861868
[self handleConversationStateChange:self.activeConversation];
862869

863870
[self updateManifestIfNeeded];

Apptentive/Apptentive/Engagement/Model/ApptentiveCount.h

+12
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ NS_ASSUME_NONNULL_BEGIN
7979
*/
8080
- (void)invoke;
8181

82+
83+
/**
84+
Adds the values from `otherCount` to the current object.
85+
86+
@param oldCount The older count object, presumably from a previous version.
87+
@param newCount The newer count object, which would have been invoked last.
88+
@return The sum of the two count objects.
89+
90+
@discussion If either is nil, the other one is returned. If both are nil, nil is returned.
91+
*/
92+
+ (ApptentiveCount *)mergeOldCount:(nullable ApptentiveCount *)oldCount withNewCount:(nullable ApptentiveCount *)newCount;
93+
8294
@end
8395

8496
NS_ASSUME_NONNULL_END

Apptentive/Apptentive/Engagement/Model/ApptentiveCount.m

+10
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ - (NSString *)description {
7878
return [NSString stringWithFormat:@"[%@] totalCount=%ld versionCount=%ld buildCount=%ld lastInvoked=%@", NSStringFromClass([self class]), (unsigned long)_totalCount, (unsigned long)_versionCount, (unsigned long)_buildCount, _lastInvoked];
7979
}
8080

81+
// This is used for migrating version 5.1.0 through 5.2.2 (with unescaped code points) to 5.2.3 and later.
82+
+ (ApptentiveCount *)mergeOldCount:(nullable ApptentiveCount *)oldCount withNewCount:(nullable ApptentiveCount *)newCount {
83+
NSInteger totalCount = oldCount.totalCount + newCount.totalCount;
84+
NSInteger versionCount = newCount.versionCount; // Old count is likely to be for a different version
85+
NSInteger buildCount = newCount.buildCount; // Old count is likely to be for a different build
86+
NSDate *lastInvoked = newCount.lastInvoked ?: oldCount.lastInvoked; // New count, if present, will have been invoked last
87+
88+
return [[ApptentiveCount alloc] initWithTotalCount:totalCount versionCount:versionCount buildCount:buildCount lastInvoked:lastInvoked];
89+
}
90+
8191
@end
8292

8393

Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.h

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ NS_ASSUME_NONNULL_BEGIN
7272
*/
7373
- (void)resetBuild;
7474

75+
// Test only
76+
+ (nullable NSString *)escapedKeyForKey:(NSString *)key;
77+
@property (readonly, nonatomic) NSInteger version;
78+
7579
@end
7680

7781
NS_ASSUME_NONNULL_END

Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.m

+90-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
#import "ApptentiveEngagement.h"
1010
#import "ApptentiveCount.h"
11+
#import "ApptentiveBackend+Engagement.h"
1112

1213
NS_ASSUME_NONNULL_BEGIN
1314

1415
static NSString *const InteractionsKey = @"interactions";
1516
static NSString *const CodePointsKey = @"codePoints";
17+
static NSString *const VersionKey = @"version";
1618

1719
// Legacy keys
1820
static NSString *const ATEngagementCodePointsInvokesTotalKey = @"ATEngagementCodePointsInvokesTotalKey";
@@ -23,23 +25,24 @@
2325
static NSString *const ATEngagementInteractionsInvokesVersionKey = @"ATEngagementInteractionsInvokesVersionKey";
2426
static NSString *const ATEngagementInteractionsInvokesBuildKey = @"ATEngagementInteractionsInvokesBuildKey";
2527
static NSString *const ATEngagementInteractionsInvokesLastDateKey = @"ATEngagementInteractionsInvokesLastDateKey";
26-
28+
static NSInteger const CurrentVersion = 2;
2729

2830
@interface ApptentiveEngagement ()
2931

3032
@property (strong, nonatomic) NSMutableDictionary<NSString *, ApptentiveCount *> *mutableInteractions;
3133
@property (strong, nonatomic) NSMutableDictionary<NSString *, ApptentiveCount *> *mutableCodePoints;
34+
@property (assign, nonatomic) NSInteger version;
3235

3336
@end
3437

35-
3638
@implementation ApptentiveEngagement
3739

3840
- (instancetype)init {
3941
self = [super init];
4042
if (self) {
4143
_mutableInteractions = [NSMutableDictionary dictionary];
4244
_mutableCodePoints = [NSMutableDictionary dictionary];
45+
_version = CurrentVersion;
4346
}
4447
return self;
4548
}
@@ -49,6 +52,20 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder {
4952
if (self) {
5053
_mutableInteractions = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:InteractionsKey];
5154
_mutableCodePoints = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:CodePointsKey];
55+
if ([coder containsValueForKey:VersionKey]) {
56+
_version = [coder decodeIntegerForKey:VersionKey];
57+
} else {
58+
_version = 1;
59+
}
60+
61+
@try {
62+
if (_version != CurrentVersion) {
63+
[self migrateFrom:_version to:CurrentVersion];
64+
}
65+
} @catch(NSException *exception) {
66+
ApptentiveLogError(ApptentiveLogTagConversation, @"Caught exception %e when migrating engagement data. Starting over.", exception);
67+
return [self init];
68+
}
5269
}
5370
return self;
5471
}
@@ -57,8 +74,10 @@ - (void)encodeWithCoder:(NSCoder *)coder {
5774
[super encodeWithCoder:coder];
5875
[coder encodeObject:self.mutableInteractions forKey:InteractionsKey];
5976
[coder encodeObject:self.mutableCodePoints forKey:CodePointsKey];
77+
[coder encodeInteger:self.version forKey:VersionKey];
6078
}
6179

80+
// This migrates pre-4.0 data stored in NSUserDefaults to 4.0 and later versions stored in NSCoding archive
6281
- (instancetype)initAndMigrate {
6382
self = [self init];
6483

@@ -96,6 +115,75 @@ + (void)deleteMigratedData {
96115
[[NSUserDefaults standardUserDefaults] removeObjectForKey:ATEngagementInteractionsInvokesLastDateKey];
97116
}
98117

118+
- (void)migrateFrom:(NSInteger)fromVersion to:(NSInteger)toVersion {
119+
if (fromVersion == 1 && toVersion == 2) {
120+
if ([self escapeUnescapedKeysInCodePoints]) {
121+
_version = toVersion;
122+
}
123+
}
124+
}
125+
126+
- (BOOL)escapeUnescapedKeysInCodePoints {
127+
NSMutableArray *codePointsToMerge = [NSMutableArray array];
128+
129+
for (NSString *key in self.codePoints) {
130+
NSString *escapedKey = [[self class] escapedKeyForKey:key];
131+
132+
if (escapedKey == nil) {
133+
continue;
134+
}
135+
136+
[codePointsToMerge addObject:@[key, escapedKey]];
137+
}
138+
139+
NSMutableDictionary *escapedCodePoints = [NSMutableDictionary dictionaryWithDictionary:self.codePoints];
140+
for (NSArray *keys in codePointsToMerge) {
141+
NSString *key = keys[0];
142+
NSString *escapedKey = keys[1];
143+
144+
ApptentiveCount *oldCount = [self.codePoints objectForKey:key];
145+
ApptentiveCount *newCount = [self.codePoints objectForKey:escapedKey];
146+
escapedCodePoints[escapedKey] = [ApptentiveCount mergeOldCount:oldCount withNewCount:newCount];
147+
[escapedCodePoints removeObjectForKey:key];
148+
}
149+
150+
_mutableCodePoints = escapedCodePoints;
151+
152+
return YES;
153+
}
154+
155+
+ (nullable NSString *)escapedKeyForKey:(NSString *)key {
156+
NSArray *keyParts = [key componentsSeparatedByString:@"#"];
157+
158+
if (keyParts.count < 3) {
159+
ApptentiveLogWarning(ApptentiveLogTagConversation, @"Unable to migrate unencoded code point %@", key);
160+
return nil;
161+
}
162+
163+
NSString *vendor = keyParts[0];
164+
NSString *interaction = keyParts[1];
165+
// If the event name had pound signs in it, then there will be two or more parts starting at index 2.
166+
// We join those parts with a pound sign, which conveniently no-ops in the case of a single part.
167+
NSString *event = [[keyParts subarrayWithRange:NSMakeRange(2, keyParts.count - 2)] componentsJoinedByString:@"#"];
168+
169+
if ([[self class] eventNeedsEscaping:event]) {
170+
return [ApptentiveBackend codePointForVendor:vendor interactionType:interaction event:event];
171+
} else {
172+
return nil;
173+
}
174+
}
175+
176+
// Use some heuristics to see if the event name needs to be escaped and hasn't already been escaped
177+
+ (BOOL)eventNeedsEscaping:(NSString *)event {
178+
// Slashes definitey need escaping
179+
BOOL slashesFound = [event containsString:@"/"];
180+
// Pound signs definitely need escaping
181+
BOOL poundSignsFound = [event containsString:@"#"];
182+
// Third-party percent signs would also need escaping, but there aren't instances of those in our events database, so we good.
183+
184+
return slashesFound || poundSignsFound;
185+
}
186+
99187
- (NSDictionary<NSString *, ApptentiveCount *> *)interactions {
100188
return [NSDictionary dictionaryWithDictionary:self.mutableInteractions];
101189
}

Apptentive/Apptentive/Engagement/Persistence/ApptentiveBackend+Engagement.h

+1-15
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,6 @@
1111

1212
NS_ASSUME_NONNULL_BEGIN
1313

14-
extern NSString *const ATEngagementInstallDateKey;
15-
extern NSString *const ATEngagementUpgradeDateKey;
16-
extern NSString *const ATEngagementLastUsedVersionKey;
17-
extern NSString *const ATEngagementIsUpdateVersionKey;
18-
extern NSString *const ATEngagementIsUpdateBuildKey;
19-
extern NSString *const ATEngagementCodePointsInvokesTotalKey;
20-
extern NSString *const ATEngagementCodePointsInvokesVersionKey;
21-
extern NSString *const ATEngagementCodePointsInvokesBuildKey;
22-
extern NSString *const ATEngagementCodePointsInvokesLastDateKey;
23-
extern NSString *const ATEngagementInteractionsInvokesTotalKey;
24-
extern NSString *const ATEngagementInteractionsInvokesVersionKey;
25-
extern NSString *const ATEngagementInteractionsInvokesBuildKey;
26-
extern NSString *const ATEngagementInteractionsInvokesLastDateKey;
27-
extern NSString *const ATEngagementInteractionsSDKVersionKey;
28-
2914
extern NSString *const ATEngagementCodePointHostAppVendorKey;
3015
extern NSString *const ATEngagementCodePointHostAppInteractionKey;
3116
extern NSString *const ATEngagementCodePointApptentiveVendorKey;
@@ -45,6 +30,7 @@ extern NSString *const ApptentiveEngagementMessageCenterEvent;
4530
- (BOOL)canShowInteractionForCodePoint:(NSString *)codePoint;
4631

4732
+ (NSString *)codePointForVendor:(NSString *)vendor interactionType:(NSString *)interactionType event:(NSString *)event;
33+
+ (NSString *)stringByEscapingCodePointSeparatorCharactersInString:(NSString *)string;
4834

4935
- (void)engageApptentiveAppEvent:(NSString *)event;
5036
- (void)engageLocalEvent:(NSString *)event userInfo:(nullable NSDictionary *)userInfo customData:(nullable NSDictionary *)customData extendedData:(nullable NSArray *)extendedData fromViewController:(nullable UIViewController *)viewController;

0 commit comments

Comments
 (0)