同期性
あなたが尋ねている問題に入る前に、@MartinR がより詳細に言及したことについて議論する価値があります: メソッド-[ACAccount performRequestWithHandler:]
は非同期です。
コードが実行されているスレッドからメソッドを呼び出すと、一部のアクティビティが 2 番目のバックグラウンドキューで開始されます。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:
2 番目のサブクラスから呼び出す場合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]
。ここでの問題はスコープの 1 つです。(結果として、ARCのおかげで、問題はメモリ管理の問題でもあります。)
私たちの目標は、最初のテーブル ビュー コントローラーからどこかのインスタンスを隠し、ACAccount
2 番目のテーブル ビュー コントローラーからそのインスタンスにアクセスすることです。
これにはいくつかの方法があります。
グローバル変数を使用する
最悪の アプローチは、恐ろしいグローバル変数を使用することです:
では、for twitterを次の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;
//...
}
注: このアプローチは非常にスタイリッシュではなく、まったく危険なことは言うまでもありません。 グローバル変数は可能な限り避けるべきです。 この場合、1 つを回避することは確かに可能です。
次の 2 つの方法は、どちらもインスタンス メソッドへの変更と へのプロパティ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
共有インスタンスを使用する
この場合の 2 番目に悪い方法は、 の共有インスタンスを使用することですTwitterWrapper
。これには、クラス メソッド ( のような巧妙なものと呼ばれる+sharedWrapper
) をクラスに追加することが含まれます。
@interface TwitterWrapper : NSObject
+ (TwitterWrapper *)sharedWrapper;
@property (nonatomic) ACAccount *account;
- (void)fetchTweetsWithCompletion:(void (^)(NSArray *, NSError *))block;
@end
Grand Central Dispatchを使用してローカル変数dispatch_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
の のインスタンスを格納し、2 番目のテーブル ビュー コントローラーから(の実装内で) を利用できる のインスタンスができました。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];
//...
次に、2 番目の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
より良い解決策は、最初のテーブル ビュー コントローラーから 2 番目のテーブル ビュー コントローラーにのインスタンスを渡すことです。
注: 簡潔にするために、ユーザーが最初のテーブル ビュー コントローラーでアカウント行を選択した直後に、2 番目のテーブル ビュー コントローラーがアクティブになると仮定します。
これを行うには、プロパティを追加する必要があります
@property (nonatomic) TwitterWrapper *twitterWrapper;
あなたの2番目の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;
//...
次に、2 番目の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
2 番目UITableViewController
のサブクラス インスタンスにのインスタンスを格納するのではなく、 のインスタンスを格納し、もう一度ツイートを取得するために のインターフェイスをACAccount
変更するのが最善の解決策です。TwitterWrapper
この場合、fetch メソッドを再びクラス メソッドにする必要があります。@MartinR が提案したように、フェッチ メソッドにアカウント パラメータを追加します。
+ (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
2 番目の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;
//...
次に、2 番目の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...
}
}];
//...