diff --git a/StreetHawk/Classes/Core/Publish/SHApp.m b/StreetHawk/Classes/Core/Publish/SHApp.m index 9d41620..b52512f 100644 --- a/StreetHawk/Classes/Core/Publish/SHApp.m +++ b/StreetHawk/Classes/Core/Publish/SHApp.m @@ -26,6 +26,7 @@ #import "SHFriendlyNameObject.h" #import "SHUtils.h" #import "SHHTTPSessionManager.h" //for set header "X-Installid" +#import "SHBaseViewController.h" //for aspect //header from System #import //for spotlight search #import //for kUTTypeImage @@ -360,6 +361,8 @@ - (void)registerInstallForApp:(nonnull NSString *)appKey withDebugMode:(BOOL)isD { action(); } + //aspect view controller + [StreetHawkViewControllerSwizzle aspect]; } - (void)registerInstallForApp:(nonnull NSString *)appKey segmentId:(NSString *)segmentId withDebugMode:(BOOL)isDebugMode diff --git a/StreetHawk/Classes/Core/Publish/SHBaseViewController.h b/StreetHawk/Classes/Core/Publish/SHBaseViewController.h index 23981fe..6e12030 100644 --- a/StreetHawk/Classes/Core/Publish/SHBaseViewController.h +++ b/StreetHawk/Classes/Core/Publish/SHBaseViewController.h @@ -53,12 +53,21 @@ @end /** - Base class for all view controller inherit from UIViewController. It sends logs when enter/exit this VC. + Method to aspect StreetHawk related functions into normal view controller. + */ +@interface StreetHawkViewControllerSwizzle : NSObject + ++ (void)aspect; + +@end + +/** + Keep StreetHawk base vc classes for compatibility. */ @interface StreetHawkBaseViewController : UIViewController /** - Some customer view controller may be inherited not in purpose (such as base vc do inherit). + Some customer view controller may be inherited not in purpose (such as base vc do inherit). Use this property to exclude them from being treated as StreetHawk behavior vc. */ @property (nonatomic) BOOL excludeBehavior; @@ -70,9 +79,6 @@ @end -/** - Base class for all view controller inherit from UITableViewController. It sends logs when enter/exit this VC. - */ @interface StreetHawkBaseTableViewController : UITableViewController /** @@ -88,9 +94,6 @@ @end -/** - Base class for all view controller inherit from UICollectionViewController. It sends logs when enter/exit this VC. - */ @interface StreetHawkBaseCollectionViewController : UICollectionViewController /** diff --git a/StreetHawk/Classes/Core/Publish/SHBaseViewController.m b/StreetHawk/Classes/Core/Publish/SHBaseViewController.m index ed8cb19..b647e09 100644 --- a/StreetHawk/Classes/Core/Publish/SHBaseViewController.m +++ b/StreetHawk/Classes/Core/Publish/SHBaseViewController.m @@ -23,6 +23,8 @@ #import "SHCoverView.h" //for cover view //header from System #import //for associate object +//header from third-party +#import "SHAspects.h" @interface NSString (SHEnterExitExt) @@ -51,132 +53,150 @@ - (NSString *)refinePageName @end -@implementation StreetHawkBaseViewController - -- (id)init -{ - if (self = [super init]) - { - self.excludeBehavior = NO; - } - return self; -} - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) - { - self.excludeBehavior = NO; - } - return self; -} +@implementation StreetHawkViewControllerSwizzle -- (id)initWithCoder:(NSCoder *)aDecoder ++ (void)aspect { - if (self = [super initWithCoder:aDecoder]) - { - self.excludeBehavior = NO; - } - return self; -} - -- (void)viewDidLoad + //aspect UIViewController + [UIViewController aspect_hookSelector:@selector(viewDidLoad) + withOptions:SHAspectPositionAfter + usingBlock:^(id aspectInfo) { + UIViewController *vc = (UIViewController *)aspectInfo.instance; + if (![self checkReactNative] + && ![self ignoreVC:vc]) + { + [self _doViewDidLoad:vc]; + } + } error:nil]; + + //tricky: Record `viewWillAppear` as backup, become in canceled pop up `viewDidAppear` is not called. + [UIViewController aspect_hookSelector:@selector(viewWillAppear:) + withOptions:SHAspectPositionAfter + usingBlock:^(id aspectInfo, BOOL animated) { + UIViewController *vc = (UIViewController *)aspectInfo.instance; + if (![self checkReactNative] + && ![self ignoreVC:vc]) + { + [self _doViewWillAppear:vc]; + } + [self hookReactNative:vc]; + } error:nil]; + + //tricky: Here must use `viewDidAppear` and `viewWillDisappear`. + //if use `viewWillAppear`, two issues: 1) Launch App `viewWillAppear` is called before `didFinishLaunchingWithOptions`, making home page not logged; 2) `viewWillAppear` cannot get self.view.window, always null, making it's unknown to check `SHCoverWindow`(deprecated). + //if use `viewDidDisappear`, present modal view controller has problem. For example, A present modal B, first call B `viewDidAppear` then call A `viewDidDisappear`, making the order wrong, expecting A disappear first and then B appear. Use `viewWillDisappear` solve this problem. + //the mix just match requirement: disappear first and appear. + [UIViewController aspect_hookSelector:@selector(viewDidAppear:) + withOptions:SHAspectPositionAfter + usingBlock:^(id aspectInfo, BOOL animated) { + UIViewController *vc = (UIViewController *)aspectInfo.instance; + if (![self checkReactNative] + && ![self ignoreVC:vc]) + { + [self _doViewDidAppear:vc]; + } + } error:nil]; + + [UIViewController aspect_hookSelector:@selector(viewWillDisappear:) + withOptions:SHAspectPositionAfter + usingBlock:^(id aspectInfo, BOOL animated) { + UIViewController *vc = (UIViewController *)aspectInfo.instance; + if (![self checkReactNative] + && ![self ignoreVC:vc]) + { + [self _doViewWillDisappear:vc]; + } + } error:nil]; +} + ++ (void)_doViewDidLoad:(UIViewController *)vc { - [super viewDidLoad]; - if (![self checkReactNative]) { - [self _doViewDidLoad]; - } -} - -- (void)_doViewDidLoad { - if ([self respondsToSelector:@selector(displayDeepLinkingToUI)]) + if ([vc respondsToSelector:@selector(displayDeepLinkingToUI)]) { - [self performSelector:@selector(displayDeepLinkingToUI)]; + [vc performSelector:@selector(displayDeepLinkingToUI)]; } - if (!self.excludeBehavior && !shIsSDKViewController(self)) //Not show tip and super tag for SDK vc + if (!shIsSDKViewController(vc)) //Not show tip and super tag for SDK vc { - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_CustomFeed_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_SuperTag_Notification" object:nil userInfo:@{@"vc": self}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowTip_Notification" object:nil userInfo:@{@"vc": vc}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_CustomFeed_Notification" object:nil userInfo:@{@"vc": vc}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_SuperTag_Notification" object:nil userInfo:@{@"vc": vc}]; } } -//tricky: Record `viewWillAppear` as backup, become in canceled pop up `viewDidAppear` is not called. -- (void)viewWillAppear:(BOOL)animated ++ (void)_doViewWillAppear:(UIViewController *)vc { - [super viewWillAppear:animated]; - if (![self checkReactNative]) { - [self _doViewWillAppear]; - } - [self hookReactNative]; -} - -- (void)_doViewWillAppear { - if (!self.excludeBehavior && !shIsSDKViewController(self)) + if (!shIsSDKViewController(vc)) { - [[NSUserDefaults standardUserDefaults] setObject:self.class.description forKey:@"ENTERBAK_PAGE_HISTORY"]; + [[NSUserDefaults standardUserDefaults] setObject:[shAppendUniqueSuffix(vc) refinePageName] forKey:@"ENTERBAK_PAGE_HISTORY"]; [[NSUserDefaults standardUserDefaults] synchronize]; } } -//tricky: Here must use `viewDidAppear` and `viewWillDisappear`. -//if use `viewWillAppear`, two issues: 1) Launch App `viewWillAppear` is called before `didFinishLaunchingWithOptions`, making home page not logged; 2) `viewWillAppear` cannot get self.view.window, always null, making it's unknown to check `SHCoverWindow`(deprecated). -//if use `viewDidDisappear`, present modal view controller has problem. For example, A present modal B, first call B `viewDidAppear` then call A `viewDidDisappear`, making the order wrong, expecting A disappear first and then B appear. Use `viewWillDisappear` solve this problem. -//the mix just match requirement: disappear first and appear. -- (void)viewDidAppear:(BOOL)animated ++ (void)_doViewDidAppear:(UIViewController *)vc { - [super viewDidAppear:animated]; - if (![self checkReactNative]) { - [self _doViewDidAppear]; + if (!shIsSDKViewController(vc)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). + { + [StreetHawk shNotifyPageEnter:[shAppendUniqueSuffix(vc) refinePageName]]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_EnterVC_Notification" object:nil userInfo:@{@"vc": vc}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowAuthor_Notification" object:nil userInfo:@{@"vc": vc}]; } } -- (void)_doViewDidAppear ++ (void)_doViewWillDisappear:(UIViewController *)vc { - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). + if (!shIsSDKViewController(vc)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). { - [StreetHawk shNotifyPageEnter:[shAppendUniqueSuffix(self) refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_EnterVC_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowAuthor_Notification" object:nil userInfo:@{@"vc": self}]; + [StreetHawk shNotifyPageExit:[shAppendUniqueSuffix(vc) refinePageName]]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ForceDismissTip_Notification" object:nil userInfo:@{@"vc": vc}]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ExitVC_Notification" object:nil userInfo:@{@"vc": vc}]; } } -- (void)viewWillDisappear:(BOOL)animated ++ (BOOL)ignoreVC:(UIViewController *)vc { - [super viewWillDisappear:animated]; - if (![self checkReactNative]) { - [self _doViewWillDisappear]; + //aspect affects all UIViewControllers, including system's such as UINavigationController. + //These system's are container and they should not do StreetHawk behaviors. Ignore them before doing action. + //More system UI found such as UIInputWindowController, UIAlertController etc. + if ([vc.class.description hasPrefix:@"UI"]) + { + return YES; } -} - -- (void)_doViewWillDisappear -{ - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). + if ([vc respondsToSelector:@selector(excludeBehavior)]) { - [StreetHawk shNotifyPageExit:[shAppendUniqueSuffix(self) refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ForceDismissTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ExitVC_Notification" object:nil userInfo:@{@"vc": self}]; + SEL selector = @selector(excludeBehavior); + NSMethodSignature *ms = [vc methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:ms]; + invocation.target = vc; + invocation.selector = selector; + [invocation invoke]; + BOOL rtnVal = NO; + [invocation getReturnValue:&rtnVal]; + if (rtnVal) + { + return YES; + } } + return NO; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" -- (BOOL)checkReactNative { ++ (BOOL)checkReactNative { Class rcClass = NSClassFromString(@"RCTRootView"); return (rcClass != nil); } -- (void)hookReactNative { ++ (void)hookReactNative:(UIViewController *)vc { Class rcClass = NSClassFromString(@"RCTRootView"); if (!rcClass) { return; } - if (![self.view respondsToSelector:@selector(bridge)]) { + if (![vc.view respondsToSelector:@selector(bridge)]) { return; } - id bridge = [self.view valueForKey:@"bridge"]; + id bridge = [vc.view valueForKey:@"bridge"]; if (!bridge) { return; } @@ -202,48 +222,48 @@ - (void)hookReactNative { [observerCoordinator performSelector:@selector(addObserver:) withObject:self]; } -BOOL _uiMayChange = false; -NSDate *_lastChangeDate = nil; - -- (void)uiManagerWillPerformMounting:(id)manager{ - id blocks = nil; - @try { - blocks = [manager valueForKey:@"_pendingUIBlocks"]; - } - @catch(NSException *e) {} - if (!blocks) { - return; - } - if (![blocks respondsToSelector:@selector(count)]) { - return; - } - int changePageCount = (int)[blocks performSelector:@selector(count)]; - if (changePageCount > 1) { - if (!_uiMayChange) { - // equal to viewWillDisappear - [self _doViewWillDisappear]; - } - _uiMayChange = true; - _lastChangeDate = [NSDate date]; - } - else if (_uiMayChange) { - NSDate *now = [NSDate date]; - NSTimeInterval changeTimeInterval = [now timeIntervalSinceDate:_lastChangeDate]; - if (changeTimeInterval > 1.0f) { - _uiMayChange = false; - // equal to viewDidLoad + viewWillAppear + viewDidAppear - [self _doViewDidLoad]; - [self _doViewWillAppear]; - [self _doViewDidAppear]; - } - } -} - #pragma clang diagnostic pop +//BOOL _uiMayChange = false; +//NSDate *_lastChangeDate = nil; +// +//- (void)uiManagerWillPerformMounting:(id)manager{ +// id blocks = nil; +// @try { +// blocks = [manager valueForKey:@"_pendingUIBlocks"]; +// } +// @catch(NSException *e) {} +// if (!blocks) { +// return; +// } +// if (![blocks respondsToSelector:@selector(count)]) { +// return; +// } +// int changePageCount = (int)[blocks performSelector:@selector(count)]; +// if (changePageCount > 1) { +// if (!_uiMayChange) { +// // equal to viewWillDisappear +// [self _doViewWillDisappear]; +// } +// _uiMayChange = true; +// _lastChangeDate = [NSDate date]; +// } +// else if (_uiMayChange) { +// NSDate *now = [NSDate date]; +// NSTimeInterval changeTimeInterval = [now timeIntervalSinceDate:_lastChangeDate]; +// if (changeTimeInterval > 1.0f) { +// _uiMayChange = false; +// // equal to viewDidLoad + viewWillAppear + viewDidAppear +// [self _doViewDidLoad]; +// [self _doViewWillAppear]; +// [self _doViewDidAppear]; +// } +// } +//} + @end -@implementation StreetHawkBaseTableViewController +@implementation StreetHawkBaseViewController - (id)init { @@ -272,60 +292,44 @@ - (id)initWithCoder:(NSCoder *)aDecoder return self; } -- (id)initWithStyle:(UITableViewStyle)style +@end + +@implementation StreetHawkBaseTableViewController + +- (id)init { - if (self = [super initWithStyle:style]) + if (self = [super init]) { self.excludeBehavior = NO; } return self; } -- (void)viewDidLoad -{ - [super viewDidLoad]; - if ([self respondsToSelector:@selector(displayDeepLinkingToUI)]) - { - [self performSelector:@selector(displayDeepLinkingToUI)]; - } - if (!self.excludeBehavior && !shIsSDKViewController(self)) //Not show tip and super tag for SDK vc - { - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_CustomFeed_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_SuperTag_Notification" object:nil userInfo:@{@"vc": self}]; - } -} - -- (void)viewWillAppear:(BOOL)animated +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - [super viewWillAppear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { - [[NSUserDefaults standardUserDefaults] setObject:self.class.description forKey:@"ENTERBAK_PAGE_HISTORY"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + self.excludeBehavior = NO; } + return self; } -- (void)viewDidAppear:(BOOL)animated +- (id)initWithCoder:(NSCoder *)aDecoder { - [super viewDidAppear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). + if (self = [super initWithCoder:aDecoder]) { - [StreetHawk shNotifyPageEnter:[self.class.description refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_EnterVC_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowAuthor_Notification" object:nil userInfo:@{@"vc": self}]; + self.excludeBehavior = NO; } + return self; } -- (void)viewWillDisappear:(BOOL)animated +- (id)initWithStyle:(UITableViewStyle)style { - [super viewWillDisappear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). + if (self = [super initWithStyle:style]) { - [StreetHawk shNotifyPageExit:[self.class.description refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ForceDismissTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ExitVC_Notification" object:nil userInfo:@{@"vc": self}]; + self.excludeBehavior = NO; } + return self; } @end @@ -368,53 +372,6 @@ - (id)initWithCollectionViewLayout:(UICollectionViewLayout *)layout return self; } -- (void)viewDidLoad -{ - [super viewDidLoad]; - if ([self respondsToSelector:@selector(displayDeepLinkingToUI)]) - { - [self performSelector:@selector(displayDeepLinkingToUI)]; - } - if (!self.excludeBehavior && !shIsSDKViewController(self)) //Not show tip and super tag for SDK vc - { - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_CustomFeed_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_SuperTag_Notification" object:nil userInfo:@{@"vc": self}]; - } -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) - { - [[NSUserDefaults standardUserDefaults] setObject:self.class.description forKey:@"ENTERBAK_PAGE_HISTORY"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). - { - [StreetHawk shNotifyPageEnter:[self.class.description refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_EnterVC_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ShowAuthor_Notification" object:nil userInfo:@{@"vc": self}]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - if (!self.excludeBehavior && !shIsSDKViewController(self)) //several internal used vc not need log, such as SHFeedbackViewController, SHSlideWebViewController (it calls appear even not show). - { - [StreetHawk shNotifyPageExit:[self.class.description refinePageName]]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ForceDismissTip_Notification" object:nil userInfo:@{@"vc": self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"SH_PointziBridge_ExitVC_Notification" object:nil userInfo:@{@"vc": self}]; - } -} - @end @interface UIViewController (SHViewExt_private) diff --git a/streethawk.podspec b/streethawk.podspec index 9611b04..7213aa8 100644 --- a/streethawk.podspec +++ b/streethawk.podspec @@ -35,6 +35,7 @@ Pod::Spec.new do |s| sp.dependency 'Masonry' sp.dependency 'WebViewJavascriptBridge' sp.dependency 'SDWebImage/GIF' + sp.dependency 'Aspects' end s.subspec 'Growth' do |sp|