首页 > 解决方案 > 使用 .mm 文件中的 Objective-C++ 和 C++ 类将 Apple 的 ClassKit 和 swift 类合并到现有应用程序中时出错

问题描述

我正在做一个测试项目,看看 Apple 的 ClassKit 是否可以合并到现有的 ios 教育应用程序中,其中包含 .mm 文件中的 Objective-C++ 和 C++ 类,并且在 ...-Swift.h 桥接头中出现错误(在我添加 swift 文件时由 xcode 自动创建),甚至在将桥接头导入到 ObjectiveC++ 文件以允许它们访问 Swift 类之前。

作为一个简单的测试项目,ObjectiveC-Test,我创建了一个类:

// Quiz.swift

import Foundation 
import ClassKit 

@objcMembers public class Quiz : NSObject
{
    var mTitle            : String
    var mNumberOfProblems : Double
    var mNumberCorrect    : Double

    var context = CLSContext (type : CLSContextType.quiz, identifier : "Test", title : "Parent Test")

    override init ()
    {
        self.mTitle            = ""
        self.mNumberOfProblems = 0
        self.mNumberCorrect    = 0

        super.init()
    }


    init (title : String)
    {
        self.mTitle            = title
        self.mNumberOfProblems = 0
        self.mNumberCorrect    = 0
    }

}  // end class


protocol Node
{
    var parent      : Node?          { get }
    var children    : [Node]?        { get }
    var identifier  : String         { get }
    var contextType : CLSContextType { get }
}


extension Quiz : Node
{
    var parent      : Node?          { return nil }
    var children    : [Node]?        { return nil }
    var identifier  : String         { return mTitle }
    var contextType : CLSContextType { return .quiz }
}

“protocol Node”和“extension Quiz : Node”取自 Apple GreatPlays 项目,该项目说明了如何将 ClassKit 合并到项目中,但严格来说是 Swift 代码。CLSContext 是一个 ClassKit 类,不会导致任何问题,但是“CLSContextType”,ClassKit 中的枚举会导致 ...-Swift.h 标头中的错误:“未知类型名称 'CLSContextType'。

但是,在测验类中,“var context = CLSContext (...)”中的“CLSContextType.quiz”不会导致任何问题。但是,如果我在测验类中添加“var contextType = CLSContextType.quiz”行,我会收到错误消息。

xcode 生成的 swift 桥接头 (ObjectiveC_Test-Swift.h) 中的相关代码为:

@class CLSContext;

SWIFT_CLASS("_TtC15ObjectiveC_Test4Quiz")
@interface Quiz : NSObject
@property (nonatomic, copy) NSString * _Nonnull mTitle;
@property (nonatomic) double mNumberOfProblems;
@property (nonatomic) double mNumberCorrect;
@property (nonatomic, strong) CLSContext * _Nonnull context;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
- (nonnull instancetype)initWithTitle:(NSString * _Nonnull)title OBJC_DESIGNATED_INITIALIZER;
@end


@interface Quiz (SWIFT_EXTENSION(ObjectiveC_Test))
@property (nonatomic, readonly, copy) NSString * _Nonnull identifier;
@property (nonatomic, readonly) CLSContextType contextType;
@end

错误发生在倒数第二行:“CLSContextType contextType”:“未知类型名称'CLSContextType'”。它还会产生一个错误:“接口类型不能被静态分配”但我认为这是由于第一个错误。第八行的“CLSContext”不会导致任何错误。

如果我在协议和节点扩展中注释掉 CLSContextType 行,项目运行良好。

该项目在 xcode 10.0 和 ios 11.4 下。

构建设置包括:

Packaging
   Defines Module:    Yes

Build Options
   Always Embed Swift Standard Libraries:  Yes

Swift Compiler - General:
   Install Objective-C Compatibility Header:  Yes
   Objective-C Bridging Header:   ObjectiveC-Test/ObjectiveC-Test-Bridging-Header.h
   Objective-C-Generated Interface Header Name:   ObjectiveC_Test-Swift.h

Apple Clang - Language - C++
   C++ Language Dialect:  GNU++14 [std=gnu++14]
   C++ Standard Library:  libcc++ (LLVM C++ standard library with C++11 support)

任何帮助将不胜感激。

更新:

从 Quiz 类中删除“@objcMembers”消除了 xcode 创建的桥接头中的 CLSContextType 错误,但随后无法从 Objective-C++ .mm 文件中访问类属性。将“@obj”添加到那些已修复但不适用于 contextType。

然后可以在 Objective-C++ .mm 文件中访问和打印类属性。

协议属性,包括 CLSContextType,可以从 swift 函数 (start) 和节点扩展函数 (printContextTypeFromNodeExtension) 打印到 Objective-C++ .mm 文件中。

//  @objcMembers public class Quiz : NSObject
public class Quiz : NSObject
{
    @objc var mTitle            : String
    @objc var mNumberOfProblems : Double
    @objc var mNumberCorrect    : Double
//    @objc var mLessonContextType : CLSContextType { return .lesson }  //  doesn't work
                                                           //  causes same bridging error


@objc func start ()
{
    print ("printing title from start() in swift: \(mTitle)")
    print ("printing identifier from start() in swift: \(identifier)")

    print ("printing CLSContextType from start() in swift: \(contextType.rawValue)")
    printContextTypeFromNodeExtension ()
}

}  // end class

extension Node
{
    func printContextTypeFromNodeExtension ()
    {
        print ("printing CLSContextType from Node extension: \(contextType.rawValue)")
    }    
}

Objective-C++ .mm 文件:

Quiz * Q1 = [ [Quiz alloc] init];
Q1.mTitle = @"Place Value Blocks";
Q1.mNumberOfProblems = 10;
Q1.mNumberCorrect    = 3;

printf ("printing mTitle from objc: %s \n", [Q1.mTitle UTF8String] );
printf ("printing mNumberCorrect from objc: %f \n", Q1.mNumberCorrect);

[Q1 start];

Results:

printing mTitle from objc: Place Value Blocks 
printing mNumberCorrect from objc: 3.000000 

printing title from start() in swift: Place Value Blocks
printing identifier from start() in swift: Place Value Blocks
printing CLSContextType from start() in swift: 8
printing CLSContextType from Node extension: 8

将“@objc”添加到 Node 协议允许在 Objective-C++ .mm 文件中访问除 CLSContextType 之外的所有 Node 属性。如果保留,则返回快速桥接头错误。

@objc protocol Node
{
    var parent      : Node?          { get }
    var children    : [Node]?        { get }
    var identifier  : String         { get }
    //  var contextType : CLSContextType { get }  // error if included when "@objc" added
}


extension Quiz : Node
{
    var parent      : Node?          { return nil }
    var children    : [Node]?        { return nil }
    var identifier  : String         { return mTitle }
//    var contextType : CLSContextType { return .quiz }
}

Objective-C++ .mm file:

printf ("printing parent from objc: %p \n", [Q1 parent] );
printf ("printing children from objc: %p \n", [Q1 children] );
printf ("printing identifier from objc: %s \n", [Q1.identifier UTF8String] );

Results:

printing parent from objc: 0x0 
printing children from objc: 0x0 
printing identifier from objc: Place Value Blocks 

结论:我在另一篇文章中发现了一条评论,说在 Swift 中定义的枚举和结构在 Objective-C 中不可用,但找不到参考,因此最好使用类而不是结构并处理枚举仅在 swift 文件中。

标签: c++iosswiftobjective-c++

解决方案


您的 ObjC 标头是否导入 ClassKit?

协议,需要用@objc 声明。

@objc protocol Node

推荐阅读