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)
}
Related
I'm working on application using SpriteKit and I'm having trouble with NSCoding on a particular object (a GKComponent object). I'm using Swift 4.0 and iOS 11 in Build Settings. The application always crashes on the line (marked with //***) where I'm trying to decode the object. I've been using the same structure for encoding/decoding other objects and they seem to work fine. Not sure why it won't work with this particular GKComponent object.I've spent hours trying to resolve this issue but couldn't figure out why.
Any help will be appreciated.
Thanks in advance!
class ItemEntity: GKEntity {
var name: String!
var embeddedSpell: KnockbackComponent?
//Other variables
init(itemName: String) {
super.init()
name = itemName
embeddedSpell = KnockbackComponent(anItem: self)
addComponent(embeddedSpell!)
//Other initializations
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "Item.name")
aCoder.encode(embeddedSpell, forKey: "Item.embeddedSpell")
super.encode(with: aCoder)
}
required convenience init?(coder aDecoder: NSCoder) {
let itemName = aDecoder.decodeObject(forKey: "Item.name") as! String
self.init(itemName: itemName)
embeddedSpell = aDecoder.decodeObject(forKey: "Item.embeddedSpell") as? KnockbackComponent
if embeddedSpell != nil {
addComponent(self.embeddedSpell!)
}
}
//Other codes
}
Other class
class KnockbackComponent: GKComponent {
weak var item: ItemEntity!
init(anItem: ItemEntity) {
item = anItem
super.init()
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "KnockbackComponent.item")
super.encode(with: aCoder)
}
required convenience init?(coder aDecoder: NSCoder) {
//***This line causes crash with error msg Thread 1: EXC_BAD_INSTRUCTION***//
let item = aDecoder.decodeObject(forKey: "KnockbackComponent.item") as? ItemEntity //<--error msg Thread 1: EXC_BAD_INSTRUCTION
self.init(anItem: item!)
}
//Other codes
}
I've resolved the issue by getting my GKComponent class to conform to Codable with the codes below:
override func encode(to encoder: Encoder) {
var container = encoder.container(keyedBy: CodingKeys.self)
try? (container.encode(itemEnt, forKey: .itemEnt))
}
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let itemEntity = try container.decode(ItemEntity.self, forKey: .itemEnt)
self.init(entity: itemEntity)
}
I am using this class to create an object but when I archive and unarchive from and to UserDefaults it only gives me the value of trialUrl but not it's superclass properties.Since I don't know if CLCircularRegion supports Codable protocol I'm stuck with this approach.
class TrialRegion: CLCircularRegion{
var trailUrl:URL?
init(center: CLLocationCoordinate2D, radius: CLLocationDistance, identifier: String,trailUrl:URL) {
super.init(center: center, radius: radius, identifier: identifier)
self.trailUrl = trailUrl
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(URL.self, forKey: "trialUrl")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let url = aDecoder.decodeObject(of: NSURL.self, forKey: "trialUrl") as URL? else {return}
self.trailUrl = url
}
}
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.
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")
}
}
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.