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

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];
}

Related

NSURLSessionDownloadTask in NSOperation crashes on cancel

I'm trying to create a DownloadOperation subclass of NSOperation to download data asynchronously. Everything seemed to be working fine until I tried to add cancelling support. Basically, the completion handler of the operation's NSURLSessionDownloadTask seems to be called after the operation has been released. It will crash with EXC_BAD_ACCESS at the line weakSelf.state = kFinished.
The full sample project is here: https://github.com/angstsmurf/DownloadOperationQueue. Press Command+. after running to crash.
#import "DownloadOperation.h"
typedef enum OperationState : NSUInteger {
kReady,
kExecuting,
kFinished
} OperationState;
#interface DownloadOperation ()
#property NSURLSessionDownloadTask *task;
#property OperationState state;
#end
#implementation DownloadOperation
// default state is ready (when the operation is created)
#synthesize state = _state;
- (void)setState:(OperationState)state {
#synchronized(self) {
if (_state != state) {
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_state = state;
[self didChangeValueForKey: #"isExecuting"];
[self didChangeValueForKey: #"isFinished"];
}
}
}
- (OperationState)state {
#synchronized (self) {
return _state;
}
}
- (BOOL)isReady { return (self.state == kReady); }
- (BOOL)isExecuting { return (self.state == kExecuting); }
- (BOOL)isFinished { return (self.state == kFinished); }
- (BOOL)isAsynchronous {
return YES;
}
- (instancetype)initWithSession:(NSURLSession *)session downloadTaskURL:(NSURL *)downloadTaskURL completionHandler:(nullable void (^)(NSURL * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable))completionHandler {
self = [super init];
if (self) {
__unsafe_unretained DownloadOperation *weakSelf = self;
// use weak self to prevent retain cycle
_task = [[NSURLSession sharedSession] downloadTaskWithURL:downloadTaskURL
completionHandler:^(NSURL * _Nullable localURL, NSURLResponse * _Nullable response, NSError * _Nullable error) {
/*
if there is a custom completionHandler defined,
pass the result gotten in downloadTask's completionHandler to the
custom completionHandler
*/
if (completionHandler) {
completionHandler(localURL, response, error);
}
/*
set the operation state to finished once
the download task is completed or have error
*/
weakSelf.state = kFinished;
}];
}
return self;
}
- (void)start {
/*
if the operation or queue got cancelled even
before the operation has started, set the
operation state to finished and return
*/
if (self.cancelled) {
self.state = kFinished;
return;
}
// set the state to executing
self.state = kExecuting;
NSLog(#"downloading %#", self.task.originalRequest.URL.absoluteString);
// start the downloading
[self.task resume];
}
-(void)cancel {
[super cancel];
// cancel the downloading
[self.task cancel];
}
#end
As pointed out in the comments by Scott Thompson, the correct keyword to use for the weakSelf variable is __weak, not __unsafe_unretained.

How to perform all other tasks once the asynchronous dispatch queue finishes execution

I have a PDFDocument where it has some actions on pages like delete,crop,rotate etc.
So when I click on the delete button and click on save(current thread : main thread)
-(void)save
{
// -- process deleted pages first
for( NSString* deletedPageId in self.deletedPageIdList )
{
[self.pdfCoordinator removePageWithId:deletedPageId];
}
// -- wait for all pages to delete before continuing
[self.pdfCoordinator waitUntilAllOperationsAreFinished];
// few lines of code after this should run only when the above code finishes its execution
// code to save the changes in the pdf to managedObject context
}
The code for removePageWithId:
- (void) removePageWithId:(NSString*)pageId
{
NRMPDFOperation *op = [[NRMPDFOperation alloc] initWithNRMPDF:self
pageId:pageId
selector:#selector(removePageOpImpl:)
args:nil];
[self addOperation:op];
[op release];
}
above code creates an operation and adds it to the operation queue for each deletion of the page
code for removePageOpImpl:
- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
NSError* error = [self loadDocument];
if( !error )
{
NSUInteger index = [self pageIndexForId:[op pageId]];
if( index < [self pageCount] )
{
[[self pdfDocument] removePageAtIndex:index];
[[self mutablePageIdList] removeObjectAtIndex:index];
[self updatePageLabelsFromIndex:index];
[self updateChangeCount:NSChangeDone];
self.contentsChanged = YES;
}
else
{
// TODO: error
}
}
return error;
}
In the removePageOpImpl: method the line of code
[[self pdfDocument] removePageAtIndex:index]; internally executing some tasks on main thread(but we are making the main thread to wait until this operation finishes).which causes the deadlock.
I tried to execute the code inside removePageOpImpl: in an asynchronous dispatch queue to avoid the deadlock.below is the code for that
- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
NSError* error = [self loadDocument];
if( !error )
{
NSUInteger index = [self pageIndexForId:[op pageId]];
if( index < [self pageCount] )
{
dispatch_async(dispatch_get_main_queue(), ^{
[[self pdfDocument] removePageAtIndex:index];
[[self mutablePageIdList] removeObjectAtIndex:index];
[self updatePageLabelsFromIndex:index];
[self updateChangeCount:NSChangeDone];
self.contentsChanged = YES;
});
}
else
{
// TODO: error
}
}
return error;
}
Now I am out from the deadlock. But another issue is by putting the above code into asynchronous block the code which should run after this tasks is executing before this, because of that my app is not behaving as expected.
Code inside waitUntilAllOperationsAreFinished method
- (void) addOperation:(NRMPDFOperation*)operation
{
[_operationSet addObject:operation];
[[self operationQueue] addOperation:operation];
}
- (void) waitUntilAllOperationsAreFinished
{
[[self operationQueue] waitUntilAllOperationsAreFinished];
}
- (NSOperationQueue *)operationQueue
{
if( !_operationQueue )
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
_operationQueue = queue;
}
return _operationQueue;
}
This is how the original Save method looks like :
- (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
{
// TODO: don't just ignore saveOperation
__block BOOL success = YES;
__block NSError *error = nil;
/* write back field changes */
if ([item hasChangesInEditFieldsetFor:#"values"] )
{
//Some code
}
if( self.isPDFEdited )
{
// -- process deleted pages first
for( NSString* deletedPageId in self.deletedPageIdList )
{
[self.itemPDFCoordinator removePageWithId:deletedPageId];
}
// -- wait for all pages to delete before continuing
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
// -- replace the search text any pages we deleted
if( [self.deletedPageIdList count] )
{
[self.item setValue:[self.editPDF string] forKeyPath:#"dict.values._searchText"];
}
NSMutableDictionary* originalRotations = [NSMutableDictionary dictionaryWithCapacity:
[self.itemPDFCoordinator pageCount]];
for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
{
NSInteger rotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
[originalRotations setObject:[NSNumber numberWithInteger:rotation] forKey:pageId];
}
// -- now process actions on remaining pages (crop, rotate, and convert to b&w)
BOOL didCropAnyPages = NO;
NSMutableArray* convertToBwJobs = [NSMutableArray array];
for( NSString* pageId in [self.pageActionDict allKeys] )
{
NSArray* actions = [self.pageActionDict objectForKey:pageId];
for( NSDictionary* action in actions )
{
NSNumber* rotationNumber = [action objectForKey:#"rotation"];
NSValue* cropRectVal = [action objectForKey:#"cropRect"];
NSNumber* convertToBlackAndWhite = [action objectForKey:#"convertToBlackAndWhite"];
if( rotationNumber )
{
[self.itemPDFCoordinator rotateByDegrees:[rotationNumber integerValue]
forPageWithID:pageId];
}
else if( cropRectVal )
{
[self.itemPDFCoordinator setNormalizedBounds:[cropRectVal rectValue]
forBox:kPDFDisplayBoxCropBox
forPageWithID:pageId];
// -- set a flag so we know to recrop the entire document
didCropAnyPages = YES;
}
else if( [convertToBlackAndWhite boolValue] )
{
NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
NRMJob* job = [NRMAppJobFactory convertToBlackAndWhiteJobForItem:self.item
pageIndex:pageIndex];
[convertToBwJobs addObject:job];
}
}
}
// -- reapply crop box to any cropped pages
if( didCropAnyPages )
{
[self.itemPDFCoordinator applyCropBoxToAllPages];
}
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
for( NRMJob* job in convertToBwJobs )
{
if( ![[self.masterDocument docjob] addJob:job forItem:self.item error:&error] )
[NSApp presentError:error];
else
[job waitUntilDone];
}
// -- make sure document attributes are updated
NSDictionary *docDict = [self.itemPDFCoordinator documentAttributes];
NSDictionary *newDict = [(NRMItem *)item updateDocumentAttributes:docDict];
if (![newDict isEqualToDictionary:docDict])
[self.itemPDFCoordinator setDocumentAttributes:newDict];
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
// -- check if we need to reprocess any pages
for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
{
NSInteger oldRotation = [[originalRotations objectForKey:pageId] integerValue];
NSInteger newRotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
if( oldRotation != newRotation )
{
// -- if it's an image page and we already have OCR data for it, we should reanalyze
NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
BOOL isPageImage = [self.itemPDFCoordinator isImagePageAtIndex:pageIndex DPI:NULL];
if( isPageImage && [item OCRDataForPageIndex:pageIndex] )
{
NRMJob* job = [NRMAppJobFactory reprocessPageJobForItem:self.item
pageIndex:pageIndex];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reanalyzeJobFinished:)
name:kNRMJobFinishedNotification
object:job];
success = [[self.masterDocument docjob] addJob:job forItem:self.item error:&error];
if( !success )
{
if( error )
[self presentError:error];
}
//goto bail;
}
}
}
//Force Save of PDF to Disk
[self.itemPDFCoordinator setManagedObjectContext:[item managedObjectContext]];
[self.itemPDFCoordinator saveChanges];
}
if( success )
{
[self updateChangeCount:NSChangeCleared];
if( self.isPDFEdited )
{
// !CLEARSPLIT! please do not remove this comment
[self.item setValue:nil forKeyPath:#"dict.values._splitId"];
if( ![self loadPDFForItem:item error:&error] )
goto bail;
}
}
bail:
if( error )
[self presentError:error];
if( delegate )
{
/* signature:
- (void)document:(NSDocument *)document didSave:(BOOL)didSaveSuccessfully contextInfo:(void *)contextInfo;
*/
objc_msgSend( delegate, didSaveSelector, self, (error ? NO : YES), contextInfo );
}
}
can anyone suggest me how can I get out of this issue.
Rather than calling waitUntilAllOperationsAreFinished (which blocks a thread), I'd suggest specifying an additional completion operation, which you can make dependent upon the other operations finishing:
-(void)save {
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// whatever you want to do when it's done
}];
for (NSString* deletedPageId in self.deletedPageIdList) {
NSOperation *operation = [self.pdfCoordinator removePageWithId:deletedPageId];
[completionOperation addDependency:operation];
}
// add this completionOperation to a queue, but it won't actually start until the other operations finish
[queue addOperation:completionOperation];
}
Clearly this assumes that you change removePageWithId to return the operation it added to the queue:
- (NSOperation *)removePageWithId:(NSString*)pageId {
NRMPDFOperation *operation = [[NRMPDFOperation alloc] initWithNRMPDF:self
pageId:pageId
selector:#selector(removePageOpImpl:)
args:nil];
[self addOperation:operation];
[operation release];
return operation;
}
This way, you're not blocking any thread waiting for the operations to finish, but simply specify what to do when they do finish.

Objective C autorelease object with block

I have an object that I use like this
[PeripheralManager readValueForCharacteristic:[CBUUID UUIDWithString:CHARACTERISTIC_TX_POWER_LEVEL]
inService:[CBUUID UUIDWithString:SERVICE_TX_POWER]
completionBlock:^(NSData *value) {
//code
}];
The problem is that if I doesn't save the object in a strong property when I call completionBlock the app crashes.
You can say "Save your PeripheralManager in a strong property"... well there are some situation in witch I have to read a value and than another value again e so on, maybe for 3 or 4 times.
In other situation is possible call class method that returns some values in a block and the block is not deallocated. Is there a way to keep mi PeripheralManager in memory ?
Edit
+ (id)readValueForCharacteristic:(CBUUID *)cUUID
inService:(CBUUID *)sUUID
completionBlock:(completionBlock)completionBlock
{
return [[self alloc] initForReadValueForCharacteristic:cUUID
inService:sUUID
completionBlock:completionBlock];
}
- (id)initForWriteValue:(unsigned char)value
forCharacteristic:(CBUUID *)cUUID
inService:(CBUUID *)sUUID
completionBlock:(completionBlock)completionBlock
{
self = [super init];
if (self)
{
self.completionBlock = [completionBlock copy];
self.accessType = PeripheralAccessTypeWrite;
self.valueToSet = value;
if (cUUID && sUUID)
{
self.sUUID = sUUID;
self.cUUID = cUUID;
self.connectedPeripheral = [[BLEManager manager] getConnectedPeripheral];
self.connectedPeripheral.delegate = self;
if (self.connectedPeripheral && self.connectedPeripheral.state == CBPeripheralStateConnected)
{
[self.connectedPeripheral discoverServices:#[sUUID]];
}
}
else NSLog(#"cUUID and sUUID are mandatory");
}
return self;
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
// NSLog(#"didDiscoverServices");
for (CBService *service in peripheral.services)
{
if ([service.UUID isEqual:self.sUUID])
{
[peripheral discoverCharacteristics:#[self.cUUID] forService:service];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (service.UUID.UUIDString.length >= 8)
{
for (CBCharacteristic *characteristic in service.characteristics)
{
if ([characteristic.UUID isEqual:self.cUUID])
{
if (self.accessType == PeripheralAccessTypeRead)
{
self.connectedCharacteristic = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
[peripheral readValueForCharacteristic:characteristic];
}
else
{
self.connectedCharacteristic = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
unsigned char dat = self.valueToSet;
NSLog(#"dat %i", dat);
NSData * valueData = [NSData dataWithBytes:&dat length:1];
NSLog(#"valueData %#",valueData);
[peripheral writeValue:valueData forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(#"%#",error.localizedDescription);
}
else
{
NSString * characteristicName = [self getCharacteristicName:characteristic];
NSData * value = [self readValueFromCharacteristic:characteristic];
NSLog(#"update %#: %#",characteristicName ,value);
self.completionBlock(value);
}
}
Thanks

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?

Method to return block result

- (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);
}
}];
}