Swift Questions

  1. What is the difference between an enumeration and a structure in Swift?

    In Swift, an enumeration (or “enum” for short) is a value type that represents a group of related values. Enums are defined using the enum keyword and can be used to define a set of possible values for a variable, for example, a list of error codes, or a list of days of the week. Enums can also have associated values, which can be used to store additional information for each case.
    A structure (or “struct” for short) is also a value type in Swift that represents a group of related values, but it can store multiple values of different types, similar to a class. Structures can have properties, methods, and initializers, and they can also conform to protocols.

    요약: 열거형(“Enum”)은 관련 값 그룹 유형이며 값 집합을 정의하는데 사용한다. 구조체(“Struct”)는 관련 값 그룹 인것 맞지만 서로 다른 유형의 여러 값을 저장할 수 있으며, 속성, 메서드, 이니셜라이저를 갖고 프로토콜을 따를 수도 있다.

  2. Can you give an example of using optional chaining in Swift?

    Optional chaining in Swift is a way of calling properties, methods, or subscripts on an optional value, where the optional value may be nil. If the optional value is nil, the result of the optional chaining is nil, otherwise, it returns the result of the operation.

    Here’s an example of using optional chaining in Swift:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Person {
    var name: String
    var address: Address?

    init(name: String) {
    self.name = name
    }
    }
    class Address {
    var street: String
    var city: String

    init(street: String, city: String) {
    self.street = street
    self.city = city
    }
    }
    let john = Person(name: "John")
    if let street = john.address?.street {
    print(street)
    } else {
    print("john does not have an address")
    }
    john.address = Address(street: "Jong-ro",city: "Seoul")
    if let street = john.address?.street {
    print(street)
    } else {
    print("john does not have an address")
    }

    Output:

    1
    2
    john does not have an address
    Jong-ro
  3. What is the purpose of the “defer” statement in Swift?

    The “defer” statement in Swift is used to execute a block of code just before the current function returns. The code in the “defer” block is executed regardless of how the function returns, whether it returns normally, or due to an exception being thrown.
    The main use case for the “defer” statement is to perform cleanup tasks, such as freeing up resources, closing files, or releasing locks, even if the function exits early due to an error. This helps to simplify the code by centralizing the cleanup code in a single place and making it easier to understand the code flow.

    요약: “defer”는 함수 반환 전 정상, 예외 여부와 관계없이 마지막에 실행되는것으로 오류로 인해 조기에 종료되더라도 파일닫기, 잠금해제같은 작업을 수행하기 위함이다.

    Here’s an example of using the “defer” statement in Swift:

    1
    2
    3
    4
    5
    6
    7
    8
    func processFile(fileName: String) throws {
    let file = try openFile(fileName)
    defer {
    closeFile(file)
    }
    // perform some processing on the file
    try processContents(file)
    }

    In this example, the openFile function is called to open a file, and the closeFile function is called in the defer block to close the file. The closeFile function is guaranteed to be called, even if the processContents function throws an error, because the defer block is executed just before the processFile function returns. This makes it easy to ensure that the file is always closed, even if an error occurs during the processing of the file.

  4. What is the difference between a class and a structure in Swift?

    In Swift, a class and a structure are both used to define custom data types, but they have some key differences.
    Classes are reference types, which means that when you assign a class instance to a variable, you are actually creating a reference to the instance, not a copy of it. This means that multiple variables can refer to the same instance, and changes to the instance made through one reference will be visible through all references.
    Structures, on the other hand, are value types, which means that when you assign a struct instance to a variable, you are creating a copy of the instance. This means that changes to the instance made through one reference will not affect other references to the same instance.
    Classes can also inherit from other classes and can be used to create objects, while structures cannot. Structures, however, are generally faster and more efficient than classes, especially when it comes to small instances, and they are also easier to manage and debug because they are self-contained.

    요약: 구조체, 클래스 모두 데이터 유형 정의에 사용. 클래스를 인스턴스에 할당하면 복제가 아닌 참조를 만드는것이나, 구조는 복사본이 생성된다. 클래스는 다른 클래스에서 상속할 수 있고 객체를 만드는데 사용할 수 있지만 구조체는 그렇지 않다. 인스턴스가 작으면 구조체가 더 효율적일 수 있다.

  5. Can you explain the difference between a class method and an instance method in Swift?

    Class methods are called on the class itself, while instance methods are called on instances of the class.
    Class methods can be called without creating an instance of the class, while instance methods require an instance to be created.
    Class methods have the class keyword in their declaration, while instance methods do not.

    클래스 메서드는 클래스 자체에서 인스턴스 할당 없이 호출되는 반면,
    인스턴스 메서드는 클래스를 인스턴스에 할당 한 뒤에 그 인스턴스에서 호출된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Car {
    static func displayClassName() {
    print("Car")
    }
    func displayInstanceName() {
    print("Instance of Car")
    }
    }
    Car.displayClassName() // Output: Car
    let myCar = Car()
    myCar.displayInstanceName() // Output: Instance of Car
  6. Can you give an example of using generics in Swift?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func sortArray<T: Comparable>(_ array: [T]) -> [T] {
    return array.sorted()
    }

    // Example usage:
    let unsortedInts = [5, 3, 9, 2, 7]
    let sortedInts = sortArray(unsortedInts)
    print(sortedInts) // Output: [2, 3, 5, 7, 9]

    let unsortedStrings = ["cat", "dog", "bird", "fish"]
    let sortedStrings = sortArray(unsortedStrings)
    print(sortedStrings) // Output: ["bird", "cat", "dog", "fish"]

    함수 sortArray는 Comparable프로토콜을 따르는 제네릭 타입 T를 사용한다. Comparable을 따른다는것은 비교연산자를 사용할 수 있다는것이다.

  7. What is the difference between optional binding and optional chaining in Swift?

    Optional binding is used to safely unwrap an optional value and bind it to a new variable or constant, and it’s typically used when you need to perform operations on the unwrapped value.
    In this example, we use optional binding with if let to safely unwrap the optional value optionalName and bind it to the new constant name. If the optional value is nil, the code inside the else block will be executed. Here’s an example:

    1
    2
    3
    4
    5
    6
    7
    let optionalName: String? = "John"

    if let name = optionalName {
    print("Hello, \(name)!")
    } else {
    print("Hello, stranger.")
    }

    옵셔널 바인딩은 안전하게 변수를 언랩하기 위함이다.

    Optional chaining, on the other hand, is used to safely access a property or call a method on an optional value, without needing to first unwrap the value. In this example, we use optional chaining with the ? operator to safely access the street property of the person’s address property. If person.address is nil, the code inside the else block will be executed instead.
    Here’s an example:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct Person {
    var name: String
    var age: Int
    var address: Address?
    }

    struct Address {
    var street: String
    var city: String
    var state: String
    }

    let person = Person(name: "John", age: 30, address: Address(street: "123 Main St", city: "Anytown", state: "CA"))

    if let street = person.address?.street { // <==
    print("He lives on \(street).")
    } else {
    print("No address found.")
    }

    반면 옵셔널체이닝은 안전하게 변수에 접근하기 위함이다.

  8. Can you explain the difference between class inheritance and protocol-oriented programming in Swift?

    Both class inheritance and protocol-oriented programming are ways to achieve code reuse and polymorphism in Swift, but they have different approaches and trade-offs.

    Class inheritance is a mechanism where a subclass inherits properties and methods from a superclass. The subclass can also override and add its own properties and methods. Class inheritance is a form of vertical code reuse because it defines a hierarchy of classes with shared functionality.

    => 클래스는 일부를 공유하는 계층적 구조이므로 수직적인 상속 형태라고 볼 수 있다.

    In this example, the Cat class inherits from the Animal class, which defines a name and sound property and a makeSound() method. The Cat class overrides the makeSound() method to provide its own implementation.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Animal {
    var name: String
    var sound: String

    init(name: String, sound: String) {
    self.name = name
    self.sound = sound
    }

    func makeSound() {
    print("\(name) makes \(sound)!")
    }
    }

    class Cat: Animal {
    init(name: String) {
    super.init(name: name, sound: "meow")
    }

    override func makeSound() {
    print("\(name) purrs.")
    }
    }

    let cat = Cat(name: "Whiskers")
    cat.makeSound() // Output: "Whiskers purrs." (Example of overriden func)

    Protocol-oriented programming, on the other hand, is a programming paradigm where functionality is defined in small, composable protocols that can be combined to create new types. This is a form of horizontal code reuse because it encourages the creation of many small protocols with specific functionality that can be combined and reused in different ways.

    다양한 방식으로 결합하고 재사용할 수 있는 작은 프로토콜의 생성=> 수평적 코드 재사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    protocol Movable {
    var speed: Double { get }

    func move()
    }

    class Car: Movable {
    var speed: Double

    init(speed: Double) {
    self.speed = speed
    }

    func move() {
    print("The car is moving at \(speed) mph.")
    }
    }

    class Plane: Movable {
    var speed: Double

    init(speed: Double) {
    self.speed = speed
    }

    func move() {
    print("The plane is flying at \(speed) mph.")
    }
    }

    let car = Car(speed: 60)
    let plane = Plane(speed: 500)

    let vehicles: [Movable] = [car, plane]

    for vehicle in vehicles {
    vehicle.move()
    }

    While protocols and class inheritance can achieve similar results in some cases, they have different strengths and use cases.

    One advantage of using protocols is that they allow for greater flexibility and modularity in your code. Protocols can be adopted by a wide range of types, including classes, structs, and enums. This means that you can create more specialized types that conform to a protocol, rather than having to subclass a more general type. This can help avoid issues with tight coupling and inheritance hierarchies that can arise with class inheritance.

    Additionally, protocols can be used to achieve polymorphism across types that don’t share a common superclass. With protocols, you can define a common set of requirements that any conforming type must implement, allowing you to work with a wider range of types that share a particular behavior.

    Another advantage of protocols is that they allow for composition over inheritance. With protocols, you can define small, modular interfaces that can be combined to create larger and more complex behaviors. This can make your code more flexible and easier to maintain, since you can reuse small pieces of functionality in different contexts.

    In summary, while protocols and class inheritance can both achieve code reuse and polymorphism, protocols have different strengths and use cases, including greater flexibility and modularity, better support for polymorphism across different types, and the ability to compose smaller pieces of functionality into larger behaviors.

    => 프로토콜은 더 큰 유연성과 모듈성을 허용하며, 클래스 뿐만 아니라 구조체, 열거형 등에서도 사용하며 하위 클래스로 분류하는게 아닌 프로토콜을 준수하는 유형으로 취급하여 클래스 상속에서 발생할 수 있는 강결합 및 상속 계층 문제를 방지한다.
    => 클래스 상속과 프로토콜을 동시에 사용이 가능하다.

  9. How does Swift handle type casting?

    In Swift, type casting is the process of checking the type of an instance at runtime and converting it to another type if possible. There are two types of type casting in Swift: upcasting and downcasting.

    Upcasting is the process of converting an instance of a subclass to an instance of its superclass. Since a subclass is guaranteed to have all the properties and methods of its superclass, upcasting is always safe in Swift.

    Here’s an example of upcasting and downcasting in Swift:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
        class Human {
    let name: String

    init(name: String) {
    self.name = name
    }
    }

    class Teacher: Human {
    let school: String

    init(name: String, school: String) {
    self.school = school
    super.init(name: name)
    }
    }

    class Student: Human {
    let school: String

    init(name: String, school: String) {
    self.school = school
    super.init(name: name)
    }
    }
    // Upcasting to the Human superclass
    let human: Human = Teacher(name: "John Doe", school: "Acme High School")
    let human2: Human = Student(name: "Jimmy Yo", school: "Batman School")

    var humanList = [Human]()

    humanList.append(human)
    humanList.append(human2)

    // Downcasting to the Teacher subclass
    for human in humanList {
    if let teacher = human as? Teacher {
    print("\(teacher.name) is teacher")
    } else if let student = human as? Student {
    print("\(student.name) is student")
    } else {
    print("\(human.name) is something else")
    }
    }

    Downcasting is the process of converting an instance of a superclass to an instance of a subclass. Since a superclass does not necessarily have all the properties and methods of its subclasses, downcasting is not always safe in Swift. To perform a downcast safely, you need to use the optional type-casting operator as? or the forced type-casting operator as!.

    => type을 쓰면 어떤 클래스인지 정확하게 알 수 있다.
    => 업캐스팅은 서브클래스를 어퍼클래스로 취급하고싶을때 사용 (포장)
    => 다운캐스팅은 업캐스팅된 서브클래스를 다시 서브클래스로 취급하고싶을때 사용 (포장 제거)

  10. Can you give an example of using the guard statement in Swift to handle optional values?

    guard. 진짜면 나간다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func greet(name: String?) {
    guard let name = name else {
    print("No name provided")
    return
    }
    print("Hello, \(name)!")
    }

    greet(name: "John") // prints "Hello, John!"
    greet(name: nil) // prints "No name provided"

    greet 함수는 옵셔널 String을 파라메터로 받는다. 이 함수에서 guard는 name 파라메터가 nil인지 검사하고 조건이 true 이면 gurad를 나가고, false 이면(nil이면) guard 안을 돈다.