-
Notifications
You must be signed in to change notification settings - Fork 217
/
ARDSL.m
189 lines (154 loc) · 7.59 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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#import "ARDSL.h"
#import <RSSwizzle/RSSwizzle.h>
#import <ReactiveObjC/ReactiveObjC.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 ARAnalyticsPageNameBlock = @"pageNameBlock";
NSString * const ARAnalyticsEventName = @"event";
NSString * const ARAnalyticsEventNameBlock = @"eventBlock";
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;
}
static NSString *
ARExtractPageName(id object, NSDictionary *analyticsEntry, RACTuple *parameters, NSDictionary *extractedProperties)
{
ARAnalyticsNameBlock pageNameBlock = analyticsEntry[ARAnalyticsPageNameBlock];
if (pageNameBlock) {
return pageNameBlock(object, parameters.allObjects, extractedProperties);
}
return nil;
}
static NSString *
ARExtractEventName(id object, NSDictionary *analyticsEntry, RACTuple *parameters, NSDictionary *extractedProperties)
{
ARAnalyticsNameBlock eventNameBlock = analyticsEntry[ARAnalyticsEventNameBlock];
if (eventNameBlock) {
return eventNameBlock(object, parameters.allObjects, extractedProperties);
}
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) {
NSString *eventName = event;
NSDictionary *properties = ARExtractProperties(instance, object, parameters);
if (!eventName) {
// if the event name was not set statically, see if it's available via the block parameter
eventName = ARExtractEventName(instance, object, parameters, properties);
}
[ARAnalytics event:eventName withProperties:properties];
}
}];
}];
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;
if (instance == nil) {
return;
}
BOOL shouldFire = ar_shouldFireForInstance(object, instance, parameters);
if (shouldFire) {
NSString *pageName;
NSDictionary *properties = ARExtractProperties(instance, object, parameters);
if (dictionaryPageName) {
pageName = dictionaryPageName;
} else if (pageNameKeypath) {
pageName = [instance valueForKeyPath:pageNameKeypath];
NSAssert(pageName, @"Value for Key on `%@` returned nil.", pageNameKeypath);
} else {
// if we still don't have a page name, check to see if it is supplied dynamically
pageName = ARExtractPageName(instance, object, parameters, properties);
}
// Because of backwards compatibility we can't currently expect
// `-[ARAnalyticsProvider pageView:withProperties:]` to call existing
// `-[ARAnalyticsProvider pageView:]` implementations, so call the right one.
if (properties) {
[ARAnalytics pageView:pageName withProperties:properties];
} else {
[ARAnalytics pageView:pageName];
}
}
}];
}];
return instance;
}));
}
@end