SHApp Class Reference
Inherits from | NSObject |
---|---|
Conforms to | UIApplicationDelegate |
Declared in | SHApp.h |
Overview
The SHApp Class is core of whole SDK. It contains almost all the functions.
Normal usage:
SHApp is singleton, access it by
[SHApp sharedInstance]
orStreetHawk
.When your Application starts, usually in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
, callregisterInstallForApp:withDebugMode:
to initialize all required StreetHawk features.This is simple, here are a few lines of sample code:
[StreetHawk registerInstallForApp:appId(registered with the StreetHawk Cloud) withDebugMode:Yes];
Add tag for user is simple, code like:
[StreetHawk tagString:@"a@a.com" forKey:@"sh_email"];
Necessary project settings:
To have your StreetHawk-enabled Application work properly, some project settings are necessary.
- Add “-ObjC” into “Other Linker Flags”. It’s necessary for using SDK otherwise link error may occur for category methods.
- Add resource bundle into project. It’s necessary for SDK’s xib and image resources.
- Add “Background fetch” in “Background Modes”, since iOS 7.
- Add “NSLocationAlwaysUsageDescription” or “NSLocationWhenInUseUsageDescription” in Info.plist for enabling location service since iOS 8.
- Because StreetHawk SDK uses significant location change at background, there is NO need to add “Required background modes” with location service.
Extension for logs:
- The Profile for this install will be tagged with the given values. You can use these tags to run campaigns.
[StreetHawk tagString:@“a@a.com” forKey:@“sh_email”];
- Remove a tagged profile if not need it anymore.
[StreetHawk removeTag:@“sh_email”];
Extension for Feed API.
Extension for Notification API.
Extension for Crash API.
Extension for Crash API.
Extension for install register or update:
Call registerOrUpdateInstallWithHandler:
to register or update install attributes.
Extension for Growth API.
Create and initialize
+ sharedInstance
Singleton creator of SHApp. Normally use StreetHawk
to represent [SHApp sharedInstance]
.
+ (SHApp *)sharedInstance
Return Value
Singleton SHApp instance.
Declared In
SHApp.h
– registerInstallForApp:withDebugMode:
Initialize for an Application, setting up the environment.
- (void)registerInstallForApp:(NSString *)appKey withDebugMode:(BOOL)isDebugMode
Parameters
appKey |
The global name of the app. This is registered in StreetHawk server, once registered it cannot change. There are three ways to set |
---|---|
isDebugMode |
The mode of whether print NSLog in Xcode console. |
Declared In
SHApp.h
– registerInstallForApp:withDebugMode:withiTunesId:
Deprecated, use - (void)registerInstallForApp:(NSString *)
appKey withDebugMode:(BOOL)
isDebugMode;
instead. iTunesId
is setup in web console, or by property @property (nonatomic, strong) NSString *
itunesAppId;
.
- (void)registerInstallForApp:(NSString *)appKey withDebugMode:(BOOL)isDebugMode withiTunesId:(NSString *)iTunesId
Declared In
SHApp.h
Global properties
appKey
The allocated name or code for this app as set in the StreetHawk Cloud, for example “SHSheridan1”. It’s mandatory for an Application to work. Check - (void)registerInstallForApp:(NSString *)appKey withDebugMode:(BOOL)
isDebugMode for how it works.
@property (nonatomic, strong) NSString *appKey
Declared In
SHApp.h
isDebugMode
Decide whether need to show debug log in console.
@property (nonatomic) BOOL isDebugMode
Declared In
SHApp.h
itunesAppId
The App id after register in iTunes, for example @“337064413”. It used for rating and upgrading App, if this id is not setup, rating or upgrading dialog will not promote.
@property (nonatomic, strong) NSString *itunesAppId
Declared In
SHApp.h
clientVersion
The application version and build version of current Application, formatted as @“[CFBundleShortVersionString] ([CFBundleVersion])”, for example @“1.2.7 (10)”. This is version for Application project, not for SDK. Use version
to get StreetHawkCore.framework SDK version.
@property (nonatomic, strong, readonly) NSString *clientVersion
Declared In
SHApp.h
version
Version for StreetHawkCore.framework SDK, formatted as [framework version] (X.Y.Z), for example “1.2.5”. In Finder [framework version] is visible by view framework file info.
@property (nonatomic, strong, readonly) NSString *version
Declared In
SHApp.h
currentInstall
Before successfully install, it’s nil. After install once, it’s the install instance.
@property (nonatomic, strong) SHInstall *currentInstall
Declared In
SHApp.h
install_semaphore
Make sure installs/register or installs/update happen in sequence.
@property (nonatomic, strong) dispatch_semaphore_t install_semaphore
Declared In
SHApp.h
developmentPlatform
An enum for current App’s development platform, refer to SHDevelopmentPlatform
for supporting platforms. This is only used internally, and setup by Phonegap plugin, Titanium module, Xamarin binding etc. Normal customer does not need to change it.
@property (nonatomic, readonly) SHDevelopmentPlatform developmentPlatform
Declared In
SHApp.h
advertisingIdentifier
StreetHawk can use AdvertisingIdentifier
to help trace end-user, however it requires customer’s App is capable to use advertising function according to Apple’s agreement. If customer’s App can get this, pass into StreetHawk.
@property (nonatomic, strong) NSString *advertisingIdentifier
Discussion
The steps is:
- Add framework AdSupport.framework.
- Add line: #import <AdSupport/ASIdentifierManager.h>.
Add code from system to get advertising identifier and pass to StreetHawk SDK.
if([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) { NSString *idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; StreetHawk.advertisingIdentifier = idfaString; }
Since iOS native version 1.8.3 it can automatically capture advertising identifier as long as you add framework AdSupport.framework, thus only step 1 is required.
If customer App set StreetHawk.advertisingIdentifier = XXX
manually, this will be used at priority. In case SDK capture string is different from customer’s, use customer’s. Customer can also set `StreetHawk.advertisingIdentifier = nil;
to give up his own and enable automatically capture.
Declared In
SHApp.h
autoIntegrateAppDelegate
StreetHawk requires AppDelegate has some common functions, if autoIntegrateAppDelegate
is YES (by default), customer App does not need to manually implement any of the push-related UIApplicationDelegate protocol methods or pass notifications to the library. The library is able to do this by setting itself as the app delegate, intercepting messages and forwarding them to your original app delegate. This must be setup before register install. It’s YES by default but if custome App set it to NO, customer App must implement these functions manually:
@property (nonatomic) BOOL autoIntegrateAppDelegate
Discussion
`- (void)application:(UIApplication )application didRegisterUserNotificationSettings:(UIUserNotificationSettings )notificationSettings //since iOS 8.0 { [StreetHawk handleUserNotificationSettings:notificationSettings]; }
(void)application:(UIApplication )application didRegisterForRemoteNotificationsWithDeviceToken:(NSData )deviceToken { [StreetHawk setApnsDeviceToken:deviceToken]; }
(void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo { [StreetHawk handleRemoteNotification:userInfo treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:nil]; }
(void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [StreetHawk handleRemoteNotification:userInfo treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:completionHandler]; }
(void)application:(UIApplication )application handleActionWithIdentifier:(NSString )identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler { [StreetHawk handleRemoteNotification:userInfo withActionId:identifier needComplete:YES completionHandler:completionHandler]; }
(void)application:(UIApplication )application didReceiveLocalNotification:(UILocalNotification )notification { [StreetHawk handleLocalNotification:notification treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:nil]; }
(void)application:(UIApplication )application handleActionWithIdentifier:(NSString )identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler { [StreetHawk handleLocalNotification:notification withActionId:identifier needComplete:YES completionHandler:completionHandler]; }
(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [StreetHawk shRegularTask:completionHandler needComplete:YES]; }
(BOOL)application:(UIApplication )application openURL:(NSURL )url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { return [StreetHawk openURL:url]; }
(BOOL)application:(UIApplication )application continueUserActivity:(NSUserActivity )userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { return [StreetHawk continueUserActivity:userActivity]; }`
Declared In
SHApp.h
Handlers
openUrlHandler
Callback to let customer App to handle deeplinking url.
Launch view controller in such scenarios:
1. Click a link “<app_scheme://host/path?param=value>” in Email or social App, launch this App by openURL
.
2. Notification “Launch page activity” sends from StreetHawk campaign with deeplinking url “<app_scheme://host/path?param=value>”, and host not equal “launchvc”.
3. Growth recommend friend to install a new App, after first launch Growth automatically match a deeplinking url “<app_scheme://host/path?param=value>” and launch view controller.
@property (nonatomic, copy) SHOpenUrlHandler openUrlHandler
Declared In
SHApp.h
logger
An instance to deal with log.
@property (nonatomic, readonly, weak) SHLogger *logger
Declared In
SHApp.h
Global properties and methods
– shCustomActivityList:
Push notification 8004/8006/8007 is to launch a certain view controller, however it’s difficult for server to know “how to launch the view controller”. In iOS platform it requires the following elements to initialize a view controller:
- (BOOL)shCustomActivityList:(NSArray *)arrayFriendlyNameObj
Parameters
arrayFriendlyNameObj |
The array with each object defines a friendly name object. |
---|
Return Value
If the format is correct for register friendly name, return YES; otherwise return NO, and error information can refer to console log output (withDebugMode:YES).
Discussion
- view controller class name – mandatory, must be a class inherit from UIViewController.
- xib for iPhone – optional, used for intialize the view controller on iPhone device, and xib name is different from class name; if nil use class name as the xib name.
- xib for iPad – optional, used for intialize the view controller on iPad device, and xib name is different from class name; if nil use class name as the xib name.
For Phonegap App, it can be load an html page. Refer to shPGHtmlReceiver
and shPGLoadHtml
.
Server may have problem to send push notification, because:
- iOS and Android possibly have different class name, different mechanism to launch view controller. Need to consider the platform details.
- Applications even running on the same platform can have different ac/vc names in different versions.
To make server simpler in this aspect, client side provide a way to register “friendly name”, while locally store the map for vc and xib. For example, client side can register: friendly name = “login”, vc = “MyLoginViewController”, xib_iphone = “MyLoginViewController_iphone”, xib_ipad = “MyLoginViewController_ipad”, so server display “login” in web console when sending push notification. After client side receive this notification, find vc and xib locally to initialize view controller.
Note 1: Friendly names will be visible in StreetHawk web interface and they should be the same across different platforms (ios, android etc.).
Note 2: Use friendly name = “register” (FRIENDLYNAME_REGISTER) for register page, which can be specifically handled by 8006 push notification.
Note 3: Use friendly name = “login” (FRIENDLYNAME_LOGIN) for login page, which can be specifically handled by 8007 push notification.
**Note 4: The friendly name will be submitted when next app_status
with “submit_views” = true.
Declared In
SHApp.h
– shFeedback:needInputDialog:needConfirmDialog:withTitle:withMessage:withPushData:
API to trigger feedback UI and send feedback. It behaves in this way:
- (void)shFeedback:(NSArray *)arrayChoice needInputDialog:(BOOL)needInput needConfirmDialog:(BOOL)needConfirm withTitle:(NSString *)infoTitle withMessage:(NSString *)infoMessage withPushData:(PushDataForApplication *)pushData
Parameters
arrayChoice |
The option choice list. For example, @[@“Product not Available”, @“Wrong Address”, @“Description mismatch”]. It can be nil. |
---|---|
needInput |
Whether need to show free text input dialog. If |
needConfirm |
Whether need to show confirm alert dialog of Cancel/Yes Please!. When App in FG and notification arrive needs to show confirm dialog. |
infoTitle |
The title display on choice list. If nil shows “ |
infoMessage |
The message display on choice list. It can be nil or empty. |
pushData |
When used in notification, pass in payload from server. If not used in notification, pass nil. |
Discussion
- If define option choice list by
arrayChoice
, a choice list will show first, after user select one of the option: a) IfneedInput
is Yes, an input UI with the selected choice is displayed for user to input free text; b) IfneedInput
is No user’s selected choice is posted to server directly. - If
arrayChoice
= nil or empty, input free text UI is displayed for user to type. - Choice title is mandatory, free text detail content is optional.
Declared In
SHApp.h
– shSendFeedbackWithTitle:withContent:withHandler:
Submit feedback request to server without UI.
- (void)shSendFeedbackWithTitle:(NSString *)title withContent:(NSString *)content withHandler:(SHCallbackHandler)handler
Parameters
title |
Feedback title submit to server. |
---|---|
content |
Feedback content submit to server. |
handler |
Request callback handler. |
Declared In
SHApp.h
– shNotifyPageEnter:
Send enter log (8108) for page
. For trace view it’s recommended to inherit from StreetHawkViewController
or StreetHawkBaseViewController
, which automatically call shNotifyPageEnter
on viewDidAppear
and shNotifyPageExit
on viewDidDisappear
. But for App which cannot do inheritance (for example Phonegap, Titanium and Xamarin), call shNotifyPageEnter
and shNotifyPageExit
explictly.
Note: if history has a page record, it sends exit log (8108) for the history. This is a workaround fix for “forget” add shNotifyPageExit
on viewDidDisappear
, and more importantly, some App such as Phonegap cannot call shNotifyPageExit
.
- (void)shNotifyPageEnter:(NSString *)page
Parameters
page |
Enter page name. It cannot be nil. For UIViewController it’s class name such as |
---|
Declared In
SHApp.h
– shNotifyPageExit:
Send exit log (8109) for page
. For trace view it’s recommended to inherit from StreetHawkViewController
or StreetHawkBaseViewController
, which automatically call shNotifyPageEnter
on viewDidAppear
and shNotifyPageExit
on viewDidDisappear
. But for App which cannot do inheritance (for example Phonegap, Titanium and Xamarin), call shNotifyPageEnter
and shNotifyPageExit
explictly.
- (void)shNotifyPageExit:(NSString *)page
Parameters
page |
Exit page name. It cannot be nil. For UIViewController it’s class name such as |
---|
Declared In
SHApp.h
– getFormattedDateTime:
Get StreetHawk formatted datetime string for given seconds since 1970.
- (NSString *)getFormattedDateTime:(NSTimeInterval)seconds
Parameters
seconds |
Seconds since 1970. |
---|
Return Value
Streethawk formatted string in style yyyy-MM-dd HH:mm:ss
, such as 2016-10-21 16:23:18.
Declared In
SHApp.h
– getCurrentFormattedDateTime
Get current datetime string in Streethawk format (UTC and yyyy-MM-dd HH:mm:ss).
- (NSString *)getCurrentFormattedDateTime
Return Value
Streethawk formatted string in style yyyy-MM-dd HH:mm:ss
, such as 2016-10-21 16:23:18.
Declared In
SHApp.h
– getAppDelegate
Get real App’s delegate. If autoIntegrateAppDelegate
= YES
which is by default, the [UIApplication sharedApplication].delegate is actually SHInterceptor
. It works well in Object-C as it does not really check type when do type-cast AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
, and forward selector works; however it cause crash in Swift which force type check when downcast, thus let shared = UIApplication.sharedApplication().delegate as! AppDelegate
crash because SHInterceptor
cannot be casted to AppDelegate
. To avoid public type SHInterceptor
and to make Swift can get real App delegate, add this API to return the value.
- (id)getAppDelegate
Return Value
Get real App delegate.
Declared In
SHApp.h
Background Regular Task
– shRegularTask:needComplete:
Perform regular task at certain time interval. It leverages UIApplicationDelegate
function - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
to do some tasks at background, and when App in foreground, it calls each time when App become active. Note:
- (void)shRegularTask:(void ( ^ ) ( UIBackgroundFetchResult result ))completionHandler needComplete:(BOOL)needComplete
Discussion
- Customer App must have Background mode -> fetch enabled to have this work.
- This function is available since iOS 7.0. Previous iOS system cannot support it.
- User App implement this function by calling it in AppDelegate.m if NOT auto-integrate. If
StreetHawk.
autoIntegrateAppDelegate= YES;
make sure NOT call this otherwise cause dead loop. Code snippet:- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
[StreetHawk shRegularTask:completionHandler needComplete:YES];
}
This function perform following tasks: 1. If user’s location service is enabled, time interval one hour, send non-priority log for current user location (code=19). 2. Sends priority heartbeat log in 6 hours(code=8051).
Declared In
SHApp.h
Open Url Scheme
– openURL:
Handle open URL, customer’s App must register “URL Types” in Info.plist with its own scheme. User App implement this function by calling it in AppDelegate.m if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
- (BOOL)openURL:(NSURL *)url
Return Value
[StreetHawk openURL:url];
}
or
- (BOOL)application:(UIApplication )app openURL:(NSURL )url options:(NSDictionary
{`
[StreetHawk openURL:url];
}`
This function performs following tasks:
1. StreetHawk formatted deeplinking: open a view and call function of the view. The url must be formatted as: StreetHawk.openUrlHandler = ^(NSURL *openUrl) {}
.
Declared In
SHApp.h
Permission
– launchSystemPreferenceSettings
Show this App’s preference settings page. Only available since iOS 8. In previous iOS nothing happen.
- (BOOL)launchSystemPreferenceSettings
Return Value
YES if can show preference page since iOS 8; NO if called in previous iOS and nothing happen.
Declared In
SHApp.h
Spotlight and Search
– indexSpotlightSearchForIdentifier:forDeeplinking:withSearchTitle:withSearchDescription:withThumbnail:withKeywords:
Add or update a spotlight search item into system. It’s an easy to use wrapper for CSSearchableItemAttributeSet
, CSSearchableItem
and CSSearchableIndex
, customer can gain same or more powerful result by using iOS API. However this wrapper API is more user friendly and easy to understand, besides it indexs deeplinking which will be used for StreetHawk.
openUrlHandler = ^(NSURL *openUrl) {}
.
- (void)indexSpotlightSearchForIdentifier:(NSString *)identifier forDeeplinking:(NSString *)deeplinking withSearchTitle:(NSString *)searchTitle withSearchDescription:(NSString *)searchDescription withThumbnail:(UIImage *)thumbnail withKeywords:(NSArray *)keywords
Parameters
identifier |
Mandatory, the identifier of this spotlight item. It’s unique for each item, if use same identifier it means update to existing item. |
---|---|
deeplinking |
Optional, the deeplinking url of this item. It will be used in |
searchTitle |
Optional, the title displaying in search result as title. |
searchDescription |
Optional, the description displaying in search result as description. |
thumbnail |
Optional, the thumbnail displaying in search result in left. |
keywords |
Optional, the keywords used for search, and it doesn’t display in search result. |
Declared In
SHApp.h
– deleteSpotlightItemsForIdentifiers:
Delete spotlight search items according to the array of identifiers.
- (void)deleteSpotlightItemsForIdentifiers:(NSArray *)arrayIdentifiers
Parameters
arrayIdentifiers |
An array of identifiers. |
---|
Declared In
SHApp.h
– deleteAllSpotlightItems
Delete all spotlight search items.
- (void)deleteAllSpotlightItems
Declared In
SHApp.h
– continueUserActivity:
Handle user activity from spotlight search result. User App implement this function by calling it in AppDelegate.m if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
- (BOOL)continueUserActivity:(NSUserActivity *)userActivity
Return Value
[StreetHawk continueUserActivity:userActivity];
}`
This function checks local mapping of spotlight item’s identifier
and deeplinking
, and trigger StreetHawk.openUrlHandler = ^(NSURL *openUrl) {}
. If find mapping use deeplinking
in callback’s openUrl
, otherwise use identifier
in callback’s openUrl
.
Declared In
SHApp.h
Other Methods
– tagCuid:
Internally call
- (BOOL)tagCuid:(NSString *)uniqueId
Parameters
uniqueId |
The unique user id from customer’s App. |
---|
Return Value
If tag to server return YES; if fail to send to server return NO.
Discussion
[StreetHawk tagString:uniqueId forKey:@"sh_cuid"];
Declared In
SHApp.h
– tagUserLanguage:
Internally call
- (BOOL)tagUserLanguage:(NSString *)language
Parameters
language |
The language of current device chosen, if pass nil it will automatically detect current device’s settings. |
---|
Return Value
If tag to server return YES; if fail to send to server return NO.
Discussion
[StreetHawk tagString:language forKey:@"sh_language"];
This is automatically called by SDK when App launch, and current device’s language is submitted. Customer can also use this API to submit their own.
Declared In
SHApp.h
– tagString:forKey:
Send log with code=8999. It’s used for tagging a string value for user. For example, you can tag user’s email as by:
- (BOOL)tagString:(NSString *)value forKey:(NSString *)key
Parameters
value |
The value for tag to the user profile. Cannot be empty. It can be NSString, or NSDictionary, or NSArray. |
---|---|
key |
The key for tag to the user profile. Cannot be empty. |
Return Value
If tag to server return YES; if fail to send to server return NO.
Discussion
[StreetHawk tagString:@"a@a.com" forKey:@"sh_email"];
This will send log comment as {“key”: “sh_email”, “string”: @“a@a.com”}.
Declared In
SHApp.h
– tagNumeric:forKey:
Send log with code=8999. It’s used for tagging a number value for user. For example, you can tag user’s favourite product count by:
- (BOOL)tagNumeric:(double)value forKey:(NSString *)key
Parameters
value |
The number value for tag to the user profile. |
---|---|
key |
The key for tag to the user profile. Cannot be empty. |
Return Value
If tag to server return YES; if fail to send to server return NO.
Discussion
[StreetHawk tagNumeric:8 forKey:@"fave_product"];
This will send log comment as {“key”: “fave_product”, “numeric”: [NSNumber numberWithDouble:8]}.
Declared In
SHApp.h
– tagDatetime:forKey:
Send log with code=8999. It’s used for tagging a date value for user. For example, you can tag user’s visit time by:
- (BOOL)tagDatetime:(NSDate *)value forKey:(NSString *)key
Parameters
value |
The date value for tag to the user profile. Cannot be empty. |
---|---|
key |
The key for tag to the user profile. Cannot be empty. |
Return Value
If tag to server return YES; if fail to send to server return NO.
Discussion
[StreetHawk tagDatetime:[NSDate date] forKey:@"visit_time"];
This will send log comment as {“key”: “visit_time”, “datetime”: [NSDate date]}.
Declared In
SHApp.h
– removeTag:
This is opposite function of tagString
or tagNumeric
or tagDatetime
. It’s to remove a user tag by the key, for example tagDatetime
adds {“key”: “sh_date_of_birth”, “datetime”: “2012-12-12 11:11:11”}, so this removeUserTag
can remove the tag by key = “sh_date_of_birth”. It send log with code=8998, comment = “{key : "sh_date_of_birth”}“.
- (BOOL)removeTag:(NSString *)key
Parameters
key |
Key for existing tag. Cannot be empty. |
---|
Return Value
If tag to server return YES; if fail to send to server return NO.
Declared In
SHApp.h
– incrementTag:
Send log with code=8997, comment={“key”: “
- (BOOL)incrementTag:(NSString *)key
Parameters
key |
Key for existing tag. Cannot be empty. |
---|
Return Value
If tag to server return YES; if fail to send to server return NO.
Declared In
SHApp.h
– incrementTag:forKey:
Send log with code=8997, comment={“key”: “
- (BOOL)incrementTag:(double)value forKey:(NSString *)key
Parameters
value |
The numeric value of how many the key should be increment. |
---|---|
key |
Key for existing tag. Cannot be empty. |
Return Value
If tag to server return YES; if fail to send to server return NO.
Declared In
SHApp.h
Other Methods
newFeedHandler
Callback happen when new feed detects by app_status/feed.
@property (nonatomic, copy) SHNewFeedsHandler newFeedHandler
Declared In
SHApp+Feed.h
– feed:withHandler:
Fetch feeds starting from offset
.
- (void)feed:(NSInteger)offset withHandler:(SHFeedsFetchHandler)handler
Parameters
offset |
Offset from which to fetch. |
---|---|
handler |
Callback for fetch handler, which return NSArray of SHFeedObject and error if meet. |
Declared In
SHApp+Feed.h
– sendFeedAck:
Send no priority logline for feedack. Customer developer should call this when a feed is read. Server may receive multiple loglines if user read one feed many times.
- (void)sendFeedAck:(NSString *)feed_id
Parameters
feed_id |
The feed id of reading feed. |
---|
Declared In
SHApp+Feed.h
– notifyFeedResult:withResult:
Send no priority logline for feed result.
- (void)notifyFeedResult:(NSString *)feed_id withResult:(SHResult)result
Parameters
feed_id |
The feed id of result feed. |
---|---|
result |
The result for accept, or postpone or decline. |
Declared In
SHApp+Feed.h
– notifyFeedResult:withResult:withStepId:deleteFeed:completed:
Send priority logline for feed result.
- (void)notifyFeedResult:(NSString *)feed_id withResult:(SHResult)result withStepId:(NSString *)stepId deleteFeed:(BOOL)feedDelete completed:(BOOL)complete
Parameters
feed_id |
The feed id of result feed. |
---|---|
result |
The result for accept, or postpone or decline. String must be accepted|postponed|rejected. |
stepId |
The ID or label about a step. |
feedDelete |
Set to true if feed items should be deleted from server for the given install. |
complete |
Set to true when tour complete. |
Declared In
SHApp+Feed.h
Other Methods
isDefaultNotificationEnabled
Default value to initialise isNotificationEnabled
, it’s called once when App first launch to set to isNotificationEnabled
. A typical usage is to delay asking for notification permission:
@property (nonatomic) BOOL isDefaultNotificationEnabled
Discussion
StreetHawk.isDefaultNotificationEnabled = NO; //not trigger remote/local notification when App launch.
[registerInstallForApp... ]; //do register, it will not register notification.
StreetHawk.
isNotificationEnabled = YES; //later trigger remote/local notification when need it.
Declared In
SHApp+Notification.h
isNotificationEnabled
Property to control enabling remote/local notification.
@property (nonatomic) BOOL isNotificationEnabled
Discussion
- If user set
isDefaultNotificationEnabled
= NO
before callingregisterInstallForApp...
, notification is not register and system permission dialog not promote. - Call
registerInstallForApp...
, it will not register notification. - Later when user wants to do register, set
isNotificationEnabled = YES
and system permission dialog promote. - Step 1 is optional. If not manually set
isDefaultNotificationEnabled
= NO
, it’s YES by default and system permission dialog show whenregisterInstallForApp
called at very first launch. - Set
isNotificationEnabled = NO
again afterisNotificationEnabled = YES
makes StreetHawk server receiverevoked
, and cause StreetHawk server not send notification to client. But it does not call system unregisterForRemoteNotification, so this App can still receive remote notification from other way.
Declared In
SHApp+Notification.h
notificationTypes
Property to define what kind of types will display for notification.
@property (nonatomic) NSUInteger notificationTypes
Discussion
- Before iOS 8, it’s combine of UIRemoteNotificationType, default value is UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeNewsstandContentAvailability.
- Since iOS 8, it’s combine of UIUserNotificationType, default value is UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound.
Customer is free to change the types, and make sure setting up this property before registering for remote notification, such as before calling [registerForInstall…].
Declared In
SHApp+Notification.h
notificationHandler
Handler for notification stuff.
@property (nonatomic, strong) SHNotificationHandler *notificationHandler
Declared In
SHApp+Notification.h
– setInteractivePushBtnPairs:
Submit notification’s interactive button pairs to server. The button pairs will be used to create campaign, and result in notification’s interactive buttons.
- (BOOL)setInteractivePushBtnPairs:(NSArray *)arrayPairs
Parameters
arrayPairs |
An array of |
---|
Return Value
If the format is correct, return YES; otherwise return NO, and error information can refer to console log output (withDebugMode:YES).
Declared In
SHApp+Notification.h
– registerForNotificationAndNotifyServer
According to system and customer code’s setting, call system API to register notification
- (void)registerForNotificationAndNotifyServer
Discussion
- Call system API to register notification unless
StreetHawk.isEnableNotification=NO
. - Check system enable notification or
StreetHawk.isEnableNotification=NO
to update flagrevoked
in StreetHawk server. This flag can stop StreetHawk server from sending remote notification.
Declared In
SHApp+Notification.h
– handleUserNotificationSettings:
Handle user notification settings callback. Call this in customer App’s UIApplicationDelegate if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)handleUserNotificationSettings:(UIUserNotificationSettings *)settings
Discussion
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[StreetHawk handleUserNotificationSettings:notificationSettings];
}
Declared In
SHApp+Notification.h
– setApnsDeviceToken:
Set StreetHawk SDK the token assigned by Apple for push notification. Previous device token is cached in NSUserDefaults key “APNS_DEVICE_TOKEN”. If current data is nil, it’s ignored and previous used. When setting a different device token, an install update sent immediately for updating server’s token. Call this in customer App’s UIApplicationDelegate if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)setApnsDeviceToken:(NSData *)value
Parameters
value |
Device token got from Apple’s register remote notification. |
---|
Discussion
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[StreetHawk setApnsDeviceToken:deviceToken];
}
Declared In
SHApp+Notification.h
– apnsDeviceToken
Get current saved apns device token as NSString.
- (NSString *)apnsDeviceToken
Declared In
SHApp+Notification.h
– handleUserNotificationInFG:completionHandler:
Since iOS 10 notification handler when App in foreground. If customer want to change to his own, just switch [UNUserNotificationCenter currentNotificationCenter].delegate = <custoemr’s_own_delegate>.
- (void)handleUserNotificationInFG:(UNNotification *)notification completionHandler:(void ( ^ ) ( UNNotificationPresentationOptions options ))completionHandler
Parameters
notification |
User notification received. |
---|---|
completionHandler |
Pass in system’s to finish when task is done. |
Declared In
SHApp+Notification.h
– handleUserNotificationInBG:completionHandler:
Since iOS 10 notification handler when App in background. If customer want to change to his own, just switch [UNUserNotificationCenter currentNotificationCenter].delegate = <custoemr’s_own_delegate>.
- (void)handleUserNotificationInBG:(UNNotificationResponse *)response completionHandler:(void ( ^ ) ( UNNotificationPresentationOptions options ))completionHandler
Parameters
response |
User notification response received. |
---|---|
completionHandler |
Pass in system’s to finish when task is done. |
Declared In
SHApp+Notification.h
– handleRemoteNotification:treatAppAs:needComplete:fetchCompletionHandler:
Customer Application should implement this in UIApplicationDelegate to forward handling to StreetHawk library if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)handleRemoteNotification:(NSDictionary *)userInfo treatAppAs:(SHAppFGBG)appFGBG needComplete:(BOOL)needComplete fetchCompletionHandler:(void ( ^ ) ( UIBackgroundFetchResult ))completionHandler
Parameters
userInfo |
Payload passed in by remote notification. |
---|---|
appFGBG |
The App in FG or BG when notification arrives. If not sure put unknown. |
needComplete |
Whether need to call |
completionHandler |
Pass in system’s to finish when task is done. |
Discussion
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[StreetHawk handleRemoteNotification:userInfo treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:nil];
}
or
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[StreetHawk handleRemoteNotification:userInfo treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:completionHandler];
}
Declared In
SHApp+Notification.h
– handleRemoteNotification:withActionId:needComplete:completionHandler:
Customer Application should implement this in UIApplicationDelegate to forward handling to StreetHawk library if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)handleRemoteNotification:(NSDictionary *)userInfo withActionId:(NSString *)identifier needComplete:(BOOL)needComplete completionHandler:(void ( ^ ) ( ))completionHandler
Parameters
userInfo |
Payload passed in by remote notification. |
---|---|
identifier |
Action button’s identifier. |
needComplete |
Whether need to call |
completionHandler |
Pass in system’s to finish when task is done. |
Discussion
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler
{
[StreetHawk handleRemoteNotification:userInfo withActionId:identifier needComplete:YES completionHandler:completionHandler];
}
Declared In
SHApp+Notification.h
– handleLocalNotification:treatAppAs:needComplete:fetchCompletionHandler:
Customer Application should implement this in UIApplicationDelegate to forward handling to StreetHawk library if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)handleLocalNotification:(UILocalNotification *)notification treatAppAs:(SHAppFGBG)appFGBG needComplete:(BOOL)needComplete fetchCompletionHandler:(void ( ^ ) ( UIBackgroundFetchResult ))completionHandler
Parameters
notification |
Object passed in by local notification. |
---|---|
appFGBG |
The App in FG or BG when notification arrives. If not sure put unknown. |
needComplete |
Whether need to call |
completionHandler |
Pass in system’s to finish when task is done. |
Discussion
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[StreetHawk handleLocalNotification:notification treatAppAs:SHAppFGBG_Unknown needComplete:YES fetchCompletionHandler:nil];
}`
Declared In
SHApp+Notification.h
– handleLocalNotification:withActionId:needComplete:completionHandler:
Customer Application should implement this in UIApplicationDelegate to forward handling to StreetHawk library if NOT auto-integrate. If StreetHawk.
autoIntegrateAppDelegate = YES;
make sure NOT call this otherwise cause dead loop. Code snippet:
- (void)handleLocalNotification:(UILocalNotification *)notification withActionId:(NSString *)identifier needComplete:(BOOL)needComplete completionHandler:(void ( ^ ) ( ))completionHandler
Parameters
notification |
Object passed in by local notification. |
---|---|
identifier |
Action button’s identifier. |
needComplete |
Whether need to call |
completionHandler |
Pass in system’s to finish when task is done. |
Discussion
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler
{
[StreetHawk handleLocalNotification:notification withActionId:identifier needComplete:YES completionHandler:completionHandler];
}
Declared In
SHApp+Notification.h
– setApplicationBadge:
Set badge number on Application icon. In iOS 8 it needs to check permission, if not have permission return NO.
- (BOOL)setApplicationBadge:(NSInteger)badgeNumber
Parameters
badgeNumber |
Number shown on App icon. |
---|
Return Value
If set successfully return YES, else return NO in case no permission.
Declared In
SHApp+Notification.h
– shSetAlertSetting:finish:
Set alert settings times measured by minutes.
- (void)shSetAlertSetting:(NSInteger)pauseMinutes finish:(SHCallbackHandler)handler
Parameters
pauseMinutes |
Minute measured pause time. If |
---|---|
handler |
Callback for finish. |
Declared In
SHApp+Notification.h
– getAlertSettingMinutes
Get alert settings times measured by minutes.
- (NSInteger)getAlertSettingMinutes
Return Value
Integer value, measured in minutes. If return
<= 0 means not pause, if return
>= StreetHawk_AlertSettings_Forever
means pause forever.
Declared In
SHApp+Notification.h
– getAlertSettingPauseUntil:
Client setup minutes pass to server. Server will calculate pause time according to it. pause_until
is calculated by current time and pause_minutes
. For example if current time is 8:00 and set pause_minutes
= 60 (1 hour), pause_until
will be 9:00. Later when time pass and now is 8:30, pause_minutes
is still 60, but must relay on pause_until
(9:00) to know the stop time, cannot use current time + pause_minutes
.
- (void)getAlertSettingPauseUntil:(SHCallbackHandler)handler
Discussion
pause_minutes
is minute value.- If set
pause_minutes
>=SH_AlertSettings_Forever
, it treats as pause forever. - If set
pause_minutes
<= 0, it treats as not paused.
@handler Need to read from server, use this asynchronous callback. It’s (NSDate pauseUntil, NSError error). If never set, return ([NSDate date], nil).
Declared In
SHApp+Notification.h
arrayCustomisedHandler
Array for hosting customised handler.
@property (nonatomic, strong) NSMutableArray *arrayCustomisedHandler
Declared In
SHApp+Notification.h
– shSetCustomiseHandler:
Register handler for customised tasks.
- (void)shSetCustomiseHandler:(id<ISHCustomiseHandler>)handler
Parameters
handler |
Instance class of |
---|
Declared In
SHApp+Notification.h
– handlePushDataForAppCallback:clickButton:
Go through register ISHCustomiseHandler
until find one which can handle this notitication.
- (void)handlePushDataForAppCallback:(PushDataForApplication *)pushData clickButton:(ClickButtonHandler)handler
Parameters
pushData |
Payload from this notification. |
---|---|
handler |
Callback with result. |
Declared In
SHApp+Notification.h
– shPGHtmlReceiver:
Register observer for phonegap App to load html page when receive 8004 push notification.
- (void)shPGHtmlReceiver:(id<ISHPhonegapObserver>)phonegapObserver
Parameters
phonegapObserver |
Instance class of |
---|
Declared In
SHApp+Notification.h
arrayPGObservers
Array for hosting PG observers. Set it by - (void)
shPGHtmlReceiver:(id
ISHPhonegapObserver)phonegapObserver
.
@property (nonatomic, strong) NSMutableArray *arrayPGObservers
Declared In
SHApp+Notification.h
– shGetViewName
Get stored view name for push 8004, this is used for App launches and check whether a 8004 push notification occured. If this App is waken up by 8004 push notification, the view name is stored locally and read by this function, so that App knows a specific page should be loaded.
- (NSString *)shGetViewName
Return Value
Locally stored view name when 8004 comes. It’s read only once, after read local cache is cleared.
Declared In
SHApp+Notification.h
– shPGLoadHtml:
Trigger phonegap observer to load html page. This is used for 8004 notification which can map friendly name to html file name.
- (void)shPGLoadHtml:(NSString *)htmlFile
Parameters
htmlFile |
The html page register by |
---|
Declared In
SHApp+Notification.h
systemPreferenceDisableNotification
Does user disable notification permission for this App in system preference settings App. It’s used to check before promote settings dialog by calling - (void)
launchSystemPreferenceSettings to let user reset location since iOS 8, or before iOS 8 needs to show self made instruction. Return YES if notification is disabled or no type is enabled.
@property (nonatomic, readonly) BOOL systemPreferenceDisableNotification
Declared In
SHApp+Notification.h
Other Methods
isDefaultLocationServiceEnabled
Default value to initialise isLocationServiceEnabled
, it’s called once when App first launch to set to isLocationServiceEnabled
. A typical usage is to delay asking for location allow permission (*** would like to use your current location (Don’t allow/OK)):
@property (nonatomic) BOOL isDefaultLocationServiceEnabled
Discussion
StreetHawk.isDefaultLocationServiceEnabled = NO; //not trigger location service when App launch.
[registerInstallForApp... ]; //do register without trigger location service
StreetHawk.
isLocationServiceEnabled = YES; //later trigger location service when need it.
Declared In
SHApp+Location.h
isLocationServiceEnabled
Property to control using location service or not. Geo-location update, iBeacon, region update needs this to be enabled to work. Internal CLLocation is not released when disable location service, but all functions not trigger StreetHawk’s notification.
@property (nonatomic) BOOL isLocationServiceEnabled
Declared In
SHApp+Location.h
reportWorkHomeLocationOnly
A flag to only sends logline 19, stop logline 20. Keep consistent with Android to save battery. Default is NO
to report 20 logline normally, FG uses standard and BG uses significant. If set to YES
to save battery and meantime can detect location for logline 19, always uses significant location, not use standard location even in FG.
@property (nonatomic) BOOL reportWorkHomeLocationOnly
Declared In
SHApp+Location.h
locationManager
An instance to deal with location.
@property (nonatomic, strong) SHLocationManager *locationManager
Declared In
SHApp+Location.h
systemPreferenceDisableLocation
Does user disable location permission for this App in system preference settings App. It’s used to check before promote settings dialog by calling - (void)
launchSystemPreferenceSettings to let user reset location since iOS 8, or before iOS 8 needs to show self made instruction. It’s only return YES when make sure global location is disabled or App location is disabled. If this App not has location required (for example not have location key in Info.plist), or not ask for location service by prevent enable it, return NO.
@property (nonatomic, readonly) BOOL systemPreferenceDisableLocation
Declared In
SHApp+Location.h
– setLocationUpdateFrequencyForFGInterval:forFGDistance:forBGInterval:forBGDistance:
StreetHawk SDK sends logline to server to report current location. The frequency can be controlled by this API. By default the frequency is following.
- (void)setLocationUpdateFrequencyForFGInterval:(int)fgInterval forFGDistance:(int)fgDistance forBGInterval:(int)bgInterval forBGDistance:(int)bgDistance
Parameters
fgInterval |
Minimum time interval for sending location logline when App in FG, measured in minutes. |
---|---|
fgDistance |
Minimum distance for sending location logline when App in FG, measured in meters. |
bgInterval |
Minimum time interval for sending location logline when App in BG, measured in minutes. |
bgDistance |
Minimum distance for sending location logline when App in BG, measured in meters. |
Discussion
Parameter | Background | Foreground |
---|---|---|
(bg/fg)MinDistanceBetweenEvents | 500m `SHLocation_BG_Distance` | 100m `SHLocation_FG_Distance` |
(bg/fg)MinTimeBetweenEvents | 5mins `SHLocation_BG_Interval` | 1min `SHLocation_FG_Interval` |
Declared In
SHApp+Location.h
Other Methods
isEnableCrashReport
StreetHawk uses PLCrashReport to collect App’s crash report and upload to StreetHawk server when next launch the App. You can check crash report on web site. It’s enabled by default. Note: if would like to disable crash report, suggest set this property before “registerInstallForApp” to avoid loading PLCrashReport.
@property (nonatomic) BOOL isEnableCrashReport
Declared In
SHApp+Crash.h
crashHandler
Handler for crash report stuff.
@property (nonatomic, strong) SHCrashHandler *crashHandler
Declared In
SHApp+Crash.h
isSendingCrashReport
To avoid sending twice, for example SHDemo location update and login happen same time, cause two install/update happen. This should be private however category class cannot define property in private interface.
@property (nonatomic) BOOL isSendingCrashReport
Declared In
SHApp+Crash.h
– sendCrashReportForInstall:withContent:onCrashDate:withHandler:
Sends crash report content info to the server.
- (void)sendCrashReportForInstall:(NSString *)installId withContent:(NSString *)crashReportContent onCrashDate:(NSDate *)crashDate withHandler:(SHCallbackHandler)handler
Declared In
SHApp+Crash.h
Install
– registerOrUpdateInstallWithHandler:
Update the current install or create a new one if one does not exist.
- (void)registerOrUpdateInstallWithHandler:(SHCallbackHandler)handler
Parameters
handler |
Callback for result. |
---|
Declared In
SHApp.h
– checkInstallChangeForLaunch
Some attribute maybe changed when re-launch this App, check them with pre-sent install when App launch. They include: app_key, clientversion, shversion, mode, carrier_name, os_version.
- (BOOL)checkInstallChangeForLaunch
Return Value
If any of above attributes changes compared with previous install/register or install/update return YES; If not sent before or nothing change, return NO.
Declared In
SHApp.h
Other Methods
– originateShareWithCampaign:withSource:withMedium:withContent:withTerm:shareUrl:withDefaultUrl:streetHawkGrowth_object:
Call this function to share and invite friend. It will return a callback with share url, and customer developer is responsible to perform the action to share.
- (void)originateShareWithCampaign:(NSString *)utm_campaign withSource:(NSString *)utm_source withMedium:(NSString *)utm_medium withContent:(NSString *)utm_content withTerm:(NSString *)utm_term shareUrl:(NSURL *)shareUrl withDefaultUrl:(NSURL *)default_url streetHawkGrowth_object:(SHCallbackHandler)handler
Parameters
utm_campaign |
Optional, for identify how this share is used for. For example in a book App, it would be “Child”, “Computer”, “Poetry”. It’s an Id to be used in StreetHawk Analytics. |
---|---|
utm_source |
Optional, indicate where share url will be posted (Example facebook, twitter, whatsapp etc). It’s free text string. |
utm_medium |
Optional, medium as url will be posted. For example cpc. |
utm_content |
Optional, content of campaign. |
utm_term |
Optional, keywords for campaing. |
shareUrl |
Optional, share url which will open App by browser link. For example, to open App page with parameter, url like “hawk://launchVC?vC=Deep%20Linking¶m1=this%20is%20a%20test¶m2=123”. |
default_url |
Optional, fallback url if user opens url not on iOS or Android mobile devices. It’s a normal url to display on browser, for example the developer’s website which describes the App, like http://www.myapp.com. |
handler |
Share result callback handler, when successfully share |
Declared In
SHApp+Growth.h
– originateShareWithCampaign:withMedium:withContent:withTerm:shareUrl:withDefaultUrl:withMessage:
Call this function to share and invite friend. It will promote a list of StreetHawk supporting share channel, and after user choose the channel, the share content will be shared automatically.
- (void)originateShareWithCampaign:(NSString *)utm_campaign withMedium:(NSString *)utm_medium withContent:(NSString *)utm_content withTerm:(NSString *)utm_term shareUrl:(NSURL *)shareUrl withDefaultUrl:(NSURL *)default_url withMessage:(NSString *)message
Parameters
utm_campaign |
Optional, for identify how this share is used for. For example in a book App, it would be “Child”, “Computer”, “Poetry”. It’s an Id to be used in StreetHawk Analytics. |
---|---|
utm_medium |
Optional, medium as url will be posted. For example cpc. |
utm_content |
Optional, content of campaign. |
utm_term |
Optional, keywords for campaing. |
shareUrl |
Optional, share url which will open App by browser link. For example, to open App page with parameter, url like “hawk://launchVC?vC=Deep%20Linking¶m1=this%20is%20a%20test¶m2=123”. |
default_url |
Optional, fallback url if user opens url not on iOS or Android mobile devices. It’s a normal url to display on browser, for example the developer’s website which describes the App, like http://www.myapp.com. |
message |
The message text which will display in share channel, such as “I would like to recommend an excellent book to you.”. |
Declared In
SHApp+Growth.h