用户数据在验证产品功能效果,用户服务定制,产品运营决策等方方面面起到支撑作用
相对问卷调查那种侵入式的数据统计方式,应用中的数据收集是被动获取的,拿到的是用户最直接的信息,也就是跟踪调查,这也是心理学研究中最常用,也被认为最有效的实验方式。
在实际应用中怎么建立数据统计系统,首先来个逻辑疑问三连
- 要达到什么样的效果?
建立起生态、用户、应用的完整画像 - 需要什么?
充足、全面、准确的数据 - 该怎么做?
收集用户数据、使用APP数据,清洗并建模
系统中最核心的元素是什么?用户!怎么搞用户数据?我们需要考虑一个哲学问题
- 你是谁?
用户基础数据 - 你从哪里来?
用户行为数据 - 你要到那里去?
A/Btest等,基于用户数据推出的产品验证数据
核心元素是用户,那么数据统计的关键位置,就在用户手里的APP上。
在iOS技术实现上来说,OC开发借助Runtime可以实现无侵入埋点,由于不继承自NSObject的swift对象不具备Runtime提供的特性,部分需要手动埋点。
数据收集的两个核心元素是触发和统计,在iOS上的实现如下
- 通过类别Hook原生方法:网上最普遍的方式。对event事件、table代理、页面生命周期等方法进行Hook,实现收集触发。
- 通过Runtime对NSObject动态扩展出一个打点专用结构体来存储额外数据。hook指定指定方法,获取对象数据。
在iOS端的系统包含 - 抓取模块:实现原理如上
- 组装模块:映射成洗数据友好的数据结构
- 存储模块:内存和磁盘存储,保障数据安全,预备上传
- 上报模块:按策略上报
以下是几个响应式关键位置的代码实现
/// button
+ (void)swizzleMethod {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(sendAction:to:forEvent:) with:@selector(mySendAction:to:forEvent:)];
});
}
- (void)mySendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
//统计
[self mySendAction:action to:target forEvent:event];
}
/// 手势
+ (void)swizzleMethod {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(initWithTarget:action:) with:@selector(myInitWithTarget:action:)];
[self swizzleInstanceMethod:@selector(addTarget:action:) with:@selector(myAddTarget:action:)];
});
}
- (instancetype)myInitWithTarget:(nullable id)target action:(nullable SEL)action {
UITapGestureRecognizer *instance = [self myInitWithTarget:[OXManager sharedInstance] action:@selector(tapGestureRecognizerDidTap:)];
[instance myAddTarget:target action:action];
return instance;
}
- (void)myAddTarget:(id)target action:(SEL)action {
[self myAddTarget:[OXManager sharedInstance] action:@selector(tapGestureRecognizerDidTap:)];
[self myAddTarget:target action:action];
}
/// 代理
@interface UICollectionView ()
@property (nonatomic, strong) UICollectionViewDelegateForwarder *delegateForward;
@end
@implementation UICollectionView (DelegateForward)
+ (void)swizzleMethod {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(setDelegate:) with:@selector(setMyDelegate:)];
});
}
- (void)setMyDelegate:(id<UICollectionViewDelegate>)delegate {
[self setDelegateForward:nil];
if (!delegate) {
[self setMyDelegate:nil];
return;
}
UICollectionViewDelegateForwarder *delegateForwarder = [[UICollectionViewDelegateForwarder alloc] init];
delegateForwarder.delegate = delegate;
[self setDelegateForward:delegateForwarder];
[self setMyDelegate:nil];
[self setMyDelegate:delegateForwarder];
}
- (void)setDelegateForward:(UICollectionViewDelegateForwarder *)delegateForward {
objc_setAssociatedObject(self, @selector(delegateForward), delegateForward, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UICollectionViewDelegateForwarder *)delegateForward {
return objc_getAssociatedObject(self, @selector(delegateForward));
}
/// UICollectionViewDelegateForwarder
- (void)forwardInvocation:(NSInvocation *)invocation
{
if (invocation.selector == @selector(init)) {
//delegate被释放
return;
}
[invocation retainArguments];
SEL selector = [invocation selector];
if([_delegate respondsToSelector:selector])
{
OXManager *manger = [OXManager sharedInstance];
if ([manger respondsToSelector:selector]) {
[invocation invokeWithTarget:manger];
}
[invocation invokeWithTarget:_delegate];
}
}
- (BOOL)respondsToSelector:(SEL)selector
{
BOOL resule = [_delegate respondsToSelector:selector];
return resule;
}
- (id)methodSignatureForSelector:(SEL)selector
{
if (!_delegate) {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
return [(NSObject *)_delegate methodSignatureForSelector:selector];
}
hook在工程架构上来说,貌似侵入性小,实则很大,所有的触发时间一棍子全部打到,实际运用中会出现难以修复的问题,谨慎使用。
以上是iOS客户端BI系统常规写法
从业务上来看,有价值的数据主要集中于和服务端交互的过程,那么我们可以在网络请求过程中添加钩子(具体实现就不贴代码了,很简单),根据不同策略来收集这部分的数据,比如在客户端来说,网络状态相对复杂,在不同的位置埋点,统计的数据就会有差异,不过这也给我们一个网络请教情况的数据统计契机。另外,这个功能也可以由服务端实现,客户端同学是不是很开心~