Simple Swift class does not compile - swift

My simple class, ClassWithOneArray, produces this error:
Bitcast requires both operands to be pointer or neither %19 =
bitcast i64 %18 to %objc_object*, !dbg !470 LLVM ERROR: Broken
function found, compilation aborted! Command
/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
failed with exit code 1
However, my class, ClassWithOneInt, does not. Why?
class ClassWithOneInt {
var myInt = Int()
init(myInt: Int) {
self.myInt = Int()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myInt, forKey: "myInt")
}
init(coder aDecoder: NSCoder) {
self.myInt = aDecoder.decodeObjectForKey("myInt") as Int
}
}
class ClassWithOneArray {
var myArray = String[]()
init(myArray: String[]) {
self.myArray = String[]()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myArray, forKey: "myArray")
}
init(coder aDecoder: NSCoder) {
self.myArray = aDecoder.decodeObjectForKey("myArray") as String[]
}
}

As I point out in comments, your example seems to compile fine on beta 2, although it still won't work for a couple of reasons, for encoderWithCoder to be of any use, ClassWithOneArray needs to:
declare conformance with NSCoding,
implement NSCoding,
inherit from NSObject or implement NSObjectProtocol, and,
use a non-mangled name.
All told, that means:
#objc(ClassWithOneArray)
class ClassWithOneArray:NSObject, NSCoding {
var myArray: String[]
init(myArray: String[]) {
self.myArray = myArray
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myArray, forKey: "myArray")
}
init(coder aDecoder: NSCoder) {
self.myArray = aDecoder.decodeObjectForKey("myArray") as String[]
}
}
Also it seems as if the simple methods of testing archiving aren't available in the playground, probably because the classes don't get properly registered.
let foo = ClassWithOneArray(myArray:["A"])
let data = NSKeyedArchiver.archivedDataWithRootObject(foo)
let unarchiver = NSKeyedUnarchiver(forReadingWithData:data)
unarchiver.setClass(ClassWithOneArray.self, forClassName: "ClassWithOneArray")
let bar = unarchiver.decodeObjectForKey("root") as ClassWithOneArray

It looks like your syntax is a bit off for what you're trying to accomplish - something like this should work:
class ClassWithOneInt {
var myInt: Int
init(myInt: Int) {
self.myInt = myInt
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myInt, forKey: "myInt")
}
init(coder aDecoder: NSCoder) {
self.myInt = aDecoder.decodeObjectForKey("myInt") as Int
}
}
class ClassWithOneArray {
var myArray: String[]
init(myArray: String[]) {
self.myArray = myArray
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myArray, forKey: "myArray")
}
init(coder aDecoder: NSCoder) {
self.myArray = aDecoder.decodeObjectForKey("myArray") as String[]
}
}

In my experience simply declaring the Protocol "NSCoding" to your class should do the trick. Hope this helps someone.

Related

Swift: How to add new property to UIImageView class?

I'm afraid I'm relatively new to Swift, but have looked around as best I could and haven't been able to figure out how to do this relatively simple task!
I would like to add a new property called "angle" to the class UIImageView, such that you could use "image.angle". Here's what I've got, having attempted to follow the method of a tutorial I used (note that the required init? part was suggested by Xcode and I am not too sure of what it does):
class selection_image: UIImageView {
var angle = Double()
init(angle: Double) {
self.angle = angle
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thank you very much for any help!!
class selection_image: UIImageView {
var angle = Double()
// your new Init
required convenience init(angle: Double) {
self.init(frame: CGRect.zero)
self.angle = angle
}
override init(frame: CGRect) {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Using Swift 4.2
#jasperthedog. You can add a property to a given class using AssociatedObjects of the Runtime using an extension as follows:
In this example, I add an optional viewAlreadyAppeared: Bool? property to UIViewController. And with this, I avoid creating subclasses of UIViewController
extension UIViewController {
private struct CustomProperties {
static var viewAlreadyAppeared: Bool? = nil
}
var viewAlreadyAppeared: Bool? {
get {
return objc_getAssociatedObject(self, &CustomProperties.viewAlreadyAppeared) as? Bool
}
set {
if let unwrappedValue = newValue {
objc_setAssociatedObject(self, &CustomProperties.viewAlreadyAppeared, unwrappedValue as Bool?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}

The required encoding function doesn't shown in a working code

serialization and deserialization is done by the two method defined in the NSCoding protocol as follow
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize your object here
}
init(coder aDecoder: NSCoder) {
// Deserialize your object here
}
In my own ViewController (inherited from UITableViewController), I have a Array which contain my own object named Item and I do implemented the init(coder aDecoder: NSCoder) initial function. The Item is defined as follow
class Item: NSObject, NSCoding {
var text = ""
var checked = false
func toggleChecked() {
checked = !checked
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.text, forKey: "SLAText")
aCoder.encode(self.checked, forKey: "SLAChecked")
}
required init?(coder aDecoder: NSCoder) {
self.text = aDecoder.decodeObject(forKey: "SLAText") as! String
self.checked = aDecoder.decodeBool(forKey: "SLAChecked")
super.init()
}
override init() {
super.init()
}
}
Instead implement the function encode(with: NSCoder) I defined my own serialization function named saveItems()
func saveItems() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(items, forKey: "ItemLists") //items is a array of type [Item]
archiver.finishEncoding()
data.write(to: dataFilePath(), atomically: true)
}
Question
Why the code is working with out implement the required NSCoding function? The code is simplified from a example of the book I'm studying, I didn't find the encodeWithCoder(_ aCoder: NSCoder) function at all. Isn't the required means you have to implemented it?
Thanks for your time and help
Why the code is working with out implement the required NSCoding function?
This is not true.
NSCoding is a protocol
First of all NSCoding is a protocol, not a function. And in order to conform a type to NSCoding you need to implement a method and an initializer
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder)
Why does your code work?
Let's look at what you are doing
archiver.encode(items, forKey: "ItemLists")
Here items is defined as [Item] which is and array of Item(s).
And Array does conform to NSCoding. This can be easily tested by writing
let nums = [1, 2, 3]
if nums is NSCoding {
print("Array does conform to NSCoding")
} else {
print("Array does NOT conform to NSCoding")
}
The result is
Array does conform to NSCoding
Conclusion
Of course to work properly the element inside of the array must be conform to NSCodable too. And since the generic type of your Array is Item (which you made conform to NSCodable) the mechanism does work properly.

Swift Unable to Override Variables in Subclass

I am working on a macOS project in Swift, and I've been having a lot of trouble with overriding variables in a few classes I've made. In classTwo Xcode is presenting the error Cannot override with a stored property 'texture' on the line;
override var texture: SKTexture?
This is a bit of the code I'm using.
public class classOne: SKSpriteNode {
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Other functions
}
And the second class;
class classTwo: classOne {
override var texture: SKTexture? // Cannot override with a stored property 'texture'
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Some other functions
}
I do plan to actually define a specific texture for classTwo, but for now I couldn't even make this work.. Thanks in advance, and I'd appreciate any help on solving this issue!
EDIT
#Knight0fDragon was right, a better approach is to use super.texture
class classTwo: classOne {
override var texture:SKTexture? {
get {
return super.texture
}
set {
super.texture = newValue
}
}
}
In order to override the property you need to initialise it. In other words, giving it an initial value. If you don't want, here is a workaround:
class classTwo: classOne {
var _texture:SKTexture
override public var texture: SKTexture? {
get {
return _texture
}
set {
_texture = newValue!
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Some other functions
}
Try this
override var texture: SKTexture? {
get {
return SKTexture()
}
set {
}
}
obviously returning something a bit more useful.
In your subclass you need to do this.
override var texture:SKTexture? {
get {
return super.texture
}
set {
super.texture = newValue
}
}
From all my reading, I do not think overriding is the approach you want, you want to create a convenience init
class classTwo: classOne {
convenience init()
{
let texture = SKTexture(imageNamed:"myImage")
self.init(texture,.white,texure.size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
}
}
Then to use it, you just need to do classTwo()
(Note, this has not been tested, so I may have typos, the general concept is here though)

Encoding/Decoding an array of objects which implements a protocol in Swift 2

I have got a class that inherits from NSObject and I want it to be NSCoding compliant. But I ran into trouble while encoding an array of objects which should implement a protocol.
protocol MyProtocol {
var myDescription: String { get }
}
class DummyClass: NSObject, NSCopying, MyProtocol {
var myDescription: String {
return "Some description"
}
func encodeWithCoder(aCoder: NSCoder) {
// does not need to do anything since myDescription is a computed property
}
override init() { super.init() }
required init?(coder aDecoder: NSCoder) { super.init() }
}
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(myCollection, forKey: "collection")
}
}
For aCoder.encodeObject(myCollection, forKey: "collection") I get the error:
Cannot convert value of type '[MyProtocol]' to expected argument type 'AnyObject?'
OK, a protocol obviously is not an instance of a class and so it isn't AnyObject? but I've no idea how to fix that. Probably there is a trick that I'm not aware? Or do you do archiving/serialization differently in Swift as in Objective-C?
There's probably a problem with let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol], too but the compiler doesn't complain yet…
I've just found the solution myself: The key is to map myCollection into [AnyObject] and vice-versa, like so:
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection1 = aDecoder.decodeObjectForKey("collection") as! [AnyObject]
let collection2: [MyProtocol] = collection1.map { $0 as! MyProtocol }
self.init(myCollection: collection2)
}
func encodeWithCoder(aCoder: NSCoder) {
let aCollection: [AnyObject] = myCollection.map { $0 as! AnyObject }
aCoder.encodeObject(aCollection, forKey: "collection")
}
}
I know your title specifies Swift 2, but just for reference, for a similar problem I was working on, I found that in Swift 3, you don't need to convert anymore to AnyObject.
The following works for me in Swift 3 (using your example):
class MyClass: NSObject, NSCoding {
let myCollection: [MyProtocol]
init(myCollection: [MyProtocol]) {
self.myCollection = myCollection
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let collection = aDecoder.decodeObject(forKey: "collection") as! [MyProtocol]
self.init(myCollection: collection)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(aCollection, forKey: "collection")
}
}

How to test required init(coder:)?

In my custom class WLNetworkClient I had to implement such method:
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I do not need to use that, but I would like to test this to make 100% code coverage. Do you know how to achieve this?
I tried following way with no success:
let nc = WLNetworkClient(coder: NSCoder())
XCTAssertNotNil(nc)
Production code:
required init?(coder: NSCoder) {
return nil
}
Test:
func testInitWithCoder() {
let archiverData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: archiverData)
let someView = SomeView(coder: archiver)
XCTAssertNil(someView)
}
Since the required initializer returns nil and does not use the coder, the above code can be simplified to:
func testInitWithCoder() {
let someView = SomeView(coder: NSCoder())
XCTAssertNil(someView)
}
Here is answer which should help you:
let cd = NSKeyedUnarchiver(forReadingWithData: NSMutableData())
let c = CustomTextField(coder:cd)
Answer from Rudolf Adamkovič ist still working with Swift 4:
required init?(coder aDecoder: NSCoder) {
return nil
}
func testInitWithCoder() {
// 1. Arrange
let archiver = NSKeyedArchiver(forWritingWith: NSMutableData())
// 2. Action
let viewController = ViewController(coder: archiver)
// 3. Assert
XCTAssertNil(viewController)
}
I combined Rudolf's answer with Liz's suggestion and ended with the following solution:
let viewController = SomeTableViewController(
presenter: SomePresenterMock(),
coder: NSKeyedUnarchiver(forReadingWith: Data()))
XCTAssert(viewController?.tableView.numberOfSections == 1)
The key here is to use NSKeyedUnarchiver(forReadingWith: Data()) as mock coder, otherwise the test crashes with NSInvalidArgumentException.
I use improved Rudolf's Adamkovič answer:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func test_initWithCoder() {
let object = SomeView()
let data = NSKeyedArchiver.archivedData(withRootObject: object)
let coder = NSKeyedUnarchiver(forReadingWith: data)
let sut = SomeView(coder: coder)
XCTAssertNotNil(sut)
}
It seems that all the previous answers stopped working after Swift 5.
I managed to modify #ChikabuZ answer and make it work like this:
func testInitWithCoder() {
// Given
let object = SomeView()
let data = try! NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
let coder = try! NSKeyedUnarchiver(forReadingFrom: data)
// When
let sut = SomeView(coder: coder)
// Then
XCTAssertNotNil(sut)
}