Obj-C,如何使用类别来提供将在委托方法中使用的方法?

时间:2018-12-06 22:57:18

标签: objective-c cocoa-touch delegates objective-c-category

我想提供在我的委托方法中调用的多个视图控制器中使用的方法。

例如,我有一些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方法。

有什么想法我要去哪里吗?

2 个答案:

答案 0 :(得分:1)

问题:例如,多个类中的方法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

答案 1 :(得分:1)

  

(我已将其添加到自己的框架中,但我认为这并不重要)

是的,这是典型的问题。您没有在链接标志中包含-ObjC

请参见Building Objective-C static libraries with categories。这也适用于框架。

ObjC不会为方法创建链接器符号。不能,直到运行时才解决。因此,链接器不会将类别方法视为“丢失”,并且不会麻烦链接相关的编译单元。这是一项重要的优化,它使您无法链接所有大型C库,只是因为您在其中使用了一个函数,但是Objective-C类别破坏了链接程序的某些假设。编译器(通过标头)看到了定义,但是链接器不在乎,因此直到运行时才出现错误。

-ObjC标志说:“这个看起来像C的编译单元实际上是Objective-C;即使您不需要,也可以链接所有这些单元。”