forked from orta/ARAnalytics
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ARDSL.m
153 lines (122 loc) · 6.08 KB
/
ARDSL.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#import "ARDSL.h"
#import <RSSwizzle/RSSwizzle.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
NSString * const ARAnalyticsTrackedEvents = @"trackedEvents";
NSString * const ARAnalyticsTrackedScreens = @"trackedScreens";
NSString * const ARAnalyticsClass = @"class";
NSString * const ARAnalyticsDetails = @"details";
NSString * const ARAnalyticsProperties = @"properties";
NSString * const ARAnalyticsPageName = @"pageName";
NSString * const ARAnalyticsPageNameKeyPath = @"keypath";
NSString * const ARAnalyticsEventName = @"event";
NSString * const ARAnalyticsSelectorName = @"selector";
NSString * const ARAnalyticsEventProperties = @"properties";
NSString * const ARAnalyticsShouldFire = @"shouldFire";
static BOOL ar_shouldFireForInstance (NSDictionary *dictionary, id instance, RACTuple *context) {
ARAnalyticsEventShouldFireBlock shouldFireBlock = dictionary[ARAnalyticsShouldFire];
BOOL shouldFire;
if (shouldFireBlock) {
shouldFire = shouldFireBlock(instance, context.allObjects);
} else {
shouldFire = YES;
}
return shouldFire;
}
static SEL ar_selectorForEventAnalyticsDetails (NSDictionary *detailsDictionary) {
NSString *selectorName = detailsDictionary[ARAnalyticsSelectorName];
SEL selector = NSSelectorFromString(selectorName);
return selector;
}
static SEL ar_selectorForScreenAnalyticsDetails (NSDictionary *dictionary, Class klass) {
SEL selector;
NSString *selectorName = dictionary[ARAnalyticsSelectorName];
if (selectorName) {
selector = NSSelectorFromString(selectorName);
} else {
#if TARGET_OS_IPHONE
selector = @selector(viewDidAppear:);
NSCAssert([klass isSubclassOfClass:UIViewController.class] || klass == UIViewController.class, @"Default selector of viewDidAppear: must only be used on classes extending UIViewController or UIViewController itself.");
#else
NSCAssert(NO, @"You must specify a selector name for page views on OS X.");
#endif
}
return selector;
}
@implementation ARAnalytics (DSL)
+ (void)setupWithAnalytics:(NSDictionary *)analyticsDictionary configuration:(NSDictionary *)configurationDictionary {
[self setupWithAnalytics:analyticsDictionary];
NSArray *trackedScreenClasses = configurationDictionary[ARAnalyticsTrackedScreens];
[trackedScreenClasses enumerateObjectsUsingBlock:^(NSDictionary *screenDictionary, NSUInteger idx, BOOL *stop) {
[self addScreenMonitoringAnalyticsHook:screenDictionary];
}];
NSArray *trackedEventClasses = configurationDictionary[ARAnalyticsTrackedEvents];
[trackedEventClasses enumerateObjectsUsingBlock:^(NSDictionary *eventDictionary, NSUInteger idx, BOOL *stop) {
[self addEventAnalyticsHook:eventDictionary];
}];
}
static NSDictionary *
ARExtractProperties(id object, NSDictionary *analyticsEntry, RACTuple *parameters)
{
ARAnalyticsPropertiesBlock propertiesBlock = analyticsEntry[ARAnalyticsProperties];
if (propertiesBlock) {
return propertiesBlock(object, parameters.allObjects);
}
return nil;
}
+ (void)addEventAnalyticsHook:(NSDictionary *)eventDictionary {
Class klass = eventDictionary[ARAnalyticsClass];
RSSwizzleClassMethod(klass, @selector(alloc), RSSWReturnType(id), void, RSSWReplacement({
id instance = RSSWCallOriginal();
[eventDictionary[ARAnalyticsDetails] enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
SEL selector = ar_selectorForEventAnalyticsDetails(object);
NSString *event = object[ARAnalyticsEventName];
__weak __typeof(instance) weakInstance = instance;
[[instance rac_signalForSelector:selector] subscribeNext:^(RACTuple *parameters) {
id instance = weakInstance;
BOOL shouldFire = ar_shouldFireForInstance(object, instance, parameters);
if (shouldFire) {
[ARAnalytics event:event withProperties:ARExtractProperties(instance, object, parameters)];
}
}];
}];
return instance;
}));
}
+ (void)addScreenMonitoringAnalyticsHook:(NSDictionary *)screenDictionary {
Class klass = screenDictionary[ARAnalyticsClass];
RSSwizzleClassMethod(klass, @selector(alloc), RSSWReturnType(id), void, RSSWReplacement({
id instance = RSSWCallOriginal();
[screenDictionary[ARAnalyticsDetails] enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
SEL selector = ar_selectorForScreenAnalyticsDetails(object, klass);
// Try to grab the page name from the dictionary.
NSString *dictionaryPageName = object[ARAnalyticsPageName];
// If there wasn't one, then try to invoke keypath.
NSString *pageNameKeypath = object[ARAnalyticsPageNameKeyPath];
__weak __typeof(instance) weakInstance = instance;
[[instance rac_signalForSelector:selector] subscribeNext:^(RACTuple *parameters) {
id instance = weakInstance;
BOOL shouldFire = ar_shouldFireForInstance(object, instance, parameters);
if (shouldFire) {
NSString *pageName;
if (dictionaryPageName) {
pageName = dictionaryPageName;
} else {
pageName = [instance valueForKeyPath:pageNameKeypath];
NSAssert(pageName, @"Value for Key on `%@` returned nil.", pageNameKeypath);
}
// Because of backwards compatibility we can't currently expect
// `-[ARAnalyticsProvider pageView:withProperties:]` to call existing
// `-[ARAnalyticsProvider pageView:]` implementations, so call the right one.
NSDictionary *properties = ARExtractProperties(instance, object, parameters);
if (properties) {
[ARAnalytics pageView:pageName withProperties:properties];
} else {
[ARAnalytics pageView:pageName];
}
}
}];
}];
return instance;
}));
}
@end