superpuccio
superpuccio

Reputation: 12992

Refer to 'self' in a static context

I'm here to understand why the solution I had implemented won't work. Basically I have a class called MyClass and in this class I'd like to have a static dictionary created from a plist file. Like this:

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

If I do so, compiler will complain that:

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

But if I change myDic var and create a static method returning that dic, all is fine:

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

Two questions here:

  1. What does this sintax mean in the compiler error? '(MyClass) -> () -> (MyClass)'
  2. What's the difference between the two cases? Why doesn't the former work and the latter is fine?

Thanks.

Upvotes: 2

Views: 1707

Answers (3)

staticVoidMan
staticVoidMan

Reputation: 20274

Lets look at a simpler (working) example keeping your core issue the same:

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

Firstly, lets note that Bundle(for: AnyClass) takes an object type.


1. Regarding Variables

Variables at top-level, access self as ClassName which is an instance type, whether it is declared as let/var/lazy/computed, static or not.

So:

static var bundle = Bundle(for: self)

is same as:

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

Both are invalid and generate the following error:

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

For sure, this is because we're passing an instance type instead of the expected Object type.

Solution:

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

2. Regarding static functions

As for a static function, this is a bit different.

The metatype that you call the static method on is available to you in the method as self (it's simply passed as an implicit parameter).

Ref: https://stackoverflow.com/a/42260880/2857130

In my example, we have:

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

When you call ClassName.getBundle(), ClassName.Type is implicitly passed to the function.
Now within the static function, self is of type ClassName.Type which is an Object type and can be applied directly in Bundle(for:), or similar functions that accept an Object type as it's parameter.

So, static functions access self as ClassName.Type which is same as ClassName.self, just not apparent as it's passed implicitly.

You can confirm this behavior of self in a static function, and furthermore even observe how self behaves in a normal function in the following example:

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()

Also shows us that normal functions access self as ClassName which is an instance type, similar to variables.


Summary:

  • Bundle(for:) takes an object type
  • Variables at top-level, access self as ClassName which is an instance type
  • Normal functions access self as ClassName which is an instance type
  • Static functions access self as ClassName.Type which is an Object type, as it's passed implicitly to the function

Upvotes: 1

David Pasztor
David Pasztor

Reputation: 54745

You can simply use the class name instead of self.

You also shouldn't be using NSDictionary and then casting to a Swift Dictionary. Use PropertyListDecoder instead.

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

Upvotes: 1

Shehata Gamal
Shehata Gamal

Reputation: 100533

You can do

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

As you can't access self inside a static variable , or use the bundle identifier

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

Upvotes: 1

Related Questions