[Swift] print함수 딥다이브

강대훈·2025년 4월 17일
post-thumbnail

지금까지 print 함수는 천 번, 만 번은 써왔던 거 같은데 한 번도 아래의 있는 print 함수는 사용해 본 적이 없다.

func print<Target>(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n",
    to output: inout Target
) where Target : TextOutputStream

대충 매개 변수가 어떤 역할을 하는지 알고 있었기 때문에 깊이 공부해본 적도 없었고, 저 두 번째 있는 함수는 처음 보는 함수이기에 궁금해서 이번 기회에 알아보려고 한다.

먼저 아래에 있는 첫 번째 함수를 먼저 설명하고 그 다음에 처음 보는 두 번째 함수에 대해서 설명해보려고 한다.

// 1
func print(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n"
)
// 2
func print<Target>(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n",
    to output: inout Target
) where Target : TextOutputStream

첫 번째 함수

Writes the textual representations of the given items into the standard output.

→ 주어진 항목들의 텍스트 표현들을 표준 출력에 작성합니다.

func print(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n"
)

// items -> 가변 매개변수로 0개 이상의 어떤 타입이든 상관없이 받을게.
// separator -> items로 받은 변수 사이에 구분자를 넣어줄게. 기본 값은 " " 이야.
// terminator -> 모든 items가 표준 출력되고 출력될 문자열이고 기본 값은 "\n" (줄바꿈) 이야.

각각의 매개변수가 어떤 역할을 하는지 알았으니 한 번 사용해보자.

print("A", 1, 1.0) // A 1 1.0

print("A", "B", "C", separator: "\n")
// A
// B
// C

print("A", terminator: "BC")
// ABC

여기까지는 항상 쉽게 사용했었던 print 함수다. 크게 어려울게 없으니 이제 두 번째 print 함수를 한 번 보자.

두 번째 함수

Writes the textual representations of the given items into the given output stream.

→ 주어진 항목들의 텍스트 표현들을 주어진 output stream에 작성합니다.

아까 봤었던 첫 번째 print 함수와 어떤 다른점이 있을까?

아까 함수는 standard output에 write한다고 적혀있고, 지금 보고 있는 두 번째 print 함수는 output stream에 write한다고 적혀있다. 이 두 개의 차이점을 잘 기억하고 선언부를 한 번 보자.

func print<Target>(
    _ items: Any...,
    separator: String = " ",
    terminator: String = "\n",
    to output: inout Target
) where Target : TextOutputStream

// items, separator, terminator는 첫 번째 print함수와 동일
// output -> 각각의 item의 텍스트 표현을 수신 할 output stream 이야.

inout 키워드가 보이는데 이것은 어떤 의미를 가질까?

inout 키워드

먼저 기본적으로 값 타입의 변수는 함수의 매개 변수로 전달할 때 Copy In, Copy Out 방식을 사용한다. 그렇게 되면 값 자체를 복사해서 함수에 전달하기 때문에, 실제로 함수 내부에서 사용되는 파라미터의 값이 변경되어도 영향을 받지 않는다.

하지만 inout 키워드를 사용한다면 매개 변수로 전달된 값 타입이 직접 수정될 수 있다. 값을 복사해서 전달을 하긴 하지만, 함수가 종료될 때 함수 외부에 있는 값 타입에 함수 내부에 있는 복사된 값 타입이 복사된다.

쉽게 생각한다면 실제 원본이 전달된다고 생각하면 이해하기가 쉬울 것이다.

func add(a: inout Int) {
    a += 5
}

var num = 5
add(a: &num) // inout 키워드가 있으면 &를 통해 전달해야 한다.
print(num) // 10 출력

그리고 Target에 타입 제약 TextOutputStream 이 걸려있는 것을 확인할 수 있다.

TextOutputStream

그러면 TextOutputStream 객체가 하는 역할은 무엇일까?

A type that can be the target of text-streaming operations.

→ text-streaming작업의 target이 될 수 있는 타입이다.

한글로 번역은 해봤고 어째 영어가 더 많긴 하지만, text를 스트림 받을 수 있다는 뜻처럼 보이기는 한다.

여기서 text는 print 함수로부터 오는 텍스트 표현이라고 받아들이면 될 거 같다.

실제로 아까 위에서 봤던 print 함수 정의를 다시 한 번 보자.

주어진 항목들의 텍스트 표현들을 output stream에 작성합니다.

여기서 output stream은 Target 타입을 의미하는데, Target 은 아까 TextOutputStream 타입으로 제약이 걸려있는 것을 확인했었다.

그러니까 output stream에 작성하겠다는 것은 TextOutputStream 타입을 채택한 타입에게 스트림하겠다는 의미와 동일하다.

그렇다면 TextOutputStream을 채택한 타입은 어떤게 있을까? 가장 대표적인 것은 String이다.

그러면 우리는 먼저 String타입을 사용해서 일단 한 번 실행해보자! 결과는 어떨까?

var str: String = "111" // TextOutputStream이 채택되어 있음!
print("abcde", to: &str)

// 출력 : 111abcde

출력은 “abcde”가 아닌 “111abcde”가 나온다. 왜 그럴까?

먼저 결과를 얘기해보자면, print 함수에 있는 출력 될 표현 값들이(= “abcde\n”) 주어진 output(= “111”)에 스트림 된 것이다.

그러니까 output에 스트림 된 건 알겠는데, 왜 덮어쓰지 않고 이어져서 작성이 된 걸까? 그것에 대한 해답은 바로 write(String) 메소드에 있다. 해당 메소드는 TextOutputStream타입의 유일한 필수 구현 메소드다.

실제로 String에서 채택한 write(String) 메소드의 정의를 찾아보자.

mutating func write(_ other: [String])

Appends the given string to this string.

→ 이 String타입에 주어진 String타입을 추가합니다.

이거에 대한 뜻은 “111” 에 “abcde\n” 을 스트림 받아서 추가한다는 것으로 받아들여질 수 있다.

write(String) 메소드의 구현부는 감춰져있지만, print 함수가 호출될 때 write(String)메소드 또한 호출되며 구현부에서 “abcde\n”이 스트림되는 과정이 존재하고, 출력된다는 것을 추측해 볼 수 있다.

그러면은 우리는 이런 로직으로 실행되는 것이 맞는지 직접 확인해 볼 필요가 있다.

직접 TextOutputStream을 채택해서 실행해보자!

직접 만들어보기

struct CustomString: TextOutputStream {
    var value: String

    mutating func write(_ string: String) {
        value += string
        print(value)
    }
}

var customString = CustomString(value: "111")
print("abcde", to: &customString)

실제로 채택된 write(String) 메소드가 호출되는 것을 볼 수 있으며, 지금 보고 있는 함수는 첫 번째에 봤던 print 함수와는 달리 표준 출력을 하지 않기 때문에 만약 콘솔창에서 출력 값을 보고 싶다면 write(String) 메소드에서 print(items) 함수를 호출해주어야 한다.

var str: String = "111"
print("abcde", to: &str)

// 출력 : 111abcde

아까 위에서 봤던 예제가 표준 출력이 이루어졌던 걸 보면, String에서 구현한 write(String) 메소드에는 print(items) 함수가 호출되었다는 것도 짐작할 수 있다.

print 함수는 무조건 표준 출력을 해준다고 알고 있었는데, 다른 방법으로 사용될 수 있다는 것을 또 알게 된 계기가 되었다.

새로운 개념을 알아가는 건 역시 어렵고 공식문서와도 친해지기란 참 어려운 거 같지만 익숙해지다 보면 더 성장할 거 같다!

참고자료

https://developer.apple.com/documentation/swift/print(_:separator:terminator:to:)

https://developer.apple.com/documentation/swift/string/write(_:)

https://developer.apple.com/documentation/swift/textoutputstream

https://developer.apple.com/documentation/swift/string

https://zeddios.tistory.com/1010

0개의 댓글