Search code examples
iosobjective-cpropertiesafnetworking

How to reference a method of a class


I'm writing an App.net client and using ADNKit as the framework that communicates with the ADN's servers. I've gotten a few ideas from the open source code of Zephyr, an app.net client that was turned into an open source project.

Their view controllers for displaying lists of posts (PostStreamViewController) use a data controlling object and a configuration object that has a property called apiCallMaker.

The property is defined like this:

typedef void (^APIPostListCallback)(NSArray *posts, PostListMetadata *meta, NSError *error);
...
@property (nonatomic, copy) void (^apiCallMaker)(APIPostParameters *parameters, APIPostListCallback callback);

This allows them to reuse the same data controller object for the main timeline, mentions timeline, etc. All they need to do is provide a configuration file for each of these post stream types, each one referencing a different api call.

For example, in the configuration file for the mentions post stream they define self.apiCallMaker like this:

- (void (^)(APIPostParameters *parameters, APIPostListCallback callback))apiCallMaker
{
    return [^(APIPostParameters *parameters, APIPostListCallback callback) {
        [APIUserMentionStream getUserMentionStreamWithParameters:parameters userID:self.userID completionHandler:callback];
    } copy];
}

This is becoming long winded but stick with me. I thought this method was quite cool, it makes for a lighter data controller object and configuration files are easy and light.

In my implementation, I instead declare my apiCallMaker like this:

// this is the default parameters returned by ANKClients post fetching methods
typedef void (^APIPostListCallback)(id responseObject, ANKAPIResponseMeta *meta, NSError *error);  
... 
@property (nonatomic, copy) void (^apiCallMaker)(ANKClient *client, APIPostListCallback callback);

In my configuration files I define apiCallMaker like this:

- (void (^)(ANKClient *client, APIPostListCallback callback))apiCallMaker
{
    return [^(ANKClient *client, APIPostListCallback callback) {
        [client fetchPostsMentioningUser:self.user completion:callback];
    } copy];
}

Then when fetching posts I do this:

ANKClient *client = ... // authenticated client object with parameters
self.apiCallMaker(clientCopy, ^(id responseObject, ANKAPIResponseMeta *meta, NSError *error){
    if (!error) {
        // handle data
    } else {
        // handle error
    }
});

The problem with this is that I can't store a reference to the ANKJSONRequestOperation that is returned by the ANKClient object when I call [client fetchPostsMentioningUser:self.user completion:callback]; via the apiCallMaker. I want to store the reference to the ANKJSONRequestOperation because I can easily cancel network requests when my view controller is popped/deallocated.

- (void)fetchPosts
{
    ANKClient *client = ... // authenticated client object with parameters
    self.requestOperation = [clientCopy fetchPostsMentioningUser:user completion:^(id responseObject, ANKAPIResponseMeta *meta, NSError *error) {
        // handle posts/error
    }];
}

...

- (void)dealloc {
    ...
    [self.requestOperation cancel];
}

Is there a way to store a reference to the method that I need to call (fetchPostsMentioningUser:) while still calling the client object so I can store the returned ANKJSONRequestOperation?


Update

Ok, as suggested by @berg, I've changed my property's return type to now be ANKJSONRequestOperation *.

I forgot to mention that I also have a property on the data controller that is just like the configuration file. It's kind of redundant but when initializing the data controller I set its apiCallMaker with the one from the configuration. So here's how I have it laid out now, I've changed the property name for testing.

The problem is, whenever I set self.fetcher it is NULL. Do I need to change how the configuration file returns the ANKJSONRequestOperation. Please forgive my ignorance, blocks give me so many headaches.

Configuration.h
typedef void (^APIPostListCallback)(id responseObject, ANKAPIResponseMeta *meta, NSError *error);
@property (nonatomic, readonly) ANKJSONRequestOperation *(^fetcher)(ANKClient *client, APIPostListCallback callback);
Configuration.m
- (ANKJSONRequestOperation *(^)(ANKClient *client, APIPostListCallback callback))fetcher
{
    return [^(ANKClient *client, APIPostListCallback callback) {
        [client fetchUnifiedStreamForCurrentUserWithCompletion:callback];
    } copy];
}
DataController.h
@property (nonatomic, copy) ANKJSONRequestOperation *(^fetcher)(ANKClient *client, APIPostListCallback callback);
DataController.m
@property (nonatomic, strong) ANKJSONRequestOperation *operation;

- (id)initWithConfiguration:(Configuration *)configuration {
    ...
    self.fetcher = configuration.fetcher
}

- (void)fetchPosts {
    ...
    self.operation = self.fetcher(clientCopy, ^(id responseObject, ANKAPIResponseMeta *meta, NSError *error) {
        if (!error) {
            [self.data setPosts:responseObject meta:meta];
        } else {
            // handle error
        }
    });
}

Solution

  • The implementation of your property getter in Configuration.m should be:

    - (ANKJSONRequestOperation *(^)(ANKClient *client, APIPostListCallback callback))fetcher
    {
        return [^(ANKClient *client, APIPostListCallback callback) {
            return [client fetchUnifiedStreamForCurrentUserWithCompletion:callback];
        } copy];
    }