Swift已推出数年,与Objective-C相比Swift的语言机制及使用简易程度上更接地气,大大降低了iOS入门门槛。当然这对新入行的童鞋们来讲,的确算是福音,但对于整个iOS编程从业者来讲,真真是,曾几何时“高大上”,转瞬之间“矮矬穷”。再加上培训班横行,批量批发之下,iOS再也看不到当年的辉煌。
往事不再提,事还是要做滴。iOS10推出后,紧跟着Xcode8也推送了更新,细心者会发现,Xcode8下iOS版本最低适配已变为iOS8.0,加上Swift版本趋于稳定,从某种意义上讲,Swift的时代正式开启,替代Objective-C怕也只是时间问题。当然,在这之前,我们也应做好准备。今年越来的越多的公司,也开始了Swift和Objective-C混编。
我们今天就来看看两者混编中的一些注意事项及问题:
混编也无非两种情况,
在混编的过程中最重要的两个文件:
1. 桥接文件:
桥接文件“ProjectName-Bridging-Header.h”,在首次创建其他文件的时候,会自动生成。如果不小心删除后,也可以手动添加,不过名字必须是“ProjectName-Bridging-Header.h”头文件(名称组成:工程名-Bridging-Header.h
)。如果名字记不清也可以自己新建Header file后,在Targets-->Build Settings-->Swift Compiler - General-->Objective-C Bridging Header
配置文件路径,这个文件主要是Swift使用OC类时使用。
2. Objective-C Generated Interface Header Name文件
这个文件是混编时,系统生成的Swift文件对应的Objective-C的头文件,具体可以在Targets-->Build Settings-->Swift Compiler - General-->Objective-C Generated Interface Header Name
进行配置,默认文件名是工程名-Swift.h
,一般不做改动。
当在OC文件中调用Swift文件中的类的时候,首先在OC文件中要加上 #import "
ProjectName-swift.h”(名字组成:工程名-swift)
这个文件虽然在工程中看不到,但是她真实存在,编译后,你可以按住Command+单击该文件名,就会看到具体生成的代码。
引入后,具体类的使用,直接按照OC的方式使用即可。
当在Swift中使用OC文件的时候,只需在桥接文件即projectName-Bridging-Header.h文件中引入需要的头文件。
具体使用,按照对应的Swift语法结构来即可。
class TestClass
{
// 属性
// 实现
}
如果要在Objective-C类中使用TestClass类,应当使用@objc加以声明,或者将TestClass继承自NSObject或NSObject的子类,否则,引入ProductName-Swift.h之后,程序找不到对应类。
对于自定义的类而言,Objective-C的类,不能继承自Swift的类,即要混编的OC类不能是Swift类的子类。反过来,需要混编的Swift类可以继承自OC的类。 注解
如Swift文件要使用OC中定义的宏,只能使用常量简单宏文件。
Swift中有许多OC没有的特性,比如,Swift有元组、为一等公民的函数、还有特有的枚举类型。所以,要使用的混编文件要注意Swift独有属性问题。
*Swift 2. **:Swift中使用Closure不能使用Block作为属性进行传值,必须是初始化方法或函数。
Objective-C文件中:
#import
typedef void (^Myblock)(NSString *arg);
@interface FirViewController : UIViewController
//@property (copy, nonatomic) Myblock myBlock;
// Swift 2.*版本,这种作为公共参数的形式,如果在Swift类中去回调的话,是有问题的。提示没有初始化方法,所以使用下面的以Block为参数的方法
- (void)transValue:(Myblock) block;
@end
下面是.m文件
#import "FirViewController.h"
@implementation FirViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)transValue:(Myblock)block
{
if (block)
{
block(@"firBack");
}
}
@end
在Swift文件回调:
在Swift使用OC的类时,首先在桥接文件中声明oc的头文件
工程名-Bridging-Header.h这是创建Swift工程的情况下
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
}
@IBOutlet weak var goFirst: UIButton!
@IBAction func goFirstAction(sender: AnyObject)
{
let firVC:FirViewController = FirViewController()
firVC. transValue { ( arg:String !) -> Void in
self.aBtn?.setTitle(arg, forState: UIControlState.Normal)
}
self.navigationController?.pushViewController(firVC, animated: true)
}
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
Swift’s compatibility with Objective-C lets you create a project that contains files written in either language. You can use this feature, called mix and match, to write apps that have a mixed-language codebase. Using mix and match, you can implement part of your app’s functionality using the latest Swift features and seamlessly incorporate it back into your existing Objective-C codebase.
Objective-C and Swift files can coexist in a single project, whether the project was originally an Objective-C or Swift project. You can simply add a file of the other language directly to an existing project. This natural workflow makes creating mixed-language app and framework targets as straightforward as creating an app or framework target written in a single language.
The process for working with mixed-language targets differs slightly depending on whether you’re writing an app or a framework. The general import model for working with both languages within the same target is depicted below and described in more detail in the following sections.
If you’re writing a mixed-language app, you may need to access your Objective-C code from Swift and your Swift code from Objective-C. The process described in this section applies to non-framework targets.
To import a set of Objective-C files in the same app target as your Swift code, you rely on an Objective-C bridging header to expose those files to Swift. Xcode offers to create this header file when you add a Swift file to an existing Objective-C app, or an Objective-C file to an existing Swift app.
If you accept, Xcode creates the header file along with the file you were creating, and names it by your product module name followed by adding "-Bridging-Header.h"
. (You’ll learn more about the product module name later, in Naming Your Product Module.)
Alternatively, you can create a bridging header yourself by choosing File > New > File > (iOS, watchOS, tvOS, or macOS) > Source > Header File.
You’ll need to edit the bridging header file to expose your Objective-C code to your Swift code.
To import Objective-C code into Swift from the same target
In your Objective-C bridging header file, import every Objective-C header you want to expose to Swift. For example:
#import "XYZCustomCell.h"
#import "XYZCustomView.h"
#import "XYZCustomViewController.h"
In Build Settings, in Swift Compiler - Code Generation, make sure the Objective-C Bridging Header build setting has a path to the bridging header file.
The path should be relative to your project, similar to the way your Info.plist path is specified in Build Settings. In most cases, you should not need to modify this setting.
Any public Objective-C headers listed in this bridging header file will be visible to Swift. The Objective-C functionality will be available in any Swift file within that target automatically, without any import statements. Use your custom Objective-C code with the same Swift syntax you use with system classes.
let myCell = XYZCustomCell()
myCell.subtitle = "A custom cell"
When you import Swift code into Objective-C, you rely on an Xcode-generated header file to expose those files to Objective-C. This automatically generated file is an Objective-C header that declares the Swift interfaces in your target. It can be thought of as an umbrella header for your Swift code. The name of this header is your product module name followed by adding "-Swift.h"
. (You’ll learn more about the product module name later, in Naming Your Product Module.)
By default, the generated header contains interfaces for Swift declarations marked with the public
or open
modifier. It also contains those marked with the internal
modifier if your app target has an Objective-C bridging header. Declarations marked with the private
or fileprivate
modifier do not appear in the generated header. Private declarations are not exposed to Objective-C unless they are explicitly marked with @IBAction
, @IBOutlet
, or @objc
. If your app target is compiled with testing enabled, a unit test target can access any declaration with the internal
modifier as if they were declared with the public
modifier by prepending @testable
to the product module import statement.
For more information on access-level modifiers, see Access Control in The Swift Programming Language (Swift 4.1).
You don’t need to do anything special to create the generated header file—just import it to use its contents in your Objective-C code. Note that the Swift interfaces in the generated header include references to all of the Objective-C types used in them. If you use your own Objective-C types in your Swift code, make sure to import the Objective-C headers for those types before importing the Swift generated header into the Objective-C .m
file you want to access the Swift code from.
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m
file within that target using this syntax and substituting the appropriate name:
#import "ProductModuleName-Swift.h"
The Swift files in your target will be visible in Objective-C .m
files containing this import statement. For information on using Swift from Objective-C code, see Using Swift from Objective-C.
Import into Swift |
Import into Objective-C |
|
---|---|---|
Swift code |
No import statement |
|
Objective-C code |
No import statement; Objective-C bridging header required |
|
If you’re writing a mixed-language framework, you may need to access your Objective-C code from Swift and your Swift code from Objective-C.
To import a set of Objective-C files in the same framework target as your Swift code, you’ll need to import those files into the Objective-C umbrella header for the framework.
To import Objective-C code into Swift from the same framework
Under Build Settings, in Packaging, make sure the Defines Module setting for that framework target is set to “Yes”.
In your umbrella header file, import every Objective-C header you want to expose to Swift. For example:
#import
#import
#import
Swift will see every header you expose publicly in your umbrella header. The contents of the Objective-C files in that framework will be available in any Swift file within that framework target automatically, without any import statements. Use your custom Objective-C code with the same Swift syntax you use with system classes.
let myOtherCell = XYZCustomCell()
myOtherCell.subtitle = "Another custom cell"
To import a set of Swift files in the same framework target as your Objective-C code, you don’t need to import anything into the umbrella header for the framework. Instead, import the Xcode-generated header file for your Swift code into any Objective-C .m
file you want to use your Swift code from.
Because the generated header for a framework target is part of the framework’s public interface, only declarations marked with the public
or open
modifier appear in the generated header for a framework target.
Swift methods and properties that are marked with the internal
modifier and declared within a class that inherits from an Objective-C class are accessible to the Objective-C runtime. However, they are not accessible at compile time and do not appear in the generated header for a framework target.
For more information on access-level modifiers, see Access Control in The Swift Programming Language (Swift 4.1).
To import Swift code into Objective-C from the same framework
Under Build Settings, in Packaging, make sure the Defines Module setting for that framework target is set to “Yes”.
Import the Swift code from that framework target into any Objective-C .m
file within that framework target using this syntax and substituting the appropriate names:
#import
The Swift files in your framework target will be visible in Objective-C .m
files containing this import statement. For information on using Swift from Objective-C code, see Using Swift from Objective-C.
Import into Swift |
Import into Objective-C |
|
---|---|---|
Swift code |
No import statement |
|
Objective-C code |
No import statement; Objective-C umbrella header required |
|
You can import external frameworks that have a pure Objective-C codebase, a pure Swift codebase, or a mixed-language codebase. The process for importing an external framework is the same whether the framework is written in a single language or contains files from both languages. When you import an external framework, make sure the Defines Module build setting for the framework you’re importing is set to “Yes”.
You can import a framework into any Swift file within a different target using the following syntax:
import FrameworkName
You can import a framework into any Objective-C .m
file within a different target using the following syntax:
@import FrameworkName;
Import into Swift |
Import into Objective-C |
|
---|---|---|
Any language framework |
|
|
Once you import your Swift code into Objective-C, use regular Objective-C syntax for working with Swift classes.
MySwiftClass *swiftObject = [[MySwiftClass alloc] init];
[swiftObject swiftMethod];
A Swift class must be a descendant of an Objective-C class to be accessible and usable in Objective-C. For more information about what you can access from Objective-C and how the Swift interface is imported, see Swift Type Compatibility.
When your code refers to a Swift class or protocol that comes from a different module, you import the Swift module into your Objective-C header using @import
. However, to avoid cyclical references, don’t import Swift code from within the same module into an Objective-C header (.h
) file. Instead, you can forward declare a Swift class or protocol to reference it in an Objective-C interface.
// MyObjcClass.h
@class MySwiftClass;
@protocol MySwiftProtocol;
@interface MyObjcClass : NSObject
- (MySwiftClass *)returnSwiftClassInstance;
- (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
// ...
@end
Forward declarations of Swift classes and protocols can only be used as types for method and property declarations.
To create a Swift protocol that can be adopted by an Objective-C class, mark the protocol
declaration with the @objc
attribute.
@objc public protocol MySwiftProtocol {
func requiredMethod()
@objc optional func optionalMethod()
}
A protocol declares all initializers, properties, subscripts, and methods that an Objective-C class must implement in order to conform to the protocol. Any optional protocol requirements must be marked with the @objc
attribute and have the optional
modifier.
If you need to declare a weak property in Objective-C that satisfies a Swift protocol requirement, mark the Swift property requirement with the weak
modifier. Note that applying the weak
modifier has no effect for Swift types that adopt the protocol.
An Objective-C class can adopt a Swift protocol in its implementation (.m
) file by importing the Xcode-generated header for Swift code and using a class extension.
// MyObjcClass.m
#import "ProductModuleName-Swift.h"
@interface MyObjcClass () <MySwiftProtocol>
// ...
@end
@implementation MyObjcClass
// ...
@end
Swift enumerations conforming to the Error
protocol and declared with the @objc
attribute produce an NS_ENUM
declaration, as well as an NSString
constant for the corresponding error domain in the generated header. For example, given the following Swift enumeration declaration:
@objc public enum CustomError: Int, Error {
case a, b, c
}
Here’s the corresponding Objective-C declaration in the generated header:
// Project-Swift.h
typedef SWIFT_ENUM(NSInteger, CustomError) {
CustomErrorA = 0,
CustomErrorB = 1,
CustomErrorC = 2,
};
static NSString * const CustomErrorDomain = @"Project.CustomError";
The Swift compiler automatically imports Objective-C code as conventional Swift code. It imports Objective-C class factory methods as Swift initializers, and Objective-C enumeration cases truncated names.
There may be edge cases in your code that are not automatically handled. If you need to change the name imported by Swift of an Objective-C method, enumeration case, or option set value, you can use the NS_SWIFT_NAME
macro to customize how a declaration is imported.
If the Swift compiler fails to identify a class factory method, you can use the NS_SWIFT_NAME
macro, passing the Swift signature of the initializer to have it imported correctly. For example:
+ (instancetype)recordWithRPM:(NSUInteger)RPM NS_SWIFT_NAME(init(rpm:));
If the Swift compiler mistakenly identifies a method as a class factory method, you can use the NS_SWIFT_NAME
macro, passing the Swift signature of the method to have it imported correctly. For example:
+ (id)recordWithQuality:(double)quality NS_SWIFT_NAME(record(quality:));
By default, Swift imports enumerations by truncating enumeration value name prefixes. To customize the name of an enumeration case, you can use the NS_SWIFT_NAME
macro, passing the Swift enumeration case name. For example:
typedef NS_ENUM(NSInteger, ABCRecordSide) {
ABCRecordSideA,
ABCRecordSideB NS_SWIFT_NAME(flipSide),
};
You can use the NS_REFINED_FOR_SWIFT
macro on an Objective-C method declaration to provide a refined Swift interface in an extension, while keeping the original implementation available to be called from the refined interface. For instance, an Objective-C method that takes one or more pointer arguments could be refined in Swift to return a tuple of values.
Initializer methods are imported by Swift with double underscores (__
) prepended to their first argument labels.
Object subscripting methods are imported by Swift as methods with double underscores (__
) prepended to their base names, rather than as a Swift subscript, if either the getter or setter method is marked as NS_REFINED_FOR_SWIFT
.
Other methods are imported with double underscores (__
) prepended to their base names.
Given the following Objective-C declarations:
@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
You can provide a refined Swift interface in an extension like this:
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)
}
}
Some Objective-C interfaces may not be suitable or necessary to be exposed as Swift interfaces. To prevent an Objective-C declaration from being imported by Swift, use the NS_SWIFT_UNAVAILABLE
macro, passing a message directing API consumers to any alternatives that may exist.
For example, an Objective-C class providing a convenience initializer that takes variadic arguments for keys-value pairs may advise a Swift consumer to use a dictionary literal instead:
+ (instancetype)collectionWithValues:(NSArray *)values forKeys:(NSArray<NSCopying> *)keys NS_SWIFT_UNAVAILABLE("Use a dictionary literal instead");
Attempting to call the +collectionWithValues:forKeys:
method from Swift code will result in a compiler error.
To make an Objective-C declaration unavailable at compile time in both Swift and Objective-C, use the NS_UNAVAILABLE
macro. The macro behaves just like the NS_SWIFT_UNAVAILABLE
macro except that it omits the customizable error message and it restricts compile-time access to the declaration in Objective-C code.
In Swift, you use the @available
attribute to control whether a declaration is available to use when building an app for a particular target platform. Similarly, you use the availability condition #available
to execute code conditionally based on required platform and version conditions.
Both kinds of availability specifier are available in Objective-C using the corresponding syntax shown in the following examples.
This example shows availability information used on a declaration in Swift:
@available(iOS 11, macOS 10.13, *)
func newMethod() {
// Use iOS 11 APIs.
}
And here’s how you add the same availability information in Objective-C:
@interface MyViewController : UIViewController
- (void) newMethod API_AVAILABLE(ios(11), macosx(10.13));
@end
The example below shows availability information used in a conditional statement in Swift:
if #available(iOS 11, *) {
// Use iOS 11 APIs.
} else {
// Alternative code for earlier versions of iOS.
}
And here’s how you use the same availability information in Objective-C:
if (@available(iOS 11, *)) {
// Use iOS 11 APIs.
} else {
// Alternative code for earlier versions of iOS.
}
For more information about specifiying platform availability, see Declaration Attributes in The Swift Programming Language (Swift 4.1).
The name of the Xcode-generated header for Swift code, and the name of the Objective-C bridging header that Xcode creates for you, are generated from your product module name. By default, your product module name is the same as your product name. However, if your product name has any nonalphanumeric characters, such as a period (.
), they are replaced with an underscore (_
) in your product module name. If the name begins with a number, the first number is replaced with an underscore.
You can also provide a custom name for the product module name and Xcode will use this when naming the bridging and generated headers. To do this, change the Product Module Name build setting.
NOTE
You cannot override the product module name of a framework.
Treat your Swift and Objective-C files as the same collection of code, and watch out for naming collisions.
If you’re working with frameworks, make sure the Defines Module (DEFINES_MODULE
) build setting under Packaging is set to “Yes”.
If you’re working with the Objective-C bridging header, make sure the Objective-C Bridging Header (SWIFT_OBJC_BRIDGING_HEADER
) build setting under Swift Compiler - Code Generation is set to a path to the bridging header file relative to your project (for example, “MyApp/MyApp-Bridging-Header.h”).
Xcode uses your product module name (PRODUCT_MODULE_NAME
)—not your target name (TARGET_NAME
)—when naming the Objective-C bridging header and the generated header for your Swift code. For information on product module naming, see Naming Your Product Module.
To be accessible and usable in Objective-C, a Swift class must be a descendant of an Objective-C class or it must be marked @objc
.
When you bring Swift code into Objective-C, remember that Objective-C won’t be able to translate certain features that are specific to Swift. For a list, see Using Swift from Objective-C.
If you use your own Objective-C types in your Swift code, make sure to import the Objective-C headers for those types before importing the Swift generated header into the Objective-C .m
file you want to use your Swift code from.
Swift declarations marked with the private
or fileprivate
modifier do not appear in the generated header. Private declarations are not exposed to Objective-C unless they are explicitly marked with @IBAction
, @IBOutlet
, or @objc
.
For app targets, declarations marked with the internal
modifier appear in the generated header if the app target has an Objective-C bridging header.
For framework targets, only declarations with the public
or open
modifier appear in the generated header. You can still use Swift methods and properties that are marked with the internal
modifier from within the Objective-C part of your framework, as long they are declared within a class that inherits from an Objective-C class. For more information on access-level modifiers, see Access Control in The Swift Programming Language (Swift 4.1).