带有+(NSArray)的NSObject

时间:2013-01-20 15:05:19

标签: ios cocoa-touch nsarray nsobject

我正在创建一个NSObject来加载TimeLine twitter 代码:

+ (NSArray *)executeTweetFetch

{

    __block NSArray *fetchedTweets = [NSArray array];

    NSURL *getTweetUrl = [NSURL URLWithString:@"http://api.twitter.com/1.1/statuses/home_timeline.json"];



    SLRequest *tweetRequest  = [SLRequest requestForServiceType:SLServiceTypeTwitter

                                                  requestMethod:SLRequestMethodGET

                                                            URL:getTweetUrl

                                                     parameters:nil];
    tweetRequest.account = ACAccount HERE!!!;

    [tweetRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {

        if ([urlResponse statusCode] == 200) {
            // Parse the responseData, which we asked to be in JSON format for this request, into an NSDictionary using NSJSONSerialization.

            NSError *jsonParsingError = nil;
            fetchedTweets = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonParsingError];
            //At this point, fetchedTweet seems working fine, it gets the array send back.
        }
    }];

    return fetchedTweets;

}

...很好......但我必须添加一个ACAccount(另一个视图)。并且由于它是+(NSArray *),它不识别我的(ACAccount)或外面的任何对象

1 个答案:

答案 0 :(得分:3)

Synchroneity

在讨论你所问的问题之前,最好还是讨论@MartinR提到的更长的内容:方法-[ACAccount performRequestWithHandler:]是异步的。

从正在执行代码的线程调用该方法会导致某些活动在第二个背景queue上启动。同时,在第一个线程(正在执行performRequestWithHandler:executeTweetRequest的线程)上继续执行。在executeTweetRequest被调用之后的一些不确定的时间量,作为-[ACAccount performRequestWithHandler:]的唯一参数被传递的块被调用。

因此,您将无法从executeTweetRequest同步返回推文数组。

最时尚的方法是将您的方法更改为基于块的方法:

+ (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block
{
    NSURL *getTweetUrl = [NSURL URLWithString:@"http://api.twitter.com/1.1/statuses/home_timeline.json"];
    SLRequest *tweetRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
                                                 requestMethod:SLRequestMethodGET
                                                           URL:getTweetUrl
                                                    parameters:nil];
    tweetRequest.account = //...this will be addressed in a moment
    [tweetRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
        if ([urlResponse statusCode] == 200) {
            NSError *jsonParsingError = nil;
            NSArray *fetchedTweets = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonParsingError];
            block(fetchedTweets, jsonParsingError);
        }
    }];
}

从第二个fetchTweetsWithCompletion:子类调用UITableViewController时,您将传入一个块,可能与以下内容类似:

- (void)viewDidLoad
{
    //...
    [TwitterWrapper fetchTweetsWithCompletion:^(NSArray *tweets, NSError *error) {
        if (tweets) {
            self.tweets = tweets;
            [self.tableView reloadData];
        } else if (error) {
            //present a UIAlertView, perhaps...
        }
    }];
    //...
}

此代码的结果是,一旦后台队列上的推文获取完成,表视图控制器的属性tweets将被设置为获取的结果,其表视图将重新加载其数据。 / p>

范围(解决问题)

您正在描述的问题

  

因为它是+(NSArray *)它不能识别我的(ACAccount)或外面的任何对象

是您无法在类方法TwitterWrapper中访问任何类实例的实例变量(为方便起见,我将其称为+[TwitterWrapper executeTweetFetch])。这里的问题是scope之一。 (因此,感谢ARC,问题也是内存管理问题。)

我们的目标是从第一个表视图控制器中隐藏ACAccount某个实例,并从第二个表视图控制器访问该实例。

有几种方法可以做到这一点:

使用全局变量

最差 approach是使用可怕的global variable

TwitterWrapper.h中,您要为{}宣布extern ACAccount

//TwitterWrapper.h

extern ACAccount *twitterAccount;

@interface TwitterWrapper : executeTweetFetch
+ (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block;
@end

TwitterWrapper.m中,您可以定义twitterAccount以使其具有全局范围(例如,在@implementation块之前:

ACAccount *twitterAccount;

@implementation TwitterWrapper
//...
@end

在您的类方法executeTweetFetch的定义中,您将拥有

+ (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block
{
    //...
    tweetRequest.account = twitterAccount;
    //...
}

注意:这种做法非常不合时宜,更不用说完全危险了。 Global variables should be avoided whenever possible.在这种情况下,当然可以避免使用。


接下来的两种方法都涉及将fetchTweetsWithCompletion:更改为实例方法并将ACAccount属性添加到TwitterWrapper。 “@interface”类似乎

@interface TwitterWrapper : NSObject
@property (nonatomic) ACAccount *account;
- (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block;
@end

@implementation看起来像

@implementation TwitterWrapper

- (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block
{
    //...
    twitterRequest.account = self.account;
    //...
}

@end

使用共享实例

在这种情况下,第二个最糟糕的方法是使用TwitterWrapper的共享实例。这涉及到类中添加一个类方法(称为聪明的+sharedWrapper):

@interface TwitterWrapper : NSObject
+ (TwitterWrapper *)sharedWrapper;
@property (nonatomic) ACAccount *account;
- (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block;
@end

并使用Grand Central Dispatchdispatch_once初始化static local variable

@implementation TwitterWrapper

//...

+ (TwitterWrapper *)sharedWrapper
{
    static TwitterWrapper *sharedWrapper = nil;

    static dispatch_once_t pred;
    dispatch_once(&pred, ^{ 
        sharedWrapper = [[self alloc] init]; 
    });

    return sharedWrapper;
}

//...

@end

此时,您有一个TwitterWrapper实例,您可以从第一个表视图控制器中存储ACAccount的实例,并在实现中使用(通过self.account来自第二个表视图控制器的-fetchTweetsWithCompletion:):

你的第一个UITableViewController子类中的某个地方

//...
ACAccount *theAccount = //this is initialized somehow within this class (the first UITableViewController subclass), probably by the selection of a row
[[TwitterWrapper sharedWrapper] setAccount:theAccount];
//...

然后,在你的第二个UITableViewController子类(可能在-viewDidLoad内),你会有

    //...
    [[TwitterWrapper sharedWrapper] fetchTweetsWithCompletion:^(NSArray *tweets, NSError *error) {
        if (tweets) {
            self.tweets = tweets;
            [self.tableView reloadData];
        } else if (error) {
            //present a UIAlertView, perhaps...
        }
    }];
    //...

然而,此解决方案与使用全局变量非常相似。 (不同之处在于全局变量的创建将是线程安全的。)

传递TwitterWrapper的实例

更好的解决方案是将TwitterWrapper的实例从第一个表视图控制器传递到第二个。

注意:为简洁起见,我假设第二个表视图控制器在用户选择第一个表视图控制器中的帐户行后立即激活。

为此,您需要添加属性

@property (nonatomic) TwitterWrapper *twitterWrapper;

到你的第二个UITableViewController子类。

你的第一个表视图控制器中的某个地方

//...
ACAccount *theAccount = //this is initialized somehow within this class (the first UITableViewController subclass), probably by the selection of a row
TwitterWrapper *theTwitterWrapper = [[TwitterWrapper alloc] init];
twitterWrapper.account = theAccount;
SecondTableViewController *tweetsTableViewController = //initialize the table view controller
tweetsTableViewController.twitterWrapper = theTwitterWrapper;
//...    

然后,在你的第二个UITableViewController子类(可能在-viewDidLoad内),你会有

    //...
    [self.twitterWrapper fetchTweetsWithCompletion:^(NSArray *tweets, NSError *error) {
        if (tweets) {
            self.tweets = tweets;
            [self.tableView reloadData];
        } else if (error) {
            //present a UIAlertView, perhaps...
        }
    }];
    //...

传递ACAccount实例

最好的解决方案是存储TwitterWrapper的实例和更改UITableViewController的接口,而不是将ACAccount的实例存储在第二个TwitterWrapper子类实例上。再次发送推文。

在这种情况下,我们希望fetch方法再次成为一个类方法。正如@MartinR建议的那样,在fetch方法中添加一个帐户参数:

+ (void)fetchTweetsWithAccount:(ACAccount *)theAccount completion:(void (^)(NSArray *, NSError *))block
{
    NSURL *getTweetUrl = [NSURL URLWithString:@"http://api.twitter.com/1.1/statuses/home_timeline.json"];
    SLRequest *tweetRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
                                                 requestMethod:SLRequestMethodGET
                                                           URL:getTweetUrl
                                                    parameters:nil];
    tweetRequest.account = theAccount;
    [tweetRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
        if ([urlResponse statusCode] == 200) {
            NSError *jsonParsingError = nil;
            NSArray *fetchedTweets = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonParsingError];
            block(fetchedTweets, jsonParsingError);
        }
    }];
}

接下来,将类型ACAccount的属性添加到第二个UITableViewController子类:

@property (nonatomic) ACAccount *account;

然后,在你的第一个表视图控制器中的某个地方

//...
ACAccount *theAccount = ////this is initialized somehow within this class (the first UITableViewController subclass), probably by the selection of a row
SecondTableViewController *tweetsTableViewController = //initialize the table view controller
tweetsTableViewController.account = theAccount;
//...    

然后,在你的第二个UITableViewController子类(可能在-viewDidLoad内),你会有

    //...
    [TwitterWrapper fetchTweetsWithAccount:self.account completion:^(NSArray *tweets, NSError *error) {
        if (tweets) {
            self.tweets = tweets;
            [self.tableView reloadData];
        } else if (error) {
            //present a UIAlertView, perhaps...
        }
    }];
    //...