Swift第十一章 从OC到Swift

MARK、TODO、FIXME

  1. // MARK: 类似于OC中的 #pragma mark
  2. // MARK: - 类似于OC中的 #pragma mark -
  3. // TODO: 用于标记未完成的任务
  4. // FIXME: 用于标记待修复的问题
  5. #warning("xxx") 警告

条件编译

  1. 系统自带条件编译

     // 操作系统: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
    
  2. 自定义条件编译

    1.png

     // debug模式
     #if DEBUG
     // release模式
     #else
     #endif
        
     //在Active compliation Conditions ->Debug  自定义内容:DEBUG TEST
     #if TEST
     print("test")
     #endif
        
     //Other Swift Flags ->Debug 自定义内容:-D OTHER
     #if OTHER
     print("other")
     #endif
    

打印

  1. 由于Swift中没有宏定义,那如果需要实现dubug打印、release不打印怎么办?
  2. 新建一个Log.swift文件

     func log<T>(_ msg: T,
     //#file当前执行所在环境的文件,作为参数的目的就是确保文件的路径是当前方法调用者的文件路径
     file: NSString = #file,
     //#line当前执行所在的行
     line: Int = #line,
     // #function当前执行所在的方法
     fn: String = #function) {
         //debug模式下打印
         #if DEBUG
         let prefix = "\(file.lastPathComponent)_\(line)_\(fn):"
         print(prefix, msg)
         //release不打印
         #endif
     }
    

系统版本检测

```
if #available(iOS 10, macOS 10.12, *) {
    // 对于iOS平台,只在iOS10及以上版本执行
    // 对于macOS平台,只在macOS 10.12及以上版本执行
    // 最后的*表示在其他所有平台都执行
}
```

API可用性说明

  1. 示例

     //当前类只支持iOS10
     @available(iOS 10, macOS 10.15, *)
     class Person {}
        
     struct Student {
         @available(*, unavailable, renamed: "study")
         func study_() {}
         func study() {}
         //表示该方法iOS11已经过期
         @available(iOS, deprecated: 11)
         @available(macOS, deprecated: 10.12)
         func run() {}
     }
    
  2. 更多用法参考:https://docs.swift.org/swift-book/ReferenceManual/Attributes.html

iOS程序的入口

  1. 在AppDelegate上面默认有个@UIApplicationMain标记,这表示:编译器自动生成入口代码(main函数代码),自动设置AppDelegate为APP的代理
  2. 也可以删掉@UIApplicationMain,自定义入口代码,这样可以实现自定义Application:新建一个main.swift文件(名字必须叫main)

        
     //创建一个自定义的ZHApplication.Swift
     class ZHApplication :UApplication {}
        
     main.swift
     //因为swift中不需要定义main函数,直接写main函数要执行的内容即可
     import UIKit
     import ZHApplication
     //main函数中调用UIApplicationMain方法,指定代理。前两个参数固定.第三个参数地表那个类作为application,第四个参数代表那个类作为代理
     UIApplicationMain(CommandLine.argc,CommandLine.unsafeArgv,
    NSStringFromClass(ZHApplication.self),NSStringFromClass(AppDelegate.self))
    

Swift调用OC

  1. 场景:很多常用的第三方框架没有Swift版本,有些老项目Swift+OC混合开发

创建桥接文件

  1. 手动创建:
    1. 新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h,targetName一般就是项目名称
    2. 在BuildSetting->搜索bridging->Swift Compiler - General -> Objective -C Bridging Header 右侧设置交接文件的路径

      1.png

  2. 自动创建
    1. 在Swift项目下,直接创建一个OC的类,编译其会提醒是否创建一个桥接文件,点击是,会自动创建上面的桥接文件,以及设置引用路径
  3. 在 {targetName}-Bridging-Header.h 文件中 #import OC需要暴露给Swift的内容

     #import "ZHPerson.h"
    

Swift调用OC

  1. ZHPerson.h

     #import<Foundation/Foundation.h>
     //定义一个C语言函数
     int sum(int a, int b);
        
     @interface ZHPerson : NSObject
     @property (nonatomic, assign) NSInteger age;
     @property (nonatomic, copy) NSString *name;
     - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
     + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name;
     - (void)run;
     + (void)run;
     - (void)eat:(NSString *)food other:(NSString *)other;
     + (void)eat:(NSString *)food other:(NSString *)other;
     @end
    
  2. ZHPerson.m

     @implementation ZHPerson
     - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name {
         if (self = [super init]) {
             self.age = age;
             self.name = name;
         }
         return self;
     }
     + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name {
         return [[self alloc] initWithAge:age name:name];
     }
     + (void)run { NSLog(@"Person +run"); }
     - (void)run { NSLog(@"%zd %@ -run", _age, _name); }
     + (void)eat:(NSString *)food other:(NSString *)other { NSLog(@"Person +eat %@ %@", food, other); }
     - (void)eat:(NSString *)food other:(NSString *)other { NSLog(@"%zd %@ -eat %@ %@", _age, _name, food, other); }
     @end
     //C语言函数实现
     int sum(int a, int b) { return a + b; }
    
  3. Swift代码

     //这里不需要导入任何头文件,桥接文件代表全局导入
        
     //对应OC中initWithAge: name:
     var p = ZHPerson(age: 10, name: "Jack")
     p.age = 18
     p.name = "Rose"
     p.run() // 18 Rose -run
     p.eat("Apple", other: "Water") // 18 Rose -eat Apple Water
     ZHPerson.run() // Person +run
     ZHPerson.eat("Pizza", other: "Banana") // Person +eat Pizza Banana
     print(sum(10, 20)) // 30
    

@_silgen_name

  1. 如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,可以在Swift中使用 @_silgen_name 修改C函数名
  2. 如果不修改,会默认使用Swift中的函数

     // C语言
     int sum(int a, int b) {
         return a + b;
     }
        
     // Swift
     //将C语言中的sum函数修改为swift_sum
     @_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
     print(swift_sum(10, 20)) // 30 调用C语言中的sum
     print(sum(10, 20)) // 30 调用Swift中的sum
    

OC调用Swift

  1. Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h,在以下路径可以查看。在项目目录下没有,可以使用command+enter查看源文件。

    1.png

  2. 使用条件
    1. Swift暴露给OC的类最终继承自NSObject
    2. 使用@objc修饰需要暴露给OC的成员—指定某个成员(变量、方法)暴露
    3. 使用@objcMembers修饰类—所有成员都暴露
      1. 代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
      2. 最终是否成功暴露,还需要考虑成员自身的访问级别
  3. 举例:

     import Foundation
     @objcMembers class Car: NSObject {
         var price: Double
         var band: String
         init(price: Double, band: String) {
             self.price = price
             self.band = band
         }
         func run() { print(price, band, "run") }
         static func run() { print("Car run") }
     }
     extension Car {
         func test() { print(price, band, "test") }
     }
    
  4. Xcode会根据Swift代码生成对应的OC声明,写入 {targetName}-Swift.h 文件

     @interface Car : NSObject
         @property (nonatomic) double price;
         @property (nonatomic, copy) NSString * _Nonnull band;
         - (nonnull instancetype)initWithPrice:(double)price band:(NSString * _Nonnull)band OBJC_DESIGNATED_INITIALIZER;
         - (void)run;
         + (void)run;
         - (nonnull instancetype)init SWIFT_UNAVAILABLE;
         + (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
     @end
        
     @interface Car (SWIFT_EXTENSION(备课_Swift))
         - (void)test;
     @end
    
  5. OC调用Swift代码

     #import "备课_Swift-Swift.h"
     int sum(int a, int b) {
         Car *c = [[Car alloc] initWithPrice:10.5 band:@"BMW"];
         c.band = @"Bently";
         c.price = 108.5;
         [c run]; // 108.5 Bently run
         [c test]; // 108.5 Bently test
         [Car run]; // Car run
         return a + b;
     }
    

@objc

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

     //Swift源码
     @objc(ZHCar)
     @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") }
     }
        
     //OC调用Swift代码
     ZHCar *c = [[ZHCar 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
     [ZHCar run]; // Car run
    

选择器(Selector)

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

     //Swift源码
     @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))
         }
     }
    

String

  1. Swift的字符串类型String,跟OC的NSString,在API设计上还是有较大差异

     // 空字符串
     var emptyStr1 = ""
     var emptyStr2 = String()
        
     var str = "123456"
     print(str.hasPrefix("123")) // true
     print(str.hasSuffix("456")) // true
        
     var str: String = "1"
     // 拼接,jack_rose
     str.append("_2")
     // 重载运算符 +
     str = str + "_3"
     // 重载运算符 +=
     str += "_4"
     // \()插值
     str = "\(str)_5"
     // 长度,9,1_2_3_4_5
     print(str.count)
    

String的插入和删除

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

Substring

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

     var str = "1_2_3_4_5"
     // 1_2
     var substr1 = str.prefix(3)
     // 4_5
     var substr2 = str.suffix(3)
     // 1_2
     var range = str.startIndex..<str.index(str.startIndex, offsetBy: 3)
     var substr3 = str[range]
     // 最初的String,1_2_3_4_5
     print(substr3.base)
     // Substring -> String
     var str2 = String(substr3)
    
  2. Substring和它的base,共享字符串数据
  3. Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据

String 与 Character

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

String相关的协议

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

多行String

let str = """
1
    "2"
3
    '4'
"""

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

//打印如下
Escaping the first quote """
Escaping two quotes """
Escaping all three quotes """

// 以下2个字符串是等价的
let str1 = "These are the same."
let str2 = """
These are the same.
"""

// 缩进以结尾的3引号为对齐线
let str = """
        1
            2
    3
        4
    """

String 与 NSString

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

     var str1: String = "jack"
     var str2: NSString = "rose"
     var str3 = str1 as NSString
     var str4 = str2 as String
     // ja
     var str5 = str3.substring(with: NSRange(location: 0, length: 2))
     print(str5)
    
  2. 比较字符串内容是否等价
    1. String使用 == 运算符
    2. NSString使用isEqual方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
  3. Swift、OC桥接转换表

1.png

只能被class继承的协议

  1. 以下三种方式定义的协议,只能被类继承,不能被结构体继承。

     protocol Runnable1: AnyObject {}
     protocol Runnable2: class {}
     //被@objc修饰的协议
     @objc protocol Runnable3 {}
    
  2. @objc 修饰的协议,还可以暴露给OC去遵守实现
  3. 可以通过 @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
    

dynamic

  1. @objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程,这样可以通过runtime实现特殊的功能;反之,走Swift的虚表那一套。

     class Dog: NSObject {
         //走runtime
         @objc dynamic func test1() {}
         //走Swift的虚表
         func test2() {}
     }
     var d = Dog()
     d.test1()
     d.test2()
    

KVC\KVO

  1. Swift 支持 KVC \ KVO 的条件
    1. 属性所在的类、监听器最终继承自 NSObject
    2. @objc dynamic 修饰对应的属性
     //监听器最终继承自 NSObject
     class Observer: NSObject {
         override func observeValue(forKeyPath keyPath: String?,
         of object: Any?,
         change: [NSKeyValueChangeKey : Any]?,
         context: UnsafeMutableRawPointer?) {
             print("observeValue", change?[.newKey] as Any)
         }
     }
        
     //属性所在的类最终继承自 NSObject
     class Person: NSObject {
         //用 `@objc dynamic` 修饰对应的属性
         @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")
    
  2. 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")
    

关联对象(Associated Object)

  1. 在Swift中,class依然可以使用关联对象
  2. 默认情况,extension不可以增加存储属性。
  3. 那如果需要对一个原来的类新增存储属性,而且不直接修改原来类的情况下,怎么办?
    1. 借助关联对象,可以实现类似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

资源名管理

  1. 举例

     let img = UIImage(named: "logo")
     let btn = UIButton(type: .custom)
     btn.setTitle("添加", for: .normal)
     performSegue(withIdentifier: "login_main", sender: self)
        
        
     enum R {
         enum string: String {
             case add = "添加"
         }
         enum image: String {
             case logo
         }
         enum segue: String {
             case login_main
         }
     }
        
     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)
    
  2. 新增扩展

     extension UIImage {
         //扩展UIImage初始化可以接收R.image类型
         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)
         }
     }
    
  3. 其他管理思路

     let img = UIImage(named: "logo")
     let font = UIFont(name: "Arial", size: 14)
     let img = R.image.logo
     let font = R.font.arial(14)
        
     enum R {
         enum image {
             static var logo = UIImage(named: "logo")
         }
         enum font {
             static func arial(_ size: CGFloat) -> UIFont? {
                 UIFont(name: "Arial", size: size)
             }
         }
     }
    
  4. 更多优秀的思路参考

    1. https://github.com/mac-cain13/R.swift
    2. https://github.com/SwiftGen/SwiftGen

多线程开发

异步

  1. 类的封装

     //Asyncs.swift
        
     import Foundation
     //封装任务
     public typealias Task = () -> Void
        
     public static Asyncs {
         //自定义async,接收一个任务闭包
         public static func async(_ task: @escaping Task) {
             _async(task)
         }
            
         //自定义async,接收一个任务闭包、主线程执行任务
         public static func async(_ task: @escaping Task,
         _ mainTask: @escaping Task) {
             _async(task, mainTask)
         }
            
         private static func _async(_ task: @escaping Task,
         _ mainTask: Task? = nil) {
             //1. OC方式
             //1.1 开启一个异步队列,然后异步线程
             //DispatchQueue.global().async {
                 //执行任务
                
                 //1.2返回主线程执行任务
                 //DispatchQueue.main.async{
                     //执行任务
                //}
             //}
                
             //2. 通过DispatchWorkItem封装任务
             let item = DispatchWorkItem(block: task)
             DispatchQueue.global().async(execute: item)
                
             //2.2 返回主线程执行任务
             if let main = mainTask {
                 //item中的任务执行完进行通知,然后执行当前任务
                 item.notify(queue: DispatchQueue.main, execute: main)
             }
         }
     }
    
  2. 外部调用

     //子线程执行
     Asyncs.async {
         print(1)
     }
        
     Asyncs.async({
         //子线程执行
         print(1,Thread.current)
     }) {
         //主线程执行
         print(2,Thread.current)
     }
    

延迟

  1. swift中没有dispatch_after
@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
}

//威慑么有返回值DispatchWorkItem,可以使用这个返回值随时取消任务执行
//item?.cancel();

once

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

     //等价于lazy + dispatch_once
     fileprivate let initTask2: Void = {
         print("initTask2---------")
     }()
        
     class ViewController: UIViewController {
         //等价于lazy + dispatch_once
         static let initTask1: Void = {
             print("initTask1---------")
         }()
            
         override func viewDidLoad() {
             super.viewDidLoad()
             //使用_表示,就是为了让initTask2只执行一次,而且不会使用返回值。
             let _ = Self.initTask1
             let _ = initTask2
         }
     }
    

加锁

  1. gcd信号量实现加解锁-swift独有

     class Cache {
         private static var data = [String: Any]()
         //value的值代允许表同时有几条线程访问
         private static var lock = DispatchSemaphore(value: 1)
            
         static func set(_ key: String, _ value: Any) {
             lock.wait()
             //当前{}执行完解锁
             defer { lock.signal() }
             data[key] = value
         }
     }
    
  2. Foundation框架的锁–OC、Swift都有

     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() }
     }
    
Table of Contents