Search Your Question

Swift Initializers

Ans :

Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.

Initializers : Initializers, are like special methods that can be called to create a new instance of a particular type.

init() {
// perform some initialization here
}

Customizing Initialization :

struct Human {
    var gender:Gender
    var age:Int = 10
    
    init(age:Int) { // initializer 1
        self.age = age
        self.gender = .unknown
    }
    
    init(age:Int, gender:Gender) { // initializer 2
        self.age = age
        self.gender = gender
    }
}
//------------------------------

let human2 = Human(age: 20)
let human3 = Human(age: 40, gender: .male)

We can have more than one initializers based on our requirement, with different parameter names, types. The swift compiler will decide which init method to call based on the argument label. 

Note that it is not possible to call these initializers without using argument labels. Argument labels must always be used in an initializer if they are defined, and omitting them is a compile-time error:

let human4 = Human() // error :cannot invoke initializer for type ‘Human’ with no arguments
let human5 = Human(40,.male) //error:error: missing argument labels 'age:gender:' in call

Default Initialisers :

Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself.

class ShoppingListItem {
var nameString?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()

Memberwise Initializers for Structure Types :

Structure types automatically receive a memberwise initializer if they do not define any of their own custom initializers. 

struct Size {
var width, height :Double // stored properties without default values
}
-------- or --------------
struct Size {
var width = 10.0, height = 30.0 // stored properties with default values
}

In above both Size struct, there is no any init method defined. Still Size struct can be instantiated by 

let twoByTwo = Size(width: 2.0, height: 2.0)

If we provide custom initialisation, then memberwise initializer instantiation will be failed. See following :  

struct Size {
var width, height :Double
init(){
self.width = 10.0
self.height = 30.0
}
}
let sizeObj1 = Size(width: 2.0, height: 2.0)// error. argument passed to call that takes no arguments
let sizeObj2 = Size() // success.

Initializer Delegation for Value Types :

Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation.

struct Size {
 var width = 0.0, height = 0.0
}
struct Point {
 var x = 0.0, y = 0.0
}

struct Rect {
 var origin = Point()
 var size = Size()
 init() {}
 init(origin: Point, size: Size) {
   self.origin = origin
   self.size = size
 }

init(center: Point, size: Size) {
  let originX = center.x - (size.width / 2)
  let originY = center.y - (size.height / 2)
  self.init(origin: Point(x: originX, y: originY), size: size)
 }
}

Designated Initializers and Convenience Initializers :

Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value.

Designated initializers
Convenience initializers

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
Every class should have at least one designated initializer.

Designated initializers for classes are written in the same way as simple initializers for value types:

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.

class HumanBeing {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: “not set”)
// Convenience init call the designated init method
}
}
let humanBeingObj1 = HumanBeing() // calls convenience init
let humanBeingObj2 = HumanBeing(name: “abhilash”) // calls designated init


Failable Initializers :

We cannot always assume that the initialization will always succeed for a struct, enum or class. It can fail for several reasons. It is sometimes useful to define a class, structure, or enumeration for which initialization can fail.

You write a failable initializer by placing a question mark after the initkeyword (init?).

You cannot define a failable and a nonfailable initializer with the same parameter types and names.

 In swift, the initializers won’t return anything. But objective -C does. In swift, You write return nil to trigger an initialization failure, you do not use the return keyword to indicate initialization success.

struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}

}

Check :

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}

// Prints "An animal was initialized with a species of Giraffe"


Failable Initializers for Enumerations :

You can use a failable initializer to select an appropriate enumeration case based on one or more parameters. The initializer can then fail if the provided parameters do not match an appropriate enumeration case.

enum TemperatureUnit {
case kelvin, celsius, fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celsius
case "F":
self = .fahrenheit
default:
return nil
}
}

guard let fahrenheitUnit = TemperatureUnit(symbol: "X") else {
print("This is not a defined temperature unit, so initialization failed.")
}

// Prints "This is not a defined temperature unit, so initialization failed."

Failable Initializers for Enumerations with Raw Values :

Enumerations with raw values automatically receive a failable initializer, init?(rawValue:), that takes a parameter called rawValue of the appropriate raw-value type and selects a matching enumeration case if one is found, or triggers an initialization failure if no matching value exists.

enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."


Propagation of Initialization Failure :

If your subclass is having a failable initializer which in turn call its superclass failable designated initializer, then if either of the initialization failed means the entire initialization process fails immediately, and no further initialization code is executed.

class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}

if let zeroShirts = CartItem(name: "shirt", quantity: 0) { // fails
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"

if let oneUnnamed = CartItem(name: "", quantity: 1) { // fails
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"


Required Initializers in swift :

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.

You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain. You do not write the override modifier when overriding a required designated initializer:

//required init
class classA {
required init() {
var a = 10
print(a)
}
}
class classB: classA {
required init() {
var b = 30
print(b)
}
}
//______________________
let objA = classA()
let objB = classB()
prints:
10
30
10

//______________________

class classC: classA { }
//______________________
let objC = classC() // prints 10 ..superclass init method gets called.


Note: You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initializer. In the above class classC , since we don’t have any stored properties unintialized at the time of initialization, we dont have to declare the required init explicitly.

When required init method is mandatory? What is that?

Ans :


Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.
You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain. You do not write the override modifier when overriding a required designated initializer.
“The use of the required modifier ensures that you provide an explicit or inherited implementation of the initializer requirement on all subclasses of the conforming class, such that they also conform to the protocol.”

How we stop notification to be sent to device who is uninstalled?

Ans :

The push message service has a feedback channel which reports error messages when the application has been removed or the device no longer accepts the push messages.

"The Apple Push Notification service includes a feedback service to give you information about failed remote notifications. When a remote notification cannot be delivered because the intended app does not exist on the device, the feedback service adds that device’s token to its list. Remote notifications that expire before being delivered are not considered a failed delivery and don’t impact the feedback service. By using this information to stop sending remote notifications that will fail to be delivered, you reduce unnecessary message overhead and improve overall system performance."

How to iterate over all enum cases?

Ans : 

Swift 4.2's CaseIterable can help us avoid bugs and make our code more consistent when defining enum-keyed dictionaries. We'll start by making TextType conform to CaseIterable. Just like with Codable, Equatable and Hashable, we don't need to write any additional code ourselves - instead CaseIterable kind of acts as a marker for the compiler, telling it to synthesize an allCases collection for us:


enum TextType: CaseIterable {
    case title
    case subtitle
    case sectionTitle
    case body
    case comment
}


With the above change, we can now use TextType.allCases to access a collection of all cases in order, which we can then use to build up our fonts dictionary without the risk of missing a case, like this:


var fonts = [TextType : UIFont]()

for type in TextType.allCases {
    switch type {
    case .title:
        fonts[type] = .preferredFont(forTextStyle: .headline)
    case .subtitle:
        fonts[type] = .preferredFont(forTextStyle: .subheadline)
    case .sectionTitle:
        fonts[type] = .preferredFont(forTextStyle: .title2)
    case .body:
        fonts[type] = .preferredFont(forTextStyle: .body)
    case .comment:
        fonts[type] = .preferredFont(forTextStyle: .footnote)
    }
}


Definitely an improvement, since if we now add a new text type without a matching font definition - we'll get a compiler error 👍.


Difference between OperationQueue and DispatchGroup

Ans : 

You probably found yourself in a situation where you had to do a bunch of asynchronous tasks and you just wanted to get notified when all of them finish. There are two easy ways of doing this: DispatchGroup and OperationQueue. 

DispatchGroup :

DispatchGroups is probably the easiest way for you to know when a bunch of asynchronous calls is finished.  DispatchGroup is a part of GCD and in Swift 3 we got a nice swiftified GCD.

func getMovies(onCompleted: (([MovieItem]) -> ())?) {
    
    var result: [MovieItem] = []
    
    let dispatchGroup = DispatchGroup()
    
    for i in 1...5 {
        guard
            let urlString = DataSourceConstants.URLString(forPage: "\(i)")
            else {
                continue
        }
        
        dispatchGroup.enter()
        self.networkingProvider.restCall(urlString: urlString) {
            
            (responseObject) in
            
            guard
                let responseData = responseObject,
                let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments])
                else {
                    dispatchGroup.leave()
                    return
            }
            
            let movies = self.moviesFactory.movieItems(withJSON: jsonObject)
            result = result + movies
            
            dispatchGroup.leave()
        }
    }
    
    dispatchGroup.notify(queue: DispatchQueue.global()) {
        onCompleted?(result)
    }

}

Here we create your DispatchGroup at the top of the function and just before you execute an async piece of code, you enter the group. In your closure, you leave the same group just before the closure completes. When your DispatchGroup is empty, a closure will be called.

What’s really important here is the enter-leave pairs. You have to be very careful and make sure that you leave the group. It would be easy to introduce a bug in the code above. Let’s say that we didn’t leave the group in that guard statement above, just before the return. If the API called failed, or the JSON was malformed, the number of group entries would not match the number of leaves. So the group completion handler would never get called. If you’re calling this method from the UI and displaying an activity indicator while your networking requests are running, you would never get a callback from the method, and you would keep on spinning.

OperationQueue :

Operation queues are great and all, but if you just want to know when your queue is finished you won’t find a ready-made API waiting for you.

There is a simple trick you can use to get notified when your async tasks are finished. The trick is to use dependencies. You have two options here. If you need your operations to execute one after another, you can set the next operation to be dependent on the previous. So when your last operation is finished, your queue is finished as well. This is easy to do.

For concurrent operations(executing at same time),  Create another operation. Operations can have dependencies on multiple operations. So when you create your operations you add them as a dependency to that operation. When all dependent operation finish, your operation will get executed. And this way you can tell that your ‘queue’ is finished. If you think about this from a logical perspective, it makes perfect sense. Anyone can add a bunch of operations in the operation queue, and if you don’t own it completely, you don’t know who added what. So having a callback that will tell you that the queue is empty would not be very useful. Dependencies are baked into the Operations.

func getMovies(onCompleted: (([MovieItem]) -> ())?) {
    
    operationQueue.cancelAllOperations()
    
    var result: [MovieItem] = []
    
    let queueCompletionOperation = BlockOperation {
        onCompleted?(result)
    }
    
    var operations: [Operation] = []
    
    operationQueue.isSuspended = true
    
    for i in 1...5 {
        guard
            let urlString = DataSourceConstants.URLString(forPage: "\(i)")
            else {
                continue
        }
        
        let networkingOperation = GetDataOperation(withURLString: urlString, andNetworkingProvider: networkingProvider)
        let parsingOperation = ParseDataOperation(withFactory: moviesFactory)
        
        networkingOperation.completionBlock = {
            parsingOperation.moviesData = networkingOperation.responseData
        }
        
        parsingOperation.completionBlock = {
            if let moviesArray = parsingOperation.movies {
                DispatchQueue.global().sync(flags: .barrier) {
                    result = result + moviesArray
                }
            }
        }
        
        parsingOperation.addDependency(networkingOperation)
        
        queueCompletionOperation.addDependency(parsingOperation)
        
        operations.append(contentsOf: [parsingOperation, networkingOperation])
    }
    
    operations.append(queueCompletionOperation)
    
    operationQueue.addOperations(operations, waitUntilFinished: false)
    operationQueue.isSuspended = false
}

We have our ‘queueCompletionOperation’ at the top and in the block we’re calling our closure. We suspend the queue before adding new operations and we’re setting the dependency on our queueCompletionOperation to parsingOperation. After the loop is finished, we add all the operations in the queue and un-suspend the queue.


Dispatch queue :

DispatchQueue is an abstraction layer on top of the GCD queue that allows you to perform tasks asynchronously and concurrently in your application. Tasks are always executed in the order they’re added to the queue.
There are two types of dispatch queues :
1. Serial Dispatch Queue
2. Concurrent Dispatch Queue