我正在创建一个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)或外面的任何对象
答案 0 :(得分:3)
在讨论你所问的问题之前,最好还是讨论@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 Dispatch的dispatch_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
的实例从第一个表视图控制器传递到第二个。
注意:为简洁起见,我假设第二个表视图控制器在用户选择第一个表视图控制器中的帐户行后立即激活。
为此,您需要添加属性
@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...
}
}];
//...
最好的解决方案是存储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...
}
}];
//...