Swift第十一章 从OC到Swift
MARK、TODO、FIXME
// MARK:类似于OC中的 #pragma mark// MARK: -类似于OC中的 #pragma mark -// TODO:用于标记未完成的任务// FIXME:用于标记待修复的问题#warning("xxx")警告
条件编译
-
系统自带条件编译
// 操作系统: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 -
自定义条件编译

// 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
打印
- 由于Swift中没有宏定义,那如果需要实现dubug打印、release不打印怎么办?
-
新建一个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可用性说明
-
示例
//当前类只支持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() {} } -
更多用法参考:https://docs.swift.org/swift-book/ReferenceManual/Attributes.html
iOS程序的入口
- 在AppDelegate上面默认有个
@UIApplicationMain标记,这表示:编译器自动生成入口代码(main函数代码),自动设置AppDelegate为APP的代理 -
也可以删掉@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
- 场景:很多常用的第三方框架没有Swift版本,有些老项目Swift+OC混合开发
创建桥接文件
- 手动创建:
- 新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h,targetName一般就是项目名称
-
在BuildSetting->搜索bridging->Swift Compiler - General -> Objective -C Bridging Header 右侧设置交接文件的路径

- 自动创建
- 在Swift项目下,直接创建一个OC的类,编译其会提醒是否创建一个桥接文件,点击是,会自动创建上面的桥接文件,以及设置引用路径
-
在 {targetName}-Bridging-Header.h 文件中
#import OC需要暴露给Swift的内容#import "ZHPerson.h"
Swift调用OC
-
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 -
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; } -
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
- 如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,可以在Swift中使用
@_silgen_name修改C函数名 -
如果不修改,会默认使用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
-
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h,在以下路径可以查看。在项目目录下没有,可以使用command+enter查看源文件。

- 使用条件
- Swift暴露给OC的类最终继承自NSObject
- 使用
@objc修饰需要暴露给OC的成员—指定某个成员(变量、方法)暴露 - 使用
@objcMembers修饰类—所有成员都暴露- 代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
- 最终是否成功暴露,还需要考虑成员自身的访问级别
-
举例:
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") } } -
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 -
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
-
可以通过
@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)
- Swift中依然可以使用选择器,使用
#selector(name)定义一个选择器 -
必须是被
@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
-
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
-
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) - Substring和它的base,共享字符串数据
- Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据
String 与 Character
```
for c in "jack" { // c是Character类型
print(c)
}
var str = "jack"
// c是Character类型
var c = str[str.startIndex]
```
String相关的协议
- BidirectionalCollection 协议包含的部分内容
- startIndex 、 endIndex 属性、index 方法
- String、Array 都遵守了这个协议
- RangeReplaceableCollection 协议包含的部分内容
- append、insert、remove 方法
- String、Array 都遵守了这个协议
- 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
-
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) - 比较字符串内容是否等价
- String使用 == 运算符
- NSString使用isEqual方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
- Swift、OC桥接转换表

只能被class继承的协议
-
以下三种方式定义的协议,只能被类继承,不能被结构体继承。
protocol Runnable1: AnyObject {} protocol Runnable2: class {} //被@objc修饰的协议 @objc protocol Runnable3 {} - 被
@objc修饰的协议,还可以暴露给OC去遵守实现 -
可以通过
@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
-
被
@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
- Swift 支持 KVC \ KVO 的条件
- 属性所在的类、监听器最终继承自 NSObject
- 用
@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") -
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)
- 在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
资源名管理
-
举例
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) -
新增扩展
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) } } -
其他管理思路
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) } } } -
更多优秀的思路参考
https://github.com/mac-cain13/R.swifthttps://github.com/SwiftGen/SwiftGen
多线程开发
异步
-
类的封装
//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) } } } -
外部调用
//子线程执行 Asyncs.async { print(1) } Asyncs.async({ //子线程执行 print(1,Thread.current) }) { //主线程执行 print(2,Thread.current) }
延迟
- 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
- dispatch_once在Swift中已被废弃,取而代之
- 可以用类型属性或者全局变量\常量
- 默认自带 lazy + dispatch_once 效果
-
举例
//等价于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 } }
加锁
-
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 } } -
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() } }