Class bound protocol with private(set) requirement - swift

I have a Nameable protocol meant to be used on NSManagedObjects. This protocol is declared in its own Swift package and is meant to be imported by many other packages, so the protocol needs to be public. I also need to maintain the protocol approach rather than using a base class with inheritance. I would like to validate my names and throw errors using the below default implementation, but there seems to be no way to enforce that a developer uses set(_ name: String).
import CoreData
public protocol Nameable: NSManagedObject {
/// The name property of the entitiy
/// - Warning: **don't** set this directly, use `set(_ name: String)` to ensure the name is validated
///
/// - Note: This needs to be an `#NSManaged`
var name: String { get set }
static var defaultName: String { get }
static var maxNameLength: Int { get }
func set(_ name: String) throws
}
public extension Nameable {
// TODO: Localize
static var defaultName: String { "Untitled" }
static var maxNameLength: Int { 128 }
func set(_ name: String) throws {
guard !name.isEmpty else { throw NameError.nameEmpty }
guard name.count <= Self.maxNameLength else { throw NameError.nameTooLong }
self.name = name
try managedObjectContext?.save()
}
}
public enum NameError: Error {
case nameEmpty
case nameTooLong
}
I would like to use the protocol like so:
#objc(MyObject)
class MyObject: NSManagedObject, Nameable {
#NSManaged public private(set) var name: String
}
But since the protocol is public, name also needs to be publicly settable. The only solutions that I have come up with are "soft" (ie. warning comments, or something like #NSManaged var unsafeName: String { get set }). Is there any way to achieve the desired results that can be enforced by the compiler?

Related

Swift thinks my protocol has associated type requirements, but they're explicitly defined

I have the following protocol:
protocol Device: AnyObject, Identifiable where ID == String {
var id: String { get }
var name: String { get }
var isConnected: Bool { get }
func connect()
func disconnect()
}
When I use it like this:
class ConnectionManager: ObservableObject {
#Published private (set) var devices = [Device]()
}
I get this error:
Protocol 'Device' can only be used as a generic constraint because it has Self or associated type requirements
Removing the Identifiable definition gets rid of this warning, but I need that definition to satisfy usage in SwiftUI.
Aren't the associated type requirements already satisfied by explicitly defining ID as String?
I'm using Xcode 13.1 / Swift 5.5.

Swift type does not conform to protocol error at generic constraint but not at class itself

I'm having some troubles figuring this whole thing out here.
Starting from code:
Entity:
protocol EntityProtocol : class {
var id: String { get set }
var version: String { get }
var deleted: Bool { get set }
var uid: String { get set }
func Validate() -> [String: String]
}
extension EntityProtocol {
var version: String {
get { return "v0.0" }
set { }
}
func Validate() -> [String: String]{
//some default checking for default fields
}
}
typealias Entity = EntityProtocol & Codable
Product:
class Product: Entity {
var id: String = ""
var deleted: Bool
var uid: String = ""
func Validate() -> [String : String] {
//implementation
}
}
Up until now, no errors when compiling... Then I have class Repository<TEntity> where TEntity: Entity a base class which implements the actual repository functionality...
Now when I do class ProductRepo<Product> : Repository<Product> it shows an error here saying Type 'Product' does not conform to protocol 'EntityProtocol' However, still no error in the Product class itself.
P.S: I've tried to add the version field to product, still same error.
The reason I'm using protocol not class with inheritance is that Codable can not be inherited and will have to write init and serialize myself.
Anyone can tell me why this is happening and how to fix it? I'm confused that if Product does not conform to protocol, then why compiler does not complain in the Product class itself?
Your ProductRepo declaration is missing a type constraint on Product. You need to add the Entity constraint to it to make it conform to Repository.
class ProductRepo<Product: Entity> : Repository<Product> {
}
Unrelated to your question, but there's no need for having Entity as a type alias, you can simply make EntityProtocol conform to Codable. Also, for the version default implementation, there's no need for adding an empty setter, since the protocol only requires a getter.
protocol Entity: class, Codable {
var id: String { get set }
var version: String { get }
var deleted: Bool { get set }
var uid: String { get set }
func validate() -> [String: String]
}
extension Entity {
var version: String {
"v0.0"
}
func validate() -> [String: String]{
[:]
}
}
U tried adding version field to product, but u should also make version {get set} in EntityProtocol protocol then it will work
protocol EntityProtocol : class {
var id: String { get set }
var version: String { get }
var deleted: Bool { get set }
var uid: String { get set }
func Validate() -> [String: String]
}

How to make a dynamic generic?

I'm trying to write a protocol that allows me to version models in my app. In order to do that I wrote the following VersionManager.
class VersionManager<Type: Decodable> {
private var database: Database
init(database: Database) {
self.database = database
}
var versions: [Type] {
return []
}
}
After that I wrote a protocol that I can add to models:
protocol Versionable {
}
extension Versionable {
private var manager: VersionManager<Restaurant> {
return VersionManager<Restaurant>(database: Database.shared)
}
public var versions: [Restaurant] {
return manager.versions
}
}
Now, the problem I'm facing is that I tried passing the type dynamically instead of hardcoded, like I have now for Restaurant.
So I tried changing the protocol to this:
protocol Versionable {
var kind: Decodable.Type { get }
}
Then I wanted to pass kind to VersionManager. However, when I try that Xcode throws this error: Expected '>' to complete generic argument list.
Is there any other way to do this?
If you want to use generics inside a protocol, you need to use an associatedtype
protocol Versionable {
associatedtype Model: Decodable
}
extension Versionable {
private var manager: VersionManager<Model> {
return VersionManager<Model>(database: Database.shared)
}
public var versions: [Model] {
return manager.versions
}
}
The model that is going to implement the Versionable protocol will have to resolve this type:
struct SomeModel: Versionable {
typealias Model = Int
}
SomeModel().versions // [Int]
I'm guessing Restaurant in your example refers to the model that implements Versionable. In that case, you can just use the Self reference inside your protocol extension:
protocol Versionable: Decodable {
}
extension Versionable {
private var manager: VersionManager<Self> {
return VersionManager<Self>(database: Database.shared)
}
public var versions: [Self] {
return manager.versions
}
}
struct SomeModel: Versionable {}
SomeModel().versions // [SomeModel]
Please note that the Versionable protocol now requires the Decodable conformance because of the VersionManager<Type: Decodable> generic constraint.

How to resolve collision between protocol and class fields types?

I have a protocol and a class which I want to extend. The protocol requires field of some type and the class has a field with the same name and the type as Implicitly Unwrapped Optional of this type.
Can this class be extended by this protocol? If yes, then how?
If I try to write an extension, Xcode give an error of not conforming. But if I add the field into the extension, it gives an error of redeclaration.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
extension Person: Named {
// Type 'Finances.Account' does not conform to protocol 'Named'
}
Property names and types declared in a protocol must exactly be matched by the conforming classes.
So you cannot resolve the error without changing the property type in either the protocol or the conforming type. You could also rename one of the properties and add the matching property to the conforming type as a new field.
So either do:
protocol Named {
var name: String { get }
}
class Person {
var name: String
init(_ name:String) {
self.name = name
}
}
extension Person: Named {
}
Or
protocol Named {
var name: String { get }
}
class Person {
var _name: String!
}
extension Person: Named {
var name: String {
return _name
}
}
As #user28434 pointed out, there's a(n ugly) workaround. You can create a wrapper protocol that matches the optionality of the Person class, make that protocol inherit from the original protocol, declare the non-optional variable in an extension on the new protocol and make Person conform to the new protocol instead of the original Named.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
protocol Namedd: Named {
var name: String! { get }
}
extension Namedd {
var name: String {
return name!
}
}
extension Person: Namedd {
}

How to define variable that can be set and get in extension of protocol

I come from Java world. Now I am programming in Swift 4.
I would like to implement abstract class in Swift, I know in Swift there is no such concept of abstract class. But I know we could mimic this concept in Swift by using protocol. For example this is what I tried:
// With protocol, I can define functions that concrete class have to implement
protocol ProductProvider {
func getProductNumber() -> Int
}
// with protocol extension, I can define shared (computed) properties and functions among concrete classes that comply with this protocol
extension ProductProvider {
var maxProductCount: Int {
return 10
}
}
But now, I would like to have a shared variable that could be set & get ("shared" means to be shared with classes that comply with this protocol):
extension ProductProvider {
var maxProductCount: Int {
set(newValue) {
// set to what if I couldn't define private stored variable in extension to hold the most recent set value ?
}
get{
// how to return the most recent value set?
}
}
}
My question is in the comment of above code. How can I do that set and get for a variable in extension of protocol in Swift 4? If it is impossible, what are the workarounds possible?
The simplest way i think is define the variable in the protocol with the getter and setter. Then in your conform object you should declare the variable to conformance.
An Example:
protocol AbstractObject {
var maxProductCount: Int { get set }
}
struct ConformObject: AbstractObject {
var maxProductCount: Int
}
So now you can use your variable in your default implementations
extension AbstractObject {
mutating func addOne() -> Int {
self.maxProductCount += 1
return self.maxProductCount
}
}
Aside from the discussion if its a right way to achieve what you want, you can use object association.
public final class ObjectAssociation<T: AnyObject> {
private let policy: objc_AssociationPolicy
/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
self.policy = policy
}
/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {
get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}
And in your extension
extension SomeType {
private static let association = ObjectAssociation<NSObject>()
var simulatedProperty: NSObject? {
get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}
It is not possible to store Swift types via object association directly. You can store e.g. NSNumber instead of Int.
Source: https://stackoverflow.com/a/43056053/1811810