三、字符串
- 3.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)
- 3.2、String的插入和删除
var str = "1_2" // 插入 单个字符,结果是: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)) // 删除值为1的第一个索引的值,,结果是:666hello_2_3_8884 str.remove(at: str.firstIndex(of: "1")!) // 删除值为字符为 6 的字符,结果是: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)
- 3.3、Substring 子串
- String可以通过下标、 prefix、 suffix等截取子串,子串类型不是String,而是Substring
- Substring和它的base,共享字符串数据
- Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据,也就是深度拷贝
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)
prefix(3)
代表从 头 截取三位suffix(3)
代表从 尾 截取三位- 子串在没有进行修改前 和 原字符串公用一块内存,在子串进行修改后,那么就要进行深度拷贝了
- 3.4、String 与 Character
for c in "jack" { // c是Character类型 print(c) } var str = "jack" // c是Character类型 var c = str[str.startIndex]
- 3.5、String 相关的协议
- BidirectionalCollection 协议包含的部分内容
- startIndex 、 endIndex 属性、index 方法
- String、Array 都遵守了这个协议
- RangeReplaceableCollection 协议包含的部分内容
- append、insert、remove 方法
- String、Array 都遵守了这个协议
- Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
- 3.6、多行String
- 放在 三个双引号之间的代表是多行,如下
let str = """ 1 "2" 3 '4' """
- 如果需要显示三个 引号,至少转义一个引号
let str = """ Escaping the first quote \""" Escaping two quotes \"\"" Escaping all three quotes \"\"\" """
- 缩进以结尾的 三引号为对齐线
let str = """ 1 "2" 3 '4' """
- 以下两个字符串是等价的
let str1 = "These are the same." let str2 = """ These are the same. """
- 3.7、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 // OC的使用 var str5 = str3.substring(with: NSRange(location: 0, length: 2)) print(str5)
- 比较字符串内容是否等价
String使用 == 运算符 NSString使用 isEqual 方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
- Swift、OC桥接转换表
- 提示:不可以由
不可变
强转成可变的
四、OC 与 Swift 其他的不同点
- 4.1、只能被class继承的协议
protocol Runnable1: AnyObject {} protocol Runnable2: class {} @objc protocol Runnable3 {}
- 被
@objc
修饰的协议,还可以暴露给OC去遵守实现 - 4.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
- 第二种:我们可以通过扩展,如下Dog类就不需要实现
run2()
,因为扩展中已经实现
protocol Runnable { func run1() func run2() } extension Runnable { func run2(){ } } class Dog: Runnable { func run1() { print("Dog run1") } } var d = Dog() d.run1() // Dog run1
- 4.3、dynamic
被@objc dynamic
修饰的内容会具有动态性,比如调用方法会走runtime那一套流程
class Dog: NSObject { @objc dynamic func test1() {} func test2() {} } var d = Dog() d.test1() d.test2()
- 4.4、KVO / KVC
- Swift 支持 KVC \ KVO 的条件 ,必须满足以下条件
- (1)、属性所在的类、监听器最终继承自 NSObject
- (2)、 用
@objc dynamic
修饰对应的属性
class Observer: NSObject { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { print("observeValue", change?[.newKey] as Any) } } 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")
- 4.5、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")
- 4.6、关联对象
- 在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 = Person1() print(p.age) // 0 p.age = 10 print(p.age) // 10
提示:唯一的存储空间
private static var AGE_KEY: Void?
,我们使用Void和Bool 都是 1 个存储空间,节省内存
- 4.7、资源名管理
- 平时的做法:直接加载图片名字或者按钮的名字,如下
let img = UIImage(named: "logo") let btn = UIButton(type: .custom) btn.setTitle("添加", for: .normal)
- 优化后的做法,先定义一个资源枚举 JKResource
enum JKResource { /// 按钮名字 enum string: String { case add = "添加" } /// 图片名字 enum image: String { case logo } enum segue: String { case login_main } } // 调用 let img = UIImage(named: JKResource.image.logo.rawValue) let btn = UIButton(type: .custom) btn.setTitle(JKResource.string.add.rawValue, for: .normal)
- 提示:这种做法实际上是参考了Android的资源名管理方式
- 通过扩展进一步管理资源名
extension UIImage { convenience init?(_ name: R.image) { self.init(named: name.rawValue) } } extension UIButton { func setTitle(_ title: R.string, for state: UIControl.State) { setTitle(title.rawValue, for: state) } }
- 资源名管理的其他思路
enum JKResource { enum image { static var logo = UIImage(named: "logo") } enum font { static func arial(_ size: CGFloat) -> UIFont? { UIFont(name: "Arial", size: size) } } } // 使用如下 let img = JKResource.image.logo let font = JKResource.font.arial(14)
更多优秀的思路参考如下
五、多线程
- 5.1、多线程开发-异步
public typealias Task = () -> Void public struct JKAsyncs { 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 { item.notify(queue: DispatchQueue.main, execute: main) } } } // 调用如下 JKAsyncs.async({ print(Thread.current) // 自线程 }) { print(Thread.current) // 主线程 }
提示:开辟线程任务可能是在大括号之外完成所以加上
@escaping
:逃逸闭包
DispatchWorkItem
的使用,子线程和其他线程分开,更加的直观
let item = DispatchWorkItem { print(Thread.current) } DispatchQueue.global().async(execute: item) item.notify(queue: DispatchQueue.main) { print(Thread.current) }
- 5.2、多线程开发-主线程延迟
- 平时的用法
let seconds:Double = 5 let item = DispatchWorkItem { print("\(seconds)秒后打印",Thread.current) } DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
- 写到封装的
JKAsyncs
结构体里面
@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
代表在使用的时候我们可以忽略函数的返回值,也就是可以不用接收返回值
- 返回值itme的作用:
item.cancel()
取消 方法的执行,也就是取消延迟- 延迟操作是在子线程
- 5.3、多线程开发-异步延迟
@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 }
- 5.4、多线程开发 - once:一次性代码,
dispatch_once
在 Swift 中已被废弃,取而代之 如下方式
- 可以用类型属性或者全局变量\常量(整个程序启动后只有一份内存)
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 let _ = Self.initTask1 let _ = initTask2 } }
- 打印结果
initTask1--------- initTask2---------
提示:默认自带
lazy + dispatch_once
效果
- 第一个字母大写的
Self
代表当前的类- 懒加载的属性里面只会走一次
- 5.5、多线程开发-加锁(线程同步技术,防止资源抢夺)
- 第一种锁:gcd 信号量
class Cache { private static var data = [String: Any]() // 设置信号量的锁 private static var lock = DispatchSemaphore(value: 1) static func get(_ key: String) -> Any? { data[key] } static func set(_ key: String, _ value: Any) { // 加锁 lock.wait() defer { // 解锁 lock.signal() } data[key] = value } }
- NSLock锁
class Cache { private static var data = [String: Any]() private static var lock = NSLock() static func get(_ key: String) -> Any? { data[key] } static func set(_ key: String, _ value: Any) { // 加锁 lock.lock() defer { // 解锁 lock.unlock() } data[key] = value }
- 递归锁:如果一个调用存在调用自身(递归),那么我们就是用递归锁:
NSRecursiveLock()
,加锁解锁和上面 NSLock锁 一样