1, the Selector

@selector is an Objective-C keyword that converts and assigns a method to an SEL type, which behaves much like a dynamic function pointer. In Objective-C, a lot of selector is used, from setting a target-action, to bootstrap asking whether to respond to a method, to specifying the method that needs to be called when a notification is received, and so on, it’s all done by selector. So the way you generate a selector in Objective-C is going to look something like this

-(void) callMe { //... } -(void) callMeWithParam:(id)obj { //... } SEL someMethod = @selector(callMe); SEL anotherMethod = @selector(callMeWithParam:); NSSelectorFromString // SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:"); 
Copy the code

In general, a lot of people will choose to use @selector for convenience, but if you’re looking for flexibility, you might prefer to use the version of NSSelectorFromString — because you can dynamically generate strings at run time and call the corresponding method from the method name

There is no @selector in Swift. Instead, starting with Swift 2.2 we use # Selector to get a selector from the code exposed to Objective-C. Similarly, the type of the original SEL in Swift is a structure called Selector

@objc func callMe() {
    //...
}
@objc func callMeWithParam(obj: AnyObject!) {
    //...
}
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam(obj:))  
Copy the code

[Note] Selector is actually a concept of objective-C Runtime. In Swift 4, all Swift methods are invisible in Objective-C by default, so you need to expose them to Objective-C by prefacing them with the @objc keyword

If the method name is unique in the method’s field, we can simply use the method name as #selector. This is easier to write than the full form with a colon in front of it

let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam)
Copy the code

If there are two methods in the same scope with the same name, but with different parameters, we can use them by casting the methods

@objc func commonFunc() {}

@objc func commonFunc(input: Int) -> Int {
    return input
} 

let method1 = #selector(commonFunc as ()->())
let method2 = #selector(commonFunc as (Int)->Int)
Copy the code

2. Dynamic invocation of instance methods

class MyClass {
    func method(number: Int) -> Int {
        return number + 1
    }
}
Copy the code

The most common way to call the method method is to generate an instance of MyClass and call it with.method

let cls = MyClass()
cls.method(number: 1)
Copy the code

We could have done it like this

let f = MyClass.method
let object = MyClass()
let result = f(object)(1)
Copy the code

Let’s look at class F: Alt + click

let f: (MyClass) -> (Int) -> Int
Copy the code

In the case of Type. InstanceMethod, it’s actually just

let f = MyClass.method
Copy the code

It does something similar to the literal conversion below

let f = { (obj: MyClass) in obj.method }
Copy the code

3, singleton

The accepted way of writing singletons in OC

@implementation MyManager
+ (id)sharedManager {
    static MyManager * staticInstance = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        staticInstance = [[self alloc] init];
    });
    return staticInstance;
}
@end 
Copy the code

Using dispatch_once_t in GCD ensures that the code inside is called only once, thus ensuring that the singleton is thread safe

We’ve moved dispatch_once out of Swift, but we have an easier way to write it

class MyManager  {
    static let shared = MyManager()
    private init() {}}Copy the code

Conditional compilation

In C languages, you can use a compiler conditional branch like #if or #ifdef to control which code needs to be compiled and which does not. There is no concept of macro definition in Swift, so we cannot use #ifdef to check if a symbol is macro defined. However, to control the compilation process and content, Swift provides several simple mechanisms for customizing the compilation to your needs.

The first is that the #if set of compile tags still exists, with #elseif and #else optional.

#if <condition>

#elseif <condition>

#else

#endif
Copy the code

However, the condition in these expressions is not arbitrary. Swift has built in several combinations of platforms and architectures to help us compile different code for different platforms, specifically

methods Optional parameters
os() macOS, iOS, tvOS, watchOS, Linux
arch() x86_64, arm, arm64, i386
swift() >= A version

If we were to unify our color apis on iOS and Mac, one possible approach would be conditional compilation with TypeAlias:

#if os(macOS)
    typealias Color = NSColor
#else
    typealias Color = UIColor
#endif 

#if arch(x86_64)
     
#else
     
#endif


# if swift (> = 14.0)
     
#else
     
#endif
Copy the code

Compiling custom symbols

We need to use the same target to complete the two versions of the paid version and the free version of the same app, and we want the paid version to perform the function when clicking a button. If the free version pops up a prompt, we can use a method similar to the following

func someButtonPressed(sender: AnyObject!) {
    #if FREE_VERSION// Pop up a purchase prompt, navigate to the store, etc#else// The actual function#endif
}
Copy the code

Here we use the compilation symbol FREE_VERSION to represent the free version. To make this work, we need to set it in the Build options of the project. In the Build Settings of the project, go to Swift Compiler-Custom Flags, Add -d FREE_VERSION to Other Swift Flags.

5, @ UIApplicationMain

In C language, the program entry is the main function. For an Objective-C iOS app project, when we create a new project, Xcode will prepare a main.m file that contains the main function

int main(int argc, char * argv[])
{
    @autoreleasepool {
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

This method initializes an object of UIApplication or a subclass of it with the third argument and starts receiving events (in this case, passing nil, which means using the default UIApplication). The last parameter specifies the AppDelegate class as the application’s delegate, which is used to receive application life-cycle delegate methods like didFinishLaunching or didEnterBackground. Also, although this method is marked to return an int, it does not actually return. It remains in memory until the user or system forces it to terminate

When we create a Swift iOS app project, we’ll see that none of the files have a main file like objective-C, and there’s no main function. The only thing related to Main is the @UIApplicationMain tag at the top of the default AppDelegate class declaration.

The Swift app also requires the main function, but by default @uiApplicationMain helps us to generate it automatically.

For example, after removing @uiApplicationMain, add a main.swift file to the project and add code like this

UIApplicationMain(Process.argc, Process.unsafeArgv, nil,
    NSStringFromClass(AppDelegate)) 
Copy the code

Now compile and run, and there will be no more errors. Of course, we can also easily do something to control the entire application behavior by replacing the third argument with our own UIApplication subclass. For example, change the contents of main.swift to

UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv)
        .bindMemory(
            to: UnsafeMutablePointer<Int8>.self,
            capacity: Int(CommandLine.argc)),
    NSStringFromClass(MyApplication.self),
    NSStringFromClass(AppDelegate.self)
)

import UIKit
class MyApplication: UIApplication {
    override func sendEvent(_ event: UIEvent) {
        super.sendEvent(event)
        print("Event sent:\(event)")}}let cls = MyClass()
cls.mustProtocolMethod()
cls.mustProtocolMethod1()
Copy the code

This allows us to listen for the event every time it is sent (such as when a button is clicked)

6. Optional protocols and protocol extensions

The protocol in Objective-C has the @optional keyword, and methods decorated with this keyword do not have to be implemented. We can define a series of methods through a protocol, and then selectively implement some of them by the class that implements the protocol. The best examples I think are UITableViewDataSource and UITableViewDelegate. There are two necessary methods in the former

-tableView:numberOfRowsInSection:
-tableView:cellForRowAtIndexPath:  
Copy the code

There are no options in the native Swift Protocol, and all defined methods are mandatory

Protocol MyProtocol {func mustProtocolMethod() // must implement method func mustprotocol1 () // must implement method} class MyClass: MyProtocol { funcmustProtocolMethod() {
        print("MyClass--> Must implement method: mustProtocolMethod")
    }
    
    func mustProtocolMethod1() {
        print("MyClass--> Must implement method: mustProtocolMethod1")}}Copy the code

If we wanted to define the optional protocol methods as objective-C does, we would define both the protocol itself and the optional methods as Objective-C, i.e., at @objc before the protocol definition and before the protocol methods. And unlike @optional in Objective-C, we use the keyword optional without the @ sign to define optional methods

@objc protocol MyProtocol1 {@objc optional func optionalProtocolMethod() // Optional func mustProtocol1 () // Must implement method} class MyClass1: MyProtocol1 { funcmustProtocolMethod1() {
         print("MyClass1--> Must implement method: mustProtocolMethod1")}}let cls1 = MyClass1()
cls1.mustProtocolMethod1()
Copy the code

One of the unavoidable limitations is that protocols modified with @objc can only be implemented by class. That is, struct and enum types cannot be implemented with optional methods or properties

In Swift 2.0, we have another option, which is to use Protocol Extension. We can use extension to give partial default implementations of methods after declaring a protocol. These methods are then implemented as optional in real classes

Protocol MyProtocol2 {func OptionalProtocol1 () // Optional func OptionalProtocol2 () // Optional func Mustprotocol1 () // Must implement method} extension MyProtocol2{funcoptionalProtocolMethod1(){}
    func optionalProtocolMethod2(){}
}
Copy the code

7. Memory management, weak and unowned

Like OC, Swift uses a reference computation-based ARC memory management scheme (for heap space)

ARC has three references in Swift

  • Strong references: By default, references are strong references
  • 2. Weak references (weak) :weakDefining weak references
    • It must be an optional var because ARC automatically sets the weak reference to after instance destructionnil
    • ARC automatically sets weak referencesnilDoes not fire the property viewer
  • 3. Unreferenced reference (unowned) :unownedDefine an ownerless reference
    • No strong references are generated, and the memory address of the instance is still stored after the instance is destroyed (similar to that in OC)unsafe_unretained)
    • A runtime error (wild pointer) is generated when accessing an unowned reference after attempting to destroy it
    • Fatal error: Attempted to read an unowned reference but object 0x10070a460 was already deallocated
class Person {
    func eat() {
    }
    deinit {
        print("Person destroyed")
    }
}

unowned var p = Person()
p.eat()
Copy the code

This code generates a runtime error

A circular reference

Weak and unowned can solve the problem of circular reference, while unowned has less performance consumption than weak

  • It can be set to nil during its lifetime using weak
  • Initialize assignments will not be set to nil after use unowned

Circular references to closures

  • Closure expressions by default generate extra strong references to the outer objects used (retain the outer layer)
class Person {
    var fn:(() -> ())?
    func run() {
        print("run")
    }
    deinit {
        print("Person destroyed")
    }
}
func test() {
    let p = Person()
    p.fn = {
        p.run()
    }
}
test(a)Copy the code

The following code creates a circular reference. To solve this problem, use weak or unowned

func test() {
    let p = Person()
    p.fn = {[weak p] inp? .run() } } functest() {
    let p = Person()
    p.fn = {[unowned p] in
        p.run()
    }
}
Copy the code

If you want to reference self while defining a closure attribute, the closure must be lazy, because self cannot be referenced until the instance is initialized

class Person {
    lazy var fun:(() -> ()) = {
        [weak self] inself? .run() } funcrun() {
        print("run")
    }
    deinit {
        print("Person destroyed")}}Copy the code

The compiler will force you to write self explicitly if you use instance members, attributes, and methods inside the closure FN

[Note] : The compiler’s mandate to explicitly write self may result in circular references, so be careful

If lazy is the result of a closure call, then you don’t have to worry about circular references (because the declaration cycle ends after the closure is called)

class Person {
    var age: Int = 0
    lazy var getAge: Int = {
        self.age
    }()
    deinit {
        print("Person destroyed")}}Copy the code

Value types and reference types

There are two areas in RAM, the stack and the heap. In Swift, the value type is stored on the stack; Reference type, stored in the heap area.

Value Type

Value type, which maintains one copy of data per instance

In Swift, struct, enum, and tuple are typically value types. Int, Double, Float, String, Array, Dictionary, and Set are all structs and value types.

In Swift, the assignment of Value type is Deep Copy, and Value Semantics means that the new object and the source object are independent. When the attributes of the new object are changed, the source object will not be affected, and vice versa.

struct CoordinateStruct { var x: Double var y: Double } var coordA = CoordinateStruct(x: 0, y: Var coordB = coordA coorda.x = 100.0print("coordA.x -> \(coordA.x)")
print("coordB.x -> \(coordB.x)")
Copy the code

Declaring a constant of value type means that the constant is immutable (whether the internal data is var/let).

let coordC = CoordinateStruct(x: 0, y: 0)
Copy the code

In Swift 3.0, you can print the memory address of a value type variable using the withUnsafePointer(to:_:) function so that you can see that the memory address of the two variables is not the same.

withUnsafePointer(to: &coordA) { print("\ [$0)") }
withUnsafePointer(to: &coordB) { print("\ [$0)") }

0x0000000100007670
0x0000000100007680
Copy the code

In Swift, the double equal sign (== &! =) can be used to compare the consistency of the contents stored in variables. If we want our struct type to support this symbol, we must comply with the Equatable protocol.

extension CoordinateStruct: Equatable {
    static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
        return (left.x == right.x && left.y == right.y)
    }
}

ifcoordA ! = coordB {print("coordA ! = coordB")}Copy the code

Reference Type

Reference type, where all instances share a copy of data

In Swift, classes and closures are reference types. The assignment of a Reference type is Shallow Copy, and the Reference Semantics is that the variable name of the new object is different from that of the source object, but the Reference is the same. Therefore, when a new object is used to manipulate its internal data, the internal data of the source object is also affected.

Class Dog {var height = 0.0 var weight = 0.0} var dogA = Dog() var dogB = dogA. Height = 50.0print("dogA.height -> \(dogA.height)")
print("dogB.height -> \(dogB.height)") // doga. height -> 50.0 // dogB. Height -> 50.0Copy the code

In Swift 3.0, you can use the following method to print the memory address pointed to by a reference type variable. As you can see, both variables refer to the same memory space.

print(Unmanaged.passUnretained(dogA).toOpaque())
print(Unmanaged.passUnretained(dogB).toOpaque())

//0x0000000100772ff0
//0x0000000100772ff0
Copy the code

In Swift, the triple equal sign (=== &! ==) can be used to compare whether references of reference types (that is, to memory addresses) are consistent. You can also use the double equal sign (== &! =) is used to compare the contents of variables.

String or NSString

In short: There is no particular need to use String whenever possible, for three reasons

  • 1, althoughStringNSStringIt’s nice to convert to each other, but now all the apis in Cocoa accept and returnStringType. There is no need or need to create trouble for ourselves by converting the string returned from the framework
  • 2, because in SwiftThe String is a structIs better suited to the “immutable” nature of strings than NSObject’s NSString class. In conjunction with constant assignment (LET), this invariance is important in multithreaded programming, in principle freeing programmers from worries about memory access and operation order. In addition, without touching the nsString-specific operations and dynamic nature, using the String method also provides performance gains
  • 3. Because String implements a protocol like Collection, there are some Swift syntax features that only String can use that NSString does not. A typical example isfor... inThe enumeration of

10 and the GCD

Swift and OC are similar in GCD. For convenience, we can simply package the following GCD

typealias Task = (_ cancel : Bool) -> Void

@discardableResult
func delay(_ time: TimeInterval, task: @escaping ()->()) ->  Task? {

    func dispatch_later(block: @escaping ()->()) {
        let t = DispatchTime.now() + time
        DispatchQueue.main.asyncAfter(deadline: t, execute: block)
    }

    var closure: (()->Void)? = task
    var result: Task?

    let delayedClosure: Task = {
        cancel in
        if let internalClosure = closure {
            if (cancel == false) {
                DispatchQueue.main.async(execute: internalClosure)
            }
        }
        closure = nil
        result = nil
    }

    result = delayedClosure
    dispatch_later {
        if let delayedClosure = result {
            delayedClosure(false)}}returnresult; } func cancel(_ task: Task?) { task? (true)}Copy the code

11, introspection

Making a query to an object to determine whether it belongs to a class is called introspection.

In OC an object asks if it belongs to a class. There are two types of common methods

OC method

[obj1 isKindOfClass:[ClassA class]];
[obj2 isMemberOfClass:[ClassB class]];
Copy the code
  • 1.-isKindOfClass:Determine if obj1 is an instance object of ClassA or its subclasses;
  • 2,isMemberOfClass:Evaluates obj2 and returns true if and only if obj2 is of type ClassB

Swift method

class ClassA: NSObject {}
class ClassB: ClassA {}

let obj1 = ClassA()
let obj2 = ClassB()

print(obj1.isKind(of: ClassA.self))
print(obj2.isMember(of: ClassA.self))

//true
//false
Copy the code

For an indeterminate type, we can now use is to determine. Is is functionally equivalent to isKindOfClass and can check whether an object belongs to a certain type or its subtypes. The main difference is that it can be used not only for class types, but also for other types of Swift such as struct or enum types

class ClassA { }
class ClassB: ClassA { }

let obj: AnyObject = ClassB()

if (obj is ClassA) {
    print("Belongs to ClassA")}if (obj is ClassB) {
    print("Belongs to the ClassB")}Copy the code

12, KVO

In Swift KVO is limited to subclasses of NSObject, and we need to do the extra work of marking the objects we want to observe as dynamic and @objc

In versions prior to Swift 4, the simplest example of implementing KVO for a subclass of NSObject looked like this


class MyClass: NSObject {
    @objc dynamic var date = Date()
}

private var myContext = 0
class Class: NSObject {

    var myObject: MyClass!

    override init() {
        super.init()
        myObject = MyClass()
        print("Initialize MyClass, current date: \(myObject.date)")
        myObject.addObserver(self,
            forKeyPath: "date",
            options: .new,
            context: &myContext)

        delay(3) {
            self.myObject.date = Date()
        }
    }

    override func observeValue(forKeyPath keyPath: String? , of object: Any? , change: [NSKeyValueChangeKey : Any]? , context: UnsafeMutableRawPointer?) {if let change = change, context == &myContext {
            if let newDate = change[.newKey] as? Date {
                print("MyClass date changed \(newDate)")}}}}letObj = Class() initializes MyClass, current date: 2020-04-08 07:26:22 +0000 MyClass date changed 2020-04-08 07:26:25 +0000Copy the code

In Swift 4 Apple introduced a new KeyPath expression. Now, for the variable bar: bar in type Foo, the corresponding KeyPath can be written as \ foo.bar

class AnotherClass: NSObject {
    var myObject: MyClass!
    var observation: NSKeyValueObservation?
    override init() {
        super.init()
        myObject = MyClass()
        print("Initialize AnotherClass, current date: \(myObject.date)")

        observation = myObject.observe(\MyClass.date, options: [.new]) { (_, change) in
            if let newDate = change.newValue {
                print("AnotherClass date changed \(newDate)")
            }
        }

        delay(1) { self.myObject.date = Date() }
    }
} 
Copy the code

The benefits of using Swift 4.0 KeyPath are numerous

  • 1. The code for setting up observations and processing observations is put together, making the maintenance of the code much easier;
  • 2. Second, we get type-safe results instead of dictionary values.
  • 3. We no longer need to use context to tell which observations have changed, and using Observations to hold an observer frees us from the hassle of memory management. The lifetime of the observer will end with the release of AnotherClass

There are two obvious problems with using KVO in Swift

  • 1. In Objective-C we can listen on almost any property that satisfies KVC without limitation, and now we need the property to haveThe dynamic and @ objcSometimes we may not be able to modify the source code of the class we want to observe. In this case, a possible solution is to inherit the class and use the properties we want to observeThe dynamic and @ objcrewrite
class MyClass: NSObject {
    var date = Date()
}

class MyChildClass: MyClass {
    @objc dynamic override var date: Date {
        get { return super.date }
        set { super.date = newValue }
    }
}
Copy the code
  • 2. Another big problem is what to do about Swift types that are not NSObject. We can do this with a property viewer

13. Local scope

In C language, we can arbitrarily add pairs of curly braces {} inside the method to limit the scope of code. There are two general benefits to doing this. The first is that temporary variables will become invalid after the scope is exceeded. This not only makes naming within methods easier, but also allows the collection of unwanted references to be carried out in advance, making the code a little more efficient. In addition, inserting parentheses in the right place is also helpful in sorting out methods. For code that is not easy to extract as a separate method, but should be separated from the rest of the current method, using curly braces provides a relatively natural partition of the structure

OC code

- (void)loadView {
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
    {
        UILabel *titleLabel = [[UILabel alloc]
                initWithFrame:CGRectMake(150, 30, 200, 40)];
        titleLabel.textColor = [UIColor redColor];
        titleLabel.text = @"Title";
        [view addSubview:titleLabel];
    }

    {
        UILabel *textLabel = [[UILabel alloc]
                initWithFrame:CGRectMake(150, 80, 200, 40)];
        textLabel.textColor = [UIColor redColor];
        textLabel.text = @"Text";
        [view addSubview:textLabel];
    }

    self.view = view;
}
Copy the code

Swift method

In Swift, using curly braces is not supported because it conflicts with the closure definition. If we want to similarly use local scopes to separate code, a good choice would be to define a global method that takes ()->() as a function, and then execute it

override func loadView() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
        view.backgroundColor = .white

        local {
            let titleLabel = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
            titleLabel.textColor = .red
            titleLabel.text = "Title"
            view.addSubview(titleLabel)
        }

        local {
            let textLabel = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
            textLabel.textColor = .red
            textLabel.text = "Text"
            view.addSubview(textLabel)
        }

        self.view = view
    }
Copy the code

We can also do this using anonymous closures

override func loadView() {
        
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
        view.backgroundColor = .white
        
        let titleLabel: UILabel = {
            let label = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
            label.textColor = .red
            label.text = "Title"
            return label
        }()
        view.addSubview(titleLabel)
        
        let textLabel: UILabel = {
            let label = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
            label.textColor = .red
            label.text = "Text"
            return label
        }()
        view.addSubview(textLabel)
        
        self.view = view
    }
Copy the code

14. Associated objects

We often encounter the problem of adding member variables to a classification. For this kind of problem, we are familiar with the writing of OC. For example, add a viewId member variable to UIView

#import <objc/runtime.h>
static const void *RunTimeViewID = @"RunTimeViewID";

@implementation UIView (JHExtension)

- (NSString *)viewID{
    NSString *ID = objc_getAssociatedObject(self, &RunTimeViewID);
    return ID;
}
- (void)setViewID:(NSString *)viewID{
    objc_setAssociatedObject(self, &RunTimeViewID, viewID, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
Copy the code

Swift

This method still works in Swift, though it may be written differently. The two corresponding runtime GET and Set Associated Object apis look like this

func objc_getAssociatedObject(object: AnyObject! , key: UnsafePointer<Void> ) -> AnyObject! func objc_setAssociatedObject(object: AnyObject! , key: UnsafePointer<Void>, value: AnyObject! , policy: objc_AssociationPolicy)Copy the code
struct RunTimeViewKey {
    static let RunTimeViewID = UnsafeRawPointer.init(bitPattern: "RunTimeViewID".hashValue)
}

extension UIView {
    var ViewID: String? {
        set{ objc_setAssociatedObject(self, RunTimeViewKey.RunTimeViewID! , newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) } get {return  objc_getAssociatedObject(self, RunTimeViewKey.RunTimeViewID!) as? String
        }
    }
    
}
Copy the code

15, the Lock

No concurrency, no encoding. When it comes to multi-threading or concurrent code, it can be hard to get around the discussion of locking. Simply put, in order to safely access the same resource in different threads, we need these access sequences to proceed

OC method

- (void)myMethod:(id)anObj {@synchronized(anObj) {Copy the code

Swift method

Synchronized methods are removed from Swift, and what @synchronized does behind the scenes is call objc_sync_enter and objc_sync_exit methods from objc_sync and add some exception checks. So, in Swift, if we ignore those exceptions, if we want to lock a variable

// Define a closure func synchronized(_ lock: AnyObject, closure: () -> ()) { objc_sync_enter(lock) closure() objc_sync_exit(lock) } func myMethodLocked(anObj: AnyObject!) {synchronized(anObj) {// Lock anObj in parentheses}}Copy the code

To give a specific usage example, say we want to implement a thread-safe setter for a class, we can rewrite it like this

class Obj {
    var _str = "123"
    var str: String {
        get {
            return _str
        }
        set{synchronized(self) {_str = newValue}}Copy the code

16. Performance

The biggest change to Swift compared to Objective-C is the optimization of method calls.

OC method call

In Objective-C, all method calls to NSObject are converted to the objc_msgSend method at compile time. This method uses the runtime features of Objective-C to find methods at run time in a distributed manner. Because the types in Objective-C are not determined at compile time, the types we write in our code are just “suggestions” to the compiler, and the cost of this lookup is pretty much the same for any method, right

An equivalent statement of this process might look something like this (note that this is just a statement and has nothing to do with the actual code and how it works)

methodToCall = findMethodInClass(class, selector); // This lookup usually involves traversing the class's method table, which takes some time with methodToCall(); / / callCopy the code

Swift method call

Swift uses more secure and strict types, so if we write code that specifies an actual type (note that we need an actual concrete type, not an abstract protocol like Any), We can assure the compiler that the object must be of the declared type at run time because with more explicit type information, the compiler can create a virtual function table (Vtable), which is an indexed array that holds the location of the method, while processing polymorphism in the type. Instead of distributing and finding methods dynamically, methods can now be retrieved and called directly through the index, which is a real performance improvement. This process is roughly equivalent to:

letMethodToCall = class.vtable[methodIndex] // Use methodIndex to implement methodToCall(); / / callCopy the code

Further, in certain cases, the compiler’s optimization of Swift can even optimize some method calls to be inline. For example, when a method is marked as final, the implementation of that method in VTable is completely fixed because there is no possibility of overwriting it. With such a method, the compiler can, if appropriate, extract the method content into the call at the code generation stage, thus avoiding the call altogether

conclusion

  • 1, the article is read Wang Wei (onevcat). “Swifter-Swift essential Tips (fourth edition) summary
  • 2. The demo address of the code in the article