From 24fe937facdaa3badddb46ec2902336a8f452133 Mon Sep 17 00:00:00 2001 From: Giuseppe Nucifora Date: Thu, 2 Feb 2017 17:20:19 +0100 Subject: [PATCH] - Release 1.3.0 --- Example/PNObject/PNObjAppDelegate.m | 9 ++- Example/Podfile.lock | 6 +- .../Pods/Local Podspecs/PNObject.podspec.json | 4 +- Example/Pods/Manifest.lock | 6 +- PNObject.podspec | 2 +- .../Classes/PNObject+PNObjectConnection.m | 8 +-- PNObject/Classes/PNObjectConfig.h | 7 +++ PNObject/Classes/PNObjectConfig.m | 60 +++++++++++++------ README.md | 43 ++++++++++++- 9 files changed, 109 insertions(+), 36 deletions(-) diff --git a/Example/PNObject/PNObjAppDelegate.m b/Example/PNObject/PNObjAppDelegate.m index c32e75f..bddeb1e 100644 --- a/Example/PNObject/PNObjAppDelegate.m +++ b/Example/PNObject/PNObjAppDelegate.m @@ -33,9 +33,9 @@ _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. - [PNObjectConfig initSharedInstanceForEnvironments:@{EnvironmentDevelopment : @"http://pnobject.local/api/v1/", - EnvironmentStage : @"http://pnobject.stage.it/api/v1/", - EnvironmentProduction : @"http://pnobject.prod.it/api/v1/" + [PNObjectConfig initSharedInstanceForEnvironments:@{EnvironmentDevelopment : @{BaseUrl:@"http://pnobject.local/",EndpointPath:@"api/v1/"}, + EnvironmentStage : @{BaseUrl:@"http://pnobject.stage.it/",EndpointPath:@"api/v1/"}, + EnvironmentProduction : @{BaseUrl:@"http://pnobject.prod.it/",EndpointPath:@"api/v1/"}, } userSubclass:[PNUser class] withOauthMode:OAuthModeClientCredential]; [[PNObjectConfig sharedInstance] setClientID:@"xxxxxxxxx" clientSecret:@"xxxxxxxxxxxx" forEnv:EnvironmentStage]; @@ -44,6 +44,9 @@ [[PNObjectConfig sharedInstance] setEnvironment:EnvironmentStage]; //[[PNObjectConfig sharedInstance] setHTTPHeaderValue:@"XMLHttpRequest" forKey:@"X-Request-With"]; + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] baseUrl]); + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] endPointPath]); + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] endPointUrl]); diff --git a/Example/Podfile.lock b/Example/Podfile.lock index eaf4b80..040a09f 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -37,7 +37,7 @@ PODS: - NSString-Helper (1.0.5) - nv-ios-http-status (0.0.1) - PEAR-FileManager-iOS (1.3.1) - - PNObject (1.2.2): + - PNObject (1.3.0): - AFNetworking - CodFis-Helper - DDDKeychainWrapper @@ -84,7 +84,7 @@ SPEC CHECKSUMS: NSString-Helper: 459e1b6a62b3bf7db10f01b0d102548608e945c4 nv-ios-http-status: b6c2b5fc8656cc19e0d3000dadce2080b99d0e2f PEAR-FileManager-iOS: 3bc403f68a53483f5629aa822f4649e40275c4d3 - PNObject: 156e78eee9d0e3a10f059102e66d8c06a1296afe + PNObject: d085a7b5f0dc2a06ac60012556ba1dd44ab6c66d PureLayout: 4d550abe49a94f24c2808b9b95db9131685fe4cd RZDataBinding: 6981e90ddaae2f5e02028323b1043f8c31013109 Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 @@ -93,4 +93,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: db08ccdd0a68e33d4a1cceb1843643fbab2f2a8e -COCOAPODS: 1.2.0.rc.1 +COCOAPODS: 1.2.0 diff --git a/Example/Pods/Local Podspecs/PNObject.podspec.json b/Example/Pods/Local Podspecs/PNObject.podspec.json index 223dcbf..6671201 100644 --- a/Example/Pods/Local Podspecs/PNObject.podspec.json +++ b/Example/Pods/Local Podspecs/PNObject.podspec.json @@ -1,6 +1,6 @@ { "name": "PNObject", - "version": "1.2.2", + "version": "1.3.0", "summary": "PNObject is a simple replica of the more complex ParseObject", "homepage": "https://github.com/giuseppenucifora/PNObject", "license": { @@ -12,7 +12,7 @@ }, "source": { "git": "https://github.com/giuseppenucifora/PNObject.git", - "tag": "1.2.2" + "tag": "1.3.0" }, "platforms": { "ios": "8.0" diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock index eaf4b80..040a09f 100644 --- a/Example/Pods/Manifest.lock +++ b/Example/Pods/Manifest.lock @@ -37,7 +37,7 @@ PODS: - NSString-Helper (1.0.5) - nv-ios-http-status (0.0.1) - PEAR-FileManager-iOS (1.3.1) - - PNObject (1.2.2): + - PNObject (1.3.0): - AFNetworking - CodFis-Helper - DDDKeychainWrapper @@ -84,7 +84,7 @@ SPEC CHECKSUMS: NSString-Helper: 459e1b6a62b3bf7db10f01b0d102548608e945c4 nv-ios-http-status: b6c2b5fc8656cc19e0d3000dadce2080b99d0e2f PEAR-FileManager-iOS: 3bc403f68a53483f5629aa822f4649e40275c4d3 - PNObject: 156e78eee9d0e3a10f059102e66d8c06a1296afe + PNObject: d085a7b5f0dc2a06ac60012556ba1dd44ab6c66d PureLayout: 4d550abe49a94f24c2808b9b95db9131685fe4cd RZDataBinding: 6981e90ddaae2f5e02028323b1043f8c31013109 Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 @@ -93,4 +93,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: db08ccdd0a68e33d4a1cceb1843643fbab2f2a8e -COCOAPODS: 1.2.0.rc.1 +COCOAPODS: 1.2.0 diff --git a/PNObject.podspec b/PNObject.podspec index 5a9c6cd..743a571 100644 --- a/PNObject.podspec +++ b/PNObject.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'PNObject' -s.version = '1.2.2' +s.version = '1.3.0' s.summary = 'PNObject is a simple replica of the more complex ParseObject' diff --git a/PNObject/Classes/PNObject+PNObjectConnection.m b/PNObject/Classes/PNObject+PNObjectConnection.m index df89096..f770983 100644 --- a/PNObject/Classes/PNObject+PNObjectConnection.m +++ b/PNObject/Classes/PNObject+PNObjectConnection.m @@ -44,7 +44,7 @@ failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure { if ([[PNObjectConfig sharedInstance] currentOauthCredential] && ![[[PNObjectConfig sharedInstance] currentOauthCredential] isExpired]) { - [[[PNObjectConfig sharedInstance] manager] GET:[[[PNObjectConfig sharedInstance] baseUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters progress:downloadProgress success:^(NSURLSessionDataTask *task, id responseObject) { + [[[PNObjectConfig sharedInstance] manager] GET:[[[PNObjectConfig sharedInstance] endPointUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters progress:downloadProgress success:^(NSURLSessionDataTask *task, id responseObject) { if (success) { success(task,responseObject); @@ -96,7 +96,7 @@ if ([[PNObjectConfig sharedInstance] currentOauthCredential] && ![[[PNObjectConfig sharedInstance] currentOauthCredential] isExpired]) { - [[[PNObjectConfig sharedInstance] manager] POST:[[[PNObjectConfig sharedInstance] baseUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters progress:uploadProgress success:^(NSURLSessionDataTask *task, id responseObject) { + [[[PNObjectConfig sharedInstance] manager] POST:[[[PNObjectConfig sharedInstance] endPointUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters progress:uploadProgress success:^(NSURLSessionDataTask *task, id responseObject) { if (success) { success(task,responseObject); @@ -153,7 +153,7 @@ failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure { if ([[PNObjectConfig sharedInstance] currentOauthCredential] && ![[[PNObjectConfig sharedInstance] currentOauthCredential] isExpired]) { - [[[PNObjectConfig sharedInstance] manager] POST:[[[PNObjectConfig sharedInstance] baseUrl] stringByAppendingFormat:@"%@",endPoint] + [[[PNObjectConfig sharedInstance] manager] POST:[[[PNObjectConfig sharedInstance] endPointUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters constructingBodyWithBlock:^(id _Nonnull formData) { if (postFormData) { @@ -217,7 +217,7 @@ failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure { if ([[PNObjectConfig sharedInstance] currentOauthCredential] && ![[[PNObjectConfig sharedInstance] currentOauthCredential] isExpired]) { - [[[PNObjectConfig sharedInstance] manager] DELETE:[[[PNObjectConfig sharedInstance] baseUrl] stringByAppendingFormat:@"%@",endPoint] + [[[PNObjectConfig sharedInstance] manager] DELETE:[[[PNObjectConfig sharedInstance] endPointUrl] stringByAppendingFormat:@"%@",endPoint] parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (success) { diff --git a/PNObject/Classes/PNObjectConfig.h b/PNObject/Classes/PNObjectConfig.h index f8ae422..c173e3f 100644 --- a/PNObject/Classes/PNObjectConfig.h +++ b/PNObject/Classes/PNObjectConfig.h @@ -37,6 +37,9 @@ extern NSString* _Nonnull const PNObjectLocalNotificationPNInstallationUserDelet #pragma mark - +extern NSString* _Nonnull const BaseUrl; +extern NSString* _Nonnull const EndpointPath; + extern NSString* _Nonnull const EnvironmentProduction; extern NSString* _Nonnull const EnvironmentStage; extern NSString* _Nonnull const EnvironmentDevelopment; @@ -154,6 +157,10 @@ typedef NS_ENUM(NSInteger, OAuthMode) { */ - (NSString * _Nonnull) baseUrl; +- (NSString * _Nonnull) endPointPath; + +- (NSString * _Nonnull) endPointUrl; + /** * <#Description#> * diff --git a/PNObject/Classes/PNObjectConfig.m b/PNObject/Classes/PNObjectConfig.m index d8858db..eb9d09c 100644 --- a/PNObject/Classes/PNObjectConfig.m +++ b/PNObject/Classes/PNObjectConfig.m @@ -46,6 +46,7 @@ NSString* const EnvironmentStage = @"PNObjectConfigEnvStage"; NSString* const EnvironmentDevelopment = @"PNObjectConfigDevelopment"; NSString* const BaseUrl = @"base_url"; +NSString* const EndpointPath = @"endpoint_path"; NSString* const Client_ID = @"client_id"; NSString* const Client_Secret = @"client_secret"; NSString* const Client_Username = @"client_username"; @@ -58,7 +59,9 @@ NSString* const Client_Password = @"client_password"; @property (nonatomic, strong) NSMutableDictionary *configuration; @property (nonatomic, strong) NSMutableDictionary *headerFields; @property (nonatomic, strong) NSString *currentEnv; -@property (nonatomic, strong) NSString *currentEndPointBaseUrl; +@property (nonatomic, strong) NSString *currentBaseUrl; +@property (nonatomic, strong) NSString *currentEndPointPath; +@property (nonatomic, strong) NSString *currentEndPointUrl; @property (nonatomic, strong) NSString *currentOAuthClientID; @property (nonatomic, strong) NSString *currentOAuthClientSecret; @property (nonatomic, strong) NSString *currentOAuthUserName; @@ -112,11 +115,20 @@ static bool isFirstAccess = YES; for (NSString *key in [endpointUrlsForEnvironments allKeys]) { - NSURL * endpointUrl = [NSURL URLWithString:[endpointUrlsForEnvironments objectForKey:key]]; - if (endpointUrl) { - [SINGLETON_PNObjectConfig.configuration setValue:[NSDictionary dictionaryWithObjectsAndKeys:[endpointUrl absoluteString],BaseUrl, nil] forKey:key]; + if ([[endpointUrlsForEnvironments objectForKey:key] isKindOfClass:[NSDictionary class]]) { + + NSURL * baseUrl = [NSURL URLWithString:[[endpointUrlsForEnvironments objectForKey:key] objectForKey:BaseUrl]]; + NSString * endPointUrl = [[endpointUrlsForEnvironments objectForKey:key] objectForKey:EndpointPath]; + if (baseUrl) { + [SINGLETON_PNObjectConfig.configuration setValue:[NSDictionary dictionaryWithObjectsAndKeys:[baseUrl absoluteString],BaseUrl,endPointUrl,EndpointPath, nil] forKey:key]; + } + } + else if ([[endpointUrlsForEnvironments objectForKey:key] isKindOfClass:[NSString class]]) { + NSURL * baseUrl = [NSURL URLWithString:[endpointUrlsForEnvironments objectForKey:key]]; + if (baseUrl) { + [SINGLETON_PNObjectConfig.configuration setValue:[NSDictionary dictionaryWithObjectsAndKeys:[baseUrl absoluteString],BaseUrl,@"",EndpointPath, nil] forKey:key]; + } } - } NSAssert([SINGLETON_PNObjectConfig.configuration objectForKey:EnvironmentProduction], @"EnvironmentProduction must be valid endpoint url"); @@ -172,8 +184,6 @@ static bool isFirstAccess = YES; _headerFields = [[NSMutableDictionary alloc] init]; - - if(![DDDKeychainWrapper dataForKey:PNObjectEncryptionKey]){ NSData *key = [[NSString getRandString:256] dataUsingEncoding:NSUTF8StringEncoding]; @@ -209,14 +219,18 @@ static bool isFirstAccess = YES; - (void) setEnvironment:(NSString * _Nonnull) environment { _currentEnv = environment; - _currentEndPointBaseUrl = nil; + _currentBaseUrl = nil; + _currentEndPointPath = nil; + _currentEndPointUrl = nil; _currentOAuthClientID = nil; _currentOAuthClientSecret = nil; _currentOAuthUserName = nil; _currentOAuthPassword = nil; if ([_configuration objectForKey:environment]) { - _currentEndPointBaseUrl = [[_configuration objectForKey:_currentEnv] objectForKey:BaseUrl]; + _currentBaseUrl = [[_configuration objectForKey:_currentEnv] objectForKey:BaseUrl]; + _currentEndPointPath = ([[_configuration objectForKey:_currentEnv] objectForKey:EndpointPath] ? [[_configuration objectForKey:_currentEnv] objectForKey:EndpointPath] : @""); + _currentEndPointUrl = [_currentBaseUrl stringByAppendingString:_currentEndPointPath]; _currentOAuthClientID = [[_configuration objectForKey:_currentEnv] objectForKey:Client_ID]; _currentOAuthClientSecret = [[_configuration objectForKey:_currentEnv] objectForKey:Client_Secret]; if ([[_configuration objectForKey:_currentEnv] objectForKey:Client_Username] && [[_configuration objectForKey:_currentEnv] objectForKey:Client_Password]) { @@ -227,7 +241,7 @@ static bool isFirstAccess = YES; NSLogDebug(@"%@",[[_configuration objectForKey:_currentEnv] objectForKey:BaseUrl]); - NSAssert(_currentEndPointBaseUrl,@"Selected environment generate error. Please check configuration"); + NSAssert(_currentEndPointUrl,@"Selected environment generate error. Please check configuration"); if (_currentOAuthClientID && _currentOAuthClientSecret) { [self authManager]; @@ -237,7 +251,15 @@ static bool isFirstAccess = YES; } - (NSString *) baseUrl { - return _currentEndPointBaseUrl; + return _currentBaseUrl; +} + +- (NSString *) endPointPath { + return _currentEndPointPath; +} + +- (NSString *) endPointUrl { + return _currentEndPointUrl; } - (AFHTTPSessionManager *) manager { @@ -281,7 +303,7 @@ static bool isFirstAccess = YES; if (![_authManager clientID]) { - _authManager = [AFOAuth2Manager managerWithBaseURL:[NSURL URLWithString:_currentEndPointBaseUrl] clientID:_currentOAuthClientID secret:_currentOAuthClientSecret]; + _authManager = [AFOAuth2Manager managerWithBaseURL:[NSURL URLWithString:_currentEndPointUrl] clientID:_currentOAuthClientID secret:_currentOAuthClientSecret]; } [_authManager setUseHTTPBasicAuthentication:NO]; @@ -294,7 +316,7 @@ static bool isFirstAccess = YES; if (_currentOAuthClientID && _currentOAuthClientSecret && _currentOAuthUserName && _currentOAuthPassword) { if (![_authManager clientID]) { - _authManager = [AFOAuth2Manager managerWithBaseURL:[NSURL URLWithString:_currentEndPointBaseUrl] clientID:_currentOAuthClientID secret:_currentOAuthClientSecret]; + _authManager = [AFOAuth2Manager managerWithBaseURL:[NSURL URLWithString:_currentEndPointUrl] clientID:_currentOAuthClientID secret:_currentOAuthClientSecret]; } [_authManager setUseHTTPBasicAuthentication:NO]; @@ -370,7 +392,7 @@ static bool isFirstAccess = YES; failure:(nullable void (^)(NSError * _Nonnull error))failure { if (_currentOauthCredential) { - [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] refreshToken:[_currentOauthCredential refreshToken] success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] refreshToken:[_currentOauthCredential refreshToken] success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; @@ -443,7 +465,7 @@ static bool isFirstAccess = YES; return; } } - [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] username:email password:password scope:nil success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] username:email password:password scope:nil success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; @@ -486,7 +508,7 @@ static bool isFirstAccess = YES; } } - [_authManager authenticateUsingFacebookOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] facebookID:facebookID facebookToken:facebookToken scope:nil success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingFacebookOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] facebookID:facebookID facebookToken:facebookToken scope:nil success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; [AFOAuthCredential storeCredential:_currentOauthCredential withIdentifier:PNObjectServiceUserCredentialIdentifier]; @@ -519,7 +541,7 @@ static bool isFirstAccess = YES; if (_currentOauthCredential) { - [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] refreshToken:[_currentOauthCredential refreshToken] success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] refreshToken:[_currentOauthCredential refreshToken] success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; [AFOAuthCredential storeCredential:_currentOauthCredential withIdentifier:PNObjectServiceClientCredentialIdentifier]; @@ -545,7 +567,7 @@ static bool isFirstAccess = YES; else { switch (_oauthMode) { case OAuthModeClientCredential:{ - [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] scope:nil success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] scope:nil success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; [AFOAuthCredential storeCredential:_currentOauthCredential withIdentifier:PNObjectServiceClientCredentialIdentifier]; @@ -570,7 +592,7 @@ static bool isFirstAccess = YES; break; case OAuthModePassword:{ - [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointBaseUrl stringByAppendingString:@"oauth-token"] username:_currentOAuthUserName password:_currentOAuthPassword scope:nil success:^(AFOAuthCredential * _Nonnull credential) { + [_authManager authenticateUsingOAuthWithURLString:[_currentEndPointUrl stringByAppendingString:@"oauth-token"] username:_currentOAuthUserName password:_currentOAuthPassword scope:nil success:^(AFOAuthCredential * _Nonnull credential) { _currentOauthCredential = credential; [AFOAuthCredential storeCredential:_currentOauthCredential withIdentifier:PNObjectServiceClientCredentialIdentifier]; diff --git a/README.md b/README.md index 95fc348..c3a86bd 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,49 @@ it, simply add the following line to your Podfile: pod "PNObject" ``` -Configure PNObject endpoint client ID, client secret and OAuthModePassword +New in version 1.3.0 +--- + +- Can Separate Base url with endpoint api path during configuration +- Access to separate base url, endpoint path or complete endpoint url + + +Configure PNObject endpoint client ID, client secret and OAuthModePassword with separate base url and api path +--- + +``` +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + + [PNObjectConfig initSharedInstanceForEnvironments:@{EnvironmentDevelopment : @{BaseUrl:@"http://pnobject.local/",EndpointPath:@"api/v1/"}, + EnvironmentStage : @{BaseUrl:@"http://pnobject.stage.it/",EndpointPath:@"api/v1/"}, + EnvironmentProduction : @{BaseUrl:@"http://pnobject.prod.it/",EndpointPath:@"api/v1/"}, + } userSubclass:[PNUser class] withOauthMode:OAuthModeClientCredential]; + + [[PNObjectConfig sharedInstance] setClientID:@"xxxxxxxxx" clientSecret:@"xxxxxxxxxxxx" forEnv:EnvironmentStage]; + [[PNObjectConfig sharedInstance] setClientID:@"xxxxxxxxx" clientSecret:@"xxxxxxxxxxxx" forEnv:EnvironmentProduction]; + + + + [[PNObjectConfig sharedInstance] setOauthUserName:@"admin" oauthPassword:@"admin" forEnv:EnvironmentStage]; + + [[PNObjectConfig sharedInstance] setEnvironment:EnvironmentStage]; + + +} +``` + +Get BaseUrl, endPointPath and endPointUrl +--- +``` + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] baseUrl]); + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] endPointPath]); + NSLogDebug(@"%@",[[PNObjectConfig sharedInstance] endPointUrl]); +``` + ### +Configure PNObject endpoint client ID, client secret and OAuthModePassword +--- ``` - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions