ShannonChenCHN / ASwiftTour

A journey of learning Swift. Swift 学习之旅。
36 stars 12 forks source link

Swift-ObjC 混编 #1

Open ShannonChenCHN opened 4 years ago

ShannonChenCHN commented 4 years ago
ShannonChenCHN commented 4 years ago

Swift 相关

ShannonChenCHN commented 4 years ago

Swift 混编 & 组件化

2021 最新

ShannonChenCHN commented 4 years ago

Presentation(2019.11.18)

--------- 11.29 更新-----------

--------- 12.12 更新-----------

--------- 2020.01.10 更新-----------

解决方案:将模块 A 中定义的 modulemap 移到一个 ObjC 模块中去,同时把所有 Swift 模块中导入桥接的 ObjC 头文件统一收到那一个模块中的 modulemap 中去(当然,理论上来讲,最好是每个 ObjC 模块都有自己对应的 modulemap 来导出自己的头文件)

ShannonChenCHN commented 4 years ago

Swift Documentation

目录


Data Modeling

Data Flow and Control Flow

Language Interoperability

1. Objective-C and C Code Customization

1.1 Customizing Objective-C APIs

1) Designating Nullability in Objective-C APIs

在 Objective-C 中,我们通常是通过指向对象的指针来引用对象的,这个指针的值可能是 NULL,在 ObjC 中是 nil。 但是在 Swift 中,所有的值,包括对象实例,都是 non-null 的。我们可以用一个 optional 类型来表示一个值可能是空的,同时,我们用 nil 来表示一个空值。

我们在 Swift 中引入 OC 类的声明时,可以通过给 OC 的头文件中的声明添加 nullability annotations 来说明那些参数可能是 NULL 或者 nil。

如果没有给 OC 代码添加 nullability annotations 的话,Swift 中导入的函数参数、返回值和属性就变成了 implicitly wrapped optional。

@interface MyList : NSObject

- (MyListItem *)itemWithName:(NSString *)name;
- (NSString *)nameForItem:(MyListItem *)item;
@property (copy) NSArray<MyListItem *> *allItems;

@end
        ||
        \/
class MyList: NSObject {
    func item(withName name: String!) -> MyListItem!
    func name(for item: MyListItem!) -> String!
    var allItems: [MyListItem]!
}

三种 nullablility 标记:

另外,nullablenonnull 实际上分别是 _Nullable_Nonnull 的简化形式,绝大多数场景下,两种形式都可以。但是如果是复杂的指针类型,比如 id *,那就必须要使用 _Nullable_Nonnull 了。

NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN

@interface MyList : NSObject
- (nullable MyListItem *)itemWithName:(NSString *)name;
- (nullable NSString *)nameForItem:(MyListItem *)item;
@property (copy) NSArray<MyListItem *> *allItems;
@end

NS_ASSUME_NONNULL_END

2) Renaming Objective-C APIs for Swift

使用宏 NS_SWIFT_NAME 给 OC 类取一个给 Swift 用的别名。

NS_SWIFT_NAME(Sandwich.Preferences)
@interface SandwichPreferences : NSObject

@property BOOL includesCrust NS_SWIFT_NAME(isCrusty);

@end

@interface Sandwich : NSObject
@end
        ||
        \/
var preferences = Sandwich.Preferences()
preferences.isCrusty = true

枚举:

typedef NS_ENUM(NSInteger, SandwichBreadType) {
    brioche, pumpernickel, pretzel, focaccia
} NS_SWIFT_NAME(SandwichPreferences.BreadType);

3) Improving Objective-C API Declarations for Swift

@interface Color : NSObject

- (void)getRed:(nullable CGFloat *)red
         green:(nullable CGFloat *)green
          blue:(nullable CGFloat *)blue
         alpha:(nullable CGFloat *)alpha NS_REFINED_FOR_SWIFT;

@end

加了 NS_REFINED_FOR_SWIFT 之后,上面的代码被转成了:

func __getRed(_ red: UnsafeMutablePointer<CGFloat>?, green: UnsafeMutablePointer<CGFloat>?, blue: UnsafeMutablePointer<CGFloat>?, alpha: UnsafeMutablePointer<CGFloat>?)

然后再在 Swift 中重新定义一个新方法,并在这个新方法中调用原来的方法:

extension Color {
    var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var r: CGFloat = 0.0
        var g: CGFloat = 0.0
        var b: CGFloat = 0.0
        var a: CGFloat = 0.0
        __getRed(red: &r, green: &g, blue: &b, alpha: &a)
        return (red: r, green: g, blue: b, alpha: a)
    }
}

4) Grouping Related Objective-C Constants

NS_ENUM for simple enumerations

NS_CLOSED_ENUM for simple enumerations that can never gain new cases

NS_OPTIONS for enumerations whose cases can be grouped into sets of options

NS_TYPED_ENUM for enumerations with a raw value type that you specify

NS_TYPED_EXTENSIBLE_ENUM for enumerations that you expect might gain more cases

除了 NS_ENUM 会被转成 enum 类型之外,其他的几种宏都被转成了 struct 类型。

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};
        ||
        \/
enum UITableViewCellStyle: Int {
    case `default`
    case value1
    case value2
    case subtitle
}
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
        ||
        \/
public struct UIViewAutoresizing: OptionSet {
    public init(rawValue: UInt)

    public static var flexibleLeftMargin: UIViewAutoresizing { get }
    public static var flexibleWidth: UIViewAutoresizing { get }
    public static var flexibleRightMargin: UIViewAutoresizing { get }
    public static var flexibleTopMargin: UIViewAutoresizing { get }
    public static var flexibleHeight: UIViewAutoresizing { get }
    public static var flexibleBottomMargin: UIViewAutoresizing { get }
}
typedef long FavoriteColor NS_TYPED_EXTENSIBLE_ENUM;
FavoriteColor const FavoriteColorBlue;
        ||
        \/
struct FavoriteColor: RawRepresentable, Equatable, Hashable {
    typealias RawValue = Int

    init(_ rawValue: RawValue)
    init(rawValue: RawValue)
    var rawValue: RawValue { get }

    static var blue: FavoriteColor { get }
}

5) Marking API Availability in Objective-C

@available#available

6) Making Objective-C APIs Unavailable in Swift

NS_SWIFT_UNAVAILABLENS_UNAVAILABLE

1.2 Customizing C APIs

1) Customizing Your C Code for Swift

C 语言中的结构体只有成员变量,没有方法、getter 和 setter,给 C 函数或者常量加了 CF_SWIFT_NAME 之后,可以将其关联到一个结构体类型,这样就可以转成一个 Swift 结构体的接口。

#ifndef Color_h
#define Color_h

#import <Foundation/Foundation.h>

struct Color {
    float r;
    float g;
    float b;
    float alpha;
};

typedef struct Color Color;

Color ColorCreateWithCMYK(float c, float m, float y, float k) CF_SWIFT_NAME(Color.init(c:m:y:k:)); // 构造器

float ColorGetHue(Color color) CF_SWIFT_NAME(getter:Color.hue(self:)); // 实例属性 getter
void ColorSetHue(Color color, float hue) CF_SWIFT_NAME(setter:Color.hue(self:newValue:)); // 实例属性 setter

Color ColorDarkenColor(Color color, float amount) CF_SWIFT_NAME(Color.darken(self:amount:)); // 实例方法

extern const Color ColorBondiBlue CF_SWIFT_NAME(Color.bondiBlue); // 常量

Color ColorGetCalibrationColor(void) CF_SWIFT_NAME(getter:Color.calibration());  // 类属性 getter
Color ColorSetCalibrationColor(Color color) CF_SWIFT_NAME(setter:Color.calibration(newValue:));  // 类属性 setter

#endif /* Color_h */

Swift 调用效果:

let _ = Color.init(r: 1, g: 1, b: 1, alpha: 1)
let color = Color.init(c: 2, m: 4, y: 5, k: 5)
let darkenColor = color.darken(amount: 1)
darkenColor.hue = 1
print(darkenColor.hue)
let bondiBlue = Color.bondiBlue
let _ = Color.calibration
Color.calibration = bondiBlue

2. Migrating Your Objective-C Code to Swift

3. Cocoa Design Patterns

3.1 Using Key-Value Observing in Swift

3.2 Using Delegates to Customize Object Behavior

3.3 Managing a Shared Resource Using a Singleton

    static let sharedInstance = Singleton()
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()

3.4 About Imported Cocoa Error Parameters

- (BOOL)removeItemAtURL:(NSURL *)URL
                  error:(NSError **)error;

在 Swift 中被转成了:

func removeItem(at: URL) throws

3.5 Handling Cocoa Errors in Swift

https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift

4. Handling Dynamically Typed Methods and Objects in Swift

id(Objc) == Any(Swift)

https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html

5. Using Objective-C Runtime Features in Swift

https://developer.apple.com/documentation/swift/using_objective-c_runtime_features_in_swift https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID563

6. Imported C and Objective-C APIs

6.1 Swift and Objective-C in the Same Project

6.1.1 Importing Objective-C into Swift

A. Import Code Within an App Target

在 ObjC 项目中新建 Swift 文件时或者在 Swift 项目中新建 ObjC 文件时都会自动帮你新建一个 Objective-C bridging header file,我们可以将需要暴露给 Swift 代码调用的 ObjC 的头文件在这个文件中导入。

B. Import Code Within a Framework Target

6.1.2 Importing Swift into Objective-C

编译器会自动给你的 Swift 代码生成一个 ProductModuleName-Swift.h 的头文件,暴露给 ObjC 使用。

A. Import Code Within an App Target

在 ObjC 代码里可以直接通过 #import "ProductModuleName-Swift.h" 来导入这个由编译器自动生成的 Swift 声明。

默认情况下,编译器生成的 ProductModuleName-Swift.h 文件只包含带有 public 或者 open 修饰符的 Swift 接口对应的 OC 接口。

如果你的 target 中有 Objective-C bridging header file 的话,那就还会为 internal 修饰符的 Swift 接口生成对应的 OC 接口。

标记了 private 或者 fileprivate 修饰符的接口声明不会出现在 ProductModuleName-Swift.h 中,也不会暴露给 ObjC Runtime,除非它们同时被被显式声明了 @IBAction@IBOutlet 或者 @objc

B. Import Code Within a Framework Target

C. Include Swift Classes in Objective-C Headers Using Forward Declarations

跟 ObjC 中类似,如果避免头文件循环引用,可以使用 @class 或者 @protocol 的方式来引用 Swift 类或者协议。

// MyObjcClass.h
@class MySwiftClass;
@protocol MySwiftProtocol;

@interface MyObjcClass : NSObject
- (MySwiftClass *)returnSwiftClassInstance;
- (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
// ...
@end

6.2 Cocoa Frameworks

6.2.1 Working with Foundation Types

Prefer Swift Value Types to Bridged Objective-C Reference Types

When Swift code imports Objective-C APIs, the importer replaces Foundation reference types with their corresponding value types.

Swift value type Objective-C reference type
AffineTransform NSAffineTransform
Array NSArray
Calendar NSCalendar
Data NSData
... ...

如果确实需要在 Swift 中仍然使用引用类型,可以使用 as 关键字:

let dataValue = Data(base64Encoded: myString)
let dataReference = dataValue as NSData?

Note Renamed Reference Types

还有一些不能被桥接成 Swift 值类型的 Foundation 类型,Swift 层会将这些 class、protocol,还有相关的 enumeration 和 constant 重新命名,它们的 NS 前缀会被去掉。

比如,NSJSONReadingOptions 枚举类型就被转成了 JSONSerialization.ReadingOptions

不过,以下两种情况除外:

6.2.2 Working with Core Foundation Types

什么叫 Toll-Free Bridging

Use Memory Managed Objects

Convert Unmanaged Objects to Memory-Managed Objects

返回值为 Core Foundation 对象但是没有标记内存管理方式的函数或者方法,Swift 会将返回的对象包装成一个 Unmanaged<Instance> 类型。

例如,下面的 C 函数:

CFStringRef StringByAddingTwoStrings(CFStringRef s1, CFStringRef s2)

会被转成 Swift 代码:

func StringByAddingTwoStrings(_: CFString!, _: CFString!) -> Unmanaged<CFString>! {
    // ...
}

Unmanaged<Instance> 提供了两个方法将 unmanaged object 转成一个 memory-managed object:

例如:

let memoryManagedResult = StringByAddingTwoStrings(str1, str2).takeUnretainedValue()
// memoryManagedResult is a memory managed CFString

6.3 Objective-C APIs

6.3.1 Using Imported Lightweight Generics in Swift

ObjC 轻量泛型的转换。

@property NSArray<NSDate *> *dates;
@property NSCache<NSObject *, id<NSDiscardableContent>> *cachedData;
@property NSDictionary <NSString *, NSArray<NSLocale *> *> *supportedLocales;

在 Swift 中被转成了:

var dates: [Date]
var cachedData: NSCache<NSObject, NSDiscardableContent>
var supportedLocales: [String: [Locale]]
@interface List<T: id<NSCopying>> : NSObject
- (List<T> *)listByAppendingItemsInList:(List<T> *)otherList;
@end

@interface ListContainer : NSObject
- (List<NSValue *> *)listOfValues;
@end

@interface ListContainer (ObjectList)
- (List *)listOfObjects;
@end

在 Swift 中被转成了:

class List<T: NSCopying> : NSObject {
    func listByAppendingItemsInList(otherList: List<T>) -> List<T>
}

class ListContainer : NSObject {
    func listOfValues() -> List<NSValue>
}

extension ListContainer {
    func listOfObjects() -> List<NSCopying>
}

6.3.2 Using Imported Protocol-Qualified Classes in Swift

protocol composition types

@property UIViewController<UITableViewDataSource, UITableViewDelegate> * myController;

转成 Swift 之后:

var myController: UIViewController & UITableViewDataSource & UITableViewDelegate

Objective-C protocol-qualified metaclass

- (void)doSomethingForClass:(Class<NSCoding>)codingClass;

转成 Swift 之后:

func doSomething(for codingClass: NSCoding.Type)

6.4 C APIs

6.4.1 Using Imported C Structs and Unions in Swift

Structures with Default Values

struct Color {
    float r, g, b;
};
typedef struct Color Color;
public struct Color {
    var r: Float
    var g: Float
    var b: Float

    init()
    init(r: Float, g: Float, b: Float)
}

Unions

虽然在 Swift 中,将 C 的 Union 类型转成了 Structure,但是实际上表现还是一样的。

union SchroedingersCat {
    bool isAlive;
    bool isDead;
};
struct SchroedingersCat {
    var isAlive: Bool { get set }
    var isDead: Bool { get set }

    init(isAlive: Bool)
    init(isDead: Bool)

    init()
}

var mittens = SchroedingersCat(isAlive: false)

print(mittens.isAlive, mittens.isDead)
// Prints "false false"

mittens.isAlive = true
print(mittens.isDead)
// Prints "true"

Bit Fields

Swift imports bit fields that are declared in structures, like those found in Foundation’s NSDecimal type, as computed properties. When accessing a computed property corresponding to a bit field, Swift automatically converts the value to and from compatible Swift types.

Unnamed Structure and Union Fields

struct Cake {
    union {
        int layers;
        double height;
    };

    struct {
        bool icing;
        bool sprinkles;
    } toppings;
};
var simpleCake = Cake()
simpleCake.layers = 5
print(simpleCake.toppings.icing)
// Prints "false"

let cake = Cake(
    .init(layers: 2),
    toppings: .init(icing: true, sprinkles: false)
)

print("The cake has \(cake.layers) layers.")
// Prints "The cake has 2 layers."
print("Does it have sprinkles?", cake.toppings.sprinkles ? "Yes." : "No.")
// Prints "Does it have sprinkles? No."

6.4.2 Using Imported C Functions in Swift

一般的 C 函数可以转成 Swift 中的全局函数。比如,

int product(int multiplier, int multiplicand);
int quotient(int dividend, int divisor, int *remainder);

struct Point2D createPoint2D(float x, float y);
float distance(struct Point2D from, struct Point2D to);
func product(_ multiplier: Int32, _ multiplicand: Int32) -> Int32
func quotient(_ dividend: Int32, _ divisor: Int32, _ remainder: UnsafeMutablePointer<Int32>) -> Int32

func createPoint2D(_ x: Float, _ y: Float) -> Point2D
func distance(_ from: Point2D, _ to: Point2D) -> Float

Use a CVaListPointer to Call Variadic Functions

In Swift, you can call C variadic functions, such as vasprintf(_:_:_:), using the Swift getVaList(_:) or withVaList(_:_:) functions.

func swiftprintf(format: String, arguments: CVarArg...) -> String? {
    return withVaList(arguments) { va_list in
        var buffer: UnsafeMutablePointer<Int8>? = nil
        return format.withCString { cString in
            guard vasprintf(&buffer, cString, va_list) != 0 else {
                return nil
            }

            return String(validatingUTF8: buffer!)
        }
    }
}
print(swiftprintf(format: "√2 ≅ %g", arguments: sqrt(2.0))!)
// Prints "√2 ≅ 1.41421"

注:Swift 中只能导入有 va_list 参数的函数,而不能导入有 ... 参数的函数。

Call Functions with Pointer Parameters

For return types, variables, and arguments, the following mappings apply:

C Syntax Swift Syntax
const Type * UnsafePointer
Type * UnsafeMutablePointer

For class types, the following mappings apply:

C Syntax Swift Syntax
Type const UnsafePointer
Type __strong UnsafeMutablePointer
Type ** AutoreleasingUnsafeMutablePointer

For pointers to untyped, raw memory, the following mappings apply:

C Syntax Swift Syntax
const void * UnsafeRawPointer
void * UnsafeMutableRawPointer

6.4.3 Using Imported C Macros in Swift

ObjC 中普通的宏可以转成 Swift 中的全局常量:

#define FADE_ANIMATION_DURATION 0.35
#define VERSION_STRING "2.2.10.0a"
#define MAX_RESOLUTION 1268

#define HALF_RESOLUTION (MAX_RESOLUTION / 2)
#define IS_HIGH_RES (MAX_RESOLUTION > 1024)
let FADE_ANIMATION_DURATION = 0.35
let VERSION_STRING = "2.2.10.0a"
let MAX_RESOLUTION = 1268

let HALF_RESOLUTION = 634
let IS_HIGH_RES = true

另外,如果你在 Objective-C 中定义了一些包含复杂逻辑的宏,建议手动转成 Swift 中的函数或者泛型。

ShannonChenCHN commented 4 years ago

Swift 静态库之间的相互调用问题(组件化)

1. Swift 没有头文件,那么在跨模块调用的场景下,要单独编译一个静态库怎么办?

这个时候如果直接 import CTHotelFramework,就会报错 No such module 'CTHotelFramework'

解决办法是创建 Swift 静态库时需要导出一堆 .swiftmodule 文件。

参考

其他

2. import 混编的模块之后提示Implicit import of bridging header 'CTHotelFramework-Bridging-Header.h' via module 'CTHotelFramework' is deprecated and will be removed in a later version of Swift

13:53:02 /Users/cbuilder/jenkins_slaves/slave_216/workspace/IOS_BUNDLE/wirelesscode/app-8.18.0/rel/8.18/IOS_2/CTHotelViewModel/CTHotelViewModel/Request/HotelSOTPCachePolicy.swift:10:8: warning: implicit import of bridging header 'CTHotelFramework-Bridging-Header.h' via module 'CTHotelFramework' is deprecated and will be removed in a later version of Swift
13:53:02 import CTHotelFramework
13:53:02        ^
13:53:02 /Users/cbuilder/jenkins_slaves/slave_216/workspace/IOS_BUNDLE/wirelesscode/app-8.18.0/rel/8.18/IOS_2/CTHotelViewModel/CTHotelViewModel/Request/HotelSOTPCachePolicy.swift:10:8: error: failed to import bridging header '/Users/xianglongchen/Desktop/CtripGitRepo/IOS_2/CTHotelFramework/CTHotelFramework/CTHotelFramework-Bridging-Header.h'
13:53:02 import CTHotelFramework
13:53:02        ^

参考

解决办法之一,就是在当前 target(也就是 CTHotelViewModel)的 bridging header 中显式导入 CTHotelFramework-Bridging-Header.h

3. 但是 MCD 上打 CTHotelViewModel 的静态库仍然报错了,接口声明重复了

/Users/xianglongchen/Desktop/CtripGitRepo/IOS_2/CTHotelFramework/CTHotelFramework/CTHotelFramework-Bridging-Header.h:7:2: note: in file included from /Users/xianglongchen/Desktop/CtripGitRepo/IOS_2/CTHotelFramework/CTHotelFramework/CTHotelFramework-Bridging-Header.h:7:
10:02:59 # 1 "/Users/xianglongchen/Desktop/CtripGitRepo/IOS_2/CTHotelFramework/../CTBaseFramework/CTBaseFramework/CTBaseBusiness/CTBaseBusiness/CTService/CTABTestResultModel.h" 1
10:02:59  ^
10:02:59 /Users/xianglongchen/Desktop/CtripGitRepo/IOS_2/CTHotelFramework/../CTBaseFramework/CTBaseFramework/CTBaseBusiness/CTBaseBusiness/CTService/CTABTestResultModel.h:31:40: error: property has a previous declaration
10:02:59 @property(nonatomic,strong) NSString * expResult;
10:02:59                                        ^
10:02:59 /Users/cbuilder/jenkins_slaves/slave_196/workspace/IOS_BUNDLE/wirelesscode/app-8.18.0/rel/8.18/IOS_2/CTHotelViewModel/../CTBaseFramework/CTBaseFramework/CTBaseBusiness/CTBaseBusiness/CTService/CTABTestResultModel.h:31:40: note: property declared here
10:02:59 @property(nonatomic,strong) NSString * expResult;

4. Swift 5.1 编译的接口在 Swift 5.1.2 上不能用(Module compiled with Swift 5.1 cannot be imported by the Swift 5.1.2 compiler

https://stackoverflow.com/questions/58654714/module-compiled-with-swift-5-1-cannot-be-imported-by-the-swift-5-1-2-compiler 给出的解决方案是:

You need to set the Build Libraries for Distribution option to Yes in your framework's build settings, otherwise the swift compiler doesn't generate the neccessary .swiftinterface files which are the key to future compilers being able to load your old library.

但是又报了另外一个错误:error: using bridging headers with framework targets is unsupported

https://stackoverflow.com/questions/24875745/xcode-6-beta-4-using-bridging-headers-with-framework-targets-is-unsupported 给的解释是,根据官方文档 Import Code Within a Framework Target

To use the Objective-C declarations in files in the same framework target as your Swift code, you’ll need to import those files into the Objective-C umbrella header—the master header for your framework. Import your Objective-C files by configuring the umbrella header:

Under Build Settings, in Packaging, make sure the Defines Module setting for the framework target is set to Yes.

In the umbrella header, import every Objective-C header you want to expose to Swift.

解决方案如下:

  1. Remove your bridging header file.
  2. Remove references to the bridging header file in the build settings for the framework
  3. Add the necessary headers to your umbrella file ([ProductName].h)
  4. Make the included files public in the framework's "Headers" section of its "Build Phases".
  5. Clean and rebuild.

但是 modulemap 文件是什么呢?怎么加呢?

参考

加好之后再编译一下 CTHotelFramwork,最后我们可以看到 .swiftmodule 目录下多了几个 xxx.swiftinterface 文件。

延伸

小结

5. 编译时生成不同的 CPU 架构对应的 xx.swiftinterface

在 CMD+R/CMD+B 编译时执行 xcodebuild 脚本再编译一次?

6. 更完美的方案

将 CTHotelFramework 同时添加为 CTHotelViewModel 的子工程,这样就可以将 CTHotelFramework 设置为它的 dependencies 之一了,但是不要添加 linked library,在主工程里面 link CTHotelFramework 就行了。

这样就不再需要每次修改 CTHotelFramework 并编译之后再重新 copy swiftmodule 和 -swift 文件到代码目录下了,实现了真正的编译依赖。

ShannonChenCHN commented 4 years ago

在 ObjC 项目中使用 Swift 静态库时出现了调试问题

$ po itemId
Cannot create Swift scratch context (couldn't load the Swift stdlib).Shared Swift state for CTRIP_WIRELESS could not be initialized. The REPL and expressions are unavailable.
debugging_variables

参考: