Using Class as RawValue of Enum not working - ios

I am trying to use my custom class ChallengeUIElement as rawValue of my enum ChallengeUIElementType.
Therefore, I tried everything that Xcode wanted me to do, but I still get error messages thrown right at my face. It starts to hurt...
Anyways, I followed along this tutorial and get lots of error messages that I do not understand.
After googling for a while, I found some stack overflow entries about classes as rawValue types of enums.
However, I still was unable to get my enum with class type working.
My errors:
Here is my code:
enum ChallengeUIElementType: ChallengeUIElement, CaseIterable {
typealias RawValue = ChallengeUIElement
case Default = "DefaultElementCell"
}
class ChallengeUIElement: Equatable, ExpressibleByStringLiteral {
static func == (lhs: ChallengeUIElement, rhs: ChallengeUIElement) -> Bool {
return lhs.identifier == rhs.identifier && lhs.height == rhs.height && lhs.type == rhs.type
}
var height: CGFloat
var identifier: String
var type: UICollectionViewCell.Type
public init(stringLiteral value: String) {
let components = value.components(separatedBy: ",")
if components.count == 1 {
if components[0] == "DefaultElementCell" {
self.identifier = components[0]
self.type = DefaultElementCell.self
self.height = 380
}
}
}
public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
Also, I do not understand why I could use RawRepresentable as protocol with this approach, but not with this one?
Any help would be highly appreciated.

The errors you get are mostly about the way you incorrectly declared your initialisers. They don't really have much to do with creating an enum with a class raw value.
Because your initialisers are required by the protocol, they need to be marked as required. You don't need to do this if your class is final. So either mark the class as final or mark all the initialisers as required.
The two convenience initialisers need to be marked as convenience:
public convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
These are "convenience" initialisers because they call another initialiser declared in the class.
Additionally, the public init(stringLiteral value: String) initialiser does not initialise all the properties. Think about what happens if the if statements are not run. You need to give your properties (height, identifier and type) some default values.

Related

Issue with `Protocol can only be used as a generic constraint because it has Self or associated type requirements`

I'm trying to generate a ViewModel that conforms to a Protocol Protocoling, the protocol is generic, and has an associated type.
There are a few ViewModel's that conform to the protocol, so I am trying to create a factory for the viewModel.
I have encotuntered the following error by Swift:
Protocol can only be used as a generic constraint because it has Self or associated type requirements
Example code:
protocol Protocoling {
associatedtype modulingType
var data: modulingType { get }
}
enum MyTypes {
case myName
case myAddress
}
class NameViewModel: Protocoling {
let data: String
init(name: String) {
data = name
}
}
class AddressViewModel: Protocoling {
let data: [String]
init(address: [String]) {
data = address
}
}
class DataFactory {
func viewModel(forType type: MyTypes) -> Protocoling {
switch type {
case .name: return NameViewModel(name: "Gil")
case .address: return AddressViewModel(address: ["Israel", "Tel Aviv"])
}
}
}
The error is in func viewModel(forType type: MyTypes) -> Protocoling.
Is there a way to solve this issue?
You can use a protocol with an associated type (PAT) as a return type like that without more constraint because the compiler needs to know which type to use.
In your case you must use a technic called the type erasure to be able to work with any Protocoling:
class AnyProtocoling: Protocoling {
let data: Any
init<U: Protocoling>(_ viewModel: U) {
self.data = viewModel.data as Any
}
}
class DataFactory {
func viewModel(forType type: MyTypes) -> AnyProtocoling {
switch type {
case .myName:
return AnyProtocoling(NameViewModel(name: "Gil"))
case .myAddress:
return AnyProtocoling(AddressViewModel(address: ["Israel", "Tel Aviv"]))
}
}
}
This will allow you to "erase" the associated type of your protocol and return an Any version of your view model.
In order to understand why the PAT needs to work like that I like the following example: the Equatable protocol (which is a PAT):
static func ==(lhs: Self, rhs: Self) -> Bool
This function uses the Self type which is an associated type. You want to use it in the next generic function:
func areEquals(left: Equatable, right: Equatable) -> Bool {
return left == right
}
Here the compiler will trigger this error: Protocol can only be used as a generic constraint because it has Self or associated type requirements. Why? Lets take this example:
struct tomato: Equatable {}
struct salad: Equatable {}
areEquals(left: tomato(), right: salad())
There is no reason to compare tomatoes and salads. The associated type Self is not the same. To avoid this error in this case you need to constraint the Self type as following:
func areEquals<T: Equatable>(left: T, right: T) -> Bool
Now you know the T are equatables and with the same associated types.
This is very simple to fix, in your concrete factory implementation you just need to specify a generic for your factory that has to conform to protocol protocoling, see code below :
Swift 4
protocol Protocoling {
associatedtype modulingType
var data: modulingType { get }
}
enum MyTypes {
case myName
case myAddress
}
class NameViewModel: Protocoling {
let data: String
init(name: String) {
data = name
}
}
class AddressViewModel: Protocoling {
let data: [String]
init(address: [String]) {
data = address
}
}
class DataFactory<T> where T: Protocoling {
func viewModel(forType type: MyTypes) -> T? {
switch type {
case .myName: return NameViewModel(name: "Gil") as? T
case .myAddress: return AddressViewModel(address: ["Israel", "Tel Aviv"]) as? T
default: return nil /* SUPPORT EXTENSION WITHOUT BREAKING */
}
}
}
It's a first step into the wonderful world of abstraction with protocols. You really create some amazing things with it. Though, I have to say, that personally it's not as intuitive as something like inheritance, it's a great little mind bending puzzle for creating decoupled and abstract systems, that are actually far more powerful.
Swift is a great introductory language, and I believe that it's protocol and extension mechanisms make it one of the more complex and interesting languages.
This design pattern is a great way setting up things like dependency injection.

NSURL extension to adopt StringLiteralConvertible

According to this post from NSHipster, we have extended the NSURL class to initialize NSURL objects using literals like this:
// the following is a full fledged NSURL object
let url: NSURL = "http://nshipster.com/"
Unfortunately, the post was written when Swift was first announced and it no longer compiles.
I was able to get my own custom object to conform to StringLiteralConvertible, and it looked like this:
final class Dog {
let name: String
init(name: String) {
self.name = name
}
}
// MARK: - StringLiteralConvertible
extension Dog: StringLiteralConvertible {
typealias UnicodeScalarLiteralType = StringLiteralType
typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
convenience init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(stringLiteral: value)
}
convenience init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(stringLiteral: value)
}
convenience init(stringLiteral value: StringLiteralType) {
self.init(name: value)
}
}
This works great. For example, the following 2 lines of code will create a Dog object:
let dog = Dog(name: "Bob")
let dog: Dog = "Bob"
Unfortunately, using this strategy via extending NSURL was met with errors:
extension NSURL: StringLiteralConvertible {
public typealias UnicodeScalarLiteralType = StringLiteralType
public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
convenience public init?(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(stringLiteral: value)
}
convenience public init?(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(stringLiteral: value)
}
convenience public init?(stringLiteral value: StringLiteralType) {
self.init(string: value)
}
}
I've been trekking through the compiler errors, solving them 1 guess at a time. However, I can't get past the following error that occurs for each of the initializers:
Initializer requirement 'init(...)' can only be satisfied by a 'required' initializer in the definition of non-final class 'NSURL'
Adding the required keyword will give the error that you may not declared required initializers within extensions.
Looking for some directions :|
StringLiteralConvertible support
Unfortunately, StringLiteralConvertible support for NSURL seems to be not possible in the current Swift version (2.2). The closest I can get is the following:
extension NSURL: StringLiteralConvertible {
public convenience init(stringLiteral value: String) {
self.init(string: value)!
}
public convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(string: value)!
}
public convenience init(unicodeScalarLiteral value: String) {
self.init(string: value)!
}
}
But the compiler complains:
Playground execution failed: OS X Playground.playground:5:24: error: initializer requirement 'init(stringLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(stringLiteral value: String) {
^
OS X Playground.playground:3:24: error: initializer requirement 'init(extendedGraphemeClusterLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(extendedGraphemeClusterLiteral value: String) {
^
OS X Playground.playground:7:24: error: initializer requirement 'init(unicodeScalarLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(unicodeScalarLiteral value: String) {
And required initializers cannot be implemented in an extension.
Alternative solution
We can simplify string-to-URL conversion from the other side!
extension String {
var url: NSURL? {
return NSURL(string: self)
}
}
var url = "http://google.coom/".url
print(url?.scheme) // Optional("http")
An another alternative is to subclass NSURL and conform to StringLiteralConvertible there.
class URL: NSURL, StringLiteralConvertible {
<#...#>
}

Accessing Swift OptionSetType in Objective-C

In my Swift class, I have an OptionSetType defined for fulfillment options.
struct FulfillmentOption : OptionSetType {
let rawValue: Int
static let Pickup = FulfillmentOption(rawValue: 1 << 0)
static let Shipping = FulfillmentOption(rawValue: 1 << 1)
static let UserShipping = FulfillmentOption(rawValue: 1 << 2)
}
I then create a variable to add/remove and read options. This works as expected.
var options: FulfillmentOption = []
options.insert(FulfillmentOption.Pickup)
options.contains(FulfillmentOption.Pickup)
However I need to access the options variable from one of my Objective-C classes. Since OptionSetType is not defined in Objective-C, the variable is not visible to any of my Objective-C classes.
What is the best way for me to expose this to Objective-C? Should I stop using OptionSetType altogether?
I've considered doing creating public and private variables like this to convert between the two. I don't love this, but it's the best I've come up with thus far.
private var _options: FulfillmentOptions = []
private var options: UInt {
get {
// get raw value from _options
}
set {
// set raw value to _options
}
}
Is there a more elegant way to accomplish this? I'd like to avoid writing unnecessary code.
Not a direct answer to your question, but as an alternative you can
work the other way around. Define
typedef NS_OPTIONS(NSInteger, FulfillmentOption) {
FulfillmentOptionPickup = 1 << 0,
FulfillmentOptionShipping = 1 << 1,
FulfillmentOptionUserShipping = 1 << 2,
};
in an Objective-C header, this would be imported into Swift as
public struct FulfillmentOption : OptionSetType {
public init(rawValue: Int)
public static var Pickup: FulfillmentOption { get }
public static var Shipping: FulfillmentOption { get }
public static var UserShipping: FulfillmentOption { get }
}
More Information can be found in the "Using Swift with Cocoa and Objective-C" reference:
"Interaction with C APIs":
Swift also imports C-style enumerations marked with the
NS_OPTIONS
macro as a Swift option set. Option sets behave similarly to imported
enumerations by truncating their prefixes to option value names.
"Swift and Objective-C in the Same Project":
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
...
Structures defined in Swift
...
Objective-C can't see structs, but luckily, you can implement OptionSet with an #objc class instead.
Note that it is very important to implement -hash and -isEqual: because lots of the SetAlgebra default implementations that OptionSet inherits rely on them to work. If you don't implement them, print("\(Ability(.canRead) == Ability(.canRead))") prints false.
#objc
public final class Ability: NSObject, OptionSet {
// Don't use
//
// public static let canRead = Ability(rawValue: 1 << 0)
// public static let canWrite = Ability(rawValue: 1 << 1)
//
// because `Ability` is a `class`, not a `struct`,
// so `let` doesn't enforce immutability, and thus
//
// Ability.canRead.formUnion(Ability.canWrite)
//
// would break `Ability.canRead` for everyone!
public static func canRead(): Ability {
return Ability(rawValue: 1 << 0)
}
public static func canWrite(): Ability {
return Ability(rawValue: 1 << 1)
}
public var rawValue: Int
public typealias RawValue = Int
public override convenience init() {
self.init(rawValue: 0)
}
public init(rawValue: Int) {
self.rawValue = rawValue
super.init()
}
/// Must expose this to Objective-C manually because
/// OptionSet.init(_: Sequence) isn't visible to Objective-C
/// Because Sequence isn't an Objective-C-visible type
#objc
#available(swift, obsoleted: 1.0)
public convenience init(abilitiesToUnion: [Ability]) {
self.init(abilitiesToUnion)
}
// MARK: NSObject
// Note that it is very important to implement -hash and
// -isEqual: because lots of the `SetAlgebra`
// default implementations that `OptionSet` inherits
// rely on them to work. If you don't implement them,
// print("\(Ability(.canRead) == Ability(.canRead))")
// prints `false`
public override var hash: Int {
return rawValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let that = object as? Ability else {
return false
}
return rawValue == that.rawValue
}
// MARK: OptionSet
public func formUnion(_ other: Ability) {
rawValue = rawValue | other.rawValue
}
public func formIntersection(_ other: Ability) {
rawValue = rawValue & other.rawValue
}
public func formSymmetricDifference(_ other: Ability) {
rawValue = rawValue ^ other.rawValue
}
}
Then, I can instantiate this from Objective-C:
Ability *emptyAbility = [Ability new];
Ability *readOnlyAbility = [[Ability alloc] initWithAbilitiesToUnion:#[Ability.canRead]];
Ability *readWriteAbility = [[Ability alloc] initWithAbilitiesToUnion:#[Ability.canRead, Ability.canWrite]];

Cannot assign to property in protocol - Swift compiler error

I'm banging my head against the wall with the following code in Swift. I've defined a simple protocol:
protocol Nameable {
var name : String { get set }
}
and implemented that with:
class NameableImpl : Nameable {
var name : String = ""
}
and then I have the following method in another file (don't ask me why):
func nameNameable( nameable: Nameable, name: String ) {
nameable.name = name
}
The problem is that the compiler gives the following error for the property assignment in this method:
cannot assign to 'name' in 'nameable'
I can't see what I'm doing wrong... The following code compiles fine:
var nameable : Nameable = NameableImpl()
nameable.name = "John"
I'm sure it's something simple I've overlooked - what am I doing wrong?
#matt's anwer is correct.
Another solution is to declare Nameable as a class only protocol.
protocol Nameable: class {
// ^^^^^^^
var name : String { get set }
}
I think, this solution is more suitable for this case. Because nameNameable is useless unless nameable is a instance of class.
It's because, Nameable being a protocol, Swift doesn't know what kind (flavor) of object your function's incoming Nameable is. It might be a class instance, sure - but it might be a struct instance. And you can't assign to a property of a constant struct, as the following example demonstrates:
struct NameableStruct : Nameable {
var name : String = ""
}
let ns = NameableStruct(name:"one")
ns.name = "two" // can't assign
Well, by default, an incoming function parameter is a constant - it is exactly as if you had said let in your function declaration before you said nameable.
The solution is to make this parameter not be a constant:
func nameNameable(var nameable: Nameable, name: String ) {
^^^
NOTE Later versions of Swift have abolished the var function parameter notation, so you'd accomplish the same thing by assigning the constant to a variable:
protocol Nameable {
var name : String { get set }
}
func nameNameable(nameable: Nameable, name: String) {
var nameable = nameable // can't compile without this line
nameable.name = name
}
Here, i written some code, that might give some idea on Associated generic type Usage:
protocol NumaricType
{
typealias elementType
func plus(lhs : elementType, _ rhs : elementType) -> elementType
func minus(lhs : elementType, _ rhs : elementType) -> elementType
}
struct Arthamatic :NumaricType {
func addMethod(element1 :Int, element2 :Int) -> Int {
return plus(element1, element2)
}
func minusMethod(ele1 :Int, ele2 :Int) -> Int {
return minus(ele1, ele2)
}
typealias elementType = Int
func plus(lhs: elementType, _ rhs: elementType) -> elementType {
return lhs + rhs
}
func minus(lhs: elementType, _ rhs: elementType) -> elementType {
return lhs - rhs
}
}
**Output:**
let obj = Arthamatic().addMethod(34, element2: 45) // 79

XCTAssertEqual for custom objects in Swift

XCode 6, Beta 5
I have a unit test like this:
func testMyObjectsEqual()
{
//....
XCTAssertEqual(myObject, myOtherObject, "\(myObject) and \(myOtherObject) should be equal")
}
XCTAssertEqualObjects is no longer available in Swift since the language makes no distinction between scalars and objects.
So we have to use XCTAssertEqual which leads to the following error:
"Type MyObject does not conform to protocol Equatable"
The only workaround I have found is to inherit (MyObject) from NSObject so that I can do the following:
XCTAssert(myObject == myOtherObject, "\(myObject) and \(myOtherObject) should be equal")
So my question is: Is there a way (as of beta 5) to use XCTAssertEqual for custom types without having to rely on NSObject or littering all custom types with "==" overloads ?
If you want to compare references in Swift, you can use === operator. This is what happens when you subclass from NSObject (when you are comparing objects in Objective-C using XCTAssertEqual, you are comparing references).
But is this really what you want?
class MyObject {
var name: String
init(name: String) {
self.name = name
}
}
func testMyObjectsEqual() {
let myObject = MyObject(name: "obj1")
let myOtherObject = MyObject(name: "obj1")
let otherReferenceToMyFirstObject = myObject
XCTAssert(myObject === myOtherObject) // fails
XCTAssert(myObject === otherReferenceToMyFirstObject) // passes
}
I guess that when you are comparing custom objects, you probably should make them conform to the Equatable protocol and specify, when your custom objects are equal (in the following case the objects are equal when they have the same name):
class MyObject: Equatable {
var name: String
init(name: String) {
self.name = name
}
}
func ==(lhs: MyObject, rhs: MyObject) -> Bool {
return lhs.name == rhs.name
}
func testMyObjectsEqual()
{
let myObject = MyObject(name: "obj1")
let myOtherObject = MyObject(name: "obj1")
XCTAssertEqual(myObject, myOtherObject) // passes
}
Since Xcode 12.5 there is XCTAssertIdentical

Resources