How to test required init(coder:)?
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)
}
The optionality of init?(coder:) vs init(coder:). What to do if nil?
You'd better not return nil
.
As my test in Xcode 8.3.2 (8E2002), return nil
in init(coder:)
cause NSKeyedUnarchiver.unarchiveObject
crash or return unexpected result.
Prepare a class which encode wrong data type for "test2":
class MyClass: NSObject, NSCoding {
var x: String
init(_ x: String) {
self.x = x
}
required init?(coder aDecoder: NSCoder) {
guard let x = aDecoder.decodeObject(forKey: "x") as? String else {
return nil
}
self.x = x
}
func encode(with aCoder: NSCoder) {
if x == "test2" {
aCoder.encode(Int(4), forKey: "x")
} else {
aCoder.encode(x, forKey: "x")
}
}
}
TestCaseA: archive a dictionary which contains above MyClass
, then unarchive.
Result: crash on NSKeyedUnarchiver.unarchiveObject
.
let encodedData = NSKeyedArchiver.archivedData(withRootObject: [
"k1":MyClass("test1"),
"k2":MyClass("test2"),
"k3":"normal things"
])
UserDefaults.standard.set(encodedData, forKey: "xx")
if let data = UserDefaults.standard.data(forKey: "xx"),
let _data = NSKeyedUnarchiver.unarchiveObject(with: data) {
if let dict = _data as? [String:Any] {
debugPrint(dict.count)
}
}
TestCaseB: archive an array which contains above MyClass
, then unarchive.
Result: return an empty array (but expected is an array with 1 element)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: [
MyClass("test1"),
MyClass("test2")
])
UserDefaults.standard.set(encodedData, forKey: "xx")
if let data = UserDefaults.standard.data(forKey: "xx"),
let _data = NSKeyedUnarchiver.unarchiveObject(with: data) {
if let dict = _data as? [Any] {
debugPrint(dict.count)
}
}
UIViewController and required init?(coder: )
Remove
fatalError("init(coder:) has not been implemented")
line from required init?(coder aDecoder: NSCoder)
initialiser. Your error is thrown by this line. This line is not needed.
XCode adds this line when you create init?(coder aDecoder: NSCoder)
method to remind developer to implement this method or to never call it. If developer will forget to implement it but will try to instantiate a class from archive or Storyboard he will receive an assert and will be able to find a bug easily.
Swift : Error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'
Note for required: Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.
Note for override: You always write the override modifier when overriding a superclass designated initializer, even if your subclass’s implementation of the initializer is a convenience initializer.
Above both notes are referred from: Swift Programming Language/Initialization
Therefore, your subclass of UIView should look similar to the sample below:
class MyView: UIView {
...
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
...
}
Is it OK to have fatalError in required init?(coder aDecoder: NSCoder) when I don't use Storyboards?
Since you don't use storyboard you can disable your init, so you won't be able to use it in code:
@available(*, unavailable) required init?(coder aDecoder: NSCoder) {
fatalError("disabled init")
}
Swift 2.0 Unit Testing for NSCoding
Walking away from a problem always helps.
func testDecoder() {
let path = NSTemporaryDirectory() as NSString
let locToSave = path.stringByAppendingPathComponent("teststasks")
let newTask = Task(name: "savename", time: "10")
// save tasks
NSKeyedArchiver.archiveRootObject([newTask], toFile: locToSave)
// load tasks
let data = NSKeyedUnarchiver.unarchiveObjectWithFile(locToSave) as? [Task]
XCTAssertNotNil(data)
XCTAssertEqual(data!.count, 1)
XCTAssertEqual(data!.first?.name, "savename")
XCTAssertEqual(data!.first?.time, 10)
}
Related Topics
Swift Firebase Using an Unspecified Index
Xcode 7 Run on MAC Osx 10.10 Yosemite
Running Swift Build in Terminal Leading to "Platform Path" Errors
Swift - Getting Only Alphanumeric Characters from String
Swiftui View Does Not Updated When Observedobject Changed
Formula to Pick Every Pixel in a Bitmap Without Repeating
Swift - How to Resize a Uiview Based on Its Uilabel Size (That Is Inside)
Currying in Swift, New Declaration Syntax in Future
How to Load Nsview from Xib with Swift 3
Create a Loading Image/ Activity Indicator, Until the Image Is Shown in the Screen in Swift
Attrackingmanager Stopped Working in iOS 15
Swift: Reflecting Properties of Subclass of Nsmanagedobject
C-Style Uninitialized Pointer Passing in Apple Swift
Animate Path Stroke Drawing in Swiftui
How to Access a Custom Function from Any File in the Same Swift Project
How to Implement a 'Next' Property to a Caseiterable Enum in Swift