Method to return block result - objective-c-blocks

- (ACAccount *)accountFacebook{
if (_accountFacebook) {
return _accountFacebook;
}
if (!_accountStoreFacebook) {
_accountStoreFacebook = ACAccountStore.new;
}
ACAccountType *accountTypeFacebook = [self.accountStoreFacebook accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{ACFacebookAppIdKey : #"xxxxxxxxx",
ACFacebookAudienceKey : ACFacebookAudienceEveryone,
ACFacebookPermissionsKey : #[#"user_about_me", #"publish_actions"]
};
__block ACAccount *accountFb;
[_accountStoreFacebook requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(#"Facebook access granted");
accountFb = _accountStoreFacebook.accounts.lastObject;
}else {
NSLog(#"Facebook access denied");
accountFb = nil;}
if (error) {
NSLog(error.localizedDescription);
}
}];
return accountFb;
}
When I run
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if (appDelegate.accountFacebook) {
NSLog(#"accountFacebook OK");
}else NSLog(#"accountFacebook Not Exists");
appDelegate.accountFacebook return nil always, doesn't wait for block to complete.
What should be changed?

This is an asynchronous call, so the block completes after your method ends. You need to redesign your app to do what it has to do in the completion block. You call appDelegate.accountFacebook and expect to do something if it is not nil. Why not pass this method a completion block that would perform what you want it to perform like so:
typedef void(^HandlerType)(ACAccount* account);
- (void)performForFacebookAccount: (HandlerType) handler{
if (_accountFacebook) {
handler(_accountFacebook);
return;
}
if (!_accountStoreFacebook) {
_accountStoreFacebook = ACAccountStore.new;
}
ACAccountType *accountTypeFacebook = [self.accountStoreFacebook accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{ACFacebookAppIdKey : #"xxxxxxxxx",
ACFacebookAudienceKey : ACFacebookAudienceEveryone,
ACFacebookPermissionsKey : #[#"user_about_me", #"publish_actions"]
};
[_accountStoreFacebook requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(#"Facebook access granted");
_accountFacebook = _accountStoreFacebook.accounts.lastObject;
handler(_accountFacebook);
}else {
NSLog(#"Facebook access denied");
_accountFacebook = nil;}
if (error) {
NSLog(error.localizedDescription);
}
}];
}

Related

Microsoft MSAL ObjC - Trying to acquire token Interactively for multiple scopes

I am trying to acquire token Interactively for multiple scopes, Policy and RMS scopes using Objective C. I am not sure whether I am doing it right or wrong, the way I am trying to get it as below.
I have written a method "aquireToken" where I am calling the function twice one for Policy scope and other RMS scope and updating result in NSDictionary.
There is a flag which is being updated inside the "completionBlock = ^(MSALResult *result, NSError *error)". But its value is not reflected in the caller "aquireToken" function.
The code snippet is as below:
- (void) aquireToken
{
policyTokenResult = false;
rmsTokenResult = false;
NSError *error = nil;
MSALPublicClientApplication *application = [self createPublicClientApplication:&error];
[self retrieveTokens:application forScopes:scopesPolicy isPolicy:true];
if (policyTokenResult)
{
[self retrieveTokens:application forScopes:scopesRMS isPolicy:false];
}
for (NSString* key in resultMap) {
id value = resultMap[key];
// id object = [resultDict objectForKey:key];
NSLog(#"%# = %#", key, value);
// do stuff
}
}
- (void)retrieveTokens:(MSALPublicClientApplication*) application
forScopes: (NSArray<NSString *> *) scopes
isPolicy: (BOOL) isPolicy
{
NSError *error = nil;
MSALAccount* userAccount = nil;
for (MSALAccount *account in [application allAccounts:&error])
{
if([[account.username uppercaseString] isEqualToString:[authID uppercaseString]])
{
NSLog(#"Account Found: \t%#", account.username);
userAccount = account;
break;
}
}
MSALCompletionBlock completionBlock;
__block __weak MSALCompletionBlock weakCompletionBlock;
weakCompletionBlock = completionBlock = ^(MSALResult *result, NSError *error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!error)
{
if (isPolicy)
{
[resultMap setObject:result.accessToken forKey:#"PolicyAccessToken"];
[resultMap setObject:result.account.username forKey:#"UserId"];
authID = result.account.username;
policyTokenResult = true;
}
else
{
[resultMap setObject:result.accessToken forKey:#"RMSAccessToken"];
rmsTokenResult = true;
}
if(policyTokenResult && rmsTokenResult)
{
[resultMap setObject:#"" forKey:#"ResultStatusSuccess"];
}
return;
}
if ([error.domain isEqualToString:MSALErrorDomain] && error.code == MSALErrorInteractionRequired)
{
[self acquireTokenInteractive:application scopes:scopes isPolicy:isPolicy completionBlock:weakCompletionBlock];
return;
}
});
};
if(userAccount)
{
[self acquireTokenSilent:application scopes:scopes forAccount:userAccount isPolicy:isPolicy completionBlock:completionBlock];
}
else
{
[self acquireTokenInteractive:application scopes:scopes isPolicy:isPolicy completionBlock:completionBlock];
}
}
- (void) acquireTokenSilent: (MSALPublicClientApplication *) application
scopes: (NSArray<NSString *> *) scopes
forAccount: (MSALAccount *) userAccount
isPolicy: (BOOL) isPolicy
completionBlock: (MSALCompletionBlock) completionBlock
{
MSALSilentTokenParameters *silentParams = [[MSALSilentTokenParameters alloc] initWithScopes:scopes account:userAccount];
[application acquireTokenSilentWithParameters:silentParams completionBlock:completionBlock];
}
- (void) acquireTokenInteractive: (MSALPublicClientApplication *) application
scopes: (NSArray<NSString *> *) scopes
isPolicy: (BOOL) isPolicy
completionBlock: (MSALCompletionBlock)completionBlock
{
MSALInteractiveTokenParameters *interactiveParams = [[MSALInteractiveTokenParameters alloc] initWithScopes:scopes];
[interactiveParams setPromptType:MSALPromptTypeSelectAccount];
interactiveParams.completionBlockQueue = dispatch_get_main_queue();
[application acquireTokenWithParameters:interactiveParams completionBlock:completionBlock];
}

The command is disabled and cannot be executed

So, when I trying to fetch some data, RACCommand return this error.
I have a picker for example and when user scroll it, app get data from server and show them, but if user scroll fast, (previous operation in progress) RACCommand get this error:
Error Domain=RACCommandErrorDomain Code=1 "The command is disabled and cannot be executed" UserInfo={RACUnderlyingCommandErrorKey=<RACCommand: 0x174280050>, NSLocalizedDescription=The command is disabled and cannot be executed}
I know, its related with some cancel mechanism, but I tried many examples and not working as well.
Its my piece of code:
#weakify(self);
[[[self.viewModel makeCommand] execute:nil]
subscribeError:^(NSError *error) {
#strongify(self);
[self showAlertWithError:error];
}];
and viewModel:
- (RACCommand*)makeCommand {
if (!_makeCommand) {
_makeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [self getVehicleMake];
}];
}
return _makeCommand;
}
- (RACSignal*)getVehicleMake {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[self.forumService getForumMakesWithYear:#([self.selectedYear integerValue])
category:self.vehicleCategory]
subscribeNext:^(RACTuple *result) {
self.makes = result.first;
[subscriber sendNext:self.makes];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
}];
}];
}
RACCommand doesn't allow concurrent execution by default. When it's executing, it becomes disabled. If you try to execute again, it will send that error.
But you can test for that error—RACCommand has RACCommandErrorDomain and RACCommandErrorNotEnabled constants available.
#weakify(self);
[[[self.viewModel makeCommand] execute:nil]
subscribeError:^(NSError *error) {
#strongify(self);
if ([error.domain isEqual:RACCommandErrorDomain] && error.code == RACCommandErrorNotEnabled) {
return;
}
[self showAlertWithError:error];
}];

Custom login with facebook button. Not getting info other than name and UserID

I'm trying to get email from my logged in user using Facebook SDK. Here are 2 of my login methods
-(void)loginButtonClicked {
FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
[login logInWithReadPermissions: #[#"public_profile", #"email", #"user_friends"]
fromViewController:self
handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
NSLog(#"Process error");
} else if (result.isCancelled) {
NSLog(#"Cancelled");
} else {
[self getFBUSerData];
NSLog(#"Logged in");
}
}];
}
- (void) getFBUSerData {
if (FBSDKAccessToken.currentAccessToken != nil) {
[[[FBSDKGraphRequest alloc] initWithGraphPath:#"me" parameters:nil]
startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(#"fetched user:%# and Email : %#", result,result[#"email"]);
}
}];
}
}
The result in the log:
(lldb) po result
{
id = 42357***4506554;
name = "Fname Lname";
}
But no email or friends list. Any help how to get those?
You have to assign the parameters you need to get in getFBUserData().
Something like this:
- (void) getFBUSerData {
if (FBSDKAccessToken.currentAccessToken != nil) {
NSDictionary *dict = [[NSDictionary alloc] initWithObjects:#[#"id, name, first_name, last_name, picture.type(large), email"] forKeys:#[#"fields"]];
[[[FBSDKGraphRequest alloc] initWithGraphPath:#"me" parameters:dict]
startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(#"fetched user:%# and Email : %#", result,result[#"email"]);
}
}]; }
}

TouchID forward to system passcode authentication

I want to use TouchID authenticate my own app.
1.I want user can click 'Enter passcode' to invoke system build-in passcode screen to authenticate,if success then enter my own app.
But I don't how know to make it forward to passcode authenticate view like the following screen in 'case LAErrorUserFallback'
Here is my code :
LAContext *context = [[LAContext alloc] init];
__block NSString *msg;
__block BOOL bAuth;
// show the authentication UI with our reason string
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
if (success) {
bAuth = YES;
msg =[NSString stringWithFormat:NSLocalizedString(#"EVALUATE_POLICY_SUCCESS", nil)];
dispatch_async(dispatch_get_main_queue(), ^{
[[MYAppDelegate theDelegate] initializeAppAfterKeyVaultUnlocked];
});
NSLog(#"%#",msg);
} else {
bAuth = NO;
switch (authenticationError.code) {
case LAErrorAuthenticationFailed:
msg = [NSString stringWithFormat:NSLocalizedString(#"Authentication Failed", nil)];
// ...
break;
case LAErrorUserCancel:
msg = [NSString stringWithFormat:NSLocalizedString(#"User pressed Cancel button", nil)];
dispatch_async(dispatch_get_main_queue(), ^{
[[MYAppDelegate theDelegate] exitAndLock];
});
break;
case LAErrorUserFallback:
msg = [NSString stringWithFormat:NSLocalizedString(#"User pressed \"Enter Password\"", nil)];
//Here I want to fallback to iOS build-in passcode authenticate view, and get the auth result.
break;
default:
msg = [NSString stringWithFormat:NSLocalizedString(#"Touch ID is not configured", nil)];
// ...
break;
}
NSLog(#"%#",authenticationError.localizedDescription);
}
}];
Now in iOS 9 it is pretty simple actually - you just have to use LAPolicyDeviceOwnerAuthentication instead of LAPolicyDeviceOwnerAuthenticationWithBiometrics
So in your code you just replace this:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
With this:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
Thus, when user fails to authenticate with fingerprint, there'd be "enter passcode" option which would invoke system passcode input screen.
In my understanding, you will have to build the passcode screen yourself if you want to use evaluatePolicy.
If you use keychain, the SecItemCopyMatching function automatically falls back to passcode if fingering fails. Here's a reference on how to get it going (see section 3): https://www.secsign.com/fingerprint-validation-as-an-alternative-to-passcodes/
Didn't try that, but this post claim you can use the system as follow here (This works only with iOS 8 or later).
Or (which is what I did) you can build your passcode entry screen (to support older iOS versions), my controller have a passcode entry view, which will get exposed when the user select use passcode. At that point the call back from evaluatePolicy will return with LAErrorUserFallback, which can be the time to open your custom passcode screen.
something like that:
-(void)maybeShowTouchIDMessage {
if (![SettingsManager sharedManager].isUseTouchID || self.createPassCodeMode) {
self.shieldView.hidden = YES;
return;
}
self.shieldView.hidden = NO;
self.shieldView.alpha = 1.0;
LAContext *context = [[LAContext alloc] init];
NSError *evalError = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&evalError] ) {
__weak SecurityWindowViewController *weakSelf = self;
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Use touch id \n or hit \"Cancel\" to enter passcode"
reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
SecurityWindowViewController *strongSelf = weakSelf;
if (success) {
[strongSelf hideWithSuccess:YES];
} else if (error){
NSString *errorMessage;
BOOL showError = NO;
switch (error.code) {
case LAErrorAuthenticationFailed:
errorMessage = #"Sorry couldn't autheticate";
showError = YES;
break;
case LAErrorPasscodeNotSet:
errorMessage = #"Sorry couldn't autheticate";
showError = YES;
break;
case LAErrorTouchIDNotEnrolled:
errorMessage = #"Touch ID has no enrolled fingers";
showError = YES;
break;
default:
showError = NO;
break;
}
[UIView animateWithDuration:0.5 animations:^{
strongSelf.shieldView.alpha = 0.0;
} completion:^(BOOL finished) {
strongSelf.shieldView.hidden = YES;
}];
if (showError) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:errorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
}
}
});
}];
}
For your case :
case LAErrorUserFallback:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:NSLocalizedString(#"Unlock APP With PassCode", nil) reply: ^(BOOL success, NSError *authenticationError) {
if(success){
NSLog(#"PassCode Login successful");
}else{
NSLog(#"%#",authenticationError);
}
}
For device passcode verification you need to use LAPolicyDeviceOwnerAuthentication instead of LAPolicyDeviceOwnerAuthenticationWithBiometrics. Hope this helps !!
You can add another case and call your passcode screen from that.
Here is my code:
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = strMessage;
objFlockr.pVerificationBlock = objResponse;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
if (!isShow) {
myContext.localizedFallbackTitle = #"";
}
else
{
myContext.localizedFallbackTitle = #"Set Up Passcode";
}
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL succes, NSError *error) {
if (!AppDel.firstAttampt && succes && !isShow)
{
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(1);
}
else if (succes) {
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(0);
NSLog(#"User authenticated");
} else {
switch (error.code) {
case LAErrorAuthenticationFailed:
NSLog(#"Authentication Failed");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(1);
break;
case LAErrorUserCancel:
NSLog(#"User pressed Cancel button");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(3);
break;
case LAErrorUserFallback:
NSLog(#"User pressed \"Enter Password\"");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(4);
break;
default:
[self showMessage:#"Touch ID is not configured" withTitle:#"Error"];
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(2);
NSLog(#"Touch ID is not configured");
break;
}
NSLog(#"Authentication Fails");
}
}];
} else {
NSLog(#"Can not evaluate Touch ID");
[self showMessage:#"Can not evaluate TouchID" withTitle:#"Error"];
}
Replace LAPolicy policy enum value deviceOwnerAuthenticationWithBiometrics with deviceOwnerAuthentication
Note: If user has enable biometric (face id or touch id) authentication, then device will ask first for biometric authentication and if user choose fall back authentication, then only deviceOwnerAuthentication will show passcode screen.
Try this and see (swift 4):
func touchIDAuthentication() {
let context = LAContext()
var error:NSError?
// edit line - deviceOwnerAuthentication
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
//showAlertViewIfNoBiometricSensorHasBeenDetected()
return
}
// edit line - deviceOwnerAuthentication
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &errorPointer) {
// edit line - deviceOwnerAuthentication
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason, reply: { (success, error) in
if success {
DispatchQueue.main.async {
print("Authentication was successful")
}
}else {
DispatchQueue.main.async {
//self.displayErrorMessage(error: error as! LAError )
print("Authentication was error")
}
}
})
}else {
// self.showAlertWith(title: "Error", message: (errorPointer?.localizedDescription)!)
}
}

Objective-C crash with zombie object

I've made an implementation for an JSON-RPC (a little bit modified) Server/Client in objective-c with the GCDAsyncSocket library. but the app crashes on responding to an request. without debugging for zombies i'm getting this error:
JSONRPCTestServer(1301,0x7fff7f887960) malloc: *** error for object 0x10014db10: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
the screenshot in xcode shows the error is in the completeCurrentRead method of the GCDAsyncSocket library.
when debugging for zombies it logs this:
2013-02-04 14:36:16.430 JSONRPCTestServer[1367:603] *** -[__NSArrayI release]: message sent to deallocated instance 0x1005b6fd0
and instruments shows this:
as this happens when a response to the rpc-call is of type nsarray i'd guess its this array that is causing the error. the rpc-method is:
-(NSArray *)testArray:(GCDAsyncSocket *)sock {
return [NSArray arrayWithObjects:#"test1",#"test2", nil];
}
The JSON-RPC header is:
#import <Foundation/Foundation.h>
#import "JSONRPCMethod.h"
#import "JSONRPCResponse.h"
#import "JSONRPCError.h"
#import "JSONRPCArgument.h"
#import "JSONRPCRequest.h"
#import "GCDAsyncSocket.h"
#class GCDAsyncSocket;
#protocol JSONRPCResponseDelegate <NSObject>
#optional
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
#end
#interface JSONRPC : NSObject {
NSMutableArray *supportedMethods;
GCDAsyncSocket *mainSocket;
NSMutableArray *connectedSockets;
NSMutableArray *responseDelegates;
BOOL isServer;
}
+(JSONRPC*)sharedConnection;
-(BOOL)startServer:(NSUInteger)port;
-(BOOL)connectToServer:(NSString*)host port:(NSUInteger)port;
-(BOOL)addMethod:(JSONRPCMethod*)method;
-(void)removeMethod:(JSONRPCMethod*)method;
-(void)removeMethodsWithTarget:(id)target;
-(void)sendRequest:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendRequest:(JSONRPCRequest*)req;
-(void)sendNotification:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendNotification:(JSONRPCRequest*)req;
-(void)sendNotification:(JSONRPCRequest*)req toSocketsWithUserData:(id)userData;
#end
.m is:
#import "JSONRPC.h"
#import "GCDAsyncSocket.h"
#define kGeneralReadTimeout -1.0
#define kGeneralWriteTimeout -1.0
#implementation JSONRPC
- (id)init
{
self = [super init];
if (self) {
isServer = NO;
supportedMethods = [[NSMutableArray alloc] init];
mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
connectedSockets = [[NSMutableArray alloc] init];
responseDelegates = [[NSMutableArray alloc] init];
}
return self;
}
+ (JSONRPC *)sharedConnection {
static JSONRPC *sharedSingleton;
#synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[JSONRPC alloc] init];
return sharedSingleton;
}
}
-(BOOL)startServer:(NSUInteger)port {
// Now we tell the socket to accept incoming connections.
// We don't care what port it listens on, so we pass zero for the port number.
// This allows the operating system to automatically assign us an available port.
isServer = YES;
NSError *err = nil;
if ([mainSocket acceptOnPort:port error:&err]) {
} else {
DDLogError(#"Error while starting JSON-RPC Server: %#",err);
return NO;
}
DDLogInfo(#"Started JSON-RPC Server on port %hu",[mainSocket localPort]);
return YES;
}
-(BOOL)connectToServer:(NSString *)host port:(NSUInteger)port {
NSError *err = nil;
mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[mainSocket connectToHost:host onPort:port error:&err];
if(err != nil) {
DDLogError(#"Couldn't connect to host %#:%lu (Error: %#)",host,port,err);
return NO;
}
return YES;
}
-(BOOL)addMethod:(JSONRPCMethod *)method {
for (JSONRPCMethod *meth in supportedMethods) {
if([meth.name isEqualToString:method.name]) {
return NO;
}
}
[supportedMethods addObject:method];
return YES;
}
-(void)removeMethod:(JSONRPCMethod *)method {
[supportedMethods removeObject:method];
}
-(void)removeMethodsWithTarget:(id)target {
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (JSONRPCMethod *meth in supportedMethods) {
if(meth.target == target) {
[toRemove addObject:meth];
}
}
[supportedMethods removeObjectsInArray:toRemove];
}
-(void)sendRequest:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock {
[responseDelegates addObject:req];
[req setIdentifier:[NSNumber numberWithUnsignedInteger:[responseDelegates count]-1]];
[self sendPackage:[req dictionary] toSocket:sock];
}
-(void)sendRequest:(JSONRPCRequest *)req {
[self sendRequest:req toSocket:mainSocket];
}
-(void)sendNotification:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock{
[req setIdentifier:nil];
[self sendPackage:[req dictionary] toSocket:sock];
}
-(void)sendNotification:(JSONRPCRequest *)req {
[self sendNotification:req toSocket:mainSocket];
}
-(void)sendNotification:(JSONRPCRequest *)req toSocketsWithUserData:(id)userData {
NSMutableArray *matchingSockets = [[NSMutableArray alloc] init];
for (GCDAsyncSocket*sock in connectedSockets) {
if(sock.userData == userData) {
[matchingSockets addObject:sock];
}
}
if(matchingSockets.count == 0)
return;
[req setIdentifier:nil];
NSData *pkgData = [self writableDataFromDictionary:[req dictionary]];
for (GCDAsyncSocket*sock in matchingSockets) {
[sock writeData:pkgData withTimeout:kGeneralWriteTimeout tag:0];
}
}
#pragma mark Socket Delegate
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
[connectedSockets addObject:newSocket];
[newSocket readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
DDLogVerbose(#"socket:didConnectToHost:%# port:%hu", host, port);
[sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
DDLogVerbose(#"socketDidDisconnect:%#", err);
if(isServer)
[connectedSockets removeObject:sock];
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// So, we've received something from the client
// As we have to cut out the last 0x00 for JSONSerialization it has to be longer than 1 byte
if(data.length > 1) {
// Shorten out that 0x00
data = [data subdataWithRange:NSMakeRange(0, data.length-1)];
// Try to serialize
NSError *err;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
DDLogVerbose(#"Dict: %#",dict);
if(err != nil) {
// The package isn't json
JSONRPCResponse *response = [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
[self sendPackage:[response dictionary] toSocket:sock];
} else {
JSONRPCResponse *response = [self handleDictionary:dict fromSocket:sock];
if(response != nil) {
[self sendPackage:[response dictionary] toSocket:sock];
}
}
}
[sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}
-(JSONRPCResponse*)handleDictionary:(NSDictionary*)dict fromSocket:(GCDAsyncSocket*)sock {
// Check if the "id" is of a correct value/type
id identifier = [dict valueForKey:#"id"];
if(!(identifier == nil || [identifier isKindOfClass:[NSNumber class]])) {
return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
}
// Handle the package
NSString *methodName = [dict valueForKey:#"method"];
id errorValue = [dict valueForKey:#"error"];
id resultValue = [dict valueForKey:#"result"];
if([methodName isKindOfClass:[NSString class]]) {
// We have a string as method
DDLogInfo(#"Method: %#, object: %#",methodName,[dict valueForKey:#"params"]);
for (JSONRPCMethod *method in supportedMethods) {
if([method.name isEqualToString:methodName]) {
id result = nil;
if(isServer == YES) {
// It is a server and the method needs to know from where the call comes
result = [method invoke:[dict valueForKey:#"params"] fromSocket:sock];
} else {
// It is a client and we don't need to know where the call is from. it can only be the server.
result = [method invoke:[dict valueForKey:#"params"]];
}
if([result isKindOfClass:[JSONRPCError class]]) {
return [JSONRPCResponse responseWithError:result id:identifier];
} else if(result != nil) {
return [JSONRPCResponse responseWithResult:result id:identifier];
} else {
return nil;
}
}
}
} else if(resultValue != nil) {
// We have a response from our partner
//DDLogInfo(#"Result: %#",resultValue);
NSUInteger responseDelegateId = [identifier unsignedIntegerValue];
if(responseDelegateId < [responseDelegates count]) {
JSONRPCRequest *originalRequest = [responseDelegates objectAtIndex:responseDelegateId];
if(originalRequest.sender == nil) {
return nil;
}
#try {
SEL selector;
if(isServer) {
selector = #selector(rpcSocket:returnedValue:forMethod:id:);
} else {
selector = #selector(rpcReturnedValue:forMethod:id:);
}
NSMethodSignature *signature = [originalRequest.sender methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:originalRequest.sender];
[invocation setSelector:selector];
NSUInteger startArg = 2;
if(isServer) {
[invocation setArgument:&sock atIndex:startArg];
startArg++;
}
NSString *method = [originalRequest method];
id orgId = [originalRequest identifier];
[invocation setArgument:&resultValue atIndex:startArg];
[invocation setArgument:&method atIndex:startArg+1];
[invocation setArgument:&orgId atIndex:startArg+2];
[invocation invoke];
}
#catch (NSException *exception) {
DDLogWarn(#"Couldn't find a response: %#",exception);
}
}
} else if(errorValue != nil) {
// We have a string as method
DDLogInfo(#"Error: %#",errorValue);
} else {
return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest] id:identifier];
}
return nil;
}
-(void)sendPackage:(NSDictionary *)dict toSocket:(GCDAsyncSocket *)sock {
NSData *answerData = [self writableDataFromDictionary:dict];
[sock writeData:answerData withTimeout:kGeneralWriteTimeout tag:0];
}
-(NSData*)writableDataFromDictionary:(NSDictionary*)dict {
NSMutableData *answerData = [[NSMutableData alloc] init];
// Serialize the answer
NSError *err = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&err];
if(err != nil) {
// Log
DDLogError(#"JSON-RPC had an internal error while converting the answer to JSON. The answer-dictionary is: %#",dict);
// Form answer manually
jsonData = [NSData dataWithBytes:"{\"error\":{\"code\":-32700,\"message\":\"Parse error\"}}"
length:49];
}
// Format the answer
[answerData appendData:jsonData];
[answerData appendData:[GCDAsyncSocket ZeroData]];
return answerData;
}
i just don't know how to fix this. why do i keep getting an error with an nsarray but when i return a nsnumber it works? how can i fix this?