Can I implement default initialization in protocol in Swift - swift

I have a variety of classes that all conform to a single protocol and share the same initialization method. Is there a way to implement the initialization in the protocol? So I don't have to copy the code in every class. This is what I have so far
protocol someProtocol {
init(data: Data)
}
class ObjectA: someProtocol {
let data: Data
required init(data: Data) {
self.data = data
}
}
class ObjectB: someProtocol {
let data: Data
required init(data: Data) {
self.data = data
}
}

You can’t do this as the protocol and protocol extension have no knowledge about the properties in the objects that conform to them and so you cannot initialise all the story properties.
I’m sure there are other runtime reason about type inference too but this one is probably the most simple to explain.

Related

Why do I get a Swift error with nested associated types

I would like to create a couple protocols to have a more type-safe friendly Notification interface.
The idea is to have a single type which from it, can be retrieved the notifications name, user info type (which will be the only entry of the dict), and a delegate for simplifying the observed response with the user info as a parameter.
Here are two protocols to help with building this interface.
protocol StructuredNotificationIdentifier {
associatedtype Delegate: StructuredNotificationUserInfoType
static var notification: Notification.Name { get }
}
protocol StructuredNotificationUserInfoType {
associatedtype UserInfoType
}
Here is an implementation of a notification with its name, user info type, and protocol.
protocol SomethingDidHappenDelegate: StructuredNotificationUserInfoType where UserInfoType == String {
func somethingDidHappen(_ value: UserInfoType)
}
enum SomethingDidHappenNotification: StructuredNotificationIdentifier {
typealias Delegate = SomethingDidHappenDelegate
static var notification: Notification.Name { Notification.Name("SomethingDidHappenNotification") }
}
And here is one more example of how the interface becomes simplified.
extension NotificationCenter {
func post<T: StructuredNotificationIdentifier>(identifier: T, userInfo: T.Delegate.UserInfoType) {
post(name: type(of: identifier).notification, object: nil, userInfo: ["info": userInfo])
}
}
Now, my error happens on the enum declaration.
Type 'SomethingDidHappenNotification' does not conform to protocol 'StructuredNotificationIdentifier'
The problem as I understand it is coming from the StructuredNotificationIdentifier.Delegate requiring conformance of StructuredNotificationUserInfoType which also needs a defined associatedtype. The part that I'm confused about is why this is not resolved with the SomethingDidHappenDelegate declaration?
I understand I can set the user info type in multiple places, however since this is a pattern I'll be using often, I wanted the least amount of redundant code needed for the setup.
Why am I getting this error and is there an elegant way to resolve it?
Protocols don't conform to protocols, so although it seems like
typealias Delegate = SomethingDidHappenDelegate
satisfies the Delegate associated type requirement of StructuredNotificationIdentifier, it doesn't, because Delegate must conform to StructuredNotificationUserInfoType. And SomethingDidHappenDelegate, being a protocol, does not conform to any protocols.
I tried to come up with a workaround, and this is what I've got:
protocol StructuredNotificationIdentifier {
associatedtype Delegate
associatedtype UserInfoType
static var notification: Notification.Name { get }
}
protocol SomethingDidHappenDelegate {
func somethingDidHappen(_ value: String)
}
enum SomethingDidHappenNotification: StructuredNotificationIdentifier {
typealias Delegate = SomethingDidHappenDelegate
typealias UserInfoType = String
static var notification: Notification.Name { Notification.Name("SomethingDidHappenNotification") }
}
extension NotificationCenter {
func post<T: StructuredNotificationIdentifier>(identifier: T, userInfo: T.UserInfoType) {
post(name: type(of: identifier).notification, object: nil, userInfo: ["info": userInfo])
}
}
I moved UserInfoType into StructuredNotificationIdentifier as well and deleted the constraint on Delegate, because I don't think it is possible to access T.Delegate.UserInfoType when Delegate is a protocol. The error message I got when I tried to do that was pretty clear:
Associated type 'UserInfoType' can only be used with a concrete type or generic parameter base
The following makes your code compilable
enum SomethingDidHappenNotification<D: SomethingDidHappenDelegate>: StructuredNotificationIdentifier {
typealias Delegate = D
static var notification: Notification.Name { Notification.Name("SomethingDidHappenNotification") }
}

My structure does not conform to protocol 'Decodable' / 'Encodable'

I was trying to use Codable to save my data from the app I am creating but when I put Codable into my structure I keep getting the error:
Type 'ReminderGroups' does not conform to protocol 'Decodable'
and
Type 'ReminderGroups' does not conform to protocol 'Encodable'
struct ReminderGroups: Codable {
var contentsArray: [ReminderItem] = []
var reminderName: String = ""
var reminderItem: UIImage = #imageLiteral(resourceName: "Folder")
}
In order for a class or a struct to conform to a protocol, all properties of that class or struct must conform to the same protocol.
UIImage does not conform to Codable, so any class or struct that has properties of type UIImage won’t conform as well. You can replace the image with image data or the image’s base64 representation (as String).
I’ll show you the first option. I suppose you don’t want to write those if lets every time, so let’s add two little extensions to UIImage and Data that will speed up future conversions.
extension UIImage {
var data: Data? {
if let data = self.jpegData(compressionQuality: 1.0) {
return data
} else {
return nil
}
}
}
extension Data {
var image: UIImage? {
if let image = UIImage(data: self) {
return image
} else {
return nil
}
}
}
Change reminderItem’s type from UIImage to Data.
From now on, when you need to access the image, write something like imageView.image = reminderGroup.reminderItem.image. And when you need to save an instance of UIImage to reminderItem, write something like reminderGroup.reminderItem = image.data! (the bang operator (exclamation mark) is needed because the computed property data is optional).
Also make sure ReminderItem does conform to Codable. You didn’t provide the declaration of that type, so I can’t say whether it conforms of not.

How can an NSManagedObject conform to the NSItemProviderReading protocol?

After creating an NSManagedObject subclass of a Core Data entity, how can I make it conform properly to the NSItemProviderReading protocol? The protocol has a required initializer that must be declared directly in the class. But which designated initializer should NSItemProviderReading's init(itemProviderData:, typeIdentifier:) call?
This is what I have below:
import Foundation
import CoreData
#objc(Something)
public class Something: NSManagedObject, NSItemProviderReading {
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
// MARK: - Item Provider Reading
public static var readableTypeIdentifiersForItemProvider: [String] {
return []
}
public required init(itemProviderData data: Data, typeIdentifier: String) throws {
// This seems very hack-y…
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
self.init(context: context)
}
}
Is calling self.init(context:) really the right way to go here?
To conform to NSItemProviderReading protocol requires conformance to the init you mentioned and also the static var readableTypeIdentifiersForItemProvider:
init(itemProviderData: Data, typeIdentifier: String)
static var readableTypeIdentifiersForItemProvider: [String]
The documentation indicates this with the Required note.
I don't see anything wrong with your init so if it compiles and works with how you plan on using the class I don't see an issue. What I would recommend is using dependency injection to pass Core Data context throughout your app, to the view controllers that need them. That way you don't have to do the annoying AppDelegate code every time (and it's safer because you can be sure the context is always available).

Any ideas on how to have a generic class, implementing a protocol, be cast to that protocol and allow retrieving a property?

I have a simple Result object which should contain an object (class, struct or enum) and a Bool to say whether it was cancelled or not. I need to interrogate this object along its path (before it gets to its destination, where the destination knows what kind of object to expect) to determine whether it was cancelled or not (without worrying about the accompanying object at that moment). My object looks like:
import Foundation
#objc protocol Resultable {
var didCancel: Bool { get }
}
class Result<T>: Resultable {
let didCancel: Bool
let object: T?
init(didCancel: Bool, object: T?) {
self.didCancel = didCancel
self.object = object
}
}
The idea being that my Result object can wrap the didCancel flag and the actual object (which can be of any type), and the fact that it implements the Resultable protocol means that I can interrogate it at any point to see whether it was cancelled by casting it to Resultable.
I understand (while not liking it) that the protocol has to be prefixed with #objc so that we can cast to it (according to the Apple docs). Unfortunately, when I run the code below (in a playground or in a project), I get a nasty "does not implement methodSignatureForSelector:" error message:
let test = Result(didCancel: false, object: NSArray())
println(test.didCancel)
// transform the object into the form it will be received in
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel)
}
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject (and this is not a requirement that Apple makes explicit). This is obviously a problem for a generic class.
Is there anything I am missing here? Any way to get this to work? Failing that, are there any workarounds (although I strongly believe that this kind of thing should be possible - perhaps we can hope that Apple will do away with the #objc protocol casting requirement at some stage)?
UPDATE
It looks like this is solved with Swift 1.2
You can now cast to a non-ObjC protocol, and without the object having to inherit from NSObject.
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject
This is not true. For example, the following code works as expected:
#objc protocol MyProtocol {
var flag: Bool { get }
}
class MyClass: MyProtocol {
let flag = true
}
let foo:AnyObject = MyClass()
if let p = foo as? MyProtocol {
println(p.flag)
}
The problems is that: Any methods/properties declared in Swift Generic classes are invisible from Objective-C. Hence, from the perspective of #objc protocol Resultable, didCancel property declared in Result<T> is invisible. That's why methodSignatureForSelector: is called.
The workaround here is very annoying: You have to have non Generic base class for Result that implements didCancel.
#objc protocol Resultable {
var didCancel: Bool { get }
}
class ResultBase: Resultable {
let didCancel: Bool
init(didCancel: Bool) { self.didCancel = didCancel }
}
class Result<T>: ResultBase, Resultable {
// ^^^^^^^^^^^^
// You have to explicitly conforms `Resultable` here as well for some reason :/
let object: T?
init(didCancel: Bool, object: T?) {
self.object = object
super.init(didCancel: didCancel)
}
}
let test = Result(didCancel: false, object: NSArray())
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel) // -> outputs "false"
}

Call methods on Protocol Type

I've searched quite a bit around and played within a playground but I had no success, so I ask here:
Any way to have variable containing a non-#objc protocol metatype and then call class/static methods from it?
e.g.:
protocol MyProtocol {
class func myFunc() -> Int
}
enum MyEnum: Int, MyProtocol {
case A
case B
static func myFunc() -> Int { return A.rawValue }
}
let foo: MyProtocol.Type = MyEnum.self
let bar = foo.myFunc()
p.s. it says the last call is unimplemented, so should I expect it in a future Swift release?
UPDATED for Swift Version 2.0 and above
Swift 2.0+ allows methods to be declared as static in the protocol definition. These must be satisfied with static/class methods in objects that implement the protocol.
You cannot satisfy a protocol definition for a instance method with a static method or vice-versa, which makes this an incomplete answer for the question above.
If you want to try this just use the keyword "static" in your protocol definition for methods you will implement as static or class methods in your conforming objects:
protocol InstanceVsStatic {
func someInstanceFunc()
static func someStaticFunc()
}
enum MyConformingEnum: InstanceVsStatic {
case someCase
static func someStaticFunc() {
// code
}
func someInstanceFunc() {
// code
}
}
class MyConformingClass: InstanceVsStatic {
class func someStaticFunc() {
// code
}
func someInstanceFunc() {
// code
}
}
struct MyConformingStruct: InstanceVsStatic {
static func someStaticFunc() {
// code
}
func someInstanceFunc() {
// code
}
}
You can have an instance method call a static/class method:
This allows you to execute static code when you need to conform to a protocol that requires an instance method.
struct MyConformingStruct: InstanceVsStatic {
static func doStuffStatically(){
// code
}
static func someStaticFunc() {
// code
}
func someInstanceFunc() {
MyConformingStruct.doStuffStatically()
}
}
Swift 1.2
Other than indirectly as above, there is no way to use static (class) methods to conform to a protocol in pure swift version 1.2 and below. It is a known bug / unimplemented feature: https://openradar.appspot.com/20119848
Class methods are only allowable in classes and protocols; an enum is neither.
Class methods are one type of Swift "type methods"; the other type is a static method, which is what you've declared in your enum. Class and static methods are different.
See the Swift methods docs for a few different patterns and examples.
p.s. it says the last call is unimplemented, so should I expect it in a future Swift release?
Yes.