从OC到swift

Posted by sunzhongliang on April 25, 2020

swift当中的标记

MARK
可使用MARK:对代码块进行标记说明
MARK:类似于OC当中的#pragma mark
MARK: - 类似于OC当中的#pragma mark -

class Person: NSObject {

    // MARK: - 属性
    var age = 0
    var weight = 0
    
    // MARK: - 私有方法
    private func run() {}
}


TODO
使用// TODO: 未完成来标记未完成的任务

func test() {
    // TODO: 未完成
}


FIXME
使用// FIXME: 有待修复来标记待修复的事情

func test2() {
    // FIXME: 有待修复
}


warning
使用#warning("undo")来标记待处理的警告

func test() {
    #warning("对用户信息进行保存")    
}


fatalError
使用fatalError来标记未处理的事情,写在方法中可以不用写返回值了

func test() -> Int {
    // 假如test方法待完成,而不想写返回值,此时可以用fatalError来标记,代码可以不用写返回值就能编译通过
    fatalError()
}

条件编译

swfit中常用的条件编译有以下几种:

// 操作系统: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

打印

func log<T>(_ msg: T,
            file: NSString = #file,
            line: Int = #line,
            fn: String = #function) {
    #if DEBUG
    let prefix = "\(file.lastPathComponent)_\(line)\(fn):"
    print(prefix, msg)
    #endif
}

swift调用OC

新建一个桥接文件,文件命名默认格式为:{targetName}-Bridging-Header.h
一般情况下,在XCodeswift项目中第一次新建OCClass时,XCode会自动询问我们是否需要创建桥接文件,这时候可以选择让XCode帮我们创建就行了
桥接文件新建完成后,OC当中需要暴露给swift中的类写在这个桥接文件里面,然后就完成了桥接可以在swift中调用了

.h文件声明

.m文件声明

桥接文件

调用

@_silgen_name
如果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))
print(sum(10, 20)) 

OC调用swift

OC调用swift同样需要一个桥接文件,文件名格式为:{targetName}-Swift.h
同样,在项目当中创建swift类时,xcode会自动询问是否需要创建桥接文件

swift类想要暴露给OC的话:

  • 必须要继承自NSObject
  • 使用@objc需要暴露给OC的成员(适合暴露的成员)
  • 使用objcMembers修饰类(适合全部暴露,只需要修饰类即可,类中的方法、属性都会暴露)


重命名
可以通过@objc重命名swift暴露给OC的符号名(类名、属性名、函数名)等

// 通过@objc将Person重命名为MyPerson给OC调用
@objc(MyPerson)
// 使用@objcMembers将所有成员导出给OC调用
@objcMembers class Person: NSObject {
    var price: Double
    
    @objc(name) // 重命名为name
    var band:String
    
    init(price: Double, band: String) {
        self.price = price
        self.band = band
    }
    
    @objc(drive) // 将run改为drive
    func run() {
        print("run method")
    }
}

extension Person {
    func test() {
        print(price, band, "test")
    }
}

选择器(Selector)

OC当中的@selector(doSomething)在swift当中依然可以使用, 语法为:#selector(name)定义一个选择器, 必须被@objcMembers或者@objc修饰的方法才可以定义选择器

func run() {
    print("run method")
    perform(#selector(test1))
    perform(#selector(test(v1:)))
    perform(#selector(test1 as (Double, Double) -> Void))
}

swift与OC桥接转换表

swift 是否可以互相转换 OC
String NSString
String NSMutableString
Array NSArray
Array NSMutableArray
Dictionary NSDictionary
Dictionary NSMutableDictionary
Set NSSet
Set NSMutableSet

协议

只能被class继承的协议

此前,我们写一个协议,那么这个协议既可以被结构体遵守、也可以被枚举遵守、也可以被class遵守,比如:

protocol Runnable {
    
}

struct Cat: Runnable {}
class Dog: Runnable {}
enum Monkey: Runnable {}

如果我们希望这个协议只能被class遵守,有以下几种方法:

  • 将协议继承自AnyObject
    protocol Runnable: AnyObject {
            
    }
    class Dog: Runnable {}
    
  • 在协议前面加上@objc
    @objc protocol Runnable {
            
    }
    class Dog: Runnable {}
    

objc修饰的协议,还可以交给OC去遵守

可选协议

协议里面定义的方法,如果其他对象遵守了这个协议,那么协议里面的方法默认必须全部实现, 如果想要实现可选协议有以下几种方式:

  • 使用extension(扩展这个协议)
    @objc protocol Runnable {
        func run2()
    }
      
    extension Runnable {
        func run1() {
              
        }
    }
      
    class Dog: Runnable {
        func run2() {
            print("run2")
        }
    }
    
  • 在协议定义的方法前面@objc optional
    @objc protocol Runnable {
        func run2()
        @objc optional func run1()
    }
      
    class Dog: Runnable {
        func run2() {
            print("run2")
        }
    }
    

dynamic

objc dynamic修饰的内容会具有动态性,比如调用方法会走runtime那一套流程,因此可以利用这一特性实现swift的方法交换

class Dog: NSObject {
    @objc dynamic func run2() {
        print("run2")
    }
    
    func test2() {
        
    }
}

var d = Dog()
d.run2()
d.test2()

通过汇编查看调用流程

KVC/KVO

swift也是可以支持KVC/KVO的,但是属性所在的类、监听器最终需要继承自NSObject,需要监听哪个属性,就要用objc dynamic修饰对应的属性

class Dog: 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")
    }
}

class Observer: NSObject {
    override func observeValue(forKeyPath keyPath: String?,
                               of object: Any?,
                               change: [NSKeyValueChangeKey : Any]?,
                               context: UnsafeMutableRawPointer?) {
        print("observerValue", change?[.newKey] as Any)
    }
}
// 调用KVO监听
func test() {
    let p = Dog()
    // observeValue Optional(20)
    p.age = 20;
    p.setValue(225, 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)
        {
            (persion, change) in
            print(change.newValue as Any)
            
        }
    }
    

}
func test () {
    let p = Person()
    // Optional(20)
    p.age = 20
    // Optional(25)
    p.setValue(25, forKey: "age")
}

关联对象(Associated Object)

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

class Person {}

extension Person {
    private static var AGE_KEY: Void?
    
    var age: Int {
        // 小写的self代表实例对象访问;大写的Self代表类型访问,因此使用Self可以访问到AGE_KEY
        // 或者使用Person.AGE_KEY也是可以的
        get {
            (objc_getAssociatedObject(self,
                                      &Self.AGE_KEY) as? Int) ?? 0
        }
        set {
            objc_setAssociatedObject(self,
                                     &Self.AGE_KEY, newValue,
                                     .OBJC_ASSOCIATION_ASSIGN)
        }
    }
}

func test() {
    var p = Person()
    print(p.age)
    p.age = 10
    print(p.age)
}

多线程

通常我们在异步线程中执行某些操作是使用GCD, 在swift当中GCD是这样使用的:

// gcd 主线程
DispatchQueue.main.async {
    
}
// gcd 异步全局并发对列 子线程
DispatchQueue.global().async {
    // gcd 主线程
    DispatchQueue.main.async {
        
    }
}

swift当中有一个DispatchWorkItem,其常用场景:

public struct Asyncs {
    public typealias Task = () -> Void
    
    public static func async(_ task: @escaping Task) {
        _async(task)
    }
    
    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 {
            // 通知main队列做mainTask事情
            item.notify(queue: DispatchQueue.main, execute: main)
        }
    }
}

// 用法
Asyncs.async{
    print(1)
}

Asyncs.async({
    print(1, Thread.current)
}) {
    print(2, Thread.current)
}

多线程-延迟执行

// 延迟3s后执行
let time = DispatchTime.now() + 3
DispatchQueue.main.asyncAfter(deadline: time) {
    print("1")
}

把它放在我们前面刚刚封装好的异步操作类Asyncs当中去:

@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
}

注意,我们封装的asyncDelay方法,都是有返回DispatchWorkItem的,其目的就是为了方便我们取消这个任务

let item = Asyncs.asyncDelay(5) {
    print(1)
}
// 调用DispatchWorkItem的cancel方法取消任务
item.cancel()

多线程-once

dispatch_once方法在swift当中已经被废弃掉了,我们可以利用类型存储属性这个思想来设计:

class ViewController: UIViewController {
    static var age: Int = {
        print(1)
        return 0
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 我们访问age3次, 打印了3次age,但age里面打印的1只会打印一次(因为只会初始化一次)
        print(Self.age)
        print(Self.age)
        print(Self.age)
    }
}

静态的存储属性(也叫做类型存储属性),在整个程序运行过程中,只会初始化一次,因为是全局变量,而且默认是lazy(用到的时候才初始化)

多线程-加锁

我们在设计一套缓存存储系统的时候,通常会这样去设计

import Foundation

public struct Cache {
    // 全局变量,存储缓存数据
    private static var data = [String: Any]()
    
    // 从缓存里面获取数据
    public static func get(_ key: String) -> Any? {
        data[key]
    }
    
    // 从缓存里面存储数据
    public static func set(_ key: String, _ value: Any) -> Void {
        data[key] = value
    }
}

但这样设计看似实现了需求,但其实有个很严重的问题:data存储的缓存数据是个全局变量,程序当中内存只有一份,假设有多线程在同时调用set方法,那么是一件很危险的方式, 这个时候就需要使用多线程加锁技术了

GCD信号量
GCD下有个信号量,可以使用它来控制

public struct Cache {
    // 全局变量,存储缓存数据
    private static var data = [String: Any]()
    // 信号量(后面的参数代表同时可以有几条线程来访问,这里设置为1)
    private static var lock = DispatchSemaphore(value: 1);
    
    // 从缓存里面获取数据
    public static func get(_ key: String) -> Any? {
        data[key]
    }
    
    // 从缓存里面存储数据
    public static func set(_ key: String, _ value: Any) -> Void {
        lock.wait()
        // defer 语句在即将离开当前代码块时执行一系列语句。
        // 不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 return、break 的语句
        defer {
            lock.signal()
        }
        data[key] = value
    }
}

NSLock
也可以使用Foundation下的NSLock来实现

public struct Cache {
    // 全局变量,存储缓存数据
    private static var data = [String: Any]()
    // NSLock
    private static var lock = NSLock()
    
    // 从缓存里面获取数据
    public static func get(_ key: String) -> Any? {
        data[key]
    }
    
    // 从缓存里面存储数据
    public static func set(_ key: String, _ value: Any) -> Void {
        lock.lock()
        // defer 语句在即将离开当前代码块时执行一系列语句。
        // 不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 return、break 的语句
        defer {
            lock.unlock()
        }
        data[key] = value
    }
}

swift当中同样有OC当中存在的各种锁iOS当中的多线程, 在swift下使用更简单

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.