Open ShannonChenCHN opened 4 years ago
Always Embed Swift Standard Library
选项<CTHotelFramework/CTHotelFramework-Swift.h>
--------- 11.29 更新-----------
--------- 12.12 更新-----------
xxx.swiftmodule
文件和 Import Path
设置选项xx.swiftinterface
文件--------- 2020.01.10 更新-----------
xx
类,同时模块 B 中导入了模块 A,并且调用了模块 A 中定义的函数 aa
,该函数中其中有一个参数类型正是来自模块 X 的 xx
类,此时,Xcode 却报错,找不到模块 A 中定义的函数 aa
解决方案:将模块 A 中定义的 modulemap 移到一个 ObjC 模块中去,同时把所有 Swift 模块中导入桥接的 ObjC 头文件统一收到那一个模块中的 modulemap 中去(当然,理论上来讲,最好是每个 ObjC 模块都有自己对应的 modulemap 来导出自己的头文件)
Data Modeling
Data Flow and Control Flow
Language Interoperability
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 标记:
nonnull
— Imported as nonoptionals, whether annotated directly or by inclusion in an annotated regionnullable
— Imported as optionalsnull_resettable
annotation — Imported as implicitly unwrapped optionals另外,nullable
和 nonnull
实际上分别是 _Nullable
和 _Nonnull
的简化形式,绝大多数场景下,两种形式都可以。但是如果是复杂的指针类型,比如 id *
,那就必须要使用 _Nullable
和 _Nonnull
了。
NS_ASSUME_NONNULL_BEGIN
和 NS_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_UNAVAILABLE
和 NS_UNAVAILABLE
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
Clean Up Your Code(准备工作)
Migrate Your Code(代码迁移 ObjC -> Swift)
@objc(name)
-
and class +
methods with func and class func, respectivelyTroubleshooting Tips and Reminders(注意点)
static let sharedInstance = Singleton()
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
- (BOOL)removeItemAtURL:(NSURL *)URL
error:(NSError **)error;
在 Swift 中被转成了:
func removeItem(at: URL) throws
https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift
id
(Objc) == Any
(Swift)
https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html
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
在 ObjC 项目中新建 Swift 文件时或者在 Swift 项目中新建 ObjC 文件时都会自动帮你新建一个 Objective-C bridging header file,我们可以将需要暴露给 Swift 代码调用的 ObjC 的头文件在这个文件中导入。
YES
编译器会自动给你的 Swift 代码生成一个 ProductModuleName-Swift.h
的头文件,暴露给 ObjC 使用。
在 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
。
YES
#import <ProductName/ProductModuleName-Swift.h>
跟 ObjC 中类似,如果避免头文件循环引用,可以使用 @class
或者 @protocol
的方式来引用 Swift 类或者协议。
// MyObjcClass.h
@class MySwiftClass;
@protocol MySwiftProtocol;
@interface MyObjcClass : NSObject
- (MySwiftClass *)returnSwiftClassInstance;
- (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
// ...
@end
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?
还有一些不能被桥接成 Swift 值类型的 Foundation 类型,Swift 层会将这些 class、protocol,还有相关的 enumeration 和 constant 重新命名,它们的 NS
前缀会被去掉。
比如,NSJSONReadingOptions
枚举类型就被转成了 JSONSerialization.ReadingOptions
。
不过,以下两种情况除外:
什么叫 Toll-Free Bridging?
Ref
后缀被移除CFTypeRef
类型变成了 AnyObject
返回值为 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:
—takeUnretainedValue()
takeRetainedValue()
例如:
let memoryManagedResult = StringByAddingTwoStrings(str1, str2).takeUnretainedValue()
// memoryManagedResult is a memory managed CFString
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>
}
@property UIViewController<UITableViewDataSource, UITableViewDelegate> * myController;
转成 Swift 之后:
var myController: UIViewController & UITableViewDataSource & UITableViewDelegate
- (void)doSomethingForClass:(Class<NSCoding>)codingClass;
转成 Swift 之后:
func doSomething(for codingClass: NSCoding.Type)
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)
}
虽然在 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"
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.
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."
一般的 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
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
参数的函数,而不能导入有 ...
参数的函数。
@convention(c)
关键字的闭包,比如,int (*)(void)
被转成了@convention(c) () -> Int32
OpaquePointer
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 |
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 中的函数或者泛型。
这个时候如果直接 import CTHotelFramework
,就会报错 No such module 'CTHotelFramework'
。
解决办法是创建 Swift 静态库时需要导出一堆 .swiftmodule
文件。
参考:
其他:
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
/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;
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 toYes
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.
解决方案如下:
- Remove your bridging header file.
- Remove references to the bridging header file in the build settings for the framework
- Add the necessary headers to your umbrella file ([ProductName].h)
- Make the included files public in the framework's "Headers" section of its "Build Phases".
- Clean and rebuild.
但是 modulemap 文件是什么呢?怎么加呢?
module.modulemap
文件,语法参考 LLVM 官方文档参考:
加好之后再编译一下 CTHotelFramwork,最后我们可以看到 .swiftmodule
目录下多了几个 xxx.swiftinterface 文件。
延伸:
小结:
xx.swiftinterface
在 CMD+R/CMD+B 编译时执行 xcodebuild
脚本再编译一次?
将 CTHotelFramework 同时添加为 CTHotelViewModel 的子工程,这样就可以将 CTHotelFramework 设置为它的 dependencies 之一了,但是不要添加 linked library,在主工程里面 link CTHotelFramework 就行了。
这样就不再需要每次修改 CTHotelFramework 并编译之后再重新 copy swiftmodule 和 -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.
参考: