Search code examples
swiftobjective-cswift-packagemodule-map

I added Objective-C files to Swift package via modulemap file, but implementation wasn't connected


I have a Swift package, and try to add Objective-c files.

My package now:

Root
- Package.swift
+ Sources
  + ObjC
    + DataDeflate
      - DataDeflate.h
      - module.modulemap
      - NSData+Deflate.h
      - NSData+Deflate.m
  + Swift
    + DataDeflator
      - DataDeflatow.swift
+ Tests
  + DataDeflatorTests
    - DataDeflatorTests.swift

Package.swift content:

import PackageDescription

let package = Package(
    name: "DataDeflator",
    products: [
        .library(name: "DataDeflator", targets: ["DataDeflator"]),
    ],
    targets: [
        .target(
            name: "DataDeflator",
            dependencies: ["DataDeflate"],
            path: "Sources/Swift/DataDeflator"),
        .systemLibrary(name: "DataDeflate", path: "Sources/ObjC/DataDeflate"),
        .testTarget(
            name: "DataDeflatorTests",
            dependencies: ["DataDeflator"]),
    ]
)

module.modulemap content:

module DataDeflate [system] {
  header "DataDeflate.h"
  link "DataDeflate"
  export *
}

DataDeflate.h content:

#ifndef DataDeflate_h
#define DataDeflate_h

#import "NSData+Deflate.h"

#endif /* DataDeflate_h */

Now, using these settings (module.modulemap and targets in Package.swift), Swift code has access to Objective-C NSData category. But only for declaration (NSData+Deflate.h), and not to implementation (NSData+Deflate.m)!

When I add Objective-C method calling, it builds successfully, without any warnings. But unrecognized selector sent to instance error appears when unit tests runs.

What did I forget? How can I connect both declaration and implementation from Objective-C to Swift?


Solution

  • You don't need to use systemLibrary target with a modulemap file for your own ObjC source files because usually it's used as wrapper to available C system libraries such as sqlite3, curl etc. and the modulemap file can be created for your target automatically by XCode.

    So you can use simple target:

    ...
    targets: [
        ...
        .target(name: "DataDeflate", path: "Sources/ObjC/DataDeflate"),
    ]
    ...
    

    Next you should organise your files inside DataDeflate folder next way:

    - Package.swift
    + Sources
      + ObjC
        + DataDeflate
            + include
                - DataDeflate.h
                - NSData+Deflate.h
            - NSData+Deflate.m
    ...
    

    Where include subfolder must contain <YourTargetName>.h (DataDeflate.h) file and all other public headers to include.