我知道代理(delegates)的工作原理,也知道如何使用它们。
但是,我该如何创建它们?
我知道代理(delegates)的工作原理,也知道如何使用它们。
但是,我该如何创建它们?
Objective-C代理是分配给另一个对象的delegate
属性的对象。要创建一个代理,您需要定义一个实现您感兴趣的代理方法的类,并将该类标记为实现代理协议。
例如,假设您有一个UIWebView
。如果您想实现其代理的webViewDidStartLoad:
方法,您可以创建一个如下所示的类:
@interface MyClass<UIWebViewDelegate>
// ...
@end
@implementation MyClass
- (void)webViewDidStartLoad:(UIWebView *)webView {
// ...
}
@end
然后您可以创建一个 MyClass 的实例并将其分配为 web view 的委托:
MyClass *instanceOfMyClass = [[MyClass alloc] init];
myWebView.delegate = instanceOfMyClass;
UIWebView
方面,它可能有类似以下代码来查看委托是否响应webViewDidStartLoad:
消息,使用respondsToSelector:
并在适当时发送它。if([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
委托属性本身通常被声明为weak
(在ARC中)或assign
(在ARC之前),以避免保留循环,因为对象的委托通常持有对该对象的强引用。(例如,视图控制器通常是其所包含视图的委托。)
要定义自己的委托,您需要在某个地方声明它们的方法,如苹果关于协议的文档中所讨论的那样。通常您会声明一个正式协议。该声明,摘自UIWebView.h,看起来像这样:
@protocol UIWebViewDelegate <NSObject>
@optional
- (void)webViewDidStartLoad:(UIWebView *)webView;
// ... other methods here
@end
这类似于接口或抽象基类,因为它为您的委托创建了一个特殊类型,在这种情况下是UIWebViewDelegate
。委托实现者必须采用此协议:
@interface MyClass <UIWebViewDelegate>
// ...
@end
然后在协议中实现方法。对于在协议中声明为@optional
(像大多数委托方法一样)的方法,在调用特定方法之前,您需要使用-respondsToSelector:
进行检查。
委托方法通常以委托类名称开头,并将委托对象作为第一个参数。它们通常使用will-、should-或did-形式。例如,webViewDidStartLoad:
(第一个参数是Web视图),而不是loadStarted
(不带参数)。
我们可以在设置委托时缓存信息,而不是每次想要发送消息时都检查委托是否响应选择器。其中一种非常干净的方法是使用位域,如下所示:
@protocol SomethingDelegate <NSObject>
@optional
- (void)something:(id)something didFinishLoadingItem:(id)item;
- (void)something:(id)something didFailWithError:(NSError *)error;
@end
@interface Something : NSObject
@property (nonatomic, weak) id <SomethingDelegate> delegate;
@end
@implementation Something {
struct {
unsigned int didFinishLoadingItem:1;
unsigned int didFailWithError:1;
} delegateRespondsTo;
}
@synthesize delegate;
- (void)setDelegate:(id <SomethingDelegate>)aDelegate {
if (delegate != aDelegate) {
delegate = aDelegate;
delegateRespondsTo.didFinishLoadingItem = [delegate respondsToSelector:@selector(something:didFinishLoadingItem:)];
delegateRespondsTo.didFailWithError = [delegate respondsToSelector:@selector(something:didFailWithError:)];
}
}
@end
然后,在代码主体中,我们可以通过访问delegateRespondsTo
结构体来检查我们的代理处理消息的情况,而不是一遍又一遍地发送-respondsToSelector:
。
在协议出现之前,通常使用NSObject
上的category声明代理可以实现的方法。例如,CALayer
仍在使用此方法:
@interface NSObject(CALayerDelegate)
- (void)displayLayer:(CALayer *)layer;
// ... other methods here
@end
displayLayer:
方法。之后,您可以使用与上述描述相同的-respondsToSelector:
方法来调用此方法。代理实现此方法并分配delegate
属性,就这样(无需声明您符合协议)。该方法在苹果库中很常见,但新代码应使用上面更现代的协议方法,因为该方法会污染NSObject
(使自动完成不太有用),并且使编译器难以警告您有关拼写错误和类似错误。unsigned int
类型转换为 BOOL
,因为 delegate respondsToSelector
的返回值是 BOOL
类型。 - Roland已批准的答案很好,但如果您想要一分钟的答案,请尝试以下内容:
我的MyClass.h文件应该像这样(使用注释添加委托行!)
#import <BlaClass/BlaClass.h>
@class MyClass; //define class, so protocol can see MyClass
@protocol MyClassDelegate <NSObject> //define delegate protocol
- (void) myClassDelegateMethod: (MyClass *) sender; //define delegate method to be implemented within another class
@end //end protocol
@interface MyClass : NSObject {
}
@property (nonatomic, weak) id <MyClassDelegate> delegate; //define MyClassDelegate as delegate
@end
我的MyClass.m文件应该长成这样
#import "MyClass.h"
@implementation MyClass
@synthesize delegate; //synthesise MyClassDelegate delegate
- (void) myMethodToDoStuff {
[self.delegate myClassDelegateMethod:self]; //this will call the method implemented in your other class
}
@end
要在另一个类中使用您的委托(在本例中为名为MyVC的UIViewController),请在MyVC.h文件中进行如下设置:
#import "MyClass.h"
@interface MyVC:UIViewController <MyClassDelegate> { //make it a delegate for MyClassDelegate
}
MyVC.m:
myClass.delegate = self; //set its delegate to self somewhere
实现代理方法
- (void) myClassDelegateMethod: (MyClass *) sender {
NSLog(@"Delegates are great!");
}
myClass
被实例化在哪里? - Lane Rettig当使用正式协议方法创建委托支持时,我发现您可以通过添加以下内容来确保正确的类型检查(虽然是运行时而不是编译时):
if (![delegate conformsToProtocol:@protocol(MyDelegate)]) {
[NSException raise:@"MyDelegate Exception"
format:@"Parameter does not conform to MyDelegate protocol at line %d", (int)__LINE__];
}
在你的委托访问器(setDelegate)代码中。这可以帮助最小化错误。
也许这更符合您缺少的内容:
如果您是从C++的角度来看,委托需要一些时间来适应-但基本上“它们只是工作的”。
工作方式是,您将编写的某个对象设置为NSWindow的代理对象,但是您的对象仅具有一种或少数几种可能的代理方法的实现(方法)。因此发生了某些事情,NSWindow
想要调用您的对象-它只是使用Objective-c的respondsToSelector
方法来确定您的对象是否希望调用该方法,然后调用它。这就是objective-c的工作方式-按需查找方法。
使用自己的对象完全没有什么特别之处,非常简单,例如,您可以拥有27个对象的NSArray
,所有这些对象都是不同类型的对象,仅有18个对象具有-(void)setToBue;
方法。其他9个没有。因此,要在其中的18个对象上调用setToBlue
,类似于以下内容:
for (id anObject in myArray)
{
if ([anObject respondsToSelector:@selector(@"setToBlue")])
[anObject setToBlue];
}
另一个关于委托的事情是,它们不会被保留,因此您总是要在您的MyClass dealloc
方法中将委托设置为nil
。
我创建了两个ViewController(用于从一个ViewController向另一个ViewController发送数据)
作为苹果推荐的良好实践,代理(根据定义是协议)最好符合NSObject
协议。
@protocol MyDelegate <NSObject>
...
@end
使用@optional
注解可以创建在委托中可选的方法(即不一定需要实现的方法)。示例如下:
& to create optional methods within your delegate (i.e. methods which need not necessarily be implemented), you can use the @optional
annotation like this :
@protocol MyDelegate <NSObject>
...
...
// Declaration for Methods that 'must' be implemented'
...
...
@optional
...
// Declaration for Methods that 'need not necessarily' be implemented by the class conforming to your delegate
...
@end
因此,在使用您指定为可选的方法时,您需要在您的类中使用 respondsToSelector
检查视图(符合您的委托)是否实际实现了您的可选方法。
代理是一个为另一个类做一些工作的类。阅读以下代码,这是一个有点傻但希望能启发您的Playground示例,展示了在Swift中如何实现此功能。
// A protocol is just a list of methods (and/or properties) that must
// be used by any class that adopts the protocol.
protocol OlderSiblingDelegate: class {
// This protocol only defines one required method
func getYourNiceOlderSiblingAGlassOfWater() -> String
}
class BossyBigBrother {
// The delegate is the BossyBigBrother's slave. This position can
// be assigned later to whoever is available (and conforms to the
// protocol).
weak var delegate: OlderSiblingDelegate?
func tellSomebodyToGetMeSomeWater() -> String? {
// The delegate is optional because there might not be anyone
// nearby to boss around.
return delegate?.getYourNiceOlderSiblingAGlassOfWater()
}
}
// PoorLittleSister conforms to the OlderSiblingDelegate protocol
class PoorLittleSister: OlderSiblingDelegate {
// This method is repquired by the protocol, but the protocol said
// nothing about how it needs to be implemented.
func getYourNiceOlderSiblingAGlassOfWater() -> String {
return "Go get it yourself!"
}
}
// initialize the classes
let bigBro = BossyBigBrother()
let lilSis = PoorLittleSister()
// Set the delegate
// bigBro could boss around anyone who conforms to the
// OlderSiblingDelegate protocol, but since lilSis is here,
// she is the unlucky choice.
bigBro.delegate = lilSis
// Because the delegate is set, there is a class to do bigBro's work for him.
// bigBro tells lilSis to get him some water.
if let replyFromLilSis = bigBro.tellSomebodyToGetMeSomeWater() {
print(replyFromLilSis) // "Go get it yourself!"
}
在实际应用中,代理通常用于以下情况:
这些类之间事先不需要了解彼此的任何信息,只需代理类符合所需协议即可。
我强烈推荐阅读以下两篇文章。它们帮助我更好地理解代理,甚至比文档还要好。
我认为一旦您理解委托,所有这些答案都有很多道理。就个人而言,我来自C/C++之地,以前使用的是如Fortran等过程化语言,因此在C++范例中寻找类似的模拟品的时间不超过2分钟。
如果要向C++/Java程序员解释委托,我会说:
什么是委托? 这些是指向另一个类内部的类的静态指针。一旦分配了指针,您可以调用该类中的函数/方法。因此,您的类的某些函数被“委托”(在C++世界中,由类对象指针指向)到另一个类。
什么是协议? 概念上,它的目的与您分配为委托类的类的头文件类似。协议是明确定义需要在将指针设置为类委托的类中实现的方法的方式。
如何在C ++中做类似的事情? 如果您尝试在C ++中执行此操作,则需要在类定义中定义对类(对象)的指针,然后将它们连接到提供其他功能作为基类的委托的其他类。但是,此连接需要在代码中进行维护,会很笨拙且容易出错。Objective-C仅假定程序员不善于维护此纪律,并提供编译器限制以强制执行清洁实现。
好的,这并不是对问题的真正答案,但如果您正在查找如何制作自己的代理,那么可能有一些更简单的答案适合您。
我很少实现我的代理,因为我很少需要。我可以为委托对象拥有仅一个代理。因此,如果您希望将委托用于单向通信/传递数据,则使用通知会更好。
NSNotification 可以将对象传递给多个接收者,而且非常容易使用。 它的工作原理如下:
MyClass.m 文件应该像这样:
#import "MyClass.h"
@implementation MyClass
- (void) myMethodToDoStuff {
//this will post a notification with myClassData (NSArray in this case) in its userInfo dict and self as an object
[[NSNotificationCenter defaultCenter] postNotificationName:@"myClassUpdatedData"
object:self
userInfo:[NSDictionary dictionaryWithObject:selectedLocation[@"myClassData"] forKey:@"myClassData"]];
}
@end
要在其他类中使用您的通知: 将该类添加为观察者:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(otherClassUpdatedItsData:) name:@"myClassUpdatedData" object:nil];
实现选择器:
- (void) otherClassUpdatedItsData:(NSNotification *)note {
NSLog(@"*** Other class updated its data ***");
MyClass *otherClass = [note object]; //the object itself, you can call back any selector if you want
NSArray *otherClassData = [note userInfo][@"myClassData"]; //get myClass data object and do whatever you want with it
}
不要忘记在不需要观察时将自己作为观察者的类删除。
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@class myClass;
@protocol myClassDelegate <NSObject>
-(void)myClass:(MyClass*)myObject requiredEventHandlerWithParameter:(ParamType*)param;
@optional
-(void)myClass:(MyClass*)myObject optionalEventHandlerWithParameter:(ParamType*)param;
@end
@interface MyClass : NSObject
@property(nonatomic,weak)id< MyClassDelegate> delegate;
@end
MyClass
头文件(或单独的头文件)中声明一个协议,并声明必须/可选的事件处理程序,你的代理必须/应该实现。然后在MyClass
中声明一个类型为id<MyClassDelegate>
的属性,这意味着任何符合协议MyClassDelegate
的Objective-C类。你会注意到代理属性被声明为弱引用,这非常重要,可以防止保留循环(通常代理会保留MyClass
实例,所以如果将代理声明为保留,则它们两个将互相保留,而且永远不会释放)。MyClass
实例作为参数传递给代理,这是最佳实践,以防代理想要调用MyClass
实例上的一些方法,并且在代理声明自己为多个MyClass
实例的MyClassDelegate
时也有帮助,例如当您在ViewController
中有多个UITableView
实例并向所有实例声明自己为UITableViewDelegate
时。MyClass
内部,你可以按照以下方式使用声明的事件通知代理:if([_delegate respondsToSelector:@selector(myClass: requiredEventHandlerWithParameter:)])
{
[_delegate myClass:self requiredEventHandlerWithParameter:(ParamType*)param];
}
在调用委托方法之前,您首先要检查委托是否响应该协议方法,以防委托没有实现它并且应用程序将崩溃(即使该协议方法是必需的)。