Swift5.0 - day10- 从 OC 到 Swift(下)

简介: Swift5.0 - day10- 从 OC 到 Swift(下)

三、字符串



  • 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)


image.png


  • 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桥接转换表


image.png


  • 提示:不可以由 不可变 强转成 可变的


四、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()


image.png

  • 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锁 一样
目录
相关文章
|
6月前
|
API Swift iOS开发
45 Swift和OC的混编
Swift和OC的混编
84 0
|
7月前
|
Swift iOS开发
iOS OC混编Swift 后者无法走断点
iOS OC混编Swift 后者无法走断点
49 0
|
Swift
Swift和OC控制器互相跳转
Swift和OC控制器互相跳转
287 0
|
Swift iOS开发
Swift - 与OC混编时如何创建桥接头文件
Swift - 与OC混编时如何创建桥接头文件
376 0
Swift - 与OC混编时如何创建桥接头文件
|
Swift
OC 和 swift 创建单例方法
OC 和 swift 创建单例方法
289 0
|
Swift iOS开发
oc与swift混编
1.swift中调用oc;2.oc中调用swift
221 0
oc与swift混编
|
Swift iOS开发 容器
iOS 仿支付宝银行卡界面(支持Swift/OC)
在有支付相关的APP中,都有对应的钱包,虽然现在的支付宝,微信支付很流行,但是都是需要绑定自己的银行卡,那么这个银行卡的卡包页面该怎么实现呢?在网上找了许久也没有找到合适的,那就索性自己造轮子。
324 0
|
Swift iOS开发
Swift与OC的混编
Swift调用OC文件 OC调用Swift文件
331 0
Swift与OC的混编
|
Swift
OC与swift的数据传输
该项目主要介绍了oc与swift之间、swift内部几种常见的传值方式(属性传值、代码块传值、代理传值、通知) 如果oc与swift之间的桥接有疑问请参考:OC与swift桥接互调
184 0
OC与swift的数据传输
|
Swift iOS开发
Swift与OC混编
Swift一出来就比较受人欢迎,但是还是有很多的第三方库是不支持Swift的,个人也感觉Swift还有很长的路要走。 而且最近连Swift的创始人都离开苹果公司了。。。。。。。。。 加入Swift用到了第三方的东西那怎么办呢,其实很简单,Xcode会自动帮你创建一个桥接文件,用来连接Swift和OC这两种语言,同样的在OC里面用Swift也是一样。
1287 0

相关课程

更多