美文网首页
iOS 面试之道 阅读笔记(3)

iOS 面试之道 阅读笔记(3)

作者: 白猫大厨 | 来源:发表于2018-08-30 04:49 被阅读0次

    Swift

    直接跳到Swift语言吧。Swift很快很安全,而且就我的感受来说,写Swift有一种写脚本语言的快感。但是Swift还有很多路要走。

    我在这里就按照书中的顺序来做个笔记和添加我所知道的更多知识。

    Class和Struct和Enum

    对于Class和Struct,一个是引用类型,一个是值类型我就不赘述了,书里写的很清楚。

    先说说Struct:

    默认下Struct内部的函数是不能修改Struct内部的属性(properties)的,但是我们可以在func前面加mutating关键字来打破默认。例如下面的doSomethingToInternalProperties()。

    struct TV {
        var height: Float
        var width: Float
        static var Tag = "electronics"
        
        var diagonals: Int {
          // calculated here
          return result
        }
      
        var spaceTaken: Double {
          get {
            // calculate here
            return result
          }
          set {
            height = Double(newValue)
          }  
        }
        
        mutating func doSomethingToInternalProperties() {
            // do something
        }
    }
    

    更多:

    上面的例子里一共有三种不同的属性(properties)。

    • height和width是实例属性(instance properties)。这种属性存储了关于一个实例的信息,如高和宽的具体数值。
    • diagonals是计算属性(Computed Propertie)。这种属性其实根本就不存储任何值,它只是提供了一个getter或者setter让你直接获取一些计算后的值或直接修改实例属性。
    • Tags是类属性,开头要用static关键字。即不需要创建实例就能获得的关于这个类的属性。let tvTag = TV.Tag

    然后Enum:

    个人感觉Swift的Enum真的很强大啊,可以有不同的case,也可以有func。Enum和Struct很相似,就不赘述了。我们体验Enum的强大吧,下面是用Enum做一个数学表达式,然后用Enum内部的函数来求值。

    // example
    indirect enum Expression {
      case number(Int)
      case add(Expression, Expression)
      
      func evaluate() -> Int {
        switch self {
        case let .number(value):
          return value
        case let .add(left, right):
          return left.evaluate() + right.evaluate()
        }
      }
    }
    
    var first = Expression.number(50)
    var second = Expression.number(12)
    var sum = Expression.add(first, second)
    second.evaluate() // 12
    first.evaluate() // 50
    sum.evaluate() // 62
    

    解释一下,这里定义了一个数学表达式Expression的Enum。数学表达式有很多很多中,这里写了:

    1. 代表一个整数的表达式number(Int)
    2. 和加法表达式add(Expression, Expression),之所以括号里面的类型是Expression,是因为它不仅可能是两个整数相加,也可能是两个相加的表达式在求和,类似于(1 + 2)+ 3。

    然后内部有一个求值的方程,他会判断,如果这个表达式是一个整数表达式,那么就返回整数的值,如果是加法表达式,这里用到递归,左边表达式求值后再加上右边表达式的值。

    你看的没错,Enum里面可以用递归嵌套。这是因为有递归嵌套,所以Enum前面需要加上indirect关键字。

    最后Class:

    对比与Struct的区别,一个是传递的值得类型不同,另一个是Struct中的属性(properties)是可以不初始化的,就像上面的列子里,height和width我都没有初始化,甚至这两个属性我都没有定义为optional。

    但是Class中的属性是要定义的,如果你不定义,那么你多半会得到Class ‘XXX' has no initializers这样的错误警告。例子:

    // Compiler error: Class 'Address' has no initializers
    class Address {
        var street: String
    }
    
    // you can solve by these ways
    // add a init function
    class Address {
        var street: String
        
        init(street: String) {
          self.street = street
        }
    }
    
    // init the street by default
    class Address {
        var street: String = ""
    }
    
    // set street to optional
    class Address {
        var street: String?
    }
    

    更多:

    Class作为对象当然是可以继承的。一个类继承另一个类后,可以覆盖(override)它父类func的实现方法。如果你不希望子类获得这样的能力,你可以在父类的func前面加上final关键字。Swift不允许类的多继承。

    在Swift里面,比较推荐用protocal来代替继承,因为继承会增加程序见的耦合度,增加程序出错的可能性。用protocal的话,就是底耦合度,protocal类似于Java的interface,protocal提供所需的属性和方法,但不实现他们。这些属性和方法由遵循(comfirm)该协议的类来实现。

    我看书的目录有关于面向协议的编程一节,这里就不多说了,看到那一节在说。

    Optional:

    简单理解,就是定义一个value能不能是空的(nil)。一般用if let else或是guard let else来判断。

    用guard比if let的好处在于,可以减少if else的嵌套。

    Protocol协议:

    Swift中的协议真的用法很灵活,大家做好多看看官方Swift教科书。这里列出一些:

    • Class,Struct,Enum都可以遵循Protocal。你也可以通过在protocal前面加@objc或是在protocal后加class来强制只有Class能用这个Protocal
    @objc protocol SomeProtocal {
    }
    
    // 或者
    
    protocol SomeProtocal: class {
    }
    
    • protocal可以定义特定的初始化方法。Class在实现这类初始化方法时,前面要加require
    protocol SomeProtocal {
        var aVar: Int { get set }
        init()
    }
    
    class SomeClass: SomeProtocal {
        
        var aVar: Int
        
        required init() {
            aVar = 7
        }
        
        init(_ aVar: Int) {
            self.aVar = aVar
        }
    }
    
    struct SomeStruct: SomeProtocal {
        var aVar: Int
        init() {
            aVar = 7
        }
    }
    
    • protocal可以作为一个类来成为(protocal as type):
      • 一个方程的返回值类型
      • 一个值和属性的类型
      • 数组,字典或者其他容器中存储值得类型
    protocol Vehicle {
    }
    
    struct Car: Vehicle {
    }
    
    struct Bike: Vehicle {
    }
    
    let car = Car(), bike = Bike()
    var vehicles : [Vehicle] = [car, bike]
    
    // 更多:类型判定
    vehicles.forEach { v in
        if v is Car {
            print("I'm a car")
        } else if v is Bike {
            print("I'm a bike")
        }
    }
    
    • 一个class或struct可以继承多个协议。
    • 默认下,继承协议的class和struct必须实现协议中的所有属性和方法。但是可以通过转化Swift协议到ObjC协议来增加optional。
    @objc protocol SomeProtocal {
        @objc optional func someMethod() -> Int
        @objc optional var someProperty: Int { get }
    }
    
    • 代理(Delegation)不是协议(Protocal),它是iOS中的一种设计模式,但是依赖于协议这个技术。用代理的好处在于,委托方不会知道代理方的任何内部情况,通过协议,委托方制定代理方需要实现的功能,然后委托方按照协议实现功能就好了。代理是可以传递消息的,它也是iOS种的一种消息传递方式(其他有:通知NSNotification,Block或Closure,target action,KVO)。

    泛型Generic:

    书里面简单介绍的Swift泛型怎么用。在实际运用的时候,我们也可以限制泛型值的类型。看个例子,下面的方程返回泛型数组中一个特定值的下标。

    func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
    

    这段程序是有问题的,因为不是所有类型的值都能使用==来判断相等,特别是工程师自己写的类。只有满足实现了Equatable协议的Class和Struct,程序才能正常运行。所以我们可以对泛型T做限制。

    func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
        // same as above
    }
    
    // 或者用where关键字
    func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? where T: Equatable {
        // same as above
    }
    

    Open vs. Public

    **open **

    • open 修饰的 class 在 Module 内部和外部都可以被访问和继承
    • open 修饰的 func 在 Module 内部和外部都可以被访问和重载(override)

    **public **

    • public 修饰的 class 在 Module 内部可以访问和继承,在外部只能访问
    • public 修饰的 func 在 Module 内部可以被访问和重载(override),在外部只能访问

    Copy-on-write

    Swift将很多ObjC中的数据类型从引用类(reference type)改成了值类(value type)。这样及降低了内存泄露的风险,也提高了内存效率。书中给了一个例子:

    // arrayA 是一个数组,为值类型
    let arrayA = [1, 2, 3]
    // arrayB 这个时候与 arrayA 在内存中是同一个东西,内存中并没有生成新的数组
    let arrayB = arrayA
    // arrayB 被修改了,此时 arrayB 在内存中变成了一个新的数组,而不是原来的 arrayA
    arrayB.append(4)
    

    然后说到,当arrayB没有改变的时候,arrayB和arrayA指向同一个内存。意思就是他们应该有相同的address。

    然后我就像去证明一下,首先通过看官方文献我找到了这样一个方程来返回地址withUnsafePointer(to:_:)。

    var array1: [Int] = [0, 1, 2, 3]
    var array2 = array1
    
    withUnsafePointer(to: &array1) {
        print(" 1 has address: \($0)")
    }
    withUnsafePointer(to: &array2) {
        print(" 2 has address: \($0)")
    }
    
    // 输出:
    // 1 has address: 0x000000011aabf4d0
    // 2 has address: 0x000000011aabf4d8
    

    结果输出地址不一样,我当时就很怀疑人生,然后翻看一些书籍和别人的博客寻求答案。我看到的各种东西都证明《iOS面试之道》里是正确的,但是证明呢?

    接着我看到了这个方法,用UnsafeRawPointer

    func print(address o: UnsafeRawPointer ) {
        print(String(format: "%p", Int(bitPattern: o)))
    }
    
    var array1: [Int] = [0, 1, 2, 3]
    var array2 = array1
    
    print(address: array1) //0x60c00006f4a0
    print(address: array2) //0x60c00006f4a0
    

    终于得到了相同的地址。那么UnsafeRawPointer和UnsafePointer的区别是什么,我的理解是,UnsafeRawPointer是指向没有类型(untyped)的数据,可以理解为存在内存中裸的数据,例子中就是0在内存中所在位置,而UnsafePointer是指向有类型(typed)的数据,例子中应该是指向我们建立的Array在内存中的地址。

    static methods vs. class methods

    • static 和 class都是用来指定类方法
    • class关键字指定的类方法 可以被 override
    • static关键字指定的类方法 不能被 override

    as? vs. as!

    这个属于Swift的类型转换(type cast)。

    • as? - 表示这个类型转换过程是optional的。被赋予的值肯定是一个optional的值。如果类型转换失败,返回nil。
    • as! - 表示这个类型转换过程不是optional的. System will crash if down casting fails.如果类型转换失败,程序崩溃。

    加了些我知道的,但是书里没有提及的知识,先这么多吧,我以前的笔记挺乱的,整理好了有什么要加的在加进来。

    Reference:

    https://xiaozhuanlan.com/ios-interview 故胤道长和唐巧两位大神的书《iOS 面试之道》

    https://swiftdoc.org/

    https://developer.apple.com/documentation/swift/unsaferawpointer

    相关文章

      网友评论

          本文标题:iOS 面试之道 阅读笔记(3)

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