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
一般情况下,在XCode
的swift
项目中第一次新建OC
的Class
时,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
不可以增加存储属性
, 借助关联对象,可以实现类似extension
为class
增加存储属性的效果
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] , 转载请保留原文链接.