Obj-C,如何使用类别来提供将在委托方法中使用的方法?
Obj-C, How do I use a category to supply methods which I will use in delegate methods?
我想提供在我的委托方法中调用的多个视图控制器中使用的方法。
例如,我有一些 CloudKit 功能(我已将其添加到我自己的框架中,但我认为这并不重要),我想在其中提供一些崩溃日志记录。
以前我在我的每个视图控制器中都有一个 crashLog 函数,它工作正常,但是我有很多重复的代码。
因此我想用这些方法生成一个类别。
但是我很难让我的委托方法看到这些类别方法。
这是我的代码..
UIViewController+CloudKitDelegates.h
@interface UIViewController (CloudKitDelegates) <iCloudDBDelegate>
@property (weak,nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...;
@end
UIViewController+CloudKitDelegates.m
#import "UIViewController+CloudKitDelegates.h"
@implementation UIViewController (CloudKitDelegates)
@dynamic iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...
{
va_list args;
va_start(args, message);
NSLog(@"%@", [[NSString alloc] initWithFormat:message arguments:args]);
va_end(args);
}
@end
h 文件 - 我的调用视图控制器(例如我的视图控制器)
#import "UIViewController+CloudKitDelegates.h"
m 文件 - 委托方法
-(NSString*)getDBPath
{
[self.iCloudDBDelegate crashLog: @"testing"];
从这个调用中我得到一个错误...
'NSInvalidArgumentException', reason: '-[MyViewController crashLog:]:
unrecognized selector sent to instance
错误显示我调用的视图控制器 MyViewController 没有我的类别中的 crashLog 方法。
有什么地方出错了吗?
问题:多个 类 中的相同方法 crashLog:
,例如
@interface ViewController : UIViewController
@end
@implementation ViewController
- (void)someMethod {
[self crashLog:@"error"];
}
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
解决方案 A:将 crashLog:
移至公共超类(或超类 UIViewController
上的类别)
@interface CommonViewController : UIViewController
-(void)crashLog:(NSString *)message;
@end
@implementation CommonViewController
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[self crashLog:@"error"];
}
@end
解决方案 B:将 crashLog:
移动到委托和协议
@protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
@end
@interface DelegateClass : AnyClass <ICloudDBDelegate>
@end
@implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : UIViewController
@end
@implementation ViewController
@property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.iCloudDBDelegate = appDel.iCloudDBDelegate;
}
- (void)someMethod {
[self.iCloudDBDelegate crashLog:@"error"];
}
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate, AppDelProtocolDelegate, iCloudDBDelegate>
@property (strong, nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
@end
@implementation AppDelegate
- (id<iCloudDBDelegate>)iCloudDBDelegate {
if (!_iCloudDBDelegate) {
_iCloudDBDelegate = [[DelegateClass alloc] init];
}
return _iCloudDBDelegate;
}
@end
现在我们遇到了新问题:属性 iCloudDBDelegate
多个 类
解决方案 B + A:将 crashLog
移动到委托,将 iCloudDBDelegate
属性 移动到超类
@protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
@end
@interface DelegateClass : AnyClass <ICloudDBDelegate>
@end
@implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface CommonViewController : UIViewController
@property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
@end
@implementation CommonViewController
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[self.iCloudDBDelegate crashLog:@"error"];
}
@end
解决方案 C:
另一种方法是单例对象,如 NSUserDefaults.standardUserDefaults
或 NSFontManager.sharedFontManager
:CloudDBManager.sharedCloudDBManager
。不需要类别或协议,只需包含 CloudDBManager.h 并在任何地方使用 CloudDBManager.sharedCloudDBManager
。
@interface CloudDBManager : NSObject
@property(class, readonly, strong) CloudDBManager *sharedCloudDBManager;
-(void)crashLog:(NSString *)message;
@end
@implementation CloudDBManager
+ (CloudDBManager *)sharedCloudDBManager {
static CloudDBManager *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[CloudDBManager alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[CloudDBManager.sharedCloudDBManager crashLog:@"error"];
}
@end
(I've added this to my own framework, but I don't think thats important)
是的,这是典型的问题。您未能在 link 标志中包含 -ObjC
。
参见Building Objective-C static libraries with categories。这也适用于框架。
ObjC 不会为方法创建 linker 符号。它不能,它们直到运行时才被解决。因此,类别方法不会被 linker 视为 "missing",并且不会打扰 link 相关的编译单元。这是一项重要的优化,可以防止您仅仅因为在其中使用一个函数就 link 访问所有庞大的 C 库,但是 Objective-C 类别打破了 linker 的一些假设。编译器看到了定义(通过 header),但是 linker 并不关心,所以直到运行时才出现错误。
-ObjC
标志表示 "this C-looking compile unit is actually Objective-C; link all of it even if you don't think you need to."
我想提供在我的委托方法中调用的多个视图控制器中使用的方法。
例如,我有一些 CloudKit 功能(我已将其添加到我自己的框架中,但我认为这并不重要),我想在其中提供一些崩溃日志记录。 以前我在我的每个视图控制器中都有一个 crashLog 函数,它工作正常,但是我有很多重复的代码。
因此我想用这些方法生成一个类别。
但是我很难让我的委托方法看到这些类别方法。
这是我的代码..
UIViewController+CloudKitDelegates.h
@interface UIViewController (CloudKitDelegates) <iCloudDBDelegate>
@property (weak,nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...;
@end
UIViewController+CloudKitDelegates.m
#import "UIViewController+CloudKitDelegates.h"
@implementation UIViewController (CloudKitDelegates)
@dynamic iCloudDBDelegate;
-(void)crashLog:(NSString*)message, ...
{
va_list args;
va_start(args, message);
NSLog(@"%@", [[NSString alloc] initWithFormat:message arguments:args]);
va_end(args);
}
@end
h 文件 - 我的调用视图控制器(例如我的视图控制器)
#import "UIViewController+CloudKitDelegates.h"
m 文件 - 委托方法
-(NSString*)getDBPath
{
[self.iCloudDBDelegate crashLog: @"testing"];
从这个调用中我得到一个错误...
'NSInvalidArgumentException', reason: '-[MyViewController crashLog:]:
unrecognized selector sent to instance
错误显示我调用的视图控制器 MyViewController 没有我的类别中的 crashLog 方法。
有什么地方出错了吗?
问题:多个 类 中的相同方法 crashLog:
,例如
@interface ViewController : UIViewController
@end
@implementation ViewController
- (void)someMethod {
[self crashLog:@"error"];
}
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
解决方案 A:将 crashLog:
移至公共超类(或超类 UIViewController
上的类别)
@interface CommonViewController : UIViewController
-(void)crashLog:(NSString *)message;
@end
@implementation CommonViewController
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[self crashLog:@"error"];
}
@end
解决方案 B:将 crashLog:
移动到委托和协议
@protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
@end
@interface DelegateClass : AnyClass <ICloudDBDelegate>
@end
@implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : UIViewController
@end
@implementation ViewController
@property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.iCloudDBDelegate = appDel.iCloudDBDelegate;
}
- (void)someMethod {
[self.iCloudDBDelegate crashLog:@"error"];
}
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate, AppDelProtocolDelegate, iCloudDBDelegate>
@property (strong, nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
@end
@implementation AppDelegate
- (id<iCloudDBDelegate>)iCloudDBDelegate {
if (!_iCloudDBDelegate) {
_iCloudDBDelegate = [[DelegateClass alloc] init];
}
return _iCloudDBDelegate;
}
@end
现在我们遇到了新问题:属性 iCloudDBDelegate
多个 类
解决方案 B + A:将 crashLog
移动到委托,将 iCloudDBDelegate
属性 移动到超类
@protocol ICloudDBDelegate
-(void)crashLog:(NSString *)message;
@end
@interface DelegateClass : AnyClass <ICloudDBDelegate>
@end
@implementation DelegateClass
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface CommonViewController : UIViewController
@property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
@end
@implementation CommonViewController
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[self.iCloudDBDelegate crashLog:@"error"];
}
@end
解决方案 C:
另一种方法是单例对象,如 NSUserDefaults.standardUserDefaults
或 NSFontManager.sharedFontManager
:CloudDBManager.sharedCloudDBManager
。不需要类别或协议,只需包含 CloudDBManager.h 并在任何地方使用 CloudDBManager.sharedCloudDBManager
。
@interface CloudDBManager : NSObject
@property(class, readonly, strong) CloudDBManager *sharedCloudDBManager;
-(void)crashLog:(NSString *)message;
@end
@implementation CloudDBManager
+ (CloudDBManager *)sharedCloudDBManager {
static CloudDBManager *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[CloudDBManager alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
-(void)crashLog:(NSString *)message {
NSLog(@"%@", message);
}
@end
@interface ViewController : CommonViewController
@end
@implementation ViewController
- (void)someMethod {
[CloudDBManager.sharedCloudDBManager crashLog:@"error"];
}
@end
(I've added this to my own framework, but I don't think thats important)
是的,这是典型的问题。您未能在 link 标志中包含 -ObjC
。
参见Building Objective-C static libraries with categories。这也适用于框架。
ObjC 不会为方法创建 linker 符号。它不能,它们直到运行时才被解决。因此,类别方法不会被 linker 视为 "missing",并且不会打扰 link 相关的编译单元。这是一项重要的优化,可以防止您仅仅因为在其中使用一个函数就 link 访问所有庞大的 C 库,但是 Objective-C 类别打破了 linker 的一些假设。编译器看到了定义(通过 header),但是 linker 并不关心,所以直到运行时才出现错误。
-ObjC
标志表示 "this C-looking compile unit is actually Objective-C; link all of it even if you don't think you need to."