Swift 之 OC 到 Swift

1、Xcode 使用

1、常用标签

// MARK: 类似于OC中的 #pragma mark
/ MARK: - 类似于OC中的 #pragma mark -
// TODO: 用于标记未完成的任务
// FIXME: 用于标记待修复的问题
//#warning("XXX"):提示要做的事情

2、条件编译
// 操作系统:macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD
#if os(macOS) || os(iOS)
// CPU架构:i386\x86_64\arm\arm64
#elseif arch(x86_64) || arch(arm64)
// swift版本
#elseif swift(<5) && swift(>=3)
// 模拟器
#elseif targetEnvironment(simulator)
// 可以导入某模块
#elseif canImport(Foundation)
#else
#endif

编译标记
1、系统自带标记

系统自带编译标记

//系统自带编译标记使用
//其意思为:在 DEBUG 模式下和非 DEBUG 模式下编译不同代码

#if DEBUG
// debug模式
#else
// release模式
#endif

2、自定义编译器标记

//1、修改系统自带编译标记使用
//将 DEBUG 修改为其他名字,如:DEBUG TEST
#if TEST
print("test")
#endif

//2、在 Other Swift Flages 的 Debug 中添加标记,如:-D OTHER。(格式为 -D XXX)
#if OTHER
print("other")
#endif
3、打印
func log(
            _ msg: T, 
            file: NSString = #file, 
            line: Int = #line, 
            fn: String = #function) {
    #if DEBUG
    let prefix = "\(file.lastPathComponent)_\(line)_\(fn):"
    print(prefix, msg)
    #endif
}
4、系统版本检测
if #available(iOS 10, macOS 10.12, *) {
    // 对于iOS平台,只在iOS10及以上版本执行
    // 对于macOS平台,只在macOS 10.12及以上版本执行
    // 最后的*表示在其他所有平台都执行
}
5、API 可用性说明
@available(iOS 10, macOS 10.15, *)
class Person {}
struct Student {
    @available(*, unavailable, renamed: "study")
    func study_() {}
    func study() {}
    @available(iOS, deprecated: 11)
    @available(macOS, deprecated: 10.12)
func run() {}
}
6、程序的入口

在 AppDelegate 上面默认有个 @main 标记,这表示编译器自动生成入口代码(main 函数代码),自动设置 AppDelegate 为 APP 的代理。也可以删掉 @main,自定义入口代码:新建一个 main.swift 文件。
自定义入口代码
1、注释掉 Appdelegate.swift 源码中的 @main
2、创建 main.swift 文件,在文件中添加以下代码

import UIKit

class MyApplication: UIApplication{}

UIApplicationMain(CommandLine.argc, 
                  CommandLine.unsafeArgv, 
                  NSStringFromClass(MyApplication.self), 
                  NSStringFromClass(AppDelegate.self))

2、Swift 调用 OC

1、添加桥接文件

桥接文件创建
1、第一次添加 OC 代码时,会自动提示生成桥接文件,确认生成。
2、手动添加桥接文件:Build Settings --> ALL --> Combined --> 搜索 bridging --> Objective-C bridging Header

设置桥接文件名
添加 {项目名称}-Bridging-Header.h(如:{Test_Swift}-Bridging-Header.h)。在桥接文件中添加 OC 文件(如:#import "TestOne.h")

2、函数名冲突

如果 C 语言暴露给 Swift 的函数名跟 Swift 中的其他函数名冲突了,可以在 Swift 中使用 @_silgen_name 修改 C 函数名。

// C语言
int sum(int a, int b) {
    return a + b;
}
// Swift
@_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
print(swift_sum(10, 20)) // 30
print(sum(10, 20)) // 30

3、OC 调用 Swift

1、添加桥接文件

Xcode 默认生成一个用于 OC 调用 Swift 的头文件,文件名格式是: {targetName}-Swift.h。
查看桥接文件:Build Settings --> ALL --> Combined --> 搜索 generated interface --> {targetName}-Swift.h
使用:在 OC 代码中引入 #import "{项目名称}-Swift.h"(如:#import "test-Swift.h")即可在 OC 代码中使用继承自 NSObject 的类。

2、注意事项

1、Swift 暴露给 OC 的类最终继承自 NSObject。
2、使用 @objc 修饰需要单独暴露给 OC 的成员或者方法。
3、使用 @objcMembers 修饰类,代表默认所有成员都会暴露给 OC(包括扩展中定义的成员),最终是否成功暴露,还需要考虑成员自身的访问级别。
4、可以通过 @objc 重命名 Swift 暴露给 OC 的符号名(类名、属性名、函数名等)。

@objc(MJCar)
@objcMembers class Car: NSObject {
    var price: Double
    @objc(name)
    var band: String
    init(price: Double, band: String) {
        self.price = price
        self.band = band
    }
    @objc(drive)
    func run() { print(price, band, "run") }
    static func run() { print("Car run") }
}
extension Car {
    @objc(exec:v2:)
    func test() { print(price, band, "test") }
}

//说明:将类名 Car 改为 MJCar;将属性名 band 改为 name;将方法名 run 改为 drive;将扩展中的方法名 test 改为 exec:v2: 。
MJCar *c = [[MJCar alloc] initWithPrice:10.5 band:@"BMW"];
c.name = @"Bently";
c.price = 108.5;
[c drive]; // 108.5 Bently run
[c exec:10 v2:20]; // 108.5 Bently test
[MJCar run]; // Car run

4、OC 和 Swift 异同

1、选择器 Selector

Swift 中依然可以使用选择器,使用 #selector(name) 定义一个选择器,必须是被 @objcMembers 或 @objc 修饰的方法才可以定义选择器。

@objcMembers class Person: NSObject { 
    func test1(v1: Int) { print("test1") } 
    func test2(v1: Int, v2: Int) { print("test2(v1:v2:)") } 
    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(_:_:))) 
        perform(#selector(test2 as (Double, Double) -> Void)) 
    } 
}
2、String
// 常用方法
var emptyStr1 = ""
var emptyStr2 = String()

var str = "123456"
print(str.hasPrefix("123")) // true
print(str.hasSuffix("456")) // true

var str: String = "1"
str.append("_2")
str = str + "_3"
str += "_4"
str = "\(str)_5"
print(str.count)

var str = "1_2"
str.insert("_", at: str.endIndex) // 1_2_
str.insert(contentsOf: "3_4", at: str.endIndex) // 1_2_3_4
str.insert(contentsOf: "666", at: str.index(after: str.startIndex)) // 1666_2_3_4
str.insert(contentsOf: "888", at: str.index(before: str.endIndex)) // 1666_2_3_8884
str.insert(contentsOf: "hello", at: str.index(str.startIndex, offsetBy: 4)) // 1666hello_2_3_8884
str.remove(at: str.firstIndex(of: "1")!) // 666hello_2_3_8884
str.removeAll { $0 == "6" } // hello_2_3_8884
var range = str.index(str.endIndex, offsetBy: -4)..

Substring
String 可以通过下标、 prefix、 suffix 等截取子串,子串类型不是 String,而是 Substring。

var str = "1_2_3_4_5"
var substr1 = str.prefix(3) // 1_2
var substr2 = str.suffix(3) // 4_5
var range = str.startIndex.. String
var str2 = String(substr3)

注:Substring 和它的源 String,共享字符串数据。Substring 发生修改 或者 转为 String 时,会分配新的内存存储字符串数据。

Character

for c in "jack" { // c是Character类型
    print(c)
}
var str = "jack"
// c是Character类型
var c = str[str.startIndex]

String 相关的协议
1、BidirectionalCollection 协议包含的部分内容:startIndex 、 endIndex 属性、index 方法。String、Array 都遵守了这个协议。
2、RangeReplaceableCollection 协议包含的部分内容:append、insert、remove 方法。String、Array 都遵守了这个协议。
3、Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议。

多行 String

let str = """
1
        "2"
3
        '4'
"""
// 如果要显示3引号,至少转义1个引号
let str = """
Escaping the first quote \"""
Escaping two quotes \"\""
Escaping all three quotes \"\"\"
"""

String 与 NSString
String 与 NSString 之间可以随时随地桥接转换,如果觉得 String 的 API 过于复杂难用,可以考虑将 String 转为 NSString。

var str1: String = "jack"
var str2: NSString = "rose"
var str3 = str1 as NSString
var str4 = str2 as String

var str5 = str3.substring(with: NSRange(location: 0, length: 2))
print(str5) // ja

比较字符串内容是否等价
String 使用 == 运算符。NSString 使用 isEqual 方法,也可以使用 == 运算符(本质还是调用了 isEqual 方法)。

3、Swift、OC 桥接转换表

String ⇌ NSString
String ← NSMutableString
Array ⇌ NSArray
Array ← NSMutableArray
Dictionary ⇌ NSDictionary
Dictionary ← NSMutableDictionary
Set ⇌ NSSet
Set ← NSMutableSet

4、只能被 class 继承的协议
protocol Runnable1: AnyObject {}
protocol Runnable2: class {}
@objc protocol Runnable3 {}  //被 @objc 修饰的协议,还可以暴露给 OC 去遵守实现
5、可选协议

1、可以通过扩展实现可选协议

protocol TestProtocol{
    func test()
}
extension OptionalProtol{
    func test(){}
}

2、可以通过 @objc 定义可选协议,这种协议只能被 class 遵守

@objc protocol Runnable {
    func run1()
    @objc optional func run2()
    func run3()
}
class Dog: Runnable {
    func run3() { print("Dog run3") }
    func run1() { print("Dog run1") }
}
var d = Dog()
d.run1() // Dog run1
d.run3() // Dog run3
6、dynamic

被 @objc dynamic 修饰的内容会具有动态性(比如调用方法会走 runtime 那一套流程)。

7、KVC\KVO

Swift 支持 KVC \ KVO 的条件:
1、属性所在的类、监听器最终继承自 NSObject。
2、用 @objc dynamic 修饰对应的属性。

class Person: NSObject {
    @objc dynamic var age: Int = 0
    var observer: Observer = Observer()
    override init() {
        super.init()
        self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
}
    deinit {
        self.removeObserver(observer, forKeyPath: "age")
}
}
var p = Person()
// observeValue Optional(20)
p.age = 20
// observeValue Optional(25)
p.setValue(25, forKey: "age")

block 方式的 KVO

class Person: NSObject {
    @objc dynamic var age: Int = 0
    var observation: NSKeyValueObservation?
    override init() {
        super.init()
        observation = observe(\Person.age, options: .new) { (person, change) in
            print(change.newValue as Any)
        }
    }
}
var p = Person()
// Optional(20)
p.age = 20
// Optional(25)
p.setValue(25, forKey: "age")
8、关联对象(Associated Object)

在 Swift 中,class 依然可以使用关联对象,默认情况,extension 不可以增加存储属性,借助关联对象,可以实现类似 extension 为 class 增加存储属性的效果。

class Person {}
extension Person {
    private static var AGE_KEY: Void?
    var age: Int {
        get {
            (objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
        }
    }
}
var p = Person()
print(p.age) // 0
p.age = 10
print(p.age) // 10
9、资源名管理

方案一:
1、添加扩展

extension UIImage {
    convenience init?(_ name: R.image) {
        self.init(named: name.rawValue)
    }
}
extension UIViewController {
    func performSegue(withIdentifier identifier: R.segue, sender: Any?) {
        performSegue(withIdentifier: identifier.rawValue, sender: sender)
    }
}
extension UIButton {
    func setTitle(_ title: R.string, for state: UIControl.State) {
        setTitle(title.rawValue, for: state)
    }
}

2、配置文件

enum R {
    enum string: String {
        case add = "添加"
    }
    enum image: String {
        case logo
    }
    enum segue: String {
        case login_main
    }
}

3、使用

let img = UIImage(named: "logo")
let btn = UIButton(type: .custom)
btn.setTitle("添加", for: .normal)
performSegue(withIdentifier: "login_main", sender: self)
let img = UIImage(R.image.logo)
let btn = UIButton(type: .custom)
btn.setTitle(R.string.add, for: .normal)
performSegue(withIdentifier: R.segue.login_main, sender: self)

方案二:
1、添加配置文件

enum R {
    enum image {
        static var logo = UIImage(named: "logo")
    }
    enum font {
        static func arial(_ size: CGFloat) -> UIFont? {
            UIFont(name: "Arial", size: size)
        }
    }
}

2、使用

//示例
let img = UIImage(named: "logo")
let font = UIFont(name: "Arial", size: 14)
//使用
let img = R.image.logo
let font = R.font.arial(14)
10、多线程

异步

public typealias Task = () -> Void
public static func async(_ task: @escaping Task) {
    _async(task)
}
// task:子线程任务;mainTask:主线程任务
public static func async(_ task: @escaping Task, _ mainTask: @escaping Task) {
    _async(task, mainTask)
}
private static func _async(_ task: @escaping Task, _ mainTask: Task? = nil) {
    let item = DispatchWorkItem(block: task)
    DispatchQueue.global().async(execute: item)  //子线程执行的任务
    if let main = mainTask {
        item.notify(queue: DispatchQueue.main, execute: main)  //主线程执行的任务
    }
}

延迟

@discardableResult
public static func delay(_ seconds: Double, _ block: @escaping Task) -> DispatchWorkItem {
    let item = DispatchWorkItem(block: block)
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
    return item
}

异步延迟

@discardableResult
public static func asyncDelay(_ seconds: Double, _ task: @escaping Task) -> DispatchWorkItem {
    return _asyncDelay(seconds, task)
}
@discardableResult
public static func asyncDelay(_ seconds: Double,_ task: @escaping Task, _ mainTask: @escaping Task) -> DispatchWorkItem {
    return _asyncDelay(seconds, task, mainTask)
}
private static func _asyncDelay(_ seconds: Double, _ task: @escaping Task, _ mainTask: Task? = nil) -> DispatchWorkItem {
    let item = DispatchWorkItem(block: task)
    DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
    if let main = mainTask {
        item.notify(queue: DispatchQueue.main, execute: main)
    }
    return item
}

once
dispatch_once 在 Swift 中已被废弃,取而代之可以用类型属性或者全局变量\常量,默认自带 lazy + dispatch_once 效果。

fileprivate let initTask2: Void = {
    print("initTask2---------")
}()
class ViewController: UIViewController {
    static let initTask1: Void = {
        print("initTask1---------")
    }()
    override func viewDidLoad() {
        super.viewDidLoad()
        let _ = Self.initTask1
        let _ = initTask2
    }
}

加锁

class Cache {
    private static var data = [String: Any]()
    private static var lock = DispatchSemaphore(value: 1)
    static func set(_ key: String, _ value: Any) {
        lock.wait()
        defer { lock.signal() }
        data[key] = value
    }
}
private static var lock = NSLock()
static func set(_ key: String, _ value: Any) {
    lock.lock()
    defer { lock.unlock() }
}
private static var lock = NSRecursiveLock()
static func set(_ key: String, _ value: Any) {
    lock.lock()
    defer { lock.unlock() }
}

问与答

1、为什么 swift 中给 OC 使用的类都要继承自 NSObject?

因为在 OC 中使用的时候要使用 NSObject 相关初始化方法、isa 及 Runttime 调用方式。

2、OC 给 Swift 使用的方法,在 Swift 源码中调用的时候在底层是怎么调用的?Swift 给 OC 使用的方法,在 OC 源码中调用的时候在底层是怎么调用的?Swift 给 OC 使用的方法,在 Swift 源码中使用的时候底层又是怎么调用的?

1、纯 Swift 源码的调用方式为虚函数表,纯 OC 的源码的调用方式为 Runtime 调用流程。
2、OC 给 Swift 使用的方法,在 Swift 源码中调用时,底层仍然按 OC 的方式调用方法。
3、Swift 方法在 OC 源码中调用底层仍然按 OC 的方式调用方法。
4、Swift 暴露给 OC 使用的方法,在 Swift 源码中调用底层按虚函数表方式调用。
5、在 Swift 方法中调用 OC 代码,Swift 方法调用方式为虚函数表,Swift 方法中的 OC 代码片段调用方式按 OC 的方式调用方法。
6、如果希望 Swift 方法按 OC 方法的 Runtime 流程方式调用,可以在 Swift 方法前加上 dynamic。
总结:调用 OC 源码方法或者在 OC 源码中调用方法,底层均是按 Runtime 的方式执行。纯 Swift 源码调用方式为虚函数表,无论方法是否暴露给 OC 使用。

3、对比以下代码的内存结构
class person{
    var age = 10
    var weight = 20
}

//解析:
第一个 8 个字节存放地址,指向 metadata 数据。
第二个 8 个字节存放引用计数。
第三个 8 个字节存放 age。
第四个 8 个字节存放 weight。

class person: NSObject{
    var age = 10
    var weight = 20
}
//解析:
第一个 8 个字节存放 isa 指针。
第二个 8 个字节存放 age。
第三个 8 个字节存放 weight。

你可能感兴趣的:(Swift 之 OC 到 Swift)