-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathSJAnalytics.m
198 lines (176 loc) · 9.35 KB
/
SJAnalytics.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
190
191
192
193
194
195
196
197
198
#import "SJAnalytics.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "UIApplication+SJAnalytics.h"
static NSMutableDictionary *eventDetails() {
static NSMutableDictionary *dict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = [[NSMutableDictionary alloc] init];
});
return dict;
}
static NSMutableDictionary *selectorEvents() {
static NSMutableDictionary *dict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = [[NSMutableDictionary alloc] init];
});
return dict;
}
static void sj_swizzSelector(Class class, SEL swizzledSelector, SEL originalSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
static SEL sj_selectorForOriginSelector(SEL selector) {
return NSSelectorFromString([NSStringFromSelector(selector) stringByAppendingString:@"__sj"]);
}
static NSString *sj_strForClassAndSelector(Class klass, SEL selector) {
return [NSString stringWithFormat:@"%@_%@", NSStringFromClass(klass), NSStringFromSelector(selector)];
}
static NSArray *sj_parametersForInvocation(NSInvocation *invocation) {
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];
NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2];
for (NSUInteger index = 2; index < numberOfArguments; index++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:index];
#define WRAP_AND_RETURN(type) \
do { \
type val = 0; \
[invocation getArgument:&val atIndex:(NSInteger)index]; \
[argumentsArray addObject:@(val)]; \
} while (0)
if (strcmp(argumentType, @encode(id)) == 0 || strcmp(argumentType, @encode(Class)) == 0) {
__autoreleasing id returnObj;
[invocation getArgument:&returnObj atIndex:(NSInteger)index];
[argumentsArray addObject:returnObj];
} else if (strcmp(argumentType, @encode(char)) == 0) {
WRAP_AND_RETURN(char);
} else if (strcmp(argumentType, @encode(int)) == 0) {
WRAP_AND_RETURN(int);
} else if (strcmp(argumentType, @encode(short)) == 0) {
WRAP_AND_RETURN(short);
} else if (strcmp(argumentType, @encode(long)) == 0) {
WRAP_AND_RETURN(long);
} else if (strcmp(argumentType, @encode(long long)) == 0) {
WRAP_AND_RETURN(long long);
} else if (strcmp(argumentType, @encode(unsigned char)) == 0) {
WRAP_AND_RETURN(unsigned char);
} else if (strcmp(argumentType, @encode(unsigned int)) == 0) {
WRAP_AND_RETURN(unsigned int);
} else if (strcmp(argumentType, @encode(unsigned short)) == 0) {
WRAP_AND_RETURN(unsigned short);
} else if (strcmp(argumentType, @encode(unsigned long)) == 0) {
WRAP_AND_RETURN(unsigned long);
} else if (strcmp(argumentType, @encode(unsigned long long)) == 0) {
WRAP_AND_RETURN(unsigned long long);
} else if (strcmp(argumentType, @encode(float)) == 0) {
WRAP_AND_RETURN(float);
} else if (strcmp(argumentType, @encode(double)) == 0) {
WRAP_AND_RETURN(double);
} else if (strcmp(argumentType, @encode(BOOL)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argumentType, @encode(char *)) == 0) {
WRAP_AND_RETURN(const char *);
} else if (strcmp(argumentType, @encode(void (^)(void))) == 0) {
__unsafe_unretained id block = nil;
[invocation getArgument:&block atIndex:(NSInteger)index];
if (block) {
[argumentsArray addObject:[block copy]];
} else {
[argumentsArray addObject:[NSNull null]];
}
} else {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(argumentType, &valueSize, NULL);
unsigned char valueBytes[valueSize];
[invocation getArgument:valueBytes atIndex:(NSInteger)index];
[argumentsArray addObject:[NSValue valueWithBytes:valueBytes objCType:argumentType]];
}
}
return [argumentsArray copy];
}
static void SJForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation) {
NSArray *events = selectorEvents()[sj_strForClassAndSelector([assignSlf class], invocation.selector)];
[events enumerateObjectsUsingBlock:^(NSString *eventName, NSUInteger idx, BOOL *stop) {
NSDictionary *detail = eventDetails()[eventName];
NSArray *argumentsArray = sj_parametersForInvocation(invocation);
BOOL (^shouldExecuteBlock)(id object, NSArray *parameters) = detail[SJAnalyticsShouldExecute];
NSDictionary *(^parametersBlock)(id object, NSArray *parameters) = detail[SJAnalyticsParameters];
if (shouldExecuteBlock == nil || shouldExecuteBlock(assignSlf, argumentsArray)) {
[[SJAnalytics shared].provider event:eventName withParameters:parametersBlock(assignSlf, argumentsArray)];
}
}];
SEL newSelector = sj_selectorForOriginSelector(invocation.selector);
invocation.selector = newSelector;
[invocation invoke];
}
@implementation SJAnalytics
+ (instancetype)shared {
static SJAnalytics *analytics;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
analytics = [[SJAnalytics alloc] init];
});
return analytics;
}
- (void)configure:(NSDictionary *)configurationDictionary provider:(id<SJAnalyticsProvider>)provider {
self.provider = provider;
NSArray *trackedMethodCallEventClasses = configurationDictionary[SJAnalyticsMethodCall];
[trackedMethodCallEventClasses enumerateObjectsUsingBlock:^(NSDictionary *eventDictionary, NSUInteger idx, BOOL *stop) {
[self __addMethodCallEventAnalyticsHook:eventDictionary];
}];
NSArray *trackedUIControlEventClasses = configurationDictionary[SJAnalyticsUIControl];
if (trackedUIControlEventClasses.count) {
sj_swizzSelector([UIApplication class], @selector(sj_sendAction:to:from:forEvent:), @selector(sendAction:to:from:forEvent:));
[UIApplication sharedApplication].provider = self.provider;
}
[trackedUIControlEventClasses enumerateObjectsUsingBlock:^(NSDictionary *eventDictionary, NSUInteger idx, BOOL *stop) {
[self __addUIControlEventAnalyticsHook:eventDictionary];
}];
}
- (void)__addMethodCallEventAnalyticsHook:(NSDictionary *)eventDictionary {
Class klass = eventDictionary[SJAnalyticsClass];
[eventDictionary[SJAnalyticsDetails] enumerateObjectsUsingBlock:^(id dict, NSUInteger idx, BOOL *stop) {
NSString *selectorName = dict[SJAnalyticsSelector];
SEL originSelector = NSSelectorFromString(selectorName);
Method originMethod = class_getInstanceMethod(klass, originSelector);
const char *typeEncoding = method_getTypeEncoding(originMethod);
SEL newSelector = sj_selectorForOriginSelector(originSelector);
class_addMethod(klass, newSelector, method_getImplementation(originMethod), typeEncoding);
class_replaceMethod(klass, originSelector, _objc_msgForward, typeEncoding);
if (class_getMethodImplementation(klass, @selector(forwardInvocation:)) != (IMP)SJForwardInvocation) {
class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)SJForwardInvocation, "v@:@");
}
NSMutableDictionary *detailDict = [dict mutableCopy];
[detailDict removeObjectForKey:SJAnalyticsEvent];
[eventDetails() setObject:detailDict forKey:dict[SJAnalyticsEvent]];
NSString *selectorKey = sj_strForClassAndSelector(klass, originSelector);
NSMutableArray *events = selectorEvents()[selectorKey];
if (!events) events = [NSMutableArray new];
[events addObject:dict[SJAnalyticsEvent]];
[selectorEvents() setObject:events forKey:selectorKey];
}];
}
- (void)__addUIControlEventAnalyticsHook:(NSDictionary *)eventDictionary {
Class klass = eventDictionary[SJAnalyticsClass];
[eventDictionary[SJAnalyticsDetails] enumerateObjectsUsingBlock:^(id dict, NSUInteger idx, BOOL *stop) {
NSString *selectorName = dict[SJAnalyticsSelector];
SEL originSelector = NSSelectorFromString(selectorName);
NSMutableDictionary *detailDict = [dict mutableCopy];
[detailDict removeObjectForKey:SJAnalyticsEvent];
[[UIApplication sharedApplication].eventDetails setObject:detailDict forKey:dict[SJAnalyticsEvent]];
NSString *selectorKey = sj_strForClassAndSelector(klass, originSelector);
NSMutableArray *events = [UIApplication sharedApplication].selectorEvents[selectorKey];
if (!events) events = [NSMutableArray new];
[events addObject:dict[SJAnalyticsEvent]];
[[UIApplication sharedApplication].selectorEvents setObject:events forKey:selectorKey];
}];
}
@end