iOS Swift

iOS Swift 클로저(후행 클로저), 클래스(Class)

Taram 2023. 10. 22. 22:50

※이 iOS 카테고리의 글 들은 학교 강의와 과제를 기반으로 작성한 것입니다.※
※Window환경에서 작업하기 때문에 Online Compiler를 사용하는 점 참고 부탁드립니다.


이번 포스트에서는 저번 포스트에서 많이 못 다뤘던 후행 클로저와 Class에 대해 알아보겠습니다.

  • 후행 클로저(trailing closure)
    후행 클로저의 특징은 크게 2가지가 있습니다.
    - Argument Lable을 사용하지 않는다.
    - 클로저가 함수의 마지막 Argument라면 마지막 매개변수 이름을 생략 후 소괄호 외부에 클로저 구현

    그리고 클로저들은 축약 표현이 있습니다.
    - return 생략
    - 매개변수 생략하고 단축인자 사용
    return 생략부터 바로 예시 코드에서 설명드리겠습니다.
func mul(a: Int, b: Int) -> Int {
	return a * b }

let multiply = {(a: Int, b: Int) -> Int in
	return a * b }

print(mul(a:10, b:20))
print(multiply(10, 20))

let add = {(a: Int, b: Int) -> Int in
	return a + b}

print(add(10, 20)) //클로저 형식 -> Argument Lable 필요X

func math(x: Int, y: Int, cal: (Int, Int) -> Int) -> Int {
	return cal(x, y)}

var result = math(x: 10, y: 20, cal: add) //return add(10,20)
print(result)

result = math(x: 10, y: 20, cal: multiply)//return multiply(10,20)
print(result)

result = math(x: 10, y: 20, cal: {(a: Int, b: Int) -> Int in
	return a + b }) //클로저 소스를 매개변수에 직접 작성
print(result)

result = math(x: 10, y: 20) {(a: Int, b: Int) -> Int in
	return a + b}//trailing closure(후행 클로저), cal 생략
print(result)

result = math(x: 10, y: 20) {(a: Int, b: Int) -> Int in
	return a * b}//trailing closure(후행 클로저), cal 생략
print(result)

1) 기본 함수 정의 : 
첫 번째로, 기본적인 함수를 정의하는 방법을 알아봅시다. mul 함수는 두 정수를 인자로 받고, 이 두 숫자를 곱한 결과를 반환합니다.

func mul(a: Int, b: Int) -> Int {
    return a * b
}

2) let multiply, let add : 
클로저는 익명 함수로, 변수에 할당하거나 함수의 인자로 사용할 수 있습니다. 위의 코드에서, multiply, let 클로저는 두 정수를 인자로 받고, 이 두 숫자를 곱한 결과를 반환합니다.

let multiply = { (a: Int, b: Int) -> Int in
    return a * b
}

3) 함수와 클로저 호출 : 
함수는 호출 할 때 Argument Lable이 필요하지만 클로저는 Argument Lable을 사용하지 않습니다. 그럼으로써 코드를 조금이라도 더 간결하게 만들 수 있습니다.

print(mul(a: 10, b: 20)) // 결과: 200
print(multiply(10, 20)) // 결과: 200

4) 클로저를 함수에 전달 (func math) :
함수는 다른 함수나 클로저를 인자로 받을 수 있습니다. math 함수는 두 정수와 함수 또는 클로저를 인자로 받아, 이를 이용하여 연산 결과를 반환합니다.

func math(x: Int, y: Int, cal: (Int, Int) -> Int) -> Int {
    return cal(x, y)
}

5)

클로저를 함수에 전달하여 사용 :
함수 math를 이용하여 함수와 클로저를 전달하여 결과를 얻어올 수 있습니다.

var result = math(x: 10, y: 20, cal: add) // 결과: 30
print(result)

result = math(x: 10, y: 20, cal: multiply) // 결과: 200
print(result)

result = math(x: 10, y: 20, cal: { (a: Int, b: Int) -> Int in
    return a + b
}) // 결과: 30
print(result)

6) 후행 클로저 사용 :
함수 호출 시 클로저를 괄호 밖으로 빼서 사용할 수 있습니다.

result = math(x: 10, y: 20) { a, b in
    return a + b
} // 결과: 30
print(result)

7) 다양한 연산 :
마지막으로, 이 코드는 다양한 연산을 수행하는 방법을 보여줍니다. 함수와 클로저를 사용하여 코드를 모듈화하고 더 간결하게 작성할 수 있음을 보여줍니다.

result = math(x: 10, y: 20) { a, b in
    return a * b
} // 결과: 200
print(result)

 

매개변수를 생략하고 단축 인자를 사용하는 방법도 알아보겠습니다.

result = math(x: 10, y: 20) {(a: Int, b: Int) -> in //리턴형 생략 가능
	return a * b}//trailing closure(후행 클로저), cal 생략
print(result) //200

result = math(x: 10, y: 20) {
	return $0 + $1}) //매개변수 생략하고 단축인자(shorthand argument name)사용, return 생략 가능
		         //$0 : 1번째 매개변수	
print(result) //30

result = math(x: 10, y: 20) { $0 + $1} //return형 생략		
print(result) //30

1) 후행 클로저 선언 :
클로저가 마지막 Argument기 때문에 매개변수(cal)를 생략하고 소괄호 밖에 클로저 작성

2) 매개변수 단축 문법 :
$0, $1, $2와 같은 식별자를 사용하여 매개변수를 참조할 수 있습니다.
$0은 첫 번째 매개변수, $1은 두 번째 매개변수를 나타냅니다.

3) 반환 형식 생략(return 생략) :
타입 추론을 지원하기 때문에 return을 생략할 수 있습니다.
반환 형식을 명시적으로 선언하지 않았지만, 스위프트는 곱셈 연산 결과가 Int임을 인지하고 처리합니다.


  • Class(클래스)
    클래스를 이해하기 위해선 인스턴스와 클래스의 차이점을 먼저 알아야합니다.

    1. 클래스:
    - 클래스는 객체나 데이터 구조를 정의하는 설계도 또는 템플릿입니다.
    - 클래스는 어떤 객체가 가져야 하는 속성(멤버 변수)과 동작(메서드)을 정의합니다.
    - 클래스는 여러 개의 객체(인스턴스)를 생성하기 위한 틀을 제공합니다.
    2. 인스턴스:
    - 인스턴스는 클래스를 기반으로 실제로 생성된 개별 객체입니다.
    - 클래스의 설계에 따라 생성된 인스턴스는 해당 클래스의 속성과 메서드를 가집니다.
    - 여러 개의 다른 인스턴스를 생성하여 다양한 데이터를 처리하거나 다양한 동작을 수행할 수 있습니다.

    즉 이를 정리하면 클래스는 객체(인스턴스)의 설계도이며, 인스턴스는 실제로 그 설계에 따라 생성된 객체입니다. 클래스는 어떤 종류의 객체를 만들어야 하는지를 정의하고, 이에 따라 여러 개의 인스턴스가 생성됩니다. 인스턴스는 실제 작업을 수행하거나 데이터를 저장하는 데 사용됩니다.

    추가적으로 property도 알아야합니다.
    클래스, 구조체, 열거형 등의 타입 내에서 데이터를 저장하고 관리하는 데 사용되며 크게 2가지 유형이 있습니다.
    1. 저장 프로퍼티(Stored Properties):
    - 저장 프로퍼티는 값을 저장하고 나중에 검색할 수 있는 프로퍼티입니다.
    - 클래스의 인스턴스나 구조체의 인스턴스에 속하며, 해당 인스턴스의 일부로 값을 유지합니다.
    - 주로 클래스나 구조체의 상태나 속성을 표현하기 위해 사용됩니다.
    2. 계산 프로퍼티(Computed Properties):
    - 계산 프로퍼티는 값을 직접 저장하지 않고, 게터(Getter)와 세터(Setter) 메서드를 사용하여 값을 계산하는 프로퍼티입니다.
    - 주로 다른 프로퍼티의 값에 기반하여 계산된 값을 제공하거나 읽고 쓰기 동작을 커스터마이징할 때 사용됩니다.

    그리고 이러한 프로퍼티들은 3가지 조건 중 하나를 만족해야 합니다.
    - 초기값이 있어야 한다.
    - init을 이용해서 초기화한다.
    - 옵셔널 변수(상수)로 선언한다.(자동으로 nil로 초기화)

    * 객체의 상태를 나타내거나 객체 간의 데이터를 교환하는 데 사용되며, 객체 지향 프로그래밍에서 중요한 역할을 합니다. 프로퍼티는 클래스와 구조체 내에서 정의되며, 해당 타입의 인스턴스에 속합니다.

    그럼 이제 클래스 기본 구조부터 알아볼까요?
class 새로운 클래스 이름 : 부모 클래스 {
// 프로퍼티
// 인스턴스 메서드
// 타입(type) 메서드(클래스 메서드)
}

- “프로퍼티” 부분은 클래스 내에 포함되는 변수(var)와 상수(let)를 정의
- “인스턴스 메서드”는 객체가 호출하는 메서드를 정의
- “타입 메서드”는 클래스가 호출하는 메서드를 정의

이 구조에 맞춰 Property를 선언해 보겠습니다.

class Man { //저장 프로퍼티는 초기값이 꼭 필요함, nil도 상관없음
    var age : Int = 1
	//nil일 경우 var age : Int?
    var weight : Double = 3.5
}

위 코드처럼 Property를 선언해야합니다. 주석에 달아놓은 것처럼 nil로 지정해 줄 수도 있습니다.

여기서 하나 더 알아볼까요? 클래스에는 Method(메서드)도 존재합니다.
Method는 종류가 크게 2가지로 나뉩니다. 바로 instance method와 class or type method죠.
- instance Method : 인스턴스가 사용하는 메서드
- class or type Method : 클래스가 사용하는 메서드
제 주관적인 입장으로 이 둘을 구분하는 방법은 func앞에 class의 유무입니다. 클래스 메서드는 func 앞에 class를 붙이기 때문에 저는 이 방법으로 Method를 구분합니다. 바로 예제 코드에서 알아보겠습니다.

//인스턴스 생성 후 메서드, 프로퍼티 접근
class Man { //저장 프로퍼티는 초기값이 꼭 필요함, nil도 상관없음
    var age : Int = 1
    var weight : Double = 3.5
    
    func display(){ //인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
    //클래스 메서드, class func와 static func는 유사하나 상속에서 기능이 달라짐.
    class func cM(){
        print("cM은 클래스 메서드입니다.")
    }
    static func scM(){
    print("scM은 클래스 메서드(static)")
    }
}

var Taram : Man = Man() //인스턴스명 : 클래스 = 클래스()로 새로운 인스턴스 초기화
var Taram = Man()       //인스턴스명 = 클래스()도 가능
Taram.display() //나이 = 1, 몸무게 = 3.5 
print(Taram.age) //1
Man.cM() //Taram.cM()은 오류 -> 인스턴스가 아닌 클래스가 가지고 사용하는 메서드임
         //cM은 클래스 메서드입니다.
Man.scM() //Taram.scM()은 오류 -> 인스턴스가 아닌 클래스가 가지고 사용하는 메서드임
          //scM은 클래스 메서드(static)

1. 클래스 정의 :
먼저 아까와 같은 방식으로 저장 프로퍼티를 선언합니다.
추가적으로 func display()라는 나이와 몸무게를 출력하는 인스턴스 메서드를 선언합니다.
이렇게 인스턴스 메서드는 func 메서드명으로 선언합니다.
class 메서드는 class func 메서드명으로 선언합니다.

2. 인스턴스 생성 :
이제 클래스를 기반으로 인스턴스를 생성합니다. 
인스턴스명 : 클래스 = 클래스() or 인스턴스명 = 클래스() 로 새로운 인스턴스를 초기화합니다.
그럼 Taram이라는 인스턴스는 Man 클래스를 포함하는 객체가 됩니다.

3. 인스턴스 메서드와 프로퍼티 접근 :
display 메서드를 호출하면 해당 인스턴스의 age와 weight 값을 출력합니다. 또한, print(taram.age)를 통해 age 프로퍼티의 값을 출력할 수 있습니다.

4. 클래스 메서드와 프로퍼티 접근 :
자 여기서 제가 하는 말을 정확히 기억하셔야 합니다. 인스턴스 메서드는 인스턴스가 사용하는 메서드기 때문에 Taram이라는 인스턴스로 호출합니다. 하지만 클래스 메서드는 클래스가 사용하는 메서드라고 말씀 드렸습니다. 그렇기 때문에 접근하여 호출할 때 Taram.cM()이 아닌 Man.cM()으로 선언해주셔야 합니다.

다음은 init()과 self에 대해 설명해드리겠습니다.
- init()
  인스턴스를 초기화하는 역할을 수행합니다.
  클래스, 구조체, 열거형 인스턴스가 생성되는 시점에서 해야할 초기화 작업을 말합니다.
  인스턴스가 만들어지면서 자동으로 호출되며 하나라도 직접 만들면 기본적으로 만들어지는 눈에 안보이는 default initializer는 사라집니다.
- self
  현재 클래스 내 메서드나 프로퍼티를 가리킬 때 메서드나 프로퍼티 앞에 self.을 붙입니다.
  Java에서의 this와 self는 같은 기능을 지원합니다.

바로 init과 self를 사용한 예제를 살펴보겠습니다.

class Man { //저장 프로퍼티는 초기값이 꼭 필요함, nil도 상관없음
    var age : Int = 1
    var weight : Double = 3.5
    
    func display(){ //인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
    init(age: Int, weight : Double){
    self.age = age //24번째 줄을 사용하기 위해 self.사용
                       //java에서의 this와 같이 현재 클래스내의 메서드나 프로퍼티를 가리킴
                       //즉 self.age는 age를 가리킴
    self.weight = weight
    } //designated initializer

}

//var Taram : Man = Man() //인스턴스명 : 클래스 = 클래스()로 새로운 인스턴스 초기화
                        //init()을 하나라도 직접 만들면 default initializer는 사라짐.
                        
//var Taram = Man()       //인스턴스명 = 클래스()도 가능

//var Taram : Man = Man.init() //initializer 수동 입력, 입력하지 않아도 자동으로 만들어줌.
                             //.init생략 가능, 붙일 경우 init은 함수기 때문에 꼭 ()를 붙여줌.

var Taram : Man = Man(age:22, weight:76.2) //나이 = 22, 몸무게 = 76.2
//var Taram = Man(age: 22, Weight: 76.2) //지역변수를 넣고 다시 지역변수를 넣기 때문에 오류 발생
Taram.display()
print(Taram.age) //22

전 코드에서 init부분과 self부분을 추가하였습니다.
init은 인스턴스를 초기화하는 역할이라고 말씀 드렸습니다.
1) init 메서드는 age와 weight라는 2개의 매개변수를 받습니다. 이 매개변수를 통해 객체를 초기화 할 때 필요한 정보를 전달 받습니다.

2) self.age와 self.weight를 사용하여 해당 클래스의 프로퍼티 age와 weight를 초기화합니다. self 키워드는 현재 객체를 가리키며, 클래스 내에서 같은 이름의 매개변수와 프로퍼티를 구분하기 위해 사용됩니다.
즉 나중에 사용하기 위해 매개변수 age를 가리켜 저장합니다.

3) 이는 인스턴스 메서드기 때문에 Taram이라는 인스턴스를 사용하여 호출합니다.
*추가적으로 여기서 init을 사용하기 때문에 처음 age와 weight의 초기값은 생략하여도 에러가 발생하지 않습니다.