error within custom initialiser in extension of class - ios

Here is my sample code which brings my questions:
protocol Decodable {
init?(json: [String: AnyObject]) // remove this line will solve the problem
}
extension UIFont: Decodable { // remove `Decodable` will also solve the problem
convenience init?(json: [String: AnyObject]) { // Error: Initialiser requirement 'init(json:)' can only be satisfied by a `required` initializer in the definition of non-final class 'UIFont'
self.init(name: "whatever", size: 10)
}
}
The code above tested with Xcode 7.3 & Swift 2.2
Anyone have an idea about this error, why there is somehow a relation with protocol defined method ?

Related

optional delegates with struct types - swift

I am getting below error while making protocol as optionals.
Method cannot be marked #objc because the type of the parameter 1
cannot be represented in Objective-C
My code :
#objc protocol PopupDelegate : class {
#objc optional func popupItemSelected(item : PopupItem, identifier : String)
#objc optional func popupItemMultipleSelected(item : [PopupItem], identifier : String)
}
struct PopupItem : Hashable {
var name : String
var id : Int
var isSelected : Bool
init(name: String, id: Int, isSelected : Bool = false) {
self.name = name
self.id = id
self.isSelected = isSelected
}
}
I got one post with same issue in swift 2, but I can't implement this solution as Inheritance not allowed in struct.
I tried to add #objc flag to my struct but got below error
Only classes (and their extensions), protocols, methods, initializers,
properties, and subscript declarations can be declared #objc
Is there any way to implement optional delegates with struct types?
I think the error messages you posted are self explanatory, Struct is not available in Objective-C runtime so when you annotate a protocol with #objc compiler gives you warning that struct can't be passed as an argument to such protocol.
How to achieve optional behaviour in pure swift?
Officially there is no equivalent of objective-C optional in swift. But empty default extensions will help you achieve the same behavior.
protocol PopupDelegate {
func popupItemSelected(item : PopupItem, identifier : String)
func popupItemMultipleSelected(item : [PopupItem], identifier : String)
}
extension PopupDelegate {
func popupItemSelected(item : PopupItem, identifier : String) { }
func popupItemMultipleSelected(item : [PopupItem], identifier : String) { }
}
Now whoever confirms to PopupDelegate need not implement methods as default implementation is already provided and because its empty implementation it's almost same as optional.
One caveat of this approach would be though, if you call respondsToSelector this will return true as there exists a default implementation but with optional you would get appropriate response.

Protocol extension, does not conform to protocol, SwiftQueue library

I am trying to use the SwiftQueue library. The problem is Xcode says:
Type 'BackgroundJobCreator' does not conform to protocol 'JobCreator'
I have stripped back my class and made it as simplified as possible to find the problem.
class BackgroundJobCreator: JobCreator {
func create(type: String, params: [String : Any]?)->Job {
return BackgroundUploadService(params: params)
}
}
Xcode asks "Do you want to add protocol subs", when I say yes it generates this..
func create(type: String, params: [String : Any]?) -> Job {
[code]
}
The second I delete the old function, the error comes up again stating I am not conforming to protocol 'JobCreator'. The generated one is completely untouched. (I also can not have two as it raises "Invalid redeclaration of 'create(type:params:)'"
I even checked the library's source code to check the protocols were public
/// Protocol to create instance of your job
public protocol JobCreator {
/// method called when a job has be to instantiate
/// Type as specified in JobBuilder.init(type) and params as JobBuilder.with(params)
func create(type: String, params: [String: Any]?) -> Job
}
SwiftQueue Protocols - Github
If the autogenerated protocols are wrong? Then it must be a mistake by the Swift compiler (or whatever checks this stuff)? I raised the issue on the GitHub a couple months back, but I feel this might be an isolated problem on my end.
EDIT: Here is the minimal code request to run..
import Foundation
import SwiftQueue
class BackgroundJobCreator: JobCreator {
func create(type: String, params: [String : Any]?)->Job {
return BackgroundUploadService(params: params)
}
}
-
import Foundation
import SwiftQueue
class BackgroundUploadService: Job{
static let type = ""
private var params: [String: Any]?
required init(params: [String: Any]?) {
// Receive params from JobBuilder.with()
self.params = params
}
required convenience init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
}

Is that a bug in Swift Compiler?

I write a protocol (in Xcode playground),and want it be conform by specified class , eg: Root.
Because I used Swift 4.1 , So there are new syntax for this :
protocol Delegate where Self:Root{
var titleImage:UIImage? {get}
}
(PS:If you want to know more about this , please follow the link : How to make Swift protocol conformed by a specific kind of Class?)
And I also write class Root and Foo :
class Root:Delegate{
var titleImage:UIImage?
func willDo(){
let foo = Foo()
foo.delegate = self
foo.invoke()
}
}
class Foo{
var delegate:Delegate!
func invoke(){
if let image = delegate.titleImage{
print("have a image [\(image)")
}else{
print("not have a image")
}
}
}
Problem is that when I call :
let r = Root()
r.willDo()
Here are runtime error :
But when I changed Delegate code to this :
protocol Delegate{
var titleImage:UIImage? {get}
}
That's all right!!! But it can't limited Delegate to be conform by class Root anymore!
Could anyone can tell me Why is it? Why when add " Self:Root " to Delegate will trigger a BAD_ACCESS Runtime error???
Is this a Bug in Swift or I misunderstand something???
Thanks a lot ;)
Add #objc when declaring your protocol
#objc protocol Delegate where Self: Root{
var titleImage: UIImage? {get}
}
This is a current bug in swift so wrapping it in #objc should work

Cannot decode object of class

I am trying to send a "Class" to my Watchkit extension but I get this error.
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyApp.Person)
Archiving and unarchiving works fine on the iOS App but not while communicating with the watchkit extension. What's wrong?
InterfaceController.swift
let userInfo = ["method":"getData"]
WKInterfaceController.openParentApplication(userInfo,
reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in
println(userInfo["data"]) // prints <62706c69 7374303...
if let data = userInfo["data"] as? NSData {
if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
println(person.name)
}
}
})
AppDelegate.swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!) {
var bob = Person()
bob.name = "Bob"
bob.age = 25
reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
return
}
Person.swift
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
According to Interacting with Objective-C APIs:
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
By adding the annotation #objc(name), namespacing is ignored even if we are just working with Swift. Let's demonstrate. Imagine target A defines three classes:
#objc(Adam)
class Adam:NSObject {
}
#objc class Bob:NSObject {
}
class Carol:NSObject {
}
If target B calls these classes:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
The output will be:
Adam
B.Bob
B.Carol
However if target A calls these classes the result will be:
Adam
A.Bob
A.Carol
To resolve your issue, just add the #objc(name) directive:
#objc(Person)
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
I had to add the following lines after setting up the framework to make the NSKeyedUnarchiver work properly.
Before unarchiving:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Before archiving:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
NOTE: While the information in this answer is correct, the way better answer is the one below by #agy.
This is caused by the compiler creating MyApp.Person & MyAppWatchKitExtension.Person from the same class. It's usually caused by sharing the same class across two targets instead of creating a framework to share it.
Two fixes:
The proper fix is to extract Person into a framework. Both the main app & watchkit extension should use the framework and will be using the same *.Person class.
The workaround is to serialize your class into a Foundation object (like NSDictionary) before you save & pass it. The NSDictionary will be code & decodable across both the app and extension. A good way to do this is to implement the RawRepresentable protocol on Person instead.
I had a similar situation where my app used my Core framework in which I kept all model classes. E.g. I stored and retrieved UserProfile object using NSKeyedArchiver and NSKeyedUnarchiver, when I decided to move all my classes to MyApp NSKeyedUnarchiver started throwing errors because the stored objects were like Core.UserProfile and not MyApp.UserProfile as expected by the unarchiver. How I solved it was to create a subclass of NSKeyedUnarchiver and override classforClassName function:
class SKKeyedUnarchiver: NSKeyedUnarchiver {
override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
let lagacyModuleString = "Core."
if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0 {
return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
}
return NSClassFromString(codedName)
}
}
Then added #objc(name) to classes which needed to be archived, as suggested in one of the answers here.
And call it like this:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
It worked very well.
The reason why the solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") was not for me because it doesn't work for nested objects such as when UserProfile has a var address: Address. Unarchiver will succeed with the UserProfile but will fail when it goes a level deeper to Address.
And the reason why the #objc(name) solution alone didn't do it for me was because I didn't move from OBJ-C to Swift, so the issue was not UserProfile -> MyApp.UserProfile but instead Core.UserProfile -> MyApp.UserProfile.
I started facing this after the App Name change,
The error I got was - ".....cannot decode object of class (MyOldModuleName.MyClassWhichISerialized) for key....."
This is because code by default saves Archived object with ModuleName prefix, which will not be locatable after ModuleName changes. You can identify the old Module Name from the error message class prefix, which here is  "MyOldModuleName". 
I simply used the old names to locate the old Archived objects.
So before Unarchieving add line,
NSKeyedUnarchiver.setClass(MyClassWhichISerialized.self, forClassName: "MyOldModuleName.MyClassWhichISerialized")
And before Archieving add line
NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)

KVO observation not working with Swift generics

If I observe a property using KVO, if the observer is a generic class then I receive the following error:
An -observeValueForKeyPath:ofObject:change:context: message was
received but not handled.
The following setup demonstrates the problem succinctly. Define some simple classes:
var context = "SomeContextString"
class Publisher : NSObject {
dynamic var observeMeString:String = "Initially this value"
}
class Subscriber<T> : NSObject {
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<Void>) {
println("Hey I saw something change")
}
}
Instantiate them and try to observe the publisher with the subscriber, like so (done here inside a UIViewController subclass of a blank project):
var pub = Publisher()
var sub = Subscriber<String>()
override func viewDidLoad() {
super.viewDidLoad()
pub.addObserver(sub, forKeyPath: "observeMeString", options: .New, context: &context)
pub.observeMeString = "Now this value"
}
If I remove the generic type T from the class definition then everything works fine, but otherwise I get the "received but not handled error". Am I missing something obvious here? Is there something else I need to do, or are generics not supposed to work with KVO?
Explanation
There are two reasons, in general, that can prevent a particular Swift class or method from being used in Objective-C.
The first is that a pure Swift class uses C++-style vtable dispatch, which is not understood by Objective-C. This can be overcome in most cases by using the dynamic keyword, as you obviously understand.
The second is that as soon as generics are introduced, Objective-C looses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where an ancestor is not generic. This includes new methods introduced as well as overrides.
class Watusi : NSObject {
dynamic func watusi() {
println("watusi")
}
}
class Nguni<T> : Watusi {
override func watusi() {
println("nguni")
}
}
var nguni = Nguni<Int>();
When passed to Objective-C, it regards our nguni variable effectively as an instance of Watusi, not an instance of Nguni<Int>, which it does not understand at all. Passed an nguni, Objective-C will print "watusi" (instead of "nguni") when the watusi method is called. (I say "effectively" because if you try this and print the name of the class in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20, where Divided is the name of my Swift module. So ObjC is certainly "aware" that this is not a Watusi.)
Workaround
A workaround is to use a thunk that hides the generic type parameter. My implementation differs from yours in that the generic parameter represents the class being observed, not the type of the key. This should be regarded as one step above pseudocode and is not well fleshed out (or well thought out) beyond what's needed to get you the gist. (However, I did test it.)
class Subscriber : NSObject {
private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void
required init<T: NSObject>(obj: T, observe: ((T, String) -> Void)) {
_observe = { keyPath, obj, changes, context in
observe(obj as T, keyPath)
}
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
_observe(keyPath, object, change, context)
}
}
class Publisher: NSObject {
dynamic var string: String = nil
}
let publisher = Publisher()
let subscriber = Subscriber(publisher) { _, _ in println("Something changed!") }
publisher.addObserver(subscriber, forKeyPath: "string", options: .New, context: nil)
publisher.string = "Something else!"
This works because Subscriber itself is not generic, only its init method. Closures are used to "hide" the generic type parameter from Objective-C.

Resources