[잡담] 스레싱, 버벅이는 컴퓨터

컴퓨터의 메모리는 물리적으로 그 크기가 한정되어 있는데 비해, 메모리를 사용하고자 하는 프로그램은 많다. 특히 윈도와 같은 환경에서는 동시에 여러가지의 프로세스가 동시에 실행되면서 시스템이 운용되는데, 이런 경우 특정한 프로그램이 많은 양의 메모리를 필요로한다면 운영체제는 “페이징”이라는 기법을 사용해서 부족한 메모리를 최대한 활용하게 한다.

페이징이란 메모리의 내용을 그대로 하드디스크에 써두고, 따로 써둔 메모리를 비워 여유 공간을 확보하는 방법이다. 그렇다, 윈도에서 흔히 말하는 “가상메모리”가 바로 이 페이징을 말한다.

윈도를 사용하다보면 메모리가 아직 여유 공간이 20~40% 이상 남아 있음에도 불구하고 하드디스크를 미친 듯이 읽으며 반응이 없거나 계속해서 잠깐 잠깐 멈추는 것처럼 동작하는 경우를 종종 느낄 수 있다. 이런 경우 재부팅을 해보기도 하지만 만약 메모리를 많이 차지하는 프로그램을 몇 가지 계속 사용해야 하는 경우라면 얼마 지나지 않아 이러한 상태를 반복적으로 만나게 된다.

컴퓨터가 계속해서 페이징을 하면서 버벅이는것을 스레싱(Thrashing)이라고 한다. 스레싱을 일반적으로 컴퓨터에 장착된 물리적인 자원보다도 더 많은 자원을 계속적으로 필요로 할 때 발생한다. 2GB가 물려 있는 PC에서 여러 프로그램들이 필요로하는 메모리의 양이 그보다 많을 때 시스템은 계속해서 페이징을 하면서 순차적으로 각 프로그램들에게 필요한 메모리를 공급해 주려하게 된다.

이 때, 일반적으로 디스크에 입출력이 일어날 때 실제 프로세스는 잠시 대기하게 되어 실제로 프로세스가 CPU를 사용하는 일은 적고, 시스템은 계속 페이징을 하게되다보니 사용자가 느끼기에 컴퓨터가 멈추는 것처럼 보이는 것이 스레딩이다. 즉 컴퓨터가 프로세스를 실행하는 것보다 각 프로세스에게 자원을 돌려 막기 식으로 할당하느라 아무것도 못하는 상태를 말한다.

이미 말했지만 스레딩은 컴퓨터에 장착된 메모리가 부족할 때 발생한다. 하지만 경험적으로 윈도 상에서는 메모리의 여유공간이 꽤 남아 있음에도 스레딩을 경험할 수 있다. 이는 실제 메모리 상에는 여유공간이 있지만, OS가 프로세스에 할당해줄 수 있는 메모리의 주소가 부족할 때도 같은 증상이 발생할 수 있다. (특히 여유 메모리가 30% 미만일 때 프로그램 사이를 전환하면 이런 증상을 경험하기 쉽다.)

윈도XP의 경우 자원 관리가 그리 괜찮은 수준이 아니어서 그렇다고 하더라도 비스타나 7에 이르면서도 이런 자원의 효율적인 관리는 여전히 만족할만한 수준으로 이뤄내고 있지는 못하는 것 같다.

몇 년 전 주로 쓰는 컴퓨터의 운영 체제를 리눅스로 바꿨을 때, 이러한 스레딩을 별로 경험하지 못한다는 걸 알았고 맥으로 전향한 이후에도 상당히 쾌적한 (심지어 몇 달씩 컴퓨터를 끄지 않아도 되는) 사용을 계속해 왔다.

컴퓨터는 별로 느려지지 않았지만 메모리 사용량이 99%에 달했을 때 솔직히 개인적으로 충격을 받았다. 그렇다 유닉스 계열 (엄밀히 리눅스를 유닉스 계열이라 칭하는 게 정확한 표현인지는 모르겠으나)에서는 정말이지 극도로 메모리를 잘 활용하고 있더라는 걸 깨닫는 순간.

맥을 사용해온지 만2년이 되는 것 같다. 역시나 맥에서도 스레싱을 거의 경험해보지 못한 것 같다. 회사에서 무거운 아웃룩, 파워포인트, 엑셀을 거의 항상 띄워두고 일하다 보면 회사 컴퓨터도 맥으로 지급해주면 얼마나 좋을까 하는 생각을 오늘도 해 본다. 만약 이런 버벅임 때문에 컴퓨터를 쓰기가 힘든 지경이고 업그레이드를 고민하고 있다면, 더 높은 성능의 컴퓨터를 사기보다는 리눅스나 맥으로의 전향을 고려해 보는 것이 정신 건강에 좋을 것이라고 말해주고 싶다.

[Python101] 005 함수,모듈, 그리고 도움이 되는 도구들

지난 시간까지 기본적인 프로그래밍에 필요한 “입출력”을 다루는 부분을 간단하게 나마 살펴보았다. 실제로 지금까지의 내용은 뭔가 설명이 필요하거나 개념적인 이해를 도모하는 부분과는 조금 거리가 있었고, 마치 조리법처럼 “이렇게 하면 이렇게 이렇게 됩니다.” 정도였기에 경우에 따라서는 조금 재미도 떨어지고 지겨운 부분이 없지 않아 있었을 것으로 생각된다.

이번 시간부터는 프로그램을 구성하는 단위와 이를 어떻게 만드는지, 그리고 프로그램의 흐름은 어떤 식으로 만들어지는지를 살펴보고자 한다.

모듈

모듈(Module)이라는 것은 다른 것으로 대체 가능한 어떤 구성 요소를 뜻하는데, 파이썬에서는 미리 만들어두었다가 필요할 때 꺼내 쓰는 레고 조각같은 프로그램의 조각을 말한다. 보통 프로그램의 소스 코드가 몇 백줄에서 몇 천줄까지 커지는 크고 복잡한 프로그램을 만든다면 이를 하나의 파일에 모두 작성하게 되면 사소한 오타로 인해 발생하는 에러를 잡기도 힘들어지고, 나중에 어떤 부분을 수정해야할 때 큰 범위를 일일이 찾아야 하므로 상당히 불편하고 어려워진다. 파이썬 프로그램 소스는 그 단위 단위가 모두 ‘모듈’이고, (뒤에서 설명할) 단위 프로그램 내의 함수나 클래스 같은 것들도 한 편으로는 모듈이라고 할 수 있다.

예를 들어 운영 체제에서 어떤 파일이나 폴더가 있는지를 검사하고, 파일을 복사, 삭제하거나, 폴더에 파일이 몇 개나 들어있는지를 알아내는 함수들을 만들었다고 하자. 나중에 이런 기능을 다른 프로그램에서도 사용해야 한다면, 그 때가서 똑같은 기능을 일일이 구현하는 것은 상당히 번거로운 일이 될 수도 있다.

그래서 이런 여러 개의 함수를 path(경로)라는 모듈로 묶어준다. 그리고 이런 경로와 관련된 것외에 운영체제의 특성이나 명령과 관련되는 모듈들을 다시 묶어서 os라는 모듈로 만들어둘 수 있겠다. 그리고 이게 파이썬이 기본적으로 제공하는 os 라이브러리의 정체가 된다.

from os.path import exists

라는 구문을 통해서 파일의 존재여부를 알아낼 수 있는 exists()라는 함수를 이미 사용해 본 바가 있다. (이는 우리가 구현한 것이 아니라 이미 구현되어 있는 함수를 가져다 사용한 예이다.)

파이썬을 설치하면 기본적으로 함께 설치되는 표준 라이브러리에도 활용가치가 높은 모듈들이 많이 있으며, 또 이미 많은 개발자들이 유용한 모듈들을 만들어서 공개해두고 있어 엄청나게 많은 활용 가능한 모듈들이 있다. 그래서 파이썬으로 작은 유틸리티를 만드는 일은 무척이나 쉬울 수 있다. (거의 필요한 기능들은 찾아보면 다 만들어져 있어서) 마치 레고로 자동차를 조립하는 것처럼 핸들, 바퀴, 차대 등의 부속품을 import 하여 필요한 모듈들이 함께 동작하도록 만들어주면 되는 것이다.

import / from… import

지금 편집하고 있는 소스코드 파일이 아닌 다른 곳에서 모듈을 가져와서 사용할 때 import 명령을 사용한다. import 명령은 다른 모듈 파일의 전체 혹은 일부를 현재 프로그램으로 반입해 오는 명령이다. 반입한 객체는 마치 미리 정의한 것과 같은 효과를 갖게 된다. 덕분에 프로그램 소스 코드가 간결해지고, 만들어야 하는 코드가 적어진다. (게다가 이렇게 모듈로 만들어둔 프로그램은 다른 프로그램에서 쉽게 재사용할 수 있게 된다.)

또한 모듈로 작성된 파일 내에서 그 일부만 반입해오는 경우에는 from 모듈이름 import 실제 반입할 모듈과 같은 식으로 코드를 반입할 수 있다.

모듈을 통째로 반입한 경우에는 그 하위에 있는 함수를 사용할 때 모듈.함수()와 같은 식으로 . 구분자를 써서 이들을 연결해 줘야 하지만 from 모듈 import 함수한 경우에는 함수 이름을 그대로 사용해주면 된다.

실제로 모듈을 만들어서 사용하는 예는 이 글의 말미에서 다시 다루기로 하고, 이번에는 함수에 대해 알아보도록 하겠다.

함수

함수는 프로그래밍에서 가장 중요한 개념 중의 하나이다. 함수는 “가장 작은 온전한 프로그램의 최소 단위”이다. 이 말이 무슨 뜻인고 하니, 우리가 흔히 ‘함수’라고 하면 y=2x+5와 같이 중고등학교 수학시간에 배운 함수를 떠올리게 된다. 음… 맞다. 함수는 딱 저렇게 생겨서 동작하게 된다. 여전히 감이 잘 오지 않는데, 이 함수를 잘 살펴보도록 하자.

x = 1 을 넣으면 y = 7이 된다. x = 2 를 넣으면 y = 9가 된다. 즉 어떤 값을 입력 (x에 대입)해 주면 이 값을 처리하여 그 결과값을 돌려주고 있다. 그리고 그 결과는 “입력된 값을 2배하고, 거기에 5를 더한다”는 처리를 한 결과값이다.

프로그래밍에서의 함수는 “가장 작은 프로그램의 단위”라고 했다. 프로그램 자체가 입력을 받아 이를 처리하고 그 결과를 출력해주는 기계 장치이므로, 이러한 프로그램의 특성을 함수는 그대로 가지고 있다. 그래서 프로그램 그 자체도 함수이며, 프로그램은 작고, 많은 함수들이 결합된 형태로 만들어진다.

그럼 어떤 기능을 함수로 만들면 좋을까? 답은 “반복적으로 하게 되는 작업”을 함수로 만들면 된다. 그 역시 감이 잘 오지 않는다면 “함수는 타이핑을 줄여주는 테크닉”이라고 처음에 생각해도 크게 무리가 없다.

함수 만들기

실제로 함수를 한 번 만들어 보자. 함수를 만드는 키워드는 def 이다. 함수는 다음과 같이 생성한다.

def 함수이름(인자값, ...):
    <# 어떤 동작을 하는 코드들 #>
    [return 반환값]

def (define) 키워드 다음에 함수의 이름을 쓴다. 그런 다음 함수가 받는 인자값의 이름을 써준다. 문장의 끝은 콜론으로 끝나고, 실제 함수가 해야 하는 동작은 모두 들여쓰기 해준다. 위에서 이야기한 y =  2x + 5를 파이썬 함수로 만들어보자.

def doublePlusFive(x):
    y = 2*x + 5
    return 5

print(doublePlusFive(1)) #==> 7
print(doublePlusFive(2)) #==> 9
print(doublePlusFive(5)) #==> 15

물론 위의 예제에서는 함수를 만드는 예를 보이다보니 저럴 거면 왜 함수를 쓰느냐고 할 수 있는데, 만약 섭씨나 화씨 온도를 서로 변환해야 하는 경우를 생각해보자. 섭씨온도를 화씨 온도로 바꾸는 식은 다음과 같다.

℉ = ℃ * 9 / 5 + 32

반대로 화씨 온도를 섭씨온도로 바꾸는 식은 다음과 같다. (위의 식을 바꾼 것에 지나지 않는다.)

℃ = (℉ - 32) * 5 / 9

그럼 화씨와 섭씨를 각각 바꿔주는 함수는 다음과 같이 작성할 수 있을 것이다.

#ex12.py
def FtoC(fh):
  return (fh-32) * 5 / 9.0

def CtoF(cd):
  return cd*9/5.0 + 32

이렇게 함수로 만들어두면 나중에는 FtoC(45) 라고만 쓰면 화씨 45도가 섭씨 몇 도에 해당하는지 쉽게 알 수 있게된다. 만약 코드상으로 저 공식을 바로 사용하는 것도 “똑같은” 방법이라 할 수 있겠지만 1) 시간이 지나고 나서는 이 공식이 뭔지 기억이 안날 수도 있고, 2) 나중에 다시 같은 공식을 써야할 때 공식이 생각나지 않는 경우가 생길 수 있다.

대신에 함수를 사용하면 1)함수의 이름으로 이게 무슨 처리를 하는지 유추할 수 있고 2) 함수를 모듈로 만들어 놓으면 굳이 공식을 기억하지 않아도 함수를 바로 사용할 수 있다.

함수와 모듈

위의 소스 코드를 ex12.py로 저장하고 이를 모듈로 사용하는 ex13.py를 새로 작성해보도록하자.

#ex13.py
#-*-coding:utf-8
#이 파일은 ex12.py와 같은 폴더에 저장해야 함.

import ex12

celsius = 25
farenheit = 45

print("섭씨 %d도는 화씨로 %f입니다." % (celsius, ex12.CtoF(celsius))
print("화씨 %d도는 섭씨로 %f입니다." % (farenheit, ex12.FtoC(farenheit))

ex13.py는 ex12.py의 내용을 ex12라는 이름의 모듈로 입수한다. 우리가 이미 작성한 ex12.py 파일내의 모든 내용이 이 부분에 덧붙는 다고 생각하면 된다.

또한 우리가 반입한 내용이 ex12 전체이기 때문에 그 속에 들어있는 함수를 사용할 때는 ex12.FtoC(), ex12.CtoF()와 같은 식으로 반입한 모듈의 이름을 앞에 붙여주고 점(.)으로 구분해 준다. 만약 FtoC(), CtoF()의 이름을 그대로 사용하고 싶다면 다음과 같이 import 구문을 바꿔주면 된다.

from ex12 import FtoC
from ex12 import CtoF

모듈의 위치

모듈로 반입해 올 수 있는 파일의 위치는 특정한 범위로 제한된다. 현재 실행되는 프로그램 소스와 같은 폴더에 있거나, 파이썬의 표준 모듈 폴더에 있어야 한다. 이 표준 모듈 폴더는 sys.path라는 곳에 저장되어 있는데 여기에 특정한 폴더를 추가해 줄 수도 있다. 이 방법에 대해서는 나중에 다시 살펴볼 기회가 있을 것이다.

대화형 쉘 활용하기

지금까지 대화형 쉘은 그저 “계산기” 정도의 용도로 쓴 것이 사실이다. 단순히 하나의 명령이 어떤 일을 하는지 이미 알고 있는 상황에서 print 명령을 쓴다거나 하는 일이 그리 ‘효율적’으로 느껴질리도 없다. 지금부터는 대화형 쉘을 어떻게 활용할 수 있는지 살펴보겠다.

함수를 테스트하기

대화형 쉘은 기본적으로 한 번에 한 줄의 명령을 실행할 수 있도록 만들어져 있다. 즉 명령문을 입력하고 엔터키를 누르면 해당 명령이 바로 실행되어 즉시 그 결과를 볼 수 있는 것이다.

하지만 함수를 만드는 def 구문을 쓸 때는 엔터를 누르더라도 바로 실행되지 않는다. 위에서 예를 들었던 doublePlusFive() 함수를 대화형 쉘 모드에서 입력해보라. def doublePlusFive(x): 하고 엔터를 누르면 프롬프트가 나오지 않고 들여쓰기 상태가 된다. 이 상태에서 함수의 나머지 코드를 입력하고, 엔터를 두 번 누르면 결과가 출력되지 않고 다시 프롬프트가 표시된다.

>>> def doublePlusFive(x):
y = 2*x+5
return y

>>> doublePlusFive(7)
19

다시 프롬프트에서 doublePlusFive(7)을 입력하고 엔터를 치면 해당 함수가 실행되어 계산 결과를 표시하게 된다. 즉 대화형 쉘은 메모리에 만들어진 수식이나 읽어들인 외부 모듈을 계속 가지고 있으므로 간단한 ‘테스트’를 해보는 데 큰 도움이 된다. 특히 앞으로 배우게 될 리스트와 관련하여 애매한 부분들은 대화형 쉘에서 간단히 시험해봄으로써 정확한 명령문을 결정하는 데 도움이 될 수 있다.

모듈 함수의 도움말을 보기

help() 명령은 특정한 모듈 혹은 함수의 도움말을 표시해주는 명령이다. 백문이 불여일견, help(max)라고 대화형 쉘에서 실행해보자. max()는 이름에서 유추할 수 있지만 최대 값을 구하는 함수이다. 이 함수의 help 결과는 다음과 같다.

>>> help(max)
Help on built-in function max in module __builtin__:

max(...)
max(iterable[, key=func]) -> value
max(a, b, c, ...[, key=func]) -> value

With a single iterable argument, return its largest item.
With two or more arguments, return the largest argument.

help에서 표시하는 정보는 max가 __builtins__ 라는 특별한 이름의 모듈에 속해 있는 함수임을 알 수 있고, max(a, b, c, d…) 이런 식으로 쓰면 그 중에서 가장 큰 값을 반환한다는 설명을 볼 수 있다.

그러면 한 번 테스트해보자.

>>> someNumbers = (1,2,3,4,5)
>>> max(someNumbers)
5

someNumber라는 변수에 (1,2,3,4,5) 라는 숫자 묶음을 대입해주고, max를 통해 someNumber 중에서 가장 큰 숫자를 구하는 명령을 실행해보면 최대 값을 구해 반환하는 것을 볼 수 있다.

모듈이 어떤 기능을 수행할 수 있는지를 살펴보는 방법

우리는 방금 ‘최대값’을 구하는 함수를 어떻게 사용하는지 살펴보았다. 그렇다면, 반대로 최대값을 구하는 함수도 있지 않을까? 이름으로 유추해보자면 min()이라는 함수가 있을 것 같다. 그럼 그런 함수가 있는지 없는 지 어떻게 알 수 있을까?

이럴 때 유용하게 사용할 수 있는 함수가 있다. 바로 dir()이라는 함수이다. 이 함수는 인자로 받은 모듈에 포함되어 있는 하위 기능을 모두 열거해준다.

우리는 조금전 help(max)에서 이 기능이 “__builtins__”라는 특별한 모듈에 속해있다고 하는 것을 보았다. 양끝에 언더바(_)가 두 개씩 붙은 것은 “내부적으로”라는 의미라고 이해하면 되고 실제 글자로는 built-in된 모듈이라는 의미이다. dir 함수는 모듈의 내부 구성 요소를 볼 수 있는 명령이라 하였으니 이 __builtins__ 모듈의 내부에 정말 min 이라는 함수가 있는지 살펴보도록 하자.

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError',
'BaseException', 'BufferError', 'BytesWarning',
'DeprecationWarning', 'EOFError', 'Ellipsis',
'EnvironmentError', 'Exception', 'False', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'KeyError',
'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
'None', 'NotImplemented', 'NotImplementedError', 'OSError',
'OverflowError', 'PendingDeprecationWarning', 'ReferenceError',
'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration',
'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
'TabError', 'True', 'TypeError', 'UnboundLocalError',
'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError',
'_', '__debug__', '__doc__', '__import__', '__name__',
'__package__', 'abs', 'all', 'any', 'apply', 'basestring',
'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex',
'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod',
'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter',
'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'help', 'hex', 'id', 'input', 'int', 'intern',
'isinstance', 'issubclass', 'iter', 'len', 'license',
'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min',
'next', 'object', 'oct', 'open', 'ord', 'pow', 'print',
'property', 'quit', 'range', 'raw_input', 'reduce',
'reload', 'repr', 'reversed', 'round', 'set', 'setattr',
'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

여기서 표시되는 이름들은 거의 모두가 내장 함수 혹은 내장 모듈들이다. 앞쪽에 대문자로 시작하는 것들은 거의 내장 모듈이나 상수들을 의미하며, 중간에 __로 둘러 싸여진 이름은 모듈 내에서 다시 “내부적으로” 사용되는 기능이다. 마지막에 abs… 부터가 실제 우리가 자주 사용하는 내장 함수 들의 이름이다.

이러한 내장 함수의 이름이나 사용법을 절대로 다 외울 필요가 없다. 앞으로 공부를 해 나가면서 필요한 함수들은 그만큼 자주 사용하게 될 것이니 자연스럽게 익히게 될 것이다. 대신, 뭔가 새로운 모듈이나 함수를 앞으로 접하게 된다면 dir 및 help 명령을 통해 사용법을 확인하고 다른 연관된 명령이 있는지 살펴보는 것은 큰 도움이 된다. 반드시 이런 식으로 공부를 해 나갈 것을 권한다.

[OSX] NSSavePanel / NSOpenPanel

맥용 앱에서 문서와 같은 파일을 저장하거나, 디스크로부터 열 때 액세스하게 될 파일을 사용자가 고를 수 있도록 하는 대화상자는 NSSavePanel에 의해 만들어진다. NSOpenPanel은 NSSavePanel과 비슷하게 생겼는데, 아니나 다를까 NSOpenPanel은 NSSavePanel을 상속 받아 만들어진 클래스이다.

NSOpenPanel 사용법

NSOpenPanel의 +openPanel을 사용하여 오픈 패널 객체를 구할 수 있으며, 이 객체를 세팅하고 실행하면 된다. 다음과 같은 절차를 따르면 된다.

  1. 오픈 패널이 일반 패널창으로 열릴지, 현재 창의 시트로 열릴지를 먼저 결정한다. 다른 창으로 열 파일을 선택한다면 일반 패널창으로 열면되고, 만약 현재 창에 통합할 문서를 고른다거나 하는 경우에는 현재 창에 시트로 여는 것이 맞을 것이다.
  2. NSOpenPanel의 +openPanel 메소드를 사용해 오픈 패널 객체를 얻는다.
  3. 오픈 패널의 속성을 조정한다. 속성을 조정하는 메소드는 뒤에서 다시 설명한다.
  4. 오픈 패널을 실행한다. 오픈 패널에서 선택한 이후의 동작은 completionHandler로 넘겨주는 코드 블럭에서 정의하면 된다.

openPanel의 속성 결정

파일이 아닌 디렉토리만 선택하게 한다든지, 새 폴더를 만들게 한다든지, 한 번에 여러 개 파일을 선택하게 한다든지 하는 옵션을 설정해야 한다. 이는 패널을 열기 전에 다음의 메소드들을 사용해서 지정하면 된다.

  • -setCanChooseFIles: > 파일을 선택할 수 있게 한다.
  • -setCanChooseDirectories: > 폴더를 선택할 수 있게 한다.
  • -setAllowsMultipleSelection: > 한 번에 여러 개 파일을 선택할 지 여부를 결정한다.

패널 열기

패널 창으로 열기

패널 창으로 열기위해서는 -beginWithCompletionHandler: 메소드를 사용한다. 완료 핸들러는 (NSInteger) result를 인수로 받게 된다. 이 값은 사용자가 패널에서 ‘완료’를 선택했는지, ‘취소’를 선택했는지를 구분할 수 있게 해준다. 오픈 패널을 여는 예는 다음과 같다.

-(void)openDocument
{
  NSOpenPanel *panel = [NSOpenPanel openPanel];
  [panel setCanChooseFiles:NO];
  [panel setCanChooseDirectories:YES];
  [panel setAllowsMultipleSelection:NO];
  [panel beginWithCompletionHandler:^(NSInteger result) {
    if (result == NSFileHandlingPanelOKButton) {
      NSURL *theURL = [panel URL];
      // 문서 열기....
    }
  }];
}

만약 시트로 여는 경우에는 시트가 붙을 창 객체의 포인터를 필요로하는 -beginSheetModalForWindow:withCompletionHandler: 를 사용하면 된다.

NSSavePanel 사용법

저장패널도 열기 패널과 기본적인 사용방법은 동일하다. 다만 ‘저장’을 위한 대화상자이기 때문에 복수의 파일을 선택할 수 없다. NSOpenPanel과의 차이는 속성을 설정하는 부분이 약간 다르다는 것인데, 이는 개발자 문서에서 확인하기 바란다.

[19금] 학교 폭력에 대한 답

[주의 : 본 포스팅은 매우 저렴한 언어들로 점철될 우려가 있음]

연일 터지는 학교 폭력, 그리고 피해학생의 자살… 뭐 이제는 까딱하다간 말하면 입아파지는 수준에 이를지도 모르겠다. 그리고 한켠에서는 뭐 청소년 단체니, 여성 단체니, 교사 공무원이니… 하는 사람들이 책상 머리에 앉아서 “아이들에게 어쩌구 저쩌구…” 썰을 풀면서 온갖 학교 폭력 대책을 내놓고 일부에서는 “시험적으로” 시행해보기도 하니, 역시나 “시험적으로 성과가 있는 것처럼 보고”되고 있을 뿐이다.

그리고 문제를 “학교 폭력”이라고 부르는 것부터가 별로 좋지 않은 프레임을 짜고 시작하는 일이다. ‘학교 폭력’이라고하면 기껏해야 왕따나 아니면 애들 사이에 주먹다짐 정도로 문제를 축소해서 비추는 효과를 갖고 있기 때문이다. 학교 폭력이라는 이 문제는 정확하게 말하면 “미성년자 강력범죄”의 개념을 명확하게 드러내는 단어로 명칭을 바꿔야 한다. 그리고 그 문제에 대한 해법 역시 단순한 ‘학교폭력’이 아닌 ‘강력범죄’로 인식했을 때 실마리를 찾을 수 있다.

몇 년전에 밀양 개새끼들, 다들 기억이나 하고 있는지 모르겠다. 그 때도 그 개새끼들에 대해 포스팅을 하나 내면서 “아무도 처벌받지 않을 거고, 밀양에서 딸키우기 힘들거다”라는 식의 말을 했었는데, 아주 슬프게도 그 말 그대로 됐다. 40명이 자매를 1년간 강간했는데, 5명이 반성문 좀 썼고, 나머지는 아무런 조치도 취해지지 않았다.

이러한 배경에는 다음과 같은 조건들이 작용한다.

1. 가해자 일부 학생의 부모라는 인간들이 밀양 지주쯤 되나보다. 아무튼 경찰서장이나, 형사과장.. 계장.. 아니면 그냥 형사들 한테 형님 소리는 좀 들을 것 같다.

2. 교사라는 인간들은 “나만 보신하면 그만”이라는 생각에 사로잡혀 있었을 것이다. 이건 뭐 “교직사회”라는 곳을 생각해보면 굉장히 당연한 일이라 생각된다.

3. 뭐 사고를 좀 쳤을 때 학생이면 가볍게 처벌하거나 거의 처벌하지 않는다. 나의 기억을 돌이켜 볼 때 이건 20년 전 내가 중학생일 때도 유효했다. 양아치 새끼들이 좀 움찔했던 순간은 같이 뽄드 빨던 녀석이 숨진채 발견된 날 아침뿐이었다. 돈 뺏다 경찰서 갔다온 다음날도 희희낙락하다가 다른 친구가 조사 받으러 가게 되면 “어차피 학생은 반성문만 쓰면 돼”라고 조언까지 날려주는 여유.

이 몇 가지 조건들이 결합하면서 법치 국가에서는 일어날 수 없는 일이 그냥 일상화되기 시작한다. “앞길이 구만리” 이야기가 나오면서 좋은 게 좋은 거고… 형사들은 집어넣어야 마땅한 개새끼들이 알고보니 아는 형님 아들이고…

결국 그누구도 처벌받지 않았고 피해 여학생은 전학을 갔지만, 가해 부모들의 괴롭힘 (합의해 달라 어쩌라)이 이어져 결국 행방불명되는 처지에 이르렀다.인생이 망가진 지경을 떠나 생사도 확인이 안되는 듯.

물론 아주 직접적인 연쇄 작용은 아니지만, 몇 년 후, 밀양의 저 개새끼들이 딱 그나이가 될 무렵에 고대에서도 사건이 하나 벌어지지. 뭐 처벌이 어느 수준까지 이뤄졌는지는 모르지만(워낙 고대에서는 걸출한 분들이 많이 나와서…), 그 때 여론이 상당히 험한 분위기로 일어났고, 아마 그런 분위기조차 이뤄지지 못했다면 제 2의 밀양 개새끼들이 내과 산부인과를 나의, 그리고 당신의 동네에 개업해서 무슨 변태 행각을 벌일지 모를 일일뻔했다.

(천만 다행으로 아마 그녀석들은 출교조치가 되어 의사 면허를 딸 수 없게 되었다. 그런데 이게 “여론에 떠밀려” 결정된 거라는게 문제다. 게다가 고대 출신 의사들은 이 건에 대해 조용하더라? 병원을 갔는데 의사가 고대출신이면… 나라면 좀 긴장하겠다.)

청소년 범죄에 대해 이런 멋진 전례를 남겨주신 덕분에 똥만 가득찬 여러 교사들의 머리에서 나오는 발상들 중 어느 하나도 유의미한 실효성을 가진 학교 폭력 방지 대책이 될 수 없다고 단언한다.이건 단순히 애들 싸움의 문제가 아니라 “폭력 사건”이 ‘학교’라는 프레임속에서 다뤄질 때의 사회 구조의 문제이다.

답은 단 하나 “학생 신분으로 벌인 범죄에 대해서는 성인과 동등한 수준의 처벌을 받게 하며, 특정 진로를 택하려할 때 불이익을 받도록 강제”하는 방안이 필요하다. 너무 심하다고? 40마리가 넘는 미친개가 당신 딸을 1년동안 물어뜯었을 때도 이런 처벌이 그때도 무거운지 다시 생각해보라. 이게 말이 안된다고 생각될지 모르지만, 이런 강력한 처벌은 다음과 같은 효과를 가져온다.

“학교 폭력” 혹은 “청소년 강력범죄” 예방이라는 게임의 플레이어를 “피해자 vs 가해자” 구도에서 “교사 + 학부모 + 학생 당사자” 모두로 확대한다. 처벌이 강력해지면 이 게임은 실전이 될 수 밖에 없다. 행동 잘 못 했다가는 인생 좆되는 거 한순간이 되는 게 벌어질 수도 있다는 것이다.

“학교 폭력”이란 게 애들이 장난치다 보면 격해져서 서로 주먹질도 할 수 있고 어쩌고… 만약 실제로 이렇게 강력하게 처벌한다면 애들은 알아서 조심할 뿐 더러. 주먹 한 번 잘 못 놀렸다간 고등학교도 못가고 대학교도 못가게 생기면 부모들이, 선생들이 애들한테 마르고 닳도록 교육시키지 않겠는가?

아마 유치원도 들어가기 전의 아이들에게 가르치는 1순위가 영어 따위가 아니라 “친구와 싸우지 마라. 다른 사람이 싫다는 행동은 하지마라.”는 기본적인 예의와 상식이 될 것이다. 즉, 단순히 애들한테 “싸우지 마라”는 교육만 하고 사회 시스템의 책임이 끝나는 지금과 같은 상황에서 “전체 교육과정에서 절대 넘어서는 안되는 선”을 확정해주고 이를 지킬 수 있도록 교사와 학부모가 신경을 안 쓸 수 없도록 만드는 효과가 있다. 단순히 처벌의 문제가 아니다.

이런 분위기가 만들어지면 자연스럽게 “피해자 중심의 후조치”가 가능해진다. 일단 무조건 빵에 가는 거다. 아예 합의 따위도 없도록 만들어야 피해학생, 피해학부모에게 2차적인 위해가 없을 게다. 아마 이렇게 딱 10년만 시행해본다면, 물론 부작용이 없을 수 없겠지만 학교 폭력에 대한 사회 인식은 바꿀 수 있다.

앞길이 구만리 같은… 개새끼들 앞길 생각해주다가 무슨 일이 일어났는지 아까 밀양 개새끼 사건에 덧붙여 재밌는 일이 있더라.

http://bbs3.agora.media.daum.net/gaia/do/petition/read?bbsId=P001&articleId=121797

밀양, 대단하다. 고담대구니 갱스오브부산이니 꺼지라그래라.

아마 5년 안에 “경찰의 보호를 받던 여성이 경찰에게 성폭행당해…”라는 기사가 밀양 발로 뜨는 날이 올 거 같은 확신이 든다. 그리고 저런 인원을 자체적으로 걸러내지 못하는 공무원 사회를 두고서 위에서 내가 언급한 “교직사회라면 당연하다”는 식의 막말에 대해서는 읽는 누가 기분 나빠도 별로 미안해하고 싶은 마음 따위는 없다.

 

[Cocoa] NSFetchedResultsController

코어데이터와 UITableView와의 연결

지금까지 몇 개의 예제를 통해 코어데이터를 사용해서 일련의 데이터를 디스크에 읽고 쓰며, 이를 관리하는 방법에 대해 살펴보았는데, 이런 작업을 보다 쉽게 만들어주는 컨트롤러가 있었으니, 바로 NSFetchedResultsController이다.이 클래스는 코어데이터의 컨텍스트를 기반으로 저장소로부터 조건에 맞는 객체를 읽어들여서, UITableView의 데이터소스 메소드에서 쉽게 사용할 수 있는 형태로 제공한다. 또한 읽어온 객체에 대해 추적 기능을 가지고 있어서 managed object에 어떤 변경이 발생할 때 이를 감지하여 적절하게 테이블 뷰에서의 변경을 만들어낼 수 있다.

단, NSFetchedResultsController는 저장소나 컨텍스트를 생성하지는 못한다. 코어데이터 프로그램에서 수동으로 배열을 만들어 불러온 데이터를 관리하는 부분에서의 코드의 양을 줄이는 용도로 사용할 수 있다는 것이 이 클래스의 의의랄까.

NSFetchedResultsController는 이름에서처럼 불러온 데이터들을 제어한다. 따라서 데이터를 불러오는 데 필요한 managed object context, fetch request, predicate, sort descriptor 등은 일반적인 코어데이터 프로그램에서와 같이 우리가 일일이 생성해 주어야 한다. 대신, 이 컨트롤러는 UITableView의 indexPath에 대한 접근 방식에 일치하는 메소드들을 가지고 있으므로, 데이터소스를 생성하는데 매우 편리하게 활용된다.

생성방법

컨트롤러의 생성을 위해서는 initWithFetchRequest:… 메소드를 사용하면 된다. 이 때 필요한 파라미터로는 다음의 것들이 있다.

  1. (NSFetchRequest*)fetchRequest
  2. (NSManagedObjectContext*)managedObjectContext
  3. (NSString *)sectionNameKeyPath
  4. (NSString*)cacheName

기본적으로 fetchRequest는 별도로 생성해야 한다. 섹션네임키패스는 섹션이 여러 개인 경우 각 섹션의 이름을 제공하는 객체를 지정한다. 보통 섹션을 하나만 사용하는 경우에는 nil을 넘기면 된다. 또한 캐시 이름은 캐시를 저장할 파일을 생성한다. nil을 사용하면 메모리내에서만 추적이 일어나는데, 이는 약간의 오버헤드를 유발할 수 있다. 대신 서로 다른 엔트리에 대해서 컨트롤러를 반복해서 재사용해야 하거나 할 때는 일일이 캐시를 삭제해 주어야 하므로, 상황에 맞게 사용하면 된다. 또한 캐시는 단순히 이름만 주면 된다.

다음은 컨트롤러를 생성하는 예제이다. 해당 클래스에는 컨텍스트, 패치 리퀘스트, 컨트롤러가 각각 프로퍼티로 설정되어 있다고 가정한다.

<# managedObjectContext를 구함 #>
...

-(NSFetchRequest*)fetchRequest
{
  if(!_fetchRequest) {
    _fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entityDescription = [NSEntityDescription EntityForName:@"Memo" inManagedObjectContext:self.managedObjectContext];
    [_fetchRequest setEntity:entityDescription];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isAvailable == YES"];
    [_fetchRequest setPredicate:predicate];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastModifiedDate" ascending:NO];
    [_fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
  }
  return _fetchRequest;
}

-(NSFetchedResultsController*)controller {
  if (!_controller) {
    _controller = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest
        managedObjectContext:self.managedObjectContext
        sectionNameKeyPath:nil
        cacheName:nil];
  }
  return _controller;
}

performFetch

컨트롤러에 performFetch 메시지를 보내면 코어데이터 저장소로부터 지정된 조건에 맞게 결과를 가져와 그 결과값을 컨트롤러 내부에서 관리하기 시작한다. 만약 델리게이트가 지정되어 있다면, 컨트롤러는 불러온 결과에 대한 변경을 추적하고 변경이 생기면 이를 델리게이트에게 알려준다.

테이블 뷰와의 결합

컨트롤러는 sections, sectionIndexTitles의 프로퍼티와 objectAtIndexPath:, sectionForSectionIndexTitle:, sectionIndexTitleForSectionName: 등의 메소드를 가지고 있고, 이를 사용하여 테이블 뷰에 데이터소스로 연결될 수 있다.

다음은 테이블뷰의 데이터소스를 구현한 예이다.

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
  return [self.controller.sections count];
}
-(NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView
{
  return self.controller.sectionIndexTitles;
}
-(UITableView*)sectionForSectionIndexTitle:(NSString*)title atIndex:(NSInteger)index
{
  return [self.controller sectionForSectionIndexTitle:title atIndex:index];
}
-(UITableView*)tableView titleForHeaderInSection:(NSInteger)section
{
  id<NSFetchedResultSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
  return [sectionInfo name];
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
  id<NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
  return [sectionInfo numberOfObjects];
}
-(UITableViewCell)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
  UITableViewCell *cell = [tableView dequeReusableCellWithIdentifier:@"cellIdentifier"];
  NSManagedObject *managedObject= [self.controller objectAtIndexPath:indexPath];
  <# cell에 표시될 내용을 memo로 부터 지정 #>
  cell.textLabel.text = [managedObject valueForKey:@"title"];
  ...
  return cell;
}

각각의 섹션은 NSFetchedResultsSectionInfo 프로토콜을 따르는데, 이 프로토콜에는 indexTitle, name, numberOfObjects, objects이 정의되어 있고, 이를 통해 섹션과 관련한 정보를 테이블 뷰에 제공할 수 있다.

데이터의 변경

데이터가 추가/삭제/변경될 때 컨트롤러의 델리게이트가 있다면 (그리고 그 델리게이트가 최소 1개의 델리게이트 메소드를 구현했다면) 컨트롤러는 로드된 데이터를 추적하게 된다. 그리고 변화가 발생할 때 델리게이트에게 적절한 메시지를 보내게 된다.

-controllerWillChangeContent:
 -controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:
 -controller: didChangeSection: atIndex: forChangeType:
 -controllerDidChangeContent:1</pre>
이 때 가장 많이 사용하는 것은 두 번째의 것으로 각 객체의 추가/삭제에 대한 내용으로 이는 다음과 같이 구현된다. 특히 이 메소드는 여러 개의 데이터가 한 번에 변경되는 경우에, 여러 차례 반복해서 호출되므로 이에 신경을 써야 한다.

1- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{        
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeUpdate:
                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeMove:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}

또한 섹션 자체의 변경에 대해서는 다음과 같이 구현할 수 있다. 거의 1대 1로 대응된다고 보면 된다.

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}

또한, 컨텐츠의 변경 시작과 끝을 알리는 두 메소드는 tableView의 beginUpdates, endUpdates를 각각 호출하여 변경 사항이 UI에 한 번에 적용되는 것으로 보이도록 할 수 있다.