请稍侯

iOS问题汇总之iOS应用 - Swift常见问题与技术

09 September 2020

iOS问题汇总之iOS应用 - Swift常见问题与技术

Swift常见原则与问题

常见问题

参考:https://swifter.tips/

map与flatmap的区别:map不能将元素映射成可选类型,flatmap可以;

map、filter、reduce 的作用

map: 映射,将一个元素根据某个函数 映射 成另一个元素(可以是同类型,也可以是不同类型) filter: 过滤,将一个元素传入闭包中,如果返回的是false , 就过滤掉 reduce : reduce函数接收两个参数,一个初始值和一个组合闭包(combine closure),期间通过一个累加器(Accumulator)来持续记录每次迭代闭包运算后的递增状态,化零为整。
例如,将一个数组中的各个元素与一个初始值10相加,可以使用reduce函数,实现一个类似累加器的作用。

let items = [2.0, 4.0, 5.0, 7.0]
let total = items.reduce(10, combine: +)
// 28.0

let codes = ["abc", "def", "ghi"]
let text = codes.reduce("", combine: +)
// "abcdefghi"

Swift Currying(柯里化)

实现一个函数,输入是任一整数,输出要返回输入的整数 + 2.

func add(_ addition: Int) -> (Int) -> Int {
    return {
        count in 
        return count + addition
    }
}

let add2 = add(2)           //普通调用方式
let number = add2(10)

let number = add(2)(10)     //连起来调用方式

//再比如下面比较大小
func greaterThan(_ comparer: Int) -> (Int) -> Bool {
    return { $0 > comparer }
}

let compareResult = greaterThan(10)(11)

如何声明一个只能被类 conform 的 protocol?

protocol OnlyClassProtocol : class {
}

什么是 copy on write时候?

写时复制, 指的是 swift 中的值类型, 并不会在一开始赋值的时候就去复制, 只有在需要修改的时候, 才去复制.

associatedtype(关联类型) 的作用?

简单来说就是 protocol 使用的泛型.

例如定义一个列表协议

protocol ListProtcol {
    associatedtype Element
    func push(_ element: Element)
    func pop(_ element: Element) -> Element?
}

实现协议的时候, 可以使用 typealias 指定为特定的类型, 也可以自动推断, 如

class IntList: ListProtcol {
    // 使用 typealias 指定为 Int
    typealias Element = Int 
    var list = [Element](https://luowei.github.io/)

    func push(_ element: Element) {
        self.list.append(element)
    }

    func pop(_ element: Element) -> Element? {
        return self.list.popLast()
    }
}

class DoubleList: ListProtcol {
    var list = [Double](https://luowei.github.io/)

    func push(_ element: Double) {// 自动推断
        self.list.append(element)
    }

    func pop(_ element: Double) -> Double? {
        return self.list.popLast()
    }
}

使用泛型也可以

class AnyList<T>: ListProtcol {
    var list = [T](https://luowei.github.io/)
    func push(_ element: T) {
        self.list.append(element)
    }
    func pop(_ element: T) -> T? {
        return self.list.popLast()
    }
}

可以使用 where 字句限定 Element 类型, 如:

extension ListProtcol where Element == Int {
    func isInt() ->Bool {
        return true
    }
}

public 和 open 的区别

这两个都用于在模块中声明需要对外界暴露的函数. 区别在于, public 修饰的类, 在模块外无法继承,. open 则可以任意继承 公开度来说, public < open

Self 的使用场景

Self 通常在协议中使用, 用来表示实现者或者实现者的子类类型.

例如, 定义一个复制的协议

protocol CopyProtocol {
    func copy() -> Self
}

如果是结构体去实现, 要将Self 换为具体的类型

struct SomeStruct: CopyProtocol {
    let value: Int
    func copySelf() -> SomeStruct {
        return SomeStruct(value: self.value)
    }
}

KVO与KVC

参考:https://www.jianshu.com/p/d657ad764aaa
KVC(NSKeyValueCoding),KVO就是基于KVC来实现的。

在调用 setValue: forKey: 的时,程序优先调用 setName: 方法,如果没有找到 setName: 方法 KVC会检查这个类的 + (BOOL)accessInstanceVariablesDirectly 类方法看是否返回YES(默认YES),返回YES则会继续查找该类有没有名为_name的成员变量,如果还是没有找到则会继续查找_isName成员变量,还是没有则依次查找name,isName。上述的成员变量都没找到则执行setValue:forUndefinedKey: 抛出异常,如果不想程序崩溃应该重写该方法。假如这个类重写了+ (BOOL)accessInstanceVariablesDirectly 返回的是NO,则程序没有找到setName:方法之后,会直接执行setValue:forUndefinedKey: 抛出异常。

在调用valueForKey:的时,会依次按照getName,name,isName的顺序进行调用。如果这3个方法没有找到,那么KVC会按照countOfName,objectInNameAtIndex来查找。如果查找到这两个方法就会返回一个数组。如果还没找到则调用+ (BOOL)accessInstanceVariablesDirectly 看是否返回YES,返回YES则依次按照_name,_isName,name,isName顺序查找成员变量名,还是没找到就调用valueForUndefinedKey:;返回NO直接调用valueForUndefinedKey:

KVC的一些注意:KVC在设置时可能会设置错误的Key值导致程序崩溃,需要重写valueForUndefinedKey:和setValue:forUndefinedKey:。还有一种是在设置中不小心传递了nil,这时候需要重写setNilValueForKey:。

KVO的底层是通过isa-swizzling实现的
我们知道Method-Swizzling,它的实现其实是一个替换函数实现指针的过程;
isa-swizzling顾名思义就是替换isa的过程,oc中每个对象都有一个名为isa的指针,指向该对象的类。 NSObject就是一个包含isa指针的结构体,Class也是一个包含isa指针的结构体。每一个类实际上也是一个对象,每一个类也有一个名为isa的指针。Class是元类(meteClass)的实例,Class里也有个isa的指针, 指向meteClass(元类),元类也有isa指针,最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身。 isa-swizzling就是在运行时动态地修改 isa 指针的值,达到替换对象整个行为的目的。 所以KVO的实现原理是:添加观察后,系统实现了一个子类,然后将被观察的类对象的isa指针指向这个子类。再重写了setter方法。并在当中添加了willChangeValueForKey:和didChangeValueForKey:。移除观察就是将isa指针指向原来的类对象中。

Swift 如何让自定义对象支持字面量初始化?

有几个协议, 分别是: ExpressibleByArrayLiteral 可以由数组形式初始化 ExpressibleByDictionaryLiteral 可以由字典形式初始化 ExpressibleByNilLiteral 可以由nil 值初始化 ExpressibleByIntegerLiteral 可以由整数值初始化 ExpressibleByFloatLiteral 可以由浮点数初始化 ExpressibleByBooleanLiteral 可以由布尔值初始化 ExpressibleByUnicodeScalarLiteral ExpressibleByExtendedGraphemeClusterLiteral ExpressibleByStringLiteral 最后三种都是由字符串初始化, 上面两种包含有 Unicode 字符和特殊字符

?? 的作用

当可选值为nil时,输出后面给定的值

var test : String? = nil
print(test ?? "optional = nil")
//输出    optional = nil

swift dynamic的作用

dynamic的作用就是让swift代码也能有oc中的动态机制,常用的就是KVO。 使用dynamic关键字标记属性,使属性启用Objc的动态转发功能; dynamic只用于类,不能用于结构体和枚举,因为它们没有继承机制,而Objc的动态转发就是根据继承关系来实现转发。

深入理解swift 的派发机制

参考:https://kemchenj.github.io/2016-12-25-1/
编译型语言有三种基础的函数派发方式: 直接派发(Direct Dispatch), 函数表派发(Table Dispatch) 和 消息机制派发(Message Dispatch)。Java 默认使用函数表派发, 但你可以通过 final 修饰符修改成直接派发. C++ 默认使用直接派发, 但可以通过加上 virtual 修饰符来改成函数表派发. 而 Objective-C 则总是使用消息机制派发, 但允许开发者使用 C 直接派发来获取性能的提高。Swift里的派发方式如下图: 结起来有这么几点:

  • 值类型总是会使用直接派发, 简单易懂
  • 而协议和类的 extension 都会使用直接派发
  • NSObject 的 extension 会使用消息机制进行派发
  • NSObject 声明作用域里的函数都会使用函数表进行派发.
  • 协议里声明的, 并且带有默认实现的函数会使用函数表进行派发

Swift常用框架

iOS开发常用的第三方库、插件 - OC
iOS开发常用的第三方库、插件 - Swift
Swift常用的iOS开发第三方库
整理swift常用的第三方框架
swift 常用第三方框架

Swift面向协议编程

Swift 面向协议编程的那些事
SwiftGG Swift 中的面向协议编程:引言
Swift - 面向协议编程(POP)
Swift面向协议编程(POP)
Swift 面向协议编程/组件化

最新的SwiftUI相关知识点

SwiftUI 与 Combine 编程

其他

异或-交换两变量的值原理
不建议在实际使用中采用这样的写法。这种奇技淫巧虽然看起来十分巧妙,但这样并不见得能够比朴素的交换来的更快。而且这种方法存在一个缺陷:如果 a 和 b 引用的是同一个变量的话,使用这种方法会使得这个变量变为0。