Swift 进阶(十五)从OC到Swift(下)

String

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

// 空字符串
var emptyStr1 = ""
var emptyStr2 = String()
// 拼接字符串
var str: String = "1"
str.append("_2")

// 重载运算符
str = str + "_3"
str += "_4"

// 插值
str = "\(str)_5"
print(str, str.count) // 1_2_3_4_5, 9
// 字符串的判断
var str = "123456"
print(str.hasPrefix("123")) // true
print(str.hasSuffix("456")) // true

String的插入和删除

var str = "1_2"

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

Substring

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

var str = "1_2_3_4_5"

var substr1 = str.prefix(3) // 1_2
var substr2 = str.suffix(3) // 4_5

var range = str.startIndex..<str.index(str.startIndex, offsetBy: 3)
var substr3 = str[range] // 1_2

// 最初的String
print(substr3.base) // 1_2_3_4_5

// Substring -> String
var str2 = String(substr3)

Substring和它的base,共享字符串数据

其本质是Substring内部有一个指针指向String对应的区域

Substring发生修改或者转为String时,会分配新的内存存储字符串数据,不会影响到最初的String的内容,编译器会自动做优化

-w467

String与Character

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

String相关的协议

BidirectionalCollection协议包含的部分内容

  • startIndexendIndex属性、index方法
  • StringArray都遵守了这个协议

RangeReplaceableCollection协议包含的部分内容

  • appendinsertremove方法
  • StringArray都遵守了这个协议

DictionarySet也有实现上述协议中声明的一些方法,只是并没有遵守上述协议

多行String

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

如果要显示3引号,至少转义1个引号

let str = """
Escaping the first quote \"""
Escaping two quotes \"\""
Escaping all three quotes \"\"\"
"""

以下两个字符是等价的

let str1 = "These are the same."
let str2 = """
These are the same.
"""

缩进以结尾的3引号为对齐线

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

String和NSString

StringNSString之间可以随时随地的桥接转换

如果你觉得String的API过于复杂难用,可以考虑将String转为NSString

var str1: String = "jack"
var str2: NSString = "rose"

var str3 = str1 as NSString
var str4 = str2 as String

var str5 = str3.substring(with: NSRange(location: 0, length: 2))
print(str5) // ja

我们通过反汇编发现,StringNSString的转换会调用函数来实现的,相对会有性能的消耗,但由于编译器的优化,消耗的成本可以忽略不计

-w715

比较字符串内容是否等价

  • String使用==运算符
  • NSString使用isEqual方法,也可以使用==运算符(本质还是调用了isEqual方法)
var str1: NSString = "jack"
var str2: String = "rose"
var str5: String = "rose"
var str6: NSString = "jack"

print(str2 == str5)
print(str1 == str6)

通过反汇编,我们可以看到==运算符的本质还是调用了isEqual方法

-w714
-w714
-w713

下面是Swift和OC的几个类型的转换表格

StringNSString可以相互转换,而NSMutableString就只能单向转换成String

其他类型同理

-w506

只能被class继承的协议

如果协议对应AnyObject、class、@objc来修饰,那么只能被类所遵守

-w654

@objc修饰的协议,还可以暴露给OC去遵守协议实现

// Swift文件
@objc protocol Runnable4 {
    func run()
}

// OC文件
@interface LLTest : NSObject<Runnable4>

@end

@implementation LLTest

- (void)run { }
@end

可以通过@objc定义可选协议,这种协议只能被class遵守

@objc protocol Runnable4 {
    func run()
    @objc optional func eat()
}

class Person: Runnable4 {
    func run() {
        print("run")
    }
}

dynamic

@objc dynamic修饰的内容会具有动态性,比如调用方法会走Runtime的消息发送机制

class Dog {
    @objc dynamic func test1() {}
    func test2() {}
}

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

具体汇报调用过程可以参考上文混编调用的本质

KVC、KVO

Swift支持KVC、KVO的条件需要属性所在的类、监听器最终继承自NSObject

@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()
        
        addObserver(observer, forKeyPath: "age", options: .new, context: nil)
    }
    
    deinit {
        removeObserver(observer, forKeyPath: "age")
    }
}


var p = Person()
p.age = 20
p.setValue(25, forKey: "age")

// Optional(20)
// Optional(25)

block方式的KVO

class Person: NSObject {
    @objc dynamic var age: Int = 0
    var observation: NSKeyValueObservation?
    
    override init() {
        super.init()
        
        observation = observe(\Person.age, options: .new, changeHandler: { (person, change) in
            print(change.newValue as Any)
        })
    }
}


var p = Person()
p.age = 20
p.setValue(25, forKey: "age")

// Optional(20)
// Optional(25)

关联对象(Associated Object)

在Swift中,class依然可以使用关联对象

默认情况下,extension不可以增加存储属性

借助关联对象,可以实现类似extensionclass增加存储属性的效果

class Person {}
extension Person {
    // Void类型只占一个字节
    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)

我们采用枚举的方式对资源名进行管理

这种方式是参考了Android的资源名管理方式

enum R {
    enum string: String {
        case add = "添加"
    }
    
    enum image: String {
        case logo
    }
    
    enum segue: String {
        case login_main
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let img = UIImage(named: 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 {
    convenience init?(named 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)
enum R {
    enum image {
        static var logo = UIImage(named: "logo")
    }
    
    enum font {
        static func arial(_ size: CGFloat) -> UIFont? {
            UIFont(name: "Arial", size: size)
        }
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let img = R.image.logo
        let font = R.font.arial(14)
    }
}

更多优秀的思路请参考以下链接:
https://github.com/mac-cain13/R.swift
https://github.com/SwiftGen/SwiftGen

多线程开发

利用DispatchWorkItem封装常用多线程执行函数

public typealias Task = () -> Void

public struct Asyncs {
    /// 异步执行
    public static func async(_ task: @escaping Task) {
        _async(task)
    }

    public static func async(_ task: @escaping Task,
                             _ mainTask: @escaping Task) {
        _async(task, mainTask)
    }
    
    /// 主线程延迟执行
    @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 {
        _asyncDelay(seconds, task)
    }
    
    @discardableResult
    public static func asyncDelay(_ seconds: Double,
                                  _ task: @escaping Task,
                                  _ mainTask: @escaping Task) -> DispatchWorkItem {
        _asyncDelay(seconds, task, mainTask)
    }
}

// MARK: - 私有API
extension Asyncs {
    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)
        }
    }
    
    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
    }
}

dispatch_once在Swift中已被废弃,取而代之的是用类型属性或者全局变量\常量

默认自带lazy+dispatch_once效果

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

多个线程操作同一份数据会有资源抢夺问题,需要进行加锁

class Cache {
    private static var data = [String : Any]()
    private static var lock = DispatchSemaphore(value: 1)
    
    static func set(_ key: String, _ value: Any) {
        lock.wait()
        defer { lock.signal() }
        data[key] = value
    }
}
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()
    }
}
posted on 2021-03-31 02:37  FunkyRay  阅读(123)  评论(0编辑  收藏  举报