Swift基础碎片

1.函数的可变参数

同一个方法只能有一个可变参数,而且不限制可变参数在所有参数中的位置。在OC中可变参数只能作为方法中参数的最后一个。

func sum(input: Int...) -> Int {
    return input.reduce(0, +)
}
sum(input: 1,2,3,4,5)   // 15

2.inout
func switching(a: inout Int, b: inout Int) {
    (a, b) = (b, a)
}

var a = 10
var b = 20
switching(a: &a, b: &b)

inout表示Swift的可修改参数。而且这个例子中,通过tuple是a和b的值直接进行了交换,而在OC中我们还需要使用一个第三者变量进行临时存储。

3.柯里化

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回一个新的函数,这个函数能够处理剩余参数。
你可以理解它为一个返回值为闭包的函数,或者理解为一个函数对函数的调用。

看下面的例子:

func someFunc(_ index: Int) -> (Int) -> Int {
    return { num in
        return num + index
    }
}
let newFunc = someFunc(10)
let result1 = newFunc(5)    // 15

let result2 = someFunc(9)(8)    // 17

再来看一个比大小的例子:

func firstMoreThanSecond(_ num: Int) -> (Int) -> Bool {
    return {$0 > num}
}

let result3 = firstMoreThanSecond(10)(11)   // false 
let result4 = firstMoreThanSecond(8)(10)    // true

柯里化带来的优势:
对于柯里化,它可以将你的函数碎片化,将函数分层调用,所以提高了代码灵活性、复用性、降低依赖,而且代码也会相对简洁。
总之,柯里化可以很好地提现函数式编程思想。
最后借用Instance Methods are “Curried” Functions in Swift中的一个例子,体会体会好坏。

protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper<T: AnyObject>: TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()
    
    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case TouchUpInside
    case ValueChanged
    // ...
}


class Control {
    var actions = [ControlEvent: TargetAction]()
    
    func setTarget<T: AnyObject>(target: T,
                                 action: @escaping (T) -> () -> (), controlEvent: ControlEvent) {
        
        actions[controlEvent] = TargetActionWrapper(
            target: target, action: action)
    }
    
    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }
    
    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}


class DCSnail {
    func excute() {
        let button = Control()
        button.setTarget(target: self, action: DCSnail.tapAction, controlEvent: .TouchUpInside)
        button.performActionForControlEvent(controlEvent: .TouchUpInside)
    }
    
    func tapAction() {
        print("tapped!!!")
    }
}
DCSnail().excute()
4.函数嵌套

这里所说的函数嵌套与柯里化中所提到的函数分层调用不是一个概念,而是在函数中继续定义函数。来看下面一个函数:

func generateObjec(type: Int) -> String {
    if 0 == type {
        return zeroType()
    } else if 1 == type {
        return oneType()
    } else {
        return defaultType()
    }
}

func zeroType() -> String {
    return "Zero"
}

func oneType() -> String {
    return "One"
}

func defaultType() -> String {
    return "Two"
}

如果使用函数嵌套将会如下效果:

func generateObjec(type: Int) -> String {
    func zeroType() -> String {
        return "Zero"
    }
    func oneType() -> String {
        return "One"
    }
    func defaultType() -> String {
        return "Two"
    }
    
    if 0 == type {
        return zeroType()
    } else if 1 == type {
        return oneType()
    } else {
        return defaultType()
    }
}

函数嵌套在你函数主体内容过长,且本模块的功能与外部逻辑没有任何关系时,能发挥非常大的作用。它会使你的单个函数不在冗长,且将它们分成几个小型的模块,且定义在主函数之内,并不影响外部的关系。
所以这样的访问权限和这样的模块化会提高代码可读性和维护性。

5.闭包中的可选链
class A {
    func block() {
        //...
    }
}

class B {
    var letter: A?
}
let b = B()
let optionResult = b.letter?.block()    // nil

B类中有个可选类型的letter变量,当通过可选链对其函数进行调用时,将会得到nil。这是毋庸置疑的,那么继续看:

let bBlock1 = {(b: B) -> () in
    b.letter?.block()
}
let bBlock2 = {(b: B) -> ()? in
    b.letter?.block()
}

bBlock1(b)  // ()
bBlock2(b)  // nil

新建了两个block,它们的返回值分别为()和()?,最后将b传入得到的结果却是分别为()和nil。这个结果够不够出乎你的意料?

在包含可选链的block中,返回值是()或者Void的,返回值永远都不为nil;而返回值是()?或者Void?返回值为nil。所以虽然()和Void是一回事,但在这里并不是一回事。

6.@autoclosure

假如有一个尾随闭包参数的函数,只是根据闭包的返回值判断真假:

func trueOrFalse(_ condition: () -> Bool) {
    if condition() {
        print("pass")
    }
}

那么你可以定义个闭包来进行传值:

let conditionBlock = { () -> (Bool) in
    return 2 > 1
}

你也可以直接使用尾随闭包的属性,而且还可以闭包从语境中推断类型,一直简化:

trueOrFalse({ return 2 > 1 })
trueOrFalse({ 2 > 1 })

最后一直简化成这样:

trueOrFalse{ 2 > 1 }

好了,引入正题,将入使用了@autoclosure对闭包进行修饰:

func trueOrFalse2(_ condition: @autoclosure () -> Bool) {
    if condition() {
        print("pass")
    }
}

我们可以这样使用:

trueOrFalse2(2 > 1)

这是因为,@autoclosure修饰的闭包可以自动将 2>1转换为()->bool,以供函数使用。

7.@escaping
func perform(block: ()->()) {
    block()
}

func performAsync(block: @escaping ()->()) {
    DispatchQueue.main.async {
        block()
    }
}

class Test {
    var ID = "id"
    
    func method1() {
        perform {
            print(ID)
        }
        print("before method1")
        ID = "method1"
    }
    
    func method2() {
        performAsync {
            print(self.ID)
        }
        print("before method2")
        ID = "method2"
    }
    
    func method3() {
        performAsync {
            [weak self] in
            print(self?.ID ?? "nil")
        }
        print("before method3")
        ID = "method3"
    }
}
Test().method1()    // id
Test().method2()    // method
Test().method3()    // nil

在形式参数前写 @escaping 来明确闭包是允许逃逸的,闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。所以一定要注意启动异步任务回调所带来对调用时机的不同,要在合适的作用域和时机中对持有的变量进行使用。
另外,关于@escaping还需要注意,当你在协议或者父类中包含一个@escaping的闭包,那么在实现协议或者父类的子类中,该方法也必须被声明为@escaping,否则两个方法会被认为为两个函数。

8.自定义字面量(Literal)

Swift为我们提供了一组非常有用的接口,用来将字面量转换为特定的类型。

ExpressibleByArrayLiteral
ExpressibleByBooleanLiteral
ExpressibleByDictionaryLiteral
ExpressibleByFloatLiteral
ExpressibleByIntegerLiteral
ExpressibleByStringLiteral
ExpressibleByUnicodeScalarLiteral
ExpressibleByExtendedGraphemeClusterLiteral

以ExpressibleByStringLiteral为例,来自定义字符串的字面量:

class Dog: ExpressibleByStringLiteral {
    let name: String
    init(name value: String) {
        self.name = value
    }
    
    required convenience init(stringLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
    }
}
let dog: Dog = "Husky"
dog.name    // Husky

推荐阅读更多精彩内容