Swift4学习(有点乱)

作者: Mg明明就是你 | 来源:发表于2017-11-01 10:18 被阅读322次

    人呢,有时候真的会很懒,懒到吃饭都不想吃了...

    • lazy,有时候有些操作可以延迟处理,优化性能

    看看一下打印有什么区别

    let data = 1...3
    let result = data.lazy.map {
        (i: Int) -> Int in
        print("正在处理 \(i)")
        return i * 2
    }
    print("准备访问结果")
    for i in result {
        print("操作后结果为 \(i)")
    }
    print("操作完毕")
    
    结果.png
    let data = 1...3
    let result = data.map {
        (i: Int) -> Int in
        print("正在处理 \(i)")
        return i * 2
    }
    print("准备访问结果")
    for i in result {
        print("操作后结果为 \(i)")
    }
    print("操作完毕")
    
    结果.png

    • 反射Mirror可以获取模型的键值对

    struct Person {
        let name: String
        let age: Int
    }
    let xiaoMing = Person(name: "XiaoMing", age: 26)
    let r = Mirror(reflecting: xiaoMing) // r 是 MirrorType
    xiaoMing.self
    for child in r.children {
        print("属性名:\(String(describing: child.label)),值:\(child.value)")
    }
    // KVC获取值
    func valueFrom(_ object: Any, key: String) -> Any? {
        let mirror = Mirror(reflecting: object)
        for child in mirror.children {
            let (targetKey, targetMirror) = (child.label, child.value)
            if key == targetKey {
                return targetMirror
            }
        }
        return nil
    }
    dump(xiaoMing)
    dump(valueFrom(xiaoMing, key: "name"))
    
    结果png

    • 获取对象类型

      • “在 Objective-C 中,使用 -class 方法就可以拿到对象的类,我们甚至可以用 NSStringFromClass 将它转换为一个能够打印出来的字符串:

        NSString *str = [[NSString alloc] init];
        NSLog(@"%@",NSStringFromClass([str class]));”
        
      • “在 Swift 中,为了获取一个 NSObject 或其子类的对象的实际类型,对这个调用其实有一个好看一些的写法,那就是 type(of:)。”

          let date = NSDate()
          let name = type(of: date)
          print(name)
          // 输出: __NSDate  
        
      • “当然,在Swift中我们可以求助于 Objective-C 的运行时,来获取类并按照原来的方式转换:

        let date = NSDate()
        let name: AnyClass! = object_getClass(date)
        print(name)
        // 输出: __NSDate
        
      其中 object_getClass 是一个定义在 ObjectiveC 的 runtime 中的方法,它可以接受任意的 AnyObject! 并返回它的类型 AnyClass! (注意这里的叹号,它表明我们甚至可以输入 nil,并期待其返回一个 nil)。”

    • KVO

    “KVO (Key-Value Observing) 是 Cocoa 中公认的最强大的特性之一,但是同时它也以烂到家的 API 和极其难用著称。和属性观察不同,KVO 的目的并不是为当前类的属性提供一个钩子方法,而是为了其他不同实例对当前的某个属性 (严格来说是 keypath) 进行监听时使用的。其他实例可以充当一个订阅者的角色,当被监听的属性发生变化时,订阅者将得到通知。

    这是一个很强大的属性,通过 KVO 我们可以实现很多松耦合的结构,使代码更加灵活和强大:像通过监听 model 的值来自动更新 UI 的绑定这样的工作,基本都是基于 KVO 来完成的。

    在 Swift 中我们也是可以使用 KVO 的,而且在 Swift 4 中,结合 KeyPath,Apple 为我们提供了非常漂亮的一套新的 API。不过 KVO 仅限于在 NSObject 的子类中,这是可以理解的,因为 KVO 是基于 KVC (Key-Value Coding) 以及动态派发技术实现的,而这些东西都是 Objective-C 运行时的概念。另外由于 Swift 为了效率,默认禁用了动态派发,因此想用 Swift 来实现 KVO,我们还需要做额外的工作,那就是将想要观测的对象标记为dynamic和@objc

    在 Swift 4 之前的版本中,为一个 NSObject 的子类实现 KVO 的最简单的例子看起来是这样的:

    class MyClass: NSObject {
        @objc dynamic var date = Date()
    }
    
    private var myContext = 0
    
    class Class: NSObject {
    
        var myObject: MyClass!
    
        override init() {
            super.init()
            myObject = MyClass()
            print("初始化 MyClass,当前日期: \(myObject.date)")
            myObject.addObserver(self,
                                 forKeyPath: "date",
                                 options: .new,
                                 context: &myContext)
    
            delay(3) {
                self.myObject.date = Date()
            }
        }
    
        override func observeValue(forKeyPath keyPath: String?,
                                of object: Any?,
                                   change: [NSKeyValueChangeKey : Any]?,context: UnsafeMutableRawPointer?)
        {
            if let change = change, context == &myContext {
                if let newDate = change[.newKey] as? Date {
                    print("MyClass 日期发生变化 \(newDate)")
                }
            }
        }
    }
    
    let obj = Class()
    /*输出应该类似于:
    初始化 Class,当前日期: 2017-10-26 08:50:24 +0000
    Class 日期发生变化 2017-10-26 08:50:27 +0000*/
    

    Swift 4 中 Apple 引入了新的 KeyPath 的表达方式,不需要传Context去区分是那个变量发生了改变,监听方式改为闭包模式

    var observation: NSKeyValueObservation?
    observation = myObject.observe(\MyClass.date, options: [.new]) { (_, change) in
           if let newDate = change.newValue {
                print("AnotherClass 日期发生变化 \(newDate)")
           }
    }
    // 值得注意的是:必须要用属性observation赋值  否则不会调用闭包里面的代码
    

    • Lock

    无并发,不编码。而只要一说到多线程或者并发的代码,我们可能就很难绕开对于锁的讨论。简单来说,为了在不同线程中安全地访问同一个资源,我们需要这些访问顺序进行。Cocoa 和 Objective-C 中加锁的方式有很多,但是其中在日常开发中最常用的应该是 @synchronized,这个关键字可以用来修饰一个变量,并为其自动加上和解除互斥锁。这样,可以保证变量在作用范围内不会被其他线程改变。举个例子,如果我们有一个方法接受参数,需要这个方法是线程安全的话,就需要在参数上加锁:

    -(void)myMethod:(id)anObj {
          @synchronized(anObj) {
              // 在括号内持有 anObj 锁
          }
    }
    

    如果没有锁的话,一旦 anObj 的内容被其他线程修改的话,这个方法的行为很可能就无法预测了。
    但是加锁和解锁都是要消耗一定性能的,因此我们不太可能为所有的方法都加上锁。另外其实在一个 app 中可能会涉及到多线程的部分是有限的,我们也没有必要为所有东西加上锁。过多的锁不仅没有意义,而且对于多线程编程来说,可能会产生很多像死锁这样的陷阱,也难以调试。因此在使用多线程时,我们应该尽量将保持简单作为第一要务。”
    “虽然这个方法很简单好用,但是很不幸的是在 Swift 中它已经 (或者是暂时) 不存在了。其实 @synchronized 在幕后做的事情是调用了 objc_sync 中的 objc_sync_enter 和 objc_sync_exit 方法,并且加入了一些异常判断。因此,在 Swift 中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:

    func myMethod(anObj: AnyObject!) {
        objc_sync_enter(anObj)
    
        // 在 enter 和 exit 之间持有 anObj 锁
    
        objc_sync_exit(anObj)
    }
    

    “如果我们喜欢以前的那种形式,甚至可以写一个全局的方法,并接受一个闭包,来将 objc_sync_enter 和 objc_sync_exit 封装起来:

    func synchronized(_ lock: AnyObject, closure: () -> ()) {
        objc_sync_enter(lock)
        closure()
        objc_sync_exit(lock)
    }
    

    再结合 Swift 的尾随闭包的语言特性,这样,使用起来的时候就和 Objective-C 中很像了:

    func myMethodLocked(anObj: AnyObject!) {
        synchronized(anObj) {
            // 在括号内持有 anObj 锁
        }
    }
    

    举一个具体的使用例子,比如我们想要为某个类实现一个线程安全的 setter,可以这样进行重写:

    // 一个实际的线程安全的 setter 例子
    class Obj {
        var _str = "123"
        var str: String {
            get {
                return _str
            }
            set {
                synchronized(self) {
                    _str = newValue
                }
            }
        // 下略
        }
    }
    

    • UnsafePointer

    Swift 本身从设计上来说是一门非常安全的语言,在 Swift 的思想中,所有的引用或者变量的类型都是确定并且正确对应它们的实际类型的,你应当无法进行任意的类型转换,也不能直接通过指针做出一些出格的事情。这种安全性在日常的程序开发中对于避免不必要的 bug,以及迅速而且稳定地找出代码错误是非常有帮助的。但是凡事都有两面性,在高安全的同时,Swift 也相应地丧失了部分的灵活性。

    现阶段想要完全抛弃 C 的一套东西还是相当困难的,特别是在很多上古级别的 C API 框架还在使用 (或者被间接使用)。开发者,尤其是偏向较底层的框架的开发者不得不面临着与 C API 打交道的时候,还是无法绕开指针的概念,而指针在 Swift定义了一套对 C 语言指针的访问和转换方法,那就是 UnsafePointer 和它的一系列变体。对于使用 C API 时如果遇到接受内存地址作为参数,或者返回是内存地址的情况,在 Swift 里会将它们转为 UnsafePointer<Type> 的类型,比如说如果某个 API 在 C 中是这样的话:

    void method(const int *num) {
        printf("%d",*num);
    }
    其对应的 Swift 方法应该是:
    func method(_ num: UnsafePointer<CInt>) {
        print(num.pointee)
    }
    

    我们这个 tip 所说的 UnsafePointer,就是 Swift 中专门针对指针的转换。对于其他的 C 中基础类型,在 Swift 中对应的类型都遵循统一的命名规则:在前面加上一个字母 C 并将原来的第一个字母大写:比如 int,bool 和 char 的对应类型分别是CInt,CBool 和 CChar。在上面的 C 方法中,我们接受一个 int 的指针,转换到 Swift 里所对应的就是一个 CInt 的 UnsafePointer 类型。这里原来的 C API 中已经指明了输入的 num 指针的不可变的 (const),因此在 Swift 中我们与之对应的是 UnsafePointer 这个不可变版本。如果只是一个普通的可变指针的话,我们可以使用 UnsafeMutablePointer 来对应:

    C API Swift API
    const Type * UnsafePointer
    Type * UnsafeMutablePointer

    在 C 中,对某个指针进行取值使用的是 *,而在 Swift 中我们可以使用 memory 属性来读取相应内存中存储的内容。通过传入指针地址进行方法调用的时候就都比较相似了,都是在前面加上 & 符号,C 的版本和 Swift 的版本只在声明变量的时候有所区别:

    // C
    int a = 123;
    method(&a);   // 输出 123
    
    // Swift
    var a: CInt = 123
    method(&a)    // 输出 123
    

    遵守这些原则,使用 UnsafePointer 在 Swift 中进行 C API 的调用应该就不会有很大问题了。
    “另外一个重要的课题是如何在指针的内容和实际的值之间进行转换。比如我们如果由于某种原因需要涉及到直接使用 CFArray 的方法来获取数组中元素的时候,我们会用到这个方法:

    func CFArrayGetValueAtIndex(theArray: CFArray!, idx: CFIndex)
                                                -> UnsafePointer<Void>
    

    因为 CFArray 中是可以存放任意对象的,所以这里的返回是一个任意对象的指针,相当于 C 中的 void *。这显然不是我们想要的东西。Swift 中为我们提供了一个强制转换的方法 unsafeBitCast,通过下面的代码,我们可以看到应当如何使用类似这样的 API,将一个指针强制按位转成所需类型的对象:

    let arr = NSArray(object: "meow")
    let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
    // str = "meow
    

    “unsafeBitCast 会将第一个参数的内容按照第二个参数的类型进行转换,而不去关心实际是不是可行,这也正是 UnsafePointer 的不安全所在,因为我们不必遵守类型转换的检查,而拥有了在指针层面直接操作内存的机会。

    其实说了这么多,Apple 将直接的指针访问冠以 Unsafe 的前缀,就是提醒我们:这些东西不安全,亲们能不用就别用了吧 (作为 Apple,另一个重要的考虑是如果避免指针的话可以减少很多系统漏洞)!在日常开发中,我们确实不太需要经常和这些东西打交道 (除了传入 NSError 指针这个历史遗留问题以外,而且在 Swift 2.0 中也已经使用异常机制替代了 NSError)。总之,尽可能地在高抽象层级编写代码,会是高效和正确的有力保证。无数先辈已经用血淋淋“的教训告诉我们,要避免去做这样的不安全的操作,除非你确实知道你在做的是什么。”

    相关文章

      网友评论

        本文标题:Swift4学习(有点乱)

        本文链接:https://www.haomeiwen.com/subject/nmeauxtx.html