Iphone 使用块简化代理方案-在这种情况下可能吗?

Iphone 使用块简化代理方案-在这种情况下可能吗?,iphone,ios,block,Iphone,Ios,Block,我已经阅读了很多关于使用块的积极内容,特别是它通过省略委托调用简化了代码。我已经找到了在动画结束时使用块而不是代理调用的例子——在那个里我理解了如何做到这一点 但我真的很想知道,在显示和取消ViewController时必须使用代理的繁琐方案是否也可以通过块来简化 显示和取消方案的标准推荐方式如下所示,在VC1中显示一个新的VC2,该VC2由VC1中的代表再次取消 VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil ]; v

我已经阅读了很多关于使用块的积极内容,特别是它通过省略委托调用简化了代码。我已经找到了在动画结束时使用块而不是代理调用的例子——在那个里我理解了如何做到这一点

但我真的很想知道,在显示和取消ViewController时必须使用代理的繁琐方案是否也可以通过块来简化

显示和取消方案的标准推荐方式如下所示,在VC1中显示一个新的VC2,该VC2由VC1中的代表再次取消

  VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil ];
  vc2.delegate = self; 
  [self presentModalViewController: vc2 animated: YES]; // or however you present your VC2
随着VC2返回vc1:

[self.delegate vc2HasFinished];
要使其工作,必须创建如下协议: 在
VC2Protocol.h
文件中

 @protocol VC2Protocol <NSObject>
    -(void)vc2HasFinished;
    @end
如果能够编写更简洁的代码,避免仅仅为此声明协议,那就太好了


谢谢

解除视图控制器的建议方法是解除视图控制器激活:完成。这是您要查找的块。

块可以用作VC2上的属性/IVAR,因此您可以拥有一个completionBlock,VC1可以将其设置为它想要的任何形式,VC2可以将其称为
completionBlock()一旦完成

基本上:

typedef void (^VC2CompletionBlock)(void);
@interface VC2 : UIViewController {
     VC2CompletionBlock completionBlock;
}

@property (nonatomic, copy) VC2CompletionBlock completionBlock;

@end
然后在VC2.m的某个地方你可以打电话

...
self.completionBlock();
...
Typedef’ing您的块允许您创建一个新的自定义块类型,可能是一个带有返回值或其他参数的块,然后可以通过VC2将其传递给块

typedef void (^VC2CompletionBlock)(BOOL success, NSData data);
我希望这有帮助,使用块是强大的,因为对象本身只需要知道块的基本结构(即它能够接受的参数),它不需要关于块或谁创建它的任何信息


不过要注意,块可能会导致奇怪的内存问题,因此请务必阅读有关它们的适当文档。

对于拒绝使用模式呈现的vc的情况,请注意vc可能会自行拒绝。因此,不是
[self.delegate vc2hassfinished]
你可以说
[self dismissModalViewControllerAnimated:YES completion:nil]在vc2中

但我同意你的观点,块很有用,委托很笨拙(而且更容易出错,尤其是弧前)。下面是如何在vc中替换委托回调。让我们设想一种情况,vc希望告诉它的代理一些事情,例如,它刚刚获取了一个图像

// vc2.h
@property (nonatomic, copy) void (^whenYouFetchAnImage)(UIImage *);
// note, no delegate property here

// vc2.m
// with your other synthesizes
@synthesize whenYouFetchAnImage=_whenYouFetchAnImage;

// when the image is fetched
self.whenYouFetchAnImage(theFetchedImage);
呈现vc没有设置委托,但它确实给了新vc一些代码,以便在获取图像时运行(在它自己的执行上下文中)

// presenting vc.m
VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];

// say this presenting vc has an image view that will show the image fetched
// by vc2.  (not totally plausible since this image view will probably be covered by vc2
// when the block is invoked)
vc2.whenYouFetchAnImage = ^(UIImage *image) { self.myImageView.image = image; };

您可以通过常规方式解决此问题,而无需在所有控制器中创建特定的块IVAR。您可以创建一个提供“完成时”块处理的类,然后从中继承,并且您的视图控制器都将具有“完成时”功能。您只需设置属性或提供“便利”方法即可

注意,第一部分的原始代码刚刚被破坏,所以我更改了它。-啊。真尴尬。无论如何,你明白了(我只建议你是一个讨厌联想的人)

或者,您可以将其作为UIViewController上的一个类别来执行,现在所有视图控制器都将获得此功能。您可以使用通知中心调用相应的块

@interface UIViewController (WhenDoneWithViewControllerBlock)
- (void)done:(BOOL)canceled;
@end

@implementation UIViewController (WhenDoneWithViewControllerBlock)
- (void)presentViewController:(UIViewController *)viewControllerToPresent
                     animated:(BOOL)flag
                   completion:(void (^)(void))completion
                     whenDone:(WhenDoneWithViewControllerBlock)doneBlock {
    if (doneBlock) {
        __block id observer = [[NSNotificationCenter defaultCenter]
                               addObserverForName:@"DoneWithViewControllerNotification"
                               object:viewControllerToPresent
                               queue:nil
                               usingBlock:^(NSNotification *note) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
            doneBlock(viewControllerToPresent, [[note.userInfo objectForKey:@"canceled"] boolValue]);
        }];
    }
    [self presentViewController:viewControllerToPresent
                       animated:flag
                     completion:completion];
}

- (void)done:(BOOL)canceled {
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"DoneWithViewControllerNotification"
                      object:self
                    userInfo:@{ @"canceled" : @(canceled) }];
}
@end
或者,如果您仍然想要一个类别,但想要一个iVar并绕过通知中心

// Using associated objects in a category
@interface UIViewController (WhenDoneWithViewControllerBlock)
@property (nonatomic, strong) WhenDoneWithViewControllerBlock whenDone;
- (void)done:(BOOL)canceled;
@end
@implementation UIViewController (WhenDoneWithViewControllerBlock)
char const kWhenDoneKey[1];
- (WhenDoneWithViewControllerBlock)whenDone {
    return objc_getAssociatedObject(self, kWhenDoneKey);
}
- (void)setWhenDone:(WhenDoneWithViewControllerBlock)whenDone {
    objc_setAssociatedObject(self, kWhenDoneKey, whenDone, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)presentViewController:(UIViewController *)viewControllerToPresent
                     animated:(BOOL)flag
                   completion:(void (^)(void))completion
                     whenDone:(WhenDoneWithViewControllerBlock)whenDone {
    viewControllerToPresent.whenDone = whenDone;
    [self presentViewController:viewControllerToPresent animated:flag completion:completion];
}

- (void)done:(BOOL)canceled {
    if (self.whenDone) {
        self.whenDone(self, canceled);
    }
}
@end
当然,这些只是例子,但希望你能理解

完成视图控制器后,它只调用

[self done:canceledOrSuccess];
将调用该块


使用最后一个类别是我最喜欢的,尽管关联对象在时间和内存方面都会有性能代价。您可以使用“iVar”方便地保存“whenDone”块(您可以显式设置它),您可以使用“便利”方法进行显示,每个视图控制器都会自动获得此功能,只需添加类别即可。

谢谢,但这不是我要问的,我喜欢简化取消VC的委托,如果可能的话!你可以在vc上调用这个方法。然后它会自动转发给您的代表或演示vc。看Bernd Rabe的回答,他是对的,解雇控制器最好通过上述方法完成,但是用块替换代表协议仍然是可行的策略。同意Henri和Bernd的观点。关于奇怪的内存问题,亨利所指的具体危险是保留周期。块副本保留它引用的所有对象(这就是它如何在调用方的执行上下文中运行)。如果这些对象中有任何一个是块的所有者,则这是一个循环。解决方法是在块中使用时声明这些对象的未恢复副本。这里有一个像样的so ref…也同意Henri的观点,typedefs清理了语法。哇-非常感谢您的详细回答-我将尝试您所说的最后一种方法-尽管我没有完全理解所有内容,但读过一次。。。
// Using associated objects in a category
@interface UIViewController (WhenDoneWithViewControllerBlock)
@property (nonatomic, strong) WhenDoneWithViewControllerBlock whenDone;
- (void)done:(BOOL)canceled;
@end
@implementation UIViewController (WhenDoneWithViewControllerBlock)
char const kWhenDoneKey[1];
- (WhenDoneWithViewControllerBlock)whenDone {
    return objc_getAssociatedObject(self, kWhenDoneKey);
}
- (void)setWhenDone:(WhenDoneWithViewControllerBlock)whenDone {
    objc_setAssociatedObject(self, kWhenDoneKey, whenDone, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)presentViewController:(UIViewController *)viewControllerToPresent
                     animated:(BOOL)flag
                   completion:(void (^)(void))completion
                     whenDone:(WhenDoneWithViewControllerBlock)whenDone {
    viewControllerToPresent.whenDone = whenDone;
    [self presentViewController:viewControllerToPresent animated:flag completion:completion];
}

- (void)done:(BOOL)canceled {
    if (self.whenDone) {
        self.whenDone(self, canceled);
    }
}
@end
[self done:canceledOrSuccess];