0

I started writing a simple JSON RPC TCP library in Objective C. I have a method that invokes a RPC Method:

- (void)invokeMethod:(NSString *)method
      withParameters:(id)parameters
           requestId:(id)requestId
             success:(void (^)(id responseObject))success
             failure:(void (^)(NSError *error))failure
{
    NSAssert(NSClassFromString(@"NSJSONSerialization"), @"NSJSONSerialization not found!");
    NSDictionary *requestObject = @{@"jsonrpc": @"2.0",
                                    @"method": method,
                                    @"params": parameters,
                                    @"id": requestId};
    NSError *error = nil;
    NSData *jsondData = [NSJSONSerialization dataWithJSONObject:requestObject options:0 error:&error];
    if (error){
        return failure(error);
    }
    [self->callbacks setObject:@{@"success": success ? [success copy] : [NSNull null],
                                 @"failure": failure ? [failure copy] : [NSNull null]}
                        forKey:requestId];
    NSString *str = [[NSString alloc] initWithData:jsondData encoding:NSUTF8StringEncoding];
    NSLog(@"Sending: %@", str);
    [self.socket writeData:jsondData withTimeout:-1 tag:1];
}

The class basically represents a TCP connection, when calling the above method, the JSON data is sent with an id over TCP to the server which either returns a success or a failure:

- (void) socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    NSError *error = nil;
    [self.socket readDataWithTimeout:-1 tag:2];
    // … rpc response parsing code here, removed for simplicity …
    // detect if error or success
    NSDictionary *cbs = [self->callbacks objectForKey:JSONRPCObjectId];
    void(^success)(id resultObject) = [cbs objectForKey:@"success"];
    success ? success(JSONRPCObjectResult) : nil;
    return;
}

Now, I am unsure how to keep track of the success and failure blocks, currently I am storing them in an NSMutableDict, using the requestId as key. Is it fine to do this or is there a better approach that I should use?

ePirat
  • 1,068
  • 11
  • 20
  • @vikingosegundo Sorry, somehow messed it up, edited to correct it. – ePirat Aug 18 '14 at 08:45
  • wyh not just have two ivars/properties, one for success and one for failure? – vikingosegundo Aug 18 '14 at 08:46
  • The problem with TCP is that it is a constant stream, so I cannot just create a new Object for each request. I could create a new object for each invokeMethod call, but then I would need a way to store and keep track of these, not really solving the problem. – ePirat Aug 18 '14 at 08:49
  • 1
    I don't see the difference why properties wouldn't work but stored in a dictionary would work. you should give us (much) more details. And code. – vikingosegundo Aug 18 '14 at 08:52
  • what is your minimum target OS that you need to test for `NSJSONSerialization`? and when do you remove the blocks from the callback dictionary? – vikingosegundo Aug 18 '14 at 09:01
  • When using properties, calling the method again would remove the previous blocks and if I get the response the block could already be gone because of this. (Responses can arrive at any time, so I could send invokeMethod with id 1 and another with id 2, and id 2 could arrive first and only then the one for id 1) – ePirat Aug 18 '14 at 09:05
  • I remove them after calling, when I get the response. It could happen that I never get any response, I have yet not found any solution for this… – ePirat Aug 18 '14 at 09:08
  • I got the feeling that your class is already doing too much. but you still gave us too few information to make do more than guessing. – vikingosegundo Aug 18 '14 at 09:10

2 Answers2

0

Blocks in objective-c are objects and you can treat the same way as other object, so storing them in NSDictionarys, NSArrays etc is perfectly fine. The only catch is that blocks when initially created exist in the same memory scope as local variable do and so they are no longer valid when the method that the block is defined in returns, just like all other local variables so you have to copy them first, just copy them and put the copy in the collection. There is a block copy function but you can just send them a copy message [myBlock copy];

Nathan Day
  • 5,981
  • 2
  • 24
  • 40
0

Quick answer, seeing as you don't have anything workable yet...

This is more than you asked for; so, you'll probably have to pair it down to meet your specific need. Basically, it stores as many blocks as you specify at contiguous memory addresses. Paste this into a header file or somewhere global to the method from which you will call these:

typedef const typeof(id(^)(void)) retained_object;
static id (^retainable_object)(id(^)(void)) = ^ id (id(^object)(void)) {
    return ^{
        return object();
    };
};

typeof (retained_object) *(^(^retain_object)(id (^__strong)(void)))(void) = ^ (id(^retainable_object)(void)) {
    typeof(retained_object) * object_address;
    object_address = &retainable_object;
    typeof(retained_object) * persistent_object = (typeof(retained_object) *)CFBridgingRetain(retainable_object);
    return ^ typeof(retained_object) * {
        return persistent_object;
    };
};

static void (^(^iterator)(const unsigned long))(id(^)(void)) = ^ (const unsigned long object_count) {
    id const * retained_objects_ref[object_count];
    return ^ (id const * retained_objects_t[]) {
        return ^ (id(^object)(void)) {
            object();
            int index = 0UL;
            int * index_t = &index;
           for (; (*index_t) < object_count; ((*index_t) = (*index_t) + 1UL)) printf("retained_object: %p\n", (*((id * const)retained_objects_t + (object_count - index)) = retain_object(retainable_object(object()))));
        };
    }(retained_objects_ref);
};

From some method, add:

iterator(1000)(^ id { return (^{ printf("stored block\n"); }); });

This should store 1,000 blocks at as many unique memory addresses.

James Bush
  • 1,485
  • 14
  • 19