At present, most projects are still pure OC, even if the migration to Swift is only a little bit excessive module, so what is the difference between OC and Swift? How do they call each other?
A, comments,
// MARK:
Similar to OC#pragma mark
// MARK: -
Similar to OC#pragma mark-
// TODO:
Used to mark unfinished tasks// FIXME: -
Used to mark problems to be fixed#warning("msg")
For global hints
Sample code:
public class Person {
// MARK: - attributes
var age = 0
var weight = 0
var height = 0
// MARK: - Private methods
/ / MARK: running
private func run1(a) {
// TODO:unfinished
}
private func run2(a) {
// FIXME:To repair
age + = 20
}
/ / MARK: walk
private func walk1(a){}private func walk2(a){}// MARK: - Public methods
public func eat1(a){}public func eat2(a){}}Copy the code
Effect presentation:
When MARK: – is used, a dividing line is also displayed at the corresponding position of the code area (the color is very light above the tag bit).
warning
Effect:
Note: uppercase only, not lowercase, otherwise it will not work (AndroidStudio and IDEA do much better than Xcode).
Conditional compilation
Swift does not support conditional compilation very much, but it looks like this:
// Operating system: macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD
#if os(macOS) || os(iOS)
// CPU architecture: i386\x86_64\arm\arm64
#elseif arch(x86_64) || arch(arm64)
/ / swift version
#elseif swift(<5) && swift(> =3)
/ / simulator
#elseif targetEnvironment(simulator)
// Whether a module can be imported
#elseif canImport(Foundation)
#else
#endif
Copy the code
Custom compile flags:
Xcode has a DEBUG flag by default, and we can add a new flag ourselves. Active Compilation Conditions are not much different from Other Swift Flags, except that you need to add -d in front of the Other Swift Flags area to add a flag.
#if DEBUG
/ / the debug mode
#else
/ / release model
#endif
#if TEST
print("test")
#endif
#if OTHER
print("other")
#endif
Copy the code
In OC, different macros can be defined for different compilation conditions to control whether the NSLog is valid in different environments. But in Swift you can only make it run by defining a new function and using the compile flags of different environments:
func log(_ msg: String) {
#if DEBUG
print(msg)
#endif
}
Copy the code
We don’t have to worry about whether there’s extra log in the Release environment. Because the compiler automatically does inline optimization.
Custom precision printing:
func log(_ msg: String.file: NSString = #file, line: Int = #line, fn: String = #function) {
#if DEBUG
let prefix = "from:\(file.lastPathComponent)_line:\(line)_fn:\(fn):"
print(prefix, msg)
#endif
}
func test(a) {
log("Test information")
}
test() // Output: from:main.swift_line:20_fn:test(): test information
Copy the code
If you use print(#file, #line, #function, MSG) inside the log function, you will print the same file, the same line, and the same log function each time. Because #file, #line, and #function capture the context of the current function.
The reason for using OC’s NSString is because the lastPathComponent property of NSString is easier to use.
Note: there are no macros in Swift.
Version detection
3.1. System version detection
Sample code:
if #available(iOS 10, macOC 10.12.*) {
// For iOS, perform this operation only on iOS10 or later
// For macOS platforms, only macOS 10.12 and later
// The last * indicates that it is executed on all other platforms
}
Copy the code
3.2. API availability description
Some deprecated apis can be flagged.
Sample code:
// Person is only available on iOS10 and above and macOS 10.12 and above
@available(iOS 10.macOS 10.12.*)
class Person {}struct Student {
// study_ has been modified to study
@available(*, unavailable, renamed: "study")
func study_(a){}func study(a){}// the run function is deprecated in iOS11
@available(iOS, deprecated: 11)
// The run function is deprecated on macOS 10.11
@available(macOS, deprecated: 10.11)
func run(a){}}Copy the code
More usage references: docs.swift.org/swift-book/…
Tip: fatalError() can be used instead of writing internal logic to the body of a function that has a return value.
4. Entrance of iOS application
There is a default @UIApplicationMain flag on the AppDelegate, which means that the compiler automatically generates the entry code (the main function code) that automatically sets the AppDelegate as a proxy for the App.
You can also delete @uiApplicationMain and customize the entry code by creating a main.swift file and manually implementing UIApplicationMain (basically the same as OC’s main.m).
// main.swift
import UIKit
// Customize Application
class DBApplication: UIApplication {}
// Program entry
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(DBApplication.self), NSStringFromClass(AppDelegate.self))
Copy the code
Note: The file for the custom entry code must be main.swift.
Swift calls OC
A lot of third party code/libraries are written in OC, and our project uses Swift as the main development language. This is where Swift calls OC.
5.1. Establish bridge joint files
Method 1 (Manually created) :
-
Create a bridge file. The default file name format is {targetName} -bridge-header. h (the file name is fixed).
-
Set the location of the header file in Build Settings.
Method 2 (Automatic creation) :
If the source project is Swift, Xcode will prompt you to create a bridge file when creating an OC file.
Header files are useful for:
OC needs to put something exposed to Swift in a header file.
5.2. Call the OC code
- Import the relevant OC header files required by Swift into the bridge connector file.
#import "DBPerson.h"
Copy the code
DBPerson.h
int sum(int a, int b);
@interface DBPerson : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
+ (instancetype)personWithAge:(NSInteger)age name:(NSString *)name;
- (void)run;
+ (void)run;
- (void)eat:(NSString *)food other:(NSString *)other;
+ (void)eat:(NSString *)food other:(NSString *)other;
@end
Copy the code
DBPerson.m
#import "DBPerson.h"
int sum(int a, int b) {
return a + b;
}
@implementation DBPerson
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name {
NSLog(@"-init");
if (self = [super init]) {
self.age = age;
self.name = name;
}
return self;
}
+ (instancetype)personWithAge:(NSInteger)age name:(NSString *)name {
NSLog(@"+init");
return [[self alloc] initWithAge:age name:name];
}
- (void)run {
NSLog(@"%zd %@ -run", _age, _name);
}
+ (void)run {
NSLog(@"Person +run");
}
- (void)eat:(NSString *)food other:(NSString *)other {
NSLog(@"%zd %@ -eat %@ %@", _age, _name, food, other);
}
+ (void)eat:(NSString *)food other:(NSString *)other {
NSLog(@"Person +eat %@ %@", food, other);
}
@end
Copy the code
- Use the imported OC class in the Swift file.
var p = DBPerson(age: 10, name: "Jack") // Output: -init
p.age = 18
p.name = "Rose"
p.run() // Output: 18 rose-run
p.eat("Apple", other: "Water") // Output: 18 rose-eat Apple Water
DBPerson.run() // Output: Person +run
DBPerson.eat("Pizza", other: "Banana") Person +eat Pizza Banana
print(sum(10.20)) // Output: 30
Copy the code
5.3. Change the C function name
If a function name exposed to Swift by C conflicts with another function name in Swift, you can change the C function name in Swift using @_silgen_name.
Sample code:
// C
int sum(int a, int b) {
return a + b;
}
// Swift
@_silgen_name("sum")
func swift_sum(_ v1: Int32._ v2: Int32) -> Int32
print(swift_sum(10.20)) // Output: 30
print(sum(10.20)) // Output: 30
Copy the code
Note that the function parameter type of Swift must be the same as the method parameter type of C (int in C corresponds to Int32 in Swift).
You can use @_silgen_name to call the underlying private API (use caution).
6. OC calls Swift
Xcode already generates a header file for OC calls to Swift by default in the format {targetname-swift.h} (fixed format).
The classes that Swift exposes to OC ultimately inherit from NSObject.
- use
@objc
Retouching needs to be exposed to OC members - use
@objcMembers
decorator- Represents by default all members are exposed to OC (including those defined in the extension)
- The ultimate success of exposure also requires consideration of the access level of the member itself
6.1. Call the Swift
Car.swift
import Foundation
@objcMembers class Car: NSObject {
var price: Double
var band: String
init(price: Double.band: String) {
self.price = price
self.band = band
}
func run(a) {
print(price, band, "run")}static func run(a) {
print("Car run")}}extension Car {
func test(a) {
print(price, band, "test")}}Copy the code
OC call Swift
#import "swiftdemo-swift. h" void testSwift() {Car * Car = [[Car alloc] initWithPrice:2.0 band:@"BMW"]; [car test]; // output: 2.0 BMW test [car run]; // output: 2.0 BMW run [Car run]; // Output: Car run}Copy the code
SwiftDemo-Swift.h
Xcode will automatically generate the OC declaration according to the Swift code and write the {targetname-swift.h} file.
Tip: If Swift code is not prompted or found in OC after it is written, you need to compile the project. Do not modify the {targetname-swift.h} file because its contents are automatically generated after compilation.
6.2. Renamed
You can rename the symbol names (class names, attribute names, function names, and so on) that Swift exposes to OC with @objc.
Swift code:
@objc(DBCar)
@objcMembers class Car: NSObject {
var price: Double
@objc(name)
var band: String
init(price: Double.band: String) {
self.price = price
self.band = band
}
@objc(drive)
func run(a) {
print(price, band, "run")}static func run(a) {
print("Car run")}}extension Car {
@objc(exec:v2:)
func test(a: Int.b: Int) {
print(price, band, "test")}}Copy the code
OC use:
DBCar *car = [[DBCar alloc] initWithPrice:2.0 band:@"BMW"]; car.name = @"Benz"; Car. Price = 98.0; [car drive]; // output: 98.0 Benz run [car exec:10 v2:20]; // output: 98.0 Benz test [DBCar run]; // Output: Car runCopy the code
SwiftDemo-Swift.h
6.3. The selector
You can still use selectors in Swift, so you define a selector using #selector(name). Methods that must be decorated by @objcMembers or @objc can define selectors.
Sample code:
@objcMembers class Person: NSObject {
func test1(v1: Int) {
print("test1")}func test2(v1: Int.v2: Int) {
print("test2(v1:v2:)")}func test2(_ v1: Double._ v2: Double) {
print("test2(_:_:)")}func run(a) {
perform(#selector(test1))
perform(#selector(test1(v1:)))
perform(#selector(test2(v1:v2:)))
perform(#selector(test2(_:_:)))
perform(#selector(test2 as (Double.Double) - >Void))}}Copy the code
If there is no function overloading, there is no need to write an argument list after the function name of the selector.
Swift has no runtime concept, so only members exposed to OC can use selectors.
Think 6.4.
-
Why does the class that Swift exposes to OC end up inheriting from NSObject?
- Because this class is ultimately intended for use by OC, all OC classes eventually inherit from
NSObject
.
- Because this class is ultimately intended for use by OC, all OC classes eventually inherit from
-
How is p.run() called at the bottom (OC runtime or Swift virtual table)? Conversely, how does OC call Swift bottom layer?
-
OC calls Swift, and Swift code, because it generates OC code, still goes through the Runtime process, which means there must be a pointer to ISA, and ISA comes from NSObject.
-
Swift calls OC, and it still goes runtime. Even with @objcMembers, calls between Swift codes are virtual tables.
-
If class members (functions) in Swift must be implemented using OC runtime, @objc can be replaced with dynamic
-
For more articles in this series, please pay attention to wechat official account [1024 Planet].