Swift4学习(有点乱)

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

  • 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)。总之,尽可能地在高抽象层级编写代码,会是高效和正确的有力保证。无数先辈已经用血淋淋“的教训告诉我们,要避免去做这样的不安全的操作,除非你确实知道你在做的是什么。”



关注我

慕课网
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • HTTPS https简单来说就是加密过的http协议 。 使用ssl(secure sockets layer ...
    zsj0310阅读 338评论 0 0
  • 大英博物馆最终于1759年1月15日在蒙塔古宅邸成立。 关于快乐的定义:为什么看到这些埃及的雕像的图片的时候我觉得...
    shamumu阅读 351评论 0 0