Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
GMPhobos
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gengmeiios
GMPhobos
Commits
7e581998
Commit
7e581998
authored
May 09, 2020
by
艾娇平
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add Exposure
parent
a99eb119
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
1909 additions
and
7 deletions
+1909
-7
GMExposureManager.h
GMPhobos/Classes/GMExposureManager.h
+134
-0
GMExposureManager.m
GMPhobos/Classes/GMExposureManager.m
+608
-0
GMExposureModel.h
GMPhobos/Classes/GMExposureModel.h
+38
-0
GMExposureModel.m
GMPhobos/Classes/GMExposureModel.m
+20
-0
GMExposureProtocol.h
GMPhobos/Classes/GMExposureProtocol.h
+59
-0
NSObject+Swizzle.h
GMPhobos/Classes/NSObject+Swizzle.h
+128
-0
NSObject+Swizzle.m
GMPhobos/Classes/NSObject+Swizzle.m
+387
-0
NSObject+Tracker.h
GMPhobos/Classes/NSObject+Tracker.h
+16
-0
NSObject+Tracker.m
GMPhobos/Classes/NSObject+Tracker.m
+22
-0
PhobosSendManager.m
GMPhobos/Classes/PhobosSendManager.m
+2
-7
UIScrollView+Tracker.h
GMPhobos/Classes/UIScrollView+Tracker.h
+29
-0
UIScrollView+Tracker.m
GMPhobos/Classes/UIScrollView+Tracker.m
+86
-0
UIView+Tracker.h
GMPhobos/Classes/UIView+Tracker.h
+30
-0
UIView+Tracker.m
GMPhobos/Classes/UIView+Tracker.m
+207
-0
UIViewController+Tracker.h
GMPhobos/Classes/UIViewController+Tracker.h
+13
-0
UIViewController+Tracker.m
GMPhobos/Classes/UIViewController+Tracker.m
+130
-0
No files found.
GMPhobos/Classes/GMExposureManager.h
0 → 100644
View file @
7e581998
//
// GMExposureManager.h
// Gengmei
//
// Created by Mikasa on 2019/6/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "UIView+Tracker.h"
#import "UIScrollView+Tracker.h"
#import "UIViewController+Tracker.h"
#import "NSObject+Tracker.h"
/**
* 精准曝光
* 开始曝光时机:1、页面出现 2、退到后台
*/
typedef
NS_ENUM
(
NSInteger
,
GMExpoUploadMode
)
{
GMExpoUploadModeImmediate
=
0
,
// 立即上报
GMExpoUploadModePage
=
1
,
// 按页面上报
GMExpoUploadModeApp
=
2
// 按App上报
};
// 精准曝光数据上报模式
typedef
NS_ENUM
(
NSInteger
,
GMViewTrackerType
)
{
GMViewTrackerTypeUIViewSetHidden
=
0
,
GMViewTrackerTypeUIViewSetAlpha
=
1
,
GMViewTrackerTypeUIViewDidMoveToWindow
=
2
,
GMViewTrackerTypeUIScrollContentOffset
=
3
,
GMViewTrackerTypeUIScrollDragging
=
4
,
GMViewTrackerTypeUIViewSetExposure
=
5
,
GMViewTrackerTypeUIViewEnterForeground
=
6
,
GMViewTrackerTypeUIViewDidAppear
=
7
,
GMViewTrackerTypeUIViewDidDisappear
=
8
,
GMViewTrackerTypeUIViewWillResignActive
=
9
};
// 记录方式
@interface
GMExposureManager
:
NSObject
/// 设置上报数据地址
@property
(
nonatomic
,
copy
)
NSString
*
uploadExposureAPI
;
/**
* 精准曝光数据上报模式
* 默认为 GMExpoUploadModePage
*/
@property
(
nonatomic
,
assign
)
GMExpoUploadMode
uploadMode
;
/** view出现的屏幕的占比 默认0.01f */
@property
(
nonatomic
,
assign
)
CGFloat
exposureDimThreshold
;
// view visble in size > exposureDimThreshold
/** 曝光最少时长 默认0.01s (目前暂未使用)*/
@property
(
nonatomic
,
assign
)
NSTimeInterval
exposureMinDuration
;
// view duration > exposureMinDuration
@property
(
nonatomic
,
assign
)
BOOL
inBackground
;
// 当前App是否进入后台
@property
(
nonatomic
,
assign
)
BOOL
ignoreTrackModel
;
// 是否需要忽略滚动模式
/**
GMExposureManager
@return sharedManager
*/
+
(
instancetype
)
sharedManager
;
#pragma mark - Deal Tracker
/**
* 结束所有页面的曝光并上报
* 会先将所有页面上正在曝光的View置为end,再进行数据上传
*/
-
(
void
)
endExpoTrcakerForAllPage
;
/**
* 结束指定页面的曝光并上报
* 会先将指定页面上正在曝光的View置为end,再进行数据上传
* 多用于下拉刷新前需要曝光当前的vc的数据
@param pageCtrl 指定页面
*/
-
(
void
)
endExpoTrcakerForPageCtrl
:(
UIViewController
*
)
pageCtrl
;
#pragma mark - Deal Data
/**
上传指定页面的曝光数据
@param pageName 指定页面
*/
-
(
void
)
uploadExposureDataForPage
:(
UIViewController
*
)
pageCtrl
;
/**
上传所有页面的曝光数据
*/
-
(
void
)
uploadAllExposureData
;
// 存储曝光数据
-
(
void
)
startStoreExposureData
;
/**
清理指定页面的曝光数据
@param pageName 指定页面
*/
-
(
void
)
clearExposureDataForPage
:(
UIViewController
*
)
pageCtrl
;
// 清理当前数据
-
(
void
)
clearAllExposureData
;
/**
当前View上是否有需要精准曝光的数据
@param view View
@return yes/no
*/
+
(
BOOL
)
haveExposureForView
:(
UIView
*
)
view
;
#pragma mark - View Visible/UnVisibel
/**
获取当前View的可见状态
@param view view
@param 可见状态
*/
+
(
BOOL
)
isViewVisible
:(
UIView
*
)
view
;
/**
设置当前view是否可见状态
@param view view
@param recursive 是否递归子view
@param ignoreTracking 是否忽略手势滚动时追踪
*/
+
(
void
)
fetchViewForVisibleState
:(
UIView
*
)
view
trackerType
:(
GMViewTrackerType
)
trackerType
recursive
:(
BOOL
)
recursive
;
/** 设置view的曝光状态
* view 可见 && 状态none或者end——> 开始曝光
* view 可见 && 状态start——> 正在曝光,不做处理
* view 不可见 && 状态start ——> 结束曝光
* view 不可见 && 状态none或者end ——> 不做处理
*/
-
(
void
)
exposureStatusForView
:(
UIView
*
)
view
inPageCtrl
:(
UIViewController
*
)
inPageCtrl
;
@end
GMPhobos/Classes/GMExposureManager.m
0 → 100644
View file @
7e581998
//
// GMExposureManager.m
// Gengmei
//
// Created by Mikasa on 2019/6/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMExposureManager.h"
#import "GMExposureModel.h"
#import "UIResponder+PhobosPV.h"
#import "Phobos.h"
#import "PhobosUtil.h"
#import <MJExtension/MJExtension.h>
@interface
GMExposureManager
()
/// 曝光页面
@property
(
nonatomic
,
strong
)
NSMutableArray
*
pageArrayM
;
@property
(
nonatomic
,
strong
)
NSMutableDictionary
*
exposureDictM
;
// 存储开始进入曝光的数据
@property
(
nonatomic
,
strong
)
NSMutableDictionary
*
exposureViewDictM
;
// 存储开始进入曝光的View
@property
(
nonatomic
,
strong
)
NSMutableDictionary
*
exposurePageDictM
;
//存储页面上已经曝光过的View
@end
@implementation
GMExposureManager
#pragma mark - init Method
+
(
instancetype
)
sharedManager
{
static
dispatch_once_t
onceToken
;
static
id
_instance
;
dispatch_once
(
&
onceToken
,
^
{
_instance
=
[[
GMExposureManager
alloc
]
init
];
});
return
_instance
;
}
-
(
instancetype
)
init
{
if
(
self
=
[
super
init
])
{
_pageArrayM
=
[
NSMutableArray
array
];
_exposureDictM
=
[
NSMutableDictionary
dictionary
];
_exposurePageDictM
=
[
NSMutableDictionary
dictionary
];
_exposureViewDictM
=
[
NSMutableDictionary
dictionary
];
_uploadMode
=
GMExpoUploadModePage
;
_exposureDimThreshold
=
0
.
01
f
;
_exposureMinDuration
=
0
.
01
f
;
[
self
addObserverMethod
];
}
return
self
;
}
#pragma mark - Observer Method
-
(
void
)
addObserverMethod
{
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
handleNotificaion
:
)
name
:
UIApplicationDidEnterBackgroundNotification
object
:
nil
];
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
handleNotificaion
:
)
name
:
UIApplicationWillEnterForegroundNotification
object
:
nil
];
}
-
(
void
)
removeObserverMethod
{
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
name
:
UIApplicationDidEnterBackgroundNotification
object
:
nil
];
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
name
:
UIApplicationWillEnterForegroundNotification
object
:
nil
];
}
// 处理通知事件
-
(
void
)
handleNotificaion
:
(
NSNotification
*
)
notific
{
NSString
*
notifName
=
notific
.
name
;
// 进入后台 结束曝光 + 上报所有数据
if
([
notifName
isEqualToString
:
UIApplicationDidEnterBackgroundNotification
]
)
{
[
GMExposureManager
sharedManager
].
inBackground
=
YES
;
// 同步所有页面数据并上传
[[
GMExposureManager
sharedManager
]
endExpoTrcakerForAllPage
];
}
// 回到前台 记录数据
if
([
notifName
isEqualToString
:
UIApplicationWillEnterForegroundNotification
]
)
{
[
GMExposureManager
sharedManager
].
inBackground
=
NO
;
// 取出临时存储的需要曝光的页面,进行曝光
for
(
NSString
*
pageKey
in
self
.
pageArrayM
)
{
UIViewController
*
pageCtrl
=
[
GMExposureManager
expoPageCtrl
:
pageKey
];
if
(
pageCtrl
&&
[
pageCtrl
isKindOfClass
:[
UIViewController
class
]])
{
[
GMExposureManager
fetchViewForVisibleState
:
pageCtrl
.
view
trackerType
:
GMViewTrackerTypeUIViewEnterForeground
recursive
:
YES
];
}
}
[
self
.
pageArrayM
removeAllObjects
];
}
}
#pragma mark - Tracker Method
// 结束所有页面的曝光并上报
-
(
void
)
endExpoTrcakerForAllPage
{
// 将所有正在曝光的View的状态变更为结束曝光
for
(
UIView
*
view
in
self
.
exposureViewDictM
.
allValues
)
{
view
.
expoStatus
=
GMViewExpoStatusEnd
;
}
[
self
.
exposureViewDictM
removeAllObjects
];
for
(
NSString
*
viewKey
in
self
.
exposureDictM
.
allKeys
)
{
// 将正在曝光的数据直接更改状态为结束曝光
NSDictionary
*
dict
=
self
.
exposureDictM
[
viewKey
];
NSString
*
page
=
[[
viewKey
componentsSeparatedByString
:
@"/"
]
firstObject
];
UIViewController
*
pageCtrl
=
[
GMExposureManager
expoPageCtrl
:
page
];
[
self
storeExposureData
:
dict
pageCtrl
:
pageCtrl
];
}
[
self
.
exposureDictM
removeAllObjects
];
// 将之前曝光的页面临时存储起来,确保下次从后台回来页面会立即开启曝光
[
self
.
pageArrayM
addObjectsFromArray
:
self
.
exposurePageDictM
.
allKeys
];
[
self
uploadAllExposureData
];
}
/**
1.结束指定页面的曝光并上报
2.多用于下拉刷新前需要曝光当前的vc的数据
@param pageCtrl 指定页面
*/
-
(
void
)
endExpoTrcakerForPageCtrl
:
(
UIViewController
*
)
pageCtrl
{
// 在结束之前获取一次
[
GMExposureManager
fetchViewForVisibleState
:
pageCtrl
.
view
trackerType
:
GMViewTrackerTypeUIViewDidDisappear
recursive
:
YES
];
NSString
*
page
=
[
NSString
stringWithFormat
:
@"%p"
,
pageCtrl
];
// View
NSMutableDictionary
*
dictViewM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureViewDictM
];
for
(
NSString
*
viewKey
in
self
.
exposureViewDictM
.
allKeys
)
{
if
([
viewKey
hasPrefix
:
page
])
{
UIView
*
view
=
self
.
exposureViewDictM
[
viewKey
];
view
.
expoStatus
=
GMViewExpoStatusEnd
;
[
dictViewM
removeObjectForKey
:
viewKey
];
}
}
self
.
exposureViewDictM
=
dictViewM
;
// data
NSMutableDictionary
*
dictDataM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureDictM
];
for
(
NSString
*
viewKey
in
self
.
exposureDictM
.
allKeys
)
{
if
([
viewKey
hasPrefix
:
page
])
{
NSDictionary
*
dict
=
self
.
exposureDictM
[
viewKey
];
[
self
storeExposureData
:
dict
pageCtrl
:
pageCtrl
];
[
dictDataM
removeObjectForKey
:
viewKey
];
}
}
self
.
exposureDictM
=
dictDataM
;
[
self
uploadExposureDataForPage
:
pageCtrl
];;
}
#pragma mark - Deal Data
// 存储曝光数据
-
(
void
)
startStoreExposureData
{
// 当前曝光View 上数据曝光情况
NSMutableDictionary
*
dictDataM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureDictM
];
for
(
NSString
*
key
in
dictDataM
.
allKeys
)
{
UIView
*
exposureView
=
self
.
exposureViewDictM
[
key
];
// 当前view已经结束曝光 记录曝光数据---等待上传
if
(
!
exposureView
)
{
NSString
*
page
=
[[
key
componentsSeparatedByString
:
@"/"
]
firstObject
];
UIViewController
*
pageCtrl
=
[
GMExposureManager
expoPageCtrl
:
page
];
[
self
storeExposureData
:
dictDataM
[
key
]
pageCtrl
:
pageCtrl
];
[
self
.
exposureDictM
removeObjectForKey
:
key
];
}
}
// 当前曝光数据是否仍在曝光-> 无存储曝光数据
NSMutableDictionary
*
dictViewM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureViewDictM
];
for
(
NSString
*
key
in
dictViewM
.
allKeys
)
{
NSDictionary
*
dataDict
=
self
.
exposureDictM
[
key
];
if
(
!
dataDict
)
{
UIView
*
view
=
dictViewM
[
key
];
[
self
storeStartExposureData
:
view
pageCtrl
:
view
.
pageCtrl
];
}
}
}
// 存储开始曝光数据
-
(
void
)
storeStartExposureData
:
(
UIView
*
)
startView
pageCtrl
:
(
UIViewController
*
)
pageCtrl
{
NSMutableDictionary
*
exposureDictM
=
[
GMExposureManager
sharedManager
].
exposureDictM
;
startView
.
inTime
=
[
PhobosUtil
currentTime
];
startView
.
expoStatus
=
GMViewExpoStatusStart
;
NSMutableDictionary
*
dict
=
[
NSMutableDictionary
dictionaryWithDictionary
:
startView
.
exposure
];
// 安卓没有这个功能 暂时屏蔽
//[dict setObject:SafeString([PhobosUtil currentTime]) forKey:@"inTime"];
// 获取卡片位置
[
dict
setObject
:
startView
.
relative_position
?:
@""
forKey
:
@"relative_position"
];
[
dict
setObject
:
startView
.
absolute_position
?:
@""
forKey
:
@"absolute_position"
];
if
(
startView
.
extra_param
&&
startView
.
extra_param
.
allKeys
.
count
>
0
)
{
[
dict
addEntriesFromDictionary
:
startView
.
extra_param
];
}
[
exposureDictM
setObject
:
dict
forKey
:[
GMExposureManager
exposureKeyForData
:
startView
]];
}
/**
按指定页面存储曝光数据
@param view 曝光数据
@param pageName 指定页面
*/
-
(
void
)
storeExposureData
:
(
NSDictionary
*
)
expoDict
pageCtrl
:
(
UIViewController
*
)
pageCtrl
{
// 卡片曝光持续时间超出曝光最少时长才进行记录
/*
NSInteger inTime = [expoDict[@"inTime"] integerValue];
NSInteger duration = [view.outTime integerValue] - [view.inTime integerValue];
if (!duration) {
return;
}*/
// 将曝光数据所需要的某些参数从Page中取出
NSString
*
pageKey
=
[
GMExposureManager
exposureKeyForPage
:
pageCtrl
];
GMExposureItemModel
*
itemModel
=
[
self
.
exposurePageDictM
objectForKey
:
pageKey
];
if
(
!
itemModel
)
{
itemModel
=
[
GMExposureItemModel
new
];
}
NSMutableDictionary
*
dict
=
[
NSMutableDictionary
dictionaryWithDictionary
:
expoDict
];
/* 安卓没有此功能 暂屏蔽
if (![dict[@"outTime"] isNonEmpty]) {
[dict setObject:SafeString([PhobosUtil currentTime]) forKey:@"outTime"];
}*/
[
itemModel
.
exposure_cards
addObject
:
dict
];
[
self
.
exposurePageDictM
setObject
:
itemModel
forKey
:
pageKey
];
}
// 同步Item数据
-
(
void
)
synchronizeViewItemData
:
(
UIView
*
)
view
{
NSMutableDictionary
*
dict
=
[
self
.
exposureDictM
objectForKey
:[
GMExposureManager
exposureKeyForData
:
view
]];
if
(
dict
)
{
if
(
!
[
PhobosUtil
isNonEmpty
:
dict
[
@"relative_position"
]]
||
!
[
PhobosUtil
isNonEmpty
:
dict
[
@"absolute_position"
]])
{
[
view
fechExposurePositionForView
];
[
dict
setObject
:
view
.
relative_position
?:
@""
forKey
:
@"relative_position"
];
[
dict
setObject
:
view
.
absolute_position
?:
@""
forKey
:
@"absolute_position"
];
if
(
view
.
extra_param
&&
view
.
extra_param
.
allKeys
.
count
>
0
)
{
[
dict
addEntriesFromDictionary
:
view
.
extra_param
];
}
[
self
.
exposureDictM
setObject
:
dict
forKey
:[
GMExposureManager
exposureKeyForData
:
view
]];
}
}
}
// 进行数据同步
-
(
void
)
synchronizeUploadItem
:
(
GMExposureItemModel
*
)
itemModel
withViewPageData
:
(
UIViewController
*
)
pageCtrl
{
if
(
!
itemModel
||
!
pageCtrl
)
{
return
;
}
itemModel
.
tab_name
=
pageCtrl
.
tabName
?:
@""
;
itemModel
.
page_name
=
pageCtrl
.
pageName
?:
@""
;
itemModel
.
business_id
=
pageCtrl
.
businessId
?:
@""
;
itemModel
.
referrer
=
pageCtrl
.
referer
?:
@""
;
itemModel
.
referrerId
=
pageCtrl
.
referrerId
?:
@""
;
itemModel
.
up_slide_times
=
pageCtrl
.
up_slide_times
;
itemModel
.
down_slide_times
=
pageCtrl
.
down_slide_times
;
itemModel
.
up_loading_times
=
pageCtrl
.
up_loading_times
;
itemModel
.
down_loading_times
=
pageCtrl
.
down_loading_times
;
[
itemModel
.
exposure_cards
sortUsingComparator
:
^
NSComparisonResult
(
NSDictionary
*
obj1
,
NSDictionary
*
obj2
){
return
[
obj1
[
@"absolute_position"
]
integerValue
]
>
[
obj2
[
@"absolute_position"
]
integerValue
];
}];
NSDictionary
*
dic
=
[
itemModel
mj_keyValues
];
NSMutableDictionary
*
dicM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
dic
];
[
dicM
addEntriesFromDictionary
:
pageCtrl
.
extra_param
];
[
Phobos
track
:
@"page_precise_exposure"
attributes
:
dicM
sendNow
:
YES
currentAPI
:
self
.
uploadExposureAPI
];
}
/**
上传指定页面的曝光数据
@param pageName 指定页面
*/
-
(
void
)
uploadExposureDataForPage
:
(
UIViewController
*
)
pageCtrl
{
GMExposureItemModel
*
itemModel
=
[
self
.
exposurePageDictM
objectForKey
:[
GMExposureManager
exposureKeyForPage
:
pageCtrl
]];
if
(
!
itemModel
.
exposure_cards
||
itemModel
.
exposure_cards
.
count
==
0
)
{
return
;
}
[
self
synchronizeUploadItem
:
itemModel
withViewPageData
:
pageCtrl
];
// 上传成功后进行数据清理
[
self
clearExposureDataForPage
:
pageCtrl
];
}
/**
上传所有页面的曝光数据
*/
-
(
void
)
uploadAllExposureData
{
// 同步所有页面数据并上传
__weak
__typeof
(
self
)
weakSelf
=
self
;
NSDictionary
*
pageDictM
=
[
GMExposureManager
sharedManager
].
exposurePageDictM
;
[
pageDictM
enumerateKeysAndObjectsUsingBlock
:
^
(
id
_Nonnull
key
,
id
_Nonnull
obj
,
BOOL
*
_Nonnull
stop
)
{
NSString
*
pageName
=
key
;
UIViewController
*
pageCtrl
=
[
GMExposureManager
expoPageCtrl
:
pageName
];
[
weakSelf
uploadExposureDataForPage
:
pageCtrl
];
}];
[
self
clearAllExposureData
];
}
/**
清理指定页面的曝光数据
@param pageName 指定页面
*/
-
(
void
)
clearExposureDataForPage
:
(
UIViewController
*
)
pageCtrl
{
pageCtrl
.
up_loading_times
=
0
;
pageCtrl
.
down_loading_times
=
0
;
pageCtrl
.
up_slide_times
=
0
;
pageCtrl
.
down_slide_times
=
0
;
pageCtrl
.
extra_param
=
[
NSDictionary
dictionary
];
NSString
*
pageName
=
[
GMExposureManager
exposureKeyForPage
:
pageCtrl
];
// 清理指定页面存储view
NSMutableDictionary
*
dictViewM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureViewDictM
];
for
(
NSString
*
viewKey
in
self
.
exposureViewDictM
.
allKeys
)
{
if
([
viewKey
hasPrefix
:
pageName
])
{
[
dictViewM
removeObjectForKey
:
viewKey
];
}
}
self
.
exposureViewDictM
=
dictViewM
;
// 清理指定页面的数据
NSMutableDictionary
*
dicDataM
=
[
NSMutableDictionary
dictionaryWithDictionary
:
self
.
exposureDictM
];
for
(
NSString
*
viewKey
in
self
.
exposureDictM
.
allKeys
)
{
if
([
viewKey
hasPrefix
:
pageName
])
{
[
dicDataM
removeObjectForKey
:
viewKey
];
}
}
[
GMExposureManager
sharedManager
].
exposureDictM
=
dicDataM
;
[[
GMExposureManager
sharedManager
].
exposurePageDictM
removeObjectForKey
:
pageName
];
}
/**
清理所有页面的曝光数据
*/
-
(
void
)
clearAllExposureData
{
[[
GMExposureManager
sharedManager
].
exposurePageDictM
removeAllObjects
];
[[
GMExposureManager
sharedManager
].
exposureDictM
removeAllObjects
];
[[
GMExposureManager
sharedManager
].
exposureViewDictM
removeAllObjects
];
}
#pragma mark - Verify ViewExposure
/**
当前View上是否有需要精准曝光的数据
@param view View
@return yes/no
*/
+
(
BOOL
)
haveExposureForView
:
(
UIView
*
)
view
{
if
(
view
.
exposure
&&
view
.
exposure
.
allKeys
.
count
>
0
)
{
return
YES
;
}
return
NO
;
}
/**
是否是需要精准曝光的View
@param view view
@return yes/no
*/
+
(
BOOL
)
isTargetViewForExposure
:
(
UIView
*
)
view
{
// NSLog(@"%@--%@",view,view.pageCtrl);
return
view
.
pageCtrl
.
needExpo
;
}
/**
是否需要忽略UITrackingRunLoopMode模式下的记录
@param ignoreTracking 是否需要忽略
@return 模式下的记录
*/
+
(
BOOL
)
isNeedIgnoreTrackExposure
:
(
BOOL
)
ignoreTracking
{
NSRunLoopMode
mode
=
[
NSRunLoop
currentRunLoop
].
currentMode
;
if
(
ignoreTracking
&&
[
mode
isEqualToString
:
UITrackingRunLoopMode
])
{
return
YES
;
}
return
NO
;
}
/**
设置当前view是否可见状态
@param view view
@param recursive 是否递归子view
*/
+
(
void
)
fetchViewForVisibleState
:
(
UIView
*
)
view
trackerType
:
(
GMViewTrackerType
)
trackerType
recursive
:
(
BOOL
)
recursive
{
BOOL
ignoreTracking
=
(
trackerType
==
GMViewTrackerTypeUIScrollDragging
||
trackerType
==
GMViewTrackerTypeUIViewEnterForeground
||
trackerType
==
GMViewTrackerTypeUIViewDidAppear
||
trackerType
==
GMViewTrackerTypeUIViewDidDisappear
||
trackerType
==
GMViewTrackerTypeUIViewWillResignActive
)?
NO
:
YES
;
if
([
GMExposureManager
haveExposureForView
:
view
]
&&
[
GMExposureManager
isTargetViewForExposure
:
view
])
{
view
.
visibleView
=
[
self
isViewVisible
:
view
];
[
view
fechExposurePositionForView
];
if
(
!
[
GMExposureManager
isNeedIgnoreTrackExposure
:
ignoreTracking
])
{
/*
if (trackerType == GMViewTrackerTypeUIViewSetExposure &&
![GMExposureManager sharedManager].inBackground) {
view.visibleView = YES;
}*/
[[
GMExposureManager
sharedManager
]
exposureStatusForView
:
view
inPageCtrl
:
view
.
pageCtrl
];
}
else
{
[[
GMExposureManager
sharedManager
]
synchronizeViewItemData
:
view
];
}
}
if
(
recursive
)
{
for
(
UIView
*
subview
in
view
.
subviews
)
{
if
(
!
subview
.
pageCtrl
)
{
subview
.
pageCtrl
=
view
.
pageCtrl
;
}
[
GMExposureManager
fetchViewForVisibleState
:
subview
trackerType
:
trackerType
recursive
:
recursive
];
}
}
if
(
!
[
GMExposureManager
isNeedIgnoreTrackExposure
:
ignoreTracking
]
&&
(
!
recursive
||
view
.
subviews
.
count
==
0
))
{
[[
GMExposureManager
sharedManager
]
startStoreExposureData
];
}
}
/**
判断当前view是否可见
@param view view
@return yes/no
*/
+
(
BOOL
)
isViewVisible
:
(
UIView
*
)
view
{
// NSLog(@"%@:%@:%d:%d:%f",view,view.window ,view.hidden,view.layer.hidden,view.alpha);
// 可见
if
(
!
view
.
window
||
view
.
hidden
||
view
.
layer
.
hidden
||
!
view
.
alpha
)
{
return
NO
;
}
// App进入后台,当前view均置为不可见
if
([
GMExposureManager
sharedManager
].
inBackground
)
{
return
NO
;
}
UIView
*
current
=
view
;
while
([
current
isKindOfClass
:[
UIView
class
]])
{
if
(
current
.
alpha
<=
0
||
current
.
hidden
)
{
return
NO
;
}
current
=
current
.
superview
;
}
// 判断当前View 与 view所在window是否存在交集
CGRect
viewRectInWindow
=
[
view
convertRect
:
view
.
bounds
toView
:
view
.
window
];
BOOL
isIntersects
=
CGRectIntersectsRect
(
view
.
window
.
bounds
,
viewRectInWindow
);
if
(
isIntersects
)
{
// 获取View 与 view所在window的相交区域
CGRect
intersectRect
=
CGRectIntersection
(
view
.
window
.
bounds
,
viewRectInWindow
);
if
(
intersectRect
.
size
.
width
!=
0
.
f
&&
intersectRect
.
size
.
height
!=
0
.
f
)
{
// size > exposureDimThreshold 视为可见
CGFloat
dimThreshold
=
[
GMExposureManager
sharedManager
].
exposureDimThreshold
;
if
(
intersectRect
.
size
.
width
/
viewRectInWindow
.
size
.
width
>
dimThreshold
&&
intersectRect
.
size
.
height
/
viewRectInWindow
.
size
.
height
>
dimThreshold
)
{
return
YES
;
}
}
}
return
NO
;
}
#pragma mark - View Visible/UnVisibel
/** 设置view的曝光状态
* view 可见 && 状态none或者end——> 开始曝光
* view 可见 && 状态start——> 正在曝光,不做处理
* view 不可见 && 状态start ——> 结束曝光
* view 不可见 && 状态none或者end ——> 不做处理
*/
-
(
void
)
exposureStatusForView
:
(
UIView
*
)
view
inPageCtrl
:
(
UIViewController
*
)
inPageCtrl
{
// GMViewExpoStatus status = view.expoStatus;
if
(
view
.
visibleView
)
{
// 开始曝光
[
self
view
:
view
startVisibleInPageCtrl
:
inPageCtrl
];
}
else
{
// 结束曝光
[
self
view
:
view
endVisibleInPageCtrl
:
inPageCtrl
];
}
}
/**
view 在当前页面开始可见(开始曝光)
@param view 目标view
@param inPageCtrl inPageViewContrl 当前页面
*/
-
(
void
)
view
:
(
UIView
*
)
startView
startVisibleInPageCtrl
:
(
UIViewController
*
)
inPageCtrl
{
// 无状态进入页面直接开启曝光
NSString
*
viewKey
=
[
GMExposureManager
exposureKeyForData
:
startView
];
UIView
*
view
=
[
self
.
exposureViewDictM
objectForKey
:
viewKey
];
if
(
!
view
)
{
startView
.
expoStatus
=
GMViewExpoStatusStart
;
[
self
.
exposureViewDictM
setObject
:
startView
forKey
:
viewKey
];
}
}
/**
view在当前页面结束可见(曝光结束)
@param view 目标view
@param inPageViewContrl 指定页面
*/
-
(
void
)
view
:
(
UIView
*
)
endView
endVisibleInPageCtrl
:
(
UIViewController
*
)
inPageCtrl
{
if
([
self
.
exposureViewDictM
.
allValues
containsObject
:
endView
])
{
NSArray
*
keys
=
[
self
.
exposureViewDictM
allKeysForObject
:
endView
];
for
(
NSString
*
key
in
keys
)
{
[
self
.
exposureViewDictM
removeObjectForKey
:
key
];
}
}
}
#pragma mark - ExposureKey
/**
* 获取曝光View存储Key(pageCtrl地址/inTime/view)
* pageCtrl地址:当前view所在页面的地址,方便后续根据页面就能删除
* inTime:当前开始曝光时间
* view地址:view
* /:后续可根据/进行切分
@param view View
@return 当前曝光View pageCtrl地址/inTime/view的地址为key
*/
+
(
NSString
*
)
exposureKeyForData
:
(
UIView
*
)
view
{
return
[
NSString
stringWithFormat
:
@"%p/%p"
,
view
.
pageCtrl
,
view
.
exposure
];
}
/**
获取页面key存储的曝光数据
@param pageCtrl pageCtrl
@return 当前页面地址
*/
+
(
NSString
*
)
exposureKeyForPage
:
(
UIViewController
*
)
pageCtrl
{
return
[
NSString
stringWithFormat
:
@"%p"
,
pageCtrl
];
}
/// 根据view的地址获取当前的page地址
/// @param viewKey viewKey
+
(
UIViewController
*
)
expoViewKeyForPageCtrl
:
(
NSString
*
)
viewKey
{
NSString
*
page
=
[[
viewKey
componentsSeparatedByString
:
@"/"
]
firstObject
];
return
[
GMExposureManager
expoPageCtrl
:
page
];
}
/**
根据页面地址获取页面对象
@param pageAddress 页面地址
@return 页面对象
*/
+
(
UIViewController
*
)
expoPageCtrl
:
(
NSString
*
)
pageAddress
{
uintptr_t
hex
=
strtoull
(
pageAddress
.
UTF8String
,
NULL
,
0
);
id
gotcha
=
(
__bridge
id
)(
void
*
)
hex
;
UIViewController
*
pageCtrl
=
(
UIViewController
*
)
gotcha
;
return
pageCtrl
;
}
-
(
void
)
dealloc
{
[
self
removeObserverMethod
];
}
@end
GMPhobos/Classes/GMExposureModel.h
0 → 100644
View file @
7e581998
//
// GMExposureModel.h
// Gengmei
//
// Created by Mikasa on 2019/6/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
// 基于页面的精准曝光Item
@interface
GMExposureItemModel
:
NSObject
/** 记录上滑总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
up_slide_times
;
/** 记录下滑总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
down_slide_times
;
/** 记录向上加载总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
up_loading_times
;
/** 记录向下加载总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
down_loading_times
;
/** 当前tab_name */
@property
(
nonatomic
,
copy
)
NSString
*
tab_name
;
/** 当前page_name */
@property
(
nonatomic
,
copy
)
NSString
*
page_name
;
/** 当前business_id */
@property
(
nonatomic
,
copy
)
NSString
*
business_id
;
/** 当前referrer */
@property
(
nonatomic
,
copy
)
NSString
*
referrer
;
/** 当前referrer */
@property
(
nonatomic
,
copy
)
NSString
*
referrerId
;
/** 当前filter_f */
@property
(
nonatomic
,
copy
)
NSString
*
filter_f
;
/** 曝光卡片数据 */
@property
(
nonatomic
,
strong
)
NSMutableArray
*
exposure_cards
;
/** 是否精准曝光 */
@property
(
nonatomic
,
copy
)
NSString
*
is_exposure
;
@end
GMPhobos/Classes/GMExposureModel.m
0 → 100644
View file @
7e581998
//
// GMExposureModel.m
// Gengmei
//
// Created by Mikasa on 2019/6/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMExposureModel.h"
@implementation
GMExposureItemModel
-
(
instancetype
)
init
{
if
(
self
=
[
super
init
])
{
_exposure_cards
=
[
NSMutableArray
array
];
_is_exposure
=
@"1"
;
}
return
self
;
}
@end
GMPhobos/Classes/GMExposureProtocol.h
0 → 100644
View file @
7e581998
//
// GMExposureProtocol.h
// Gengmei
//
// Created by Mikasa on 2019/6/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
typedef
NS_ENUM
(
NSInteger
,
GMViewExpoStatus
)
{
GMViewExpoStatusNone
=
0
,
// 无状态
GMViewExpoStatusStart
=
1
,
// 开始曝光
GMViewExpoStatusIng
=
2
,
// 正在曝光(暂未使用,暂时先保留)
GMViewExpoStatusEnd
=
3
// 结束曝光
};
@protocol
GMViewExposureProtocol
<
NSObject
>
@property
(
nonatomic
,
weak
)
UIViewController
*
pageCtrl
;
// 当前view所在页面
/** view别名 */
@property
(
nonatomic
,
copy
)
NSString
*
viewName
;
/** view别名 */
@property
(
nonatomic
,
copy
)
NSString
*
inTime
;
/** view别名 */
@property
(
nonatomic
,
copy
)
NSString
*
outTime
;
/** 绝对位置 */
@property
(
nonatomic
,
copy
)
NSString
*
absolute_position
;
/** 相对位置(相对屏幕所在位置)*/
@property
(
nonatomic
,
copy
)
NSString
*
relative_position
;
/** 额外参数*/
@property
(
nonatomic
,
strong
)
NSDictionary
*
extra_param
;
/** 曝光状态 */
@property
(
nonatomic
,
assign
)
GMViewExpoStatus
expoStatus
;
@end
@protocol
GMPageExposureProtocol
<
NSObject
>
@property
(
nonatomic
,
assign
)
BOOL
needExpo
;
// 页面需要精准曝光
/** 记录上滑总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
up_slide_times
;
/** 记录下滑总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
down_slide_times
;
/** 记录向上加载总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
up_loading_times
;
/** 记录向下加载总次数 */
@property
(
nonatomic
,
assign
)
NSInteger
down_loading_times
;
/** 额外参数 */
@property
(
nonatomic
,
strong
)
NSDictionary
*
extra_param
;
@end
@protocol
GMExposureProtocol
<
NSObject
>
/** 精准曝光数据 */
@property
(
nonatomic
,
strong
)
NSDictionary
*
exposure
;
@end
GMPhobos/Classes/NSObject+Swizzle.h
0 → 100644
View file @
7e581998
//
// GMHookTool.h
// Gengmei
//
// Created by Mikasa on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#define MethodSwizzlerReplacement(returntype, selftype, ...) ^ returntype (__unsafe_unretained selftype self, ##__VA_ARGS__)
#define MethodSwizzlerReplacementProviderBlock ^ id (IMP original, __unsafe_unretained Class swizzledClass, SEL _cmd)
#define MethodSwizzlerOriginalImplementation(functype, ...) do{\
if (original)\
((functype)original)(self, _cmd, ##__VA_ARGS__);\
}while(0);
typedef
id
(
^
MethodSwizzlerProvider
)(
IMP
original
,
__unsafe_unretained
Class
swizzledClass
,
SEL
selector
);
OBJC_EXTERN
BOOL
deswizzleAll
(
void
);
@interface
NSObject
(
SwizzleMethod
)
/**
swizzleClassMethod
@param selector swizzleClass selector
@param replacementProvider replacementProvider
*/
+
(
void
)
swizzleClassMethod
:(
SEL
)
selector
withReplacement
:(
MethodSwizzlerProvider
)
replacementProvider
;
/**
swizzleInstanceMethod
@param selector swizzleInstanceMethod selector
@param replacementProvider replacementProvider
*/
+
(
void
)
swizzleInstanceMethod
:(
SEL
)
selector
withReplacement
:(
MethodSwizzlerProvider
)
replacementProvider
;
/**
Swizzle the specified instance method with another selector
@param selector Selector of the method to swizzle.
@param swizzledSelector Selector of the new method will be swizzled.
*/
+
(
void
)
swizzleInstanceMethod
:(
SEL
)
selector
withSelector
:(
SEL
)
swizzledSelector
;
/**
Swizzle the specified class method with another selector
@param selector Selector of the method to swizzle.
@param swizzledSelector Selector of the new method will be swizzled.
*/
+
(
void
)
swizzleClassMethod
:(
SEL
)
selector
withSelector
:(
SEL
)
swizzledSelector
;
/**
Swizzle the specified instance method with another swizzledClass swizzledMethod
@param originalClass originalClass
@param originalSel originalSel
@param swizzledClass swizzledClass
@param swizzledSelector swizzledSelector
@param noneSel noneSel
*/
+
(
void
)
swizzelInstanceMethodForClass
:(
Class
)
originalClass
originalSel
:(
SEL
)
originalSel
swizzledClass
:(
Class
)
swizzledClass
swizzledSelector
:(
SEL
)
swizzledSelector
noneSel
:(
SEL
)
noneSel
;
@end
@interface
NSObject
(
DeSwizzleMethod
)
/**
Restore the specified class method by removing all swizzles.
@param selector Selector of the swizzled method.
@return \c YES if the method was successfully restored, \c NO if the method has never been swizzled.
*/
+
(
BOOL
)
deswizzleClassMethod
:(
SEL
)
selector
;
/**
Restore the specified class method by removing all swizzles.
@param selector Selector of the swizzled method.
@return \c YES if the method was successfully restored, \c NO if the method has never been swizzled.
*/
+
(
BOOL
)
deswizzleInstanceMethod
:(
SEL
)
selector
;
/**
Restore all swizzled class methods.
@return \c YES if the method was successfully restored, \c NO if no method has never been swizzled
*/
+
(
BOOL
)
deswizzleAllClassMethods
;
/**
Restore all swizzled instance methods.
@return \c YES if the method was successfully restored, \c NO if no method has never been swizzled.
*/
+
(
BOOL
)
deswizzleAllInstanceMethods
;
/**
Restore all swizzled class and instance methods.
@return \c YES if the method was successfully restored, \c NO if no method has never been swizzled.
*/
+
(
BOOL
)
deswizzleAllMethods
;
@end
@interface
GMHookTool
:
NSObject
//判断页面是否实现了某个sel
+
(
BOOL
)
isContainSel
:(
SEL
)
sel
inClass
:(
Class
)
className
;
@end
@interface
GMWeakObject
:
NSObject
@property
(
nonatomic
,
weak
)
id
obj
;
-
(
instancetype
)
initWithObj
:(
id
)
obj
;
@end
GMPhobos/Classes/NSObject+Swizzle.m
0 → 100644
View file @
7e581998
//
// GMHookTool.m
// Gengmei
//
// Created by Mikasa on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "NSObject+Swizzle.h"
#import <objc/runtime.h>
#import <libkern/OSAtomic.h>
static
OSSpinLock
lock
=
OS_SPINLOCK_INIT
;
static
NSMutableDictionary
*
originalClassMethods
;
static
NSMutableDictionary
*
originalInstanceMethods
;
#pragma mark - Global Method
NS_INLINE
void
classSwizzleMethod
(
Class
cls
,
Method
method
,
IMP
newImp
)
{
if
(
!
class_addMethod
(
cls
,
method_getName
(
method
),
newImp
,
method_getTypeEncoding
(
method
)))
{
// class already has implementation, swizzle it instead
method_setImplementation
(
method
,
newImp
);
}
}
NS_INLINE
IMP
originalClassMethodImplementation
(
__unsafe_unretained
Class
class
,
SEL
selector
,
BOOL
fetchOnly
)
{
if
(
!
originalClassMethods
)
{
originalClassMethods
=
[[
NSMutableDictionary
alloc
]
init
];
}
NSString
*
classKey
=
NSStringFromClass
(
class
);
NSString
*
selectorKey
=
NSStringFromSelector
(
selector
);
NSMutableDictionary
*
classSwizzles
=
originalClassMethods
[
classKey
];
NSValue
*
pointerValue
=
classSwizzles
[
selectorKey
];
if
(
!
classSwizzles
)
{
classSwizzles
=
[
NSMutableDictionary
dictionary
];
originalClassMethods
[
classKey
]
=
classSwizzles
;
}
IMP
orig
=
NULL
;
if
(
pointerValue
)
{
orig
=
[
pointerValue
pointerValue
];
if
(
fetchOnly
)
{
if
(
classSwizzles
.
count
==
1
)
{
[
originalClassMethods
removeObjectForKey
:
classKey
];
}
else
{
[
classSwizzles
removeObjectForKey
:
selectorKey
];
}
}
}
else
if
(
!
fetchOnly
)
{
orig
=
(
IMP
)[
class
methodForSelector
:
selector
];
classSwizzles
[
selectorKey
]
=
[
NSValue
valueWithPointer
:
orig
];
}
if
(
classSwizzles
.
count
==
0
)
{
[
originalClassMethods
removeObjectForKey
:
classKey
];
}
if
(
originalClassMethods
.
count
==
0
)
{
originalClassMethods
=
nil
;
}
return
orig
;
}
NS_INLINE
IMP
originalInstanceMethodImplementation
(
__unsafe_unretained
Class
class
,
SEL
selector
,
BOOL
fetchOnly
)
{
if
(
!
originalInstanceMethods
)
{
originalInstanceMethods
=
[[
NSMutableDictionary
alloc
]
init
];
}
NSString
*
classKey
=
NSStringFromClass
(
class
);
NSString
*
selectorKey
=
NSStringFromSelector
(
selector
);
NSMutableDictionary
*
classSwizzles
=
originalInstanceMethods
[
classKey
];
NSValue
*
pointerValue
=
classSwizzles
[
selectorKey
];
if
(
!
classSwizzles
)
{
classSwizzles
=
[
NSMutableDictionary
dictionary
];
originalInstanceMethods
[
classKey
]
=
classSwizzles
;
}
IMP
orig
=
NULL
;
if
(
pointerValue
)
{
orig
=
[
pointerValue
pointerValue
];
if
(
fetchOnly
)
{
[
classSwizzles
removeObjectForKey
:
selectorKey
];
if
(
classSwizzles
.
count
==
0
)
{
[
originalInstanceMethods
removeObjectForKey
:
classKey
];
}
}
}
else
if
(
!
fetchOnly
)
{
orig
=
(
IMP
)[
class
instanceMethodForSelector
:
selector
];
classSwizzles
[
selectorKey
]
=
[
NSValue
valueWithPointer
:
orig
];
}
if
(
classSwizzles
.
count
==
0
)
{
[
originalInstanceMethods
removeObjectForKey
:
classKey
];
}
if
(
originalInstanceMethods
.
count
==
0
)
{
originalInstanceMethods
=
nil
;
}
return
orig
;
}
#pragma mark - Deswizzling Global Swizzles
NS_INLINE
BOOL
deswizzleClassMethod
(
__unsafe_unretained
Class
class
,
SEL
selector
)
{
OSSpinLockLock
(
&
lock
);
IMP
originalIMP
=
originalClassMethodImplementation
(
class
,
selector
,
YES
);
if
(
originalIMP
)
{
method_setImplementation
(
class_getClassMethod
(
class
,
selector
),
(
IMP
)
originalIMP
);
OSSpinLockUnlock
(
&
lock
);
return
YES
;
}
else
{
OSSpinLockUnlock
(
&
lock
);
return
NO
;
}
}
NS_INLINE
BOOL
deswizzleInstanceMethod
(
__unsafe_unretained
Class
class
,
SEL
selector
)
{
OSSpinLockLock
(
&
lock
);
IMP
originalIMP
=
originalInstanceMethodImplementation
(
class
,
selector
,
YES
);
if
(
originalIMP
)
{
method_setImplementation
(
class_getInstanceMethod
(
class
,
selector
),
(
IMP
)
originalIMP
);
OSSpinLockUnlock
(
&
lock
);
return
YES
;
}
else
{
OSSpinLockUnlock
(
&
lock
);
return
NO
;
}
}
NS_INLINE
BOOL
deswizzleAllClassMethods
(
__unsafe_unretained
Class
class
)
{
OSSpinLockLock
(
&
lock
);
BOOL
success
=
NO
;
NSDictionary
*
d
=
[
originalClassMethods
[
NSStringFromClass
(
class
)]
copy
];
for
(
NSString
*
sel
in
d
)
{
OSSpinLockUnlock
(
&
lock
);
if
(
deswizzleClassMethod
(
class
,
NSSelectorFromString
(
sel
)))
{
success
=
YES
;
}
OSSpinLockLock
(
&
lock
);
}
OSSpinLockUnlock
(
&
lock
);
return
success
;
}
NS_INLINE
BOOL
deswizzleAllInstanceMethods
(
__unsafe_unretained
Class
class
)
{
OSSpinLockLock
(
&
lock
);
BOOL
success
=
NO
;
NSDictionary
*
d
=
[
originalInstanceMethods
[
NSStringFromClass
(
class
)]
copy
];
for
(
NSString
*
sel
in
d
)
{
OSSpinLockUnlock
(
&
lock
);
if
(
deswizzleInstanceMethod
(
class
,
NSSelectorFromString
(
sel
)))
{
success
=
YES
;
}
OSSpinLockLock
(
&
lock
);
}
OSSpinLockUnlock
(
&
lock
);
return
success
;
}
BOOL
deswizzleAll
(
void
)
{
BOOL
success
=
NO
;
OSSpinLockLock
(
&
lock
);
NSDictionary
*
d
=
originalClassMethods
.
copy
;
for
(
NSString
*
classKey
in
d
)
{
OSSpinLockUnlock
(
&
lock
);
BOOL
ok
=
[
NSClassFromString
(
classKey
)
deswizzleAllMethods
];
OSSpinLockLock
(
&
lock
);
if
(
success
!=
ok
)
{
success
=
YES
;
}
}
NSDictionary
*
d1
=
originalInstanceMethods
.
copy
;
for
(
NSString
*
classKey
in
d1
)
{
OSSpinLockUnlock
(
&
lock
);
BOOL
ok
=
[
NSClassFromString
(
classKey
)
deswizzleAllMethods
];
OSSpinLockLock
(
&
lock
);
if
(
success
!=
ok
)
{
success
=
YES
;
}
}
OSSpinLockUnlock
(
&
lock
);
return
success
;
return
YES
;
}
#pragma mark - Global Swizzle Method
NS_INLINE
void
swizzleClassMethod
(
__unsafe_unretained
Class
class
,
SEL
selector
,
MethodSwizzlerProvider
replacement
)
{
OSSpinLockLock
(
&
lock
);
Method
originalMethod
=
class_getClassMethod
(
class
,
selector
);
IMP
orig
=
originalClassMethodImplementation
(
class
,
selector
,
NO
);
id
replaceBlock
=
replacement
(
orig
,
class
,
selector
);
Class
meta
=
object_getClass
(
class
);
classSwizzleMethod
(
meta
,
originalMethod
,
imp_implementationWithBlock
(
replaceBlock
));
OSSpinLockUnlock
(
&
lock
);
}
NS_INLINE
void
swizzleInstanceMethod
(
__unsafe_unretained
Class
class
,
SEL
selector
,
MethodSwizzlerProvider
replacement
)
{
OSSpinLockLock
(
&
lock
);
Method
originalMethod
=
class_getInstanceMethod
(
class
,
selector
);
IMP
orig
=
originalInstanceMethodImplementation
(
class
,
selector
,
NO
);
id
replaceBlock
=
replacement
(
orig
,
class
,
selector
);
IMP
replace
=
imp_implementationWithBlock
(
replaceBlock
);
classSwizzleMethod
(
class
,
originalMethod
,
replace
);
OSSpinLockUnlock
(
&
lock
);
}
@implementation
NSObject
(
SwizzleMethod
)
+
(
void
)
swizzleClassMethod
:(
SEL
)
selector
withReplacement
:(
MethodSwizzlerProvider
)
replacementProvider
{
swizzleClassMethod
(
self
,
selector
,
replacementProvider
);
}
+
(
void
)
swizzleInstanceMethod
:(
SEL
)
selector
withReplacement
:(
MethodSwizzlerProvider
)
replacementProvider
{
swizzleInstanceMethod
(
self
,
selector
,
replacementProvider
);
}
+
(
void
)
swizzleInstanceMethod
:(
SEL
)
originalSelector
withSelector
:(
SEL
)
swizzledSelector
{
Method
originalMethod
=
class_getInstanceMethod
(
self
,
originalSelector
);
Method
swizzledMethod
=
class_getInstanceMethod
(
self
,
swizzledSelector
);
BOOL
didAddMethod
=
class_addMethod
(
self
,
originalSelector
,
method_getImplementation
(
swizzledMethod
),
method_getTypeEncoding
(
swizzledMethod
));
if
(
didAddMethod
)
{
class_replaceMethod
(
self
,
swizzledSelector
,
method_getImplementation
(
originalMethod
),
method_getTypeEncoding
(
originalMethod
));
}
else
{
method_exchangeImplementations
(
originalMethod
,
swizzledMethod
);
}
}
+
(
void
)
swizzelInstanceMethodForClass
:(
Class
)
originalClass
originalSel
:(
SEL
)
originalSel
swizzledClass
:(
Class
)
swizzledClass
swizzledSelector
:(
SEL
)
swizzledSelector
noneSel
:(
SEL
)
noneSel
{
Method
originalMethod
=
class_getInstanceMethod
(
originalClass
,
originalSel
);
Method
swizzingMethod
=
class_getInstanceMethod
(
swizzledClass
,
swizzledSelector
);
// 如果没有实现 delegate 方法,则手动动态添加
if
(
!
originalMethod
)
{
Method
noneMethod
=
class_getInstanceMethod
(
swizzledClass
,
noneSel
);
BOOL
didAddNoneMethod
=
class_addMethod
(
originalClass
,
originalSel
,
method_getImplementation
(
noneMethod
),
method_getTypeEncoding
(
noneMethod
));
if
(
didAddNoneMethod
)
{
// NSLog(@"******** 没有实现 (%@) 方法,手动添加成功!!(%@)",NSStringFromSelector(originalSel),NSStringFromClass(originalClass));
// originalMethod = class_getInstanceMethod(originalClass, originalSel);
}
return
;
}
// 向实现 delegate 的类中添加新的方法
BOOL
didAddMethod
=
class_addMethod
(
originalClass
,
swizzledSelector
,
method_getImplementation
(
swizzingMethod
),
method_getTypeEncoding
(
swizzingMethod
));
if
(
didAddMethod
)
{
// 添加成功
// NSLog(@"******** 实现了 (%@) 方法并成功 Hook 为 --> (%@)",NSStringFromSelector(originalSel) ,NSStringFromSelector(swizzledSelector));
// 重新拿到添加被添加的 method,这里是关键(注意这里 originalClass, 不 replacedClass), 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法
Method
newMethod
=
class_getInstanceMethod
(
originalClass
,
swizzledSelector
);
// 实现交换
method_exchangeImplementations
(
originalMethod
,
newMethod
);
}
else
{
// 添加失败,则说明已经 hook 过该类的 delegate 方法,防止多次交换。
// NSLog(@"******** 已替换过,避免多次替换 --> (%@)",NSStringFromClass(originalClass));
}
}
+
(
void
)
swizzleClassMethod
:(
SEL
)
originalSelector
withSelector
:(
SEL
)
swizzledSelector
{
Method
originalMethod
=
class_getClassMethod
(
self
,
originalSelector
);
Method
swizzledMethod
=
class_getClassMethod
(
self
,
swizzledSelector
);
BOOL
didAddMethod
=
class_addMethod
(
self
,
originalSelector
,
method_getImplementation
(
swizzledMethod
),
method_getTypeEncoding
(
swizzledMethod
));
if
(
didAddMethod
)
{
class_replaceMethod
(
self
,
swizzledSelector
,
method_getImplementation
(
originalMethod
),
method_getTypeEncoding
(
originalMethod
));
}
else
{
method_exchangeImplementations
(
originalMethod
,
swizzledMethod
);
}
}
@end
@implementation
NSObject
(
DeSwizzleMethod
)
+
(
BOOL
)
deswizzleClassMethod
:(
SEL
)
selector
{
return
deswizzleClassMethod
(
self
,
selector
);
}
+
(
BOOL
)
deswizzleInstanceMethod
:(
SEL
)
selector
{
return
deswizzleInstanceMethod
(
self
,
selector
);
}
+
(
BOOL
)
deswizzleAllClassMethods
{
return
deswizzleAllClassMethods
(
self
);
}
+
(
BOOL
)
deswizzleAllInstanceMethods
{
return
deswizzleAllInstanceMethods
(
self
);
}
+
(
BOOL
)
deswizzleAllMethods
{
BOOL
c
=
[
self
deswizzleAllClassMethods
];
BOOL
i
=
[
self
deswizzleAllInstanceMethods
];
return
(
c
||
i
);
}
@end
@implementation
GMHookTool
//判断页面是否实现了某个sel
+
(
BOOL
)
isContainSel
:(
SEL
)
sel
inClass
:(
Class
)
className
{
unsigned
int
count
;
Method
*
methodList
=
class_copyMethodList
(
className
,
&
count
);
for
(
int
i
=
0
;
i
<
count
;
i
++
)
{
Method
method
=
methodList
[
i
];
NSString
*
tempMethodString
=
[
NSString
stringWithUTF8String
:
sel_getName
(
method_getName
(
method
))];
if
([
tempMethodString
isEqualToString
:
NSStringFromSelector
(
sel
)])
{
return
YES
;
}
}
return
NO
;
}
@end
@implementation
GMWeakObject
-
(
instancetype
)
initWithObj
:(
id
)
obj
{
if
(
self
=
[
super
init
])
{
_obj
=
obj
;
}
return
self
;
}
@end
GMPhobos/Classes/NSObject+Tracker.h
0 → 100644
View file @
7e581998
//
// NSObject+Tracker.h
// GMPhobos
//
// Created by Mikasa on 2020/5/9.
//
#import <Foundation/Foundation.h>
#import "GMExposureProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface
NSObject
(
Tracker
)
<
GMExposureProtocol
>
@end
NS_ASSUME_NONNULL_END
GMPhobos/Classes/NSObject+Tracker.m
0 → 100644
View file @
7e581998
//
// NSObject+Tracker.m
// GMPhobos
//
// Created by Mikasa on 2020/5/9.
//
#import "NSObject+Tracker.h"
#import <objc/runtime.h>
@implementation
NSObject
(
Tracker
)
#pragma mark - Exposure
-
(
void
)
setExposure
:(
NSDictionary
*
)
exposure
{
objc_setAssociatedObject
(
self
,
@selector
(
exposure
),
exposure
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSDictionary
*
)
exposure
{
return
objc_getAssociatedObject
(
self
,
@selector
(
exposure
));
}
@end
GMPhobos/Classes/PhobosSendManager.m
View file @
7e581998
...
@@ -11,12 +11,7 @@
...
@@ -11,12 +11,7 @@
#import "PhobosConfig.h"
#import "PhobosConfig.h"
#import "Phobos.h"
#import "Phobos.h"
#import "PhobosUtil.h"
#import "PhobosUtil.h"
#import "GMExposureManager.h"
#ifdef APPSTORE
#define GMExactExposureApi @"https://log.igengmei.com/log/precise_exposure"
#else
#define GMExactExposureApi @"http://log.test.igengmei.com/log/precise_exposure"
#endif
@implementation
PhobosSendManager
@implementation
PhobosSendManager
...
@@ -44,7 +39,7 @@
...
@@ -44,7 +39,7 @@
}
}
}];
}];
}
}
NSString
*
exposureAPI
=
GMExactExposureApi
;
NSString
*
exposureAPI
=
[
GMExposureManager
sharedManager
].
uploadExposureAPI
;
NSArray
*
exposureArray
=
[
GMCache
fetchObjectAtDocumentPathWithkey
:[
PhobosUtil
MD5String
:
exposureAPI
]];
NSArray
*
exposureArray
=
[
GMCache
fetchObjectAtDocumentPathWithkey
:[
PhobosUtil
MD5String
:
exposureAPI
]];
if
(
exposureArray
.
count
>
0
)
{
if
(
exposureArray
.
count
>
0
)
{
/** 获取非灰度下发送失败的埋点,进行发送 */
/** 获取非灰度下发送失败的埋点,进行发送 */
...
...
GMPhobos/Classes/UIScrollView+Tracker.h
0 → 100644
View file @
7e581998
//
// UIScrollView+Tracker.h
// Gengmei
//
// Created by Mikasa on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
@interface
UIScrollView
(
Tracker
)
@property
(
nonatomic
,
assign
)
CGFloat
preContentOffsetY
;
// 记录历史滚动的ContentOffset的Y值
/// 计算当前列表上滑/下拉次数,自动进行+1
-
(
void
)
calculateUpOrDownTimes
;
@end
@interface
UITableView
(
Tracker
)
@end
@interface
UICollectionView
(
Tracker
)
@end
GMPhobos/Classes/UIScrollView+Tracker.m
0 → 100644
View file @
7e581998
//
// UIScrollView+Tracker.m
// Gengmei
//
// Created by Mikasa on 2019/6/19.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "UIScrollView+Tracker.h"
#import "UIViewController+Tracker.h"
#import "NSObject+Swizzle.h"
#import "GMExposureManager.h"
#import <objc/runtime.h>
@implementation
UIScrollView
(
Tracker
)
-
(
void
)
calculateUpOrDownTimes
{
if
(
self
.
preContentOffsetY
==
self
.
contentOffset
.
y
)
{
return
;
}
// 判断滚动方向
if
(
self
.
preContentOffsetY
<
self
.
contentOffset
.
y
)
{
self
.
pageCtrl
.
up_slide_times
++
;
}
else
{
self
.
pageCtrl
.
down_slide_times
++
;
}
}
#pragma mark - Add Method
-
(
void
)
setPreContentOffsetY
:
(
CGFloat
)
preContentOffsetY
{
objc_setAssociatedObject
(
self
,
@selector
(
preContentOffsetY
),
@
(
preContentOffsetY
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
CGFloat
)
preContentOffsetY
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
preContentOffsetY
))
floatValue
];
}
@end
@implementation
UITableView
(
Tracker
)
+
(
void
)
load
{
[
self
swizzleInstanceMethod
:
@selector
(
reloadData
)
withSelector
:
@selector
(
swizzle_reloadData
)];
}
-
(
void
)
swizzle_reloadData
{
[
self
swizzle_reloadData
];
if
(
self
.
pageCtrl
.
needExpo
)
{
__weak
typeof
(
self
)
weakSelf
=
self
;
dispatch_async
(
dispatch_get_main_queue
(),
^
{
[
GMExposureManager
fetchViewForVisibleState
:
weakSelf
trackerType
:
GMViewTrackerTypeUIViewSetExposure
recursive
:
YES
];
});
}
}
@end
@implementation
UICollectionView
(
Tracker
)
+
(
void
)
load
{
[
self
swizzleInstanceMethod
:
@selector
(
reloadData
)
withSelector
:
@selector
(
swizzle_reloadData
)];
}
-
(
void
)
swizzle_reloadData
{
[
self
swizzle_reloadData
];
if
(
self
.
pageCtrl
.
needExpo
)
{
__weak
typeof
(
self
)
weakSelf
=
self
;
dispatch_async
(
dispatch_get_main_queue
(),
^
{
[
GMExposureManager
fetchViewForVisibleState
:
weakSelf
trackerType
:
GMViewTrackerTypeUIViewSetExposure
recursive
:
YES
];
});
}
}
@end
GMPhobos/Classes/UIView+Tracker.h
0 → 100644
View file @
7e581998
//
// UIView+Tracker.h
// Gengmei
//
// Created by Mikasa on 2019/6/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMExposureProtocol.h"
@interface
UIView
(
Tracker
)
<
GMViewExposureProtocol
>
@property
(
nonatomic
,
assign
)
BOOL
visibleView
;
// 是否是可见View
#pragma mark - View Method
/**
获取指定View所在的Controller
@param view 指定View
@return 当前所在Controller
*/
+
(
UIViewController
*
)
currentControllerForView
:(
UIView
*
)
view
;
/**
获取曝光卡片所在的位置
*/
-
(
void
)
fechExposurePositionForView
;
-
(
void
)
synchronizePageTimesToOther
:(
UIView
*
)
otherView
;
@end
GMPhobos/Classes/UIView+Tracker.m
0 → 100644
View file @
7e581998
//
// UIView+Tracker.m
// Gengmei
//
// Created by Mikasa on 2019/6/21.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "UIView+Tracker.h"
#import "UIViewController+Tracker.h"
#import "NSObject+Swizzle.h"
#import "PhobosUtil.h"
#import "GMExposureModel.h"
#import <objc/runtime.h>
@implementation
UIView
(
Tracker
)
#pragma mark - View Method
/**
获取指定View所在的Controller
@param view 指定View
@return 当前所在Controller
*/
+
(
UIViewController
*
)
currentControllerForView
:(
UIView
*
)
view
{
UIViewController
*
currentCtrl
;
UIResponder
*
responder
=
[
view
.
superview
nextResponder
];
while
(
responder
&&
!
[
responder
isKindOfClass
:[
UIViewController
class
]])
{
responder
=
[
responder
nextResponder
];
}
currentCtrl
=
(
UIViewController
*
)
responder
;
return
currentCtrl
;
}
/**
获取曝光卡片所在的位置
*/
-
(
void
)
fechExposurePositionForView
{
UIView
*
view
=
self
;
NSInteger
absolute_position
=
[
PhobosUtil
isNonEmpty
:
self
.
absolute_position
]?
self
.
absolute_position
.
integerValue
:
0
;
UIView
*
current
=
(
UIView
*
)
view
;
UIView
*
tempView
=
view
;
while
([
current
isKindOfClass
:[
UIView
class
]]
&&
(
current
!=
view
.
pageCtrl
.
view
))
{
if
([
current
isKindOfClass
:[
UITableViewCell
class
]])
{
tempView
=
current
;
}
if
([
current
isKindOfClass
:[
UICollectionViewCell
class
]])
{
tempView
=
current
;
}
if
([
current
isKindOfClass
:[
UITableView
class
]])
{
UITableView
*
tableView
=
(
UITableView
*
)
current
;
UITableViewCell
*
cell
=
(
UITableViewCell
*
)
tempView
;
if
([
tableView
.
visibleCells
containsObject
:
cell
])
{
NSIndexPath
*
indexPath
=
[
tableView
indexPathForCell
:
cell
];
if
(
indexPath
)
{
// 当有多个分组时,将position转化为绝对路径的position,一维数组
if
(
tempView
==
view
)
{
absolute_position
=
indexPath
.
row
;
for
(
NSInteger
i
=
0
;
i
<
indexPath
.
section
;
i
++
)
{
absolute_position
+=
[
tableView
numberOfRowsInSection
:
i
];
}
}
NSInteger
relative_position
=
0
;
if
([[
tableView
visibleCells
]
containsObject
:
cell
])
{
relative_position
=
[[
tableView
visibleCells
]
indexOfObject
:
cell
];
view
.
relative_position
=
[
NSString
stringWithFormat
:
@"%zd"
,
relative_position
];
break
;
}
}
}
}
if
([
current
isKindOfClass
:[
UICollectionView
class
]])
{
UICollectionView
*
collectionView
=
(
UICollectionView
*
)
current
;
UICollectionViewCell
*
cell
=
(
UICollectionViewCell
*
)
tempView
;
if
([
collectionView
.
visibleCells
containsObject
:
cell
])
{
NSIndexPath
*
indexPath
=
[
collectionView
indexPathForCell
:
cell
];
if
(
indexPath
)
{
// 当有多个分组时,将position转化为绝对路径的position,一维数组
if
(
tempView
==
view
)
{
absolute_position
=
indexPath
.
row
;
for
(
NSInteger
i
=
0
;
i
<
indexPath
.
section
;
i
++
)
{
absolute_position
+=
[
collectionView
numberOfItemsInSection
:
i
];
}
}
NSInteger
relative_position
=
0
;
// UICollectionViewCell
if
([[
collectionView
visibleCells
]
containsObject
:
cell
])
{
relative_position
=
[[
collectionView
visibleCells
]
indexOfObject
:
cell
];
view
.
relative_position
=
[
NSString
stringWithFormat
:
@"%zd"
,
relative_position
];
break
;
//NSLog(@"%f---%f",relative_position,absolute_position);
}
}
}
}
current
=
(
UITableViewCell
*
)
current
.
superview
;
}
view
.
absolute_position
=
[
NSString
stringWithFormat
:
@"%zd"
,
absolute_position
];
}
// 将当前UIScrollView数据同步给另外一个otherScrollView
-
(
void
)
synchronizePageTimesToOther
:
(
UIView
*
)
otherView
{
otherView
.
pageCtrl
.
up_slide_times
=
self
.
pageCtrl
.
up_slide_times
;
otherView
.
pageCtrl
.
down_slide_times
=
self
.
pageCtrl
.
down_slide_times
;
otherView
.
pageCtrl
.
up_loading_times
=
self
.
pageCtrl
.
up_loading_times
;
otherView
.
pageCtrl
.
down_loading_times
=
self
.
pageCtrl
.
down_loading_times
;
}
#pragma mark - GMViewExposureProtocol
-
(
void
)
setVisibleView
:
(
BOOL
)
visibleView
{
objc_setAssociatedObject
(
self
,
@selector
(
visibleView
),
@
(
visibleView
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
BOOL
)
visibleView
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
visibleView
))
boolValue
];
}
-
(
void
)
setInTime
:
(
NSString
*
)
inTime
{
objc_setAssociatedObject
(
self
,
@selector
(
inTime
),
inTime
,
OBJC_ASSOCIATION_COPY_NONATOMIC
);
}
-
(
NSString
*
)
inTime
{
return
objc_getAssociatedObject
(
self
,
@selector
(
inTime
));
}
-
(
void
)
setOutTime
:
(
NSString
*
)
outTime
{
objc_setAssociatedObject
(
self
,
@selector
(
outTime
),
outTime
,
OBJC_ASSOCIATION_COPY_NONATOMIC
);
}
-
(
NSString
*
)
outTime
{
return
objc_getAssociatedObject
(
self
,
@selector
(
outTime
));
}
-
(
void
)
setAbsolute_position
:
(
NSString
*
)
absolute_position
{
objc_setAssociatedObject
(
self
,
@selector
(
absolute_position
),
absolute_position
,
OBJC_ASSOCIATION_COPY_NONATOMIC
);
}
-
(
NSString
*
)
absolute_position
{
return
objc_getAssociatedObject
(
self
,
@selector
(
absolute_position
));
}
-
(
void
)
setRelative_position
:
(
NSString
*
)
relative_position
{
objc_setAssociatedObject
(
self
,
@selector
(
relative_position
),
relative_position
,
OBJC_ASSOCIATION_COPY_NONATOMIC
);
}
-
(
NSString
*
)
relative_position
{
return
objc_getAssociatedObject
(
self
,
@selector
(
relative_position
));
}
-
(
void
)
setPageCtrl
:
(
UIViewController
*
)
pageCtrl
{
GMWeakObject
*
obj
=
[[
GMWeakObject
alloc
]
initWithObj
:
pageCtrl
];
objc_setAssociatedObject
(
self
,
@selector
(
pageCtrl
),
obj
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
UIViewController
*
)
pageCtrl
{
GMWeakObject
*
weakObj
=
objc_getAssociatedObject
(
self
,
@selector
(
pageCtrl
));
if
(
!
weakObj
.
obj
)
{
weakObj
.
obj
=
[
UIView
currentControllerForView
:
self
];
}
return
weakObj
.
obj
;
}
-
(
void
)
setViewName
:
(
NSString
*
)
viewName
{
objc_setAssociatedObject
(
self
,
@selector
(
viewName
),
viewName
,
OBJC_ASSOCIATION_COPY_NONATOMIC
);
}
-
(
NSString
*
)
viewName
{
return
objc_getAssociatedObject
(
self
,
@selector
(
viewName
));
}
-
(
void
)
setExpoStatus
:
(
GMViewExpoStatus
)
expoStatus
{
objc_setAssociatedObject
(
self
,
@selector
(
expoStatus
),
@
(
expoStatus
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
if
(
expoStatus
==
GMViewExpoStatusStart
)
{
}
else
if
(
expoStatus
==
GMViewExpoStatusEnd
)
{
self
.
inTime
=
@""
;
self
.
outTime
=
@""
;
self
.
relative_position
=
@""
;
self
.
absolute_position
=
@""
;
}
}
-
(
GMViewExpoStatus
)
expoStatus
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
expoStatus
))
integerValue
];
}
-
(
void
)
setExtra_param
:
(
NSDictionary
*
)
extra_param
{
objc_setAssociatedObject
(
self
,
@selector
(
extra_param
),
extra_param
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSDictionary
*
)
extra_param
{
return
objc_getAssociatedObject
(
self
,
@selector
(
extra_param
));
}
@end
GMPhobos/Classes/UIViewController+Tracker.h
0 → 100644
View file @
7e581998
//
// UIViewController+Tracker.h
// Gengmei
//
// Created by Mikasa on 2019/6/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMExposureProtocol.h"
@interface
UIViewController
(
Tracker
)
<
GMPageExposureProtocol
,
UIScrollViewDelegate
>
@end
GMPhobos/Classes/UIViewController+Tracker.m
0 → 100644
View file @
7e581998
//
// UIViewController+Tracker.m
// Gengmei
//
// Created by Mikasa on 2019/6/25.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "UIViewController+Tracker.h"
#import "GMExposureManager.h"
#import "NSObject+Swizzle.h"
#import <objc/runtime.h>
@implementation
UIViewController
(
Tracker
)
+
(
void
)
load
{
static
dispatch_once_t
onceToken
;
dispatch_once
(
&
onceToken
,
^
{
[
self
startViewPageExpoTracker
];
});
}
+
(
void
)
startViewPageExpoTracker
{
[
self
swizzleInstanceMethod
:
@selector
(
viewDidLoad
)
withSelector
:
@selector
(
swizzle_viewDidLoad
)];
[
self
swizzleInstanceMethod
:
@selector
(
viewDidAppear
:)
withSelector
:
@selector
(
swizzle_viewDidAppear
:
)];
[
self
swizzleInstanceMethod
:
@selector
(
viewDidDisappear
:)
withSelector
:
@selector
(
swizzle_viewDidDisappear
:
)];
}
-
(
void
)
swizzle_viewDidLoad
{
[
self
swizzle_viewDidLoad
];
if
(
self
.
needExpo
)
{
self
.
view
.
pageCtrl
=
self
;
}
}
-
(
void
)
swizzle_viewDidAppear
:
(
BOOL
)
animated
{
[
self
swizzle_viewDidAppear
:
animated
];
if
(
self
.
needExpo
)
{
self
.
view
.
pageCtrl
=
self
;
[
GMExposureManager
fetchViewForVisibleState
:
self
.
view
trackerType
:
GMViewTrackerTypeUIViewDidAppear
recursive
:
YES
];
}
}
-
(
void
)
swizzle_viewDidDisappear
:
(
BOOL
)
animated
{
if
(
self
.
needExpo
)
{
[[
GMExposureManager
sharedManager
]
endExpoTrcakerForPageCtrl
:
self
];
}
[
self
swizzle_viewDidDisappear
:
animated
];
}
#pragma mark - UIScrollViewDelegate
-
(
void
)
scrollViewWillBeginDragging
:
(
UIScrollView
*
)
scrollView
{
if
(
scrollView
.
pageCtrl
.
needExpo
)
{
scrollView
.
preContentOffsetY
=
scrollView
.
contentOffset
.
y
;
}
}
-
(
void
)
scrollViewDidEndDragging
:
(
UIScrollView
*
)
scrollView
willDecelerate
:
(
BOOL
)
decelerate
{
if
(
scrollView
.
pageCtrl
.
needExpo
&&
!
decelerate
)
{
[
scrollView
calculateUpOrDownTimes
];
[
GMExposureManager
fetchViewForVisibleState
:
scrollView
trackerType
:
GMViewTrackerTypeUIScrollDragging
recursive
:
YES
];
}
}
-
(
void
)
scrollViewDidEndDecelerating
:
(
UIScrollView
*
)
scrollView
{
if
(
scrollView
.
pageCtrl
.
needExpo
)
{
[
scrollView
calculateUpOrDownTimes
];
[
GMExposureManager
fetchViewForVisibleState
:
scrollView
trackerType
:
GMViewTrackerTypeUIScrollDragging
recursive
:
YES
];
}
}
#pragma mark - GMPageExposureProtocol
-
(
void
)
setNeedExpo
:
(
BOOL
)
needExpo
{
objc_setAssociatedObject
(
self
,
@selector
(
needExpo
),
@
(
needExpo
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
BOOL
)
needExpo
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
needExpo
))
boolValue
];
}
-
(
void
)
setUp_slide_times
:
(
NSInteger
)
up_slide_times
{
objc_setAssociatedObject
(
self
,
@selector
(
up_slide_times
),
@
(
up_slide_times
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSInteger
)
up_slide_times
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
up_slide_times
))
integerValue
];
}
-
(
void
)
setDown_slide_times
:
(
NSInteger
)
down_slide_times
{
objc_setAssociatedObject
(
self
,
@selector
(
down_slide_times
),
@
(
down_slide_times
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSInteger
)
down_slide_times
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
down_slide_times
))
integerValue
];
}
-
(
void
)
setUp_loading_times
:
(
NSInteger
)
up_loading_times
{
objc_setAssociatedObject
(
self
,
@selector
(
up_loading_times
),
@
(
up_loading_times
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSInteger
)
up_loading_times
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
up_loading_times
))
integerValue
];
}
-
(
void
)
setDown_loading_times
:
(
NSInteger
)
down_loading_times
{
objc_setAssociatedObject
(
self
,
@selector
(
down_loading_times
),
@
(
down_loading_times
),
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSInteger
)
down_loading_times
{
return
[
objc_getAssociatedObject
(
self
,
@selector
(
down_loading_times
))
integerValue
];
}
-
(
void
)
setExtra_param
:
(
NSDictionary
*
)
extra_param
{
objc_setAssociatedObject
(
self
,
@selector
(
extra_param
),
extra_param
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
-
(
NSDictionary
*
)
extra_param
{
return
objc_getAssociatedObject
(
self
,
@selector
(
extra_param
));
}
@end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment