讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 2.8 函數中的函數 >

2.8 函數中的函數

可以在任何地方聲明函數,包括在函數體中聲明。聲明在函數體中的函數(也叫作局部函數)可以被相同作用域的後續代碼所調用,不過在作用域之外則完全不可見。

對於那些旨在輔助其他函數的函數,這是個非常優雅的架構。如果只有函數A需要調用函數B,那麼函數B就可以放在函數A中。

如下示例來自於我所寫的應用(僅保留了結構):


func checkPair(p1:Piece, and p2:Piece) -> Path? {
    // ...
    func addPathIfValid(midpt1:Point, _ midpt2:Point) {
    // ...
    }
    for y in -1..._yct {
        addPathIfValid((pt1.x,y),(pt2.x,y))
    }
    for x in -1..._xct {
        addPathIfValid((x,pt1.y),(x,pt2.y))
    }
    // ...
}  

第1個循環(for y)與第2個循環(for x)所做的事情是一樣的,不過起始值集合是不同的。我們可以在每個for循環中編寫整個功能,不過這麼做沒有必要,並且會導致重複(這種重複違背了DRY原則,即「不要重複自己」)。為了防止這種重複,我們可以將重複代碼重構到實例方法中,然後由兩個for循環調用,不過這麼做會將該功能所公開的範圍擴大,因為它只會由checkPair中的這兩個for循環所調用。對於這種情況,局部函數就是很好的折中。

有時,即便函數只會在一個地方調用,使用局部函數也是值得的。如下示例也來自於我所編寫的代碼(它是同一個函數的另外一個部分):


func checkPair(p1:Piece, and p2:Piece) -> Path? {
    // ...
    if arr.count > 0 {
        func distance(pt1:Point, _ pt2:Point) -> Double {
            // utility to learn physical distance between two points
            let deltax = pt1.0 - pt2.0
            let deltay = pt1.1 - pt2.1
            return sqrt(Double(deltax * deltax + deltay * deltay))
        }
        for thisPath in arr {
            var thisLength = 0.0
            for ix in 0..<(thisPath.count-1) {
                thisLength += distance(thisPath[ix],thisPath[ix+1])
            }
           // ...
        }
    }
    // ...
}  

上述代碼的結構很清晰(不過代碼使用了尚未介紹的一些Swift特性)。進入函數checkPair中,我有一個路徑的數組(arr),我需要知道每條路徑的長度。每條路徑本身都是點的一個數組,因此為了獲取其長度,我需要計算出每兩個點之間的距離總和。為了得到兩個點之間的距離,我使用了勾股定理。我可以使用勾股定理,在for循環(for ix)中進行計算。不過,我將其作為單獨的一個函數distance,這樣在for循環中就可以調用該函數了。

這麼做並沒有減少代碼的行數;事實上,聲明distance還會增加行數!嚴格來說,我並沒有重複自己;勾股定理會使用多次,不過代碼中只出現了一次,位於for循環中。不過,將代碼抽像為更加通用的距離計算功能使代碼變得更加整潔了:實際上,我是先說明要做什麼(要計算兩個點之間的距離),然後再去做。函數名distance為代碼賦予了含義;這麼做相比於直接寫出距離計算步驟來說,可理解性與可維護性都更好。

局部函數就是帶有函數值的局部變量(本章後面將會介紹這個概念)。因此,局部函數不能與相同作用域中的局部變量同名,相同作用域中的兩個局部函數也不能同名。