ios swift method parameters - ios

I'm reading the swift getting started apple's guide. And they give the following code :
required init?(coder aDecoder: NSCoder)
Why are they two parameters name ? coder and aDecoder.
I don't understand why we don't use this syntax that works to :
required init?(coder: NSCoder)
Thank you.

coder is external parameter name. It is used when calling method:
SomeClass(coder: someCoder)
aDecoderis local parameter name. It is used inside method:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You may specify just local parameter name:
required init?(coder: NSCoder)
In this case external parameter name automatically takes the same value coder.
The main use is readability. Your example is not very obvious. Lets take a better one from the link posted by Eric D:
func sayHello(to person: String, and anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(to: "Bill", and: "Ted"))
This is more readable (better explains what method is doing) when calling sayHello method than
func sayHello(person: String, anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(person: "Bill", anotherPerson: "Ted"))
Of course you can write
func sayHello(to: String, and: String) -> String {
return "Hello \(to) and \(and)!"
}
print(sayHello(to: "Bill", and: "Ted"))
But in this case local variables are using bad naming style

Related

Required Init throwing fatal error when implementing Stripe

I am working on adding Stripe into my project. There's more code than this, but it was all working before I began adding in Stripe and the init. This is the init that Stripe says to use in their docs
Here's my beginning code and init code:
class BusinessOwnerVC: UIViewController, MyProtocol {
let paymentContext: STPPaymentContext
init() {
let customerContext = STPCustomerContext(keyProvider: SwiftAPI())
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
.....
I am using Storyboards, as I've heard that matters. When I run the code, the fatalError gets thrown and crashes the app. The Stripe example project has this exact code in there, and it works.
Why is my app crashing? What is this required init even doing? I think I understand why I need it, but if you could elaborate beyond it's required for subclasses, that would be helpful.
The solution to my issue was removing the init and only user the required init, like so:
required init?(coder aDecoder: NSCoder) {
//fatalError("init(coder:) has not been implemented")
let customerContext = STPCustomerContext(keyProvider: SwiftAPI())
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(coder: aDecoder)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
I left the commented fatelError portion, but as you can see it's not necessary. It's like the others said, the required init gets used by storyboards and you must have it when you're setting up data in your storyboard class, like Stripe requires.
Just have the super.init in there and you should be all good to go.

Swift error from required convenience for viewcontroller

I'm playing around with having a factory that sets up a login in a view controller to satisfy myself what is going on (this is old code I'm updating) I'm trying to make this as simple an example as possible.
It crashes!
View Controller
init(loginServerClass: String){
super.init(nibName: nil, bundle: nil)
}
//called to initialize the login server class
public init?(coder aDecoder: NSCoder, loginServerClass: String){
super.init(coder: aDecoder)
}
//called from storyboard
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder)
}
using a LoginFactory
public protocol LoginFactoryProtocol{
static func createLogin () -> String
}
class LoginFactory : LoginFactoryProtocol {
static public func createLogin () -> String {
return "testlogintype"
}
}
So
1). It crashes with the minimal example above (bad access)
2). It crashes when I add my prefered convenience int as below:
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder, loginServerClass: LoginFactory.createLogin() )
}
(This coder requires that replaced objects be returned from initWithCoder:)
How can I compile this minimum code?
Github link https://github.com/stevencurtis/initissue
If downvoters could explain why, that would be appreciated as I can't improve without knowing what needs to be added. I already added the error messages, a Github link with the problem. I've listed what I've tried, and I'm going from existing previously working code to a small example (therefore I HAVE tried things, the documentation is https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_324 and similar questions on stack overflow have not helped (they seem to have the same issues i.e. (NSGenericException: This coder requires that replaced objects be returned from initWithCoder))). By definition this is a small example of the problem. I believe this is how a question should be structured (from https://stackoverflow.com/help/how-to-ask), so please do tell me: what else do I need to do to write a good question, please?
One solution (and I don't know if it is good practice) seems to be to use a single initializer that calls super.init
public required init?(coder aDecoder: NSCoder) {
loginServerClass = LoginFactory.createLogin()
super.init(coder: aDecoder)
}
and then have a single intializer for testing and mocking (or to create the viewcontroller otherwise from code)
init(loginServerClass: String){
self.loginServerClass = loginServerClass
super.init(nibName: nil, bundle: nil)
}

Does not conform to protocol 'NSCoding' - Swift 3

I have seen several questions similar to mine; however, those are pertaining to swift 2/1 and I am currently using swift 3. I believe Apple has changed it slightly.
class Person: NSObject, NSCoding {
var signature: UIImage
init(signature: UIImage) {
self.signature = signature
}
required convenience init(coder aDecoder: NSCoder) {
let signature = aDecoder.decodeObject(forKey: "signature") as! UIImage
self.init(signature: signature)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(signature, forKey: "signature")
}
}
You will notice how Swift 3 now forces me to use required convenience init( instead of required init(. Perhaps that has something to do with it.
How can I resolve this issue? Thanks!
The encode method in Swift 3 has been renamed to
func encode(with aCoder: NSCoder)
When you get the do not conform error you can easily find out which required methods are missing
Press ⌘B to build the code.
Press ⌘4 to show the issue navigator.
Click on the disclosure triangle in front of the issue line.

'self' used before super.init call and property not initialized at super.init call

I'm trying to init an array of Int in my custom UIView:
var graphPoints:[Int]
required init?(coder aDecoder: NSCoder) {
RestApiManager.sharedInstance.requestData() { (json: JSON) in
for item in json[].array! {
let n=item["n"].intValue
print(n)
if(n>=0) {
self.graphPoints.append(n)
}
}
}
super.init(coder: aDecoder)
}
But int this row RestApiManager.sharedInstance.requestData() { i reiceved this error: 'self' used before super.init call
And at this row super.init(coder: aDecoder) the error: property self.graphPoint not initialized at super.init call
There are a few seperate issues here
The first related two you question being that you must call the super.init method before you reference self. Simply move this line to the beginning on the init method.
Unfortunately because you are preforming an asynchronous request this will still cause you problems as mentioned in the comments.
Also, note init?(coder aDecoder: NSCoder) is not intended for this use. Another issue is if you are requesting data from your API you should create a model object. As opposed to directly creating a UIView and then when you wish to display it to a user create a UIView from that model.
class GraphPointsModel {
var graphPoints:[Int]
init(graphPoints: [Int]) {
self.graphPoints = graphPoints
}
class func retreiveGraphPoints(handler: (GraphPointsModel -> ())) {
RestApiManager.sharedInstance.requestData() { (json: JSON) in
//Error checking...
//Create instance of GraphPointsModel
let instance = GraphPointsModel(...)
//Call handler to do with as you wish with result
handler(instance)
}
}
}
Swift requires all parameters to be initialized before calling super.init(). There are several ways to make this happen.
Declare graphPoints with an empty initializer like this:
var graphPoints:[Int] = [] or var graphPoints = [Int]()
You can also change the graphPoints to an Optional, like this:
var graphPoints:[Int]?
You can also leave the declaration alone, and just initialize it to an empty array before calling super.init()
You will also need to move your RestAPI call below your super.init call.
Hope this helps.
Maybe you must to do this:
super.init(coder: aDecoder)
put that before your requestData call.

NSCoding required initializer in inherited classes in Swift

I have class Foo which conforms to NSObject and NSCoding which I want to be able to persist with NSKeyedArchiver I want to create class Bar, a subclass of Foo that will also conform to NSObject and NSCoding. I am having a problem understanding how to create the required convenience init?(coder aDecoder: NSCoder) in the subclass.
so class Foo...
class Foo: NSObject, NSCoding {
let identifier:String
init(identifier:String) {
self.identifier = identifier
}
override var description:String {
return "Foo: \(identifier)"
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(identifier, forKey: "identifier")
}
required convenience init?(coder aDecoder: NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String
else {
return nil
}
self.init(identifier:identifier)
}
}
Then class Bar ...
class Bar:Foo {
let tag:String
init(identifier:String, tag:String) {
self.tag = tag
super.init(identifier: identifier)
}
override var description:String {
return "Bar: \(identifier) is \(tag)"
}
}
I can get this to compile by adding the following methods on to make this NSCoding compliant
override func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(tag, forKey: "tag")
super.encodeWithCoder(aCoder)
}
this makes sense because I call super.encodeWithCoder(...) reusing the super makes this DRY. The problem I am having is creating the required convenience init?(...) the only way I can seem to get it to compile is by doing this...
required convenience init?(coder aDecoder:NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String,
let tag = aDecoder.decodeObjectForKey("tag") as? String
else {
return nil
}
self.init(identifier:identifier, tag:tag)
}
I basically have copied the superclass required initializer and then added the additional decode method for the subclass property. This approach does not seem correct...
Is there a better way to implement this??
Right after you decode and assign all the subclass properties in the required init method, call:
super.init(coder: aDecoder)
Have thought about this for a while and believe that this is the correct way to implement this.
The reason is the way Swift enforces object initialization. Convenience initializers can only call the required initializers on self. Only the required initializer can call the init on super.
Therefore the only way to initialize the subclass object is to decode all of the required initialization parameters before you call the subclass required initializer...which then calls the super class initializer
Here is code you can copy to a playground https://gist.github.com/vorlando/dc29ba98c93eaadbc7b1
I have try your code in playground, it just auto to add the code when I tick the red circle of the Error.
The coding is like your function required convenience init.
sample code:
required convenience init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}

Resources