美文网首页iOS学习
iOS碎片化知识点汇总(持续更新)

iOS碎片化知识点汇总(持续更新)

作者: 奥利奥_2aff | 来源:发表于2019-03-31 20:39 被阅读13次

    语言

    weak, _ _weak 和 _ _block 修饰符

    Objective-C中,weak用于修饰属性,__weak__block用于修饰局部变量.

    weak __weak __block
    修饰对象 属性 局部变量 局部变量
    意义 弱引用 弱引用 block以外弱引用, block以内保留引用, 需要在block内手动释放.

    Swift中只有weak修饰符, 同时用于声明一个变量或者属性为弱引用, 并没有__block意义上的声明.

    便利构造函数 (designated, convenience)

    便利构造函数(designated initializer)是为了保证类型在初始化后, 其所有属性的值都是存在的.

    在Swift中, 又多了一个关键字convenience去加强这个特性.convenience initializer的作用在于, 可以规定一个类型的属性的初始值.

    designated convenience
    存在性 Objective-C, Swift Swift
    • 在Objective-C中

    • 声明一个便利构造函数的做法, 就是在声明函数的最后面加上宏NS_DESIGNATED_INITIALIZER

    • 当一个类型声明了designated initializer之后, 我们希望创建这个类型的实例, 就必须调用它的designated initializer.

    • 如果不调用designated initializer`, 编译器会发出警告.

    • 在Swift中

    • 每个类型都会有designated initializer, 一个类型的init函数在没有修饰的情况下, 它就是这个类型的designated initializer.

    • convenience initializer的调用优先级在designated initializer之上: 创建这个类型的实例, 如果这个类型有定义convenience initializer, 则必须调用.

    • 声明convenience initializer, 其内部必须调用这个类型自己的designated initializer.

    • 如果不按照上一条去做, 编译器会直接报错.

    struct 和 class

    很大路的知识点struct是值类型, 而class是引用类型.

    关键在于,一个struct实例的属性里面存在一个引用类型class的时候, 使用这个struct去赋值到一个新的变量的时候,struct属性里面的class会被怎么处理?

    
    class Teacher: NSObject {
    
    var name: String
    
    init(name: String) {
    
    self.name = name
    
    }
    
    }
    
    struct Room {
    
    var memberNum: Int
    
    var teacher: Teacher
    
    }
    
    let room = Room(memberNum: 1, teacher: Teacher(name: "oreo"))
    
    var room2 = room
    
    room2.memberNum = 2
    
    room2.teacher.name = "loli v"
    
    print(room.memberNum,room.teacher.name)
    
    print(room2.memberNum, room2.teacher.name)
    
    print(room2.teacher == room.teacher)
    
    room2.teacher = Teacher(name: "oreo")
    
    print("modify room2's teacher")
    
    print(room.memberNum,room.teacher.name)
    
    print(room2.memberNum, room2.teacher.name)
    
    print(room2.teacher == room.teacher)
    
    /*
    
    结果:
    
    1 loli v
    
    2 loli v
    
    true
    
    modify room2's teacher
    
    1 loli v
    
    2 oreo
    
    false
    
    */
    
    

    可以看到,room赋值之后room2,room2的值类型属性被拷贝了一份, 修改room2memberNum不会影响room.

    但是room2的引用类型属性, 其实是拷贝了一份room对应属性的地址, 所以修改room2teacher的属性, 会影响room,而重新给room2赋值一个新的Teacher实例之后, 两个Room实例里面的引用型属性就不是同一个了.

    运算符

    在OC中运算符是针对值类型使用的.

    在Swift中, 用户可以自定义运算符, 运算符可以用在应用型对象身上. 并且用户可以自己定义运算符的功能.

    协议 protocol

    • Objective-C:

      • 协议主要是MVC设计模式中的数据回调手段.
      • 因为用于回调, 协议本身可以被声明为弱引用, 因此协议都继承NSObject.
    • Swift:

      拥有Objective-C中已经有的全部特性, 并且被功能更强大:

      • 可以使用extension特性默认实现的协议函数或属性.
      • 协议中默认实现的函数可以在实现类型的代码中"重写".
      • 在使用extension特性声明的时候可以添加约束条件, 这样默认实现的函数只会在满足条件的时候才会生效, 如果不满足则等于没有实现.
      • 因为使用范围不止是用于回调, Swift中的协议不必继承NSObjectProtocol.
      • structclass都可以实现的协议, 因此在使用协议的时候不能直接当做引用型来使用, 除非协议本身就是继承自引用型协议.

    extension


    Foundation

    double 和 NSDecimalNumber

    项目中我们使用double表示和存储小数, 而在需要对小数进行计算的时候, 比如电商项目的购物车功能要进行金额结算, 我们使用double就可能出现金额精度不正确的问题.

    针对小数运算, Apple给我们提供了一个专门的类: NSDecimalNumber, NSDecimalNumber进行算数运算的方式是通过调用函数, 在Swift中可以用链式的方法计算, NSDecimalNumber运算结果仍然是一个NSDecimalNumber, 我们可以使用它的对应属性获得我们想要的值类型.

    struct Item {
        let unitPrice: Double
    }
    struct Coupon {
        let value: Double
    }
    // 20件商品, 75折
    let item = Item(unitPrice: 9.99)
    let coupon = Coupon(value: 0.75)
    let count: Int = 20
    let dPrice = NSDecimalNumber(value: item.unitPrice)
    let dCount = NSDecimalNumber(value: count)
    let dCouponValue = NSDecimalNumber(value: coupon.value)
    let result = dPrice.multiplying(by: dCouponValue).multiplying(by: dCount)
    let doubleResult = result.doubleValue
    let stringResult = result.stringValue
    

    GCD

    GCD会自动管理线程的生命周期(创建线程, 调度任务, 销毁线程). 我们调用GCD的时侯不会面对线程, 而是面对队列. 就是说:

    即使N次调用了异步执行并发队列, 并不代表我们创建了N个的子线程.

    • 死锁

      GCD的死锁, 关键是阻塞. 有很多面试题里面提到在主线程里面同步调用主队列会引起死锁. 而之前在公司面试, 问起死锁很多人也是这样回答, 回答者没有真的明白GCD的死锁. 事实上, 即使不是在主队列做这种操作, 用一个任意的串行队列如此操作, 都会进入死锁.

      let serialQueue = DispatchQueue(label: "serial")
      
      serialQueue.async {
          serialQueue.sync {
              print("sync in async???")
          }
          print("may I run?")
      }
      

      以上这段代码同样是编译不通过的, 因为sync函数做的第一件事情就是阻塞当前队列的任务(也就是print("may i run?")这一行代码), 而串行队列同时间只能执行1个任务, sync调用之后, 而闭包里面的代码也要在serialQueue中执行, 而这个任务会被排在了serialQueue队列的最后方, 而串行队列后方的任务必须等待前方的任务执行完才能执行, 因此陷入一个无限等待的循环.

      而主队列其实就是一个串行的队列!!!

      再看下面的代码

      let concurrentQueue = DispatchQueue(label: "concurrent", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
      
      concurrentQueue.async {
          print(Thread.current)
          concurrentQueue.sync {
              print("sync in async???", "Thread:", Thread.current)
          }
          print("yes you can!", Thread.current)
      }
      /*
      结果:
      <NSThread: 0x600001a5c100>{number = 3, name = (null)}
      sync in async??? Thread: <NSThread: 0x600001a5c100>{number = 3, name = (null)}
      yes you can! <NSThread: 0x600002b32d00>{number = 3, name = (null)}
      */
      

      没错, 在并发队列里面, 这几串代码是正常运行的. 因为sync阻塞了print("yes you can!"), 但是concurrentQueue它可以同时执行多个任务, 闭包里面的代码不需要排到print("yes you can!")的后面, 而是可以使用concurrentQueue分配的其他任务资源去执行.

    • 线程和队列

      回头继续看上面的代码, 可以发现, 由始至终concurrentQueue都一直在用同一个线程!!!

      这就是GCD做了它应该做的事情: 管理线程的生命周期.

      因为上面的代码, 事实上concurrentQueue同一时间永远只有1个任务需要执行, 所以即使它本身可以同时执行多个任务, 它也没必要创建多个线程来给任务开始.

      (请脑补一下收银排队的情景, 一模一样.)

      也就如开头所说的, 并发队列异步执行命令, 并不等于就是开辟了新的线程.


    UIKit

    UIView 和 CALayer 的关系

    当我们需要在屏幕上显示一些元素的时候, 一般会使用到UIView的各种子类. 而实际上如果我们只需要实现单纯的显示功能, 也可以直接使用CALayer去实现.

    相比起UIVIew, CALayer并不具备交互的功能, 就是不能响应任何点击拖拽行为.查看UIView的类不难发现, 它自身也有一个layer的属性, 其对应的其实就是用于显示在屏幕上的CALayer对象.

    UIViewsubviewsCALayersublayers是对应关系.

    待完成

    Responder

    待完成


    BluetoothKit


    RxSwift

    相关文章

      网友评论

        本文标题:iOS碎片化知识点汇总(持续更新)

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