Commit 9f93ec88 authored by yueming lu's avatar yueming lu

数据库添加和常驻线程实现埋点

parent 418086ca
......@@ -439,18 +439,18 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-GMPhobos_Example/Pods-GMPhobos_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework",
"${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework",
"${BUILT_PRODUCTS_DIR}/MJExtension/MJExtension.framework",
"${BUILT_PRODUCTS_DIR}/MagicalRecord/MagicalRecord.framework",
"${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMCache.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMPhobos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJExtension.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MagicalRecord.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TMCache.framework",
);
runOnlyForDeploymentPostprocessing = 0;
......
......@@ -22,7 +22,11 @@ NSString *const MockEventId = @"eventId";
NSString *const MockUserId = @"1";
NSString *const MockCityId = @"beijing";
@interface GMViewController ()
@interface GMViewController ()<UITableViewDelegate ,UITableViewDataSource>
{
UITableView *_tableView;
NSArray *_dataSource;
}
@end
......@@ -31,23 +35,101 @@ NSString *const MockCityId = @"beijing";
- (void)viewDidLoad
{
[super viewDidLoad];
#ifdef POD_CONFIGURATION_APP_STORE
NSString *url = @"http://log.gmei.com/log/collect";
#else
NSString *url = @"http://log.test.igengmei.com/log/collect";
#endif
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:PhobosGray];
Phobos *client = [Phobos clientWithAppName:MockAppName channelId:MockChannelId];
[Phobos setSharedClient:client];
Phobos.sharedClient.serverAPI = url;
[Phobos.sharedClient setLogEnabled:NO]; // 调试打Log模式,看情况开启
Phobos.sharedClient.signingType = PhobosSigningTypeDebug;
Phobos.sharedClient.userId = @"";
Phobos.sharedClient.getTopController = ^UIViewController * _Nonnull{
return self;
};
[self sdk];
_dataSource = @[
@"pageView",
@"100个线程 * 100数据",
@"计数发送",
@"瞬间发送",
@"瞬间发送和计数发送",
@"表中有多少条数据",
@"清空数据"
];
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
[self.view addSubview:_tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataSource.count + 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (indexPath.row < _dataSource.count) {
cell.textLabel.text = _dataSource[indexPath.row];
} else {
cell.textLabel.text = [NSString stringWithFormat:@"%ld", indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row >= _dataSource.count) return;
switch (indexPath.row) {
case 0: //pageView
{
for (int i = 0; i < 20; i++) {
[self pageView:i];
}
}
break;
case 1://100个线程 * 100数据"
{
dispatch_queue_t queue = dispatch_queue_create("lym", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 100; i++) {
dispatch_async(queue, ^{
for (int j = 0; j< 100; j++) {
[Phobos track:@"100*100" attributes:@{@"index":@(i)} sendNow:NO];
}
});
}
}
break;
case 2://@"计数发送",
{
for (int i = 0; i< 10000; i++) {
[Phobos track:@"计数发送" attributes:@{@"index":@(i)} sendNow:NO];
}
NSLog(@"&&&&&&&&&&&&&&&&&");
}
break;
case 3://@"瞬间发送",
{
[Phobos track:@"瞬间发送" attributes:@{@"index":@(0)} sendNow:YES];
}
break;
case 4://瞬间发送和计数发送
{
for (int i = 0; i< 100; i++) {
[Phobos track:@"瞬间发送和计数发送" attributes:@{@"index":@(i)} sendNow:i % 9];
}
}
break;
case 5://清空数据
{
[Phobos fetchToBeSendPhobosDataCount];
}
break;
case 6://清空数据
{
[Phobos removeAllPhobosData];
}
break;
default:
break;
}
}
-(void)pageView:(int)index {
NSString *inDate = [PhobosUtil currentTime];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[PhobosUtil currentTime] forKey:@"out"];
......@@ -60,36 +142,27 @@ NSString *const MockCityId = @"beijing";
[dict setObject:@"" forKey:@"extra_param"];
[dict setObject:@"" forKey:@"referrer_tab_name"];
[dict setObject:@(0) forKey:@"is_push"];
// [Phobos track:@"page_view" attributes:dict];
// NSArray *array = [GMCache fetchObjectAtDocumentPathWithkey:PhobosCacheKey];
//
// [Phobos track:@"page_view" attributes:dict];
// array = [GMCache fetchObjectAtDocumentPathWithkey:PhobosCacheKey];
// for (int i = 0; i < 200; i++) {
// [Phobos track:[NSString stringWithFormat:@"phobos>>>tt-%d", i] attributes:dict sendNow:YES];
// [Phobos track:[NSString stringWithFormat:@"phobos>>>pv-%d", i] attributes:dict sendNow:(arc4random() % 2 == 0)];
// NSUInteger count = [Phobos fetchToBeSendPhobosDataCount];
// NSLog(@"%lu", (unsigned long)count);
// }
// for (int i = 0; i < 200; i++) {
// dispatch_async(dispatch_get_global_queue(0, 0), ^{
// NSUInteger count = [Phobos fetchToBeSendPhobosDataCount];
// NSLog(@"%lu", (unsigned long)count);
// [Phobos track:[NSString stringWithFormat:@"phobos>>>ay-%d", i] attributes:dict sendNow:YES];
// });
// }
NSString *name = [NSString stringWithFormat:@"page_view-%d", index];
[Phobos track:name attributes:dict];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
swiftVC *vc = [swiftVC new];
[self presentViewController:vc animated:YES completion:nil];
- (void)sdk {
#ifdef POD_CONFIGURATION_APP_STORE
NSString *url = @"http://log.gmei.com/log/collect";
#else
NSString *url = @"http://log.test.igengmei.com/log/collect";
#endif
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:PhobosGray];
Phobos *client = [Phobos clientWithAppName:MockAppName channelId:MockChannelId];
[Phobos setSharedClient:client];
Phobos.sharedClient.serverAPI = url;
[Phobos.sharedClient setLogEnabled:NO]; // 调试打Log模式,看情况开启
Phobos.sharedClient.signingType = PhobosSigningTypeDebug;
Phobos.sharedClient.userId = @"";
Phobos.sharedClient.getTopController = ^UIViewController * _Nonnull{
return self;
};
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- GMCache (1.0.1):
- TMCache (= 2.1.0)
- GMPhobos (2.0.7):
- FMDB
- GMCache
- MagicalRecord
- MJExtension
- MagicalRecord (2.3.2):
- MagicalRecord/Core (= 2.3.2)
- MagicalRecord/Core (2.3.2)
- MJExtension (3.2.1)
- TMCache (2.1.0)
......@@ -18,7 +18,7 @@ SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GMCache
https://github.com/CocoaPods/Specs.git:
- MagicalRecord
- FMDB
- MJExtension
- TMCache
......@@ -27,9 +27,9 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMPhobos: 115e2608cdebcccf445bf95766fee25a4b62153d
MagicalRecord: 53bed74b4323b930992a725be713e53b37d19755
GMPhobos: eee96b9eb4c381183e01ae71f1df2deefb0ec0b1
MJExtension: 635f2c663dcb1bf76fa4b715b2570a5710aec545
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
......
......@@ -26,7 +26,7 @@ Pod::Spec.new do |s|
s.source_files = 'GMPhobos/Classes/**/*'
s.dependency 'GMCache'
s.dependency 'MJExtension'
s.dependency 'MagicalRecord'
s.dependency 'FMDB'
s.library = 'z'
s.resource = 'GMPhobos/*.xcdatamodeld'
......
......@@ -174,7 +174,6 @@ static NewPhobos *_sharedClient;
phobosLog(@"handleAppInForeground");
[self handleSessionStart];
[self handleEventDeviceOpened];
[NewPhobos disposeSendDataWithImmediately:NO];
[self handlePVEventAppInForeground];
}
......@@ -187,7 +186,6 @@ static NewPhobos *_sharedClient;
phobosLog(@"handleAppInBackgound");
[self handlePVEventAppInBackgound];
[self handleSessionOver];
[NewPhobos disposeSendDataWithImmediately:NO];
}
/**
......@@ -408,11 +406,10 @@ static NewPhobos *_sharedClient;
+ (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];
[PhobosDataManager insertData:dataDict sendAPI:currentAPI completion:^(BOOL contextDidSave, NSError * _Nullable error) {
[self disposeSendDataWithImmediately:sendNow];
}];
[[PhobosDataManager sharedPhobosDataManager] insertData:dataDict sendAPI:currentAPI phobosType:eventName immediately:sendNow];
} @catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE不能为NSObject ");
}
......@@ -425,39 +422,26 @@ static NewPhobos *_sharedClient;
*/
+ (void)trackSessionOverWithAttributes:(NSDictionary *)attributes {
NSDictionary *dataDict = [_sharedClient prepareDictionaryForEvent:@"on_app_session_over" attributes:attributes];
[PhobosDataManager insertData:dataDict sendAPI:_sharedClient.serverAPI completion:nil];
[self disposeSendDataWithImmediately:YES];
[[PhobosDataManager sharedPhobosDataManager] insertData:dataDict sendAPI:_sharedClient.serverAPI phobosType:@"on_app_session_over" immediately:YES];
}
/**
* 处理发送数据
*/
+ (void)disposeSendDataWithImmediately:(BOOL)immediately {
NSInteger count = [PhobosDataManager fetchCountOfToBeSendEntities];
if (immediately || count >= PhobosShardCount) {
NSArray<PhobosSendDataEntity *> *entities = [PhobosDataManager fetchToBeSendDataEntitiesAndUpdateWithSendStatus:PhobosDataSendStatusSending];
[PhobosSendManager sendDataWithEntities:entities completion:^(NSArray<PhobosSendDataEntity *> * _Nonnull finishEntities, NSInteger code) {
[PhobosDataManager updateDataEntities:finishEntities sendStatus:(code == 200 ? PhobosDataSendStatusFinish : PhobosDataSendStatusError) completion:nil];
}];
}
}
@end
@implementation NewPhobos (UtilTest)
/** 获取所有非立即发送埋点数量 */
///** 获取所有非立即发送埋点数量 */
+ (NSUInteger)fetchToBeSendPhobosDataCount {
return [PhobosDataManager fetchCountOfToBeSendEntities];
}
/** 获取待发送埋点数据, 用同步来保障异步获取数据 */
+ (NSArray *)fetchToBeSendPhobosData {
return [PhobosDataManager fetchToBeSendDataEntities];
return [[PhobosDataManager sharedPhobosDataManager] messageCount] ;
}
///** 获取待发送埋点数据, 用同步来保障异步获取数据 */
//+ (NSArray *)fetchToBeSendPhobosData {
// return [PhobosDataManager fetchToBeSendDataEntities];
//}
//
/** 清除待发送埋点数据缓存 */
+ (void)removeAllPhobosData {
[PhobosDataManager deleteAllEntities];
[[PhobosDataManager sharedPhobosDataManager] removeAll];
}
@end
//
// PhobosDataManager.h
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import <Foundation/Foundation.h>
#import <MagicalRecord/MagicalRecord.h>
#import "PhobosSendDataEntity+CoreDataClass.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, PhobosDataSendStatus) {
PhobosDataSendStatusToBeSend = 1, // 待发送数据
PhobosDataSendStatusSending = 2, // 发送中数据
PhobosDataSendStatusFinish = 3, // 发送完成数据
PhobosDataSendStatusError = 4, // 发送失败数据
};
@interface PhobosDataManager : NSObject
/**
* 获取待发送和发送失败的数据数量
*/
+ (NSUInteger)fetchCountOfToBeSendEntities;
/**
* 获取待发送数据,包含待发送数据和发送失败数据
* predicate 通过谓词获取相应的数据
*/
+ (NSArray<PhobosSendDataEntity *> *)fetchToBeSendDataEntities;
+ (NSArray<PhobosSendDataEntity *> *)fetchDataEntitiesWithPredicate:(NSPredicate *)predicate;
/**
* 获取待发送数据,包含待发送数据和发送失败数据, 并修改状态为sendStatus
*/
+ (NSArray<PhobosSendDataEntity *> *)fetchToBeSendDataEntitiesAndUpdateWithSendStatus:(PhobosDataSendStatus)sendStatus;
/**
* 插入埋点数据
* data 埋点参数
* sendAPI 数据接收的服务器API
* completion 插入数据库成功后的回调方法,在保存完成后调用的完成块。如果发生错误,块将以“BOOL”和“NSError”实例的形式传递成功状态。总是在主队列上调用。
* 该方法调用完成,fetch方法获取相应的数据就可获取到,不需要等completion回调。
*/
+ (void)insertData:(NSDictionary *)data sendAPI:(NSString *)sendAPI completion:(MRSaveCompletionHandler)completion;
/**
* 修改埋点数据
* entities 需要修改的实体
* sendStatus 需要修改为的状态,
* completion 插入数据库成功后的回调方法,在保存完成后调用的完成块。如果发生错误,块将以“BOOL”和“NSError”实例的形式传递成功状态。总是在主队列上调用。
* 该方法调用完成,fetch方法获取相应的数据就可获取到,不需要等completion回调。
*/
+ (void)updateDataEntities:(NSArray<PhobosSendDataEntity *> *)entities sendStatus:(PhobosDataSendStatus)sendStatus completion:(MRSaveCompletionHandler)completion;
/**
* 删除所有数据
* 仅限单元测试使用
*/
+ (void)deleteAllEntities;
@end
NS_ASSUME_NONNULL_END
//
// PhobosDataManager.m
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import "PhobosDataManager.h"
#import "PhobosSendManager.h"
#import "PhobosConfig.h"
#import "PhobosUtil.h"
#import <MJExtension/MJExtension.h>
#import <mach/mach_time.h>
@implementation PhobosDataManager
static NSManagedObjectContext *PhobosDefaultContext;
static NSUInteger PhobosToBeSendEntitiesCount = 0;
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self setupSDK];
[self resetData];
});
}
+ (NSUInteger)fetchCountOfToBeSendEntities {
return PhobosToBeSendEntitiesCount;
}
+ (NSArray<PhobosSendDataEntity *> *)fetchToBeSendDataEntities {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
return [self fetchDataEntitiesWithPredicate:predicate];
}
+ (NSArray<PhobosSendDataEntity *> *)fetchDataEntitiesWithPredicate:(NSPredicate *)predicate {
__block NSArray *results = nil;
[[self PhobosContext] performBlockAndWait:^{
results = [self noWaitFetchDataEntitiesWithPredicate:predicate];
}];
return results;
}
+ (NSArray<PhobosSendDataEntity *> *)fetchToBeSendDataEntitiesAndUpdateWithSendStatus:(PhobosDataSendStatus)sendStatus {
__block NSArray *results = nil;
[[self PhobosContext] performBlockAndWait:^{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
results = [self noWaitFetchDataEntitiesWithPredicate:predicate];
[self noWaitUpdateEntities:results sendStatus:sendStatus completion:nil];
}];
return results;
}
+ (void)insertData:(NSDictionary *)data sendAPI:(NSString *)sendAPI completion:(MRSaveCompletionHandler)completion {
if (!sendAPI || [sendAPI isEqualToString:@""] || !data) {
return;
}
[[self PhobosContext] performBlockAndWait:^{
PhobosSendDataEntity *entity = [PhobosSendDataEntity MR_createEntityInContext:[self PhobosContext]];
entity.data = [data mj_JSONData];
entity.api = sendAPI;
entity.status = PhobosDataSendStatusToBeSend;
entity.id = mach_absolute_time();
PhobosToBeSendEntitiesCount++;
[self saveWithCompletion:completion];
}];
}
+ (void)updateDataEntities:(NSArray<PhobosSendDataEntity *> *)entities sendStatus:(PhobosDataSendStatus)sendStatus completion:(MRSaveCompletionHandler)completion {
if (entities.count > 0) {
[[self PhobosContext] performBlockAndWait :^{
[self noWaitUpdateEntities:entities sendStatus:sendStatus completion:completion];
}];
}
}
+ (void)deleteAllEntities {
[[self PhobosContext] performBlockAndWait:^{
PhobosToBeSendEntitiesCount = 0;
[PhobosSendDataEntity MR_truncateAllInContext:[self PhobosContext]];
[self saveWithCompletion:nil];
}];
}
#pragma mark - private
/**
* 初始化sdk
*/
+ (void)setupSDK {
// 创建 NSManagedObjectContext,供埋点库访问CoreData库使用
PhobosDefaultContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
// 获取埋点数据的实体
NSURL *modelURL = [bundle URLForResource:@"GMPhobosData" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// 创建持久化存储调度器
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 创建并关联SQLite数据库文件,如果已经存在则不会重复创建
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite",@"GMPhobos"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
// 设置storeCoordinator
[self PhobosContext].persistentStoreCoordinator = coordinator;
}
/*
* 重置数据库中的数据和待发送数量
*/
+ (void)resetData {
[[self PhobosContext] performBlockAndWait:^{
/** 将上次没有获取到发送结果的数据的状态修改为发送失败,待下次重新发送 */
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"status = %d", PhobosDataSendStatusSending];
NSArray<PhobosSendDataEntity *> *entities = [self noWaitFetchDataEntitiesWithPredicate:predicate];
[self noWaitUpdateEntities:entities sendStatus:PhobosDataSendStatusError completion:nil];
/** 将发送成功的数据删除 */
NSPredicate *finishPredicate = [NSPredicate predicateWithFormat:@"status = %d", PhobosDataSendStatusFinish];
[PhobosSendDataEntity MR_deleteAllMatchingPredicate:finishPredicate inContext:[self PhobosContext]];
[self saveWithCompletion:nil];
/** 设置未发送数据数量 */
NSPredicate *countPredicate = [NSPredicate predicateWithFormat:@"status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
PhobosToBeSendEntitiesCount = [[PhobosSendDataEntity MR_numberOfEntitiesWithPredicate:countPredicate inContext:[self PhobosContext]] integerValue];
}];
}
/**
* 在批量修改和删除时维护PhobosToBeSendEntitiesCount字段
*/
+ (void)updatePhobosCountOfToBeSendEntitiesWithStatus:(PhobosDataSendStatus)sendStatus changeCount:(NSUInteger)changeCount {
if (sendStatus == PhobosDataSendStatusSending) {
PhobosToBeSendEntitiesCount -= changeCount;
} else if (sendStatus == PhobosDataSendStatusError) {
PhobosToBeSendEntitiesCount += changeCount;
}
}
/**
* 不使用同步线程安全方案[NSManagedObjectContext performBlockAndWait]获取谓词相应的数据
* 不对外使用,使用该方法时,需要保障线程同步
*/
+ (NSArray<PhobosSendDataEntity *> *)noWaitFetchDataEntitiesWithPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [PhobosSendDataEntity MR_createFetchRequestInContext:[self PhobosContext]];
[request setPredicate:predicate];
NSError *error = nil;
return [[self PhobosContext] executeFetchRequest:request error:&error];
}
/**
* 不使用同步线程安全方案[NSManagedObjectContext performBlockAndWait]去修改实体数据sendStatus
* 不对外使用,使用该方法时,需要保障线程同步
*/
+ (void)noWaitUpdateEntities:(NSArray<PhobosSendDataEntity *> *)entities sendStatus:(PhobosDataSendStatus)sendStatus completion:(MRSaveCompletionHandler)completion {
[self updatePhobosCountOfToBeSendEntitiesWithStatus:sendStatus changeCount:entities.count];
[entities enumerateObjectsUsingBlock:^(PhobosSendDataEntity *obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.status = sendStatus;
}];
[self saveWithCompletion:completion];
}
+ (NSManagedObjectContext *)PhobosContext {
return PhobosDefaultContext ?: [NSManagedObjectContext MR_defaultContext];
}
/**
* 在保存完成后调用的完成块。如果发生错误,块将以“BOOL”和“NSError”实例的形式传递成功状态。总是在主队列上调用。
*/
+ (void)saveWithCompletion:(MRSaveCompletionHandler)completion {
[[self PhobosContext] MR_saveOnlySelfWithCompletion:completion];
}
@end
//
// GMPhobosSqulitModel.h
// GMPhobos
//
// Created by edz on 2020/6/18.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(int, PhobosDataSendStatus) {
PhobosDataSendStatusToBeSend = 1, // 待发送数据
PhobosDataSendStatusSending = 2, // 发送中数据
PhobosDataSendStatusFinish = 3, // 发送完成数据
PhobosDataSendStatusError = 4, // 发送失败数据
};
@interface GMPhobosSqulitModel : NSObject
/// 唯一标识(用于筛选)
@property (nonatomic, strong) NSString *messageId;
/// 埋点类型
@property (nonatomic, strong) NSString *phobosType;
/// 内容
@property (nonatomic, strong) NSString *data;
/// 路径
@property (nonatomic, strong) NSString *api;
/// 数据状态
@property (nonatomic, assign) PhobosDataSendStatus status;
///是否是立刻发的埋点
@property (nonatomic, assign) BOOL immediately;
+ (GMPhobosSqulitModel *)modelOfDict:(NSDictionary *)data
type:(NSString *)phobosType
urlApi:(NSString *)urlapi
immediately:(BOOL)status;
@end
//
// GMPhobosSqulitModel.m
// GMPhobos
//
// Created by edz on 2020/6/18.
//
#import "GMPhobosSqulitModel.h"
#import <mach/mach_time.h>
@implementation GMPhobosSqulitModel
+ (GMPhobosSqulitModel *)modelOfDict:(NSDictionary *)data
type:(NSString *)phobosType
urlApi:(NSString *)urlapi
immediately:(BOOL)status {
GMPhobosSqulitModel *model = [GMPhobosSqulitModel new];
model.data = [GMPhobosSqulitModel dictionaryToJsonString:data];
NSTimeInterval time = [[NSDate date] timeIntervalSince1970] * 1000;
model.messageId = [NSString stringWithFormat:@"%.f*%lld", time, mach_absolute_time()];
model.status = PhobosDataSendStatusToBeSend;
model.phobosType = phobosType;
model.api = urlapi;
model.immediately = status;
return model;
}
+ (NSString *)dictionaryToJsonString:(NSDictionary *)dict
{
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error];
if (error) {
return nil;
}
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
@end
//
// PhobosSendDataEntity+CoreDataClass.h
// GMPhobosThread.h
// GMPhobos
//
// Created by Locus on 2020/3/9.
//
// Created by edz on 2020/6/21.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
NS_ASSUME_NONNULL_BEGIN
@interface PhobosSendDataEntity : NSManagedObject
@interface GMPhobosThread : NSThread
@end
NS_ASSUME_NONNULL_END
#import "PhobosSendDataEntity+CoreDataProperties.h"
//
// GMPhobosThread.m
// GMPhobos
//
// Created by edz on 2020/6/21.
//
#import "GMPhobosThread.h"
@implementation GMPhobosThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
//
// GMSQliteManager.h
// GMSQlite
//
// Created by 卢悦明 on 2020/6/10.
// Copyright © 2020 卢悦明. All rights reserved.
//
#import <Foundation/Foundation.h>
// 数据库中常见的几种类型
#define SQL_TEXT @"TEXT" //文本
#define SQL_INTEGER @"INTEGER" //int long integer ...
#define SQL_REAL @"REAL" //浮点
#define SQL_BLOB @"BLOB" //data
@interface GMSQliteManager : NSObject
#pragma mark - 创建数据库
/// 创建数据库的单利方法
+ (instancetype)sharedSQliteManager;
#pragma mark - 创建表
/// 创建表
/// @param tableName 名称
/// @param modelClass 类对象
- (BOOL)gm_createTable:(NSString *)tableName model:(Class)modelClass;
/// 创建表
/// @param tableName 名称
/// @param dict key 为键, value为存储类型
- (BOOL)gm_createTable:(NSString *)tableName dict:(NSDictionary *)dict;
#pragma mark - 插入操作
///字典插入
- (BOOL)gm_insertTable:(NSString *)tableName dict:(NSDictionary *)dict;
/// 模型插入
- (BOOL)gm_insertTable:(NSString *)tableName model:(NSObject *)model;
/**
批量插入或更改
@param dicOrModelArray 要insert/update数据的数组,也可以将model和dictionary混合装入array
@return 返回的数组存储未插入成功的下标,数组中元素类型为NSNumber
*/
- (NSArray *)gm_insertTable:(NSString *)tableName dicOrModelArray:(NSArray *)dicOrModelArray;
#pragma mark - 删除操作
/// 删除表中某些元素,
/// @param tableName 表的名称
/// @param format 条件语句, 如:@"where name = '小李'"
- (BOOL)gm_deleteTable:(NSString *)tableName whereFormat:(NSString *)format;
///删除表
- (BOOL)gm_deleteTable:(NSString *)tableName;
///清空表
- (BOOL)gm_deleteAllDataFromTable:(NSString *)tableName;
#pragma mark - 修改操作
/// 根据条件更改表中数据
/// @param tableName 名称
/// @param parameters 要更改的数据,可以是model或dictionary(格式:@{@"name":@"张三"})
/// @param format 条件语句, 如:@"where name = '小李'"
- (BOOL)gm_updateTable:(NSString *)tableName
dicOrModel:(id)parameters
whereFormat:(NSString *)format;
#pragma mark - 查找操作
/**
查找: 根据条件查找表中数据
@param tableName 表的名称
@param filterArray 过滤字段
@param cls [Persen class]
@param format 条件语句, 如:@"where name = '小李'",
@return 将结果存入array, @[@{@"age":@"", @"name":@""}]
*/
- (NSArray *)gm_lookupTable:(NSString *)tableName
filterArray:(NSArray *)filterArray
objectClass:(Class)cls
whereFormat:(NSString *)format;
- (NSArray *)gm_lookupTable:(NSString *)tableName
keyArray:(NSArray *)keyArray
whereFormat:(NSString *)format;
#pragma mark - 辅助接口
///是否存在表
- (BOOL)gm_isExistTable:(NSString *)tableName;
/// 表中共有多少条数据
/// @param tableName 表的名称
/// @param format 筛选条件(where name = '小李'), 如果没有条件可以传nil
- (int)gm_tableItemCount:(NSString *)tableName whereFormat:(NSString *)format;
/// 返回表中的字段名
- (NSArray *)gm_columnNameArray:(NSString *)tableName;
/// `关闭数据库
- (void)close;
/// 打开数据库 (每次shareDatabase系列操作时已经open,当调用close后若进行db操作需重新open或调用shareDatabase)
- (void)open;
/**
增加新字段, 在建表后还想新增字段,可以在原建表model或新model中新增对应属性,然后传入即可新增该字段,该操作已在事务中执行
@param tableName 表的名称
@param parameters dictionary格式为@{@"newname":@"TEXT"}
@return 是否成功
*/
- (BOOL)gm_alterTable:(NSString *)tableName keyDict:(NSDictionary *)parameters;
// ============================= 线程安全操作 ===============================
#pragma mark - 线程安全的处理
/**
将操作语句放入block中即可保证线程安全, 如:
Person *p = [[Person alloc] init];
p.name = @"小李";
[db gm_inDatabase:^{
[db gm_insertTable:@"users" dicOrModel:p];
}];
*/
- (void)gm_inDatabase:(void (^)(void))block;
/**
事务: 将操作语句放入block中可执行回滚操作(*rollback = YES;)
Person *p = [[Person alloc] init];
p.name = @"小李";
for (int i=0,i < 1000,i++) {
[db gm_inTransaction:^(BOOL *rollback) {
BOOL flag = [db gm_insertTable:@"users" dicOrModel:p];
if (!flag) {
*rollback = YES; //只要有一次不成功,则进行回滚操作
return;
}
}];
}
*/
- (void)gm_inTransaction:(void(^)(BOOL *rollback))block;
@end
This diff is collapsed.
//
// PhobosDataManager.h
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import <Foundation/Foundation.h>
@interface PhobosDataManager : NSObject
/// 初始化方法(单利)
+ (PhobosDataManager *)sharedPhobosDataManager;
/// 插入数据的方法 (如果返回的是NO,就会直接把数据发送出去,不放到数据控中)
/// @param data 埋点数据dict
/// @param sendAPI 发送的url
/// @param phoboType 埋点类型
/// @param status 是否是立即发送的埋点
- (void)insertData:(NSDictionary *)data
sendAPI:(NSString *)sendAPI
phobosType:(NSString *)phoboType
immediately:(BOOL)status;
- (void)removeAll;
- (int)messageCount;
@end
//
// PhobosDataManager.m
// GMCache
//
// Created by Locus on 2020/1/21.
//
#import "PhobosDataManager.h"
#import "PhobosSendManager.h"
#import "PhobosConfig.h"
#import "PhobosUtil.h"
#import "GMSQliteManager.h"
#import "GMPhobosSqulitModel.h"
#import <MJExtension/MJExtension.h>
#import "GMPhobosThread.h"
#define KtableName @"phobosTable"
@interface PhobosDataManager ()
//数据库操作对象(单利)
@property (nonatomic, strong) GMSQliteManager *squlitManager;
/// 记录没有发送的数据个数(大于50 就要发送数据)
@property (nonatomic, assign) NSInteger messageCont;
/// runloop线程,用来处理埋点的数据
@property (nonatomic, strong) GMPhobosThread *phobosThread;
@end
@implementation PhobosDataManager
static PhobosDataManager *dataManager;
+ (PhobosDataManager *)sharedPhobosDataManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataManager = [[PhobosDataManager alloc] init];
});
return dataManager;
}
- (instancetype)init {
self = [super init];
if (self) {
[self setupSDK];
}
return self;
}
- (void)removeAll {
[self performSelector:@selector(remove) onThread:_phobosThread withObject:nil waitUntilDone:YES];
}
- (void)remove {
bool status = [_squlitManager gm_deleteAllDataFromTable:KtableName];
NSLog(@"清空表格数状态:%d",status);
}
- (int)messageCount {
int count = [_squlitManager gm_tableItemCount:KtableName whereFormat:nil];
NSLog(@"数据总数:%d",count);
return count;
}
#pragma mark - 初始化
- (void)setupSDK {
_squlitManager = [GMSQliteManager sharedSQliteManager];
//创建表格
bool status = [_squlitManager gm_createTable:KtableName model:[GMPhobosSqulitModel class]];
//初始化线程和runloop
__weak typeof(self) weakSelf = self;
_phobosThread = [[GMPhobosThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
while (weakSelf) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
_phobosThread.name = @"phobosThread";
[_phobosThread start];
//修改数据
[self performSelector:@selector(resetData) onThread:_phobosThread withObject:nil waitUntilDone:NO];
}
#pragma mark - 方法只用在APP启动的时候
- (void)resetData {
/** 将上次没有获取到发送结果的数据的状态修改为发送失败,待下次重新发送 */
NSString *whereStr = [NSString stringWithFormat:@"where status = %d", PhobosDataSendStatusSending];
[_squlitManager gm_updateTable:KtableName dicOrModel:@{@"status":@(PhobosDataSendStatusError)} whereFormat:whereStr];
/** 将发送成功的数据删除 */
NSString *whereDelet = [NSString stringWithFormat:@"where status = %d", PhobosDataSendStatusFinish];
[_squlitManager gm_deleteTable:KtableName whereFormat:whereDelet];
/** 设置未发送数据数量 */
NSString *whereCount = [NSString stringWithFormat:@"where status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
_messageCont = [_squlitManager gm_tableItemCount:KtableName whereFormat:whereCount];
}
#pragma mark - 插入数据
- (void)insertData:(NSDictionary *)data
sendAPI:(NSString *)sendAPI
phobosType:(NSString *)phoboType
immediately:(BOOL)status {
if (!sendAPI || [sendAPI isEqualToString:@""] || !data) return;
GMPhobosSqulitModel *squlitModel = [GMPhobosSqulitModel modelOfDict:data type:phoboType urlApi:sendAPI immediately:status];
[self performSelector:@selector(insertModel:) onThread:_phobosThread withObject:squlitModel waitUntilDone:NO];
}
- (void)insertModel:(GMPhobosSqulitModel *)squlitModel {
BOOL insertType = [_squlitManager gm_insertTable:KtableName model:squlitModel];
if (!insertType) {//如果插入失败,就直接发送给服务器
[self sendModelArray:@[squlitModel]];
} else if (squlitModel.immediately) {//需要立刻发送的埋点
[self disposeSendDataWithImmediately:YES];
} else {//插入到数据中
self.messageCont++;
[self disposeSendDataWithImmediately:NO];
}
NSLog(@"插入数状态%d count:%d", insertType,_messageCont);
}
#pragma mark - 获取待发送数据并修改状态为“发送”
- (NSArray *)fetchToBeSendDataEntitiesAndUpdateWithSendStatus {
NSString *where = [NSString stringWithFormat:@"where status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
/**获取待发送的数据*/
NSArray *array = [_squlitManager gm_lookupTable:KtableName filterArray:nil objectClass:[GMPhobosSqulitModel class] whereFormat:where];
/**将待发送的数据修改为“发送中”*/
[_squlitManager gm_updateTable:KtableName dicOrModel:@{@"status":@(PhobosDataSendStatusSending)} whereFormat:where];
return array;
}
#pragma mark - 是否需要发送数据
- (void)disposeSendDataWithImmediately:(BOOL)status {
NSString *where = [NSString stringWithFormat:@"where status = %d or status = %d", PhobosDataSendStatusToBeSend, PhobosDataSendStatusError];
_messageCont = [_squlitManager gm_tableItemCount:KtableName whereFormat:where];
if (status || _messageCont >= PhobosShardCount) {
NSArray *array = [self fetchToBeSendDataEntitiesAndUpdateWithSendStatus];
NSMutableArray *entities = [GMPhobosSqulitModel mj_objectArrayWithKeyValuesArray:array];
[self sendModelArray:entities];
}
}
- (void)sendModelArray:(NSArray<GMPhobosSqulitModel *> *)entities {
NSLog(@"发送*******%lld", entities.count);
[PhobosSendManager sendDataWithEntities:entities completion:^(NSArray<GMPhobosSqulitModel *> *finishEntities, NSInteger code) {
SEL selelct = code == 200 ? @selector(sendSuccess:) :@selector(sendError:);
[self performSelector:selelct onThread:_phobosThread withObject:finishEntities waitUntilDone:NO];
}];
}
#pragma mark - 上报数据成功
- (void)sendSuccess:(NSArray<GMPhobosSqulitModel*> *)entities {
for (GMPhobosSqulitModel *model in entities) {
NSString *whereStr = [NSString stringWithFormat:@"where messageId = '%@'", model.messageId];
bool status = [_squlitManager gm_deleteTable:KtableName whereFormat:whereStr];
NSLog(@"删除状态:%d", status);
}
}
#pragma mark - 上报数据失败
- (void)sendError:(NSArray<GMPhobosSqulitModel*> *)entities {
for (GMPhobosSqulitModel *model in entities) {
NSString *whereStr = [NSString stringWithFormat:@"where messageId = '%@'", model.messageId];
bool status = [_squlitManager gm_updateTable:KtableName
dicOrModel: @{@"status":@(PhobosDataSendStatusError)}
whereFormat:whereStr];
NSLog(@"发送失败后修改状态:%d",status);
}
}
@end
......@@ -6,7 +6,7 @@
//
#import <Foundation/Foundation.h>
#import "PhobosDataManager.h"
#import "GMPhobosSqulitModel.h"
NS_ASSUME_NONNULL_BEGIN
......@@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
/**
* 发送数据
*/
+ (void)sendDataWithEntities:(NSArray<PhobosSendDataEntity *> *)needSendData completion:(nullable void(^)(NSArray<PhobosSendDataEntity *> *finishEntities, NSInteger code))completion;
+ (void)sendDataWithEntities:(NSArray<GMPhobosSqulitModel *> *)needSendData completion:(nullable void(^)(NSArray<GMPhobosSqulitModel *> *finishEntities, NSInteger code))completion;
@end
......
......@@ -40,24 +40,27 @@
}];
}
NSString *exposureAPI = [GMExposureManager sharedManager].uploadExposureAPI;
NSArray *exposureArray = [GMCache fetchObjectAtDocumentPathWithkey:[PhobosUtil MD5String:exposureAPI]];
if (exposureArray.count > 0) {
/** 获取非灰度下发送失败的埋点,进行发送 */
[self sendDataArray:exposureArray currentAPI:exposureAPI success:^(NSInteger code) {
if (code = 200) {
[GMCache removeObjectAtDocumentPathWithkey:[PhobosUtil MD5String:exposureAPI]];
}
}];
if (exposureAPI) {
NSString *str = [PhobosUtil MD5String:exposureAPI];
NSArray *exposureArray = [GMCache fetchObjectAtDocumentPathWithkey:str];
if (exposureArray.count > 0) {
/** 获取非灰度下发送失败的埋点,进行发送 */
[self sendDataArray:exposureArray currentAPI:exposureAPI success:^(NSInteger code) {
if (code == 200) {
[GMCache removeObjectAtDocumentPathWithkey:[PhobosUtil MD5String:exposureAPI]];
}
}];
}
}
}
+ (void)sendDataWithEntities:(NSArray<PhobosSendDataEntity *> *)sendDataEntities completion:(void (^)(NSArray<PhobosSendDataEntity *> * _Nonnull, NSInteger))completion {
+ (void)sendDataWithEntities:(NSArray<GMPhobosSqulitModel *> *)sendDataEntities completion:(void (^)(NSArray<GMPhobosSqulitModel *> * _Nonnull, NSInteger))completion {
if (sendDataEntities.count == 0) {
return;
} else if (sendDataEntities.count > PhobosMaxSendCount) {
/** 如果数据超过 PhobosMaxSendCount 数据规模,进行拆分,分批发送,默认100条 */
NSMutableArray *temp = [NSMutableArray arrayWithCapacity:PhobosMaxSendCount];
[sendDataEntities enumerateObjectsUsingBlock:^(PhobosSendDataEntity * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[sendDataEntities enumerateObjectsUsingBlock:^(GMPhobosSqulitModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (temp.count >= PhobosMaxSendCount) {
// 会调用当前方法,但数据规模不会满足 > PhobosMaxSendCount,不会让数据走到下面的发送,拆分完成 会执行return
[self sendDataWithEntities:temp completion:completion];
......@@ -69,16 +72,16 @@
}
NSMutableDictionary *sendDataMap = [NSMutableDictionary new];
// 将相同api的数据合并
[sendDataEntities enumerateObjectsUsingBlock:^(PhobosSendDataEntity *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[sendDataEntities enumerateObjectsUsingBlock:^(GMPhobosSqulitModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSMutableArray *sendData = sendDataMap[obj.api] ?: [NSMutableArray new];
[sendData addObject:obj];
[sendDataMap setValue:sendData forKey:obj.api];
}];
// 将实体转为待发送数据,取出PhobosSendDataEntity中的data,并发送
[sendDataMap enumerateKeysAndObjectsUsingBlock:^(NSString *api, NSArray<PhobosSendDataEntity *> *entities, BOOL * _Nonnull stop) {
[sendDataMap enumerateKeysAndObjectsUsingBlock:^(NSString *api, NSArray<GMPhobosSqulitModel *> *entities, BOOL * _Nonnull stop) {
NSMutableArray *sendDatas = [NSMutableArray new];
[entities enumerateObjectsUsingBlock:^(PhobosSendDataEntity *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[entities enumerateObjectsUsingBlock:^(GMPhobosSqulitModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {
id data = [obj.data mj_JSONObject];
if (data) {
[sendDatas addObject:data];
......
//
// PhobosSendDataEntity+CoreDataClass.m
// GMPhobos
//
// Created by Locus on 2020/3/9.
//
//
#import "PhobosSendDataEntity+CoreDataClass.h"
@implementation PhobosSendDataEntity
@end
//
// PhobosSendDataEntity+CoreDataProperties.h
// GMPhobos
//
// Created by Locus on 2020/3/9.
//
//
#import "PhobosSendDataEntity+CoreDataClass.h"
NS_ASSUME_NONNULL_BEGIN
@interface PhobosSendDataEntity (CoreDataProperties)
+ (NSFetchRequest<PhobosSendDataEntity *> *)fetchRequest;
@property (nonatomic) int64_t id;
@property (nullable, nonatomic, retain) NSData *data;
@property (nullable, nonatomic, copy) NSString *api;
@property (nonatomic) int16_t status;
@end
NS_ASSUME_NONNULL_END
//
// PhobosSendDataEntity+CoreDataProperties.m
// GMPhobos
//
// Created by Locus on 2020/3/9.
//
//
#import "PhobosSendDataEntity+CoreDataProperties.h"
@implementation PhobosSendDataEntity (CoreDataProperties)
+ (NSFetchRequest<PhobosSendDataEntity *> *)fetchRequest {
return [NSFetchRequest fetchRequestWithEntityName:@"PhobosSendDataEntity"];
}
@dynamic id;
@dynamic data;
@dynamic api;
@dynamic status;
@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