kockburn
kockburn

Reputation: 17626

Static Class vs Static Struct for static tool operations?

I need to create a "tools" class or struct with only static methods. As someone coming from a PHP background, structs don't really exist.

I was reading about this Why Choose Struct Over Class? but they do not talk about static structs or static classes.

Which should I use for static methods that are never instanced and why?

Example:

struct BasicTools {
    static func split(str: String) -> [String]{
        return str.characters.split{$0 == ","}.map(String.init)
    }
}

vs

class BasicTools {
    static func split(str: String) -> [String]{
        return str.characters.split{$0 == ","}.map(String.init)
    }
}

In use:

let StrArr: [String] = BasicTools.split("example,example2,example3")

Upvotes: 3

Views: 922

Answers (2)

dfrib
dfrib

Reputation: 73196

As R Menke has already pointed out; since you have no instance of these utility classes/structure, it doesn't make much of a difference.

One possible vote for using classes over structs would be for the very specific situation where you declare your static functions in a protocol which you specifically constrain to class usage (protocol ...: class): the counterpart for structures (protocol ...: struct) is not available.

/* only class types can conform to this protocol */
protocol MyClassProtocol: class {
    static func split(str: String) -> [String]
}
extension MyClassProtocol {
    static func split(str: String) -> [String]{
        return str.characters.split{$0 == "."}.map(String.init)
    }
}

/* Use custom implementation */
class BasicTools: MyClassProtocol {
    class func split(str: String) -> [String]{
        return str.characters.split{$0 == ","}.map(String.init)
    }
}

/* Use default implementation */
class OtherBasicTools: MyClassProtocol { }

BasicTools.split("foo,bar")         // ["foo", "bar"]
OtherBasicTools.split("foo.bar")    // ["foo", "bar"]

In your context (class/struct of only utility tools), the above :class constraint for MyClassProtocol is not really relevant. It is, however, relevant if the MyClassProtocol protocol---in addition to containing static tools---were to be used as a delegate in a context were a strong reference to this delegate would create a retain cycle. In such a case, we need to constrain the delegate protocol to only be available (for conformance) to reference types, which allow us to use weak references to the delegate itself. In this case, naturally our toolbox must be use in class context (and not in a structure).

E.g., consider the following example, where we have some static toolbox of functions we want to make available to all our different kind of delegates:

/* Delegate toolbox: static/class functions for use
   with your delegates */
protocol MyBasicDelegateTools {
    static func delegateCalled() -> ()
}
extension MyBasicDelegateTools {
    static func delegateCalled() -> () {
        print("Logging: delegate was called")
    }
}

Used here by some delegate:

/* Delegate with access to your basic delegate tools */
protocol MyDelegate: class, MyBasicDelegateTools {
    func arrUpdated(baz: [Int])
}

/* It's inherrent here that we let the reference to
   the delegate be weak, to avoid a retain cycle.    */
class Foo {
    private var baz : [Int] = [] {
        didSet {
            if let _ = delegate?.arrUpdated(baz) {
                delegate?.dynamicType.delegateCalled()
            }
        }
    }
    weak var delegate: MyDelegate?
}

/* Why? Since 'foo' in 'Bar' keeps a strong reference to the 'Foo' instance */
class Bar: MyDelegate {
    var foo : Foo
    var idx: Int = 0 {
        didSet {
            print("idx updated: \(idx)")
        }
    }

    init(foo: Foo) {
        self.foo = foo
        self.foo.delegate = self
    }

    // MyDelegate
    func arrUpdated(baz: [Int]) {
        idx = baz.count
    }
}

Example usage:

var a = Foo()
var b = Bar(foo: a)

a.baz.append(1)
    // idx updated: 1                  <-- from custom delegate
    // Logging: delegate was called    <-- from delegate toolbox

Upvotes: 1

R Menke
R Menke

Reputation: 8391

If you only use static functions there is no difference at all.

More structure can be accomplished with struct and typealias. (also true for class but no pun there)

struct MathUtils {

    static func someMaths(withInt int:Int) -> Int {
        return int
    }
}

struct StringUtils {

    static func someEditing(withString string:String) -> String {
        return string
    }
}

struct Utils {

    typealias Maths = MathUtils
    typealias Strings = StringUtils

}

Utils.Maths.someMaths(withInt: 10)

Upvotes: 3

Related Questions