Swift: Using Protocols to create private class instance - ios

I am really new to Swift, and trying to understand how to work things out with protocol extensions. So here is my protocol:
public protocol User: class {
var name : String {get}
}
private class MyUser: User {
var name : String
init(name: String) {
self.name = name
}
}
extension User where Self:User {
func createUser(name: String) -> User {
return MyUser(name)
}
}
How do I call this function createUser from a totally different class/protocol, where I want to create an instance of User?

You could remake the function in whatever class you need it for
func createUser(name: String) -> User {
return MyUser(name)
}
then say
let newUser = createUser("WhateverName")
or you could do:
let newUser = User().createUser("WhateverName")

Related

How do I implement abstract methods with different parameters/return types

I have parent class called "Item", and child class called "YTListItem". I want to have a functions called instantiate() which will be implemented in each child class, and return a view controller. The type of the view controller will be different depending on which child class is calling the instantiate() method. My issue is that swift does not seem to recognize me overriding the function if it has different parameters or return types.
The error occurs when I override the function, I get the error "Method does not override any method from its superclass".
class Item {
var name: String
var description: String
init(name: String = "Test text", description: String = "Test description of the item") {
self.name = name
self.description = description
}
func instantiate() {}
}
class YTListItem: Item {
var address: String
init(name: String = "Test text", description: String = "Test text", address: String = "SivTGfXxYz0") {
self.address = address
super.init(name: name, description: description)
}
override func instantiate(storyboard: UIStoryboard) -> YTDescViewController? {
let vc = storyboard.instantiateViewController(identifier: "desc") as? YTDescViewController
return vc
}
}
Is there a way to do this? I've seen mentions of protocols when searching how to make it work but I'm new to swift so I'm hoping to be able to get through it using methods I've already learned.
First of all I don't think you should be instantiating a ViewController from you model class. Instead you should be injecting your model to the ViewController. But for your particular scenario you can just return a UIViewController from instantiate function rather than it's subclass (YTDescViewController). And I think you should use protocols for your scenario something like this:
protocol Item {
var name: String {get set}
var description: String {get set}
var storyboard : UIStoryboard {get set}
func instatntiate() -> UIViewController
}
extension Item {
var viewControllerID : String {
return "desc"
}
}
struct YTListItem: Item {
var name: String
var description: String
var storyboard: UIStoryboard
func instatntiate() -> UIViewController {
return storyboard.instantiateViewController(identifier: viewControllerID)
}
}
You can also use associatedType to customize the return type of instantiate (or the parameters) in Item protocol. But in that case you would need to implement type erasures to hold generic reference to class/struct objects that implement Item protocol.

iOS - Error with Protocols and generics when used as a dependency

I have a protocol ContainerService whose sessionService object is used in our network layer. I'm trying to inject the sessionService into an object that encapsulates the network layer. I used a base protocol to avoid the error
Protocol `SessionService` can only be used as a generic constraint because it has Self or associated type requirements
This issue was fixed by adding
var sessionService: SessionServiceBase { get set }
as a requirement for ContainerService
However when I try to use sessionService as a property of TestClass, I come across the same error. Also I ran into another error
Member connectSession cannot be used on value of Protocol typeSessionService; use a generic constraint instead
Here is a sample code
protocol NetworkService: AnyObject {
var sessionService: SessionServiceBase { get set }
}
protocol SessionServiceBase: AnyObject { }
protocol SessionService: SessionServiceBase {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
}
struct CustomToken: Tokenable {
var token: String
var secretKey: String
}
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
}
}
class CustomNetworkService: NetworkService {
var sessionService: SessionServiceBase = CustomSessionService()
}
class ConsumerClass {
var networkService: NetworkService?
init(networkService: NetworkService) {
self.networkService = networkService
}
func test(){
let tokens = CustomToken(token: "", key: "")
guard let sessionService: SessionService = networkService?.sessionService as? SessionService else {
return
}
sessionService.connection(credential: tokens)
}
}
Is there a work around this generic error issue? If this code is run on a playground there will be two error inside test func.
1.
Protocol SessionService can only be used as a generic constraint because it has Self or associated type requirements
2.
Member `connection` cannot be used on value of protocol type `SessionService`; use a generic constraint instead.
The SessionService protocol can only be used a generic type constraint because the associated type implies that there is not one specific type of SessionService but any number, analogous to how there is not one Array type in Swift. See my answer here for some more details about this.
For instance, we could not guarantee that the sessionService instance actually connected with a CustomToken because its associated type is not specified. Hence, Swift does not allow it.
There are some alternatives though. At some point you have to make something concrete. To implement what you may be looking for with generics, observe the following:
// Protocols
protocol SessionService: AnyObject {
associatedtype T: Tokenable
func connection(credential: T)
}
protocol NetworkService: AnyObject {
associatedtype Service: SessionService
var sessionService: Service { get set }
}
protocol Tokenable {
var token: String { get set }
var key: String { get set }
// Provide an initializer to require that a token can be made in this way
init(token: String, key: String)
}
// Custom types
class CustomSessionService: SessionService {
func connection(credential: CustomToken) {
print(credential.token)
print(credential.doSomething())
}
}
class CustomNetworkService: NetworkService {
var sessionService: CustomSessionService = CustomSessionService()
}
struct CustomToken: Tokenable {
var token: String
var key: String
var secretKey: String = ""
init(token: String, key: String) {
self.token = token
self.key = key
}
var myStr: String = "aaa"
func doSomething() -> String { myStr }
}
// Type erasure
class AnyNetworkService<ServiceType>: NetworkService where ServiceType: SessionService {
typealias Service = ServiceType
var sessionService: ServiceType
init(service: ServiceType) { self.sessionService = service }
convenience init<T: NetworkService>(networkService: T) where T.Service == ServiceType { self.init(service: networkService.sessionService) }
}
// Combines everything
class ConsumerClass<Service: SessionService> {
var networkService: AnyNetworkService<Service>?
init(networkService: AnyNetworkService<Service>) {
self.networkService = networkService
}
func test() {
let tokens = Service.T(token: "asd", key: "fff") // create a token for the specific Service passed in
guard let sessionService = networkService?.sessionService else { return }
sessionService.connection(credential: tokens)
}
}
// Example
let networkService = CustomNetworkService()
let consumer = ConsumerClass<CustomSessionService>(networkService: .init(networkService: networkService))
consumer.test()
The last line will call connection(credential:) in CustomSessionService.
The only potential drawback is that there is no longer a single ConsumerClass but instead many. Also, a Token created in test() cannot have its other properties assigned for the its type is not known to you. However, given its flexibility for using different Network and SessionService concrete types, it may be of use.

Create singleton in swift 4 in a class having public init

I am having a public class in a different code like this -
public class Person {
public required init() {}
}
This class is in a framework and cannot be modified.
Now the problem is I have been told to inherit from this class and create a singleton object of the new class
public class Jon: Person {
public static var shared: Jon = Jon()
required init() {}
}
But the required init is letting users create multiple instances of the class Jon.
Can anybody suggest on how to do this in swift 4.
I found a solution to achieve this. Look into my solution hopefully it will serve your purpose.
public class ParentClass {
public required init() {}
}
class SubClass: ParentClass {
static let sharedInstance = SubClass(foo: "")
#available(*, unavailable)
required init() { }
//You can change the init parameter as per your requirement
private init(foo: String) {
super.init()
//Write your initialization code
}
}
If you dont want to pass any argument to the init you can do something like this.
private init(_ foo: String? = nil) {
super.init()
//Write your initialization code
}
Now you initialize your object without passing any argument like
static let sharedInstance = SubClass()
One solution is to use Composition rather than Inheritance as the design solution. Since the Person object is supposed to be singleton as well we make it immutable and initialise it in the private init method using the required init
public class Jon {
static let instance = Jon()
private init() {
person = Person()
}
public let person: Person
}
And it can be used like this (with a name property for instance)
var jon = Jon.instance
jon.person.name = "John"
or Person could be made private and computed properties added to the singleton class
public class Jon {
static let instance = Jon()
private init() {
person = Person()
}
private let person: Person
var name: String? {
get { return person.name }
set { person.name = newValue}
}
}
and then the name property is accessed directly
var jon = Jon.instance
jon.name = "John"

Implementing a default equals method for a protocol

I've been trying to implement a protocol and protocol extension that provides a default == method in swift. Something like this:
protocol NameProtocol: Equatable {
func getName() -> String
}
extension NameProtocol{}
func ==<T: NameProtocol>(lhs: T, rhs: T) -> Bool{
return lhs.getName() == rhs.getName()
}
Then a class like this:
class NamedObject: NSObject, NameProtocol {
let name: String
init(name: String) {
self.name = name
super.init()
}
func getName() -> String {
return self.name
}
}
However, the custom == method is never called:
let one = NamedObject(name: "Name")
let two = NamedObject(name: "Name")
if one == two {
NSLog("EQUAL")
}
else{
NSLog("NOT EQUAL")
}
Am I doing something wrong here?
UPDATE:
From the answers I got it looks like what i'm trying to accomplish isn't really possible. The only way to come close is to subclass (which has its obvious drawbacks). I'm going to keep a lookout for a proper solution.
Because the == operator from the superclass takes precedence over that of the protocol. And for NSObject, == means pointer equal.
If you remove the inheritance from NSObject, it works as expected:
class NamedObject: NameProtocol {
let name: String
init(name: String) {
self.name = name
super.init()
}
func getName() -> String {
return self.name
}
}
I can't find any documentation on the order of precedence when there are multiple implementations for ==. This is just my experience.
Edit: instead of defining == for the protocol, define your own base class to override NSObject's default behavior:
class MyBaseClass: NSObject {
func getName() -> String {
fatalError("You must override this method")
}
}
func == (lhs: MyBaseClass, rhs: MyBaseClass) -> Bool {
return lhs.getName() == rhs.getName()
}
class NamedObject: MyBaseClass {
let name: String
init(name: String) {
self.name = name
}
override func getName() -> String {
return self.name
}
}
Your class derives from NSObject. You override isEqual: (not Swift ==) to change an NSObject-derived class's idea of what instance equality means.
Your strategy to move this off to a protocol extension, however, can never work because Objective-C knows nothing of Swift protocol extensions. Your code sitting in the protocol extension is completely invisible to Objective-C.
So, basically, by making this class derive from NSObject, you've shot your protocol extension strategy in the foot.

Swift generics: EXC_BAD_ACCESS when trying to access class property

It seems a class, which uses generics in swift, sometimes cannot properly determine object type.
Consider the following model structure:
class BaseModel: NSObject, Equatable, Printable {
var id: String = ""
override var description: String {
return "id: \(id)"
}
override func isEqual(object: AnyObject?) -> Bool {
if let object = object as? BaseModel {
return object.id == id
}
else {
return super.isEqual(object)
}
}
}
class Image: BaseModel {
var image: UIImage!
}
I also have parsers, which should parse/serialize objects:
class AbstractParser<T: BaseModel where T: Equatable>: NSObject {
func convertFromParseObject(object: NSObject) -> T {
var entity = T()
......
return updateEntityWithParseObject(object, entity: entity)
}
func updateEntityWithParseObject(object: NSObject, entity: T) -> T {
fatalError("This method must be overridden")
}
}
class ImageParser<T: Image>: AbstractParser<Image> {
override func updateEntityWithParseObject(object: NSObject, entity: Image) -> Image {
println("\(entity)")
println("\(entity.id)")
// The line below outputs BaseModel, shouldn't it be Image instead?
println("\(NSStringFromClass(entity.classForCoder))")
// EXC_BAD_ACCESS here:
println("\(entity.image)")
return entity
}
}
The app crashes when I try to access entity.image.
For some reasons Swift thinks that entity object is BaseModel, not Image.
Playground file: https://drive.google.com/file/d/0B6agzpK_lR6JQUlhMFoxaGw1akU/view?usp=sharing

Resources