-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 155 KB
/
content.json
1
{"posts":[{"title":"돌아보는 2018","text":"어쩌다보니 2018년의 처음이자 마지막 포스팅이 되었다. 정비도 어느정도 마쳤으니, 내년에는 좀 더 부지런하게 기록을 남겨야겠다. 올 한 해는 이런저런 공부를 해야지 하고 다짐했던 1월 1일이 아직도 생생한데 벌써 이렇게 1년이 훌쩍 가버렸다. 그동안 이룬 것도 있었지만, 이루지 못한 잔여물을 보면서 일부 반성한다. 이직이직, 이직 1년동안 노래를 불렀는데 드디어 하게 되었다. 아마 한 해 동안 가장 큰 변화가 아닌가 싶다. SI를 떠나서 UI 솔루션 회사로 이직했다. 이직하면서 아쉬운 부분도 있었지만(💰) 전에 몸담던 곳보다 큰 규모로 옮기게 되어 뿌듯했다. 6월에 투비소프트 프리세일즈팀으로 합류하여 내부 및 외부 POC(Proof of Concept)를 수행하면서 회사 제품에 대해 알아가는 단계이다. 세일즈라는 단어 때문에 많은 고민을 했지만 결정 후 이곳에서 내가 할 일이 생각보다 많을 것 같다는 생각이 요즘 부쩍 든다. 선배 개발자와 함께 POC를 수행한 적도 있지만 최근 팀 여건상 팀에서 혼자 + 다른팀 분과 함께 수행한 POC는 힘들었지만 기억에 남을 한 줄 이력이 될 것 같다. 2019년에도 많은 POC가 있을텐데 별탈없이 잘 수행할 수 있길 바라며… BMT(Benchmark Test)와 POC(Proof of Concept)라는 말은 입사 당시 생소한 말이었다. 쉽게 말해서 제품의 기능 및 성능 검증을 통해 제품 수주를 위해 수행하는 일종의 작은 파일럿 프로젝트이다. 짧으면 1주, 길면 한 두달씩 진행되는데, 고객이 요구하는 제품의 기능과 일정 수준 이상의 성능을 보여주는 샘플 페이지 개발과 시연 등이 주된 일이다. 여행 작년에 다녀온 오사카를 기점으로 해외여행에 맛들려서 올해만 3번 정도 해외로 여행을 다녀왔다. 대만 🇹🇼 : 4월에 다녀왔음에도 불구하고 매우 덥고 습한 날씨로 고생한 곳이다. 일행이 몸이 안좋아지는 바람에 더 많이 못 먹고 더 많이 못 둘러 본 것이 다소 아쉬웠지만 음식도 맛있는 편이었고, 소소한 즐거움이 있던 곳이다. 특히 예류지질공원은 대만에서 내가 꼽은 베스트 여행지다. 후쿠오카 🇯🇵 : 6월에 다녀왔는데, 유후인을 위주로 여행루트를 짰다. 버스에서 가방도 잃어버려서 애먹는 상황도 있었고, 버스 표 날짜를 잘못 티케팅 하는 바람에 스스로 많이 자책했던 여행(ㅋㅋㅋ). 유후인은 정말 힐링되는 평화롭고 아늑한 곳이었다. 도쿄 🇯🇵 : 아이폰 XS Max를 사는 것이 또 하나의 목표였던 여행. 추석 끼면서 꽤 오래 다녀왔는데 도쿄 전역을 다 돌아다니느라 생각보다 빡빡한 일정이 되었다. 하루 2만보 이상 걸을 정도이니…😵 개발개인 프로젝트를 많이 하려고 했지만, 이직 준비와 입사 초기 적응 기간동안 정신 없던 관계로(핑계) 많은 성과를 못 내서 아쉽다. 내년에는 새로운 것을 배우는 시간보다 그동안 익힌 것을 기반으로 많이 만들어보고 부딪히며 성장하는 한 해를 다짐해본다. Java 기초 다시보기 : 2년차때부터 지속적으로 느낀 이유로 하게 되었다. 버전업이 계속 되는 Java이기 때문에 앞으로도 꾸준히 해야하는 부분이라고 생각한다. Spring, Spring boot : Java를 기반으로 프레임워크 역시 학습하게 되었다. Spring Boot는 접할 당시 신세계였고, 현재 @ruden91 과 함께 미니 프로젝트를 진행중이다. 책으로 배운 내용 외에도 많은 부분을 다뤄볼 예정이다. iOS 앱 개발 : 초기학습은 끝냈는데, 실제 앱 개발 및 스토어 배포를 아직 시행하지 못한 아쉬움이 있다. 2019년에는 반드시 해내리라… React 학습 : Velopert님의 리액트 강의를 보며 학습하고 있다. 더불어 리액트 교과서도 함께 보고 있는데, 조금씩 어려움을 느끼고 있지만 꾸준한 학습으로 극복해내고 있는 중이다. 하던 학습 마저 끝내고 실전으로 들어가서 React + Spring boot 로 어플리케이션을 개발하고 싶다. 2019앞서 언급했듯이 2019년에는 새로운 걸 학습하는 비중을 대폭 줄이고, 공부하고 다져낸 것을 기반으로 실제 개발해보는 것이 주된 목표이다. 학습만 하고 끝낸다면 학습의 의미가 점차 사라지고 시간이 지나면 까먹게 되기 때문에 이와 같은 다짐을 했다. NP17 : 1순위. 개인적인 공부도 중요하지만 회사 업무가 최우선이라고 생각한다. Java 기초 학습 : 기존 내용과 새로운 버전에 나오는 내용을 숙지하고 내가 개발자를 계속 하면서 Java가 사라지지 않는 한 계속될 내용일 듯 싶다. 알고리즘 & 자료 구조 : 요즘 가장 부족하다고 느끼는 부분이다. 컴퓨터공학 전공을 하지 않은 것도 있지만, 언어 위주로만 학습하고 개발해왔기 때문에 내가 가진 능력치 중 가장 떨어진다. 종만북이라고 불리는 서적을 사서 차근차근 익혀보고 온라인 코딩 테스트(Codewars, 백준 등등..)에 적용할 계획이다. iOS 앱 개발 : 올해 못 다 이룬 업적을 꼭 이뤄내겠다. React + Spring Boot : 1인 Frontend/Backend 개발하여 작은 서비스 하나 출시해보고 싶다. AWS : 최근들어 매력을 느낀 AWS. 개인 프로젝트는 AWS 환경에서 대부분 처리하는 방향으로 진행하며 AWS에 대해 좀 더 알아가는 한 해가 될 수 있길 바란다. 블로그 : 2018년에 글 한 개 없이 방치된 나의 블로그. 2019년에 다시 활성화 해보겠다. 주변 챙기기 : 매년 마음속으로 하는 다짐이지만 매번 부족하다고 생각되는 부분이다. 바쁜 와중에도 1분이라도 투자하여 주변사람들 잘 챙기자. 견문 : 여행도 많이 다니고, 세미나와 컨퍼런스도 올해보다 더 많이 참석해서 세상을 보는 눈이 넓어지는 한 해가 되길 바란다. 마무리하며…쓰다보니 주저리주저리가 된 느낌이다. 내년 이맘때쯤 이 글을 보면서 한 해를 알차게 보냈는지 체크해 볼 것이다. 앞서 많은 다짐을 기록했지만 무엇보다 중요한 건 건강. 혹 이 글을 보게 되는 여러분도 건강한 한 해가 되길 바라며.. 세상의 모든 개발자분들 화이팅!","link":"/2018/12/31/2018-memoir/"},{"title":"Atom 패키지 multi-cursor","text":"multi-cursor에디터를 사용하다 보면 같은 선상에 있는 몇 개의 라인에 같은 수정사항을 적용하고 싶을 때가 있다.이 때 사용하는 것이 multi-cursor패키지이다. 설치 및 실행패키지 목록에서 검색 후 설치를 하면 따로 실행할 필요는 없다.대신 따로 입맛에 맞게 단축키를 설정할 수는 있지만, 기본은 option + 위아래 화살표 이다.입력 후에 esc를 통해 해제해주자. 단축키 목록","link":"/2017/08/01/atom-multi-cursor/"},{"title":"두 번째 이직","text":"또 한 번의 이직두 번째 직장을 떠나 세 번째 직장으로 몸을 옮겼다. 5월부터 슬슬 면접을 보기 위해 이력서 최신화도 시켜 놓고 취업포털을 통해 지원서를 제출했다. 지난 번 이직이 워낙 오래걸렸던 기억이 남아있었기 때문에, 이번에도 역시 오래 걸릴 것이라 생각하고 길게 보았다. 1년까진 아니더라도 가장 잘 팔린다는 만 3년이기 때문에 어느 정도 자신이 있었다. 그 때 당시 이직 사유는 다음과 같다. 기술영업스러운 팀의 업무 이것이 나에게 좋은 자산이 될 수 있을 것이라 생각했지만, 갈수록 기술적인 부분보다 영업적인 부분의 업무가 많아지면서 이직을 결심하게 되었다. 가장 큰 이유이다. 개발에 좀 더 집중하고 싶다. 💰 연차에 비해 낮다고 생각되는 급여 역시 나의 이직 욕구를 불태웠다. 폭발적인 상승은 못하더라도 비교적 만족스러울만한 수준의 급여가 필요했다. 한정된 개발환경 UI/UX 제품 벤더사이기 때문에 아무래도 자사 제품을 활용한 개발에만 집중할 수 밖에 없었다. 자기계발이라는 소중한 시간을 잘 활용하긴 했지만, 커리어적인 면을 보았을 때, 나에게 큰 도움이 될 수 있을까 하는 의문이 들었다. 원래는 JAVA 위주로 공부하고 개발하는 것을 원했기 때문. 특히 경력자의 면접에서 항상 첫 번째 질문이 왜 이직하려고 하는가?이다. 자신이 왜 이직하려는지에 대한 고찰이 면접에서 잘 드러나야 면접관들의 마음을 사로잡기 좋다고 생각한다. 물론 돈, 잦은 야근 등 여러 가지 현실적인 이유들이 있겠지만, 커리어적으로 내가 어떤 미래를 그리고 있고 그렇기 때문에 귀사에 지원하게 되었다고 어필하는 것이 베스트가 아닐까 싶다. 서류이번 이직은 대기업 계열사 위주로 타겟을 잡았다. 사실 회사를 많이 넣지는 못했다. 채용공고들은 언제나 물들어올 때 훅 들어오듯이 뜨기 때문이다. 그래도 뜨는대로 당장 가도 될 정도의 기업에 지원하기 시작했다. 첫 이직 때보다 훨씬 빠르게 서류 통과와 함께 면접 제의를 받았고, 두어 곳 정도에서 면접을 봤었다. 실무진 면접, 임원면접사실 전 회사에서 가장 큰 수확은 말하는 스킬이었다. 말을 잘한다 라기 보다는 말을 조리있게 잘 한 것 같다. 고객사에 가서 제품소개와 기술대응을 여러 차례 하다보니, 자연스레 상대방을 이해시키고 설득시키는 대화에 익숙했다. 한 가지 아쉬운 점은 뻔뻔하게 내가 가진 것에 대한 뻥튀기(허세가 아닌 약간의 포장) 하는 스킬은 여전히 부족했다. 학부 시절 모 교수님께서 나한테 항상 아쉬워하는 부분이다. 한 마디로 거짓말을 잘 못할 뿐더러 거짓말을 하면 바로 티가 나는 스타일이다. 그래도 이번 회사에서 솔직하게 내가 가진 그대로 보여주고 말한 것을 좋게 봐주어서 면접에 생각보다 어렵지 않게 통과되었다. 하지만 이전 경력(프리세일즈)이 한편으로는 나에게 마이너스 요소가 되었나보다. 면접에 대한 피드백을 추후에 메일로 받았었는데(이런 회사가 흔치 않다.) 내 장단점을 확실하게 파악한 면접관이었다.(현 팀장님..) 임원 면접은 이상하게 입이 잘 풀린다. 다소 까다로운 질문도 받아서 적잖이 당황했지만, 그래도 잘 넘긴 것 같았다. 임원 면접은 보통 기술적인 면보다는 인성적인 부분(사실 인성을 얼마나 잘 알 수 있을지는 의문이지만)을 체크하면서 본다. 실무진면접보다는 확실히 편한 느낌을 많이 받았고, 동네 아저씨들과 수다 떨러 간다는 자세로 해서 그런지 결과적으로는 성공적인 면접이었다. 현재현재 내가 소속되어 있는 팀은 일단은 괜찮아보인다. 3개월이 조금 넘은 시점에서 다닒만하다. 웹과 모바일 등 다양한 개발환경이 있고, 현재 자바와 약간의 자바스크립트 위주로 개발 업무를 맡아 진행하고 있다. 대기업 계열사답게 지금까지 다녔던 회사들보다 복지, 급여 등 만족하지 못할 수준은 아니다. 살짝 부족한 느낌..? 사람 욕심은 끝이 없다. 😆 그래도 이곳에 몸담은 이상 다시 한 번 열심히 달려보기로 한다.","link":"/2019/10/28/2nd-job-flipping/"},{"title":"Atom 패키지 pigments","text":"pigments공식 Repo : https://github.com/abe33/atom-pigments 우리가 css에서 작업을 하거나 <style>태그 안에서 스타일 작업을 할 경우에 색을 많이 사용한다.하지만 그게 무슨 색인지 컬러코드만 보고는 알 수가 없다. 그래서 사용하는 패키지 pigments이다. pigments 패키지는 우리에게 색상 코드에 관한 미리보기를 제공한다. 설치패키지 검색을 통해 설치한다. 실행따로 실행하는 것은 없고 색상 코드를 입력하면 자동으로 해당하는 색으로 변하게 된다.","link":"/2017/07/31/atom-pigments/"},{"title":"라즈베리파이 nginx 로그의 주기를 설정하기","text":"서버에 설치된 데몬마다 로그를 생성하는데, 각 데몬별로 로그 주기를 정할 수 있는 곳이/etc/logrotate.d이다. 12$ cd /etc/logrotate.d$ vi nginx 이중 nginx를 vi 에디터로 열어서 다음과 같이 수정하자. 1234567891011121314151617181920212223242526272829#/var/log/nginx/*.log {# weekly# missingok# rotate 52# compress# delaycompress# notifempty# create 0640 www-data adm# sharedscripts# prerotate# if [ -d /etc/logrotate.d/httpd-prerotate ]; then \\# run-parts /etc/logrotate.d/httpd-prerotate; \\# fi \\# endscript# postrotate# invoke-rc.d nginx rotate >/dev/null 2>&1# endscript#}/var/log/nginx/*.log { daily rotate 365 copytruncate delaycompress compress dateext notifempty missingok} 테스트 12$ logrotate —force /etc/logrotate.d/nginx$ cd /var/log/nginx nginx 웹서버를 실행 후 생성되는 로그를 확인해보자.로그 경로는 nginx 설정에 있다.","link":"/2017/07/21/create-nginx-log-by-dates-in-raspberrypi/"},{"title":"Atom 패키지 Remote-FTP","text":"Remote-FTP공식 Repo : https://github.com/icetee/remote-ftp 텍스트 에디터를 사용하면서 가장 인기 많은 패키지가 아닐까 싶다. 이름 그대로 원격 서버, FTP에 연결하여 파일(아마 대부분 소스코드일 것이다.)을 쉽게 주고 받을 수 있게 하는 필수패키지이다. FTP/SFTP 모두 지원한다. 설치명령어를 이용하는 방법도 있지만, 앞으로 포스팅될 패키지 관련 포스팅에서는 atom 에디터 내부에서 검색을 통해 설치할 것이다.remote-ftp를 검색하여 설치해보자 설정파일설정파일에는 두 가지가 있다. (ftp, sftp) Packages - Remote-FTP - Create FTP config file 또는 Create SFTP config file 을 선택하여프로젝트에 설정파일을 생성한다. .ftpconfig라는 파일이 생성되고 다음과 같이 설정한다. 123456789101112131415161718192021222324252627282930313233343536373839# SFTP{ "protocol": "sftp", "host": "example.com", # 접속하려는 호스트 "port": 22, # 접속하려는 포트 "user": "user", # user name "pass": "pass", # user의 password "promptForPass": false, "remote": "/", # 접속해서 디폴트로 이동할 디렉토리 (홈디렉토리 기준) "agent": "", "privatekey": "", # ssh 터널링을 하는 경우 private key "passphrase": "", # ssh 터널링을 하는 경우 key의 passphrase "hosthash": "", "ignorehost": true, "connTimeout": 10000, "keepalive": 10000, "watch":[], "watchTimeout":500, "filePermissions":"0644"}# FTP{ "protocol": "ftp", "host": "example.com", # 접속하려는 호스트 "port": 21, # 접속하려는 포트 "user": "user", # user의 name "pass": "pass", # user의 password "promptForPass": false, "remote": "/", # 접속해서 디폴트로 이동할 디렉토리 (홈디렉토리 기준)) "secure": false, "secureOptions": null, "connTimeout": 10000, "pasvTimeout": 10000, "keepalive": 10000, "watch":[], "watchTimeout":500} # (주석)으로 설명한 부분만 잘 작성해주면 접속에 성공한다.","link":"/2017/07/28/atom-remote-ftp/"},{"title":"[도서리뷰] IT 트렌드 스페셜 리포트 2019","text":"4차산업혁명, 이시대에 가장 핫한 키워드임은 분명하다. 매년 가트너에서 새해에 주목해야 할 10가지 기술 및 트렌드에 대해서 발표하는데, 이 책도 약간 그러한 느낌이다. 추가적으로 챕터마다 관련된 기업과 그 기업의 솔루션 및 제품을 함께 소개한다. IT쪽에 종사하지 않는 사람은 이 책에 나오는 내용이 어려울 수도 있지만, 그래도 쉽게 풀어쓰려 노력한 흔적이 보인다. 갖가지 도표와 이미지, 예시와 함께 어려운 설명을 보충해나가며 범용적으로 읽히기 쉽도록 했다. 구성된 큼지막한 챕터는 이미 우리 생활 주변에서 한 번쯤은 들어본 말도 있을 것이다. 그만큼 4차산업혁명이라는 큰 파도의 시작 속에 우리가 살아가고 있다고 생각한다. 그 파도 속을 잘 헤쳐나가서 멋진 서퍼가 되기 위해 이 책 한 번 쯤은 읽어볼만하다. 이 리뷰는 ‘한빛미디어’로부터 도서를 제공받아 작성되었습니다.","link":"/2019/01/10/book-review-it-trend-special-report-2019/"},{"title":"Java StringJoiner 사용하기","text":"Java에서 문자열 사이에 특정한 기호로 연결시키고 싶을 경우가 간혹 생긴다. 보통이라면 for루프를 통해서 간단하게 만들 수 있을 것이다. {"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}와 같은 배열이 있다고 생각하자 이들을 -로 연결하겠다. 123456789101112131415161718public class StringJoinerExample { public void ex1() { final String DELIMITER = "-"; StringBuilder result = new StringBuilder(); String[] strArr = {"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}; for (int i = 0; i < strArr.length; i++) { result.append(strArr[i]); if (i < strArr.length - 1) { // 마지막 이전까지 delimiter 를 append. result.append(DELIMITER); } } System.out.println(result.toString()); // 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이 }} 문제 없이 실행되는 소스코드이지만 뭔가 더 간결하게 하고 싶은 욕심이 생긴다. StringJoiner를 사용하면 이를 좀 더 깔끔하게 작성이 가능하다. 12345678910111213141516import java.util.StringJoiner;public class StringJoinerExample { public void ex2() { StringJoiner joiner = new StringJoiner("-"); String[] strArr = {"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}; for (int i = 0; i < strArr.length; i++) { joiner.add(strArr[i]); } System.out.println(joiner.toString()); // 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이 }} StringJoiner는 문자열 생성자에서 선언한 구분자를 add시켜주면서 StringBuilder와 유사한 효과를 낸다. 차이점이 있다면 prefix(접두사)와 suffix(접미사)까지 선언이 가능하다는 점이다. 123456789101112131415161718import java.util.StringJoiner;public class StringJoinerExample { public void ex3() { StringJoiner joiner = new StringJoiner("-", "[ ", " ]"); String[] strArr = {"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}; for (int i = 0; i < strArr.length; i++) { joiner.add(strArr[i]); } System.out.println("AVENGERS"); System.out.println(joiner.toString()); // AVENGERS // [ 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이 ] }} StringJoiner는 이번에 암호화폐 거래소 open api를 사용하면서 알게된 클래스이다. ETH-BTC, SNT-BTC처럼 적게는 몇 개, 많게는 백 개가 넘는 마켓코드를 ,를 통해 쉽게 이어 붙이는 방법을 찾다가 발견하게 된 것이다. 앞으로도 유용하게 쓰일 것 같다. 마지막으로 StringJoiner를 활용하여 배열을 json형태로 만드는 예제를 살펴보자. 12345678910111213141516171819202122232425262728293031323334353637import java.util.StringJoiner;public class StringJoinerExample { public void ex4() { StringJoiner jsonJoiner = new StringJoiner(",\\n\\t", "{\\n\\t", "\\n}"); String[] strArr = {"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}; for (int i = 0; i < strArr.length; i++) { StringJoiner keyJoiner = new StringJoiner("", "\\"", "\\""); StringJoiner valueJoiner = new StringJoiner("", "\\"", "\\""); StringJoiner keyValueJoiner = new StringJoiner(" : "); keyJoiner.add("hero" + i); valueJoiner.add(strArr[i]); keyValueJoiner.add(keyJoiner.toString()); keyValueJoiner.add(valueJoiner.toString()); jsonJoiner.add(keyValueJoiner.toString()); } System.out.println("AVENGERS JSON RESULT"); System.out.println(jsonJoiner.toString()); /* AVENGERS JSON RESULT { "hero0" : "아이언맨", "hero1" : "캡틴아메리카", "hero2" : "블랙위도우", "hero3" : "헐크", "hero4" : "토르", "hero5" : "호크아이" } */ }} StringBuilder, 또는 기타 문자열을 생성하는 클래스와 잘 조합해서 쓴다면 다양한 방면으로 문자열을 만들어 볼 수 있을 것 같다.","link":"/2019/01/07/java-stringjoiner/"},{"title":"라라벨(Laravel) 컴포저(Composer) 명령어","text":"컴포저 명령어 리스트입니다. 12345678910111213141516# 라라벨 프로젝트 생성composer create-project laravel/laravel folder_name# composer.json 내용을 토대로 빌드(패키지 설치)composer install# 패키지 업데이트composer update# autoload 파일을 리프레시composer dump-autoload [--optimize]composer self-update# 패키지 추가 및 설치composer require [options] [--] [vendor/packages]...","link":"/2017/06/12/laravel-composer-command/"},{"title":"jsp db connection 설정 (datasource)","text":"라이브러리1234commons-collections4-4.1.jarcommons-dbcp2-2.1.1.jarcommons-pool2.2.4.2.jarmysql-connector-java-5.1.44-bin.jar 4개 파일 다운 후 WebContent/WEB-INF/lib에 복사 mysql connector는 프로젝트 web.xml 생성프로젝트 우클릭 - Java EE Tools - Generate Deployment Descriptor Stub -> WebContent/WEB-INF/web.xml 생성 확인 아래 내용 추가 123456<!-- web.xml --><resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/mysql</res-ref-name> <res-auth>Container</res-auth></resource-ref> 톰캣 서버 context.xml 설정123456789101112<!-- context.xml --><Resource name="jdbc/mysql" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="username" password="userpass" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://hostname:port/dbname"/><!—- 빨간글씨는 바꿔야 하는 부분 —-> dao 생성자12345678910111213141516171819202122232425262728293031323334import java.sql.SQLException;import java.sql.Timestamp;import java.util.ArrayList;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;import javax.sql.DataSource;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;public class BDao { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; public BDao(){ try{ Context init = new InitialContext(); Context env = (Context)init.lookup("java:comp/env"); DataSource ds = (DataSource)env.lookup("jdbc/mysql"); connection = ds.getConnection(); System.out.println("db connection success!!"); } catch (NamingException e) { e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}// 각 method 에서 connection, ps, rs 를 close() 해주는 것을 잊지 말자 주의사항mysql connector는 소스쪽과 서버쪽 폴더 동시에 적용시켜야한다.(복사) 위와 같이 했음에도 아래와 같이 에러가 나는 경우 심각: Servlet.service() for servlet [com.javalec.ex.frontcontroller.BFrontContrller] in context with path [/first_test] threw exception [Servlet execution threw an exception] with root cause 위에설치한 4개의jar파일중 버전이 맞지 않아서 생기는 에러(ex. mysql-connector-java-5.1.44-bin.jar이 낮아서 생기는 에러였음) 최신버전으로 다운로드 후 다시 WebContent/WEB-INF/lib에 복사 아파치 톰캣 설치한 경로에도 mysql-connector jar파일을 복사해주어야 한다.","link":"/2017/10/10/java-jdbc-db-connection-datasource/"},{"title":"라라벨 인스톨러 설치 (command not found)","text":"라라벨 인스톨러 설치composer를 사용하여 라라벨 프로젝트를 설치하는 방법도 있지만 laravel new blog 와 같은 명령어를 사용하여 프로젝트를 생성하는 방법도 있다. 123$ composer global require "laravel/installer"$ cd ~ command not found일 경우위와 같이 컴포저로 라라벨 인스톨러를 전역설치 한 후에 다시 홈 디렉토리로 온다. .bashrc와 같은 파일을 열고 1234# ~/.bashrcexport PATH="$PATH:$HOME/.composer/vendor/bin"# 이것을 추가한다. 또는 명령어를 활용하여 1echo 'export PATH="$PATH:$HOME/.composer/vendor/bin"' >> ~/.bashrc 그리고 추가된 한 줄을 반영해야 한다. 1source ~/.bashrc source명령어를 이용하여 반영해도 되고, 터미널을 종료 후 다시 실행해도 적용된다. ~/.bashrc가 없다면 ~/.bash_profile등에 추가하여도 무관하다.","link":"/2017/07/28/laravel-installer/"},{"title":"MVC(Model View Controller) ?","text":"Prologue학부 시절부터 MVC, MVC 수도 없이 들어왔지만, 똑 부러지게 이해하기 쉽게 설명해주는 사람은 많지 않았다. 그저 “모델 뷰 컨트롤러… 그거야..” 라고 답변이 있을 뿐이어서 프로그래밍 공부를 하는 필자도 와닿지 않는 개념이었다. MVC가 무엇인지, 내 경험과 인터넷 끄적끄적 구글링을 통해 얻은 결론을 한 번 정리해보고자 한다. CodeIgniter의 MVC구조 php 프레임워크 CodeIgniter 를 예시로 들어보겠다. 위 그림은 코드이그나이터의 MVC 구조이다. 모델, 뷰, 컨트롤러 외에도 여러가지가 있지만 일단 세 가지의 구조라도 잘 이해하고 넘어가자. 그것만 알아도 나머지는 부수적인 것에 지나지 않으니까 말이다. 어디까지나 내가 이해한 방식이니 혹여 다른 생각을 가지고 있는 다른 누리꾼이 코멘트를 남겨주신다면, 이 또한 재밌는 토론이 될 것 같다. :) 모델(Model) : 뒤에서 묵묵히 데이터를 처리한다.모델은 실제로 우리가 눈으로 보기 힘든 요소이다. 아니, 안보인다고 해도 무방하겠다. 주로 데이터베이스에 접근하여 데이터를 읽고(R), 쓰고(C), 수정하고(U), 삭제하는(D) 역할이다. 다른 로직을 쓰는 것은 모델에게 사치다. 사용자가 브라우저를 통해 데이터와 관련된 어떤 동작(주로 클릭)을 하면 컨트롤러를 통해 모델이 호출되고, 모델은 그저 데이터를 처리하고 컨트롤러로 전달해주는 역할을 수행한다. 사용자는 모델이 실제로 무엇을 하는지 정확히 알 수 없다. 이러한 MVC구조를 모른다면 ‘눌렀으니까 그냥 나오는거군!’ 하고 느낄 수 있지만, 모델 입장에서 섭섭하겠지만 어쩌나… 보이지 않는 것을… 하지만 제 역할을 해야만 어플리케이션이 정상적으로 작동할 것이다. 뷰(View) : 가상의 결과를 현실화하다. 사용자의 눈을 즐겁게 하라.주로 백엔드 개발을 하는 나에게 뷰를 만드는 프론트엔드 개발자는 해리 포터 못지 않은 마법사다. 화면을 구성하는 언어로 실제로 구현해야 할 것을 뾰로롱~ 하고 만들어내는 듯한 뷰 역시 MVC의 핵심 멤버이다. 모델과 컨트롤러가 제 역할을 수행하고 응답을 뷰에 넘겨주었다면, 뷰는 예쁘게 그리는 것이 관건이다. 사용자는 대부분 어플리케이션(웹, 모바일)에서의 만족도를 뷰에서 느끼기 때문에, 사용자 경험(UX)에 관한 부분을 특히 신경써야 한다. 학부 시절 김교수님은 뷰를 굉장히 중요하게 여기셨다. 눈이 즐거운 어플리케이션은 기능이 조금 부족할 지라도, 사용자로 하여금 만족감을 느끼게 만들 수 있다는 것이다. 과거에는 기능만 잘 돌아가면 된다! 라는 것이 주였지만, 지금은 보여지는 것 또한 기능과 거의 동등하게 여겨질 정도로 뷰도 잘 만들어져야 한다. 컨트롤러(Controller) : 어플리케이션의 핵심, 요청과 응답의 중심지축구로 치면 중원 미드필더라고 보면 되겠다. 치열한 공싸움이 일어나는 필드처럼 웹서버에서 수많은 요청과 응답을 주고 받는다. 모델과 뷰의 중간다리 역할을 하며 어플리케이션이 잘 작동되도록 요청과 응답을 받고 던지고… 제일 바쁜놈이라고 볼 수 있다. 코드이그나이터에서는 헬퍼, 라이브러리 등의 부가적인 요소들이 있어서 컨트롤러는 더욱 바빠진다. 부가 요소를 호출하고, 모델에게는 ‘데이터 처리 좀 부탁해!’ 라고 요청하며, 모델이 제 역할을 했다면 그 결과를 뷰에게 ‘자 여기 응답 결과야!’ 라며 전달한다. 가장 바쁜 시간에는 연예인 못지 않은 스케쥴이다. 결국 중요한 것은MVC에서 가장 중요한 요소? 그건 말할 수 없다. 세 가지 요소가 적절히 조화를 이루어야 좋은 어플리케이션이 될 수 있다. 어떻게 보면 개발자들에게 MVC는 부담되겠지만, 잘만 만든다면 제일 인정받는 사람이 될 수도 있다. MVC를 적용한 프레임워크는 현재 개발자들이 주로 사용한다. 우리나라에서는 특히 자바 기반의 스프링(Spring)이 가장 인기 있는 프레임워크일 정도이니…(사실 개발자들이 좋아하는진 모르겠다..)필자의 글을 읽고 조금이나마 MVC에 대한 개념이 아리송 하거나 몰랐던 분들에게 도움이 되었으면 좋겠다. :)","link":"/2017/05/18/mvc-in-codeigniter/"},{"title":"자바스크립트 차트 라이브러리 모음","text":"개인적으로는 하이차트와 amcharts등이 가장 괜찮아 보인다. nvd3http://nvd3.org/ c3jshttp://c3js.org/ 구글 차트https://developers.google.com/chart/ 하이차트http://www.highcharts.com/ chartjshttp://www.chartjs.org/ amchartshttps://www.amcharts.com d3https://github.com/d3/d3/wiki/Gallery morrisjshttp://morrisjs.github.io/morris.js/","link":"/2017/08/20/javascript-charts-library/"},{"title":"php 이번 달 날짜 리스트 구하기","text":"해당 월의 모든 날짜를 구하는 것이다. 달력 등에 관련된 것을 사용할 때 유용하다. 123<?phpdate('t', [옵션 : date 형식의 날짜]); 예제 12345678<?php$dates = array();$todayDate = '2017-07-21';for($i = 0; $i <= date('t', strtotime($todayDate)); $i++) { array_push($dates, str_pad($i, 2, '0', STR_PAD_LEFT));}","link":"/2017/07/21/php-date-list-of-this-month/"},{"title":"mysql 커스텀 정렬","text":"mysql에서 특정 값들을 내 마음대로 정렬하고 싶을 때 다음과 같이 사용한다. 123SELECT *FROM 테이블명ORDER BY FIELD(필드명, '값1', '값2', '값3');","link":"/2017/06/28/mysql-custom-order/"},{"title":"라즈베리파이 자동 마운트","text":"라즈베리파이에서 외장하드를 연결하여 사용 중인 사용자가 있다면장치를 자동으로 마운트시키고 싶을 것이다.이번 글에서는 외부 장치를 auto mount 시키는 방법을 알아보겠다. 장치 확인우선 장치 확인을 해 본다. 123456$ sudo blkid/dev/mmcblk0p1: LABEL="boot" UUID="95E0-9AC4" TYPE="vfat" PARTUUID="f8e97239-01"/dev/mmcblk0p2: UUID="b105f9a8-f450-4976-8ac8-69053f57bab4" TYPE="ext4" PARTUUID="f8e97239-02"/dev/mmcblk0: PTUUID="f8e97239" PTTYPE="dos"/dev/sda1: LABEL="My Passport" UUID="904A19514A193586" TYPE="ntfs" PARTLABEL="My Passport" PARTUUID="d714a0e3-6980-46a0-ae12-bcf81c70f056" /dev/sda1 에 연결된 외장하드를 연결해 놓았다.마운트된 장치를 unmount시킨 후에 다음과 같은 명령어로 특정 폴더에 마운트 되도록 한다. 123$ cd /$ sudo mkdir external$ sudo mount -t ntfs /dev/sda1 /external auto mount 설정/etc/fstab을 수정한다. 1/dev/sda1 /external ntfs default 0 0 위 내용을 추가하고 재부팅하여 cd /external을 통해 장치가 올바르게 마운트 되었는지 확인한다.","link":"/2017/05/17/raspberrypi-auto-mount/"},{"title":"라즈베리파이 고정 ip 설정","text":"라즈베리파이를 사용하면서 랜선에 직접 연결하면 좋겠지만그렇지 못하고 공유기의 와이파이를 이용하는 경우, 자동으로 할당되는 아이피를 고정시키고 싶을 것이다. 왜냐하면 우리는 공유기의 포트포워딩(Port Forwarding)을 사용할 것이기 때문이다. 라즈베리파이 아이피 고정iptime 공유기를 사용한다고 가정한다.라즈베리파이를 재부팅하였을 때, ip가 변동되지 않도록 12345678910111213$ sudo vi /etc/network/interfaces# 해당 부분을 찾아서 다음과 같이 수정한다.allow-hotplug wlan0iface wlan0 inet static wpa-conf /etc/wpa_supplicant/wpa_supplicant.confwpa-ssid wi-fi 이름wpa-psk wi-fi 비밀번호address 192.168.0.18network 192.168.0.0netmask 255.255.255.0gateway 192.168.0.1broadcast 192.168.0.255 재부팅 후에 ifconfig 명령어를 통해 확인한다.","link":"/2017/05/16/raspberrypi-static-ip/"},{"title":"라즈베리파이 nginx, php-fpm, mysql(mariadb) 설치","text":"php 개발환경을 세팅해보겠다. nginx 설치12$ sudo apt-get install nginx$ sudo service nginx restart 생각보다 설치와 실행이 너무 쉬워서 깜짝 놀랬다.http://localhost로 접속하여 확인하자. php7 설치설치 준비php7에 대한 정보가 없기 때문에 이를 추가해 주는 과정이다. 1234567891011121314151617181920$ sudo vi /etc/apt/sources.list# 아래 줄을 추가한다.deb http://repozytorium.mati75.eu/raspbian jessie-backports main contrib non-free# 키서버를 등록해준다.$ sudo gpg --keyserver pgpkeys.mit.edu --recv key CCD91D6111A06851gpg: directory '/root/.gnupg' createdgpg: new configuration file '/root/.gnupg/gpg.conf' createdgpg: WARNING: options in '/root/.gnupg/gpg.conf' are not yet active during this rungpg: keyring '/root/.gnupg/secring.gpg' createdgpg: keyring '/root/.gnupg/pubring.gpg' createdgpg: "key" not a key ID: skippinggpg: requesting key 11A06851 from hkp server pgpkeys.mit.edusudo gpg --armgpg: /root/.gnupg/trustdb.gpg: trustdb createdgpg: key 11A06851: public key "Mateusz Łukasik <[email protected]>" importedgpg: Total number processed: 1gpg: imported: 1 (RSA: 1)$ sudo gpg --armor --export CCD91D6111A06851 | sudo apt-key add - 업데이트한다. 1$ sudo apt update -y php7 설치 (php-fpm)1$ sudo apt install php7.0 php7.0-cli php7.0-common php7.0-fpm php7.0-gd php7.0-json php7.0-mcrypt php7.0-mysql php7.0-readline -y nginx의 소켓 업데이트123456789101112131415161718$ sudo apt-get install php7.0-fpm$ sudo vi /etc/nginx/sites-available/default#location ~ \\.php$ {# include snippets/fastcgi-php.conf;## # With php5-cgi alone:# fastcgi_pass 127.0.0.1:9000;# # With php5-fpm:# fastcgi_pass unix:/var/run/php5-fpm.sock;#}location ~ \\.php$ { fastcgi_pass unix:/run/php/php7.0-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params;}# index index.html index.php index.htm 역시 추가하도록 하자 nginx 설정이 수정되었으므로 서비스 재시작을 한다. 1$ sudo service nginx restart php 버전을 확인해본다.버전 정보가 출력된다면 설치가 잘 된 것이다. 12345$ php -vPHP 7.0.19-1~bpo8+1 (cli) (built: May 12 2017 20:05:12) ( NTS )Copyright (c) 1997-2017 The PHP GroupZend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies with Zend OPcache v7.0.19-1~bpo8+1, Copyright (c) 1999-2017, by Zend Technologies mysql 설치 (mariadb)역시 패키지부터 설치해주자. 1$ sudo apt-get install mariadb-server root 사용자 비밀번호를 설정하는 창이 나오면 입력하고 한 번 더 입력해준다. 그리고 3306 포트에 대한 방화벽을 열어준다. 123$ sudo iptables -A INPUT -p tcp --dport 3306 -j ACCEPT$ sudo iptables -A OUTPUT -p tcp --dport 3306 -j ACCEPT$ sudo iptables-save 따로 재시작을 해 줄 필요도, 할 수도 없지만 iptables-save 명령어로 방화벽 정책이 저장된 것이다. root 로 접속 확인 후‘사용자명’@‘%’ 를 mysql.user 테이블에 추가하여 테이블 권한을 적절하게 주도록 한다.","link":"/2017/05/17/raspberrypi-nginx-php7-mariadb/"},{"title":"라즈베리파이 ssh 활성화","text":"원격 또는 모니터 없이 라즈베리파이를 제어하고 싶다면, ssh를 활성화해야 한다. 1$ sudo raspi-config 위 커맨드를 실행한 후에Advanced Options 메뉴에 들어가서 ssh -> enable","link":"/2017/05/16/raspberrypi-use-ssh/"},{"title":"라즈베리파이 FTP 서버 구성 (proftpd)","text":"라즈베리파이의 ftp 서버 접근이 가능하도록 구성해보자. 설치간단한 설치만으로도 기본적인 사용이 가능하다. 1$ sudo apt-get install proftpd 설정1$ sudo vi /etc/proftpd/proftpd.conf 위 명령어로 설정파일을 열어 편집한다. 12345# Use this to jail all users in their homesDefaultRoot ~/external/fileserver# 주석처리된 DefaultRoot 옵션을 활성화시킨 후 경로를 입력한다.# 이 때, 물결(~)은 접속한 사용자의 홈 디렉토리를 의미한다. 설정파일에서 DefaultRoot 만 수정 잘해도 특별히 문제가 될 것이 없다. 이 옵션은 일종의 보안을 위해 필요한 것인데, 나중에 이 서버를 주변 친구들이나 다른 사람에게 공개할 때 아무나 내 서버에 접속해서 중요한 파일을 헤집어놓고 다니면서 설정을 바꿔놓는다면 큰 문제가 생길 것이다. 그래서 보안을 위해서는 공개를 위한 디렉토리를 따로 만들어놓고 일반 사용자들은 그 제한된 디렉토리 안에서만 접근할 수 있도록 바꿔야 한다. 사용자의 홈 디렉토리에서 하위 폴더로 이동은 가능하지만 상위 폴더로는 이동이 불가능하다는 점을 활용한다. 위 설정 예시에서는 이전에 외장하드를 auto mount시킨(라즈베리파이 오토 마운트) 것이다.","link":"/2017/05/19/raspberrypi-ftp-proftpd/"},{"title":"스프링부트 OAuth2 - 3. OAuth 인증 서버 구축","text":"https://spring.io/guides/tutorials/spring-boot-oauth2/ 를 참조하여 작성하였습니다. 개인적으로 서버사이드 프로그래밍을 하면서 가장 어렵기도 하고 귀찮은 부분이 인증하는 부분이라고 생각한다. 로직을 작성해 나간다기 보다는 클라이언트와 서버사이드 간의 통신을 통해 사용자를 인지하고 이를 세션 등으로 관리할 뿐 아니라, 권한 문제까지 연결되는 부분이기 때문이다. 고로 가장 민감한 부분이라고 본다. 스프링 공식 홈페이지의 OAuth2 가이드를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다. Facebook으로 로그인 Github로 로그인 OAuth2 인증 서버 구축 인증 서버 구축이번 포스팅에서는 앞서 만들었던 어플리케이션을 OAuth2 인증 서버로 만든다. 앞서 구현했던 facebook과 github를 통한 인증을 사용하지만, 별도의 자체 액세스 토큰을 만들어서 인증을 수행한다. 이 토큰을 사용하여 백엔드 자원을 보호하고 타 어플리케이션과 SSO를 수행한다. 인증 설정 다듬기인증 서버를 본격적으로 구축하기 전에 github와 facebook에 관련된 설정을 먼저 다듬어야 할 필요가 있다. ssoFilter()를 수정해보자. 123456789101112// Application.javaprivate Filter ssoFilter() { CompositeFilter filter = new CompositeFilter(); List<Filter> filters = new ArrayList<>(); filters.add(ssoFilter(facebook(), "/login/facebook")); filters.add(ssoFilter(github(), "/login/github")); filter.setFilters(filters); return filter;} ssoFilter()를 overloading한 새로운 메서드를 작성해야 한다. 123456789private Filter ssoFilter(ClientResources client, String path) { OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path); OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext); filter.setRestTemplate(template); UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(), client.getClient().getClientId()); tokenServices.setRestTemplate(template); filter.setTokenServices(tokenServices); return filter;} 이전에 ssoFilter()에 작성한 내용과 유사하지만 약간의 공통화 과정을 거친 것이라 보면 되겠다. ClientResources라는 오브젝트는 존재하지 않는다. 따라서 별도의 wrapper 객체를 생성한다. 이는 별도의 @Beaㅜ로 선언된 OAuth2ProtectedResourceDetails와 ResourceServerProperties를 통합하는 역할이라 보면 되겠다. 123456789101112131415class ClientResources { @NestedConfigurationProperty private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails(); @NestedConfigurationProperty private ResourceServerProperties resource = new ResourceServerProperties(); public AuthorizationCodeResourceDetails getClient() { return client; } public ResourceServerProperties getResource() { return resource; }} wrapper 클래스는 @NestedConfigurationProperty를 사용하여 어노테이션 프로세서가 하나의 값을 표현하지 않고 중첩 형식을 나타내기 때문에 메타데이터에 접근하여 크롤링하도록 지시한다. 위 wrapper 클래스 작성으로 spring 설정을 이전처럼 사용할 수 있을 뿐만 아니라 공통화를 지향한 결과이다. 마지막으로 각각 provider의 설정값을 가져오도록 @Bean을 생성한다. 1234567891011@Bean@ConfigurationProperties("github")public ClientResources github() { return new ClientResources();}@Bean@ConfigurationProperties("facebook")public ClientResources facebook() { return new ClientResources();} 인증 서버 가용 상태로 만들기특별히 어마어마한 타이핑이 필요하지 않고 어노테이션 하나로 끝낼 수 있다. 123456@SpringBootApplication@EnableOAuth2Client@EnableAuthorizationServer // 추가public class SpringBootOauth2Application extends WebSecurityConfigurerAdapter{ // ...} 새로운 어노테이션을 추가하게 되면 필요한 엔드포인트와 security를 어플리케이션에 로드할 것이다. 그리고 몇가지 OAuth2 클라이언트에 관한 정보를 설정해야 한다. 12345678# application.ymlsecurity: oauth2: client: client-id: acme client-secret: acmesecret scope: read, write auto-approve-scopes: '.*' 위 작업은 facebook.client*, github.client*에 대한 작업과 동일한 것이다. auto-approve-scopes는 위 설정처럼 정규표현식으로 작성이 가능하다. 이번 포스팅에서 작성하는 샘플에서는 특별히 제한을 두지 않기 때문에 모든 것을 허용하지만, 실제 프로젝트나 운영 단계에서는 세부적으로 설정할 필요가 있다. 인증 서버 구축을 마무리하기 위해서 UI에 관련된 security 설정이 필요하다. 샘플 어플리케이션이기 때문에 많은 설정이 필요하진 않지만, oauth와 관련된 endpoint 등의 필요한 부분에 설정해 줄 필요가 있다. 123456789101112@Overrideprotected void configure(HttpSecurity http) throws Exception { http.antMatcher("/**") // ① .authorizeRequests() .antMatchers("/", "/login**", "/webjars/**", "/error**").permitAll() // ② .anyRequest().authenticated() // ③ .and().exceptionHandling() .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")) // ④ .and().logout().logoutSuccessUrl("/").permitAll() .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);} ① : 기본적으로 모든 요청은 보호된다. ② : 로그인 엔드포인트는 제외된다. ③ : 그 외의 모든 요청은 인증이 필요하다. ④ : 인증되지 않은 사용자는 /로 redirect 된다. Access Token 얻기이제 우리가 구축한 인증 서버를 통해 Access Token을 얻을 수 있다. 가장 쉬운 방법은 “acme” 클라이언트를 통해서이다. curl 명령어를 사용하여 토큰을 얻어보자 1234$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=client_credentials# % Total % Received % Xferd Average Speed Time Time Time Current# Dload Upload Total Spent Left Speed# 100 146 0 117 100 29 7312 1812 --:--:-- --:--:-- --:--:-- 9125{"access_token":"c42ba0f2-543e-4275-9eb2-efb1f48fa680","token_type":"bearer","expires_in":43186,"scope":"read write"} 단순히 토큰을 얻는 것만으로는 뭔가 완벽한 어플리케이션을 위해서는 부족해 보인다. 특정 유저에게 생성하도록 만들어야 한다. 스프링 어플리케이션을 구동했을 때 아마 Using generated security password: ... 처럼 자동 생성되는 기본 암호를 볼 수 있을 것이다. 이를 이용하여 다시 토큰을 얻어보자. 1234$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=...# % Total % Received % Xferd Average Speed Time Time Time Current# Dload Upload Total Spent Left Speed# 100 251 0 172 100 79 1577 724 --:--:-- --:--:-- --:--:-- 2302{"access_token":"629d6260-5eba-43e7-9072-7608b6b46254","token_type":"bearer","refresh_token":"bd7e65ce-6663-40b1-8307-04787221197f","expires_in":43199,"scope":"read write"} 명령어의 “…” 부분에는 앞서 말한 자동생성되는 암호를 기입해주어서 curl명령을 날리면 역시 토큰을 받을 수 있다. 현재 테스트한 방법 외에 일반적인 소셜 로그인에서는 “인증 코드”를 받아야 한다. 즉, 이를 통해 redirect, cookie 등을 핸들링하거나 외부 provider로부터 UI를 렌더링할 수 있어야 한다. 클라이언트 어플리케이션 생성하기우리가 구축한 인증 서버에 필요한 client를 생성해보자. ClientApplication.java를 생성할 것이다. 단, 기존 *Application.java와 같은 패키지(또는 서브 패키지)에 위치해서는 안된다. 스프링은 기존의 Application을 구동하면서 ClientApplication을 하나의 설정으로 구동시킬 것이다. 1234567891011121314151617// ClientApplication.java// src/test/java/ 에 위치해있다.@EnableAutoConfiguration@Configuration@EnableOAuth2Sso@RestControllerpublic class ClientApplication { @RequestMapping("/") public String home(Principal user) { return "Hello, " + user.getName(); } public static void main(String[] args) { new SpringApplicationBuilder(ClientApplication.class) .properties("spring.config.name=client").run(args); }} 단순하게 사용자의 이름을 출력하는 페이지로 구성이 되어있다. spring.config.name=client라는 설정파일을 불러와서 실행될 것이다. client.yml파일을 생성해보자. 1234567891011121314151617# client.ymlserver: port: 9999 servlet: context-path: /clientsecurity: oauth2: client: client-id: acme client-secret: acmesecret access-token-uri: http://localhost:8080/oauth/token user-authorization-uri: http://localhost:8080/oauth/authorize resource: user-info-uri: http://localhost:8080/mespring: main: allow-bean-definition-overriding: true 메인이 되는 어플리케이션 설정과 비슷하지만, facebook이나 github 대신 “acme” 클라이언트로 통하도록 설정이 되어있다. 어플리케이션은 9999 포트에서 띄워져서 기존 포트와의 충돌을 방지한다. server.context-path의 값을 별도로 세팅하였다. 따라서 http://localhost:9999/client 를 통해 확인이 가능하다. 어플리케이션을 시작하면 아래 그림처럼 두개의 포트가 띄워질 것이다.(사실 이 부분을 늦게 확인하는 바람에 어떻게 9999포트가 열린 상태인지 확인을 못했다.) 사용자 정보 엔드포인트 보호하기Single sign on을 위한 새로운 인증 서버를 사용하기 위해 facebook과 github를 사용하는데, 사용자가 인증할 때 생성된 쿠키로 보호된다. 로컬에 부여 된 액세스 토큰과 함께 보안을 유지하기 위해 기존의 엔드포인트를 재사용하고 새 경로로 alias를 지정할 수 있다. 123456@RequestMapping({ "/user", "/me" })public Map<String, String> user(Principal principal) { Map<String, String> map = new LinkedHashMap<>(); map.put("name", principal.getName()); return map;} 기존 메서드에서 Principal을 Map으로 바꾼 이유는 브라우저에서 민감한 정보를 노출시키지 않기 위해 숨기는 것이다. 추가적으로 필요한 부분이 있다면 Map에 추가적으로 put해줌으로써 브라우저에 노출시키는 것이 가능하다. “/me” 경로는 어플리케이션이 리소스 서버로 선언됨으로서 access token으로 보호된다. 다음과 같이 새로운 설정 클래스를 만들어보자 12345678910// Application.java@Configuration@EnableResourceServerprotected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.antMatcher("/me") .authorizeRequests().anyRequest().authenticated(); }} @Order 어노테이션을 추가한다. SecurityProperties의 ACCESS_OVERRIDE_ORDER가 deprecated 상태이므로 넘어간다. 어플리케이션이 security 수행 시, 엔드포인트에 대한 우선순위가 있지만 여기서는 정하지 않겠다. 테스트http://localhost:9999/client/ 로 접속하게 되면 redirect가 되면서 localhost:8080으로 이동할 것이다. facebook과 github 로그인 중 하나를 선택하면 인증이 시작되고 완료되면 다시 http://localhost:9999/client/ 로 이동될 것이다. 그리고 인증된 사용자의 이름이 출력되면서 테스트를 마무리할 수 있다. 마무리길고 긴 OAuth2인증부터 서버 구축까지 마무리를 해보았다. Spring Security는 이러한 기능도 하고 있지만 더 많은 기능을 담고 있는 강력한 모듈이기 때문에 추가적인 학습이 필요하다. 사실 이 포스팅 뒤에 에러 처리 등의 자잘한 과정이 남아있지만 이번 포스팅에서는 다루지 않겠다. 아마 여기까지 따라오기만 해도 꽤나 지칠 수 있기 때문이다. 이정도면 OAuth2의 기능을 살펴보았다고 해도 좋다. 참고 URL은 포스팅 처음에 링크를 걸어두었으니 가서 참고하면 좋을 것이다.(영어긴 하지만..) 소스코드https://github.com/hwiVeloper/SpringBootStudy/tree/495193fb5ca875be9b7833765ce6379971bc0ba0/spring-boot-oauth2","link":"/2019/04/21/spring-boot-oauth2-authserver/"},{"title":"라즈베리파이 트랜스미션(transmission) 토렌트 사용하기","text":"라즈베리파이의 특정 폴더에 torrent 파일 업로드 시, 서버에 토렌트 파일을 다운받도록 해보자. 설치1sudo apt-get install transmission-daemon 설정설치 한 후에 자동으로 데몬이 실행되는데, 이를 중지시켜야 한다.설정 파일을 건드려 줘야 하기 때문이다. 123sudo service transmission-daemon stopcd /etc/transmission-daemon/sudo vi settings.json 이제 설정 파일 settings.json을 수정해보자. 1234567891011"download-dir": "다운로드될 디렉토리", # 이전에 오토마운트 시켰던 외장하드로 지정해도 좋다."rpc-password": "원하는 비밀번호", # 자동으로 암호화되어 저장된다."rpc-port": 9091, # 기본 포트"rpc-username": "웹 접속 시 아이디","rpc-whitelist-enabled": false, # 외부 등 모든 아이피에서 접근이 가능하게 만든다."trash-original-torrent-files": true, # 다운로드 시작 시, 토렌트 파일은 자동 삭제# 옵션 맨 마지막에 추가한다.# 토렌트 파일을 앞으로 업로드 할 폴더를 지정한다."watch-dir":"토렌트파일을 업로드할 디렉토리","watch-dir-enabled":true 실행 및 확인그리고 이제 데몬을 시작해보자. 1$ sudo service transmission-daemon start 그리고 http://해당아이피:9091에 웹에서 접속하여 테스트하자 인증 완료 시 토렌트 파일 다운로드 현황을 보여주는 화면이 등장한다. 옵션에서 주었던 watch-dir에 토렌트 파일을 올리면 자동으로 웹에서 실시간 확인이 가능하다.진정한 파일서버 같은 느낌이 물씬 난다.","link":"/2017/05/25/raspberrypi-transmission-torrent/"},{"title":"스프링부트 OAuth2 - 1. Facebook 인증","text":"https://spring.io/guides/tutorials/spring-boot-oauth2/ 를 참조하여 작성하였습니다. 개인적으로 서버사이드 프로그래밍을 하면서 가장 어렵기도 하고 귀찮은 부분이 인증하는 부분이라고 생각한다. 로직을 작성해 나간다기 보다는 클라이언트와 서버사이드 간의 통신을 통해 사용자를 인지하고 이를 세션 등으로 관리할 뿐 아니라, 권한 문제까지 연결되는 부분이기 때문이다. 고로 가장 민감한 부분이라고 본다. 스프링 공식 홈페이지의 OAuth2 가이드를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다. Facebook으로 로그인 Github로 로그인 OAuth2 인증 서버 구축 Facebook으로 로그인프로젝트 생성Spring Starter Project를 생성한다. Dependency는 Web을 선택하고 프로젝트 생성을 완료한다. 페이지 추가index.html파일을 src/main/resources/static에 추가하고 아래와 같이 작성한다. 123456789101112131415161718<!DOCTYPE html><html><head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title>OAuth2 Sample Application</title> <meta name="description" content="OAuth2 Sample Application"/> <meta name="viewport" content="width=device-width"/> <base href="/"/> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css"/> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script></head><body> <h1>OAuth2 Sample Application</h1> <div class="container"></div></body></html> <head> 안쪽에 webjars가 보이는데 클라이언트 사이드에서 사용하는 javascript 또는 css 라이브러리 등을 jar 형태로 import시킬 수 있는 기능이라고 보면 되겠다. pom.xml에 해당 내용을 추가해보자. 123456789101112131415<!-- webjars --><dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version></dependency><dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version></dependency><dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId></dependency> bootstrap과 jquery를 추가하였다. webjars-locator-core라는 녀석은 앞서 추가한 webjars들의 path를 지정해주는 역할이라고 보면 좋을 것 같다. 앞서 index.html에서 /webjars/**에 위치한 js 파일과 css파일을 불러오기 위한 dependency이다. Security어플리케이션을 안전하게 만들기 위해서는(다소 어색한 말이지만 guide에 있는 말을 빌려왔다.) Spring Security와 관련된 dependency를 적용해야 한다. pom.xml에 추가해보자. 12345678910<!-- Security --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.0.RELEASE</version></dependency> Facebook으로 연결하기 위하여 Application.java에 @EnableOAuth2Sso 어노테이션을 추가한다. 1234567@SpringBootApplication@EnableOAuth2Sso // <-- 이부분을 추가한다.public class SpringBootOauth2Application { public static void main(String[] args) { SpringApplication.run(SpringBootOauth2Application.class, args); }} application.yml 또는 application.properties를 열어서 다음과 같이 페이스북 관련 정보를 기입한다. 정보에 대한 내용은 페이스북 개발자 페이지에서 앱 생성 후 oauth2 관련 세팅을 완료한 후에 다음 스텝으로 넘어가자. 123456789101112security: oauth2: client: client-id: CLIENT-ID client-secret: CLIENT-SECRET accessTokenUri: https://graph.facebook.com/oauth/access_token userAuthorizationUri: https://www.facebook.com/dialog/oauth tokenName: oauth_token authenticationScheme: query clientAuthenticationScheme: form resource: userInfoUri: https://graph.facebook.com/me 서버를 실행하고 http://localhost:8080으로 접속해보자. 올바르게 설정이 완료되었다면 곧바로 페이스북 앱에 대해 동의하는 화면이 나올 것이다. 앱에서 정보 수신에 대해 동의버튼을 누르면 원래 우리가 호출하려 했던 index.html과 같은 화면이 등장할 것이다. 이 때 (크롬 기준) 개발자도구를 열고 Network 탭을 확인하면 아래와같이 Cookie에 JSESSIONID가 생성된 것을 확인할 수 있다. Welcome Page 추가하기Facebook 로그인 링크와 로그인 되었을 때 사용자 정보를 출력하는 페이지를 작성해보자. 다음과 같이 index.html을 작성한다. 123456<div class="container unauthenticated"> Facebook : <a href="/login">클릭</a></div><div class="container authenticated" style="display:none"> 로그인 되었습니다 : <span id="user"></span></div> authenticated와 unauthenticated 클래스를 통해 인증 여부를 확인하고 그에 따라 display를 해주는 부분이다. 이에 대한 내용을 jQuery를 사용하여 작성하자 1234567<script type="text/javascript"> $.get("/user", function(data) { $("#user").html(data.userAuthentication.details.name); $(".unauthenticated").hide() $(".authenticated").show() });</script> 서버사이드 변경위 javascript 소스코드처럼 /user 에 대한 부분을 작성해야 한다. UserController.java를 작성하자. 1234567@RestControllerpublic class UserController { @RequestMapping("/user") public Principal user(Principal principal) { return principal; }} 본래 Principal 전체를 return 하는 것은 권장되는 방법이 아니지만 샘플 프로젝트의 빠른 작업을 위해 이와 같이 사용한다. 추후 브라우저에서 보이고싶지 않은 부분에 대해서는 숨기는 처리를 한다. 잘 작동하지만 사용자가 링크를 클릭 할 수가 없다. 로그인 링크를 표시하려면 WebSecurityConfigurer를 추가하여 설정을 한다. 123456789101112131415161718// Application.java@SpringBootApplication@EnableOAuth2Ssopublic class SpringBootOauth2Application extends WebSecurityConfigurerAdapter{ public static void main(String[] args) { SpringApplication.run(SpringBootOauth2Application.class, args); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/**") .authorizeRequests() .antMatchers("/", "/login**", "/webjars/**", "/error**") .permitAll() .anyRequest() .authenticated(); }} WebSecurityConfigurerAdapter를 상속받고 configure()를 작성한다. 그리고 인증을 처리하는 로그인 엔드포인트와 다른 요청들에 대한 인증이 필요하도록 설정한다. 이제 서버를 재시작하고 접속하면 로그인 링크가 등장하고 로그인을 실행하면 로그인된 사용자의 정보가 등장할 것이다. 로그아웃 버튼 추가하기index.html에 authenticated클래스 안쪽에 로그아웃 버튼을 추가해보자. 또한 로그아웃 버튼에 대한 이벤트 함수를 작성한다. 123456<div class="container authenticated" style="display:none"> 로그인 되었습니다 : <span id="user"></span> <div> <button onClick="logout()" class="btn btn-primary">로그아웃</button> </div></div> 12345678var logout = function() { $.post("/logout", function() { $("#user").html(''); $(".unauthenticated").show(); $(".authenticated").hide(); }); return true;} 서버사이드 역시 /logout에 대한 엔드포인트가 필요하다. 조금 전 작성했던 configure()에 로그아웃에 대한 엔드포인트를 작성하자. /logout은 POST메소드로 요청할 필요가 있다. 또한 CSRF(Cross Site Request Forgery)로부터 보호해야 한다. 현재 세션에 이로부터 보호하는 토큰값이 있기 때문에 클라이언트 단에서 이에 대한 값을 가지고 있어야 한다. 요즘 성행하는 자바스크립트 프레임워크에는 위와 같은 문제에 대한 방도가 내장이 되어있어서 사용해주면 된다. 자세한 내용은 좀 더 봐야 할 것 같다. 아직 이부분에 대해서는 심화학습이 필요하다. 123456789101112// Application.java@Overrideprotected void configure(HttpSecurity http) throws Exception { http.antMatcher("/**") .authorizeRequests() .antMatchers("/", "/login**", "/webjars/**", "/error**") .permitAll() .anyRequest() .authenticated() .and().logout().logoutSuccessUrl("/").permitAll() .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());} CSRF 토큰을 클라이언트 단에 추가하기이번 샘플에서는 자바스크립트 프레임워크 등을 사용하지 않기 때문에 pom.xml에 라이브러리를 추가하는 것으로 대체한다. 라이브러리를 추가하고 html에 적용해보자. 12345<dependency> <groupId>org.webjars</groupId> <artifactId>js-cookie</artifactId> <version>2.1.0</version></dependency> 1<script type="text/javascript" src="/webjars/js-cookie/js.cookie.js"></script> 12345678910$.ajaxSetup({ beforeSend : function(xhr, settings) { if (settings.type == 'POST' || settings.type == 'PUT' || settings.type == 'DELETE') { if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { xhr.setRequestHeader("X-XSRF-TOKEN", Cookies.get('XSRF-TOKEN')); } } }}); 어플리케이션을 재시작하고 결과를 확인해본다. OAuth2 클라이언트에 대한 설정@EnableOAuth2Sso 어노테이션에는 OAuth2 클라이언트와 인증 두 가지 기능이 있다. 클라이언트 기능은 인증 서버(이번 경우는 Facebook)에서 제공하는 OAuth2 리소스와 통신하는데 사용한다. 인증 기능은 해당 어플리케이션을 타 Spring Security와 연동시키는 것이다. 클라이언트 기능은 Spring Security OAuth2에 의해 제공되는 기능이고, @EnableOAuth2Client에 의해 enable/disable이 가능하다. 이번에는 @EnableOAuth2Sso 어노테이션을 사용하지 않고 @EnableOAuth2Client 어노테이션을 사용해보자. 우선 OAuth2ClientContext를 주입시키고 인증 filter를 security 설정에 추가할 수 있게 한다. 1234567891011121314151617181920@SpringBootApplication@EnableOAuth2Client // 변경@RestControllerpublic class SocialApplication extends WebSecurityConfigurerAdapter { @Autowired OAuth2ClientContext oauth2ClientContext; // 의존성 주입 @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/**") .authorizeRequests() .antMatchers("/", "/login**", "/webjars/**", "/error**") .permitAll() .anyRequest() .authenticated() .and().logout().logoutSuccessUrl("/").permitAll() .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class); } OAuth2ClientContext를 사용하는 ssoFilter()를 작성한다. 123456789private Filter ssoFilter() { OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook"); OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext); facebookFilter.setRestTemplate(facebookTemplate); UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()); tokenServices.setRestTemplate(facebookTemplate); facebookFilter.setTokenServices(tokenServices); return facebookFilter;} 위 필터에서는 facebook과 관련된 application 속성을 가지고 client 정보를 등록한다. 12345@Bean@ConfigurationProperties("facebook.client") // application 속성public AuthorizationCodeResourceDetails facebook() { return new AuthorizationCodeResourceDetails();} 그리고 facebook 사용자 정보를 가져오는 엔드포인트를 등록하여 인증처리를 한다. (facebookResource() 작성) 12345@Bean@ConfigurationProperties("facebook.resource")public ResourceServerProperties facebookResource() { return new ResourceServerProperties();} 위 수정사항들을 추가하면서 application.yml(application.properties)의 속성을 바꿔줄 필요가 있다. 1234567891011facebook: client: client-id: CLIENT-ID client-secret: CLIENT-SECRET accessTokenUri: https://graph.facebook.com/oauth/access_token userAuthorizationUri: https://www.facebook.com/dialog/oauth tokenName: oauth_token authenticationScheme: query clientAuthenticationScheme: form resource: userInfoUri: https://graph.facebook.com/me 그리고 index.html파일을 수정해준다. 링크의 url을 변경한다. 123456<h1>로그인</h1><div class="container unauthenticated"> <div> Facebook : <a href="/login/facebook">클릭</a> <!-- url 변경 --> </div></div> 마지막으로 어플리케이션에서 Facebook으로 redirect하는 것을 지원하는 Filter를 작성하고 Bean으로 등록한다. 필터는 이미 @EnableOAuth2Client를 통해 등록된 것이기 때문에 정상적인 필터 작동을 위해 연결시켜줄 필요가 있다. 1234567@Beanpublic FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) { FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>(); registration.setFilter(filter); registration.setOrder(-100); return registration;} 이제 마지막으로 로그인 / 로그아웃을 테스트해본다. 잘되는 모습을 볼 수 있다. 막바지의 어노테이션 변경 부분부터 다소 어려운 부분인 것 같다. 평소 못보았던 클래스도 많이 보이고 OAuth2와 관련된 필터 설정이 생각보다 손이 많이 가는 것을 보았다. 포스팅 가장 처음에 말했듯이 가장 어려우면서도 귀찮은 부분인 인증 과정에 대해 책을 정독하듯이 따라오게 되었는데, 앞으로 이 부분(특히 Filter를 적용하는 부분)에 대해 더 심도있게 알아보고 싶다. 그래도 한 번 해놓으면 또 써먹을 날이 오겠지.. 다음 포스팅에서는 이와 유사하게 Github 인증을 진행할 것이다. 소스코드https://github.com/hwiVeloper/SpringBootStudy/tree/2c714d3ba9c511e790e087b9362842017dcfc9f3/spring-boot-oauth2","link":"/2019/04/05/spring-boot-oauth2-facebook/"},{"title":"라즈베리파이 설치 (MacOS)","text":"img 파일 다운로드라즈베리파이는 우분투(Ubuntu) 기반의 raspbian 이라는 OS를 사용한다.lite버전은 cli만 사용가능한 버전이다.http://www.raspberrypi.org/downloads 터미널에서 디스크 마운트 확인1$ diskutil list sd카드에 해당하는 장치를 언마운트 한다. 1$ diskutil unmountDisk /dev/disk4 img 파일이 있는 곳으로 이동 후 다음과 같은 명령어로 sd카드에 img 설치 12$ cd /path/to/img$ sudo dd bs=1m if=2017-04-10-raspbian-jessie.img of=/dev/rdisk4 ctrl + t 를 눌러 설치 현황을 확인할 수 있다. 완료되면 명령어 커맨드 대기상태가 된다. pc에서 sd카드를 언마운트한 후에, 라즈베리파이에 삽입하여 나머지 설치과정을 진행한다. 참조 : http://forteleaf.blogspot.kr/2016/06/3-via-osx.html다른 방법 : http://giyatto.tistory.com/28 - 인스톨러 활용","link":"/2017/05/16/raspberrypi-install-MacOS/"},{"title":"스프링부트 스케쥴러","text":"스프링 부트에서는 스케쥴러가 사용이 가능하다. 따로 Maven이나 Gradle에서 dependency를 추가하지 않고 기본 요소로 탑재가 되어있다. 간단하게 어노테이션과 Task를 정의하는 것만으로도 사용할 수 있는데, 이에 대해서 알아보자. 스케쥴러 ONApplication.java 1234567891011import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableScheduling // 이 부분 추가public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); }} 스프링 스타터 프로젝트 생성 시 기본으로 생성되는 어플리케이션 소스코드이다. @EnableScheduling 어노테이션 추가를 통해 스케쥴러를 동작시키겠다는 정의를 내리는 부분이다. 스케쥴(task) 정의이제 스케쥴을 정의해보자. 따로 패키지 위치는 상관없는 것으로 보인다. ScheduleTasks.java 12345678910111213@Componentpublic class ScheduledTasks { private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class); private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); /* 스케쥴 task 정의 */ @Scheduled(fixedRate = 5000) public void reportCurrentTime() { log.info("The time is now {}", dateFormat.format(new Date())); }} @Scheduled 어노테이션으로 해당 메서드가 스케쥴러에 의해 동작되는 것임을 알려주는 부분이다. 이 어노테이션은 현재 fixedRate의 값이 지정되어 있고 이는 5초에 한번씩 동작하게끔 한다. 이외에도 특히 쓸만하다고 생각되는 것은 cron이다. 리눅스 환경에서의 crontab와 같은 부분이다. 단, crontab에서는 다섯자리 표현식이 가능했던 반면, Spring boot scheduler에서는 6자리 표현식만 허용된다. 1@Scheduled(cron="*/5 * * * * *") cron 속성은 총 6자리로 이루어지고 일반적은 cron을 설정하듯이 작성해준다. 작성 후 서버를 재기동 하면 그 순간부터 스케쥴러가 돌기 시작하면서 사전에 정의된 메서드를 실행한다. @Scheduled 어노테이션 속성은 cron 외에도 다음과 같은 것들이 있다. 더 다양한 속성이 있지만 두 가지만 살펴보도록 하자. | 속성 | type | 설명 ||———— |—— |——————————————————– || fixedDelay | int | invoke 완료 후 지정된 시간(milliseconds) 이후에 재실행 || fixedRate | int | 지정된 시간(milliseconds) 간격으로 실행 | 정리스프링의 스케쥴러는 일반적인 리눅스의 crontab사용보다도 더 간단해서 마음에 쏙 들었다. 하지만 너무 많은 스케쥴 task, 또는 과중한 스케쥴러는 어플리케이션에 지장을 줄 수도 있으니 적절하게 쓰는 것이 중요하다고 생각된다. 참조https://spring.io/guides/gs/scheduling-tasks/","link":"/2019/01/16/spring-boot-scheduler/"},{"title":"스프링부트 OAuth2 - 2. Github 인증","text":"https://spring.io/guides/tutorials/spring-boot-oauth2/ 를 참조하여 작성하였습니다. 스프링 공식 홈페이지의 OAuth2 가이드를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다. Facebook으로 로그인 Github로 로그인 OAuth2 인증 서버 구축 Github로 로그인이번 포스팅에서는 지난 포스팅에서 다루었던Facebook으로 로그인에 이어서 Github계정으로 OAuth2 인증을 하는 과정에 관한 글이다. 방식은 비슷해서 금방 구현이 가능하다. 우선 index.html 파일에 Github로 로그인 할 수 있는 링크를 추가하자. 123456789<h1>로그인</h1><div class="container unauthenticated"> <div> Facebook : <a href="/login/facebook">클릭</a> </div> <div> Github : <a href="/login/github">클릭</a> </div></div> 원래는 provider(facebook, github, google 등)마다 엔드포인트에 대한 처리가 달라져야겠지만 이번 시리즈의 포스팅에서는 따로 처리하지 않아도 인증 처리와 관련된 응답에서 name이라는 필드를 공통적으로 가지고 있기 때문에 특별히 변경할 사항은 없다. Github 인증 필터 추가Application.java에서 /login/github에 대한 필터 추가가 필요하다 ssoFilter()에 추가해보자. 12345678910111213141516171819202122232425262728293031323334353637private Filter ssoFilter() { CompositeFilter filter = new CompositeFilter(); List<Filter> filters = new ArrayList<>(); OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook"); OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext); facebookFilter.setRestTemplate(facebookTemplate); UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()); tokenServices.setRestTemplate(facebookTemplate); facebookFilter.setTokenServices(tokenServices); filters.add(facebookFilter); OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github"); OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext); githubFilter.setRestTemplate(githubTemplate); tokenServices = new UserInfoTokenServices(githubResource(), getUserInfoUri(), github().getClientId()); githubFilter.setTokenServices(tokenServices); filters.add(githubFilter); filter.setFilters(filters); return filter;}// ... 중략 ...@Bean@ConfigurationProperties("github.client")public OAuth2ProtectedResourceDetails github() { return new AuthorizationCodeResourceDetails();}@Bean@ConfigurationProperties("github.resource")public ResourceServerProperties githubResource() { return new ResourceServerProperties();} 이전에 등록한 Facebook 필터로 인해 CompositeFilter를 구성하여 List로 추가하는 방식을 사용하였다. github(), githubResource()를 추가하여 나머지 내용도 보충하였다. 또한 application.yml에 github 관련 설정을 추가한다. 선행되어야 하는 OAuth2 앱을 작성해야 하는데 github OAuth2 앱을 만드는 방법은 여기를 참조하자. 123456789github: client: clientId: CLIENT-ID clientSecret: CLIENT-SECRET accessTokenUri: https://github.com/login/oauth/access_token userAuthorizationUri: https://github.com/login/oauth/authorize clientAuthenticationScheme: form resource: userInfoUri: https://api.github.com/user 결과 확인결과를 확인해보자. 깃허브 로그인 링크를 클릭하면 다음과 같은 화면을 볼 수 있고, 승인을 하면 다시 index.html로 돌아와서 사용자정보가 확인되는 것을 볼 수 있다. 소스코드https://github.com/hwiVeloper/SpringBootStudy/tree/18f34d30f05db7f0fa998b6a6cae3b0019c05331/spring-boot-oauth2","link":"/2019/04/08/spring-boot-oauth2-github/"},{"title":"스프링부트에서 STOMP 웹소켓 사용하기","text":"spring guide를 보던 중 STOMP 프로토콜을 사용하여 WebSocket을 구현하는 재미있는 샘플이 있어서 사용해보았다. WebSocket은 TCP레이어 위에 존재하여 중간에 메세지를 전달할 수 있는 채널의 개념이다. 공식 가이드에서는 STOMP라는 프로토콜을 사용하여 간단한 요청 및 응답을 하는 기본적인 구성으로 되어있다. 이에 더하여 정말 간단한 채팅 어플리케이션을 구현하는 것까지가 이번 글의 목표이다. 소스코드는 http://spring.io/guides/gs/messaging-stomp-websocket에서 참조했다. STOMP프로젝트 생성 전에 STOMP(https://stomp.github.io/)가 무엇인지 간단히 알아보자. Simple Text Oriented Messaging Protocol의 약자로 단순 텍스트 기반 메세징 프로토콜이다. 123456COMMANDkey(header):valuekey(header):value...BODY^@ 위와 같은 형식으로 되어있다. COMMAND에는 보통 SEND, SUBSCRIBE와 같은 명령을 사용할 수 있다. 추가적인 header와 body 본문 내용을 통해 통신을 하는 방식이라고 보면 되겠다. 위 예시 이미지는 소켓 통신 커넥션을 생성하는 부분이므로 COMMAND와 header만 존재하고 body 본문 내용은 존재하지 않다. 더 자세한 내용은 https://zetawiki.com/wiki/STOMP 등에서 참조하도록 하고 본격적인 프로젝트 생성부터 들어가보자. 프로젝트 생성https://start.spring.io/ 에서 프로젝트를 생성하거나 eclipse, sts 등 사용하고 있는 IDE에서 spring boot project를 생성한다. 빌드 툴Gradle과 Maven을 사용할텐데 두 가지 뭐로 하든 무방하다. Gradle 123456789101112131415161718192021222324252627282930313233343536373839404142buildscript { ext { springBootVersion = '2.1.1.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }}apply plugin: 'java'apply plugin: 'eclipse-wtp'apply plugin: 'org.springframework.boot'apply plugin: 'io.spring.dependency-management'bootJar { baseName = 'spring-boot-chatting' version = '0.1.0'}sourceCompatibility = '1.8'repositories { mavenCentral()}dependencies { compile("org.springframework.boot:spring-boot-starter-websocket") compile("org.webjars:webjars-locator-core") compile("org.webjars:sockjs-client:1.0.2") compile("org.webjars:stomp-websocket:2.3.3") compile("org.webjars:bootstrap:3.3.7") compile("org.webjars:jquery:3.1.0") testCompile("org.springframework.boot:spring-boot-starter-test")}task stage { dependsOn build} Maven 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework</groupId> <artifactId>gs-messaging-stomp-websocket</artifactId> <version>0.1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <properties> <java.version>1.8</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> 메세지 Class 작성STOMP메세지 포맷에 맞게 getter를 가진 domain class를 생성해보자. STOMP 메세지에서 body는 JSON 오브젝트로 구성이 되어있다. 스프링 부트에서 Jackson JSON 라이브러리를 사용하고 있기 때문에 domain class를 파라미터로 사용하거나 return시켜준다면 json으로 변환이 된 상태로 통신할 것이다. 123{ "name": "hwiVeloper"} HelloMessage.java를 생성하고 내용을 작성한다. 123456789101112131415161718192021package dev.hwi.domain;public class HelloMessage { private String name; public HelloMessage() { } public HelloMessage(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }} 그리고 Greeting.java를 작성해보자. 1234567891011121314151617package dev.hwi.domain;public class Greeting { private String content; public Greeting() { } public Greeting(String content) { this.content = content; } public String getContent() { return content; }} 컨트롤러STOMP 메세지는 스프링에서는 컨트롤러에서 핸들링된다. 아래 GreetingConroller.java를 작성해보자. 1234567891011121314151617181920package dev.hwi.controller;import org.springframework.messaging.handler.annotation.MessageMapping;import org.springframework.messaging.handler.annotation.SendTo;import org.springframework.stereotype.Controller;import org.springframework.web.util.HtmlUtils;import dev.hwi.domain.Chat;import dev.hwi.domain.Greeting;import dev.hwi.domain.HelloMessage;@Controllerpublic class GreetingController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(100); // delay return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); }} 단순한 컨트롤러이지만 생소한 annotation 두 개가 보인다. @MessageMapping은 클라이언트에서 /hello쪽으로 메세지를 전달하면 greeting메서드가 실행된다. 그리고 이 메서드는 다시 @SendTo 어노테이션에 정의된 쪽으로 결과를 return시킨다. 예제에서는 Thread.sleep(1000);를 작성했지만 생략해도 무방하다. @SendTo로 return시킨 후에 클라이언트에서는 전달받은 값을 렌더링하여 브라우저에 표시해 줄 수 있는 상태가 된다. STOMP 관련 설정설정이 필요하므로 WebSocketConfig.java파일을 작성한다. 12345678910111213141516171819202122package dev.hwi.config;import org.springframework.context.annotation.Configuration;import org.springframework.messaging.simp.config.MessageBrokerRegistry;import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/websocket").withSockJS(); }} @Configuration, @EnableWebSocketMessageBroker를 통해 WebSocket과 관련된 설정을 작동시킨다. WebSocketConfig 클래스는 WebSocketMessageBrokerConfigurer를 implements하고 두 개의 메서드를 오버라이딩한다. 메세지 브로커(Message Broker)는 송신자에게 수신자의 이전 메세지 프로토콜로 변환해주는 모듈 중에 하나이다. 요청이 오면 그에 해당하는 통신 채널로 전송해주고 응답 역시 왔던 길을 그대로 다시 가서 안정적으로 메세지를 응답해주기 위한 부분입니다. configureMessageBroker() enableSimpleBroker는 클라이언트로 메세지를 응답해줄 때 prefix를 정의한다. setApplicationDestinationPrefixes는 클라이언트에서 메세지 송신 시 붙여줄 prefix를 정의한다. registerStompEndpoints() 최초 소켓 연결을 하는 경우, endpoint가 되는 url이다. 추후 javascript에서 SockJS 생성자를 통해 연결될 것이다. html, js 작성/src/main/resources/static/index.html 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051<!DOCTYPE html><html><head> <title>Hello WebSocket</title> <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="/main.css" rel="stylesheet"> <script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="/app.js"></script></head><body><noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!</h2></noscript><div id="main-content" class="container"> <div class="row"> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="connect">WebSocket connection:</label> <button id="connect" class="btn btn-default" type="submit">Connect</button> <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect </button> </div> </form> </div> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="name">What is your name?</label> <input type="text" id="name" class="form-control" placeholder="Your name here..."> </div> <button id="send" class="btn btn-default" type="submit">Send</button> </form> </div> </div> <div class="row"> <div class="col-md-12"> <table id="conversation" class="table table-striped"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody id="greetings"> </tbody> </table> </div> </div></div></body></html> /src/main/resources/static/app.js 부가 내용은 주석으로 설명 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152var stompClient = null;function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#greetings").html("");}function connect() { var socket = new SockJS('/websocket'); stompClient = Stomp.over(socket); // SockJS와 stomp client를 통해 연결을 시도. stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); });}function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected");}function sendName() { // /app/hello로 JSON 파라미터를 메세지 body로 전송. stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));}function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>");}$(function () { $("form").on('submit', function (e) { e.preventDefault(); }); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); });}); 이후 작성한 프로젝트를 Server Start 한 후 확인한다. 여기까지가 공식가이드에서 보여주는 샘플이다. 진짜 채팅처럼(그래도 디자인은 자신이 없다..) ui를 약간만 추가해주자. 간단한 채팅 어플리케이션 만들어보기/app/chat으로 송신하여 /topic/chat으로 수신을 받아서 여러 명이 접속하는 경우까지 가정하여 작성해보도록 한다. 송신하는 데이터 포맷은 다음과 같다. 1234{ "name": "hwiVeloper", "message": "안녕하세요."} 이를 위해 Greeting.java와 유사하게 메세지를 받고 전달해줄 domain class가 필요하다. Chat.java 1234567891011121314151617181920212223package dev.hwi.domain;public class Chat { private String name; private String message; public Chat() { } public Chat(String name, String message) { this.name = name; this.message = message; } public String getName() { return name; } public String getMessage() { return message; }} GreetingController.java에는 메서드를 하나 추가해준다. 12345@MessageMapping("/chat")@SendTo("/topic/chat")public Chat chat(Chat chat) throws Exception { return new Chat(chat.getName(), chat.getMessage());} 동일하게 @MessageMapping과 @SendTo 어노테이션을 통해 매핑해주고 return시켜줄 내용을 작성해준다. index.html 좀전 예제에서 이름을 입력했던 input 태그 밑에 다음과 같은 내용을 추가한다. 12345<div class="form-group"> <label for="message">Input Message</label> <input type="text" id="chatMessage" class="form-control" placeholder="message.." /></div><button id="chatSend" class="btn btn-default" type="button">Chat Send</button> app.js 123456789101112131415161718192021222324252627282930// ... 앞에 생략 ...function connect() { var socket = new SockJS('/websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { // ... /* 이 부분 추가 */ stompClient.subscribe('/topic/chat', function (chat) { showChat(JSON.parse(chat.body)); }); });}// ... 중략 .../* Chat과 관련된 메서드 추가 */function sendChat() { stompClient.send("/app/chat", {}, JSON.stringify({'name': $("#name").val(), 'message': $("#chatMessage").val()}));}function showChat(chat) { $("#greetings").append("<tr><td>" + chat.name + " : " + chat.message + "</td></tr>");}$(function () { // ... $( "#chatSend" ).click(function(){ sendChat(); }); // 추가}); 테스트이제 브라우저 세션을 여러 개 띄운 후 채팅하듯이 테스트를 해본다. 정리STOMP기반의 메세징 서비스에 대해 알아보고 이를 Spring Boot에서 어떻게 사용하는지를 알아보았다. 소스코드https://github.com/hwiVeloper/SpringBootStudy/tree/master/spring-boot-websocket 참고http://spring.io/guides/gs/messaging-stomp-websockethttps://zetawiki.com/wiki/STOMPhttps://stomp.github.io/http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3.5:ptl:stomp","link":"/2019/01/10/spring-boot-stomp-websocket/"},{"title":"Swift 집단 자료형","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 집단 자료형 배열 : 일련번호로 구분되는 순서에 따라 데이터가 정렬된 목록 형태의 자료형 집합 : 중복되지 않은 유일 데이터들이 모인 집합 형태의 자료형 튜플 : 종류에 상관없이 데이터들을 모은 자료형. 수정 및 삭제 불가능 딕셔너리 : 배열과 유사. 일련번호 대신 키를 사용하여 키-값으로 연관된 데이터들이 순서 없이 모인 자료형 튜플 외에는 모든 데이터의 타입이 동일해야 한다. 여러 타입 섞어쓰기가 불가하다. 배열 순서를 가지는 리스트 형식의 값을 저장하는 데 사용된다. 다른 언어의 배열과 사용법은 같다. 저장하는 값의 타입이 모두 같아야 한다. 선언 시 배열에 저장할 값 타입을 정의해야 한다. 배열의 크기는 동적으로 지정이 가능하다. 123456var cities = ["Seoul", "New York", "LA", "Beijing"]cities.count // 4 -> 배열의 길이for i in 0..<length { print("\\(i)번째 배열 원소는 \\(cities[i])")} 배열의 선언123456789101112var 배열이름 = Array<데이터 타입>()var 배열이름 : Array<데이터 타입>배열이름 = Array() // 배열 초기화var 배열이름 : [데이터 타입] // 선언배열이름 = [데이터 타입]()배열이름 = []var cities = Array<String>()var cities : Array<String>cities = Array() 위와 같이 배열의 선언과 초기화는 다양한 방법이 존재한다. 1234567891011var arr1 : [String] // 선언arr1 = [String]() // 초기화var arr2 : [String] // 선언arr2 = [] // 초기화var arr3 : [Int] = [] // 타입 어노테이션 + 초기화var arr4 : Array<Float> = [Float]() // 타입 어노테이션 + 제네릭 + 초기화var arr5 : [String] = Array() // 타입 어노테이션 + 구방식의 초기화 배열에 아이템이 없다면 isEmpty는 true를 리턴한다. 배열 아이템 동적 추가123append( _:)insert( _:, axt:)append(contentsOf:) append( _ :)메서드는 값을 배열 맨 뒤에 추가한다. insert( _ :at:) 메서드는 값을 원하는 위치에 추가하고 그 뒤 아이템 인덱스는 증가한다. append(contentsOf:) 메서드는 배열 맨 마지막에 배열 째로 추가한다.(여러 개 아이템) 언더바( _ )는 추가될 아이템을 지칭한다. Array(repeating:count:) 배열의 인덱스가 개수만큼 미리 정의되고, 여기에 기본값이 추가된 상태로 배열이 생성된다. 123456var arr = [String](repeating: "None", count: 3)/*0 -> "None"1 -> "None"2 -> "None"*/ 집합 (Set)중복이 없는 같은 타입의 값을 저장하고자 할 때 사용된다.데이터 타입은 해시 연산을 할 수 있는 타입이어야 한다.(대부분 타입은 해시 연산이 가능하다.) 1Set <데이터 타입> () 집합의 동적 추가와 삭제insert파라미터 값을 추가하지만 이미 있는 아이템이라면 아무 처리를 하지 않는다. remove파라미터 값을 삭제한다. 없는 아이템이라면 아무 처리를 하지 않는다. removeAll집합의 모든 아이템을 삭제한다. 집합 연산기본 집합 연산 intersection : 교집합. 공통된 부분 symmetricDifference : 어느 한쪽에만 있는 아이템을 선택(합집합-교집합) union : 합집합 subtract : 차집합 앞에서 뒤를 차집합시킨다. 부분집합과 포함관계 판단 연산 isSubset : 부분집합인지의 여부 isSuperset : 상위집합인지의 여부 isStrictSubset & isStrictSuperset : 위 두개와 같지만 반대로 반환 isDisjoint : 공통값을 확인. 있다면 false, 없다면 true 튜플(Tuple)튜플은 독특한 자료형이다. 파이썬에도 등장하는 이 튜플은 한 가지 타입만을 위한 자료형이 아니다. 소괄호를 이용하여 튜플을 형성한다. 인덱스 넘버가 존재한다. 12let tupleValue:(String, Character, Int, Float, Bool) = ("a", "b", 1, 2.5, true)let (a, b, c, d, e) = tupleValue 결과값으로 튜플을 반환하는 함수123456func getTupleValue() -> (String, String, Int) { return ("t", "v", 100)}// 함수가 반환하는 튜플을 튜플 상수로 바인딩let (a, b, c) = getTupleValue() 딕셔너리1[ 키 : 데이터, 키 : 데이터, ... ] 하나의 키는 하나의 데이터에만 연결되어야 한다. 하나의 딕셔너리에서 키는 중복될 수 없다. 중복 선언 시, 아이템 추가가 아닌 수정이 이루어져 기존 키에 연결된 데이터가 삭제된다. 저장할 수 있는 데이터 타입에는 제한이 없다. 하나의 딕셔너리에 저장하는 데이터 타입은 모두 일치해야 한다. 딕셔너리의 아이템에는 순서가 없지만 키에는 내부적으로 순서가 있어서 for 구문을 이용한 탐색이 가능하다. 키의 타입은 대부분 가능하지만, 해시 연산이 가능해야 한다. 1234567var capital = ["KR":"Seoul", "EN":"London", "FR":"Paris"]capital["KR"] // "Seoul"capital["EN"] // "London"capital["FR"] // "Pairs"Dictionary <키의 타입, 값의 타입> () 딕셔너리 동적으로 제어하기1234567891011<딕셔너리 객체>.updateValue(<저장할 데이터>, forKey:<데이터 참조 및 저장하는 데 사용할 키>)newCapital.updateValue("Seoul", forKey: "KR")newCapital.updateValue("London", forKey: "EN")newCapital.updateValue("Sapporo", forKey: "JP")// 삭제// removeValue(forKey:)newCapital.removeValue(forKey: "CA")","link":"/2017/05/31/swift-collection-data-type/"},{"title":"Swift 흐름 제어 구문","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 흐름 제어 구문 반복문 (Loop Statements 조건문 (Conditional Statements) 제어 전달문 (Control Transfer Statements) 반복문for ~ in 구문1234567891011for <루프 상수> in <순회 대상> { // 실행할 내용}for row in 1...5 { print(row)}for row in 1...9 { print("2 X \\(row) = \\(row * 2)")} 순회 대상으로 사용할 수 있는 타입 배열 딕셔너리 집합 범위 데이터 문자열 순회 대상의 값들이 루프 상수에 저장되어 쓰인다. 1234var chars = "swift"for char in chars.characters { print("개별 문자는 \\(char)입니다.")} 루프 상수의 생략 123456789let size = 5let padChar = "0"var keyword = "3"for _ in 1...size { keyword = padChar + keyword}print("\\(keyword)") 순회 시 대상 아이템이 필요하지 않은 경우에 생략 가능하다. 중첩을 활용한 구구단 코드 12345for i in 1..<10 { for j in 1..<10 { print("\\(i) X \\(j) = \\(i * j)") }} while 구문 실행 횟수가 명확하지 않을 때 직접 실행해보기 전까지는 실행 횟수를 결코 알 수 없을 때 실행 횟수를 기반으로 할 수 없는 조건일 때 1234while <조건식> { <실행할 구문>}// 조건식이 true 일 시, 무한 반복 repeat ~ while 구문다른 언어의 do ~ while 과 같은 구문이다. 1234repeat { <실행할 구문>}while <조건식> 조건문 if guard switch if 구문다른 언어에서의 if와 동일하게 사용된다. 123if <조건식> { <실행할 구문>} if ~ else if 중첩 if ~ else if ~ else guard 구문표현식의 결과가 참인지 거짓인지에 따라 구문의 실행 여부를 결정한다.if와의 차이점은 참일 때의 실행 구문이 없다는 것이다. 결국 false일 때 블록 안쪽을 실행한다. 123guard <조건식 또는 표현식> else { <조건식 또는 표현식의 결과가 false일 때 실행될 코드>} #available 구문12345if #available(<플랫폼이름 버전>, <...>, <*>) { <해당 버전에서 사용할 수 있는 API 구문>} else { <API를 사용할 수 없는 환경에 대한 처리>} 호출 연산자()를 통해 플랫폼 이름과 버전 등의 인자값을 입력할 수 있다.API 버전에 따라 처리하는 구문을 구별해준다. 사용할 만한 플랫폼 아이폰, 아이패드 등 터치 기반 스마트 기기에 사용되는 iOS 맥 컴퓨터에 사용되는 OSX 애플 시계에 사용되는 watchOS 애플 TV에 사용되는 tvOS 12345if #available (iOS 9, OSX 10.10, watchOS 1, *) { // iOS 9용 API 구문 또는 OS X 10.10용 API 구문, watchOS 1용 API 구문} else { // API를 사용하지 못했을 때에 대한 실패 처리} switch 구문12345678switch <비교 대상> { case <비교 패턴1> : <비교 패턴1이 일치했을 때 실행할 구문> case <비교 패턴2>, <비교 패턴3> : <비교 패턴2 또는 3이 일치했을 때 실행할 구문> default : <어느 비교 패턴과도 일치하지 않았을 때>} 제어 전달문한 부분에서 다른 부분으로 제어 흐름을 전달하여 코드 실행 순서를 변경해준다. break, continue, fallthrough, return 등이 있다.","link":"/2017/05/31/swift-control-statement/"},{"title":"Swift 함수","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 함수와 클로저 (Closure)독립적으로 처리될 수 있는 것을 분리하여 구조화한 객체. 함수의 이점 동일 코드가 여러 번 실행될 때, 재작성이 필요가 없다. 기능 단위로 함수화하게 되면, 가독성이 좋아진다. 유지 보수 차원에서 이점이 있다. 함수의 정의func 키워드를 사용하여 함수를 정의한다. 123456789101112/*func 함수명(매개변수) -> 반환타입 { // 함수 내용}*/func funcExample(name: String, age: Int) -> String { var string = "" string += "Hello \\(name) !" return string} 함수의 호출일반적으로 함수를 호출하듯이 정의된 함수를 호출하여 실행한다. 1funcExample(name: "hwiVeloper", age: 28) 함수 호출 시 사용하는 파라미터 이름과 함수 내부에서 사용하는 파라미터 이름을 다르게 하고 싶다면 다음과 같은 방법을 사용한다. 12345func funcExample(userName name: String, userAge age: Int) { print(name)}funcExample(userName: "hwiVeloper", userAge: 28) 파라미터 이름을 _ (언더바)로 지정하면 함수 호출 시 이름을 생략할 수 있다. 1234567func funcExample(_ name: String) { print(name)}funcExample("devhwi")// 다른 언어에서 호출하는 방식과 비슷하다. 파라미터 기본값을 지정하면 호출 시 생략이 가능하다. 12345func funcExample(_ name: String, age: Int = 28) { print(name)}funcExample("devhwi") ...를 사용하면 개수가 정해지지 않은 파라미터를 받을 수 있다. 12345678func funcExample(_ numbers: Int...) -> Int { var sum = 0 for number in numbers { sum += number } return sum} 함수 중첩이 가능하다. 123456789func funcOuter(message: String) -> (String) -> String { func funcInner(name: String) -> String { return name + message } return hello}let hello = helloGenerator(message: "hello!")hello("devhwi") 눈여겨 볼 점은 funcOuter 의 return type이 (String) -> String이라는 점이다. funcOuter는 funcInner의 String 을 받아서 String으로 return 하는 함수라는 것이다. 같은 예시지만 중첩 내부에서 다수의 파라미터를 받아서 처리하는 경우이다. 1234567func funcOuter(message: String) -> (String, String) -> String { func funcInner(firstName: String, lastName: String) -> String { return lastName + firstName + message } return funcInner} 기존의 (String) -> String에서 (String, String) -> String으로 변화했다. 두 파라미터를 받아 최종적으로 하나로 처리한다는 뜻이다. 함수의 튜플 반환함수는 여러 개의 값을 반환할 수 있다. 12345678910func getIndvInfo() -> (Int, String) { let height = 177 let name = "hwiVeloper" return (height, name)}var info = getIndvInfo()info.0 // 177info.1 // "hwiVeloper" 클로저 (Closure)함수를 좀 더 간결하게 짤 수 있게끔 도와주는 개념이다. 클로저는 { }로 감싸여진 실행 가능한 코드블럭 이다. 위에서 살펴보았던 함수 중첩을 클로저를 사용하여 재구성한다면, 12345func funcOuter(message: String) -> (String, String) -> String { return { (firstName: String, lastName: String) -> String in return lastName + firstName + message }} 일반적인 함수와는 다르게 함수명이 존재하지 않는다. 허나, 함수와 기능은 동일하게 수행이 가능하다. 파라미터를 받아 return 값을 뱉어낼 수 있다는 점에서 크게 다를 건 없지만, 모양이 다르다. 결국 클로저는 이름 없는 함수 라고 볼 수 있고, 함수는 이름 없는 클로저 라고 볼 수 있다. 클로저는 중괄호( { } )로 감싸져 있고, 파라미터를 괄호로 감싸서 정의한다. 함수와 동일하게 ->로 return type을 정의할 수 있다. in 키워드는 파라미터, return type과 실제 클로저 코드를 분리하고 있다. 단순히 비주얼적으로 본다면 이걸 왜 쓰나 하는 생각이 들 수 있다. 사실 클로저의 장점은 간결함 과 유연함 이다. 이제, 생략 가능한 요소들을 제거해보자. 12345func funcOuter(message: String) -> (String, String) -> String { return { firstName, lastName in return lastName + firstName + message }} 매우 간결해졌다. 이미 funcOuter에서 정의한 중첩된 내부 함수의 타입이 선언이 되었으므로, 내부에서는 다시 한 번 타입을 정의해 줄 필요가 없는 것이다. return type 역시 마찬가지로 funcOuter에서 정의해주었기 때문에 생략된 것이다. swift 클로저를 알게 되면서 더욱 놀라웠던 점은 여기서 더 생략이 가능하다는 것이다. 위 코드에서 더 생략한 결과이다. 12345func funcOuter(message: String) -> (String, String) -> String { return { return $1 + $0 + message }} 이제서야 클로저를 쓰는 의미가 보인다. 타이핑해야 할 코드의 양이 줄어들었다. 클로저는 또한 변수처럼 정의가 가능하다. 123let funcOuter: (String, String) -> String = { $1 + $0 + ", hello!" }funcOuter("hwi", "developer") 옵셔널로도 정의가 가능하다. 123let funcOuter: ((String, String) -> String)?funcOuter?("hwi", "dev") 클로저를 변수로 정의하고 함수에서 return이 가능하듯이, 파라미터로 역시 받을 수 있다. 1234567891011121314151617181920func closureExample(number: Int, using block: Int -> Int) -> Int { return block(number)}closureExample(number: 10, using: { (number: Int) -> Int in return number * 2})// 생략을 해보자.closureExample(number: 10, using: { $0 * 2})// 함수의 마지막 파라미터가 클로저라면 괄호와 파라미터 이름까지 생략 가능하다.closureExample(number: 10) { $0 * 2}// 이게 근데 유지보수 측면에서 다소 불편할 가능성이 있다고 한다. 클로저 활용하기 sort() filter() map() reduce() 1234567891011121314let numbers = [1, 3, 2, 6, 7, 5, 8, 4]let sortedNumbers = numbers.sort { $0 < $1 }print(sortedNumbers) // [1, 2, 3, 4, 5, 6, 7, 8]let evens = numbers.filter { $0 % 2 == 0 }print(evens) // [2, 6, 8, 4]let arr1 = [1, 3, 6, 2, 7, 9]let arr2 = arr1.map { $0 * 2 } // [2, 6, 12, 4, 14, 18]arr1.reduce(0) { $0 + $1 } // 28arr1.reduce(0, +) // 28","link":"/2017/05/31/swift-function/"},{"title":"Swift 옵셔널(optional)","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 옵셔널 (잠재적 오류를 다루다)옵셔널(Optional)은 스위프트에 도입된 새로운 개념이다. 프로그램의 안정성을 높이기 위해 사용되는 것이다.성공적인 결과를 보장하지 못할 경우 에 옵셔널 타입이라는 객체를 감싼 후 반환하는 개념이다. (Optional Wrapping) 1234Int(바꿀 문자열)let num = Int("123")// 이 경우는 변환이 가능하다. 그러나 숫자로 바꿀 수 없는 경우는 퍽 난감하다. 1let num = Int("Swift") 다른 언어와 달리 스위프트는 앱이 중간에 꺼져버리거나 다른 예기치 못한 오류를 범할 수 있기 때문에 오류를 지양하고 어떠한 값이든 return하려고 노력한다. 그러나 0도, 다른 어떤 값도 return하기는 다소 부담이 있다. 그렇기 때문에 Null, 스위프트에서는 nil을 반환한다. 값이 없음을 의미한다. 1let num = Int("Swift") // nil을 반환한다. 단, 안정성 보장을 위해 nil의 사용에 제약을 걸어두었다. nil은 자료형을 가질 수 없기 때문에 문자열이나 숫자에 억지로 대입할 순 없다. 결국 옵셔널이 가질 수 있는 값은 두 가지이다. nil이 아닌 정상적인 값 nil 알아서 해주니까 편하다고 모든 값을 옵셔널 처리하면 또 다른 문제가 생기기 때문에 필요한 곳에만 사용한다. nil에 대한 처리를 일일이 해줘야 하는 복잡함이 생긴다. 옵셔널 타입의 선언과 정의12345678910111213141516171819202122// 옵셔널 Int 타입var optInt : Int?// 옵셔널 String 타입var optStr : String?// 옵셔널 Double 타입var optDouble : Double?// 옵셔널 Array 타입var optArr : [String]?// 옵셔널 Dictionary 타입var optDic1 : Dictionary<String, String>?var optDic2 : [String : String]?// 옵셔널 Class 타입var optClass : AnyObject?/** * 선언한 후에는 자유롭게 값을 대입할 수 있다. */ 주의할 점 옵셔널 타입은 결합 연산 또는 더하기 연산이 가능한 데이터 타입이 아니다. Int?와 Int는 서로 다른 타입이므로 연산이 불가능하다. 옵셔널 사용하기옵셔널 값을 사용하기 위해서는 옵셔널을 해제하는 것이 우선이다. 해제 방식은 명시적 해제와 묵시적 해제로 나뉜다. 명시적 해제 강제적 해제 비강제적 해제 묵시적 해제 컴파일러에 의한 자동 해제 ! 연산자를 이용한 자동 해제 옵셔널 강제 해제옵셔널 타입의 변수나 상수 뒤에 !만 붙이면 된다. 12Int("123")! + Int("123")! // 246Int("123")! + 30 // 153 그렇다면 옵셔널 결과가 nil인데도 불구하고 ! 연산자를 붙인다면? 이건 좀 말이 안된다. 그 경우를 대비하여 값이 nil인지 체크 후에 다음 로직을 실행해야 한다. 다소 재밌는 부분은, 옵셔널 뒤에 비교 연산자 != 를 사용할 시에, 의무적으로 공백 한 칸이 있어야 오류가 나지 않는다. 12intFromstr != nil // GoodintFromstr!=nil // Bad 딕셔너리의 옵셔널 역시 ! 연산자를 이용하여 해제한다. 옵셔널 바인딩비강제 해제 구문으로 바꾸어 작성 역시 가능하다. 비강제 해제 구문은 if 구문 내에서 조건식 대신 옵셔널 타입의 값을 일반 타입의 변수나 상수에 할당하는 구문을 사용하는 방식으로, 옵셔널 바인딩(Optional Binding)이라고 한다. 옵셔널 바인딩은 일반 상수에 옵셔널 값을 대입하여 결과가 true/false를 반환하도록 만드는데, 이를 체크하는 방식이다. 123456var str = "Swift"if let intFromStr = Int(str) { // 이 부분이 중요하다. print("값이 변환되었다. 변환된 값은 \\(intFromStr)")} else { print("변환실패")} guard 구문을 이용하여 옵셔널 바인딩을 구현할 수 있다. guard 구문은 메서드에서 사용 가능하여 메서드를 정의 후 구문을 작성한다. 앞으로 앱의 대부분이 메서드로 이루어지기 때문에 이는 눈여겨 볼 필요가 있다. 12345678func intStr(str:String) { guard let intFromStr = Int(str) else { print("값 변환 실패!") return } print("값이 변환되었다. 변환된 값은 \\(intFromStr)")} 컴파일러에 의한 옵셔널 자동 해제컴파일러에서 자동으로 옵셔널을 해제하는 경우도 존재한다. 1234567let optInt = Int("123")if(optInt == 123) { print("optInt == 123")} else { print("optInt != 123")} else 영역이 실행될 것으로 예상되지만, 실제로 optInt == 123 이 print된다. 앞서 살펴본 것과 딴 얘기다. 굳이 강제로 해제하지 않아도 무방할 경우가 존재한다. 비교 연산자를 사용할 때가 그 경우이다. 123456let tempInt = Int("123")tempInt == 123 //truetempInt == Optional(123) //truetempInt! == 123 //truetempInt! == Optional(123) //true 옵셔널의 묵시적 해제앞서 굳이 ! 연산자를 사용하지 않고 해제하는 경우를 보았다.(컴파일러가 해결해 주는 경우였다.) 이와 비슷하지만, 묵시적 해제는 옵셔널 변수를 사용하는 모든 경우에 적용할 수 있고, 옵셔널 변수의 타입을 선언할 때 묵시적 해제를 미리 선언해 주어야 한다는 차이점이 있다. 12345// 묵시적 옵셔널 선언var str : String! = "Swift"print(str)// 결과 : Swift 타입 어노테이션 뒤에 연산자 하나로 결과가 달라진다. 묵시적 옵셔널을 사용하면 옵셔널 타입 변수의 연산을 처리할 수 있다. 12345var value01 : Int? = 10value01 + 5 // Errorvar value02 : Int! = 10value02 + 5 // 15 일반적으로는 옵셔널 타입의 연산은 오류이지만, 묵시적 해제를 선언한 옵셔널 타입은 아니다. 묵시적 옵셔널 해제를 사용하는 경우는 ‘형식상 옵셔널로 정의해야 하지만, 실제로 사용할 때는 절대 nil 값이 대입될 가능성이 없을 변수에.’ 묵시적 옵셔널은 클래스나 구조체에서다. 주로 멤버 변수를 정의할 때 선언과 초기화를 분리시켜야 하는 경우에 대항한다.(아직은 자세히 알 필요는 없다.) 옵셔널은 까다롭고 귀찮은 개념일 수도 있지만, 옵셔널의 강점은 안전성과 코드 간결성이다. 다음 예시는 Objective-C와 swift의 옵셔널 사용에 대한 비교이다. 123456/* Objective-C */if (myDelegate != nil) { if([myDelegate respondsToSelector:@selector(scrollViewDidScroll:)] { [myDelegate scrollViewDidScroll:myScrollView]; }} 12/* Swift */myDelegate?.scrollViewDidScroll?(myScrollView)","link":"/2017/05/31/swift-optional/"},{"title":"Swift 연산자","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. Swift 기본 문법연산자산술 연산자단항 연산자 : -이항 연산자 : + , - , * , / , % 비교 연산자크기를 비교하는 연산자 <, >, <=, >=, ==, != 논리 연산자!(NOT), &&(AND), ||(OR) 범위 연산자닫힌 범위 연산자 : 주어진 피연산자 a, b를 포함하는 범위를 나타낸다. 121...5 // 1, 2, 3, 4, 를 나타낸다. 반 닫힌 범위 연산자 : 주어진 피연산자 a, b 중 a만 포함된다. 121...< 5 // 1, 2, 3, 41 >...5 // error. 사용할 수 없다. 오직 뒤에 위치한 피연산자만 가능 범위연산자는 주로 반복문 for 등에 사용하기 좋은 연산자이다. 대입 연산자값을 변수에 대입할 때 사용한다. 좌변 객체에 우변 값을 대입한다. 증감 연산자++, – 두 연산자가 있다. 변수에 1을 증가, 감소시킨다. 3버전에서는 증감 연산자가 삭제되었다","link":"/2017/05/31/swift-operators/"},{"title":"Swift 구조체와 클래스","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 클래스와 구조체구조체와 클래스는 하나의 큰 코드 블록이다. 안에 변수 또는 상수를 넣어 값을 정의하거나, 함수를 넣어서 기능을 정의할 수도 있다. 스위프트는 객체지향 언어이기 때문에 구조체와 클래스는 문법 상 중요한 요소 중 하나이다. 프로퍼티(Properties) : 구조체와 클래스 내부의 변수와 상수 메서드(Method) : 구조체와 클래스 내부에서 정의된 함수 멤버(Member) : 프로퍼티와 메소드를 합하여 구조체나 클래스의 멤버라고 한다. 구조체 vs 클래스 공통점 프로퍼티 메소드 서브스크립트 : 속성값에 접근할 수 있는 방법을 제공 초기화 블록 : 객체를 원하는 초기 상태로 설정 확장 : 객체에 함수적 기능을 추가하는 확장(extends) 구문 사용 프로토콜 : 특정 형식의 함수적 표준을 제공 클래스만 할 수 있는 기능 상속 : 클래스의 특성을 다른 클래스에게 물려줄 수 있다. 타입 캐스팅 : 실행 시 컴파일러가 클래스 인스턴스의 타입을 미리 파악하고 검사할 수 있다. 소멸화 구문 : 인스턴스가 소멸되기 직전에 처리해야 할 구문을 미리 등록할 수 있다. 참조에 의한 전달 : 클래스 인스턴스가 전달될 때에는 참조 형식으로 제공되며, 이 때 참조가 가능한 개수는 제약이 없다. 기본 개념정의구조체와 클래스의 이름은 대문자로 시작한다. 명명 규칙에 따르도록 하자. 123456789// 구조체struct 구조체 이름 { // 정의 내용}// 클래스class 클래스 이름 { // 정의 내용} 메소드와 프로퍼티선언된 값에 따라 타입 선언이 가능하다. 1234567891011121314struct Resolution { var width = 0 var height = 0 func desc() -> String { return "Resolution 구조체" }}class VideoMode { var interlaced = false var frameRate = 0.0 var name : String?} 인스턴스클래스와 구조체를 생성한 후 여러 개를 찍어내듯이 만들고 할당할 수 있다. 이 찍어낸 복사본과 같은 개념을 인스턴스라고 한다. 같은 건물의 설계도를 가지고 여러 개의 건물을 세우는 것과 유사한 방식이라고 보면 되겠다. 123let g = guJoChae()let c = makeClass() . (dot)을 이용하여클래스와 구조체 내의 프로퍼티에 접근할 수 있다. 예를 들어 g.name, c.width와 같이 말이다. 초기화구조체나 클래스 뒤에 빈괄호를 붙이면 기본적인 인스턴스가 만들어진다. 때로는 파라미터를 괄호 속에 넣어주기도 하는데, 이를 초기화(Initialize)하기 위해 반드시 필요한 값들입니다. 구조체나 클래스를 초기화할 때는 매개변수의 값 역시 초기화가 필요하다. 1let initTest = testMethod(a: 1, b: 2) 구조체의 값 전달 방식: 복사에 의한 전달구조체와 클래스의 차이는 값 전달 방식이다. 구조체는 인스턴스를 생성한 후 이를 변수나 상수에 할당하거나 함수의 인자값으로 전달할 때 값을 복사하여 전달하는 방식이다. 이를 값 타입(Value Type) , 또는 복사에 의한 전달 이라고 한다. 앞서 자료형은 모두 복사를 통해 값이 전달된다고 언급한 적이 있는데, 이는 자료형 자체가 실제로는 구조체로 구현되었기 때문이다. 구조체 인스턴스는 변수에 대입 시, 복사한 값으로 대입되는 것이다. 그러므로 기존 인스턴스와 대입된 인스턴스는 독립적이다. 한 쪽에서 변경이 되어도 다른 쪽에 영향을 주지 않는다. 클래스의 값 전달 방식 : 참조에 의한 전달메모리 주소를 사용하여 객체 자체를 전달한다. 따로 주소값을 전달할 필요 없이, 객체 주소값만 넘긴다면, 자유롭게 사용이 가능하다. 클래스 초기화와 동시에 프로퍼티를 사용하는 것이 가능하다. 인스턴스 비교 연산자 12// === : 동일 인스턴스인지 비교// !== : 다른 인스턴스인지 비교 일반적으로 다음 조건에 하나 이상 해당한다면 구조체 사용을 권장한다. 서로 연관된 몇 개의 기본 데이터 타입들을 캡슐화하여 묶는 것이 목적일 때 캡슐화된 데이터에 상속이 필요하지 않을 때 캡슐화된 데이터를 전달하거나 할당하는 과정에서 참조 방식보다는 값이 복사되는 것이 합리적일 때 캡슐화된 원본 데이터를 보존해야 할 때 프로퍼티(Property)클래스와 구조체를 구성하는 중요 요소이다. 프로퍼티는 값을 저장하기 위한 목적으로 클래스와 구조체 내에서 정의된 변수나 상수를 의미한다. 정확히는 값을 제공 한다. 프로퍼티에는 저장 프로퍼티와 연산 프로퍼티가 있다. 저장 프로퍼티 입력된 값을 저장, 저장된 값을 제공함 상수 및 변수를 사용하여 정의가 가능함 클래스와 구조체에서 사용이 가능하지만, 열거형에서는 사용할 수 없음 연산 프로퍼티 특정 연산을 통해 값을 만들어 제공하는 역할 변수만 사용해서 정의 가능함 클래스, 구조체, 열거형 모두에서 사용 가능함 저장 프로퍼티클래스 내에서 선언된 변수나 상수를 부르는 이름이다. 변수는 속성 변수이고, 상수는 속성 상수라고 한다. 저장 프로퍼티는 일반적인 변수, 상수를 초기화하듯이 초기화 값을 지정할 수 있다. 단, 필수는 아니다. 초기값을 할당하지 않으면 유의해야 할 점들이 있다. 초기값이 할당되지 않은 저장 프로퍼티는 반드시 옵셔널 타입으로 선언해야 한다. 클래스의 프로퍼티에 값이 비어있다면 인스턴스를 생성할 때 무조건 nil로 초기화한다. 물론 처음부터 값 할당을 하면 옵셔널로 할 필요가 없다. 초기값을 주지 않으면서 옵셔널 타입으로 선언하지 않을 수 있는 방법도 존재한다. 초기화구문에서 프로퍼티의 값을 초기화하는 것이다. 클래스 프로퍼티는 인스턴스를 생성할 때 초기화하기 때문에 프로퍼티 초기값은 인스턴스를 생성하기 전까지만 할당하면 된다. 초기화 구문에서 프로퍼티 값 할당이 가능하다면 옵셔널로 선언하지 않아도 된다. 1234class User { var name : String // 클래스 프로퍼티의 초기값 설정이 안되어서 Error} 12345678/* 올바른 예시 */class User { var name : String init() { self.name = "" }} 12345class User {// 옵셔널의 예시 var name : String? // var name : String! 도 가능.} 1234class User { // 아예 초기화하는 방법 var name : String = ""} 저장 프로퍼티의 분류 var 키워드로 정의되느 변수형 저장 프로퍼티(멤버 변수) let 키워드로 정의되는 상수형 저장 프로퍼티(멤버 상수) 1234567891011// 구조체의 저장 프로퍼티struct guJoChae1 { var intValue1 : Int let intValue2 : Int}struct guJoChae2 { let intValue3 : Int var intValue4 : Int} 지연 저장 프로퍼티 저장 프로퍼티는 클래스 인스턴스가 생성될 때 함께 초기화되지만, lazy키워드가 있다면 예외가 된다. 키워드 이름처럼 초기화를 지연시키는 역할을 한다. 프로퍼티가 호출되는 순간에 초기화된다. 1234567891011121314class OnCreate { init() { print("OnCreate") }}class LazyTest { var base = 0 lazy var late = OnCreate() init() { print("Lazy Last") }} 클로저를 이용한 저장 프로퍼티 초기화 12345// 프로퍼티 참조 시에 최초 한 번만 실행한다.let/var 프로퍼티명: 타입 = { 정의 내용 return 리턴값}() 연산 프로퍼티저장 프로퍼티와는 다르게 다른 프로퍼티의 값을 연산 처리하여 간접적으로 값을 제공한다. 프로퍼티의 값을 참조하기 위해 구문이 get구문이다. set 구문을 추가할 수 있다. 값을 할당하거나 변경할 때 실행된다. set 구문은 필요에 따라 생략이 가능하다. 123456789101112class/struct/enum 객체명 { var 프로퍼티명 : 타입 { get { //...... return 리턴값 } set ( 매개변수 ) { //...... } }} 다음은 진짜 예시이다. 12345678910111213141516171819202122232425import Foundation// 나이 계산struct UserInfo { var birth : Int! var thisYear : Int { get { let df = DateFormatter() df.dateFormat = "yyyy" return Int(df.string(from: Date()))! } } var age : Int { get { return (self.thisYear - self.birth) + 1 } }}let info = UserInfo(birth: 1990)print(info.age)// 28 프로퍼티 옵저버특정 프로퍼티를 관찰하다가 값 등이 변경되면 이에 반응한다. 직접 변경, 자동 변경 모두 반응하여 호출된다. 동일한 값이 재할당되어도 마찬가지이다. willSet : 프로퍼티의 값이 변경되기 직전 호출되는 옵저버 didSet : 프퍼티의 값이 변경된 직후 호출되는 옵저버 1234567// willSet 옵저버var <프로퍼티명> : <타입> [ = <초기값> ] { willSet [ (<인자명>) ] { <프로퍼티 값이 변경되기 전에 실행할 내용> }} 1234567// didSetvar <프로퍼티명> : <타입> [ = <초기값> ] { didSet [ (<인자명>) ] { <프로퍼티 값이 변경된 후에 실행할 내용> }} 타입 프로퍼티인스턴스에 관련된 값이 아니라 클래스나 구조체, 또는 열거형과 같은 객체 자체에 관련된 값을 다루는 때도 있다. 이 때는 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장하게 되며 이를 타입 프로퍼티라고 한다. 저장된 값은 모든 인스턴스가 공통적으로 사용할 수 있다. 타입 프로퍼티는 인스턴스가 아무리 많아도 모든 인스턴스가 하나의 값을 공용으로 사용한다. static키워드가 핵심이다. 다른 언어에서와 같이 어느 곳에서나 접근이 가능하다. 1static let/var 프로퍼티명 = 초기값 12345678class let/var 프로퍼티명 : 타입 { get { return 반환값 } set { // set 내용 }} 메소드함수의 일종이다. 구조체, 클래스 등에서 함수가 선언되면 메소드라고 지칭한다. 허나 함수와는 차이가 있다. 함수는 독립적으로 기능을 구현하지만, 메소드는 객체에서 정의된 다른 메소드와 함께 함수적인 기능을 수행한다. 메소드는 인스턴스 메소드 와 타입 메소드 로 구분된다. 인스턴스 메소드 : 객체의 인스턴스를 생성해야 사용할 수 있는 메소드. 타입 메소드 : 객체의 인스턴스를 생성하지 않아도 사용 가능한 메소드. 인자값이 있는 메소드를 호출할 때에는 함수와 동일하다. 호출 시 인자값 앞에 인자 레이블을 붙여준다. 12let temp = tempMethod()temp.isParameter(a : 5) 구조체나 열거형의 인스턴스 메소드 내부에서 프로퍼티의 값을 수정할 때는 반드시 앞에 mutating 키워드를 추가해야 한다. 이 키워드를 통해 내부 프로퍼티 값을 수정하는 메소드라는 것을 컴파일러에게 지시한다. 당연한 말이지만, 인스턴스가 상수라면 mutating 키워드는 필요가 없다. 1234567891011121314struct Point { var x = 0.0, y = 0.0 mutating func moveByX(x deltaX: Double, y deltaY: Double) { self.x += deltaX self.y += deltaY }}var point = Point(x: 10.5, y: 12.0)point.moveByX(x: 3.0, y: 4.5)print("New Point (\\(point.x), \\(point.y)))// 클래스 인스턴스 상속 (Inheritance)클래스와 구조체가 구분되는 특성이다. 하나의 클래스가 다른 클래스에게 특성을 물려줄 수가 있다. 자식 클래스가 부모 클래스의 속성과 메소드를 물려받게 되고, 사용할 수 있다. 프로퍼티와 메소드를 물려준 클래스는 부모 클래스, 상위 클래스, 슈퍼 클래스, 기본 클래스 프로퍼티와 메소드를 물려준 클래스는 자식 클래스, 하위 클래스, 슈퍼 클래스, 파생 클래스 서브클래싱기존 클래스를 기반으로 새로운 클래스를 작성하는 것을 의미한다. 123class 클래스명 : 부모클래스 { // 추가 구현 내용} 뒤에 나오는 코코아 터치 프레임워크의 클래스 정의 구문에서 콜론 : 다음에 여러 개의 클래스가 나열되는데, 가장 첫 번째는 상속, 나머지는 자바의 implements 키워드를 의미한다. 오버라이딩재정의 라는 의미로, 부모 클래스에서 상속받은 내용을 덮어쓴다. 재정의한 내용은 서브클래싱한 하위 클래스에만 적용된다. 부모 클래스까지의 영향이 없다는 의미이다. 스위프트에서는 오버라이딩하는 메소드나 프로퍼티 앞에 override 키워드를 붙인다. 없다면 오류로 간주한다. 만약 오버라이딩 할 수 있는 메소드나 프로퍼티가 아닌 경우는 컴파일러가 override 키워드를 오류로 간주한다. 상위 클래스에서 선언된 것만 오버라이딩이 가능하다. 프로퍼티를 오버라이딩 할 때 허용되는 것과 허용되지 않는 사항이 존재한다. 허용되는 것 저장 프로퍼티를 get, set 구문이 모두 있는 연산 프로퍼티로 오버라이딩 하는 것 get, set 구문이 모두 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩 하는 것. get 구문만 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩하는 것 get 구문만 제공되는 연산 프로퍼티를 get 구문만 제공되는 연산 프로퍼티로 오버라이딩 하는 것 허용되지 않는 것 저장 프로퍼티를 저장 프로퍼티로 오버라이딩하는 것 get, set 구문과 관계없이 연산 프로퍼티를 저장 프로퍼티로 오버라이딩하는 것 저장 프로퍼티를 get 구문만 제공되는 연산 프로퍼티(=readonly)로 오버라이딩하는 것 get, set 구문을 모두 제공하는 연산 프로퍼티를 get 구문만 제공되는 연산 프로퍼티로 오버라이딩하는 것 복잡하지만 단순하다. 상위 클래스의 기능을 하위 클래스가 확장하거나 변경은 할 수 있지만 제한하는 방식이 되어서는 안된다는 것이다. 12345678910111213141516171819202122class LongBoard : SkateBoard { var deck = 0 var wheel = 0 override var currentSpeed : Double { get { return Double(self.wheel * 50) } set { // nothing } } override var description : String { get { return "LongBoard : wheel\\(self.wheel), so currentSpeed=\\(self.currentSpeed)" } set { print("New Value is \\(newValue)") } }} 오버로딩 : 같은 메소드이름이지만 매개변수 변화로 새로운 메소드를 만들어 적재할 수 있도록 한다. 오버로딩은 다음과 같다. 123456func currentSpeed()func currentSpeed(param : Int)func currentSpeed(param : String)func currentSpeed(param : Double) -> Stringfunc currentSpeed(param : Double, second : String)func currentSpeed(param : Double, secondParam : String) 하위 클래스에서 오버라이딩을 차단하기 위해서는 final 키워드를 사용한다. 초기화 구문구조체나 클래스는 항상 인스턴스를 생성해서 할당 후 사용해야 하는 데, 이를 초기화라 한다. 이 과정에서 저장 프로퍼티는 생성 과정에서 초기화되어야 한다. 그 후에 구조체나 클래스의 초기화가 가능하다. Resolution(), Video(), Car() 등은 초기화 구문이다. 저장 프로퍼티를 초기화하는 구조체/클래스의 초기화다. Board(id: 1, category: 3) 초기화를 할 때 주의할 점은 미리 정의된 형태로 해야 한다는 것이다. init 메소드 (초기화)1234init(매개변수 : 타입 ... ) { // 매개변수 초기화 // 인스턴스 생성 후 처리할 로직} 일반적인 메소드와 비슷한 모양이지만 init 메소드만의 특징이 있다. 초기화 메소드의 이름은 init으로 통일한다. 매개변수의 개수, 이름, 타입은 임의로 정의할 수 있다. 매개변수의 이름과 개수, 타입이 서로 다른 여러 개의 초기화 메소드를 정의할 수 있다. 정의된 초기화 메소드는 직접 호출되지만, 대부분 인스턴스 생성 시 간접 호출된다. 여러 모양의 초기화 모양을 두고, 가까운 모양으로 호출된다. 초기화 구문의 오버라이딩초기화 구문 역시 자식 클래스에서 오버라이딩이 가능하다. 동일하게 override 키워드를 붙여야 한다. 상위 클래스에서 선언된 적이 없는 초기화 형식이라면 붙이면 안 된다. 123456789class Base {}class ExBase : Base { override init() { // some codes }} 메소드와는 달리 초기화 구문에서 오버라이딩은 더 이상 부모 클래스에서 정의한 초기화 구문이 실행되지 않는 오류를 일으킬 수 있다. 부모 클래스의 기본 초기화 구문에서 프로퍼티를 초기화했다면, 자식 클래스에서 기본 초기화 구문을 오버라이딩함으로써 부모클래스 프로퍼티의 초기화가 누락된다. 이는 곧 오류를 발생시키므로, 초기화 구문을 오버라이딩할 경우 부모 클래스에서 정의된 초기화 구문을 내부적으로 호출해야 하는데, 오버라이딩된 초기화 구문 내부에 super.init 구문을 작성한다. 123456789101112class Base { var baseValue : Double init(inputValue : Double) { self.baseValue = inputValue }}class ExBase : Base { override init(inputValue: Double) { super.init(inputValue: 3.14) }}","link":"/2017/05/31/swift-struct-and-class/"},{"title":"Swift 변수와 자료형","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. Swift 기본 문법기초 문법변수의 대소문자를 구분한다. (모든 객체)12345var a = 30;var A = 30;// 함수와 메소드, 인스턴스명 첫 글자 -> 소문자// 클래스와 구조체, 프로토콜 등 객체의 첫 글자 -> 대문자 구문 끝의 세미콜론(;)은 옵션써도 되고 안 써도 된다. 인터프리터가 자동으로 끝을해석해 준다.가독성의 이유로 주로 붙이기도 한다. main() 메서드가 없다.@UIApplicationMain 어노테이션을 사용하여 시작 객체를 지정한다.@UIApplicationMain 어노테이션이 붙는 객체는 하나다. 문자열, 문자 모두 큰따옴표(“ “)를 사용한다.자바를 생각하고 쓴다면 헷갈릴 수 있다. character 타입도 큰따옴표로 표시한다. import 키워드는 라이브러리와 프레임워크 참조 용도이다.ex) import UIKit 한 줄 주석, 여러 줄 주석 모두 사용 가능하다.12345// 한 줄 주석/* * 여러 줄 주석 */ try ~ catch 와 같이 오류 처리를 지원한다.이는 2.0에서 처음 추가되어 직접 오류를 throw 할 수 있도록 하는 기능자바 등에서는 일반적이지만 스위프트에서는 처음에 이러한 것을 지원하지 않았다. 변수와 상수변수는 값이 변할 수 있는 것, 상수는 한 번 정하면 값을 바꿀 수 없다. 변수는 지정된 타입에 해당하는 값만 넣을 수 있다. 선언, 초기화12345678// 변수var year = 2017var message = "Hello, World"// 상수let birthYear = 1990let welcomeMsg = "환영합니다."// 이후에 변경을 시도하면 에러. 변수와 상수의 이름짓기 알파벳, 한글, 아라비아 숫자, 특수기호, 한자, 이미지용 바이너리 코드 사용 가능 연산자, 공백 등은 사용 불가능하지만 언더바는 사용 가능하다. 예약어, 키워드 등은 사용 가능하지만 기존 것에서 대소문자 등을 바꾸면 가능하다. 첫째자리에 숫자가 올 수 없다. 12345var Class = 1 // okayvar class = 1 // Errorvar Enum = 2 // okayvar enum = 2 // Error 자료형기본 자료형IntInteger. 부호 있는 정수값 (127 ~ -128 까지 저장이 가능하다.) 자료형 저장할 수 있는 값 범위 크기 Int8 127 ~ -128 8bit Int16 32767 ~ -32768 16bit Int32 2147483647 ~ -2147483648 32bit Int64 9223372036854775807 ~ -9223372036854775808 64bit UIntInteger와 유사하지만 부호가 없는 정수. (0 ~ 255) 음수의 범위를 양수가 가져간다고 보면 되겠다. Double, Float실수 범위이다. Float는 32bit, Double은 그 두 배인 64bit까지 표현 가능하다. Booltrue/false의 값을 가질 수 있는 자료형이다. String아마 모든 언어에서 제일 많이 쓰는 자료형일 것이다.(C 제외) Character한 개 문자를 저장하는 자료형이다. 앞서 언급했듯이 큰따옴표(“ “)를 사용한다. 타입 추론, 어노테이션1234567var age : Int // Int 타입임을 명시한다.var ageage = 28 // 초기화를 하면서 자료형을 정한다.var weight : Float = 28var hello : Character = "안" // 이와 같이 선언 가능하다. 타입이 다른 변수끼리 결합1234var format = "당신의 나이는 "var age = 28var msg = format + String(age) // 타입이 다르다면 변형 후 결합한다. 문자열 템플릿1234567var name = "휘"var year = 1990var month = 8var day = 17// 문자열 템플릿을 사용해보자.var birth = "\\(name)는 \\(year)년 \\(month)월 \\(day)일에 출생했습니다." 문자열 템플릿 안에서의 연산 역시 가능하다.","link":"/2017/05/31/swift-variables-data-type/"},{"title":"Swift 프로토콜(protocol)","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 프로토콜 : 객체의 설계도클래스나 구조체가 어떤 기준을 만족하거나 특수한 목적을 다성하기 위해 구현해야 하는 메소드와 프로퍼티의 목록이다. 자바의 interface와 비슷한 개념이다. (이렇게 이해하니 쉬운 개념으로 와닿았다.) 각 이벤트를 관리하기 위해 대리자(delegate)를 지정하여 이벤트 처리를 위임하고, 실제 이벤트 발생 시, 지정된 대리자가 콜백 메소드를 호출해주는 델리게이트 패턴(Delegate Pattern)을 많이 사용하는데, 이 때 사용되는 것이 바로 프토콜이다. 기능에 대한 명세가 적혀 있어서 프로토콜 코드를 보면 어떤 기능을 어떻게 구현해야 하는지를 알 수 있다. 프토콜은 부모 클래스는 아니다. 프로토콜은 대상 클래스 전체를 책임지지 않으며, 일부 기능의 형식을 담당한다. 프로토콜의 정의protocol 키워드를 사용한다. 1234567891011protocol protocolName { <구현해야할 프로퍼티 명세 1> <구현해야할 프로퍼티 명세 2> <구현해야할 프로퍼티 명세 3> <구현해야할 메소드 명세 1 > <구현해야할 메소드 명세 2 > <구현해야할 메소드 명세 3 >}// 구조체, 클래스, 열거형, 익스텐션을 프로토콜로 구현할 수 있다. 프로토콜 프로퍼티프로토콜에 선언되는 프로퍼티에는 초기값을 할당할 수 없다. 프로퍼티 종류 또한 구분하지 않는다. 프로퍼티의 종류, 이름, 변수/상수 구분, 타입, 읽기/쓰기 가능 여부를 정의한다. 123456789101112protocol SomePropertyProtocol { var name : String { get set } // 읽고 쓰기 가능 var descriptioin : String { get } // 읽기 가능}// 구현struct RubyMember : SomePropertyProtocol { var name = "아모개" var description : String { return "Name : \\(self.name)" }} 프로토콜 메소드1234567891011121314151617protocol SomeMethodProtocol { func run(command : String) func showResult(param : Int) -> String}// 구현struct RubyService : SomeMethodProtocol { func run(command : String) { if command == "start" { print("시작") } } func showResult(param : Int) -> String { return "Result : \\(param)" }} mutating, static 사용구조체 내 메소드가 프로퍼티를 변경하는 경우, 메소드 앞에 반드시 mutating 키워드를 붙여 이 메소드가 프로퍼티 값을 수정하는 메소드임 을 표시하도록 강제한다. 메소드가 만약 프로토콜에서 선언된 메소드라면 반드시 프로토콜에 mutating 키워드가 추가되어 있어야 한다. 클래스와 같은 참조 타입은 mutating 키워드 없이 자유롭게 메소드 내의 프로퍼티를 수정할 수 있지만, 구조체나 열거형은 프로토콜의 메소드에 mutating 키워드가 추가되어 있지 않을 경우 프로퍼티 값을 변경할 수 없다. 억지로 키워드를 붙인다면 오류가 날 것이다. 보통 mutating 키워드가 붙지 않는 경우는 다음과 같은 경위다. 구조체나 열거형 등 값 타입의 객체에서 내부 프로퍼티의 값을 변경하기 원치 않을 때 주로 클래스를 대상으로 간주하고 작성된 프로토콜일 때 프로토콜의 초기화 메소드일반적인 메소드와 비슷하게 초기화하는 것이 가능하다. 실행블록 없이 이름과 매개변수 등을 작성한다. 1234protocol initProtocol { init() init(cmd : String)} 초기화 메소드 작성 시 유의사항은 외부매개변수명까지는 완전히 일치해야 한다는 점이다. 임의로 변경 시 프로토콜을 제대로 구현하지 않은 것으로 간주된다. 그리고 클래스에서 초기화 메소드를 구현할 때는 required 키워드를 붙여야 한다. 구현되는 초기화 메소드의 이름과 매개변수명은 프로토콜의 명세에 작성된 것과 완전히 일치해야 한다. 프로토콜 명세에 선언된 초기화 메소드는 그것이 기본 제공되는 초기화 메소드여도 직접 구현해야 한다. 클래스에서 초기화 메소드를 구현할 때는 required 키워드를 붙여야 한다. 또다른 예시를 살펴보겠다. 12345678910111213141516171819202122232425//initProtocol은 위에서 보았던 프로토콜에서 참조한 것이다.struct SInit : initProtocol { var cmd : String init() { self.cmd = "start" } init(cmd : String) { self.cmd = cmd }}class CInit : initProtocol { var cmd : String required init() { self.cmd = "start" } required init(cmd : String) { self.cmd = cmd }} 타입으로서의 프로토콜때로는 프로토콜이 타입으로서의 역할을 할 때도 있다. 마치 프로토콜을 상위 클래스 타입으로 간주하여 사용하는 것과 유사하다. 상수나 변수, 그리고 프로퍼티의 타입으로 사용할 수 있음 함수, 메소드 또는 초기화 구문에서 매개변수 타입이나 반환 타입으로 프로토콜을 사용할 수 있음 배열이나 사전, 혹은 다른 컨테이너의 타입으로 사용할 수 있음 123456789101112131415161718192021222324protocol Wheel { func spin() func hold()}class Bicycle : Wheel { var moveState = false func spin() { self.pedal() } func hold() { self.pullBreak() } func pedal() { self.moveState = true } func pullBreak() { self.moveState = false }} 일반적인 경우 Bicycle 클래스는 다음과 같이 초기화된다. 12345let trans = Bicycle()trans.moveStatetrans.spin()... let trans : Wheel = Bicycle() 이 경우는 trans가 사용할 수 있는 프로퍼티와 메소드에 제한이 생긴다. trans.spin()trans.hold() 기존에 Wheel이라는 프로토콜에 존재하던 메소드만 사용이 가능하게 된다.","link":"/2017/05/31/swift-protocol/"},{"title":"Swift 열거형(enum)과 익스텐션(extension)","text":"꼼꼼한 재은씨의 Swift3 기본편을 보며 Swift 문법 내용을 정리한 내용입니다. 열거형과 익스텐션 열거형 : 문자열이나 정수를 직접 입력받아야 할 정보들을 선택할 수 있도록 만들어주어서, 값의 범위를 제한하고 무작위로 값이 입력되는 것을 방지하여 코드의 안정성을 높여주는 역할을 한다. 이를 통해 입력값의 오류를 줄이는 것이 가능하다. 익스텐션 : 기존 객체를 수정없이 기능 추가하는 것을 제공한다. 생산성을 높여주는 강력한 도구이다. 열거형열거형(Enumeration)에서 데이터는 객체 정의 시점에 함께 정의된다. 삭제나 변경이 불가능하다. 수정 방법은 단 한 가지, 구문의 직접 수정 뿐이다. 열겨형 데이터들은 타입으로 사용할 수 있고, 컴파일러가 미리 인지할 수 있다. 다음과 같은 경우는 열거형을 사용하는 것을 권장한다. 원치 않는 값이 잘못 입력되는 것을 막고 싶을 때 입력받을 값을 미리 특정할 수 있을 때 제한된 값 중에서만 선택하는 것을 유도하고 싶을 때(약간의 강제성) 성별, 국가, 지역, 직급, 색상, 방향 등이 이에 해당한다. 열거형의 정의열거형 객체 정의는 enum 키워드를 사용한다. 123456enum 열거형 이름 { // 열거형 멤버 정의 case 멤버값 1 case 멤버값 2 case ......} 열거형 이름은 소문자로 작성하지만 첫 글자만 대문자로(카멜 표기법 - CamelCasing), 그리고 단어마다 첫 글자는 대문자로 작성한다. 12345678enum Gender { case man case woman}// enum 사용법let m = Gender.manlet w = Gender.woman 열거형 사용은 위 예시 말고도 여러 가지 방법이 있다. 1234var m1 = Gender.manvar m2 : Gender = Gender.manvar m3 = .manvar m4 : Gender = .man 열거형은 switch 구문에 활용이 가능하다 ? 123456switch m1 { case Gender.man : // something case Gendar.woman : // something} 멤버와 값 분리http 코드와 같은 값은 코드만 보고 의미를 파악하기 어려운 경우가 있다. 이러한 경우는 코드를 멤버와 값으로 나누는 방법이 있다. 123456789101112enum HTTPCode : Int { case OK = 200 case NOT_MODIFY = 304 case INCORRECT_PAGE = 404 case SERVER_ERROR = 500}// 사용할 때는 rawValue를 활용한다.HTTPCode.OK.rawValue // 200HTTPCode.NOT_MODIFY.rawValue // 304HTTPCode.INCORRECT_PAGE.rawValue // 404HTTPCode.SERVER_ERROR.rawValue // 500 열거형 객체의 멤버와 값은 선언할 때 정의되지만, 사용 시에 멤버에 보조 값을 설정할 수 있다. 이를 Associated Values(연관된 값)이라고 한다. 12345678enum ImageFormat { case JPEG case PNG(Bool) case GIF(Int, Bool)}var image = ImageFormat.PNG(true) imgae = .GIF(256, false) 열거형은 클래스나 구조체처럼 내부에 연산 프로퍼티와 메소드 또한 정의가 가능하다. 인스턴스를 만들 수는 없지만, 인스턴스처럼 사용할 수 있어서 인스턴스 프로퍼티, 타입 프로퍼티, 메소드를 모두 정의할 수 있다. 12345678910111213141516171819202122232425262728293031323334enum httpCode : Int { case OK = 200 case NOT_MODIFY = 304 case INCORRECT_PAGE = 404 case SERVER_ERROR = 500 var value : String { return "httpCode is \\(self.rawValue)" } func getDescription() -> String { switch self { case .OK : return "\\(self.rawValue)" case .NOT_MODIFY : return "\\(self.rawValue)" case .INCORRECT_PAGE : return "\\(self.rawValue)" case .SERVER_ERROR : return "\\(self.rawValue)" } } static func getName() -> String { return "httpCode" }}var res = httpCode.OK res = .NOT_MODIFYres.value // 304res.getDescription()httpCode.getName() 열거형의 활용열거형은 추후 나오는 코코아터치 프레임워크에서 자주 쓰이는 객체이다. 메소드 호출 옵션이나 스타일을 설정할 때 열거형으로 미리 정의해 둔 멤버들을 속성값으로 사용하는 경우가 많다. 12345enum UIImagePickerControllerSourceType : Int { case photoLibrary case camera case savedPhotosAlbum} 위 소스는 이미지 피커 컨트롤러에서 이미지를 어디서 가져올 것인가를 결정하는 열거형 객체이다. 만약에 이것을 열거형이 아닌 다른 것으로 정의한다면 외부에서 수정이 가능할 것이다. 가변적인 성격을 띄는 것이 아니므로 열거형으로 해야 옳다. 이처럼 지정된 값만 허용되게 할 때는 열거형 객체를 이용하는 것이다. 적절한 경우에 열거형을 적극 활용하도록 하자. 익스텐션익스텐션은 확장 구문이라고 번역된다. 클래스나 구조체, 열거형 등의 객체에 새로운 기능을 추가하여 확장하는 구문이다. 이미 존재하는 객체에 추가적인 요소를 가미하는 것이 특징이고, 거의 제약 없이 가능하여 꽤 강력한 것이라 볼 수 있다. (오브젝티브-C의 ‘카테고리’와 비슷하다고 한다.) 익스텐션은 다음과 같은 것을 구현할 수 있다. 연산 프로퍼티를 추가할 수 있다. 새 메소드 정의가 가능하다. 새로운 초기화 구문을 추가할 수 있다. 기존 객체를 수정하지 않고 프로토콜을 구현할 수 있다. 익스텐션 extension 키워드를 사용한다. 123extension <확장할 기존 객체명> { // 추가 기능에 대한 구현 코드 작성} 익스텐션과 연산 프로퍼티프로퍼티를 기존 객체에 추가 가능하지만, 연산 프로퍼티로 제한이 된다. 저장 프로퍼티는 불가능하다. 대신 인스턴스 프로퍼티든 타입 프로퍼티든 연산 프로퍼티라면 모두 추가 가능하다. 123456789extension Double { var km : Double { return self * 1_000.0 } var m : Double { return self } var cm : Double { return self / 100.0 } var mm : Double { return self / 1_000.0 } var description : String { return "\\(self)km는 \\(self.km)m, \\(self)cm는 \\(self.cm)m, \\(self)mm는 \\self.mm)mm입니다." }} 위 예시는 Double 구조체 확장 구문이다. 숫자의 1_000.0의 언더바는 자릿수를 구분해주는 구분자이다. 숫자 값에는 영향이 없다. 익스텐션과 메소드기존 객체에 새로운 인스턴스 메소드나 타입 메소드를 정의할 수 있다. 오버로딩 특성을 이용해서 새로운 메소드를 저으이할 수 있고, 매개변수명을 바꿔서 새로운 메소드를 작성할 수 있다. 그러나 기존 객체에서 사용된 같은 메소드를 익스텐션에서 재정의 하는 것은 허용되지 않는다. 1234567extension Int { func repeatRun(task : () -> Void) { for _ in 0 ..< self { task() } }} 인스턴스 메소드는 익스텐션에서도 mutating 키워드를 사용하여 인스턴스 자신을 수정하도록 허용할 수 있다. 구조체나 열거형에서 정의된 메소드가 자신의 인스턴스를 수정하거나 프로퍼티를 변경해야할 때 mutating 키워드를 사용하는데, 익스텐션이 구조체, 열거형을 확장의 대상으로 삼았을 때 사용한다. 12345678extension Innt { mutating func circle() { self = self * self * 3.14 }}var radius = 1value.circle() // 3.14 익스텐션은 특히 직접 수정할 수없는 객체나 라이브러리 등을 수정할 자주 사용된다. 익스텐션을 적지적소에 사용하는 것은 권장되지만, 남용은 금물이다.","link":"/2017/05/31/swift-enumeration-extension/"}],"tags":[{"name":"2018","slug":"2018","link":"/tags/2018/"},{"name":"memoir","slug":"memoir","link":"/tags/memoir/"},{"name":"developer","slug":"developer","link":"/tags/developer/"},{"name":"ide","slug":"ide","link":"/tags/ide/"},{"name":"atom","slug":"atom","link":"/tags/atom/"},{"name":"devtools","slug":"devtools","link":"/tags/devtools/"},{"name":"apm","slug":"apm","link":"/tags/apm/"},{"name":"multi-cursor","slug":"multi-cursor","link":"/tags/multi-cursor/"},{"name":"개발자","slug":"개발자","link":"/tags/%EA%B0%9C%EB%B0%9C%EC%9E%90/"},{"name":"이직","slug":"이직","link":"/tags/%EC%9D%B4%EC%A7%81/"},{"name":"pigments","slug":"pigments","link":"/tags/pigments/"},{"name":"raspberrypi","slug":"raspberrypi","link":"/tags/raspberrypi/"},{"name":"raspbian","slug":"raspbian","link":"/tags/raspbian/"},{"name":"ubuntu","slug":"ubuntu","link":"/tags/ubuntu/"},{"name":"logrotate","slug":"logrotate","link":"/tags/logrotate/"},{"name":"log","slug":"log","link":"/tags/log/"},{"name":"nginx","slug":"nginx","link":"/tags/nginx/"},{"name":"remote-ftp","slug":"remote-ftp","link":"/tags/remote-ftp/"},{"name":"book","slug":"book","link":"/tags/book/"},{"name":"review","slug":"review","link":"/tags/review/"},{"name":"한빛미디어","slug":"한빛미디어","link":"/tags/%ED%95%9C%EB%B9%9B%EB%AF%B8%EB%94%94%EC%96%B4/"},{"name":"java","slug":"java","link":"/tags/java/"},{"name":"stringjoiner","slug":"stringjoiner","link":"/tags/stringjoiner/"},{"name":"stringbuilder","slug":"stringbuilder","link":"/tags/stringbuilder/"},{"name":"php","slug":"php","link":"/tags/php/"},{"name":"laravel","slug":"laravel","link":"/tags/laravel/"},{"name":"composer","slug":"composer","link":"/tags/composer/"},{"name":"datasource","slug":"datasource","link":"/tags/datasource/"},{"name":"db-connection","slug":"db-connection","link":"/tags/db-connection/"},{"name":"mysql","slug":"mysql","link":"/tags/mysql/"},{"name":"installer","slug":"installer","link":"/tags/installer/"},{"name":"mvc","slug":"mvc","link":"/tags/mvc/"},{"name":"codeigniter","slug":"codeigniter","link":"/tags/codeigniter/"},{"name":"javascript","slug":"javascript","link":"/tags/javascript/"},{"name":"date","slug":"date","link":"/tags/date/"},{"name":"database","slug":"database","link":"/tags/database/"},{"name":"order by","slug":"order-by","link":"/tags/order-by/"},{"name":"automount","slug":"automount","link":"/tags/automount/"},{"name":"php7","slug":"php7","link":"/tags/php7/"},{"name":"php7-fpm","slug":"php7-fpm","link":"/tags/php7-fpm/"},{"name":"mariadb","slug":"mariadb","link":"/tags/mariadb/"},{"name":"ssh","slug":"ssh","link":"/tags/ssh/"},{"name":"ftp","slug":"ftp","link":"/tags/ftp/"},{"name":"proftpd","slug":"proftpd","link":"/tags/proftpd/"},{"name":"spring","slug":"spring","link":"/tags/spring/"},{"name":"spring-boot","slug":"spring-boot","link":"/tags/spring-boot/"},{"name":"spring-security","slug":"spring-security","link":"/tags/spring-security/"},{"name":"oauth2","slug":"oauth2","link":"/tags/oauth2/"},{"name":"transmission","slug":"transmission","link":"/tags/transmission/"},{"name":"torrent","slug":"torrent","link":"/tags/torrent/"},{"name":"facebook","slug":"facebook","link":"/tags/facebook/"},{"name":"scheduler","slug":"scheduler","link":"/tags/scheduler/"},{"name":"github","slug":"github","link":"/tags/github/"},{"name":"stomp","slug":"stomp","link":"/tags/stomp/"},{"name":"websocket","slug":"websocket","link":"/tags/websocket/"},{"name":"messaging","slug":"messaging","link":"/tags/messaging/"},{"name":"swift","slug":"swift","link":"/tags/swift/"},{"name":"iOS","slug":"iOS","link":"/tags/iOS/"},{"name":"obj-c","slug":"obj-c","link":"/tags/obj-c/"},{"name":"objective-c","slug":"objective-c","link":"/tags/objective-c/"}],"categories":[{"name":"Scribble","slug":"Scribble","link":"/categories/Scribble/"},{"name":"tools","slug":"tools","link":"/categories/tools/"},{"name":"raspberrypi","slug":"raspberrypi","link":"/categories/raspberrypi/"},{"name":"Review","slug":"Review","link":"/categories/Review/"},{"name":"java","slug":"java","link":"/categories/java/"},{"name":"php","slug":"php","link":"/categories/php/"},{"name":"Software","slug":"Software","link":"/categories/Software/"},{"name":"javascript","slug":"javascript","link":"/categories/javascript/"},{"name":"mysql","slug":"mysql","link":"/categories/mysql/"},{"name":"swift","slug":"swift","link":"/categories/swift/"}],"pages":[{"title":"Profile","text":"Ready for update….","link":"/about/index.html"}]}