카테고리 보관물: 21세기소년

IUO 를 인자로 받는 함수 (Swift)

IUO타입을 인자로 선언한 함수들이 있는데, 이들은 주로 Objective-C/C API에서 발견된다. Objective-C에서 임의의 객체를 의미하는 id 타입은 항상 nil 일 수 있기 때문에 파라미터 타입을 AnyObject 가 아닌 기대하는 타입의 옵셔널로 받는 것이다. 하지만 이러한 API의 내부에서는 대부분 해당 값에 대해서 null 검사를 엄밀히 하지 않거나 하지 않아도 되는 경우가 많다.1

또한 C-API에서도 흔히 볼 수 있다. 이들은 임의의 포인터 혹은 불투명 타입[^1]을 인자로 받는데, C언어의 특성상 nil을 다른 값과 구분할 수 없는데, Swift 입장에서는 nil이 곧 NULL이기 때문에 옵셔널을 보낼 수 있게 되기 때문이다. 따라서 어떠한 API의 인자가 !으로 선언된 암묵적 언래핑 옵셔널을 사용한다고 할 때, 이를 사용하는 측면에서는 다음의 당연한 두 가지 접근법 중 하나를 취하게 된다.

  1. 옵셔널에 대해서 nil 체크를 하고 언래핑하여, 최종적으로는 옵셔널이 아닌 타입의 값만 넘긴다.
  2. 옵셔널 값을 그냥 넘겨준다.

2의 경우에는 자신이 무엇을 하고 있는지 정확히 파악되는 경우에만 시도해야 한다. Objective-C/C API 내부에서는 받은 인자의 값이 NULL이더라도 어떠한 동작을 하게끔 시도할지는 몰라도, 그것은 감춰져서 알 수 없으며 혹시라도 nil 값이 그대로 넘어간 경우에는 런타임 에러로 직결되기 때문이다.

많은 C/Objective-C API에서 이러한 타입을 볼 수는 있다. 하지만 그것은 언어의 기능이 불안정성을 깔끔하게 덜어내지 못하고 많은 부분의 불안요소를 컴파일 타임을 넘어 런타임까지 짊어지고 갈 수 밖에 없는 한계를 가지기 때문에 그러한 디자인을 갖게 된 것이다. Swift는 언어 수준에서 불안정성을 최소화하고 컴파일 타임에서 발생할 수 있는 최대한의 범위의 에러를 미리 찾을 수 있는 기능을 제공하고, 지원한다. 그것을 이용하고 지키는 습관은 단순히 타이핑을 몇 자 덜하는 것보다 훨씬 더 생산적이며, 나아가 C가 아닌 Swift의 디자인과 패러다임을 통한 사고의 틀을 만드는 것에 보다 빨리 익숙해지게 한다.

Swift에서 T! 타입을 인자로 받는 함수를 정의하기

이런 타입 시그니처는 함수 작성시에 당연히 쓸 수 있는 표현이기 때문에 그러한 타입의 인자를 받는 함수를 정의할 수는 있다. 하지만 앞에서도 말했듯이 그럴 필요가 있을 케이스가 있을 가능성이 0%이다. 받으려는 값이 항상 있어야하는 경우에는 T 타입을 쓰면 되고, 없거나 실패할 수 있는 가능성이 있다면 옵셔널을 쓰면 된다. 그 외의 경우는 없다.


  1. Objective-C에서는 nil에 어떤 메시지를 보내면 그냥 무시되기 때문에. 

shell 파라미터 치환

${ 파라미터 }

${Parameter}$Parameter와 같은 표현으로 주로 붙여쓴 경우에 구분을 위해서 사용하는 표기로 알려져 있다.

your_id=${USER}-on-${HOSTNAME}
echo "$your_id"
#
echo "Old \$PATH = $PATH" 
PATH=${PATH}:/opt/bin
echo "New \$PATH = $PATH"

${ 파라미터-디폴트}, ${파라미터:-디폴트}

파라미터가 정의되지 않았다면 디폴트값을 쓸 수 있게 해준다.

var1=1
var2=2
# var3 is not defined
echo ${var1-$var2} # 1
echo ${var3-$var2} # 2

echo ${username-`whoami`} # username이 정의되지 않았다면 `whoami`의 결과를 출력한다.

-, :- 의 차이는 별로 없다. 다만 - 는 변수이름자체가 정의되지 않았을 때 동작하며, :- 는 값이 null 일 때도 동작한다.

username0=     # null
# username1, username2 는 선언조차 되지 않음
echo ${username0-`whoami`} # 프린트 되지 않는다.
echo ${username1-`whoami`} # 프린트 된다.
echo ${username2-`whoami`} # 프린트 된다.

echo ${username0:-`whoami`} # 프린트 된다.
#               ^
echo ${username1:-`whoami`} # 프린트 된다.
echo ${username2:-`whoami`} # 프린트 된다.


이는 흔히 스크립트 내에서 인자가 생략되었을 경우 디폴트 값을 쓰는 데 쓰인다.

#!/usr/bin/bash
# add.sh
x=$((${1-1}+${2-2}))
echo $x

그리고 다음과 같이 쓴다.

$ add.sh 30 40
70
$ add.sh 30
32
$ add.sh
3

${파라미터=디폴트}, ${파라미터:=디폴트}

${-} 는 없는 변수에 대해 그 값만 디폴트를 취하는 것인데, 이 표현은 변수가 없거나 null 값이면 (:=의 경우) 즉석에서 해당 변수를 정의한다. 따라서 해당 문맥 이후에는 내부에서 정의한 변수값을 계속 사용할 수 있게 된다.

$ echo ${var=abc} # abc
$ echo ${var=xyz} # abc
$ echo $var # abc

${파라미터+대체값}, ${파라미터:+대체값}

잘 쓰이지는 않을 것 같은데 :-의 반대 표현이다. 선언되었고 값이 정의되어 있는 경우에만 대체값이 쓰인다. 그렇지 않은 경우 널스트링이다. :+의 경우에는 선언은되었으나 값이 널인 경우만 대체값을 쓴다.

${파라미터?에러메시지}, ${파라미터:?에러메시지}

파라미터가 있으면 그 값을 쓴다. 그렇지 않은 경우에는 에러메지시를 표시하고 리턴 코드 1을 내며 스크립트를 즉시 종료한다.

${#변수}

변수를 확장했을 때 문자의 길이가 된다. 배열인 경우에는 첫번째 원소의 길이가 된다.

$ x="hello world"
$ echo ${#x}
11
$ y=(hello world)
$ echo ${#y}
5 # hello

${변수#패턴}, \${변수##패턴}

변수를 확장한 값에 대해서 패턴과 매치되는 최소(#), 최대(##) 매칭을 제거한다.

# current directory
echo $PWD  # 전체 경로 출력
echo ${PWD##*/} # 디렉토리 이름만
filename=hello.txt
ext=${filename##*.} # txt

${변수%패턴}, ${변수%%패턴}

변수의 값에 대해서 패턴과 매치되는 맨 뒤쪽 끝 매칭을 제거한다. % 는 최소, %%는 최대이다.

var1=abcd12345abc6789
pattern1=a*c

echo
echo "var1 = $var1"
echo "var1 = ${var1}"

echo "Number of characters in ${var1} = ${#var1}"
echo

echo "pattern1 = $pattern1"
echo "----------------"
echo '${var1#$pattern1} =' "${var1#$pattern1}" # abcd12345abc6789
#                                         |-|
# d12345abc6789
echo '${var1##$pattern1} =' "${var1##$pattern1}" # abcd12345abc6789
#                                           |----------|
# 6789
echo;echo;echo

pattern2=b*9
echo "var1 = $var1"
echo
echo "pattern2 = $pattern2"
echo "--------------"
echo '${var1%$pattern2} = ' "${var1%$pattern2}" # abcd12345abc6789
#                                                        |----|
# abcd12345a
echo '${var1%%$pattern2} = ' "${var1%%$pattern2}"   # abcd12345abc6789
#                                              |-------------|
# a

다음은 이를 활용한 확장자 변경 스크립트 (이름만 바꾼다.)

#!/bin/bash

E_BADARGS=65

case $# in
  0|1)     # 인자의 개수가 0, 1개인 경우,
  echo "Usage: `basename $0` old_file_suffix new_file_suffix"
  exit $E_BADARGS
esac

for filename in *.$1
do
    mv $filename ${filename%$1}$2
done

exit 0

그외 몇 가지 더…

  • ${var:pos} : 주어진 위치부터 끝까지
  • ${var:pos:len} : 중간 슬라이스
  • ${var/pattern/replacement}: 패턴에 일치하는 부분을 치환한다.
  • ${var//pattern/replacement} : 패턴에 일치하는 부분 전체를 치환한다.
  • ${var/#pattern/replacement} : 패턴에 일치하는 부분을 맨 앞 한군데만 치환한다. (맨 첫 시작부터 매칭해야 함)
  • ${var/%pattern/replacement} : 패턴에 일치하는 부분을 맨 뒤 한군데만 치환한다. (맨 끝부분이 정확히 일치해야 함)
  • ${!varprefix*}, ${!varprefix@} : 변수이름이 접두사와 매칭하는 전체 변수값들을 가져온다.