将单例从 Objective C 共享到 Swift

Posted

技术标签:

【中文标题】将单例从 Objective C 共享到 Swift【英文标题】:Share singleton from Objective C to Swift 【发布时间】:2020-08-20 21:06:02 【问题描述】:

我正在尝试从 Swift 访问一个 Objective C 单例,但是我似乎只获得了在单例的 init 函数中创建的初始值。暴露的 flightControllerState 对象在委托函数中更新,我可以看到该值在 Objective C 端正确更新。

我在 SO 和 article 上关注了一些不同的帖子,了解如何从 Swift 调用共享对象。 (我还应该提到这是在 react native 项目中运行,如果这可能会产生任何影响?)

编辑更新了 swift 代码 - 我在 init 方法中添加了错误的行以获取共享实例 - 问题仍然相同

Objective-C 单例

@import DJISDK;

@interface RCTBridgeDJIFlightController : RCTEventEmitter<DJIFlightControllerDelegate> 
    DJIFlightControllerState *flightControllerState;



@property(nonatomic, readonly) DJIFlightControllerState *flightControllerState;

+ (id)sharedFlightController;

@end


@implementation RCTBridgeDJIFlightController

DJIFlightControllerState *flightControllerState;

@synthesize flightControllerState;

    + (id)sharedFlightController 
        static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^
            sharedFlightControllerInstance = [[self alloc] init];
        );
        return sharedFlightControllerInstance;
    

    - (id)init 
    // I also tried this to make sure the shared instance was returned but no luck
    //if (sharedFlightControllerInstance != nil) 
    //    return sharedFlightControllerInstance;
    //
    if (self = [super init]) 
        flightControllerState = nil;
    
    return self;
    


    -(void)flightController:(DJIFlightController *)fc didUpdateState:(DJIFlightControllerState *)state 
    flightControllerState = state;
    
@end

Swift 类调用单例并访问值

class VirtualStickController 
  var flightControllerSharedInstance: RCTBridgeDJIFlightController

  override init() 
      self.flightControllerSharedInstance = RCTBridgeDJIFlightController.sharedFlightController()
  

  func getFlightControllerState() 
      if let state = flightControllerSharedInstance.flightControllerState 
      print("FLIGHT CONTROLLER STATE: \(state)") // always null
     else 
      print ("NULL")
    
  

【问题讨论】:

如果你的类中有一个 init 并在 swift override init 中分配它,那么你的代码中没有使用单例,而是一个普通的实例类型。 【参考方案1】:
DJIFlightControllerState *flightControllerState;

@synthesize flightControllerState;

除特殊情况外,(现代)Objective-C 中的属性无需使用@synthesize

属性flightControllerState 是一个实例属性,将使用隐藏的instance 变量来合成(带或不带@synthesize)用于存储。

变量flightControllerState是一个全局变量,它恰好与属性同名,但与它没有任何关系。

猜测您正在更改 Objective-C 中的 全局变量,并期望通过 property 在 Swift 中看到结果,但您不会。

删除全局变量,然后检查其余代码。

除此之外,您的代码会生成一个有效的共享实例,该实例可以在 Objective-C 和 Swift 之间共享,并且以一种语言所做的更改将在另一种语言中可见。

HTH

【讨论】:

感谢您的详细解释!是的,这正是我所期望看到的,我肯定混淆了全局变量和属性。我通过调整我的 init 方法来解决这个问题,使其始终返回 sharedFlightControllerInstance ,除非它不存在。不确定这是否是好的做法? - (id)init if (sharedFlightControllerInstance) return sharedFlightControllerInstance; //确保只有一个实例存在 @synchronized(self) self = [super init]; if (self) sharedFlightControllerInstance = self; 返回自我; 关于编写真正单例的一种方法,请参阅this answer,它基于 Apple 在过去时代记录的相同模型 ;-)【参考方案2】:

关于如何从 Swift 访问 Objective C 单例的名义问题,我会推荐一个替代方案。现代惯例是将您的sharedFlightController 声明为class 属性并将init 声明为NS_UNAVAILABLE

@interface RCTBridgeDJIFlightController : NSObject
...
@property (nonatomic, readonly, class) RCTBridgeDJIFlightController *sharedFlightController;
- (instancetype)init NS_UNAVAILABLE;
@end

该实现将为此类属性实现一个 getter:

@implementation RCTBridgeDJIFlightController

+ (instancetype)sharedFlightController 
    static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        sharedFlightControllerInstance = [[self alloc] init];
    );
    return sharedFlightControllerInstance;


...

@end

现在,您的 Swift 代码可以引用 RCTBridgeDJIFlightController.shared,这与 Swift 单例的约定一样。


关于您收到nil 状态的原因,可能是以下两个问题之一:

您的 Objective-C 代码将明确定义的 ivars、手动综合和全局变量的组合混淆了。 (见下文。)

我还建议您确认 flightController:didUpdateState: 是否曾经被调用过。 (我没有看到你设置过飞行控制器的委托。)在该方法中添加断点或NSLog 语句并确认。

关于上面的第一个问题,我建议:

    您不应该在 init 方法中使用这些注释行。如果您想确保使用您的单例对象,请将init 声明为NS_UNAVAILABLE

    鉴于您的所有init 方法正在将flightControllerState 更新为nil,您可以将其完全删除。在 ARC 中,属性会为您初始化为 nil

    您不应在 @interface 中声明显式 ivar。让编译器自动为你合成。

    你不应该@synthesize @implementation 中的 ivar。编译器现在将自动为您合成(并将为 ivar 使用适当的名称,在属性名称中添加下划线。

    您不应在 @implementation 中声明该全局变量。

    如果你想在 Swift 中使用这个 sharedFlightController,你应该将它定义为一个 class 属性,而不是一个类方法。我知道that article 建议使用类方法,但这确实不是最佳实践。

因此:

// RCTBridgeDJIFlightController.h

#import <Foundation/Foundation.h>

// dji imports here

NS_ASSUME_NONNULL_BEGIN

@interface RCTBridgeDJIFlightController : NSObject
@property (nonatomic, readonly, nullable) DJIFlightControllerState *flightControllerState;
@property (nonatomic, readonly, class) RCTBridgeDJIFlightController *sharedFlightController;
- (instancetype)init NS_UNAVAILABLE;
@end

NS_ASSUME_NONNULL_END

// RCTBridgeDJIFlightController.m

#import "RCTBridgeDJIFlightController.h"


#import "RCTBridgeDJIFlightController.h"

@interface RCTBridgeDJIFlightController ()
@property (nonatomic, nullable) DJIFlightControllerState *flightControllerState;
@end

@implementation RCTBridgeDJIFlightController

+ (instancetype)sharedFlightController 
    static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        sharedFlightControllerInstance = [[self alloc] init];
    );
    return sharedFlightControllerInstance;


- (void)flightController:(DJIFlightController *)fc didUpdateState:(DJIFlightControllerState *)state 
    NSLog(@"State updated");
    self.flightControllerState = state;

@end

最终结果是您现在可以像这样使用它:

class VirtualStickController 
    func getFlightControllerState() 
        if let state = RCTBridgeDJIFlightController.shared.flightControllerState 
            print("FLIGHT CONTROLLER STATE: \(state)") // always null
         else 
            print("NULL")
        
    

注意,因为sharedFlightController 现在是一个类属性,Swift/ObjC 互操作性足够智能,所以 Swift 代码可以只引用shared,如上所示。

【讨论】:

以上是关于将单例从 Objective C 共享到 Swift的主要内容,如果未能解决你的问题,请参考以下文章

使用 Objective-C/Swift 单例模型,为啥我们要创建共享实例而不只是使用类方法? [复制]

objective-c 单例

将单例注入 Spring Boot 应用程序上下文

如何使用 Swift 3 将单例与 Alamofire 一起使用?

Objective-C 替代使用 ApplicationDelegate 或单例传递数据

无法访问 XCTestCase Objective C 中的单例对象