mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: clang 插件开发
This commit is contained in:
210
Chapter1 - iOS/1.124.md
Normal file
210
Chapter1 - iOS/1.124.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# 从 OC 到 Swift
|
||||
|
||||
## OC 与 Swift 混编模式下,方法调用原理探究
|
||||
|
||||
OC 与 Swift 混编
|
||||
|
||||
`Person.h`
|
||||
|
||||
```objective-c
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Person : NSObject
|
||||
|
||||
- (instancetype)initWithCat:(id)cat;
|
||||
|
||||
- (void)showPower;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#import "Person.h"
|
||||
#import "TestiOSWithSwift-Swift.h"
|
||||
|
||||
@interface Person()
|
||||
|
||||
@property (nonatomic, strong) Cat *cat;
|
||||
@end
|
||||
@implementation Person
|
||||
|
||||
- (instancetype)initWithCat:(id)cat {
|
||||
if (self = [super init]) {
|
||||
_cat = cat;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)showPower {
|
||||
NSLog(@"I have a cat");
|
||||
[self.cat sayHi];
|
||||
[self.cat run];
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
`Cat.Swift`
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
@objcMembers class Cat: NSObject {
|
||||
var name: String
|
||||
init(_ name: String = "Tom") {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func sayHi () {
|
||||
print("My name is \(name)")
|
||||
}
|
||||
|
||||
func test1(v1: Int) {
|
||||
print("test1")
|
||||
}
|
||||
|
||||
func test2(v1: Int, v2: Int) {
|
||||
print("test2")
|
||||
}
|
||||
|
||||
func test2(_ v1: Double, _ v2: Double) {
|
||||
print("test2 _")
|
||||
}
|
||||
|
||||
func run () {
|
||||
perform(#selector(test1))
|
||||
perform(#selector(test1(v1:)))
|
||||
perform(#selector(test2(v1:v2:)))
|
||||
perform(#selector(test2(_:_:)))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
点击屏幕触发事件,在 `ViewController.swift`
|
||||
|
||||
```swift
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
var cat: Cat = Cat("屁屁")
|
||||
var person: Person = Person(cat: cat)
|
||||
person.showPower()
|
||||
}
|
||||
```
|
||||
|
||||
问题:
|
||||
|
||||
1. 为什么 Swift 暴露给 OC 的类最终要继承自 NSObject?
|
||||
|
||||
因为在 OC 中,方法消息走的是消息传递,也就是 Runtime 的机制,Runtime 的实现依赖于 isa 指针,所以类必须继承自 NSObject。
|
||||
|
||||
2. Swift 代码中调用 OC 对象的方法 `person.showPower() ` 底层是怎么调用的?
|
||||
|
||||
底层实现还是需要用汇编来验证。断点加在 `person.showPower() ` 处
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CallOCMethodInSwiftDemo1.png" style="zoom:25%">
|
||||
|
||||
可以看到即使在 Swift 代码中,调用 OC 对象方法,本质上还是走 Objc Runtime 的一套流程。50行代码,将 showPower 的地址赋值给 `rsi` 寄存器,然后调用 `objc_msgSend` 方法。
|
||||
|
||||
LLDB 下 输入 `si` 窥探下实现。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CallOCMethodInSwiftDemo2.png" style="zoom:25%">
|
||||
|
||||
可以看到一个很大的地址 `0x00007ff80002d7c0` 就是动态库的符号方法地址。同时 Xcode 很智能,右侧给出了函数名称。
|
||||
|
||||
3. OC 调用 Swift 底层又是如何调用的?在 OC 类 Person 中,底层调用 Swift Cat 类的 sayHi 方法。
|
||||
|
||||
断点加在 `[self.cat sayHi]` 处,可以看到本质上还是 Runtime objc_msgSend 那一套。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CallOCMethodInSwiftDemo3.png" style="zoom:25%">
|
||||
|
||||
4. `cat.run()` 底层是怎么调用的?
|
||||
|
||||
如果一个 Swift 类,不继承自 NSObject,那么方法调用的本质就是走虚表那套逻辑,找到指针的前8个字节,根据前8个字节找到类信息,然后在类信息中,前面一些内存地址存储类型信息,后续根据偏移在方法列表中,找到需要调用的函数地址。类似下面的图。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
那 Swift 类继承自 NSObject 后,依然在 Swfit 中调用方法,背后的原理是什么?
|
||||
|
||||
在 ViewController.swift 中 `cat.sayHi()` 下断点
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CallOCMethodInSwiftDemo4.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
## Swift 方法如何走 Runtime 消息机制
|
||||
|
||||
可以看到,即使一个 Swift 类继承自 NSObject,但依旧在 Swift 中调用对象方法,本质上还是走虚表那套方法调用流程,不会走 Runtime 消息机制。
|
||||
|
||||
如果想让 Swift 方法调用走 Runtime 消息机制,可以在方法前加 `@objc dynamic`
|
||||
|
||||
```swift
|
||||
dynamic func sayHi () {
|
||||
print("My name is \(name)")
|
||||
}
|
||||
```
|
||||
|
||||
断点查看,发现在 Swift 代码中调用同样的 Swift 对象方法,此时走了 Runtime 消息机制。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CallOCMethodInSwiftDemo5.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
## Swift OC 混编,内存布局会改变吗
|
||||
|
||||
如果一个 Swift 类继承自 NSObject,内存布局会改变
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var age: Int = 28
|
||||
var height: Int = 175
|
||||
}
|
||||
let p: Person = Person()
|
||||
print(Mems.memStr(ofRef: p))
|
||||
// console
|
||||
0x0000000100010540 0x0000000000000003 0x000000000000001c 0x00000000000000af
|
||||
```
|
||||
|
||||
可以看到一个 Swift 类,前8个字节用来存放类信息的指针,其次8个字节用来存放引用计数信息,后16个字节用来存放28和175,就是存储属性信息
|
||||
|
||||
调整下:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
class Person: NSObject {
|
||||
var age: Int = 28
|
||||
var height: Int = 175
|
||||
}
|
||||
let p: Person = Person()
|
||||
print(Mems.memStr(ofRef: p))
|
||||
// console
|
||||
0x011d8001000104e9 0x000000000000001c 0x00000000000000af 0x0000000000000000
|
||||
```
|
||||
|
||||
可以看到当 Swift 类继承自 NSObject 后,前8个字节存放的是 isa 指针,其次的16个字节存放存储属性信息,最后的8个字节用来内存对齐。
|
||||
|
||||
|
||||
## 混编
|
||||
### Swift 类如何在 OC 中使用
|
||||
OC 项目,使用 Swift 开发默认会创建 `项目名-Swift.swift` 文件。Swift 类都可以在该文件中找到
|
||||
默认情况下生成的 Swift class 是不可以直接在 OC 中使用的,如果需要访问需要在 class 前加 `@objc`,编译器生成的代码如下:
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassCannotUseInOC1.png" style="zoom:25%">
|
||||
|
||||
class 不仅需要创建对象,还需要访问属性和方法,可以在属性或者方法前加 `@objc`,效果如下
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassCannotUseInOC2.png" style="zoom:25%">
|
||||
|
||||
但这样很麻烦,需要给每个属性、方法添加 `@objc`。有简便方法,可以直接在 class 前加 `@objcMembers`,这样该 class 所有的属性、方法都可以在 OC 中访问
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassCannotUseInOC3.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
### OC 类、对象方法如何在 Swift 中访问
|
||||
要在 Swift 中访问 OC 类,需要创建桥接文件,OC 工程首次创建 Swift 文件时,Xcode 默认创建桥接文件 `项目名-Bridging-Header.h`。如果是手动创建的,则需要配置(在项目的 Build Settings 中,找到 Objective-C Bridging Header 设置项,并指定桥接头文件的路径。确保桥接头文件的路径正确无误,并且文件名和扩展名都正确)。
|
||||
|
||||
在桥接文件中(`项目名-Bridging-Header.h`) 写好需要在 Swift 中使用的 objective-C 类。
|
||||
|
||||
Swift 中不允许访问 objective-c 的方法或者需要换个方法名去调用,该怎么实现?
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/ObjcMethodCannotUseInSwift.png" style="zoom:25%">
|
||||
|
||||
`- (void)showPower NS_SWIFT_NAME(diaplayPower());` oc 对象方法名,在 Swift 中使用时,想换个名字,可以用 `NS_SWIFT_NAME(新的方法名())`
|
||||
`- (void)displayPower NS_SWIFT_UNAVAILABLE("请使用 showPower");` oc 对象方法名,不想在 Swift 使用时,可以加 `NS_SWIFT_UNAVAILABLE(原因)`
|
||||
Reference in New Issue
Block a user