Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ jobs:
test \
-only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests \
-only-testing:MParticleSampleTests/RoktNativeLayoutComponentViewSizingTests \
-only-testing:MParticleSampleTests/RNMParticleDuplicateSDKTests \
| bundle exec xcpretty -k

pr-notify:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ npm start

and build your workspace from xCode.

### Swift Package Manager

Take the mParticle SDK and its kits from one dependency manager only. If the core comes from CocoaPods (through this package) and a kit such as `mParticle-Rokt` is added to the app with Swift Package Manager, the app contains two copies of the SDK. It builds and archives without warnings, then crashes at runtime. Debug builds detect this and show a red box: `[mParticle] The mParticle SDK is loaded more than once`. To fix it, remove the mParticle and Rokt Swift packages from the app and add the kits with CocoaPods, as shown above.

## Android (Manual Setup)

1. Copy your mParticle key and secret from [your workspace's dashboard](https://app.mparticle.com/setup/inputs/apps) and construct an `MParticleOptions` object.
Expand Down
63 changes: 63 additions & 0 deletions ios/RNMParticle/RNMParticle.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
#endif
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#if DEBUG
#import <objc/runtime.h>
#endif

#ifdef RCT_NEW_ARCH_ENABLED
#import <RNMParticle/RNMParticle.h>
Expand Down Expand Up @@ -143,6 +147,65 @@ + (void)load {
RCTRegisterModule(self);
}

#if DEBUG
- (instancetype)init
{
if (self = [super init]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[RNMParticle warnIfSDKLoadedTwice];
});
}
return self;
}

// Counts, per class name, the loaded images inside the app bundle that define it. System
// images are skipped: scanning them all takes tens of seconds, the bundle takes milliseconds.
+ (NSDictionary<NSString *, NSNumber *> *)imageCountsForClassNames:(NSArray<NSString *> *)classNames
{
NSMutableDictionary<NSString *, NSNumber *> *counts = [NSMutableDictionary dictionary];
for (NSString *name in classNames) {
counts[name] = @0;
}
NSString *bundlePath = NSBundle.mainBundle.bundlePath;
unsigned int imageCount = 0;
const char **images = objc_copyImageNames(&imageCount);
for (unsigned int i = 0; i < imageCount; i++) {
if (![@(images[i]) hasPrefix:bundlePath]) {
continue;
}
unsigned int classCount = 0;
const char **classes = objc_copyClassNamesForImage(images[i], &classCount);
for (unsigned int j = 0; j < classCount; j++) {
NSString *name = @(classes[j]);
if (counts[name] != nil) {
counts[name] = @(counts[name].unsignedIntegerValue + 1);
}
}
free(classes);
}
free(images);
return counts;
}

// Mixing CocoaPods and Swift Package Manager for the mParticle SDKs links a second copy that
// builds and archives silently, then crashes at runtime. Say so early in development.
+ (void)warnIfSDKLoadedTwice
{
NSArray<NSString *> *classNames = @[ @"MParticle", @"RoktEmbeddedView" ];
NSDictionary<NSString *, NSNumber *> *counts = [self imageCountsForClassNames:classNames];
NSMutableArray<NSString *> *duplicates = [NSMutableArray array];
for (NSString *name in classNames) {
if (counts[name].unsignedIntegerValue > 1) {
[duplicates addObject:[NSString stringWithFormat:@"%@ x%@", name, counts[name]]];
}
}
if (duplicates.count > 0) {
RCTLogError(@"[mParticle] The mParticle SDK is loaded more than once (%@). This happens when the SDK comes from both CocoaPods and Swift Package Manager. See README › Swift Package Manager.", [duplicates componentsJoinedByString:@", "]);
}
}
#endif

RCT_EXPORT_METHOD(upload)
{
[[MParticle sharedInstance] upload];
Expand Down
4 changes: 4 additions & 0 deletions sample/ios/MParticleSample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */; };
B7C10E932E50AA1100000004 /* RNMPRoktPlaceholderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E922E50AA1100000003 /* RNMPRoktPlaceholderTests.m */; };
B7C10E952E50AA1100000006 /* RoktNativeLayoutComponentViewSizingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E942E50AA1100000005 /* RoktNativeLayoutComponentViewSizingTests.mm */; };
B7C10E972E50AA1100000008 /* RNMParticleDuplicateSDKTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E962E50AA1100000007 /* RNMParticleDuplicateSDKTests.m */; };
B7C10E992F6B000000000009 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7C10E982F6B000000000008 /* SceneDelegate.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -49,6 +50,7 @@
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTConvertCommerceMappingTests.m; sourceTree = "<group>"; };
B7C10E922E50AA1100000003 /* RNMPRoktPlaceholderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNMPRoktPlaceholderTests.m; sourceTree = "<group>"; };
B7C10E942E50AA1100000005 /* RoktNativeLayoutComponentViewSizingTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RoktNativeLayoutComponentViewSizingTests.mm; sourceTree = "<group>"; };
B7C10E962E50AA1100000007 /* RNMParticleDuplicateSDKTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNMParticleDuplicateSDKTests.m; sourceTree = "<group>"; };
B7C10E982F6B000000000008 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = MParticleSample/SceneDelegate.swift; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -80,6 +82,7 @@
B7C10E902E50AA1100000001 /* RCTConvertCommerceMappingTests.m */,
B7C10E922E50AA1100000003 /* RNMPRoktPlaceholderTests.m */,
B7C10E942E50AA1100000005 /* RoktNativeLayoutComponentViewSizingTests.mm */,
B7C10E962E50AA1100000007 /* RNMParticleDuplicateSDKTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = MParticleSampleTests;
Expand Down Expand Up @@ -399,6 +402,7 @@
B7C10E912E50AA1100000002 /* RCTConvertCommerceMappingTests.m in Sources */,
B7C10E932E50AA1100000004 /* RNMPRoktPlaceholderTests.m in Sources */,
B7C10E952E50AA1100000006 /* RoktNativeLayoutComponentViewSizingTests.mm in Sources */,
B7C10E972E50AA1100000008 /* RNMParticleDuplicateSDKTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
35 changes: 35 additions & 0 deletions sample/ios/MParticleSampleTests/RNMParticleDuplicateSDKTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#import <XCTest/XCTest.h>
#import "../../../ios/RNMParticle/RNMParticle.h"

// Implemented in RNMParticle.mm, Debug builds only.
@interface RNMParticle (DuplicateSDKTests)
+ (NSDictionary<NSString *, NSNumber *> *)imageCountsForClassNames:(NSArray<NSString *> *)classNames;
@end

/**
* Guards the Debug-only check that warns when two copies of the mParticle SDK are loaded.
*
* Only `MParticle` is asserted: it lives in the SDK framework, which this test bundle shares
* with the app it hosts. The wrapper's own classes are linked into this bundle a second time
* (see RNMPRoktPlaceholderTests.m), so counting them would test the test linkage.
*/
@interface RNMParticleDuplicateSDKTests : XCTestCase
@end

@implementation RNMParticleDuplicateSDKTests

- (void)testCountsOneCopyOfTheSDK
{
NSDictionary<NSString *, NSNumber *> *counts = [RNMParticle imageCountsForClassNames:@[ @"MParticle" ]];

XCTAssertEqualObjects(counts[@"MParticle"], @1);
}

- (void)testCountsZeroForAClassNoImageDefines
{
NSDictionary<NSString *, NSNumber *> *counts = [RNMParticle imageCountsForClassNames:@[ @"RNMPNoSuchClass" ]];

XCTAssertEqualObjects(counts[@"RNMPNoSuchClass"], @0);
}

@end
Loading