Objective-C 和 CoreFoundation:"unrecognized selector sent" 在 i386 上崩溃

Objective-C and CoreFoundation: "unrecognized selector sent" crash on i386

我正尝试在 Mac OS X 10.11.6 (El Capitan) 向通知中心发送通知。现在我不是 Objective-C 专家,所以我使用 this 示例作为基础。

完整清单:

//
//  main.m
//  badonkas
//
//  Created by Yoshiki Vázquez Baeza on 21/07/13.
//  Copyright (c) 2013 Yoshiki Vázquez Baeza. All rights reserved.
//
//  Based on the source code as written by Norio Nomura for the project
//  usernotification https://github.com/norio-nomura/usernotification
//  gcc -framework Foundation main.m
//

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

NSString *fakeBundleIdentifier = nil;

@implementation NSBundle(swizle)

// Overriding bundleIdentifier works, but overriding NSUserNotificationAlertStyle does not work.

- (NSString *)__bundleIdentifier{
    if (self == [NSBundle mainBundle]) {
        return fakeBundleIdentifier ? fakeBundleIdentifier : @"com.apple.terminal";
    } else {
        return [self __bundleIdentifier];
    }
}

@end

BOOL installNSBundleHook()
{
    Class class = objc_getClass("NSBundle");
    if (class) {
        method_exchangeImplementations(class_getInstanceMethod(class, @selector(bundleIdentifier)),
                                       class_getInstanceMethod(class, @selector(__bundleIdentifier)));
        return YES;
    }
    return NO;
}


#pragma mark - NotificationCenterDelegate

@interface NotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate>

@property (nonatomic, assign) BOOL keepRunning;

@end

@implementation NotificationCenterDelegate

- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
    self.keepRunning = NO;
}

@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        NSString *message = [defaults stringForKey:@"-message"] ?: @"";
        NSString *title = [defaults stringForKey:@"-title"] ?: @"Notification";
        NSString *subtitle = [defaults stringForKey:@"-subtitle"] ?: @"";

        if (installNSBundleHook()) {

            fakeBundleIdentifier = [defaults stringForKey:@"identifier"];

            NSUserNotificationCenter *nc = [NSUserNotificationCenter defaultUserNotificationCenter];
            NotificationCenterDelegate *ncDelegate = [[NotificationCenterDelegate alloc]init];
            ncDelegate.keepRunning = YES;
            nc.delegate = ncDelegate;

            NSUserNotification *note = [[NSUserNotification alloc] init];
            note.title = title;
            note.subtitle = subtitle;
            note.informativeText = message;

            [nc deliverNotification:note];

            while (ncDelegate.keepRunning) {
                [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
            }
        }

    }
    return 0;
}

现在这在编译 64 位二进制文​​件时工作正常。但是,如果我将它编译为带有 -m32-arch i386 标志的 32 位 二进制文件,它会崩溃:

2017-05-15 22:16:50.886 a.out[68034:2781117] -[NotificationCenterDelegate setKeepRunning:]: unrecognized selector sent to instance 0x7a019ef0
2017-05-15 22:16:50.886 a.out[68034:2781117] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NotificationCenterDelegate setKeepRunning:]: unrecognized selector sent to instance 0x7a019ef0'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x9bc9d9b9 __raiseError + 201
    1   libobjc.A.dylib                     0x9e277fd1 objc_exception_throw + 276
    2   CoreFoundation                      0x9bca1463 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
    3   CoreFoundation                      0x9bb91dec ___forwarding___ + 1020
    4   CoreFoundation                      0x9bb919ce _CF_forwarding_prep_0 + 14
    5   a.out                               0x000608d5 main + 533
    6   libdyld.dylib                       0x90c4c6ad start + 1
)
68034: trace trap

为什么会这样,是否有解决方法?

(因为毫无疑问有人会问 [出于充分的理由]:我需要它是 32 位的,以便 link 使用闭源的 32 位程序。而且我无法获得该程序的 64 位版本。我已经尝试 linking 32 位 dylib 和 运行 解决这个问题。)

您在构建 32 位时是否检查了编译器警告?我很确定他们会解释的。

在 32 位中,Objective-C 不提供属性的自动合成。您需要做一些事情 "by hand"。如果 属性 应该有一个用于后备存储的实例变量,则需要声明该实例变量。此外,您必须在 @interface;也不支持在 @implementation 中声明实例变量。

接下来,您需要显式 @synthesize 属性 或提供访问器方法的实现。如果您使用 @synthesize 并且您将实例变量命名为与 属性 相同的名称(没有前导下划线),那么您需要指定要使用的实例变量。例如,@synthesize keepRunning = _keepRunning;。即默认的实例变量名与通用约定和自动合成的默认不同。

所以:

@interface NotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate>
{
    BOOL _keepRunning;
}

@property (nonatomic, assign) BOOL keepRunning;

@end

@implementation NotificationCenterDelegate

@synthesize keepRunning = _keepRunning;

- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
    self.keepRunning = NO;
}

@end