如何在Objective-C中使用块进行结构化编程
当使用返回块的方法时,它们会非常方便。 然而,当你不得不把它们串在一起时,很快就会变得一团糟 例如,您必须连续调用4个URL:如何在Objective-C中使用块进行结构化编程,objective-c,lambda,objective-c-blocks,continuations,structured-programming,Objective C,Lambda,Objective C Blocks,Continuations,Structured Programming,当使用返回块的方法时,它们会非常方便。 然而,当你不得不把它们串在一起时,很快就会变得一团糟 例如,您必须连续调用4个URL: [remoteAPIWithURL:url1 success:^(int status){ [remoteAPIWithURL:url2 success:^(int status){ [remoteAPIWithURL:url3 success:^(int status){ [remoteAPIWithURL:url2 s
[remoteAPIWithURL:url1 success:^(int status){
[remoteAPIWithURL:url2 success:^(int status){
[remoteAPIWithURL:url3 success:^(int status){
[remoteAPIWithURL:url2 success:^(int status){
//succes!!!
}];
}];
}];
}];
因此,对于每一次迭代,我都会更深一层,甚至不会处理嵌套块中的错误
当存在实际循环时,情况会变得更糟。例如,假设我想上传一个100块的文件:
- (void) continueUploadWithBlockNr:(int)blockNr
{
if(blocknr>=100)
{
//success!!!
}
[remoteAPIUploadFile:file withBlockNr:blockNr success:^(int status)
{
[self continueUploadWithBlockNr:blockNr];
}];
}
这感觉很不直观,而且很快就变得不可读
在.Net中,他们使用async和await关键字解决了所有这些问题,基本上将这些延续展开为一个看似同步的流
目标C中的最佳实践是什么?减少嵌套的一种方法是定义返回单个块的方法。为了方便Objective C编译器通过闭包“自动”完成数据共享,您需要定义一个单独的类来保存共享状态 下面是如何实现这一点的大致示意图:
typedef void (^WithStatus)(int);
@interface AsyncHandler : NSObject {
NSString *_sharedString;
NSURL *_innerUrl;
NSURL *_middleUrl;
WithStatus _innermostBlock;
}
+(void)handleRequest:(WithStatus)innermostBlock
outerUrl:(NSURL*)outerUrl
middleUrl:(NSURL*)middleUrl
innerUrl:(NSURL*)innerUrl;
-(WithStatus)outerBlock;
-(WithStatus)middleBlock;
@end
@implementation AsyncHandler
+(void)handleRequest:(WithStatus)innermostBlock
outerUrl:(NSURL*)outerUrl
middleUrl:(NSURL*)middleUrl
innerUrl:(NSURL*)innerUrl {
AsyncHandler *h = [[AsyncHandler alloc] init];
h->_innermostBlock = innermostBlock;
h->_innerUrl = innerUrl;
h->_middleUrl = middleUrl;
[remoteAPIWithURL:outerUrl success:[self outerBlock]];
}
-(WithStatus)outerBlock {
return ^(int success) {
_sharedString = [NSString stringWithFormat:@"Outer: %i", success];
[remoteAPIWithURL:_middleUrl success:[self middleBlock]];
};
}
-(WithStatus)middleBlock {
return ^(int success) {
NSLog("Shared string: %@", _sharedString);
[remoteAPIWithURL:_innerUrl success:_innermostBlock];
};
}
@end
注:所有这些均假定为圆弧;如果编译时不使用它,则需要在返回块的方法中使用。您还需要在下面的调用代码中进行复制
现在可以在不嵌套的情况下重新编写原始函数,如下所示:
[AsyncHandler
handleRequest:^(int status){
//succes!!!
}
outerUrl:[NSURL @"http://my.first.url.com"]
middleUrl:[NSURL @"http://my.second.url.com"]
innerUrl:[NSURL @"http://my.third.url.com"]
];
当我自己研究这个问题时,我偶然发现了Objective-C的一个被动扩展端口。被动扩展就像拥有查询一组事件或异步操作的能力。我知道它在.Net和JavaScript下有了很大的发展,现在显然也有了Objective-C的端口
语法看起来很复杂。我想知道是否有iPhone开发的实际经验,以及它是否确实优雅地解决了这个问题。迭代算法:
- 创建一个
变量(\u块
),以跟踪当前URL(在inturlnum
中)NSArray
- 让onUrlComplete块触发下一个请求,直到所有URL都已加载
- 启动第一个请求
- 加载所有URL后,跳“//success!”舞
- 迭代算法简单明了——如果你喜欢玩
变量和作用域的游戏的话\uu block
- 或者,递归算法不需要
变量,并且与递归算法一样相当简单\u块
- 递归实现比迭代实现(如已实现)更具可重用性
- 递归算法可能会泄漏(它需要引用
),但有几种方法可以解决此问题:将其作为函数,使用self
\uu弱id weakSelf=self代码>,等等
- 迭代实现可以很容易地扩展以检查
,代价是状态的值
块变得更加复杂onurlpomlete
- 递归实现的扩展可能不那么直接——主要是因为它是可重用的。当状态为某某时,是否要取消加载更多URL?然后向下传递一个状态检查/错误处理块,该块接受
并返回int status
(例如BOOL
继续,YES
取消)。或者修改NO
以接受onSuccess
和int status
——但您需要在NSArray*remainingUrls
块实现中调用onSuccess
loadurlsasassynchronouslyrecursive…
- 您在评论中说,“异步方法提供简单的异步性,而不使用显式线程。”但您的抱怨似乎是您试图用异步方法做一些事情,这并不容易。你看到这里的矛盾了吗
当您使用基于回调的设计时,您牺牲了直接使用该语言的内置结构表达控制流的能力
因此,我建议您停止使用基于回调的设计。GrandCentralDispatch(GCD)使“在后台”执行工作变得很容易(又是这个词!),然后调用主线程更新用户界面。因此,如果您有API的同步版本,只需在后台队列中使用它:
- (void)interactWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// This block runs on a background queue, so it doesn't block the main thread.
// But it can't touch the user interface.
for (NSURL *url in @[url1, url2, url3, url4]) {
int status = [remoteAPI syncRequestWithURL:url];
if (status != 0) {
dispatch_async(dispatch_get_main_queue(), ^{
// This block runs on the main thread, so it can update the
// user interface.
[self remoteRequestFailedWithURL:url status:status];
});
return;
}
}
});
}
更新
我会先回应你的第三个评论,然后再回应你的第二个评论
第三条评论
你的第三点评论:
最后但并非最不重要的一点是,使用单独的线程来包装调用的同步版本的解决方案比使用异步替代方案的成本更高。一个线程是一个昂贵的资源,当它被阻塞时,你基本上丢失了一个线程。异步调用(至少是OS库中的调用)通常以更高效的方式处理。(例如,如果您同时请求10个URL,则很可能不会启动10个线程(或将它们放入线程池中)) 是的,使用线程比只使用异步调用更昂贵。那又怎么样?问题是它是否太贵了。Objective-C消息在当前iOS硬件上的某些场景中过于昂贵(例如,实时人脸检测或语音识别算法的内部循环),但我对大部分时间使用它们毫不犹豫 线程是否是“昂贵的资源”实际上取决于上下文。让我们考虑一下你的例子:“例如,如果你同时请求10个URL,那么它就不会旋转10个线程(或者把它们放在线程池中)”。让我们看看
NSURL *url = [NSURL URLWithString:@"http://1.1.1.1/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
for (int i = 0; i < 10; ++i) {
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(@"response=%@ error=%@", response, error);
}];
}
我将以与前面相同的方式启动控制流,将其从主线程移动到conc
- (void)interactWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// This block runs on a background queue, so it doesn't block the main thread.
// But it can't touch the user interface.
for (NSURL *url in @[url1, url2, url3, url4]) {
int status = [remoteAPI syncRequestWithURL:url];
if (status != 0) {
dispatch_async(dispatch_get_main_queue(), ^{
// This block runs on the main thread, so it can update the
// user interface.
[self remoteRequestFailedWithURL:url status:status];
});
return;
}
}
});
}
#define AsyncToMain(Block) dispatch_async(dispatch_get_main_queue(), Block)
- (void)uploadFile:(NSFileHandle *)fileHandle withRemoteAPI:(id<RemoteAPI>)remoteAPI {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int status = [remoteAPI syncRequestWithURL:url1];
if (status != 0) {
AsyncToMain(^{ [self remoteRequestFailedWithURL:url1 status:status]; });
return;
}
status = [remoteAPI syncRequestWithURL:url2];
if (status != 0) {
AsyncToMain(^{ [self remoteRequestFailedWithURL:url2 status:status]; });
return;
}
while (1) {
// Manage an autorelease pool to avoid accumulating all of the
// 100k chunks in memory simultaneously.
@autoreleasepool {
NSData *chunk = [fileHandle readDataOfLength:100 * 1024];
if (chunk.length == 0)
break;
status = [remoteAPI syncUploadChunk:chunk];
if (status != 0) {
AsyncToMain(^{ [self sendChunkFailedWithStatus:status]; });
return;
}
}
}
status = [remoteAPI syncRequestWithURL:url4];
if (status != 0) {
AsyncToMain(^{ [self remoteRequestFailedWithURL:url4 status:status]; });
return;
}
AsyncToMain(^{ [self uploadFileSucceeded]; });
});
}
- (int)syncRequestWithRemoteAPI:(id<RemoteAPI>)remoteAPI url:(NSURL *)url {
__block int outerStatus;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[remoteAPI asyncRequestWithURL:url completion:^(int status) {
outerStatus = status;
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_release(sem);
return outerStatus;
}
NSURL *url = [NSURL URLWithString:@"http://1.1.1.1/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
for (int i = 0; i < 10; ++i) {
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(@"response=%@ error=%@", response, error);
}];
}
typedef void (^RemoteAPICompletionBlock)(int status);
- (void)complexFlowWithRemoteAPI:(id<RemoteAPI>)remoteAPI {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int status = statusOfFirstRequestToSucceed(@[
^(RemoteAPICompletionBlock completion) {
[remoteAPI requestWithCompletion:completion];
},
^(RemoteAPICompletionBlock completion) {
[remoteAPI anotherRequestWithCompletion:completion];
},
^(RemoteAPICompletionBlock completion) {
[remoteAPI thirdRequestWithCompletion:completion];
}
]);
if (status != 0) {
AsyncToMain(^{ [self complexFlowFailedOnFirstRoundWithStatus:status]; });
return;
}
status = statusOfFirstRequestToFail(@[
^(RemoteAPICompletionBlock completion) {
[remoteAPI requestWithCompletion:completion];
},
^(RemoteAPICompletionBlock completion) {
[remoteAPI anotherRequestWithCompletion:completion];
},
^(RemoteAPICompletionBlock completion) {
[remoteAPI thirdRequestWithCompletion:completion];
}
]);
if (status != 0) {
AsyncToMain(^{ [self complexFlowFailedOnSecondRoundWithStatus:status]; });
return;
}
[self complexFlowSucceeded];
});
}
static int statusOfFirstRequestToSucceed(NSArray *requestBlocks) {
return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
return status == 0;
});
}
static int statusOfFirstRequestToFail(NSArray *requestBlocks) {
return statusOfFirstRequestWithStatusPassingTest(requestBlocks, ^BOOL (int status) {
return status != 0;
});
}
static int statusOfFirstRequestWithStatusPassingTest(NSArray *requestBlocks,
BOOL (^statusTest)(int status))
{
dispatch_queue_t completionQueue = dispatch_queue_create("remote API completion", 0);
dispatch_semaphore_t enoughJobsCompleteSemaphore = dispatch_semaphore_create(0);
__block int jobsLeft = requestBlocks.count;
__block int outerStatus = 0;
RemoteAPICompletionBlock completionBlock = ^(int status) {
dispatch_sync(completionQueue, ^{
if (jobsLeft == 0) {
// The outer function has already returned.
return;
}
--jobsLeft;
outerStatus = status;
if (statusTest(status)) {
// We have a winner. Prevent other jobs from overwriting my status.
jobsLeft = 0;
}
if (jobsLeft == 0) {
dispatch_semaphore_signal(enoughJobsCompleteSemaphore);
}
dispatch_release(completionQueue);
dispatch_release(enoughJobsCompleteSemaphore);
});
};
for (void (^requestBlock)(RemoteAPICompletionBlock) in requestBlocks) {
dispatch_retain(completionQueue); // balanced in completionBlock
dispatch_retain(enoughJobsCompleteSemaphore); // balanced in completionBlock
requestBlock(completionBlock);
}
dispatch_semaphore_wait(enoughJobsCompleteSemaphore, DISPATCH_TIME_FOREVER);
dispatch_release(completionQueue);
dispatch_release(enoughJobsCompleteSemaphore);
return outerStatus;
}
[remoteAPIWithURL:url1 success:^(int status){
[remoteAPIWithURL:url2 success:^(int status){
[remoteAPIWithURL:url3 success:^(int status){
[remoteAPIWithURL:url2 success:^(int status){
//succes!!!
}];
}];
}];
}];
// __block declaration of the block makes it possible to call the block from within itself
__block void (^urlFetchBlock)();
// Neatly aggregate all the urls you wish to fetch
NSArray *urlArray = @[
[NSURL URLWithString:@"http://www.google.com"],
[NSURL URLWithString:@"http://www.stackoverflow.com"],
[NSURL URLWithString:@"http://www.bing.com"],
[NSURL URLWithString:@"http://www.apple.com"]
];
__block int urlIndex = 0;
// the 'recursive' block
urlFetchBlock = [^void () {
if (urlIndex < (int)[urlArray count]){
[self remoteAPIWithURL:[urlArray objectAtIndex:index]
success:^(int theStatus){
urlIndex++;
urlFetchBlock();
}
failure:^(){
// handle error.
}];
}
} copy];
// initiate the url requests
urlFetchBlock();
typedef int(^SumUpTill)(int);
SumUpTill sum = ^(int max){
int i = 0;
int result = 0;
while (i < max) {
result += i++;
}
return result;
};
dispatch_queue_t queue = dispatch_queue_create("com.dispatch.barrier.async", DISPATCH_QUEUE_CONCURRENT);
NSArray *urlArray = @[ [NSURL URLWithString:@"http://www.google.com"],
@"Test",
[sum copy],
[NSURL URLWithString:@"http://www.apple.com"]
];
[urlArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dispatch_barrier_async(queue, ^{
if ([obj isKindOfClass:[NSURL class]]) {
NSURLRequest *request = [NSURLRequest requestWithURL:obj];
NSURLResponse *response = nil;
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(@"index = %d, response=%@ error=%@", idx, response, error);
}
else if ([obj isKindOfClass:[NSString class]]) {
NSLog(@"index = %d, string %@", idx, obj);
}
else {
NSInteger result = ((SumUpTill)obj)(1000000);
NSLog(@"index = %d, result = %d", idx, result);
}
});
}];
Serialization
– continueActivityUsingBlock:
– continueAsynchronousWorkOnMainThreadUsingBlock:
– performActivityWithSynchronousWaiting:usingBlock:
– performAsynchronousFileAccessUsingBlock:
– performSynchronousFileAccessUsingBlock: