Commit 39ba6493 authored by 井庆林's avatar 井庆林

暂提

parent 489a4305
......@@ -168,8 +168,8 @@
6003F59C195388D20070C39A /* GMAppDelegate.h */,
6003F59D195388D20070C39A /* GMAppDelegate.m */,
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
6003F5A5195388D20070C39A /* GMViewController.h */,
6003F5A6195388D20070C39A /* GMViewController.m */,
6003F5A5195388D20070C39A /* GMViewController.h */,
6003F5A8195388D20070C39A /* Images.xcassets */,
6003F594195388D20070C39A /* Supporting Files */,
);
......
//
// Phobos.h
// GengmeiDoctor
// Data Statistic Client For Mars
// Created by Thierry on 16/1/26.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "PhobosPVProtocol.h"
#import "PhobosConfig.h"
#import <CoreLocation/CLLocation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NewPhobos : NSObject
/**
* @brief 开启Phobos统计,默认以BATCH方式发送log.
*
* @param appName 通常由数据端与客户端一起确认的区分不同app的名字
* @param channelId 发布渠道
*
* @return Phobos实例
*
* @since 0.0.1
*/
+ (NewPhobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId;
+ (instancetype) alloc __attribute__((deprecated));
- (instancetype) init __attribute__((deprecated));
+ (instancetype) new __attribute__((deprecated));
#pragma mark - SDK配置
+ (instancetype)sharedClient;
@property (class, readonly, strong) NewPhobos *sharedClient;
/** 用于Phobos库从old更新到new使用,在全量之后删除相关逻辑 */
@property (nonatomic, assign) BOOL isGray;
// Phobos在处理业务端传递来的参数时会检查是否某个value为空,如果为空会调用这个block以通知业务层,业务层可以上报这个异常,以助解决问题
@property(nonatomic, copy) void (^captureNullExpection) (NSString *eventId, NSDictionary *info);
/**
网络状态 wifi=1, mobile=0, 不连通=-1
*/
@property (nonatomic, copy) NSString *netStatus;
/**
//没有网络连接
public static final String NETWORN_NONE = "none";
//wifi连接
public static final String NETWORN_WIFI = "wifi";
//手机网络数据连接类型
public static final String NETWORN_2G = "2G";
public static final String NETWORN_3G = "3G";
public static final String NETWORN_4G = "4G";
public static final String NETWORN_MOBILE = "other";
*/
@property (nonatomic, copy) NSString *networkStatus;
/**
* @brief 设置是否打印sdk的log信息,默认不开启
*
* @since 0.0.1
*/
@property (assign, nonatomic) BOOL logEnabled;
/**
* @brief 设置当前登录用户的ID,如果没有默认为@""
*
*
* @since 0.0.2
*/
@property (strong, nonatomic) NSString *userId;
/*!
* @author zhaiguojun, 16-05-31
*
* @brief 用户当前的城市id
*
*
* @since 0.2.7
*/
@property (strong, nonatomic) NSString *currentCityId;
@property (strong, nonatomic) CLLocation *gps;
/**
* 记录用户类型
*/
@property (strong, nonatomic) NSMutableDictionary *userType;
/**
数据接收的服务器API
*/
@property (copy, nonatomic) NSString *serverAPI;
/**
当前APP请求接口的 APIHOST(GMServerDomains.apiHost 主要用于flutter AppDelegate 中初始化需要传值
*/
@property (nonatomic, copy) NSString *apiHost;
/**
当前APP请求接口的 cookie(主要用于flutter) 获取到cookie 的时候穿过来 或者cookie 有变化的时候传过来
*/
@property (nonatomic, copy) NSString *cookie;
/**
灰度组, since 7.7.65
*/
@property (nonatomic, copy) NSString *greyType;
/**
包的类型:APPSTORE、RELEASE、DEBUG
*/
@property (nonatomic, assign) PhobosSigningType signingType;
/**
从主项目获取当前显示的controller
*/
@property (nonatomic, copy) UIViewController * (^getTopController) (void);
#pragma mark - 事件采集
/**
* @brief 自定义事件,数量统计.
*
* @param eventId 事件Id
* @attributes 参数
* @sendNow 是否实时发送,默认为NO
*
* @since 0.0.1
*/
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes;
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow;
+ (void)track:(NSString *)eventName;
/**
* @brief 自定义事件,数量统计 7730 精准曝光/数据链路.
*
* @param eventId 事件Id
* @attributes 参数
* @sendNow 是否实时发送,默认为NO
* @currentAPI 当前传过来的API
* @
*/
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventName currentAPI:(NSString *)currentAPI;
/**
* @brief PV事件开始。当controller viewWillAppear时调用
*/
- (void)onPVStart:(UIResponder<PhobosPVProtocol> *)page;
/**
* @brief PV事件结束。当controller viewWillDisAppear时调用
*/
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page;
@end
@interface NewPhobos (UtilTest)
/** 获取所有非立即发送埋点数量 */
+ (NSInteger)normalPhobosCount;
/** 获取所有非立即发送埋点数据 */
+ (NSDictionary *)normalPhobosData;
/** 获取url的非立即发送埋点数量 */
+ (NSInteger)normalPhobosCountForURL:(NSString *)url;
/** 获取serverAPI的非立即发送埋点数据 */
+ (NSArray *)normalPhobosDataForServerAPI;
/** 获取url的非立即发送埋点数据 */
+ (NSArray *)normalPhobosDataForURL:(NSString *)url;
/** 获取所有立即发送埋点数据 */
+ (NSDictionary *)immediatelyPhobosData;
/** 获取serverAPI的立即发送埋点数据 */
+ (NSArray *)immediatelyPhobosForServerAPI;
/** 获取url的立即发送埋点数据 */
+ (NSArray *)immediatelyPhobosDataForURL:(NSString *)url;
/** 获取url的立即发送埋点数量 */
+ (NSUInteger)immediatelyPhobosCountForURL:(NSString *)url;
/** 清除非立即发送埋点数据缓存 */
+ (void)removeAllNormalPhobosData;
/** 清除立即发送埋点数据缓存 */
+ (void)removeAllImmediatelyPhobosData;
/** 获取将要发送的数据 */
@property (nonatomic, copy) void (^phobosSendDataBlock)(NSArray *datas);
@end
NS_ASSUME_NONNULL_END
//
// Phobos.m
// GengmeiDoctor
//
// Created by Thierry on 16/1/26.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
//
#import "NewPhobos.h"
#import <AdSupport/AdSupport.h>
#import "UIResponder+PhobosPV.h"
#import "PhobosUtil.h"
#import "PhobosCustomVisibleController.h"
#import <GMCache/GMCache.h>
#import <objc/runtime.h>
static NewPhobos *_sharedClient;
static NSString *sdkVersion = @"1.4.0";
@interface NewPhobos ()
@property (strong, nonatomic) UIViewController *visibleController;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSString *appName;
@property (strong, nonatomic) NSString *channelId;
@property (strong, nonatomic) NSString *appVersion;
@property (strong, nonatomic) NSString *sessionId;
/* 每一条埋点数据的物理ID,自增,生命周期和sessionId相同。特别注意:在sessionOver的时候,要把他置为0 */
@property (assign, nonatomic) NSInteger serialId;
//@property (nonatomic, weak) dispatch_semaphore_t immediatelySemaphore;// 保障需要立即发送的埋点数据安全
//@property (nonatomic, weak) dispatch_semaphore_t normalSemaphore;// 保障普通埋点数据安全
@property (nonatomic, assign) NSInteger normalCount;// 记录普通埋点数量
@end
@implementation NewPhobos
static dispatch_semaphore_t _immediatelySemaphore;
static dispatch_semaphore_t _normalSemaphore;
static dispatch_queue_t _immediatelyQueue;
static dispatch_queue_t _normalQueue;
+ (NewPhobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId{
NewPhobos.sharedClient.appName = appName;
NewPhobos.sharedClient.channelId = channelId;
return NewPhobos.sharedClient;
}
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_immediatelySemaphore = dispatch_semaphore_create(1);
_normalSemaphore = dispatch_semaphore_create(1);
_immediatelyQueue = dispatch_queue_create("immdiately", DISPATCH_QUEUE_CONCURRENT);
_normalQueue = dispatch_queue_create("normal", DISPATCH_QUEUE_CONCURRENT);
});
}
- (instancetype)init {
if (self = [super init]) {
_appName = @"";
_channelId = @"";
_logEnabled = NO;
_userId = @"";
_netStatus = @"";
_currentCityId = @"";
_serverAPI = @"";
_greyType = @"";
_userType = [[NSMutableDictionary alloc] init];
_appVersion = [PhobosUtil getAppVersion];
_signingType = PhobosSigningTypeUndefined;
// self.immediatelySemaphore = dispatch_semaphore_create(1);
// self.normalSemaphore = dispatch_semaphore_create(1);
NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey] mutableCopy];
for (NSArray *data in dataDict.allValues) {
self.normalCount += data.count;
}
[self setupNotification];
[self handleSessionStart];
}
return self;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return NewPhobos.sharedClient;
}
- (id)copyWithZone:(struct _NSZone *)zone {
return NewPhobos.sharedClient;
}
+ (instancetype)sharedClient {// 使用单例设计模式,保证Phobos只有一个实例,并提供了全局访问点
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[super allocWithZone:NULL] init];
});
return _sharedClient;
}
- (void)dealloc{
if (self) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
- (void)setUserType:(NSMutableDictionary *)userType {
if (userType == nil && userType.allKeys.count == 0) {
return;
}
NSArray *newKeys = userType.allKeys;
NSArray *oldKeys = _userType.allKeys;
for (NSString *newKey in newKeys) {
if ([oldKeys containsObject:newKey]) {
NSString *newValue = [[userType objectForKey:newKey] stringValue];
NSString *oldValue = [[_userType objectForKey:newKey] stringValue];
if (![newValue isEqualToString:oldValue]) {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
} else {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
}
}
/**
在APP启动、从后台到前台的时候需要记录device_opened埋点
*/
- (void)handleEventDeviceOpened{
/** 每次打开APP埋点 **/
NSDictionary *dict = @{@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
};
[NewPhobos track:@"device_opened" attributes:dict sendNow:YES];
}
- (UIViewController *)visibleController {
id target = self.getTopController();
if ([target conformsToProtocol:NSProtocolFromString(@"PhobosCustomVisibleController")]) {
target = [target performSelector:@selector(phobosVisibleController)];
}
return target;
}
#pragma mark - notification handler
/**
* @brief 设置对APP的通知监听
*
* @since 0.0.1
*/
- (void)setupNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppFinishLaunch:) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInBackgound:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
/**
* @brief 每次打开APP或返回前台,即Session开始的时候的处理
* @notification didFinishLaunch和willEnterForeground的时候都需要记录
*
* @since 0.0.1
*/
- (void)handleSessionStart{
_serialId = 0;
_sessionId = [[NSUUID UUID] UUIDString];
[GMCache storeObjectAtDocumentPathWithkey:PhobosBeginTime object:[PhobosUtil currentTime]];
}
/**
* @brief 应用打开时的处理
*
* @since 0.0.1
*/
- (void)handleAppFinishLaunch:(id)sender{
phobosLog(@"handleAppFinishLaunch");
[self handleSessionStart];
[self handleEventDeviceOpened];
}
/**
* @brief 应用进入前台的处理
*
* @since 0.0.1
*/
- (void)handleAppInForeground:(id)sender{
phobosLog(@"handleAppInForeground");
[self handleSessionStart];
[self handleEventDeviceOpened];
[self sendImmediatelyNormalData];
[self handlePVEventAppInForeground];
}
/**
* @brief 应用进入后台的处理
*
* @since 0.0.1
*/
- (void)handleAppInBackgound:(id)sender{
phobosLog(@"handleAppInBackgound");
[self handlePVEventAppInBackgound];
[self handleSessionOver];
[self sendImmediatelyNormalData];
}
/**
* @brief 会话结束时候的处理
*
* @since 0.0.1
*/
- (void)handleSessionOver{
//进入后台的同时,把记录时间同步到服务端,把已使用时间清空
double beginTime = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosBeginTime] doubleValue];
if (beginTime == 0) {
return;
}
NSDate *date = [NSDate date];
double endTime = [date timeIntervalSince1970];
NSString *usedTime = [NSString stringWithFormat:@"%ld",(long)(endTime - beginTime)];
NSDictionary *dict = @{@"duration":usedTime,
@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
};
[NewPhobos track:@"on_app_session_over" attributes:dict];
[GMCache removeObjectAtDocumentPathWithkey:PhobosBeginTime];
//当前session结束之后,把id置为0
_serialId = 0;
}
/**
APP从后台到前台的时候,重新初始化pagename等信息
@author zhaiguojun 16-10-11 in (null)
*/
- (void)handlePVEventAppInForeground {
if (self.visibleController != nil) {
[self onPVStart:self.visibleController];
}
}
/**
APP进到后台的时候,把当前pageview时间结束
@author zhaiguojun 16-10-11 in (null)
*/
- (void)handlePVEventAppInBackgound {
if (self.visibleController != nil) {
[self onPVEnd:self.visibleController];
}
}
#pragma mark - PV
- (void)onPVStart:(UIResponder<PhobosPVProtocol> *)page {
// 必须在此处调用一下referer,因为onControllerStart
[page initReferer];
[page initRefererLink];
[page initReferrerIdIfNil];
[page initReferrerTabName];
page.inTime = [PhobosUtil currentTime];
}
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page {
if (![PhobosUtil isNonEmpty:page.pageName] || !page.needLogPV) {
return;
}
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
[dict setObject:[PhobosUtil currentTime] forKey:@"out"];
[dict setObject:page.inTime?:@"" forKey:@"in"];
[dict setObject:page.pageName forKey:@"page_name"];
[dict setObject:page.businessId?:@"" forKey:@"business_id"];
[dict setObject:page.referer?:@"" forKey:@"referrer"];
[dict setObject:page.referrerLink ? : @[] forKey:@"referrer_link"];
[dict setObject:@(0) forKey:@"fake"];
[dict setObject:page.referrerId ? : @"" forKey:@"referrer_id"];
[dict setObject:page.extraParam ? : @"" forKey:@"extra_param"];
[dict setObject:page.referrerTabName ? : @"" forKey:@"referrer_tab_name"];
[dict setObject:page.isPush.intValue ? @(page.isPush.intValue) : @(0) forKey:@"is_push"];
if (page.inTime.length > 0) {
// 页面显示时间为空时不记录页面pv事件
[NewPhobos track:@"page_view" attributes:dict];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
/**
* @brief 将埋点时间封装成词典数据
*
* @since 0.0.1
*/
- (NSDictionary *)prepareDictionaryForEvent:(NSString *)eventName attributes:(NSDictionary *)attributes{
NSArray *referrerLink = _sharedClient.visibleController.referrerLink;
if (![eventName isEqualToString:@"page_view"]) {
NSMutableDictionary *attributesParams = [NSMutableDictionary dictionaryWithDictionary:attributes];
[attributesParams setValue:referrerLink ? : @[] forKey:@"referrer_link"];
attributes = attributesParams;
}
[self catchNullForEvent:eventName attributes:attributes];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
NSString *currentTime = [PhobosUtil currentTime];
NSMutableDictionary *deviceParams = [NSMutableDictionary new];
[deviceParams setValue:[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString] forKey:@"idfa"];
[deviceParams setValue:[[[UIDevice currentDevice] identifierForVendor] UUIDString] forKey:@"idfv"];
[deviceParams setValue:[PhobosUtil deviceId] forKey:@"device_id"];
[deviceParams setValue:@"ios" forKey:@"device_type"];
[deviceParams setValue:@"Apple" forKey:@"manufacturer"];
[deviceParams setValue:@(self.gps.coordinate.latitude) forKey:@"lat"];
[deviceParams setValue:@(self.gps.coordinate.longitude) forKey:@"lng"];
[deviceParams setValue:_netStatus forKey:@"is_WiFi"];
[deviceParams setValue:[PhobosUtil getIPAddress:YES] forKey:@"ip"];
[deviceParams setValue:_networkStatus forKey:@"net_type"];
[deviceParams setValue:[PhobosUtil platform] forKey:@"model"];
[deviceParams setValue:@(_isGray) forKey:@"isGray"];
[deviceParams setValue:[UIDevice currentDevice].systemVersion forKey:@"sys_version"];
NSMutableDictionary *appParams = [NSMutableDictionary new];
[appParams setValue:_greyType forKey:@"grey_type"];
[appParams setValue:_appName forKey:@"name"];
[appParams setValue:_appVersion forKey:@"version"];
[appParams setValue:_channelId forKey:@"channel"];
[appParams setValue:_userType forKey:@"user_type"];
[appParams setValue:_currentCityId forKey:@"current_city_id"];
[appParams setValue:@(_serialId++) forKey:@"serial_id"];
if (_signingType == PhobosSigningTypeDebug || _signingType == PhobosSigningTypeRelease) {
[dict setObject:@(0) forKey:@"is_release"];
}
[dict setObject:eventName forKey:@"type"];
[dict setObject:appParams forKey:@"app"];
[dict setObject:sdkVersion forKey:@"version"];
[dict setObject:deviceParams forKey:@"device"];
[dict setObject:_userId forKey:@"user_id"];
[dict setObject:currentTime forKey:@"create_at"];
[dict setObject:attributes forKey:@"params"];
[dict setObject:_sessionId forKey:@"app_session_id"];
}
@catch (NSException *exception) {
phobosLog(exception);
}
return dict;
}
#pragma mark - helpers
- (void)catchNullForEvent:(NSString *)eventName attributes:(NSDictionary *)attributes {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@try {
for (NSString *key in attributes.allKeys) {
if ([attributes[key] isMemberOfClass:[NSNull class]]) {
if (self.captureNullExpection) {
self.captureNullExpection(eventName, attributes);
}
break;
}
}
} @catch (NSException *exception) {
}
});
}
+ (void)track:(NSString *)eventName{
[self track:eventName attributes:@{} sendNow:NO currentAPI:_sharedClient.serverAPI];
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes{
[self track:eventName attributes:attributes sendNow:NO currentAPI:_sharedClient.serverAPI];
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow{
[self track:eventName attributes:attributes sendNow:sendNow currentAPI:_sharedClient.serverAPI];
}
+ (void)track:(NSString *)eventName currentAPI:(NSString *)currentAPI {
[self track:eventName attributes:@{} sendNow:NO currentAPI:currentAPI];
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI {
[self track:eventName attributes:attributes sendNow:NO currentAPI:currentAPI];
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI {
NSDictionary *dataDict = [_sharedClient prepareDictionaryForEvent:eventName attributes:attributes];
@try {
NSData *JSON = [PhobosUtil encodeJSON:dataDict];
if (sendNow) {
[_sharedClient sendImmediatelyPhobosWithURL:currentAPI data:dataDict];
} else{
[_sharedClient trackPhobosWithURL:currentAPI data:dataDict];
}
}
@catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE不能为NSObject ");
}
}
#pragma mark - 事件存储、发送
/**
* 普通埋点
* @param url 接口url
* @param dataArray 埋点数据
* @param nowSend 是否需要发送当前缓存数据(app进入到后台、前台等情况)
*/
- (void)sendImmediatelyNormalData {
[self trackPhobosWithURL:nil data:nil immediate:YES];
}
- (void)trackPhobosWithURL:(NSString *)url data:(NSDictionary *)data {
[self trackPhobosWithURL:url data:data immediate:NO];
}
- (void)trackPhobosWithURL:(NSString *)url data:(NSDictionary *)data immediate:(BOOL)immediate {
dispatch_async(_normalQueue, ^{
dispatch_semaphore_wait(_normalSemaphore, DISPATCH_TIME_FOREVER);
NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey] mutableCopy];
_normalCount++;
if (!immediate) {
dataDict = [self dataDict:dataDict setObject:data forKey:url];
[GMCache storeObjectAtDocumentPathWithkey:PhobosNormalCacheKey object:dataDict];
}
if (immediate || self.normalCount >= 50) { // 数据超过一定数量 或 进入后台等逻辑,统一进行发送普通埋点数据
[dataDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
if (obj && obj.count > 0) {
[self sendImmediatelyPhobosWithURL:key data:obj];
}
}];
[NewPhobos removeAllNormalPhobosData];
}
dispatch_semaphore_signal(_normalSemaphore);
});
}
/**
* 即时发送埋点接口
* @param url : 接口url
* @param data : 使用id类型是因为直接调用实时埋点传过来的是Dict,而普通埋点过来的则是Array类型
*/
- (void)sendImmediatelyPhobosWithURL:(NSString *)url data:(id)data {
dispatch_async(_immediatelyQueue, ^{
dispatch_semaphore_wait(_immediatelySemaphore, DISPATCH_TIME_FOREVER);
__block NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey] mutableCopy];
dataDict = [self dataDict:dataDict setObject:data forKey:url];
NSInteger allCount = dataDict.allKeys.count;// 请求个数
__block finishCount = 0;// 请求完成次数,不是请求成功次数,能在最后保证未发送成功的数据,能一次保存下来,等待下次发送
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 因为网络请求是异步回调,需要保证数据安全
[dataDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
[self sendDataWithAPI:key data:obj successBlock:^(NSInteger code) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
finishCount++;
if (code == 200) {// 如果数据请求成功,删除当前数据的缓存
[dataDict removeObjectForKey:key];
}
if (finishCount == allCount) {// 所有数据都请求完成,重新进行数据缓存
[GMCache storeObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey object:dataDict];
dispatch_semaphore_signal(_immediatelySemaphore);
}
dispatch_semaphore_signal(semaphore);
phobosLog(@"✈ ---------- ✈ data arrived Mars");
}];
}];
});
}
/**
* 将数据存入到字典中
* @param dataDict 需要存入的字典
* @param object 需要存入的数据
* @param key 需要将数据存入到dataDict的key所在的数据中
*/
- (NSMutableDictionary *)dataDict:(NSMutableDictionary *)dataDict setObject:(id)object forKey:(NSString *)key {
if (!dataDict) {
dataDict = [NSMutableDictionary new];
}
// 判断数据类型,统一转换成数组
NSMutableArray *dataArray = [dataDict.allKeys containsObject:key] ? [dataDict[key] mutableCopy] : [NSMutableArray new];
NSArray *data = [object isKindOfClass:[NSArray class]] ? object :@[object];
[dataArray addObjectsFromArray:data];
// 进行数据校验
@try {
NSData *json = [PhobosUtil encodeJSON:dataArray];
[dataDict setValue:dataArray forKey:key];
} @catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE只不能为NSObject ");
}
return dataDict;
}
// 发送埋点数据
- (void)sendDataWithAPI:(NSString *)api data:(NSArray *)dataArray successBlock:(SendDataSuccessBlock)successBlock {
if (_logEnabled) {
NSData *data = [NSJSONSerialization dataWithJSONObject:dataArray options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
phobosLog([NSString stringWithFormat:@"array prepare to fly --✈: %@", jsonString]);
}
@try {
if (self.phobosSendDataBlock) {
self.phobosSendDataBlock(dataArray);
}
NSData *JSON = [PhobosUtil encodeJSON:dataArray];
NSData *compressedData = [PhobosUtil compressData:JSON];
if (compressedData) {
[PhobosUtil sendData:compressedData currentAPI:api success:successBlock];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
@end
@implementation NewPhobos (UtilTest)
/** 获取所有非立即发送埋点数量 */
+ (NSInteger)normalPhobosCount {
return NewPhobos.sharedClient.normalCount;
}
/** 获取所有非立即发送埋点数据 */
+ (NSDictionary *)normalPhobosData {
return [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
}
/** 获取url的非立即发送埋点数量 */
+ (NSUInteger)normalPhobosCountForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
NSArray *urlData = dict[url];
return urlData ? urlData.count : 0;
}
+ (NSArray *)normalPhobosDataForServerAPI {
return [self normalPhobosDataForURL:NewPhobos.sharedClient.serverAPI];
}
/** 获取url的非立即发送埋点数据 */
+ (NSArray *)normalPhobosDataForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
return dict[url];
}
+ (NSArray *)immediatelyPhobosForServerAPI {
return [self immediatelyPhobosDataForURL:NewPhobos.sharedClient.serverAPI];
}
/** 获取所有立即发送埋点数据 */
+ (NSDictionary *)immediatelyPhobosData {
return [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
}
/** 获取url的立即发送埋点数据 */
+ (NSArray *)immediatelyPhobosDataForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
return dict[url];
}
+ (NSUInteger)immediatelyPhobosCountForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
NSArray *urlData = dict[url];
return urlData ? urlData.count : 0;
}
/** 清除非立即发送埋点数据缓存 */
+ (void)removeAllNormalPhobosData {
NewPhobos.sharedClient.normalCount = 0;
[GMCache removeObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
}
/** 清除立即发送埋点数据缓存 */
+ (void)removeAllImmediatelyPhobosData {
[GMCache removeObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
}
- (void)setPhobosSendDataBlock:(void (^)(NSArray *))phobosSendDataBlock {
objc_setAssociatedObject(self, @selector(phobosSendDataBlock), phobosSendDataBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^)(NSArray *))phobosSendDataBlock {
return objc_getAssociatedObject(self, @selector(phobosSendDataBlock));
}
@end
......@@ -12,6 +12,7 @@
#import "PhobosConfig.h"
NS_ASSUME_NONNULL_BEGIN
@interface OldPhobos : NSObject
/**
......
......@@ -8,7 +8,6 @@
#import "OldPhobos.h"
#import <AdSupport/AdSupport.h>
#import "PhobosUtil.h"
#import "UIResponder+PhobosPV.h"
#import "PhobosUtil.h"
#import "PhobosCustomVisibleController.h"
......@@ -66,6 +65,7 @@ static NSString *sdkVersion = @"110";
}
return self;
}
- (void)dealloc{
if (self) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
......@@ -341,7 +341,7 @@ static NSString *sdkVersion = @"110";
[dict setObject:page.pageName forKey:@"page_name"];
[dict setObject:page.businessId?:@"" forKey:@"business_id"];
[dict setObject:page.referer?:@"" forKey:@"referrer"];
[dict setObject:page.referrerLink ? : @[] forKey:@"referer_link"];
[dict setObject:page.referrerLink ? : @[] forKey:@"referrer_link"];
[dict setObject:@(0) forKey:@"fake"];
[dict setObject:page.referrerId ? : @"" forKey:@"referrer_id"];
[dict setObject:page.extraParam ? : @"" forKey:@"extra_param"];
......@@ -665,4 +665,5 @@ static NSString *sdkVersion = @"110";
[self.APIArray addObject:api];
}
}
@end
......@@ -25,18 +25,14 @@ NS_ASSUME_NONNULL_BEGIN
*
* @since 0.0.1
*/
+ (void)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId;
+ (Phobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId;
+ (instancetype) alloc __attribute__((deprecated));
- (instancetype) init __attribute__((deprecated));
+ (instancetype) new __attribute__((deprecated));
+ (instancetype)sharedClient;
#pragma mark - SDK配置
+ (void)setSharedClient:(Phobos *)client;
@property (class, readonly, strong) Phobos *sharedClient;
/** 用于Phobos库从old更新到new使用,在全亮之后删除相关逻辑 */
@property (nonatomic, assign) BOOL isGray;
#pragma mark - SDK配置
// Phobos在处理业务端传递来的参数时会检查是否某个value为空,如果为空会调用这个block以通知业务层,业务层可以上报这个异常,以助解决问题
@property(nonatomic, copy) void (^captureNullExpection) (NSString *eventId, NSDictionary *info);
......@@ -131,12 +127,12 @@ NS_ASSUME_NONNULL_BEGIN
*
* @since 0.0.1
*/
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes;
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow;
+ (void)track:(NSString *)eventName;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow;
+ (void)track:(NSString *)eventId;
/**
* @brief 自定义事件,数量统计 7730 精准曝光/数据链路.
* @brief 自定义事件,数量统计 7730 精准曝光.
*
* @param eventId 事件Id
* @attributes 参数
......@@ -144,10 +140,9 @@ NS_ASSUME_NONNULL_BEGIN
* @currentAPI 当前传过来的API
* @
*/
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventName currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventId currentAPI:(NSString *)currentAPI;
/**
* @brief PV事件开始。当controller viewWillAppear时调用
*/
......@@ -156,9 +151,9 @@ NS_ASSUME_NONNULL_BEGIN
* @brief PV事件结束。当controller viewWillDisAppear时调用
*/
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page;
@end
@interface Phobos (UtilTest)
/** 获取所有非立即发送埋点数量 */
......
//
// Phobos.m
// GengmeiDoctor
// GMCache
//
// Created by Thierry on 16/1/26.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
// Created by Locus on 2019/12/25.
//
#import "Phobos.h"
#import "OldPhobos.h"
#import <AdSupport/AdSupport.h>
#import "PhobosUtil.h"
#import "UIResponder+PhobosPV.h"
#import "PhobosUtil.h"
#import "PhobosCustomVisibleController.h"
#import <GMCache/GMCache.h>
#import <objc/runtime.h>
static Phobos *_sharedClient;
static NSString *sdkVersion = @"1.3.1";
#import "NewPhobos.h"
static Phobos *sharedClient = nil;
static BOOL isGray = NO;
@interface Phobos ()
@property (strong, nonatomic) UIViewController *visibleController;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSString *appName;
@property (strong, nonatomic) NSString *channelId;
@property (strong, nonatomic) NSString *appVersion;
@property (strong, nonatomic) NSString *sessionId;
/* 每一条埋点数据的物理ID,自增,生命周期和sessionId相同。特别注意:在sessionOver的时候,要把他置为0 */
@property (assign, nonatomic) NSInteger serialId;
//@property (nonatomic, weak) dispatch_semaphore_t immediatelySemaphore;// 保障需要立即发送的埋点数据安全
//@property (nonatomic, weak) dispatch_semaphore_t normalSemaphore;// 保障普通埋点数据安全
@property (nonatomic, assign) NSInteger normalCount;// 记录普通埋点数量
@property (nonatomic, strong) OldPhobos *oldPhobos;
@property (nonatomic, strong) NewPhobos *rePhobos;
@end
@implementation Phobos
static dispatch_semaphore_t _immediatelySemaphore;
static dispatch_semaphore_t _normalSemaphore;
static dispatch_queue_t _immediatelyQueue;
static dispatch_queue_t _normalQueue;
/**
* @brief 开启Phobos统计,默认以BATCH方式发送log.
*
* @param appName 通常由数据端与客户端一起确认的区分不同app的名字
* @param channelId 发布渠道
*
* @return Phobos实例
*
* @since 0.0.1
*/
+ (Phobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId {
return [[self alloc] initWithAppName:appName channelId:channelId];
}
+ (Phobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId{
Phobos.sharedClient.appName = appName;
Phobos.sharedClient.channelId = channelId;
Phobos.sharedClient.oldPhobos = [OldPhobos clientWithAppName:appName channelId:channelId];
[OldPhobos setSharedClient:Phobos.sharedClient.oldPhobos];
return Phobos.sharedClient;
+ (instancetype)sharedClient {
return sharedClient;
}
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_immediatelySemaphore = dispatch_semaphore_create(1);
_normalSemaphore = dispatch_semaphore_create(1);
_immediatelyQueue = dispatch_queue_create("immdiately", DISPATCH_QUEUE_CONCURRENT);
_normalQueue = dispatch_queue_create("normal", DISPATCH_QUEUE_CONCURRENT);
});
+ (void)setSharedClient:(Phobos *)client {
sharedClient = client;
}
- (instancetype)init {
- (instancetype)initWithAppName:(NSString *)appName channelId:(NSString *)channelId{
if (self = [super init]) {
_appName = @"";
_channelId = @"";
_logEnabled = NO;
_userId = @"";
_netStatus = @"";
_currentCityId = @"";
_serverAPI = @"";
_greyType = @"";
_userType = [[NSMutableDictionary alloc] init];
_appVersion = [PhobosUtil getAppVersion];
_signingType = PhobosSigningTypeUndefined;
// self.immediatelySemaphore = dispatch_semaphore_create(1);
// self.normalSemaphore = dispatch_semaphore_create(1);
NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey] mutableCopy];
for (NSArray *data in dataDict.allValues) {
self.normalCount += data.count;
isGray = [[[NSUserDefaults standardUserDefaults] objectForKey:PhobosGray] boolValue];
if (isGray) {
_rePhobos = [NewPhobos clientWithAppName:appName channelId:channelId];
} else {
_oldPhobos = [OldPhobos clientWithAppName:appName channelId:channelId];
[OldPhobos setSharedClient:_oldPhobos];
}
[self setupNotification];
[self handleSessionStart];
}
return self;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return Phobos.sharedClient;
}
- (id)copyWithZone:(struct _NSZone *)zone {
return Phobos.sharedClient;
}
+ (Phobos *)sharedClient{// 使用单例设计模式,保证Phobos只有一个实例,并提供了全局访问点
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[super allocWithZone:NULL] init];
});
return _sharedClient;
}
- (void)dealloc{
if (self) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes {
if (isGray) {
[NewPhobos track:eventId attributes:attributes];
} else {
[OldPhobos track:eventId attributes:attributes];
}
}
- (void)setUserType:(NSMutableDictionary *)userType {
if (userType == nil && userType.allKeys.count == 0) {
return;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow {
if (isGray) {
[NewPhobos track:eventId attributes:attributes sendNow:sendNow];
} else {
[OldPhobos track:eventId attributes:attributes sendNow:sendNow];
}
NSArray *newKeys = userType.allKeys;
NSArray *oldKeys = _userType.allKeys;
for (NSString *newKey in newKeys) {
if ([oldKeys containsObject:newKey]) {
NSString *newValue = [[userType objectForKey:newKey] stringValue];
NSString *oldValue = [[_userType objectForKey:newKey] stringValue];
if (![newValue isEqualToString:oldValue]) {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
} else {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
}
_oldPhobos.userType = userType;
}
- (void)setServerAPI:(NSString *)serverAPI {
_serverAPI = serverAPI;
_oldPhobos.serverAPI = serverAPI;
}
- (void)setUserId:(NSString *)userId {
_userId = userId;
_oldPhobos.userId = userId;
}
- (void)setNetStatus:(NSString *)netStatus {
_netStatus = netStatus;
_oldPhobos.netStatus = netStatus;
}
- (void)setNetworkStatus:(NSString *)networkStatus {
_networkStatus = networkStatus;
_oldPhobos.networkStatus = networkStatus;
}
- (void)setCookie:(NSString *)cookie {
_cookie = cookie;
_oldPhobos.cookie = cookie;
}
- (void)setGps:(CLLocation *)gps {
_gps = gps;
_oldPhobos.gps = gps;
}
- (void)setGetTopController:(UIViewController * _Nonnull (^)(void))getTopController {
_getTopController = getTopController;
_oldPhobos.getTopController = getTopController;
}
- (void)setCurrentCityId:(NSString *)currentCityId {
_currentCityId = currentCityId;
_oldPhobos.currentCityId = currentCityId;
}
- (void)setGreyType:(NSString *)greyType {
_greyType = greyType;
_oldPhobos.greyType = greyType;
}
- (void)setCaptureNullExpection:(void (^)(NSString * _Nonnull, NSDictionary * _Nonnull))captureNullExpection {
_captureNullExpection = captureNullExpection;
_oldPhobos.captureNullExpection = captureNullExpection;
}
/**
在APP启动、从后台到前台的时候需要记录device_opened埋点
*/
- (void)handleEventDeviceOpened{
/** 每次打开APP埋点 **/
NSDictionary *dict = @{@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
};
[Phobos track:@"device_opened" attributes:dict sendNow:YES];
}
- (UIViewController *)visibleController {
id target = self.getTopController();
if ([target conformsToProtocol:NSProtocolFromString(@"PhobosCustomVisibleController")]) {
target = [target performSelector:@selector(phobosVisibleController)];
+ (void)track:(NSString *)eventId {
if (isGray) {
[NewPhobos track:eventId];
} else {
[OldPhobos track:eventId];
}
return target;
}
#pragma mark - notification handler
/**
* @brief 设置对APP的通知监听
*
* @since 0.0.1
*/
- (void)setupNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppFinishLaunch:) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInBackgound:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
/**
* @brief 每次打开APP或返回前台,即Session开始的时候的处理
* @notification didFinishLaunch和willEnterForeground的时候都需要记录
*
* @since 0.0.1
*/
- (void)handleSessionStart{
_serialId = 0;
_sessionId = [[NSUUID UUID] UUIDString];
[GMCache storeObjectAtDocumentPathWithkey:PhobosBeginTime object:[PhobosUtil currentTime]];
}
/**
* @brief 应用打开时的处理
*
* @since 0.0.1
*/
- (void)handleAppFinishLaunch:(id)sender{
phobosLog(@"handleAppFinishLaunch");
[self handleSessionStart];
[self handleEventDeviceOpened];
}
/**
* @brief 应用进入前台的处理
*
* @since 0.0.1
*/
- (void)handleAppInForeground:(id)sender{
phobosLog(@"handleAppInForeground");
[self handleSessionStart];
[self handleEventDeviceOpened];
[self sendImmediatelyNormalData];
[self handlePVEventAppInForeground];
}
/**
* @brief 应用进入后台的处理
*
* @since 0.0.1
*/
- (void)handleAppInBackgound:(id)sender{
phobosLog(@"handleAppInBackgound");
[self handlePVEventAppInBackgound];
[self handleSessionOver];
[self sendImmediatelyNormalData];
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI {
if (isGray) {
[NewPhobos track:eventId attributes:attributes currentAPI:currentAPI];
} else {
[OldPhobos track:eventId attributes:attributes currentAPI:currentAPI];
}
}
/**
* @brief 会话结束时候的处理
*
* @since 0.0.1
*/
- (void)handleSessionOver{
//进入后台的同时,把记录时间同步到服务端,把已使用时间清空
double beginTime = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosBeginTime] doubleValue];
if (beginTime == 0) {
return;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI {
if (isGray) {
[NewPhobos track:eventId attributes:attributes sendNow:sendNow currentAPI:currentAPI];
} else {
[OldPhobos track:eventId attributes:attributes sendNow:sendNow currentAPI:currentAPI];
}
NSDate *date = [NSDate date];
double endTime = [date timeIntervalSince1970];
NSString *usedTime = [NSString stringWithFormat:@"%ld",(long)(endTime - beginTime)];
NSDictionary *dict = @{@"duration":usedTime,
@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
};
[Phobos track:@"on_app_session_over" attributes:dict];
[GMCache removeObjectAtDocumentPathWithkey:PhobosBeginTime];
//当前session结束之后,把id置为0
_serialId = 0;
}
/**
APP从后台到前台的时候,重新初始化pagename等信息
@author zhaiguojun 16-10-11 in (null)
*/
- (void)handlePVEventAppInForeground {
if (self.visibleController != nil) {
[self onPVStart:self.visibleController];
+ (void)track:(NSString *)eventId currentAPI:(NSString *)currentAPI {
if (isGray) {
[NewPhobos track:eventId currentAPI:currentAPI];
} else {
[OldPhobos track:eventId currentAPI:currentAPI];
}
}
/**
APP进到后台的时候,把当前pageview时间结束
@author zhaiguojun 16-10-11 in (null)
* @brief PV事件开始。当controller viewWillAppear时调用
*/
- (void)handlePVEventAppInBackgound {
if (self.visibleController != nil) {
[self onPVEnd:self.visibleController];
}
}
#pragma mark - PV
- (void)onPVStart:(UIResponder<PhobosPVProtocol> *)page {
if (!_isGray) {
[_oldPhobos onPVStart:page];
return;
}
// 必须在此处调用一下referer,因为onControllerStart
[page initReferer];
[page initRefererLink];
[page initReferrerIdIfNil];
[page initReferrerTabName];
page.inTime = [PhobosUtil currentTime];
[_rePhobos onPVStart:page];
[_oldPhobos onPVStart:page];
}
/**
* @brief PV事件结束。当controller viewWillDisAppear时调用
*/
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page {
if (!_isGray) {
[_oldPhobos onPVEnd:page];
return;
}
if (![PhobosUtil isNonEmpty:page.pageName] || !page.needLogPV) {
return;
}
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
[dict setObject:[PhobosUtil currentTime] forKey:@"out"];
[dict setObject:page.inTime?:@"" forKey:@"in"];
[dict setObject:page.pageName forKey:@"page_name"];
[dict setObject:page.businessId?:@"" forKey:@"business_id"];
[dict setObject:page.referer?:@"" forKey:@"referrer"];
[dict setObject:page.referrerLink ? : @[] forKey:@"referrer_link"];
[dict setObject:@(0) forKey:@"fake"];
[dict setObject:page.referrerId ? : @"" forKey:@"referrer_id"];
[dict setObject:page.extraParam ? : @"" forKey:@"extra_param"];
[dict setObject:page.referrerTabName ? : @"" forKey:@"referrer_tab_name"];
[dict setObject:page.isPush.intValue ? @(page.isPush.intValue) : @(0) forKey:@"is_push"];
if (page.inTime.length > 0) {
// 页面显示时间为空时不记录页面pv事件
[Phobos track:@"page_view" attributes:dict];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
[_rePhobos onPVEnd:page];
[_oldPhobos onPVEnd:page];
}
/**
* @brief 将埋点时间封装成词典数据
*
* @since 0.0.1
*/
- (NSDictionary *)prepareDictionaryForEvent:(NSString *)eventName attributes:(NSDictionary *)attributes{
NSArray *referrerLink = _sharedClient.visibleController.referrerLink;
if (![eventName isEqualToString:@"page_view"]) {
NSMutableDictionary *attributesParams = [NSMutableDictionary dictionaryWithDictionary:attributes];
[attributesParams setValue:referrerLink ? : @[] forKey:@"referrer_link"];
attributes = attributesParams;
}
[self catchNullForEvent:eventName attributes:attributes];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
NSString *currentTime = [PhobosUtil currentTime];
NSMutableDictionary *deviceParams = [NSMutableDictionary new];
[deviceParams setValue:[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString] forKey:@"idfa"];
[deviceParams setValue:[[[UIDevice currentDevice] identifierForVendor] UUIDString] forKey:@"idfv"];
[deviceParams setValue:[PhobosUtil deviceId] forKey:@"device_id"];
[deviceParams setValue:@"ios" forKey:@"device_type"];
[deviceParams setValue:@"Apple" forKey:@"manufacturer"];
[deviceParams setValue:@(self.gps.coordinate.latitude) forKey:@"lat"];
[deviceParams setValue:@(self.gps.coordinate.longitude) forKey:@"lng"];
[deviceParams setValue:_netStatus forKey:@"is_WiFi"];
[deviceParams setValue:[PhobosUtil getIPAddress:YES] forKey:@"ip"];
[deviceParams setValue:_networkStatus forKey:@"net_type"];
[deviceParams setValue:[PhobosUtil platform] forKey:@"model"];
[deviceParams setValue:[UIDevice currentDevice].systemVersion forKey:@"sys_version"];
NSMutableDictionary *appParams = [NSMutableDictionary new];
[appParams setValue:_greyType forKey:@"grey_type"];
[appParams setValue:_appName forKey:@"name"];
[appParams setValue:_appVersion forKey:@"version"];
[appParams setValue:_channelId forKey:@"channel"];
[appParams setValue:_userType forKey:@"user_type"];
[appParams setValue:_currentCityId forKey:@"current_city_id"];
[appParams setValue:@(_serialId++) forKey:@"serial_id"];
if (_signingType == PhobosSigningTypeDebug || _signingType == PhobosSigningTypeRelease) {
[dict setObject:@(0) forKey:@"is_release"];
}
[dict setObject:eventName forKey:@"type"];
[dict setObject:appParams forKey:@"app"];
[dict setObject:sdkVersion forKey:@"version"];
[dict setObject:deviceParams forKey:@"device"];
[dict setObject:_userId forKey:@"user_id"];
[dict setObject:currentTime forKey:@"create_at"];
[dict setObject:attributes forKey:@"params"];
[dict setObject:_sessionId forKey:@"app_session_id"];
}
@catch (NSException *exception) {
phobosLog(exception);
}
return dict;
}
#pragma mark - helpers
- (void)catchNullForEvent:(NSString *)eventName attributes:(NSDictionary *)attributes {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@try {
for (NSString *key in attributes.allKeys) {
if ([attributes[key] isMemberOfClass:[NSNull class]]) {
if (self.captureNullExpection) {
self.captureNullExpection(eventName, attributes);
}
break;
}
}
} @catch (NSException *exception) {
}
});
- (void)setCaptureNullExpection:(void (^)(NSString * _Nonnull, NSDictionary * _Nonnull))captureNullExpection {
_rePhobos.captureNullExpection = captureNullExpection;
_oldPhobos.captureNullExpection = captureNullExpection;
}
+ (void)track:(NSString *)eventName{
if (!Phobos.sharedClient.isGray) {
[OldPhobos track:eventName];
return;
}
[self track:eventName attributes:@{} sendNow:NO currentAPI:_sharedClient.serverAPI];
- (void)setNetStatus:(NSString *)netStatus {
_rePhobos.netStatus = netStatus;
_oldPhobos.netStatus = netStatus;
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes{
if (!Phobos.sharedClient.isGray) {
[Phobos track:eventName attributes:attributes];
return;
}
[self track:eventName attributes:attributes sendNow:NO currentAPI:_sharedClient.serverAPI];
- (void)setNetworkStatus:(NSString *)networkStatus {
_rePhobos.networkStatus = networkStatus;
_oldPhobos.networkStatus = networkStatus;
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow{
if (!Phobos.sharedClient.isGray) {
[OldPhobos track:eventName attributes:attributes sendNow:sendNow];
return;
}
[self track:eventName attributes:attributes sendNow:sendNow currentAPI:_sharedClient.serverAPI];
- (void)setLogEnabled:(BOOL)logEnabled {
_rePhobos.logEnabled = logEnabled;
_oldPhobos.logEnabled = logEnabled;
}
+ (void)track:(NSString *)eventName currentAPI:(NSString *)currentAPI {
if (!Phobos.sharedClient.isGray) {
[OldPhobos track:eventName currentAPI:currentAPI];
return;
}
[self track:eventName attributes:@{} sendNow:NO currentAPI:currentAPI];
- (void)setUserId:(NSString *)userId {
_rePhobos.userId = userId;
_oldPhobos.userId = userId;
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI {
if (!Phobos.sharedClient.isGray) {
[OldPhobos track:eventName attributes:attributes currentAPI:currentAPI];
return;
}
[self track:eventName attributes:attributes sendNow:NO currentAPI:currentAPI];
- (void)setCurrentCityId:(NSString *)currentCityId {
_rePhobos.currentCityId = currentCityId;
_oldPhobos.currentCityId = currentCityId;
}
+ (void)track:(NSString *)eventName attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI {
if (!Phobos.sharedClient.isGray) {
[OldPhobos track:eventName attributes:attributes sendNow:sendNow currentAPI:currentAPI];
return;
}
NSDictionary *dataDict = [_sharedClient prepareDictionaryForEvent:eventName attributes:attributes];
@try {
NSData *JSON = [PhobosUtil encodeJSON:dataDict];
if (sendNow) {
[_sharedClient sendImmediatelyPhobosWithURL:currentAPI data:dataDict];
} else{
[_sharedClient trackPhobosWithURL:currentAPI data:dataDict];
}
}
@catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE不能为NSObject ");
}
- (void)setGps:(CLLocation *)gps {
_rePhobos.gps = gps;
_oldPhobos.gps = gps;
}
- (void)setUserType:(NSMutableDictionary *)userType {
_rePhobos.userType = userType;
_oldPhobos.userType = userType;
}
#pragma mark - 事件存储、发送
/**
* 普通埋点
* @param url 接口url
* @param dataArray 埋点数据
* @param nowSend 是否需要发送当前缓存数据(app进入到后台、前台等情况)
*/
- (void)sendImmediatelyNormalData {
[self trackPhobosWithURL:nil data:nil immediate:YES];
- (void)setApiHost:(NSString *)apiHost {
_rePhobos.apiHost = apiHost;
_oldPhobos.apiHost = apiHost;
}
- (void)trackPhobosWithURL:(NSString *)url data:(NSDictionary *)data {
[self trackPhobosWithURL:url data:data immediate:NO];
- (void)setServerAPI:(NSString *)serverAPI {
_rePhobos.serverAPI = serverAPI;
_oldPhobos.serverAPI = serverAPI;
}
- (void)trackPhobosWithURL:(NSString *)url data:(NSDictionary *)data immediate:(BOOL)immediate {
dispatch_async(_normalQueue, ^{
dispatch_semaphore_wait(_normalSemaphore, DISPATCH_TIME_FOREVER);
NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey] mutableCopy];
_normalCount++;
if (!immediate) {
dataDict = [self dataDict:dataDict setObject:data forKey:url];
[GMCache storeObjectAtDocumentPathWithkey:PhobosNormalCacheKey object:dataDict];
}
if (immediate || self.normalCount >= 50) { // 数据超过一定数量 或 进入后台等逻辑,统一进行发送普通埋点数据
[dataDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
if (obj && obj.count > 0) {
[self sendImmediatelyPhobosWithURL:key data:obj];
}
}];
[Phobos removeAllNormalPhobosData];
}
dispatch_semaphore_signal(_normalSemaphore);
});
-(void)setCookie:(NSString *)cookie {
_rePhobos.cookie = cookie;
_oldPhobos.cookie = cookie;
}
/**
* 即时发送埋点接口
* @param url : 接口url
* @param data : 使用id类型是因为直接调用实时埋点传过来的是Dict,而普通埋点过来的则是Array类型
*/
- (void)sendImmediatelyPhobosWithURL:(NSString *)url data:(id)data {
dispatch_async(_immediatelyQueue, ^{
dispatch_semaphore_wait(_immediatelySemaphore, DISPATCH_TIME_FOREVER);
__block NSMutableDictionary *dataDict = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey] mutableCopy];
dataDict = [self dataDict:dataDict setObject:data forKey:url];
NSInteger allCount = dataDict.allKeys.count;// 请求个数
__block finishCount = 0;// 请求完成次数,不是请求成功次数,能在最后保证未发送成功的数据,能一次保存下来,等待下次发送
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 因为网络请求是异步回调,需要保证数据安全
[dataDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
[self sendDataWithAPI:key data:obj successBlock:^(NSInteger code) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
finishCount++;
if (code == 200) {// 如果数据请求成功,删除当前数据的缓存
[dataDict removeObjectForKey:key];
}
if (finishCount == allCount) {// 所有数据都请求完成,重新进行数据缓存
[GMCache storeObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey object:dataDict];
dispatch_semaphore_signal(_immediatelySemaphore);
}
dispatch_semaphore_signal(semaphore);
phobosLog(@"✈ ---------- ✈ data arrived Mars");
}];
}];
});
- (void)setGreyType:(NSString *)greyType {
_rePhobos.greyType = greyType;
_oldPhobos.greyType = greyType;
}
/**
* 将数据存入到字典中
* @param dataDict 需要存入的字典
* @param object 需要存入的数据
* @param key 需要将数据存入到dataDict的key所在的数据中
*/
- (NSMutableDictionary *)dataDict:(NSMutableDictionary *)dataDict setObject:(id)object forKey:(NSString *)key {
if (!dataDict) {
dataDict = [NSMutableDictionary new];
}
// 判断数据类型,统一转换成数组
NSMutableArray *dataArray = [dataDict.allKeys containsObject:key] ? [dataDict[key] mutableCopy] : [NSMutableArray new];
NSArray *data = [object isKindOfClass:[NSArray class]] ? object :@[object];
//#ifndef POD_CONFIGURATION_APP_STORE
// [self verifyPVPhobos:dataArray data:object];
//#endif
[dataArray addObjectsFromArray:data];
// 进行数据校验
@try {
NSData *json = [PhobosUtil encodeJSON:dataArray];
[dataDict setValue:dataArray forKey:key];
} @catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE只不能为NSObject ");
}
return dataDict;
}
// 校验PV埋点是否有问题,默认返回NO
- (BOOL)verifyPVPhobos:(NSArray *)dataArray data:(id)data {
if ([data isKindOfClass:[NSDictionary class]]) {
NSDictionary *dataDict = (NSDictionary *)data;
if (dataDict[@"type"] && [dataDict[@"type"] isEqualToString:@"page_view"]) {
NSDictionary *pageParams = dataDict[@"params"];
long long pageInTime = [pageParams[@"in"] longLongValue];
long long pageOutTime = [pageParams[@"out"] longLongValue];
if (pageInTime && pageOutTime) {
BOOL checkTimeError = NO;
for (int i = 0; i < dataArray.count; i++) {
NSDictionary *obj = dataArray[i];
if (obj[@"type"] && [obj[@"type"] isEqualToString:@"page_view"]) {
NSDictionary *params = obj[@"params"];
long long objInTime = [params[@"in"] longLongValue];
long long objOutTime = [params[@"out"] longLongValue];
if ( (fabsl(objInTime - pageInTime)) < 1 || (fabsl(objOutTime - pageOutTime)) < 1) {
phobosLog(@"两个埋点inTime 时间差------%f", (fabsl(objInTime - pageInTime)));
phobosLog(@"两个埋点outTime 时间差------%f", (fabsl(objOutTime - pageOutTime)));
checkTimeError = YES;
phobosLog(@"%s____数据校验失败,PV埋点可能重复", __func__);
break;
}
}
}
return checkTimeError;
}
}
}
return NO;
- (void)setSigningType:(PhobosSigningType)signingType {
_rePhobos.signingType = signingType;
_oldPhobos.signingType = signingType;
}
// 发送埋点数据
- (void)sendDataWithAPI:(NSString *)api data:(NSArray *)dataArray successBlock:(SendDataSuccessBlock)successBlock {
if (_logEnabled) {
NSData *data = [NSJSONSerialization dataWithJSONObject:dataArray options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
phobosLog([NSString stringWithFormat:@"array prepare to fly --✈: %@", jsonString]);
}
@try {
if (self.phobosSendDataBlock) {
self.phobosSendDataBlock(dataArray);
}
NSData *JSON = [PhobosUtil encodeJSON:dataArray];
NSData *compressedData = [PhobosUtil compressData:JSON];
if (compressedData) {
[PhobosUtil sendData:compressedData currentAPI:api success:successBlock];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
- (void)setGetTopController:(UIViewController * _Nonnull (^)(void))getTopController {
_rePhobos.getTopController = getTopController;
_oldPhobos.getTopController = getTopController;
}
@end
@implementation Phobos (UtilTest)
@implementation NewPhobos (UtilTest)
/** 获取所有非立即发送埋点数量 */
+ (NSInteger)normalPhobosCount {
return Phobos.sharedClient.normalCount;
return isGray ? [NewPhobos normalPhobosCount] : 0;
}
/** 获取所有非立即发送埋点数据 */
+ (NSDictionary *)normalPhobosData {
return [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
return isGray ? [NewPhobos normalPhobosData] : nil;
}
/** 获取url的非立即发送埋点数量 */
+ (NSUInteger)normalPhobosCountForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
NSArray *urlData = dict[url];
return urlData ? urlData.count : 0;
return isGray ? [NewPhobos normalPhobosCountForURL:url] : 0;
}
+ (NSArray *)normalPhobosDataForServerAPI {
return [self normalPhobosDataForURL:Phobos.sharedClient.serverAPI];
return isGray ? [NewPhobos normalPhobosDataForServerAPI] : nil;
}
/** 获取url的非立即发送埋点数据 */
+ (NSArray *)normalPhobosDataForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
return dict[url];
return isGray ? [NewPhobos normalPhobosDataForURL:url] : nil;
}
+ (NSArray *)immediatelyPhobosForServerAPI {
return [self immediatelyPhobosDataForURL:Phobos.sharedClient.serverAPI];
return isGray ? [NewPhobos immediatelyPhobosForServerAPI] : nil;
}
/** 获取所有立即发送埋点数据 */
+ (NSDictionary *)immediatelyPhobosData {
return [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
return isGray ? [NewPhobos immediatelyPhobosData] : nil;
}
/** 获取url的立即发送埋点数据 */
+ (NSArray *)immediatelyPhobosDataForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
return dict[url];
return isGray ? [NewPhobos immediatelyPhobosDataForURL:url] : nil;
}
+ (NSUInteger)immediatelyPhobosCountForURL:(NSString *)url {
NSDictionary *dict = [GMCache fetchObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
NSArray *urlData = dict[url];
return urlData ? urlData.count : 0;
return isGray ? [NewPhobos immediatelyPhobosCountForURL:url] : 0;
}
/** 清除非立即发送埋点数据缓存 */
+ (void)removeAllNormalPhobosData {
Phobos.sharedClient.normalCount = 0;
[GMCache removeObjectAtDocumentPathWithkey:PhobosNormalCacheKey];
if (isGray) {
[NewPhobos removeAllNormalPhobosData];
}
}
/** 清除立即发送埋点数据缓存 */
+ (void)removeAllImmediatelyPhobosData {
[GMCache removeObjectAtDocumentPathWithkey:PhobosImmediatelyCacheKey];
if (isGray) {
[NewPhobos removeAllNormalPhobosData];
}
}
- (void)setPhobosSendDataBlock:(void (^)(NSArray *))phobosSendDataBlock {
objc_setAssociatedObject(self, @selector(phobosSendDataBlock), phobosSendDataBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
if (isGray) {
NewPhobos.sharedClient.phobosSendDataBlock = phobosSendDataBlock;
}
}
- (void (^)(NSArray *))phobosSendDataBlock {
return objc_getAssociatedObject(self, @selector(phobosSendDataBlock));
return NewPhobos.sharedClient.phobosSendDataBlock;
}
@end
......@@ -23,6 +23,8 @@ typedef NS_ENUM (NSInteger, PhobosSigningType) {
#define phobosLog(...)
#endif
#define PhobosGray @"PhobosGray"
#define PhobosBeginTime @"PhobosBeginTime" //记录APP打开|从后台启动时的时间戳
#define PhobosEndTime @"PhobosEndTime" //记录APP退出|退到后台时的时间戳
#define PhobosNormalCacheKey @"PhobosNormalCacheKey" //存放持久化埋点数据的key
......
//
// PhobosDataManager.h
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, PhobosModelSendStatus) {
PhobosModelSendStatusNormal = 0,
PhobosModelSendStatusSending,
PhobosModelSendStatusFinish,
PhobosModelSendStatusError,
};
@interface PhobosSendModel : NSObject
@property (nonatomic, copy, readonly) NSString *sendId;
@property (nonatomic, strong) id data;
@property (nonatomic, copy) NSString *api;
@property (nonatomic, assign) PhobosModelSendStatus sendStatus;
- (instancetype)initWithData:(id)data sendApi:(NSString *)api;
@end
@interface PhobosDataManager : NSObject
- (void)insertData:(PhobosSendModel *)data immediately:(BOOL)immediately;
- (void)updateData:(PhobosSendModel *)data sendStatus:(PhobosModelSendStatus)sendStatus;
- (void)deleteData:(PhobosSendModel *)data;
- (void)removeNormalData;
- (NSArray<PhobosSendModel *> *)fetchData;
@end
NS_ASSUME_NONNULL_END
//
// PhobosDataManager.m
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import "PhobosDataManager.h"
@implementation PhobosSendModel
- (instancetype)initWithData:(id)data sendApi:(NSString *)api {
if (self = [super init]) {
_data = data;
_sendId = @"123";
_api = api;
}
return self;
}
@end
@implementation PhobosDataManager
- (void)insertData:(PhobosSendModel *)data immediately:(BOOL)immediately {
}
- (void)updateData:(PhobosSendModel *)data sendStatus:(PhobosModelSendStatus)sendStatus {
}
- (void)deleteData:(PhobosSendModel *)data {
}
- (void)removeNormalData {
}
- (NSArray<PhobosSendModel *> *)fetchData {
return nil;
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment