美文网首页
用户行为统计与业务逻辑代码的分离

用户行为统计与业务逻辑代码的分离

作者: 大猿媛 | 来源:发表于2018-08-24 18:20 被阅读81次

统计埋点现状:

日志统计逻辑避免不了和业务逻辑搅合在一起
由于业务逻辑频繁的修改,会导致日志统计的修改不及时和遗漏
日志统计的维护在每次迭代开发后都是一次耗时不短的任务

AOP+HOOK实现统计代码的分离

AOP(Aspect Oriented Programming)面向切面编程

相比传统的OOP来说,OOP的特点在于它可以很好的将系统横向分为很多个模块(比如通讯录模块,聊天模块,发现模块),每个子模块可以横向的衍生出更多的模块,用于更好的区分业务逻辑。而AOP其实相当于是纵向的分割系统模块,将每个模块里的公共部分提取出来(即那些与业务逻辑不相关的部分,如日志,用户行为等等),与业务逻辑相分离开来,从而降低代码的耦合度。

AOP主要是被使用在日志记录,性能统计,安全控制,事务处理,异常处理几个方面。

在iOS中我们通常使用Method Swizzling(俗称iOS黑魔法)来实现AOP,Method Swizzling其实就是一种在Runtime的时候把一个方法的实现与另一个方法的实现互相替换。*

Aspects 一个基于Objective-C的AOP开发框架

It allows you to add code to existing methods per class or per instance, whilst thinking of the insertion point e.g. before/instead/after. Aspects automatically deals with calling super and is easier to use than regular method swizzling

它实现了在每个类或实例已存在的方法中添加代码,同时考虑到了代码插入的时机,如方法执行前、替换已有方法的实现代码、方法执行后,并且能自动处理父类调用,比iOS普通的method swizzling更简单易用

它提供了以下方法

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Deregister an aspect.
/// @return YES if deregistration is successful, otherwise NO.
id<AspectToken> aspect = ...;
[aspect remove];
用户行为统计代码的分离

首先,这种配置性代码必然放在AppDelegate中,既然实现分离,我们可以通过AppDelegate+StaticsConfig类目的方式,实现单独维护一个统计类,直接上代码

static NSString *GLLoggingPageImpression      = @"pageName";
static NSString *GLLoggingTrackedEvents       = @"pageEvent";
static NSString *GLLoggingEventName           = @"eventName";
static NSString *GLLoggingEventSelectorName   = @"eventSelectorName";
static NSString *GLLoggingEventHandlerBlock   = @"eventHandlerBlock";

@implementation AppDelegate (UMConfig)

- (void)configurateUMEvent{
    NSDictionary *config = @{
                             @"TableViewController": @{
                                     GLLoggingPageImpression: @"page imp - main page",  //对TableViewController这个类的描述,页面统计用到
                                     GLLoggingTrackedEvents: @[
                                             @{
                                                 GLLoggingEventName: @"TableViewRow Click",
                                                 GLLoggingEventSelectorName: @"tableView:didSelectRowAtIndexPath:",
                                                 GLLoggingEventHandlerBlock: ^(id<AspectInfo> aspectInfo) {
                                                     //添加统计代码
                                                     NSLog(@"TableViewRow Click");
                                                 },
                                                 },
                                             @{
                                                 GLLoggingEventName: @"renameFileName",
                                                 GLLoggingEventSelectorName: @"renameFile",
                                                 GLLoggingEventHandlerBlock: ^(id<AspectInfo> aspectInfo) {
                                                     //添加统计代码
                                                     NSLog(@"renameFileName");
                                                 },
                                                 },
                                             ],
                                     },
                             
                             @"DetailViewController": @{
                                     GLLoggingPageImpression: @"page imp - detail page",
                                     }
                             };
    
    [self setupWithConfiguration:config];
}


typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);

- (void)setupWithConfiguration:(NSDictionary *)configs
{
    // Hook Page Impression
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo) {
                                   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                       NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                                       NSString *pageImp = configs[className][GLLoggingPageImpression];
                                       if (pageImp) {
                                           NSLog(@"%@", pageImp);
                                       }
                                   });
                               } error:NULL];
    
    // Hook Events
    for (NSString *className in configs) {
        Class clazz = NSClassFromString(className);
        NSDictionary *config = configs[className];
        
        if (config[GLLoggingTrackedEvents]) {
            for (NSDictionary *event in config[GLLoggingTrackedEvents]) {
                SEL selekor = NSSelectorFromString(event[GLLoggingEventSelectorName]);
                AspectHandlerBlock block = event[GLLoggingEventHandlerBlock];
               //AspectPositionAfter,在业务代码执行之后调用  
                [clazz aspect_hookSelector:selekor
                               withOptions:AspectPositionAfter
                                usingBlock:^(id<AspectInfo> aspectInfo) {
                                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                        block(aspectInfo);
                                    });
                                } error:NULL];
                
            }
        }
    }
}

反思

虽然实现了统计代码和业务代码的分离,但仔细想想,业务代码虽然可以肆无忌惮的删改,但是维护的统计config,也需要不断更新;如果每个开发人员都能做到在修改业务代码后都检查一下统计代码还好,可是如果统计代码是专人维护就糗大了,业务代码的变更维护人员根本不知道;所以,各种取舍吧

相关文章

  • 用户行为统计与业务逻辑代码的分离

    统计埋点现状: 日志统计逻辑避免不了和业务逻辑搅合在一起由于业务逻辑频繁的修改,会导致日志统计的修改不及时和遗漏日...

  • AspectJ在Android埋点的实践

    在项目开发中,对 App 客户端重构后,发现用于统计用户行为的友盟统计代码和用户行为日志记录代码分散在各业务模块中...

  • 【学习】android的MVC与MVP与助手的聊天系统

    MVC模式: =>解决activity代码臃肿; 好处: 业务逻辑与界面分离开来。 M:业务逻辑,比如:数据存...

  • SSTI模板注入

    前言 开局一张图,姿势全靠yy 模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升...

  • 在Android中使用AspectJ进行AOP切面编程

    最近有做用户行为统计的需求,为了尽可能使统计代码不侵入业务代码,就研究了下hook和Aop。研究了下AspectJ...

  • 在Android中使用AspectJ进行切面编程的简易步骤

    最近有做用户行为统计的需求,为了尽可能使统计代码不侵入业务代码,就研究了下hook和Aop。之前写的hook方面的...

  • 业务逻辑与控制代码分离实践

    写代码时,控制逻辑的代码往往与业务逻辑混在一起,控制部分的代码很难被复用。 实现功能:从一个channel中批量读...

  • 如何设计业务组件动态加载方案

    概念 我为什么需要动态加载 将平台与业务分离,业务只依赖平台api,平台代码层面不依赖业务组件 业务组件逻辑相对简...

  • code review

    代码审查 5个层次1、业务架构的审查重构(1)对相应业务逻辑的设计进行审查。 目的在于使业务逻辑架构的设计与用户需...

  • 1,简单 工厂模式

    1,几点总结 1,命名的规范性 2,加强对安全代码的校验 比如是int.parse 3,业务逻辑与界面逻辑的分离...

网友评论

      本文标题:用户行为统计与业务逻辑代码的分离

      本文链接:https://www.haomeiwen.com/subject/lhebiftx.html