在 Xcode 中制作 framework 包, 并在 OC 和 Swift 中混用

371 阅读3分钟

Github 代码地址:github.com/SuiShanpu/i…

一、创建静态库

1、选择 Create New Project(我的是 Xcode 16)

2、选择类型为 Framework

3、设置名称,语言(选哪种语言似乎没有差别)

4、生成的项目基本结构如下图:

5、设置最低适配 iOS 版本(根据项目需求设置)

6、设置为静态库(必须设置)

Mach-O Type:只有系统是动态库,外部的都是静态库。

Build Libraries for Distribution 为分发构建库

Build Libraries for Distribution 设置为 YES。这是根据前边的记录,打包 XCFramework 所用到的设置。设置为Yes, 使编译出来的framework向下兼容, 即用高版本Xcode自带的Swift高版本编译出来的framework, 放到低版本Xcode低Swift版本中, 也能运行。

Define Module:这个默认就是 YES;如果使用 swift 语言则必须设置为 YES。

二、代码及相关配置

以下几种场景,根据自身情况任选其中一种即可。(前面是 framewoek 包的语言;后面是要用的项目的语言)

1、OC 包给 OC、Swift 项目均可用

编写代码

  • 添加文件 OcDemo.h
// OcDemo.h

#import <UIKit/UIKit.h>

@interface OcDemo : NSObject

- (NSString *)get:(NSString *)code;

@end
  • 添加文件 OcDemo.m
// OcDemo.m

#import "OcDemo.h"

@interface OcDemo()
@end

@implementation OcDemo

- (instancetype)init {
    self = [super init];
    return self;
}

- (NSString *)get:(NSString *)code {
    return [NSString stringWithFormat:@"我是 OcDemo: %@", code];
}

@end
  • 添加新文件到头文件 FrameworkDemo.h

** 第一,先添加 h 文件到 Xcode 的头文件配置中:

** 第二,再在代码中引入 h 文件:

//  FrameworkDemo.h

#import <FrameworkDemo/OcDemo.h>

2、Swift 包给 Swift 项目用

编写代码

  • 添加文件 SwiftDemo.swift
// SwiftDemo.swift

// 都需要设置为 public 类型
public class SwiftDemo {

    public init() {}

    public func get(code: String) -> String? {
        return "我是 SwiftDemo: " + code;
    }
}
  • 源文件 FrameworkDemo.h 不能动 (必须存在,且在头文件配置中)

3、Swift 包给 OC 项目用

编写代码

  • 添加文件 SwiftDemo.swift
// SwiftDemo.swift

import Foundation

// Swift 中需继承 NSObject 才能被 OC 识别
public class SwiftDemo: NSObject {

    // Swift方法需添加 @objc 修饰符才能被 OC 识别
    @objc
    public func get(code: String) -> String? {
        return "我是 SwiftDemo: " + code;
    }
}

设置相关配置

Generated Header Name 默认为:包名称-Swift.h。
将 Install Generated Header 配置为 YES。

此时会在编译后的 “FrameworkDemo/DerivedData/universal/FrameworkDemo.xcframework/ios-arm64/FrameworkDemo.framework/Modules/” 文件夹下生成 “module.modulemap”,内容如下:

// module.modulemap

// 这个名称必须与项目名称一致
framework module FrameworkDemo {
    // 这个名称文件必须存在(空的即可),必须与XCode 中配置项 Generated Header Name 一致
    umbrella header "FrameworkDemo.h"

    export *
    module * { export * }
}

// 下面这个,只有 Install Generated Header 配置为 YES 时,才会生成。
// 当 swift 包应用在 oc 项目中时,必须有这个才可以
module FrameworkDemo.Swift {
    header "FrameworkDemo-Swift.h"
    requires objc
}

三、使用外部脚本进行编译

1、根目录下添加脚本文件

2、添加脚本内容

    #!/bin/bash

    # 停止脚本遇到任何错误时
    set -e

    # 获取脚本所在目录
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

    # 项目配置(有3处修改为自己工程名称)
    WORKSPACE_PATH="$SCRIPT_DIR/FrameworkDemo.xcodeproj/project.xcworkspace"
    SCHEME="FrameworkDemo"
    CONFIGURATION="Release"
    DERIVED_DATA_DIR="$SCRIPT_DIR/DerivedData"
    FRAMEWORK_NAME="FrameworkDemo"

    # 创建输出目录
    mkdir -p ${DERIVED_DATA_DIR}/universal

    # 编译真机架构
    xcodebuild -configuration $CONFIGURATION -derivedDataPath ${DERIVED_DATA_DIR}/iOS -workspace $WORKSPACE_PATH -scheme $SCHEME -sdk iphoneos clean build

    # 编译模拟器架构
    xcodebuild -configuration $CONFIGURATION -derivedDataPath ${DERIVED_DATA_DIR}/simulator -workspace $WORKSPACE_PATH -scheme $SCHEME -sdk iphonesimulator clean build

    # 创建XCFramework
    xcodebuild -create-xcframework \
    -framework ${DERIVED_DATA_DIR}/iOS/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework \
    -framework ${DERIVED_DATA_DIR}/simulator/Build/Products/Release-iphonesimulator/${FRAMEWORK_NAME}.framework \
    -output ${DERIVED_DATA_DIR}/universal/${FRAMEWORK_NAME}.xcframework

    # 删除无关的配置文件
    rm -rf "${DERIVED_DATA_DIR}/iOS"
    rm -rf "${DERIVED_DATA_DIR}/simulator"

    echo "Universal XCFramework is created at: ${DERIVED_DATA_DIR}/universal/${FRAMEWORK_NAME}.xcframework"

    echo "-------------编译完成---------------------------"

3、运行脚本 (我是用 VSCode 运行的)

% sh build.sh

xcframework 包可以将多种平台(iPhone、iPad、Mac、Simulator或arm64、i386、x86_64)的库文件打包在一起;同时供真机(arm64架构的)和模拟器(x86_64 和 arm64 架构的)使用。

四、在 iOS 工程中使用

1、在 Xcode 中打开 iOS 工程

2、使用 framework 包

将 xcframework 文件夹(FrameworkDemo/DerivedData/universal/FrameworkDemo.xcframework)整个拖入 即可。

3、代码中使用

在 OC 中用

#import <FrameworkDemo/FrameworkDemo.h>

# OC 用在 OC 中
NSString *demoStr = [[[OcDemo alloc] init] get:@"rondle"];

# Swift 用在 OC 中(注意方法名称变了)
NSString *demoStr = [[[SwiftDemo alloc] init] getWithCode:@"rondle"];

在 Swift 中用

import FrameworkDemo

// Swift 用在 Swift 中
let demoStr = SwiftDemo().get(code: "rondle");

// OC 用在 Swift 中
let demoStr = OcDemo().get("rondle");

------------------ 结束 -----------------

附:在一个纯 OC 项目中使用 swift 的 framework 包时报错

如果报错: “Undefined symbol: swift_FORCE_LOAD$_swiftCompatibility51”

image.png

此时,需要在项目工程新建至少一个 swift 文件(空的也可以),然后选择创建桥接头文件。

image.png

就会生成如下文件和配置项

image.png