Swift 3.0中的函数签名与函数引用

前端之家收集整理的这篇文章主要介绍了Swift 3.0中的函数签名与函数引用前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

在Apple官方的《The Swift Programming Language》中提到了Swift中函数的定义以及如何调用的方式,并且也大概提到了函数引用的方式以及如何通过一个函数引用对象进行函数间接调用。不过,在此文中并未对函数符号的识别做详细描述,这样当我们用一个函数引用去指向一组重载的函数中的某一个时就可能会引发一些问题。这里我将为大家详细描述Swift中函数签名以及如何在一组重载的函数集中找到我们想要的那个函数


在Swift 3.0中,函数可以通过两种方式的任一种进行定位:一种是通过函数签名,还有一种是通过指定具体的函数类型。

什么是函数签名?我们知道,Swift中函数声明包括三大部分:函数名,形参列表,返回类型。而形参列表中,每个一个形参又作为该形参的标签,这在Swift中称为参数标签argument label)。一个函数函数加上其形参的标签标识即为该函数的签名,这一点与Objective-C的方法很类似。比如,我们看一下以下这组函数

func myFunc() {
    print("This is myFunc!")
}

func myFunc(a: Int) {
    print("This is myFunc,a = \(a)")
}

func myFunc(value of: Float) {
    print("This is myFunc,f = \(f)")
}
这三个函数函数名都叫myFunc,第一个函数的参数列表为空,所以,它的函数签名就是myFunc。第二个函数,它具有一个形参a,并且该形参名就作为它的参数标签,所以其函数签名为myFunc(a:),这里注意,每个参数标签后面的冒号不能省,否则就会被Swift编译器认为这是一个函数调用,而不是以函数签名作为一个函数对象。第三个函数的形参有自己的一个形参标签value,并且该形参的类型为Float,其函数签名为myFunc(value:)。

当我们知道了函数签名之后,我们在程序中直接就可以将函数签名作为一个函数对象进行调用,比如:

        myFunc(a:)(10)
        myFunc(value:)(-0.5)
这里大家可以看到,一个函数签名在Swift语句中已经作为一个标准的函数对象进行使用了,后面直接跟( )即可做函数调用。在Swift 3.0中,函数对象以及函数对象引用在做实际调用过程中不需要再添加参数标签,并且如果你添加了参数标签,编译器反而会报错。下面我们就来定义指向这三个函数函数引用对象:
        var myFun1: () -> Void = myFunc
        myFun1()
        myFun1 = myFunc as () -> Void
        myFun1()
        
        let myfun2 = myFunc(a:)
        myfun2(100)
        
        let myfun3 = myFunc(value:)
        myfun3(0.5)
这段代码就出现了本文要描述的关键现象!各位注意,这里myFun1这个函数对象引用要指向不带任何参数的myFunc时,要么必须指明该函数对象引用的具体类型,要么通过 as具体指明myFunc函数签名的具体类型,否则编译器会报错——“对myFunc的使用产生歧义”。由于在Swift中,光一个函数名就可以表示该函数对象本身,但如果此函数被重载,那么光一个函数名显然就会对应多个不同的函数实体,因此我们需要使用函数签名,要么通过函数具体类型加以区分。

那么通过函数类型对函数对象加以区分是不是万能的呢?我们知道,Objective-C中方法签名的强大之处在于,它仅仅通过标签,而不是通过类型对各个重载的方法进行区分的。在Swift中其实在此基础上又添加了对类型的判别,但是如果碰到参数类型都相同,但签名不同的情况,通过指明具体类型显然就会失效!下面我们再增加一个函数,来看看这道奇观。

func myFunc() {
    print("This is myFunc!")
}

func myFunc(a: Int) {
    print("This is myFunc,f = \(of)")
}

func myFunc(none: Void) {
    print("no parameter function!")
}
看到myFunc(none:)这个函数各位可能会有些吃惊,当然这个在《The Swift Programming Language》中也是木有提到的,但是对于Swift编程语言来说,没有做不到,只有想不到,只要你脑洞够大,还能创造更奇葩的写法~但这个例子能充分说明很多问题了,呵呵。大家没有看错,Swift可以用Void来声明一个形参,但要注意,这里的Void其实跟C语言中使用(void)表示一个空参数列表的形式差不多,在none: Void后面是不允许再增加任何形参了,否则编译即会报错。但有了标签函数签名自然就会不同,所以这里的myFunc(none:)与myFunc是不一样的,并且做直接调用的时候,我们这个none标签还不能省。但这里先要说的是,当这个函数出现之后,我们上面定义的myFun1立马就会出现报错——“对myFunc的使用是有歧义的”。此时,我们暂且把上面定义的myFun1先删除掉,然后看以下调用
        let myfun2 = myFunc(a:)
        myfun2(100)
        
        let myfun3 = myFunc(value:)
        myfun3(0.5)
        
        let myFun4 = myFunc(none:)
        myFun4()
        
        // 直接对myFunc调用完全没问题
        myFunc()
        
        // 直接对myFunc(none:)调用时,需要加none标签。
        // 同时,一个空元组即可对应Void类型。
        // 在Swift中Void类型本身就是用()空元组来描述的
        myFunc(none: ())
因为此时,myFunc与myFunc(none:)的类型是一毛一样!所以类型再怎么换都不会好使。而且由于myFunc是木有任何标签,所以在这里即便用myFunc(_:)也同样会报错!那么对于函数myFunc()难道就这么完蛋了?我们无法用一个函数引用指向它了么?其实,Swift 3.0还藏了一招。既然当这个函数存在重载的情况,那么我们是否显式地对它做一个缺省标签的指示就能让它复活呢?这个办法显然是行之有效的!
func myFunc(_: Void) {
    print("This is myFunc!")
}

func myFunc(a: Int) {
    print("This is myFunc,f = \(of)")
}

func myFunc(none: Void) {
    print("no parameter function!")
}
我们对之前的myFunc参数列表中增加_标签,表示缺省。这样一来,我们就可以这么玩儿了:
        let myFun1 = myFunc(_:)
        myFun1()
        
        let myfun2 = myFunc(a:)
        myfun2(100)
        
        let myfun3 = myFunc(value:)
        myfun3(0.5)
        
        let myFun4 = myFunc(none:)
        myFun4()
        
        // 由于标签是缺省的_,所以直接对myFunc调用完全没问题
        myFunc()
        
        // 直接对myFunc(none:)调用时,需要加none标签。
        // 同时,一个空元组即可对应Void类型。
        // 在Swift中Void类型本身就是用()空元组来描述的
        myFunc(none: ())
这样一来,问题就都解决了。

上面是对于函数的情况,那么对于类或结构体的成员方法也同样如此。

class ViewController: NSViewController {
    
    func myFunc(_: Void) {
        print("This is myFunc!")
    }
    
    func myFunc(a: Int) {
        print("This is myFunc,a = \(a)")
    }
    
    func myFunc(value of: Float) {
        print("This is myFunc,f = \(of)")
    }
    
    func myFunc(none: Void) {
        print("no parameter function!")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myFun1 = self.myFunc(_:)
        myFun1()
        
        let myfun2 = self.myFunc(a:)
        myfun2(100)
        
        let myfun3 = self.myFunc(value:)
        myfun3(0.5)
        
        let myFun4 = self.myFunc(none:)
        myFun4()
        
        // 由于标签是缺省的_,所以直接对myFunc调用完全没问题,当然使用myFunc(_: ())也是OK的。
        myFunc()
        
        // 直接对myFunc(none:)调用时,需要加none标签。
        // 同时,一个空元组即可对应Void类型。
        // 在Swift中Void类型本身就是用()空元组来描述的
        myFunc(none: ())
    }
}
上面的 self. 可省。

最后,我这里建议各位当你们碰到某一个函数方法遇到重载(overload)的情况时,倘若含有无参数的形式,那么最好用(_: Void)的形式指明,这么做有两大好处,首先能直接通过函数签名func(_:)来直接找到它,然后,也是最重要的,可完全避免函数使用歧义的情况发生。

此外,从类型系统上,函数func myFunc(_: Void)与func myFunc()的函数类型也是完全一样的,都是( () ) -> ()。这表示其形参类型为()(即Void),返回类型也是()(即Void)。


下面给各位提一下在Swift 3.0中如何对一个泛型函数进行引用。在Swift编程语言中,对一个泛型函数进行特化之后必须立马做调用操作,而不能将一个特化后的函数作为一个函数引用类型。为了能够对一个泛型函数进行使用,我们只能使用as操作符对它做显式的类型转换,而不是显式地对它做特化操作。通过as操作,也能将当前泛型函数的泛型参数做实例化。比如下面代码所示:

func foo<T: Integer>(param: T) {
 print("param value is: \(param - 1)")
}

// 以下这条语句是错误的:
// let ref = foo<Int>(param:)

// 我们应该这么使用泛型函数的引用
let ref = foo(param:) as (Int) -> Void
ref(100)

func foo<T: BinaryFloatingPoint>(f: T) -> T {
 print("The floating value is: \(f)")
 return f + T(1.0)
}

let ref2 = foo(f:) as (Float) -> Float
print("ref2 = \(ref2(9.0))")

let ref3 = foo(f:) as (Double) -> Double
print("ref3 = \(ref3(10.0))")

// 下面举一个更复杂的例子:
var uintObj: UInt = 1

let uintPtr = withUnsafePointer(to: &uintObj) {
    (ptr: UnsafePointer<UInt>) -> UnsafePointer<UInt> in
    return ptr
}

// 这里参数中的throws以及返回类型之前的throws都不能省
let ref0 = uintPtr.withMemoryRebound(to:capacity:_:) as (Int32.Type,Int,(UnsafePointer<Int32>) throws -> UnsafePointer<Int32>) throws -> UnsafePointer<Int32>

// 这里,对于函数调用表达式之后再跟else语句块的情况下就不能使用trailing closure了
guard let ptr = try? ref0(Int32.self,1,{
    return $0
}) else {
    exit(0)
}
print("The value is: \(ptr.pointee)")
我们通过这个代码例子即能看到,对于一个泛型函数的引用应该怎么去做。

猜你在找的Swift相关文章