Swift Project Guide

Time:2020-11-19

Running environment: Xcode 11.1 swift5.0

Recently, I participated in a project that needed to be transferred from Objective-C (hereinafter referred to as OC) to swift. During this period, some pits were encountered, so this summary document was provided.
If you also have the need to make OC project swift, it can be used as a reference.

There is a big premise for OC to swift is that you should have a certain understanding of swift and be familiar with swift syntax. You’d better read the official one completelyLanguage Guide

Since the recognition rate of automation tools is not satisfactory, manual conversion is needed in most cases.

Automation tools

There is a better automation toolSwiftifyIt can convert OC files and even OC projects into swift, and the accuracy rate is claimed to be up to 90%. I tried some of the features in the free version, but I felt that the effect was not ideal, because I had not used the paid version, so it was not good to evaluate it.

Swiftify also has an Xcode plug-inSwiftify for XcodeCan realize the conversion of selected code and single file. This plug-in is quite good, and it is accurate for pure system code conversion, but some code still has some identification problems, which need to be modified manually.

Manual swift

Bridging documents

If you are using swift code for the first time in a project, Xcode will prompt you to add one when you add a swift file.hBridge file for. If you don’t add it carefully, you can import it manually, that is, you can manually generate one.hFile, and then in theBuild Settings > Swift Compiler - General > Objective-C Bridging HeaderFill in the.hThe path to the file.

The function of this bridging file is for swift code to reference OC code or OC’s three-party library.

#import "Utility.h"
#import 
Copy code

stayBridging HeaderThere is also a configuration item belowObjective-C Generated Interface Header NameThe corresponding value isProjectName-Swift.h。 This is a hidden header file automatically generated by Xcode. In each build process, the part declared as external call in swift code will be converted into OC code, and the file in OC part will be similarpchThe header file is referenced globally. Because it is generated in the build process, only.mIt can be directly referenced in the.hReferences in the document are described below.

Appdelegate (program entry)

Not in swiftmain.mDocuments, replaced by@UIApplicationMainCommand, which is equivalent to the original executionmain.m。 So we can putmain.mRemove the file.

System API

aboutUIKitMost of the code conversion in the framework can be converted directly by looking at the system API documents, which will not be discussed here.

Property (property)

Swift didn’tpropertyNeithercopynonatomicAttribute modifier, which only indicates whether the property is variable or notletandvar

Attention point oneA class in OC.hand.mTwo files represent the method, variable, and method variable for internal use only. When migrating to swift, you should.mProperty in is marked asprivateIn other words, the external connection cannot be directly accessed.hThe property in the is not handled, and the default property is selectedinternal, that is, the same module can be accessed.

The same is true for function migration.

Note twoA special case is that in OC projects, some properties are internal(.m)Variable, external(.h)Read only. This situation can be dealt with as follows:

private(set) var value: String
Copy code

It’s just rightvalueOfsetThe method will doprivateMark.

Note 3There is a special symbol for null types in swift?, corresponding to thenil。 There is no such symbol in OC, but you can use thenullableandnonnullIndicates whether the property, method parameter or return value can be null.

If OC does not declare whether an attribute can be empty, it will go to the default valuenonnull

If we want all the properties of a class, the return value of the function isnonnullIn addition to manually adding them one by one, there is a macro command.

NS_ASSUME_NONNULL_BEGIN
/* code */
NS_ASSUME_NONNULL_END
Copy code

This is my IOS development exchange group:519832104No matter you are Xiaobai or Daniel, welcome to settle in. You can share experience, discuss technology, learn and grow together!
Also attached is a large factory interview questions collected by friends. You need IOS development learning materials and interview real questions, which can be obtained by entering the group!

Click here to communicate with IOS Daniel immediately

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 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 code

Swift didn’tNS_OPTIONSInstead of the concept of satisfactionOptionSetAgreedstructType.

Lazy loading

OC Code:

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

Swift Code:

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

closure

OC Code:

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

Swift Code:

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

Single case

OC Code:

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

Swift implements singleton in two ways

The first one

Let shared = xxmanager() // declare in global namespace
Class XXManager { 
}
Copy code

You may wonder, why notdispatch_onceHow to ensure the uniqueness of multi thread creation? In fact, the global variable in swift is lazy loaded and initialized in appdelegate. After that, all calls will use this instance. And the initialization of global variables is used by defaultdispatch_onceThis ensures that the initializer of the global variable will only be called onceshardOfAtomicity

The second kind

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

Swift 2 is starting to increasestaticKeyword, which is used to define the scope of a variable. If not usedstaticSo every one of themsharedWill correspond to an instance. And usestaticAfter that,sharedTo be a global variable is consistent with the principle of the first method above. Notice that the constructor uses theprivateKeyword, so it also guarantees the atomicity of the singleton.

Initialization methods and destructors

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

OC Code:

//Initialization method
@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 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 code

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 code
//Instance function (common method)
func configModel(with model: XXModel) {}
//Instance function (private method)
private func calculateProgress() {}
//Class function (cannot be overridden by subclasses)
static func configModel(with model: XXModel) {}
//Class function (can be overridden by subclasses)
class func configModel(with model: XXModel) {}
//Class function (cannot be overridden by subclasses)
class final func configModel(with model: XXModel) {}
Copy code

OC can declare the method in the.hThe file indicates whether the method is private. Not in swift.hFile, the permission control of methods is carried out through permission keywords, and the permission size of each keyword is as follows:private < fileprivate < internal < public < open

amonginternalIs the default permission, can be in the samemoduleNext visit.

Nsnotification

OC Code:

// add observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method) name:@"NotificationName" object:nil];
// post
[NSNotificationCenter.defaultCenter postNotificationName:@"NotificationName" object:nil];
Copy 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 code

Notice that the notification center in swiftNotificationCenterNoNSPrefix, notification name changed from string toNSNotification.NameThe structure of.

The purpose of changing to structure is to facilitate the management of strings. The original string type has been changed to specifiedNSNotification.NameType. The above swift code can be modified as follows:

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 code

Protocol

OC Code:

@protocol XXManagerDelegate 
- (void)downloadFileFailed:(NSError *)error;
@optional
- (void)downloadFileComplete;
@end

@interface XXManager: NSObject
@property (nonatomic, weak) id delegate;  
@end
Copy code

SwiftprotocolIt’s not just thatclassObject,structandenumCan also implement the protocol. It should be noted thatstructandenumIs a reference type and cannot be usedweakmodification. Can be used only if the current proxy supports only class objectsweak。 To convert the above code into the corresponding swift code, that is:

@objc protocol XXManagerDelegate {
    func downloadFailFailed(error: Error)
    @Objc optional func downloadfilecomplete() // implementation of Optional Protocol
}
class XXManager: NSObject {
	weak var delegate: XXManagerDelegate?  
}
Copy code

@objcIs to indicate that the current code is forNSObjectObject, that isclassYou can use the weak object.

If it is not a delegate for the nsobject object, but just a normal class object, you can set the proxy as follows:

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

It is worth noting that only@objcMarkedprotocolhave access to@optional

Notes of swift and OC Hybrid Editing

Change of function name

If you define a delegate method in a swift class:

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

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

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

This is mainly because Swift has a specified parameter tag, but OC does not. When the OC method name is generated from the swift method name, the compiler will automatically add some modifiers to make the function “smooth” as a sentence.

Call the swift class in the OC header file

If you want to reference the swift class in the OC header file, because swift does not have a header file, in order to make the swift class recognized in the header file, you need to use the@classThe method is introduced.

@class SwiftClass;

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

Rename OC class under swift call

Because Swift has a namespace for different modules, the swift class does not need to be prefixed. If there is an OC public component with prefix, it is not elegant to have to specify prefix when calling in swift environment, so Apple added a macro commandNS_SWIFT_NAMETo allow the OC class to be renamed in swift environment:

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

So we willXXLoginManagerIn swift environment, the class name is changed toLoginManager

Reference types and value types

  • structAndenumIs value type, classclassIs a reference type.
  • StringArrayAndDictionaryThey are structures, so the assignment is directly a copy, andNSStringNSArrayandNSDictionaryIt’s a class, so it’s a way to use references.
  • structCompareclassMore lightweight,structIn the stack,classAllocated in the heap.

ID type and anyobject

OCidType is automatically converted toAnyObjectThey are very similar, but they are not identical in concept. Another concept in swift isAnyThe differences among them are as follows:

  • idIs a general object type, which can point to objects belonging to any class. In OC, it can represent all objects inherited fromNSObjectObject.
  • AnyObjectCan represent anythingclassAn instance of type.
  • AnyCan represent any type, even includefuncType.

From the range size comparison, it is:id < AnyObject < Any

Other grammatical differences and precautions (to be added)

1. Semicolons are not required in swift statements;

2. The bool type is more strict. Swift is no longer a non-0 in OC, which is true. True and false only correspond to each othertrueandfalse

3. In general, there is no need to write in swift classselfBut it needs to be written in the closure.

4. Swift is a strongly typed language, and you must specify an explicit type. In swiftIntandFloatThey can’t be operated directly. They must be converted to the same type.

5. Swift abandoned the traditional++--Operation, abandon the traditional C language typeforCirculation, instead offor-in

6. Swift’sswitchOperations, which do not need to be added at the end of each case statementbreak

7. Swift rightenumIt can support any type, while OC enumeration only supportsIntType. If you want to write compatible code, select int type enumeration.

8. To be called by OC, swift code needs to be preceded by property and method names@objc

9. Swift’s unique features, such as generics,struct, non int typeenumAnd so on are included in the function parameter, even if added@objcIt will not be passed by the compiler.

10. Swift supports overloading, OC does not.

11. The swift function with default value will be expanded automatically when it is called by OC again.

Grammar check

There are many details about the syntax changes after OC to swift. Especially for those who use swift for the first time, it is easy to miss or wait for OC’s idea to write code. A framework for syntax checking is recommended hereSwiftLintCan automatically check whether our code conforms to the swift specification.

It can be done throughcocoapodsAfter configuration, each timeBuildThe lint script will perform the syntax check operation of swift code once. Lint will also grade the code specifications. Serious code errors will directly report errors, which will cause the program to fail to start. If not, code warnings will be displayed( A kind of ️)。

If you feel that swiftlint is a little too strict, you can also modify it.swiftlint.ymlFile, custom belongs to their own syntax specification.

Source: Zhang ferry