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.standardUserDefaultsNSFontManager.sharedFontManagerCloudDBManager.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."