Reactive Cocoaというライブラリがあります。これは素晴らしいですが、慣れるのは難しいです。
目標を達成するためのより簡単な方法ですが、それほど素晴らしいものではありませんが、UIAlertViewとUIActionSheetの周りにブロックラッパーを使用することです。また、これは、ネットワークコードにコールバックブロックがあることを前提としています。
例:
- (void)showActionSheet
{
BlockActionSheet *sheet = [BlockActionSheet sheetWithTitle:@"Choose one"];
__weak BlockActionSheet *weakSheet = sheet;
[sheet addButtonWithTitle:@"Blue" atIndex:0 block:^{
[self downloadFileFromServerSuccessBlock:^{
//YAY
} failureBlock:^{
BlockAlertView *alert = [BlockAlertView alertWithTitle:@"Failure" message:@"Something Went Wrong"];
[alert addButtonWithTitle:@"Try Again" block:^{
[weakSheet showInView:self.view];
}];
[alert setCancelButtonWithTitle:@"Cancel" block:nil];
}];
}];
[sheet addButtonWithTitle:@"Black" atIndex:1 block:^{
//something else
}];
[sheet setCancelButtonWithTitle:@"Cancel" block:nil];
[sheet showInView:self.view];
}
したがって、最後の行「[sheet showInView:self.view]」はすべてを開始します。青を選択すると、そのブロックが呼び出されます。ネットワークコードはブロックもサポートしているため、そこから成功と失敗のブロックコールバックを取得します。失敗した場合は、ブロックベースのアラートビューをポップアップ表示して、ActionSheetが再び表示されるようにします。
これが少なくとも少しは役に立ったと思います。また、「self.view」呼び出しで発生している強い参照がある可能性があるため、ビューへの参照も弱くします。
これがReactiveCocoaの例です。私はこのフレームワークに非常に慣れていないので、正しく使用していることを願っています。
/*
* This function is getting called from a login view controller.
*/
- (void)sendAuthentication
{
/*
* A RACSubscribable will invoke the where^ block when it calls [sendNext:];
* That call is coming from the ThaweQBRequestController.
* If the where^ block return YES then the subscribeNext^ block will get called.
* asMaybes wraps the object I send back in a RACMaybe object which has a convenience method [hasObject];
* The subscribeNext^ block has an object coming into it, that object is an array in this case that I sent
* from the ThaweQBRequestController.
*/
RACSubscribable *sub = [[ThaweQBRequestController logInWithUsername:self.thaweusername password:self.thawepassword] asMaybes];
[[sub where:^(id x) {
return [x hasObject];
}]
subscribeNext:^(id _) {
NSArray *array = [_ object];
NSString *errcode = [array objectAtIndex:0];
if (errcode.boolValue == YES)
//YAY SUCCESS!!!
} else {
//UH OH
}
// Update UI, the array has the username and password in it if I want to display or whatever is clever.
}];
}
次に、私が持っているネットワーク要求コントローラーに入ります。
+ (RACAsyncSubject *)logInWithUsername:(NSString *)username password:(NSString *)password
{
/*
* First we have our loginCommand. It will invoke the block we give it on a background thread.
* This block returns a Subscribable. We can subscribe other blocks to be invoked when the aync call is done.
* In this case we are creating loginResult to retrieve the async call.
*/
RACAsyncCommand *loginCommand = [RACAsyncCommand command];
/*
* Now we have our AsyncSubject. We are instantiated it here and are going to pass it back to the login view controller;
* When our async call finishes and we filter through the results we will call [subject sendNext:] to invoke blocks we created in
* the login view controller;
* We will filter the results uses the loginResult blocks.
*/
RACAsyncSubject *subject = [RACAsyncSubject subject];
/*
* loginResult is a "Subscribable". Every time it gets a [sendNext:] call it runs the blocks assosiated with it.
* These [sendNext:] calls are coming from our network code.
* 'repeat' means that even after the async block is invoked it keeps a reference to it incase we want to use it again.
* 'asMaybes' wraps a RACMaybe object around the object you send to the loginResult blocks. The benefit of a RACMaybe is
* that it has some convienence methods like 'hasError' and 'hasObject'.
*/
RACSubscribable *loginResult = [[[loginCommand
addAsyncBlock:^(id _) {
return [self authenticateUser:username password:password];
}]
repeat]
asMaybes];
/*
* The first block, where^, get called every time loginResult calls [sendNext:].
* If it returns YES then the select^ block gets called. The select^ block invokes the subscribeNext^ block and sends it the
* error.
*/
[[[loginResult
where:^(id x) {
return [x hasError];
}]
select:^(id x) {
return [x error];
}]
subscribeNext:^(id x) {
NSLog(@"network error omg: %@", x);
}];
/*
* Same as above except this time we are looking for instances when we have an object instead of an NSError.
* The object we are getting is being returned from our network code. In this case it is an integer, 0 means we had a successfull login.
* Now we can call [subject sencNext:] to inform our login view controller of the pass or fail of the authentication.
* [sendCompleted] is a cleanup call, allowing ReactiveCocoa to dispose of our blocks and free up memory.
*/
[loginResult
where:^(id x) {
return [x hasObject];
}]
subscribeNext:^(id _) {
NSNumber *number;
NSString *errcode = [_ object];
if (errcode.intValue == 0) number = [NSNumber numberWithBool:YES] ?: [NSNumber numberWithBool:NO];
[subject sendNext:[NSArray arrayWithObjects:number, username, password, nil]];
[subject sendCompleted];
}];
/*
* [execute:] starts the whole thing off. This call invokes the loginCommand block that returns the async call to the loginResult subscribable.
*/
[loginCommand execute:@"This value gets transfered to the addAsyncBlock:^(id _) block above."];
return subject;
}
/*
* This function uses a QuickBase wrapper I made to make server request and whatnot.
* This function is called when the [loginCommand execute:] call in the previous function gets called
* and invokes the loginCommand block which returns the async request.
* That request is what this function is returning.
*/
+ (RACAsyncSubject *)authenticateUser:(NSString *)username password:(NSString *)password
{
QBRequest *request = [[QBRequest alloc] init];
[request setQuickBaseAction:QBActionTypeAuthenticate];
[request setURLString:URLstring withDatabaseID:nil];
[request setApplicationToken:appToken];
return [request sendAndPersist:NO username:username password:password];
}
そして今、私たちは実際にネットワークラッパーにいて、リクエストがいつ完了したか失敗したかなどを認識しています。
- (RACAsyncSubject *)sendAndPersist:(BOOL)persist username:(NSString *)username password:(NSString *)password
{
self.subject = [RACAsyncSubject subject];
/*
* These next two blocks are called when my network request is done.
* My AsyncSubject is a property so that I can reference it later when I parse
* throught the response and figure out whether I logged in correctly or not.
* In the case that the network call itself fails, then the AysncSubject calls
* [sendError:] which will invoke those NSError capturing blocks in the ThaweQBRequestController.
*/
[anOp onCompletion:^(MKNetworkOperation *completedOperation) {
dispatch_async(background_parseSave_queue(), ^{
[self updateDatabase];
});
} onError:^(NSError *error) {
[subject sendError:error];
}];
[engine enqueueOperation:anOp];
return subject;
}
そして最後に、[sendNext:]という件名がパーサーにあるときのアイデアをお伝えします。self.currentParsedCharacterDataは、整数値を持つNSStringです。
else if ([elementName isEqualToString:@"errcode"])
{
if ([self.action isEqualToString:@"API_Authenticate"]) {
[subject sendNext:[self.currentParsedCharacterData copy]];
[subject sendCompleted];
}
}
これは長いことは知っていますが、実際のコード例をいくつか挙げたいと思いました。