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
) :weak
Defining weak references- It must be an optional var because ARC automatically sets the weak reference to after instance destruction
nil
- ARC automatically sets weak references
nil
Does not fire the property viewer
- It must be an optional var because ARC automatically sets the weak reference to after instance destruction
- 3. Unreferenced reference (
unowned
) :unowned
Define 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
- 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)
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, although
String
和NSString
It’s nice to convert to each other, but now all the apis in Cocoa accept and returnString
Type. There is no need or need to create trouble for ourselves by converting the string returned from the framework - 2, because in Swift
The String is a struct
Is 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 is
for... in
The 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 have
The dynamic and @ objc
Sometimes 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 @ objc
rewrite
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