Running environment: Xcode 11.1 Swift5.0

I recently worked on a project that required me to switch from Objective-C (OC) to Swift, and I ran into some craters, which led to this summary document. If you also have a need to Swift the OC project, this can be used as a reference.

A major prerequisite for transferring from OC to Swift is that you should have a certain understanding of Swift and be familiar with Swift syntax. It is better to read the official Language Guide completely.

The conversion process is divided into automated and manual translation, and since the recognition rate of automated tools is not satisfactory, manual translation is required in most cases.

Automation tool

There is a good automation tool Swiftify, which can convert OC files or even the entire OC project into Swift, boasting 90% accuracy. I tried some of the features in the free version, but it didn’t work as well, and since I haven’t used the paid version, IT’s just not good.

Swiftify also has a plug-in for Xcode, Swiftify for Xcode, which can convert selected codes and single files. This plug-in is quite good, the pure system code conversion is still accurate, but some code still has some identification problems, need to be manually modified.

Swift change manually

The bridge file

If you are using Swift for the first time in a project, Xcode will prompt you to add a.h bridge file when adding a Swift file. If you do not click add by mistake, you can also import manually. You can manually generate a. H file by yourself, and then fill in the path of the. H file in Build Settings > Swift Compiler-general > Objective-C Bridging Header.

The purpose of this bridge file is for the Swift code to reference the OC code, or the OC tripartite library.

#import "Utility.h"
#import <Masonry/Masonry.h>
Copy the code

There is another configuration item below the Bridging Header, objective-c Generated Interface Header Name, which corresponds to projectname-swift.h. This is a hidden header file automatically generated by Xcode. Each Build process will convert the part of Swift code declared as an external call into OC code. The OC part of the file will reference this header globally just like PCH. Since it is generated during the Build process, only the.m file can be referenced directly. References in the.h file are described later.

Appdelegate(application entry)

There is no main.m file in Swift. Instead, there is the @UIApplicationMain command, which is equivalent to executing main.m. So we can remove the main.m file.

System apis

Most of the code conversions in the UIKit framework can be done directly by looking at the system API documentation, which will not be covered here.

Property (property)

Swift has no property, no modifiers like copy, nonatomic, and so on. It has only lets and var that indicate whether the property is mutable.

Note that a.h and.m file in point 1 OC represent methods, variables, and method variables for internal use only. When migrating to Swift, the property in. M should be marked as private, that is, the external connection cannot be accessed directly. For the property in. H, the default internal property should be taken, that is, the same module can be accessed.

The same is true for function migration.

Note that one special case of point two is in an OC project where some attributes are internally (.m) mutable and externally (.h) read-only. This situation can be handled as follows:

private(set) var value: String
Copy the code

That is, only the set method of value is private.

Notice that there is a special symbol in Swift for null types, right? Which corresponds to nil in OC. OC does not have this symbol, but nullable and nonNULL can be used to indicate whether a method parameter or return value can be null.

If OC does not declare whether an attribute can be null, go to the default value nonnull.

If we want all properties of a class to return nonnull, there is a macro command in addition to manually adding them one by one.

NS_ASSUME_NONNULL_BEGIN
/* code */
NS_ASSUME_NONNULL_END
Copy the code

Enum (enumeration)

OC code:

typedef NS_ENUM(NSInteger, PlayerState) {
    PlayerStateNone = 0,
    PlayerStatePlaying,
    PlayerStatePause,
    PlayerStateBuffer,
    PlayerStateFailed,
};

typedef NS_OPTIONS(NSUInteger, XXViewAnimationOptions) {
    XXViewAnimationOptionNone            = 1 <<  0,
    XXViewAnimationOptionSelcted1      	 = 1 <<  1,
    XXViewAnimationOptionSelcted2      	 = 1 <<  2,
}
Copy the code

Swift code

enum PlayerState: Int {
    case none = 0
    case playing
    case pause
    case buffer
    case failed
}
struct ViewAnimationOptions: OptionSet {
    let rawValue: UInt
    static let None = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected1 = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected2 = ViewAnimationOptions(rawValue: 1 << 2)
    / /...
}
Copy the code

Swift does not have the concept of NS_OPTIONS, but instead has struct types that satisfy the OptionSet protocol.

Lazy loading

OC code:

- (MTObject *)object { if (! _object) { _object = [MTObject new]; } return _object; }Copy the code

Swift code:

lazy var object: MTObject = {
    let object = MTObject(a)return imagobjecteView
}()
Copy the code

closure

OC code:

typedef void (^DownloadStateBlock)(BOOL isComplete);
Copy the code

Swift code:

typealias DownloadStateBlock = ((_ isComplete: Bool) - >Void)
Copy the code

The singleton

OC code:

+ (XXManager *)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
Copy the code

Swift implements singletons in two ways:

The first kind of

let shared = XXManager(a)// Declare in the global namespace
Class XXManager{}Copy the code

So you might be wondering, why is there no dispatch_once, and how do you ensure uniqueness in multithreading? In fact, the global variable in Swift is lazily loaded, initialized in the AppDelegate, and all subsequent calls will use that instance. Global variables are initialized using dispatch_once by default. This ensures that the initializer for global variables is invoked only once, ensuring atomicity of the shard.

The second,

Class XXManager {
		static let shared = XXManager(a)private override init() {
   		// do something }}Copy the code

Swift 2 started adding the static keyword, which is used to limit the scope of variables. If static is not used, there will be one instance for each shared instance. Using static, shared becomes a global variable, which is the same as the first method. Note that because the constructor uses the private keyword, the atomicity of singletons is also guaranteed.

Initialization method and destructor

For the initialization method OC first calls the parent class’s initialization method, and then initializes its own member variables. Swift initializes its own member variables and then calls its parent class’s initialization methods.

OC code:

// Initialize @interface MainView: UIView@Property (nonatomic, strong) NSString *title; - (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title NS_DESIGNATED_INITIALIZER; @end @implementation MainView - (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title { if (self = [super initWithFrame:frame]) { self.title = title; } return self; } @end // destructor - (void)dealloc {//dealloc}Copy the code

When the above class is called

Swift code:

class MainViewSwift: UIView {
    let title: String
    init(frame: CGRect, title: String) {
        self.title = title
        super.init(frame: frame)
    }
    required init? (coder:NSCoder) {
        fatalError("init(coder:) has not been implemented")}deinit {
      //deinit}}Copy the code

A function call

OC code:

// instance function (common method) - (void)configModelWith:(XXModel *)model {} // instance function (private method) - (void)calculateProgress {} // class function + (void)configModelWith:(XXModel *)model {}Copy the code
// Instance function (common method)
func configModel(with model: XXModel) {}
// Instance function (private method)
private func calculateProgress(a) {}
// Class functions (cannot be overridden by subclasses)
static func configModel(with model: XXModel) {}
// Class functions (which can be overridden by subclasses)
class func configModel(with model: XXModel) {}
// Class functions (cannot be overridden by subclasses)
class final func configModel(with model: XXModel) {}
Copy the code

OC can indicate whether a method is private by whether it is declared in a.h file. H file is not available in Swift, and the permission control for methods is carried out by the permission keywords. The size of each keyword is private < fileprivate < internal < public < open

Internal is the default permission and can be accessed from the same module.

NSNotification (notice)

OC code:

// add observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method) name:@"NotificationName" object:nil];
// post
[NSNotificationCenter.defaultCenter postNotificationName:@"NotificationName" object:nil];
Copy the code

Swift code:

// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)
// post
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NotificationName"), object: self)
Copy the code

Notice that the NotificationCenter in Swift is not prefixed with NS, and the notification Name changes from a string to the structure of nsnotification. Name.

The purpose of the struct change is to make it easier to manage strings. The original string type is changed to the specified NSNotification.name type. The Swift code above can be changed to:

extension NSNotification.Name {
	static let NotificationName = NSNotification.Name("NotificationName")}// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: .NotificationName, object: nil)
// post
NotificationCenter.default.post(name: .NotificationName, object: self)
Copy the code

Protocol/proxy

OC code:

@protocol XXManagerDelegate <NSObject>
- (void)downloadFileFailed:(NSError *)error;
@optional
- (void)downloadFileComplete;
@end
  
@interface XXManager: NSObject
@property (nonatomic, weak) id<XXManagerDelegate> delegate;  
@end
Copy the code

Swift has expanded the use of protocol to include not only class objects but also structs and enums. The struct and enum types are not allowed to use weak. Weak can be used only when you specify that the current proxy supports only class objects. Convert the above code to the corresponding Swift code, which is:

@objc protocol XXManagerDelegate {
    func downloadFailFailed(error: Error)
    @objc optional func downloadFileComplete(a) // Optional protocol implementation
}
class XXManager: NSObject {
	weak var delegate: XXManagerDelegate?  
}
Copy the code

@objc means that the current code is for An NSObject, which is a class object, and you can use Weak.

If it’s not an NSObject delegate, just a normal class object, you can set the delegate like this:

protocol XXManagerDelegate: class {
    func downloadFailFailed(error: Error)
}
class XXManager {
	weak var delegate: XXManagerDelegate?
}
Copy the code

Note that only @objc marked protocols can be used with @optional.

Notes for mixing Swift and OC

Function name change

If you define a delegate method in a Swift class:

@objc protocol MarkButtonDelegate {
    func clickBtn(title: String)
}
Copy the code

If you want to implement this protocol in OC, the method name becomes:

- (void)clickBtnWithTitle:(NSString *)title {
	// code
}
Copy the code

This is mainly because Swift has specified parameter tags, but OC does not, so the compiler automatically adds modifiers when generating the OC method name from the Swift method name to make the function “smooth” as a sentence.

Call the Swift class in the OC header file

If we want to reference Swift in OC’s header file, Swift has no header file. In order to recognize Swift in the header file, we need to introduce it through the @class method.

@class SwiftClass;

@interface XXOCClass: NSObject
@property (nonatomic.strong) SwiftClass *object;
@end
Copy the code

Rename the OC class under the Swift call

Since Swift has namespaces for different Modules, no Swift class needs to be prefixed. If there is a common OC component with a prefix, it is not elegant to have to specify the prefix when called in the Swift environment, so Apple has added a macro NS_SWIFT_NAME that allows renaming of the OC class in the Swift environment:

NS_SWIFT_NAME(LoginManager)
@interface XXLoginManager: NSObject
@end
Copy the code

So we change the class name of XXLoginManager in Swift to LoginManager.

Reference types and value types

  • structenumIt’s a value type, classclassIs a reference type.
  • String.ArrayandDictionaryAre constructs, so assignment is a copy, andNSString.NSArrayNSDictionaryIs a class, so it uses a reference.
  • structclassMore “lightweight,”structIn the stack,classAllocated in the heap.

Id type and AnyObject

The ID type in OC is automatically converted to AnyObject when called by Swift. They are similar, but the concept is not the same. Swift has another concept called Any. The difference between the three is:

  • idIs a generic object type that can point to objects belonging to any class. In OC, it can represent all inherited objectsNSObjectThe object.
  • AnyObjectIt can represent anythingclassType.
  • AnyCan represent any type, even includingfuncType.

From the scope size comparison: ID < AnyObject < Any.

Other Grammar differences and Notes (to be added)

1, Swift statement does not need to add semicolon; .

2. With respect to the Bool type, Swift is no longer true as in OC. True and false only correspond to true and false.

3. You don’t normally need to write self inside a Swift class, but you do inside a closure.

Swift is a strongly typed language, and you must specify an explicit type. In Swift Int and Float cannot be evaluated directly, they must be converted to the same type.

5, Swift abandons the traditional ++, — operation, and abandons the traditional C language for loop writing, instead of for-in.

6. Swift switch does not need to add break at the end of each case statement.

7. Swift extends the use of enums to any type, while OC enumerations only support ints. If you want to write compatible code, select an Int enumeration.

8. For Swift code to be called by OC, add @objc before the property and method names.

9. Features unique to Swift, such as generics, structs, non-int enums, etc., are included in function arguments. Adding @objc will not pass the compiler.

10. Swift supports overloading, OC does not.

11. Swift functions with default values are automatically expanded when called by OC.

Syntax checking

There are a lot of details about the syntax change after OC to Swift, especially for students who use Swift for the first time, it is easy to miss or wait for OC to write the code. SwiftLint is a recommended syntax-checking framework that automatically checks our code for compliance with the Swift specification.

This can be imported via cocoapods. Once configured, the Lint script will perform a syntax check for the Swift code each Build. Lint will also level the code specification so that serious code errors will be reported directly, causing the program to fail to start, and less serious code errors will be displayed as code warnings (⚠️).

If you feel that SwiftLint is a little too strict, you can customize your own syntax by modifying the.swiftLint. yml file.