【问题标题】:Refer to 'self' in a static context在静态上下文中引用“自我”
【发布时间】:2019-02-19 11:06:58
【问题描述】:

我来这里是为了了解为什么我实施的解决方案不起作用。基本上我有一个名为MyClass 的类,在这个类中我想要一个从plist 文件创建的静态字典。像这样:

class MyClass {        
    static var myDic: [String: String] = NSDictionary(contentsOfFile: Bundle(for: self).path(forResource: "filename", ofType: "plist")!) as! [String: String]    
}

如果我这样做,编译器会抱怨:

Cannot convert value of type '(MyClass) -> () -> (MyClass)' to expected argument type 'AnyClass' (aka 'AnyObject.Type')

但如果我更改 myDic var 并创建一个返回该 dic 的静态方法,一切都很好:

class MyClass {
    static func myDic() -> [String: String] {
        return NSDictionary(contentsOfFile: Bundle(for: self).path(forResource: "PlayerRolesWithColors", ofType: "plist")!) as! [String: String]
    }
}

这里有两个问题:

  1. 编译器错误中的这个 sintax 是什么意思? '(MyClass) -> () -> (MyClass)'
  2. 这两种情况有什么区别?为什么前者不起作用而后者很好?

谢谢。

【问题讨论】:

  • 好问题,值得回答:)

标签: ios swift class


【解决方案1】:

让我们看一个保持核心问题不变的更简单(有效)的示例:

class ClassName {
    static var bundle = Bundle(for: ClassName.self)
    
    static func getBundle() -> Bundle {
        return Bundle(for: self)
    }
}

首先,请注意Bundle(for: AnyClass) 采用对象类型。


1。关于变量

顶层变量,访问self作为实例类型ClassName,无论声明为let/var/lazy/computed,静态与否。

所以:

static var bundle = Bundle(for: self)

等同于:

static var bundle = Bundle(for: ClassName())

两者都无效并产生以下错误:

无法将类型“ClassName”的值转换为预期的参数类型“AnyClass”(又名“AnyObject.Type”)

当然,这是因为我们传递的是实例类型而不是预期的 Object 类型。

解决办法:

static var bundle = Bundle(for: ClassName.self)

2。关于静态函数

对于静态函数,这有点不同。

您调用静态方法的元类型在方法中作为self 可供您使用(它只是作为隐式参数传递)。

参考:https://stackoverflow.com/a/42260880/2857130

在我的例子中,我们有:

static func getBundle() -> Bundle {
    return Bundle(for: self)
}

当您调用ClassName.getBundle() 时,ClassName.Type 会隐式传递给函数。
现在在静态函数中,self 的类型为 ClassName.Type,它是一个 Object 类型,可以直接应用于 Bundle(for:),或接受 Object 类型作为其参数的类似函数。

因此,静态函数以ClassName.Type 的形式访问self,这与ClassName.self 相同,只是因为它是隐式传递的,所以并不明显。

您可以在static 函数中确认self 的这种行为,甚至可以在以下示例中观察self 在正常函数中的行为:

class ClassName {
    static func check() {
        print("static function check")
        print(type(of: self)) //ClassName.Type
        //same as
        print(type(of: ClassName.self)) //ClassName.Type
        
        //test
        print(type(of: self) == type(of: ClassName.self)) //true
    }
    
    func check() {
        print("normal function check")
        print(type(of: self)) //ClassName
        
        //test
        print(type(of: self) == type(of: ClassName.self)) //false
    }
}

ClassName.check()
ClassName().check()

还向我们展示了普通函数以ClassName 的形式访问self,这是一个实例类型,类似于变量。


总结:

  • Bundle(for:) 采用对象类型
  • 顶级变量,以ClassName 访问self,这是一个实例类型
  • 普通函数以 ClassName 的形式访问 self,这是一个实例类型
  • 静态函数以 ClassName.Type 的形式访问 self,这是一个 Object 类型,因为它被隐式传递给函数

【讨论】:

  • 请注意static 函数和class 函数之间存在很大差异。
  • @Sulthan 当然,必须有(而且我们还没有谈到class 函数)但是......在他的问题的背景下,如果那是@ 987654358@ 或 class func 作为 type(of: self) 在这两种情况下仍然是 ClassName.Type
  • 不在子类中,但你是对的,这可能超出了这个问题的范围。
【解决方案2】:

你可以的

class MyClass {
    static var myDic: [String: String] = NSDictionary(contentsOfFile: Bundle(for:MyClass.self).path(forResource: "filename", ofType: "plist")!) as! [String: String]
}

因为您无法在静态变量中访问self,或者使用捆绑标识符

class MyClass {
    static var myDic: [String: String] = NSDictionary(contentsOfFile: Bundle(identifier: "comThisBundle")!.path(forResource: "filename", ofType: "plist")!) as! [String: String]
}

【讨论】:

    【解决方案3】:

    您可以简单地使用类名而不是self

    您也不应该使用 NSDictionary 然后转换为 Swift Dictionary。请改用PropertyListDecoder

    class MyClass {
        static let myDic = try! PropertyListDecoder().decode([String:String].self, from: try! Data(contentsOf: Bundle(for: MyClass.self).url(forResource: "filename", withExtension: "plist")))
    }
    

    【讨论】:

    • 不幸的是,这是一个非常糟糕的解决方案(即使其他解决方案目前是不可能的)。我还在等待github.com/apple/swift-evolution/blob/master/proposals/… 实现。
    • @Sulthan 是的,对于泛型方法,这种解决方法不适合,如果实施该提案会很棒
    • @DávidPásztor 感谢您将我指向我不知道的 PropertyListDecoder。
    猜你喜欢
    • 2011-09-10
    • 1970-01-01
    • 1970-01-01
    • 2014-12-25
    • 2019-03-17
    • 2011-11-30
    相关资源
    最近更新 更多