同步性
在讨论您要问的问题之前,有必要讨论一下@MartinR 更详细地提到的内容:该方法-[ACAccount performRequestWithHandler:]
是异步的。
从正在执行代码的线程调用该方法会导致某些活动在第二个后台队列上启动。同时,在第一个线程(正在执行的线程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
将设置为获取的结果,并且其表视图将重新加载其数据。
范围(解决问题)
你描述的问题
因为它是 + (NSArray *) 它无法识别我的 (ACAccount) 或外部的任何对象
TwitterWrapper
是您无法从您的类方法中访问您的类的任何实例的实例变量(为方便起见,我将调用它) +[TwitterWrapper executeTweetFetch]
。这里的问题是范围之一。(结果,多亏了ARC,问题也是内存管理之一。)
我们的目标是从您的第一个表视图控制器中存储一个实例,ACAccount
并从您的第二个表视图控制器访问该实例。
有几种方法可以做到这一点:
使用全局变量
最糟糕的 方法是使用可怕的全局变量:
在中,您将TwitterWrapper.h
声明for twitter: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;
//...
}
注意:这种方法非常不时尚,更不用说完全危险了。 应尽可能避免使用全局变量。 在这种情况下,避免一个当然是可能的。
接下来的两种方法都涉及更改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
局部变量:
@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);
}
}];
}
接下来,将 type 的属性添加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...
}
}];
//...