<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Happy EunDuk</title>
    <link>https://eunduk2.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 07:08:51 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>은덕2</managingEditor>
    <item>
      <title>[SOLID] DIP에 대해 알아보자</title>
      <link>https://eunduk2.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;(기존 벨로그 링크)&quot; href=&quot;https://velog.io/@eunduk/SOLID-DIP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(기존 벨로그 링크)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 SOLID에 마지막 법칙인 DIP !&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 DIP 없이는 OCP를 구현할 수 없다고 생각해서.. OCP를 완전히 이해하고 적용할 수 있는 사람이라면 DIP 개념이 어렵지 않을 것이라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇지만 처음 본다면 상당히 어려운 개념.. 쉬운 비유와 예시를 통해 마스터 해보도록 하자 ;-;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;dip&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DIP&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dependency Inversion Principle : 의존성 역전 법칙&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.&lt;/li&gt;
&lt;li&gt;추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이게 도대체 무슨 소리인가 싶지만.. 비유를 통해 차근차근 알아보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;노트북-vs-데스크탑&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;노트북 VS 데스크탑&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 시작에 앞서 상위 모듈은 컴퓨터, 하위 모듈은 키보드라고 해보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;노트북은 내장되어 있는 빌트인 키보드에 절대적으로 의존하고 있다고 볼 수 있다.&lt;br /&gt;키보드가 고장나면 노트북 사용에 큰 영향을 끼치고 다른 키보드로 바꿔끼기도 싶지 않다.&lt;br /&gt;이 상황에서 첫번째 정의와 같이 상위 모듈이 하위 모듈에 의존하고 있다고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 데스크탑에서의 키보드 사용을 살펴보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데스크탑은 어떤 키보드 자체를 의존하고 있는 것이 아닌 키보드의 신호가 들어오는 USB 포트를 의존하고 있다.&lt;br /&gt;마찬가지로 키보드 입장에서도 데스크탑이 아닌 USB 포트를 의존하고 있다고 볼 수 있다.&lt;br /&gt;각 모듈의 독립성은 유지한 채 USB포트라는 추상화를 바라봄으로써 상위 모듈과 하위 모듈은 추상화에 의존한다고 볼 수 있다.&lt;br /&gt;이 때, 키보드가 고장나더라도 쉽게 다른 키보드로 교체할 수 있어 유연성과 확장성에 용이하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 처럼, 실생활에서도 의존성이 낮아지면 좋은 이점을 많이 얻어갈 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;코드-예시&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드 예시&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노트북 코드 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class BuiltInKeyboard {
    func type() {
        print(&quot;Typing on built-in keyboard&quot;)
    }
}

class Laptop {
    let keyboard = BuiltInKeyboard()
    
    func useKeyboard() {
        keyboard.type()
    }
}

// 사용 예시
let myLaptop = Laptop()
myLaptop.useKeyboard()

// result
// &quot;Typing on built-in keyboard&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드에서 상위 모듈인 Laptop은 BuiltInKeyboard라는 하위 모듈을 완전히 의존하고 있는 상태이다.&lt;br /&gt;이러면 Laptop 클래스는 BuiltInKeyboard클래스만을 위한 클래스가 되는 것이다.&lt;br /&gt;이 것은 DIP가 위반된 것으로 상위 모듈과 하위 모듈 모두 추상화를 바라볼 수 있게 리팩토링 해줘야 한다.&lt;br /&gt;만약, 기존 키보드를 새로운 키보드로 교체하여 사용하려고 하면 Laptop 코드의 변경이 일어나야 하므로 OCP 원칙을 위반한게 될 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데스크탑 코드 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;// 추상화된 프로토콜
protocol Keyboard {
    func type()
}

// 구체적인 구현 1: Normal 키보드
class NormalKeyboard: Keyboard {
    func type() {
        print(&quot;Typing on normal keyboard&quot;)
    }
}

// 구체적인 구현 2: LED 키보드
class LEDKeyboard: Keyboard {
    func type() {
        print(&quot;Typing on led keyboard&quot;)
    }
}

// 데스크탑 클래스는 추상화된 프로토콜에 의존
class Desktop {
    var keyboard: Keyboard
    
    init(keyboard: Keyboard) {
        self.keyboard = keyboard
    }
    
    func useKeyboard() {
        keyboard.type()
    }
}

// 사용 예시
let normalKeyboard = NormalKeyboard()
let ledKeyboard = LEDKeyboard()

let myDesktopWithUSB = Desktop(keyboard: normalKeyboard)
myDesktopWithUSB.useKeyboard()

// result
// &quot;Typing on USB keyboard&quot;

let myDesktopWithBluetooth = Desktop(keyboard: ledKeyboard)
myDesktopWithBluetooth.useKeyboard()  

// result
// &quot;Typing on Bluetooth keyboard&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 코드에서는 프로토콜로 추상화를 구현하고 상위 모듈인 Desktop과 하위 모듈인 NormalKeyboard, LEDKeyboard가 모두 추상화를 의존하고 있는 형태이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DIP를 위반하지 않은 코드로, Desktop에서는 여러 형태의 키보드를 사용할 수 있으며 새로운 키보드로 교체 하여도 Desktop 코드에 영향을 끼치지 않으므로 OCP 원칙도 잘 지킨 것을 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 실생활 비유와 코드 예시를 통해 DIP를 알아보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 다소 생소한 원칙이지만 좋은 코드 구조를 위해서 꼭 필요한 원칙으로 실무에서도 적용하려는 습관을 들이려고 노력해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;dip-한-줄-결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DIP 한 줄 결론&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추상화에 의존하라!!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;모든 피드백 감사합니다. (꾸벅)&lt;/p&gt;</description>
      <category>Refactoring</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/47</guid>
      <comments>https://eunduk2.tistory.com/47#entry47comment</comments>
      <pubDate>Tue, 8 Oct 2024 20:47:26 +0900</pubDate>
    </item>
    <item>
      <title>[SOLID] LSP와 ISP에 대해 알아보자</title>
      <link>https://eunduk2.tistory.com/46</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;(기존 벨로그 링크)&quot; href=&quot;https://velog.io/@eunduk/SOLID-LSP%EC%99%80-ISP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(기존 벨로그 링크)&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;lsp&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LSP&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Liskov substitution principle : 리스코프 치환 원칙&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리스코프 형님이 만드신 원칙으로, &amp;lsquo;프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.&amp;rsquo; 라고 정의되어 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이게 말이 참 어려운데.. 다른 정의도 살펴보자.&lt;br /&gt;&amp;lsquo;자식클래스는 부모 클래스로써의 역할을 완벽히 할 수 있어야 한다&amp;rsquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 두 개의 정의를 살펴보니 상속에 관한 얘기인 것 같고.. 아주 쉽게 정리하면 리스코프 원칙 따라서 상속 야무지게 해라! 라는 뜻으로 봐도 될 것 같다^^&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 처음 봤을 때 상속을 잘 하라는건 알겠는데 의문이 드는 말이 하나 있었다.&lt;br /&gt;&amp;lsquo;부모 클래스를 자식 클래스로 대체해도 문제 없어야 한다&amp;rsquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이게 도대체 무슨말이지..?&lt;br /&gt;많은 예시를 보면서 오래 고민해보았는데 이렇게 정리할 수 있을 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모가 정해준 속성과 메서드를 사용하지 않거나 비워둔다면 LSP 위반!&lt;br /&gt;부모가 정해준 속성과 메서드의 의미를 변형하여 사용하면 LSP 위반!&lt;br /&gt;비유하자면 부모님이 물려준 재산 내 맘대로 사용하지 말고, 부모님 유서대로 사용하자!&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;(맞나 이거..?)&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 LSP를 지키면 어떤 장점을 가져갈 수 있을까?&lt;br /&gt;내 생각에는 LSP를 위반하지 않은 자식 인스턴스를 유연하게 교체하며 사용할 수 있는, 한마디로 재사용성이 뛰어난 장점이 있는 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 예시 코드를 보면서 이해해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Bird {
    func fly() {
        print(&quot;Flying&quot;)
    }
}

class Sparrow: Bird {
    override func fly() {
        print(&quot;Flying like a sparrow&quot;)
    }
}

class Ostrich: Bird {
    override func fly() {
        fatalError(&quot;Ostriches cannot fly&quot;) // 재정의하지 않고 에러 발생
    }
}

func makeBirdFly(bird: Bird) {
    bird.fly()
}

// 사용 예시
let sparrow = Sparrow()
let ostrich = Ostrich()

makeBirdFly(bird: sparrow) // 출력: &quot;Flying like a sparrow&quot;
makeBirdFly(bird: ostrich) // 런타임 에러: &quot;Ostriches cannot fly&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Bird라는 부모 클래스가 있고 이 클래스를 상속 받는 2개의 자식 클래스가 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모가 정해준 메서드인 fly()를 참새 클래스(sparrow)에서는 의도에 맞게 구현하고 있지만 타조 클래스에서는 fly()함수를 사용하지 못하게 구현해 놓았다.&lt;br /&gt;타조는 날 수 없기 때문에 원하는 메서드를 구현하려면 아래와 같이 변형하여 코드를 구현하여야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Ostrich: Bird {
    override func fly() {
        print(&quot;Running like a ostrich&quot;) // fly() 의도에 맞지 않게 변형하여 구현
    }
}
// OR
class Ostrich: Bird {
    override func fly() {
        fatalError(&quot;Ostriches cannot fly&quot;)
    }
    func run() { // 부모가 정해주지 않은 새로운 함수 생성
		    print(&quot;Running like a ostrich&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 부모가 정해준대로 구현하지 않으면 makeBirdFly(bird: ostrich) 에서 오류가 나거나, 의도하지 않은 동작을 하게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아까 보았던 자식은 부모를 대체할 수 있어야 한다는 의미도, 부모인 bird: 파라미터 자리에 LSP를 위반하지 않는 자식을 넣으면 문제 없이 돌아간다는 의미로 봐도 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 정의만 봤을 때는 꽤나 어려웠지만 배우고 나니 생각보다 이해하기 쉬운 원칙인 것 같다.&lt;br /&gt;물론 이 원칙을 지키며 코드를 짜는 것은 매우 어려울 것이다. ㅋㅅㅋ&lt;/p&gt;
&lt;h3 id=&quot;lsp-한-줄-결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LSP 한 줄 결론&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모님이 시키는 것만 하자 !&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;isp&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ISP&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Interface segregation principle : 인터페이스 분리 원칙&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 정의를 살펴보면 이렇다.&lt;br /&gt;&amp;ldquo;특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.&amp;rdquo;&lt;br /&gt;&amp;ldquo;클라이언트들이 사용하지 않는 인터페이스를 의존하도록 강요되어선 안된다.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 개인적으로 ISP는 SRP + LSP인 합친 느낌이 난다. (근데 protocol로 곁들여진..)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LSP법칙 처럼 어떤 protocol(inderface)를 채택한 클래스에서 무력화한 기능이 존재한다면 위반한 것이고, 그 얘기는 그 protocol이 1개의 책임만을 가진 것이 아니라고 봐도 돼서 그렇게 생각한 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 예시를 들어 설명하면 &amp;ldquo;움직이다&amp;rdquo;와 &amp;ldquo;멈추다&amp;rdquo; 기능을 가지고 있는 프로토콜이 있을 때, 이 프로토콜을 채택한 클래스에서 두 기능 중 한 기능이라도 무력화 한다면 그 것은 ISP를 위반한 것이다.&lt;br /&gt;&amp;ldquo;움직이다&amp;rdquo; 기능을 가진 프로토콜과 &amp;ldquo;멈추다&amp;rdquo; 기능을 가진 프로토콜로 나누어 해결해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 무차별적으로 프로토콜을 분리하며 구현하면 코드가 복잡해질 수 있기 때문에 기준을 잘 잡고 프로토콜 하나에 필요한 기능들만 적절히 구현해줘야 한다.&lt;/p&gt;
&lt;h3 id=&quot;isp-한-줄-결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ISP 한 줄 결론&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ISP를 위반하지 않으려면 Protocol을 구현할 때 SRP와 LSP를 잘 지켜라!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;모든 피드백 감사합니다. (꾸벅)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Refactoring</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/46</guid>
      <comments>https://eunduk2.tistory.com/46#entry46comment</comments>
      <pubDate>Tue, 8 Oct 2024 20:45:33 +0900</pubDate>
    </item>
    <item>
      <title>[SOLID] OCP에 대해 알아보자</title>
      <link>https://eunduk2.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;(기존 벨로그 링크)&quot; href=&quot;https://velog.io/@eunduk/SOLID-OCP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(기존 벨로그 링크)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 포스팅했던 SRP에 이어서 OCP를 공부해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 id=&quot;ocp&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OCP&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Open-Closed Principle : 개방-폐쇄 원칙&lt;br /&gt;&amp;ldquo;소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;말이 참 어려운거 같은데 나는 이렇게 정리하고 싶다.&lt;br /&gt;확장에는 열려있다 : 새로운 코드를 추가하여 기능을 확장한다.&lt;br /&gt;변경에는 닫혀있다 : 기능을 확장할 때 원래 코드를 건들이지 않는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 이미 짜놓은 코드 건들이지 말고 기능을 추가하거나 수정하라는 것 같다.&lt;br /&gt;근데 아무리 생각해도 기능을 추가하고 수정할 때 어떻게 원래 코드를 건들이지 말라는건가 싶다.&lt;br /&gt;그리고 원래 코드의 기준이 뭐지? 라는 생각도 들었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 코드를 보면서 이해해보자.&lt;br /&gt;아래 음식 주문을 받고 손님들에게 내어주어야 하는 중국집 프로그램이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;struct ChineseRestaurant {
    let orders: [String] = [&quot;짜장면&quot;, &quot;짜장면&quot;, &quot;짬뽕&quot;, &quot;짬뽕&quot;, &quot;짜장면&quot;]

    func serveOrders() {
        for order in orders {
            if order == &quot;짜장면&quot; {
                print(&quot;짜장면 나왔습니다~!&quot;)
            } else if order == &quot;짬뽕&quot; {
                print(&quot;짬뽕 나왔습니다~!&quot;)
            }
        }
    }
}

ChineseRestaurant().serveOrders()

// result
짜장면 나왔습니다~!
짜장면 나왔습니다~!
짬뽕 나왔습니다~!
짬뽕 나왔습니다~!
짜장면 나왔습니다~!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 먼저 이 프로그램의 &amp;lsquo;원래 코드&amp;rsquo; 기준을 좀 세워보려고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 프로그램이지만 중국집의 분명한 목표와 기능을 생각해 봤을 때 주문을 받고 주문에 맞는 음식을 출력하는 것! 그리고 그 목표를 달성하는 것은 ChineseRestaurant이라는 struct이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 나는 ChineseRestaurant를 원래 코드라고 생각하겠다.&lt;/p&gt;
&lt;h3 id=&quot;기능-추가-ocp-구현-전&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기능 추가 (OCP 구현 전)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원래 코드 기준도 세웠으니 직접 기능을 추가하거나 수정해보자.&lt;br /&gt;먼저, 탕수육이라는 메뉴가 추가 되어야 한다고 했을 때, 요구사항에 맞게 코드를 수정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;struct ChineseRestaurant {
    // 주문에 탕수육 추가, OCP 위배
    let orders: [String] = [&quot;짜장면&quot;, &quot;탕수육&quot;, &quot;짜장면&quot;, &quot;짬뽕&quot;, &quot;짬뽕&quot;, &quot;짜장면&quot;, &quot;탕수육&quot;]

    func serveOrders() {
        for order in orders {
            if order == &quot;짜장면&quot; {
                print(&quot;짜장면 나왔습니다~!&quot;)
            } else if order == &quot;짬뽕&quot; {
                print(&quot;짬뽕 나왔습니다~!&quot;)
            } else if order == &quot;탕수육&quot; { // 탕수육 로직 추가, OCP 위배
                print(&quot;탕수육 나왔습니다~!&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;탕수육이 추가되면서 탕수육 주문에 대한 새로운 로직이 추가되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 때, 내가 설정한 원래 코드인 ChineseRestaurant를 건들였으므로 우리는 OCP를 위반하게 된 것이다. (변경에 닫혀있어야 한다는 원칙을 위반)&lt;/p&gt;
&lt;h3 id=&quot;기능-수정-ocp-구현-전&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기능 수정 (OCP 구현 전)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면, 기능 수정이 필요할 때는 어떨까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 메뉴에 로직이 수정되어야 한다고 해보자.&lt;br /&gt;ex) &amp;ldquo;짜장면 나왔습니다~!&amp;rdquo; &amp;rarr; &amp;ldquo;맛있는 짜장면 나왔습니다~!&amp;rdquo;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;struct ChineseRestaurant {
    // 주문에 탕수육 추가, OCP 위배
    let orders: [String] = [&quot;짜장면&quot;, &quot;탕수육&quot;, &quot;짜장면&quot;, &quot;짬뽕&quot;, &quot;짬뽕&quot;, &quot;짜장면&quot;, &quot;탕수육&quot;]

    func serveOrders() {
        for order in orders {
            if order == &quot;짜장면&quot; {
                print(&quot;맛있는 짜장면 나왔습니다~!&quot;) // 로직 수정, OCP 위배
            } else if order == &quot;짬뽕&quot; {
                print(&quot;맛있는 짬뽕 나왔습니다~!&quot;) // 로직 수정, OCP 위배
            } else if order == &quot;탕수육&quot; {
                print(&quot;맛있는 탕수육 나왔습니다~!&quot;) // 로직 수정, OCP 위배
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요구사항 변경으로 인해 각 메뉴의 로직이 수정되었다.&lt;br /&gt;역시 내가 설정 해두었던 원래 코드를 건들였으므로 OCP를 위반하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 코드로 예시를 들어서 OCP를 위반한 것이 생각보다 상관없다고 생각할 수 있다.&lt;br /&gt;하지만 실무나 큰 프로젝트에서는 생각보다 문제가 심각해질 수 있다.&lt;br /&gt;프로젝트를 개발하면서 기능 추가와 수정은 필수로 진행되는데 OCP를 위반하며 개발하면 잘 돌아가던 기존 코드들이 먹통이 되는 경우가 분명 발생할 것이다. (실제로 새 기능을 추가할 때 OCP를 위반한 코드는 오류가 꼭 나오드라..)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;ocp-구현&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;OCP 구현&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 OCP를 지키면서 중국집 프로그램을 리팩토링 해보자!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 OCP를 지키면서 개발하려면 추상화는 기본적으로 깔고 들어가야 한다고 생각해야 한다.&lt;br /&gt;추상화의 기준을 잘 잡고 기존 코드에서 구체타입이 아닌 추상타입을 의존한다면 OCP를 지키며 개발할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 order를 추상화하여 이 추상 타입을 의존하도록 하여 리팩토링 하였다. (OrderStrategy를 전략으로 한 전략 패턴 사용)&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;protocol OrderStrategy { // 주문 전략 프로토콜
    func serve()
    func canAccept(order: Order) -&amp;gt; Bool
}

struct JajangmyeonOrder: OrderStrategy { // 짜장면 전략
    func serve() {
        print(&quot;짜장면 나왔습니다~!&quot;)
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jajangmyeon
    }
}

struct JjambbongOrder: OrderStrategy { // 짬뽕 전략
    func serve() {
        print(&quot;짬뽕 나왔습니다~!&quot;)
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jjambbong
    }
}

enum Order: String { // 메뉴 enum
    case jajangmyeon, jjambbong
}


struct ChineseRestaurant {
    let strategies: [OrderStrategy]
    let orders: [Order]
    
    init(strategies: [OrderStrategy], orders: [Order]) {
        self.strategies = strategies
        self.orders = orders
    }
    
    func serve(strategy: OrderStrategy) {
        strategy.serve()
    }
    
    func serveOrders() {
        for order in orders {
            strategies.filter { $0.canAccept(order: order) }.first?.serve()
        }
    }

}

// 전략과 주문을 주입
let restaurant = ChineseRestaurant(strategies: [JajangmyeonOrder(), JjambbongOrder()], orders: [.jajangmyeon, .jajangmyeon, .jjambbong, .jjambbong, .jajangmyeon])
restaurant.serveOrders()

// result
짜장면 나왔습니다~!
짜장면 나왔습니다~!
짬뽕 나왔습니다~!
짬뽕 나왔습니다~!
짜장면 나왔습니다~!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전략 패턴을 사용하였지만 너무 신경 쓸 필요 없이 코드가 어떻게 구현 되었는지 보면 된다.&lt;br /&gt;문자열로 받던 주문은 enum타입으로 받았고 OrderStrategy프로토콜을 기반으로 각 메뉴인 짜장면 전략과 짬뽕 전략을 각자 구현해 주었다.&lt;br /&gt;그리고 ChineseRestaurant에서는 단지 전략 프로토콜을 가져오고 주입받은 프로토콜의 serve함수만 반복문으로 수행해주면 끝이다. (canAccept로 order 검증도 진행)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 ChineseRestaurant는 짜장면, 짬뽕이라는 구체 타입을 의존하는 것이 아닌 OrderStrategy프로토콜 추상화 타입을 의존한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 리팩토링 하기 전 코드로 했던 것과 마찬가지로 새로운 기능을 추가하거나 기존 로직을 수정해보자.&lt;/p&gt;
&lt;h3 id=&quot;기능-추가-ocp-구현-후&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기능 추가 (OCP 구현 후)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저, 원래 코드 기준은 똑같이 ChineseRestaurant로 잡으면 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;탕수육 메뉴를 똑같이 추가해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;protocol OrderStrategy {
    func serve()
    func canAccept(order: Order) -&amp;gt; Bool
}

struct JajangmyeonOrder: OrderStrategy {
    func serve() {
        print(&quot;짜장면 나왔습니다~!&quot;)
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jajangmyeon
    }
}

struct JjambbongOrder: OrderStrategy {
    func serve() {
        print(&quot;짬뽕 나왔습니다~!&quot;)
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jjambbong
    }
}

struct SweetSourPork: OrderStrategy { // 탕수육 전략 추가
    func serve() {
        print(&quot;탕수육 나왔습니다~!&quot;)
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .sweetSourPork
    }
}

enum Order: String {
    case jajangmyeon, jjambbong, sweetSourPork // 탕수육 메뉴 추가
}

// 원래 코드 유지
struct ChineseRestaurant {
    let strategies: [OrderStrategy]
    let orders: [Order]
    
    init(strategies: [OrderStrategy], orders: [Order]) {
        self.strategies = strategies
        self.orders = orders
    }
    
    func serve(strategy: OrderStrategy) {
        strategy.serve()
    }
    
    func serveOrders() {
        for order in orders {
            strategies.filter { $0.canAccept(order: order) }.first?.serve()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 enum타입에 탕수육 메뉴를 추가해주고, OrderStrategy프로토콜을 상속받는 탕수육 전략 구조체를 다른 메뉴와 똑같이 만들어준다.&lt;br /&gt;이러면 탕수육 기능이 추가되었지만 원래 코드인 ChineseRestaurant는 변함이 없는 걸 알 수 있다.&lt;br /&gt;OCP에서 추구했던 &amp;ldquo;새로운 코드의 추가 만을 통해 새로운 기능을 구현하는 것&amp;rdquo;을 실현할 수 있다. 또한 원래 코드를 건들이지 않으므로써 &amp;ldquo;변경에 닫혀 있어야 한다&amp;rdquo;도 실현할 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;기능-수정ocp-구현-후&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기능 수정(OCP 구현 후)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 기존 로직을 수정해야 할 때는 어떨까?&lt;br /&gt;아까와 마찬가지로 serve함수의 print문 로직을 수정한다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;protocol OrderStrategy {
    func serve()
    func canAccept(order: Order) -&amp;gt; Bool
}

struct JajangmyeonOrder: OrderStrategy {
    func serve() {
        print(&quot;맛있는 짜장면 나왔습니다~!&quot;) // 로직 수정
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jajangmyeon
    }
}

struct JjambbongOrder: OrderStrategy {
    func serve() {
        print(&quot;맛있는 짬뽕 나왔습니다~!&quot;) // 로직 수정
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .jjambbong
    }
}

struct SweetSourPork: OrderStrategy {
    func serve() {
        print(&quot;맛있는 탕수육 나왔습니다~!&quot;) // 로직 수정
    }
    func canAccept(order: Order) -&amp;gt; Bool {
        return order == .sweetSourPork
    }
}

enum Order: String {
    case jajangmyeon, jjambbong, sweetSourPork
}

// 원래 코드 유지
struct ChineseRestaurant {
    let strategies: [OrderStrategy]
    let orders: [Order]
    
    init(strategies: [OrderStrategy], orders: [Order]) {
        self.strategies = strategies
        self.orders = orders
    }
    
    func serve(strategy: OrderStrategy) {
        strategy.serve()
    }
    
    func serveOrders() {
        for order in orders {
            strategies.filter { $0.canAccept(order: order) }.first?.serve()
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;역시 로직을 수정하였지만 원래 코드인 ChineseRestaurant는 건들이지 않은 것을 확인할 수 있다.&lt;br /&gt;마찬가지로 원래 코드를 건들이지 않고 로직 수정을 하였으므로 &amp;ldquo;변경에 닫혀 있어야 한다.&amp;rdquo;를 지키며 구현할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 OCP를 지키며 구현하면 예시처럼 코드의 유지보수성을 향상 시킬 수 있고, 큰 틀인 &amp;ldquo;원래 코드&amp;rdquo;를 건들이지 않으므로써 오류의 발생률을 줄일 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;이번-포스팅-한-줄-결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이번 포스팅 한 줄 결론&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원래 코드를 건들이지 않고 기능 수정/추가를 할 수 있도록 구현하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;모든 피드백 감사합니다. (꾸벅)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Refactoring</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/45</guid>
      <comments>https://eunduk2.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 8 Oct 2024 20:44:05 +0900</pubDate>
    </item>
    <item>
      <title>[SOLID] SRP에 대해 알아보자</title>
      <link>https://eunduk2.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;(기존 벨로그 링크)&quot; href=&quot;https://velog.io/@eunduk/SOLID-SRP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(기존 벨로그 링크)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOLID는 대학교에서 처음 접해 이론을 공부하고 정보처리기사를 딸 때 한 번 더 접했었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이론을 공부하면서 &amp;lsquo;아, 이런 식으로 코드를 짜야 좋은 코드이구나&amp;rsquo;라고 생각만 했지 실제 코드에 적용해 본 적은 없었다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;원칙 따위 무시하고 기능 구현에만 급급했던..&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 야곰 리팩토링 아카데미에 참가하면서 SOLID의 중요성을 깊이 알게 되었고 코드에도 적용해보니 꼭 필요한 원칙임을 깨닫게 되었다. (특히 실무같이 큰 프로젝트에 있어서는 더더욱..)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 SOLID의 S인 SRP부터 차근차근 공부해 보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;srp&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SRP&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Single Responsibility Principle, 단일 책임 원칙&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 클래스는 하나의 책임만 가져야 한다!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 클래스라고 나와 있지만 함수도 포함하여 생각하면 좋을 듯! (이왕 생각한 거 변수, 상수도..?)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무튼 쉽게 말하면 주어진 일만 해라!!!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 당연한 말 같으면서도 뭔 말인가 싶다.. 그래서 코드를 짜 보며 이해해 보려고 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계산기 프로그램&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;계산기 프로그램을 짠다고 생각하고 아주 쉽게 구현해 보았다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Calculator {
    func plus() {
	    // 덧셈 로직
    }
    func minus() {
	    // 뺄셈 로직
    }
    func multiple() {
	    // 곱셈 로직
    }
    func divide() {
	    // 나눗셈 로직
    }
    // 그 이외의 많은 연산들
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드만 보면 문제가 없어 보이지만, 나는 위 코드가 SRP를 위반했다고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;계산기의 단일 책임은 무엇일까? 더하기 기능? 빼기 기능?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 계산기의 단일 책임은 단지 &amp;lsquo;계산하는 기능&amp;rsquo;이 전부라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더하기, 빼기 이런 건 모르겠고~ 그냥 계산 그 잡채만 해주면 계산기의 책임을 다한 것이 아닐까?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;(개인적인 생각입니다ㅎ)&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;뭔가 말이 어려울 수 있는데 이렇게 생각해 보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;plus 함수가 잘 못 되어 더하기한 값이 엉터리로 나왔다고 가정해 보자.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그렇다면 그 엉터리 값에 대한 책임은 누가 물어줘야 하지? 라고 생각해 본다면 당연히 덧셈 로직을 가지고 있는 Calculator클래스일 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 생각하면 결국 Calculator클래스는 1개의 책임이 아닌 모든 연산에 대한 책임을 가지고 있다고 볼 수 있는 것이다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;그렇다면 어떻게 리팩토링하여 SRP원칙을 충족할 수 있을까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드를 살펴보자.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;(의존성 주입은 개나 줘버린 코드입니다.)&lt;/s&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Calculator {
    private let plusCalculator: PlusCalculator = PlusCalculator()
    private let minusCalculator: MinusCalculator = MinusCalculator()
    private let multipleCalculator: MultipleCalculator = MultipleCalculator()
    private let divideCalculator: DivideCalculator = DivideCalculator()
    
    func plus() {
        plusCalculator.plus()
    }
    func minus() {
        minusCalculator.minus()
    }
    func multiple() {
        multipleCalculator.multiple()
    }
    func divide() {
        divideCalculator.divide()
    }
}

class PlusCalculator {
    func plus() {
        // 덧셈 로직
    }
}
class MinusCalculator {
    func minus() {
        // 뺄셈 로직
    }
}
class MultipleCalculator {
    func multiple() {
        // 곱셈 로직
    }
}
class DivideCalculator {
    func divide() {
        // 나눗셈 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각각의 연산의 책임을 갖는 클래스를 4개 만들어주었고, Calculator클래스는 연산 클래스들의 연산을 가져와 사용하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Calculator클래스는 원래 코드와 동일하게 plus, minus, multiple, divide함수를 가지고 있지만 과연 여러 개의 책임을 가진다고 말할 수 있을까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 아까의 가정을 다시 가져와 비교해보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PlusCalculator클래스의 plus함수가 잘 못 되어 더하기한 값이 엉터리로 나왔다고 가정했을 때&lt;/b&gt;, 덧셈이 엉터리로 된 것에 대한 1개의 책임은 덧셈 로직을 갖고 있는 PlusCalculator클래스가 갖게 되고 Calculator클래스는 계산이 잘 못된 것에 대한 1개의 책임만 갖게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 각 클래스들은 각자 1개씩의 단일 책임을 갖고 있고 이 것은 SRP를 만족했다고 볼 수 있는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아 오케이! 한 클래스가 1개의 책임을 어떻게 가져야하는지는 알겠어.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 대체 왜 1개의 책임을 가져야 하는건데?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;1개의 책임을 캡슐화하게 되면 얻는 이점은 3가지 정도 있는 것 같다.&lt;br /&gt;(갑자기 캡슐화가 나왔는데 이건 다음에 포스팅 하는 걸로..)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 가독성 향상&lt;br /&gt;2. 오류/결함 감소&lt;br /&gt;3. 재사용성 향상&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;역시 말로는 쉬우나 와 닿지는 않으니.. 예시 코드를 짜보면서 이해해보자&lt;/p&gt;
&lt;h3 id=&quot;1-가독성-향상&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 가독성 향상&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 친구는 코드를 짜지 않아도 알겠지만 간단하게 함수로 구현해 본다면 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;// 다중 책임
func calculate(value1:Int, value2:Int, op:String) -&amp;gt; Int {
    switch op {
    case &quot;+&quot;:
        return value1 + value2
    case &quot;-&quot;:
        return value1 - value2
    default:
        return 0
    }
}
// 단일 책임
func plusCalculate(value1:Int, value2:Int) -&amp;gt; Int {
    return value1 + value2
}
func minusCalculate(value1:Int, value2:Int) -&amp;gt; Int {
    return value1 - value2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금은 매우 간단한 코드이기 때문에 둘 다 가독성이 좋아 보이지만, 큰 프로젝트라 생각해본다면 calculate 함수의 switch문은 점점 길고 무거워져 나중에는 이해하기 매우 힘들 것이다. 그리고 plus기능을 이해하려면 calculate함수의 필요 없는 부분도 분석해 나가며 이해해야하기 때문에 가독성이 떨어진다고 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면에 단일 책임을 갖는 plusCalculate는 직관적이고 사용하는 곳에서 덧셈 로직을 알 필요 없이 함수를 갖다 쓰기만 하면 된다. (사용하는 곳에서 가독성 향상)&lt;br /&gt;(덧셈 로직이 수정될 때 plusCalculate함수 내부만 건들여주면 된다는 점에서 유지보수성도 향상 됨)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-오류결함-감소&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 오류/결함 감소&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 내용은 또 응집도와 결합도가 연관되어 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;(얘네도 포스팅 해야겠네..)&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(한 줄로 설명하면 응집도가 높고 결합도가 낮으면, 서로의 프로퍼티와 로직을 모른다. 응집도가 낮고 결합도가 높으면, 서로의 프로퍼티를 바꿀 수 있고, 로직도 공유하며 작용한다.)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 클래스에 여러 책임이 얽히게 되면 자연스럽게 그 책임들은 서로의 정보나 로직을 공유하게 되고, 결합도가 높아져 좋지 않은 코드가 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SRP를 적용하면 응집도는 높아지고 결합도는 낮아지기 때문에 오류의 확률을 낮출 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Car {
    private var engine: Bool = false
    
    func depart() {
        if engine == true {
            print(&quot;출발!&quot;)
        }
    }
    func engineStart() {
        self.engine = true
        // 시동이 켜질 때 작동하는 추가 로직들
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시동을 키는 책임과 출발을 하는 책임 두가지를 가지고 있는 클래스가 있고 출발을 하려면 시동은 꼭 켜져있어야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 때, 출발 함수가 비정상적인 작동으로 인해 시동 플래그를 true로 조작하고 출발하게 된다면 분명 결함이 생길 것이다. engineStart라는 함수를 통해 정상적으로 시동을 건 것이 아니기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Car {
    private var engine: Bool = false
    
    func depart() {
		    self.engine = true
        if engine == true {
            print(&quot;출발!&quot;)
        }
    }
    func engineStart() {
        self.engine = true
        // 시동이 켜질 때 작동하는 추가 로직들
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 예시와 같이 시동 플래그를 억지로 true로 바꾸고 억지로 출발 시킬 수 있게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 예시는 억지로 플래그를 바꿔서 그런거고 실제 프로젝트에서는 저렇게 하지 않으면 되지 않냐? 라고 할 수 있는데 맞는 말이지만 실제로 큰 프로젝트에서 여러 개의 책임이 겹쳐 있으면 나 또는 내 동료가 서로 간섭하는 코드를 짤 가능성이 있고 발생한 오류들을 살펴보면 실제로 그런 것들이 원인인 경우가 빈번하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 그런 가능성 조차 없앨 수 있는 방법이 SRP가 아닌가 싶다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;3-재사용성-향상&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 재사용성 향상&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 내용은 사실 코드에서 뿐만 아니라 우리의 일상에서도 녹아들어 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;노트북과 컴퓨터만 비교해봐도 알 수 있는데 예를 들어 노트북의 키보드만 분리하고 다른 컴퓨터에 연결하여 사용하고 싶어도 절대 그렇게 할 수가 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 컴퓨터에 연결하여 사용하는 키보드는 어떤 컴퓨터에도 바꿔가며 재사용 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 예시로 아래 코드를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;class Car {
    func engineStart() {
        print(&quot;2기통 엔진 활성화 완료&quot;)
    }
    
    func depart() {
        engineStart()
        print(&quot;출발!&quot;)
    }
}

// 2기통 엔진 자동차만 사용 가능
let car = Car()
car.depart()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 엔진의 활성화와 출발의 로직을 담고 있어 2개의 책임을 가지고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Car클래스가 위처럼 구현되어 있다면 재사용성은 떨어지게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2기통 엔진이 박혀있는 차량의 틀이기 때문에 다른 엔진이 달린 차량은 만들 수 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 엔진의 책임을 분리하여 리팩토링 해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;protocol Engine {
    func engineStart()
}

class Car {
    private let engine: Engine
    
    init(engine: Engine) {
        self.engine = engine
    }
    
    func depart() {
        engine.engineStart()
        print(&quot;출발!&quot;)
    }
}

class Engine2: Engine {
    func engineStart() {
        print(&quot;2기통 엔진 활성화 완료&quot;)
    }
}
class Engine4: Engine {
    func engineStart() {
        print(&quot;4기통 엔진 활성화 완료&quot;)
    }
}

// 2기통 엔진 자동차 사용
let car2 = Car(engine: Engine2())
car2.depart()
// 4기통 엔진 자동차 사용
let car4 = Car(engine: Engine4())
car4.depart()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;엔진을 Engine프로토콜이라는 단일 책임으로 분리하고 Car클래스에서 Engine을 주입 받아 사용하고 있는데 위와같이 2기통 엔진 자동차도 사용할 수 있고, 4기통 엔진 자동차도 사용할 수 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(짜다보니 다른 원칙들이 적용되고 있는 것 같은 기분이..의존성 주입이 이렇게 좋아..)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 뿐만 아니라 실무에서 팝업을 띄우는 함수가 있을 때 단일 책임을 부여해야 재사용성이 높아질 수 있다.&lt;br /&gt;예를 들어 어떤 조건을 검사하고 팝업을 띄우면 2개의 책임을 갖기 때문에 재사용성이 현저히 떨어질 수 있다.&lt;br /&gt;어떤 조건을 검사하는 책임과, 팝업을 띄우는 책임으로 분리하여 사용하면 재사용성이 향상될 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 SRP를 예시를 통해 공부하고 이해해보았는데 하면 할 수록 캡슐화와 큰 관련이 있는 원칙이 아닌가 싶다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 SRP의 여러 정의 중에도 이런 정의가 있다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #212529; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 타입은 하나의 책임만 가지며, 타입은 그 책임을 완전히 캡슐화해야한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 SRP는 단일 책임을 가지게 하는 것뿐만 아니라 그 책임을 캡슐화 하는 것 까지가 완성이지 않나 싶다. (아무래도 포스팅 순서가 캡슐화가 먼저였어야 했다..)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나중에 캡슐화를 자세히 공부하고 SRP 내용과 같이 포스팅 해봐야겠다.&lt;/p&gt;
&lt;h3 id=&quot;이번-포스팅-한-줄-결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;이번 포스팅 한 줄 결론&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 개를 한 개로 쪼개고, 그 한 개의 완성도를 높이자!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;모든 피드백 감사합니다. (꾸벅)&lt;/p&gt;</description>
      <category>Refactoring</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/44</guid>
      <comments>https://eunduk2.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 8 Oct 2024 20:42:41 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Clone] 마이루틴 앱 팝업뷰 구현하기</title>
      <link>https://eunduk2.tistory.com/43</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;전부터 팝업뷰를 구현해 보고 싶었는데 '마이루틴' 앱을 사용하던 중&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;팝업뷰가 사용된 것을 보고 따라해보기로 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;팝업뷰는 프레임워크로도 구현할 수 있고 코드로 직접 커스텀하여 구현할 수 있는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 분의 블로그를 보고 공부하여 마이루틴 앱 처럼 구현해 보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sunidev.tistory.com/10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sunidev.tistory.com/10&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691493992021&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[iOS/Swift] Custom Animation Popup 만들기&quot; data-og-description=&quot;  이번 글은 UIView.animate를 사용하여, Custom Animation Popup을 만드는 방법입니다. 해당 포스팅은 Storyboard intreface 기반 Swift 프로젝트입니다. Fade In Out Animation Popup Move Animation Popup 1. UIWindow+Ext.swift 준&quot; data-og-host=&quot;sunidev.tistory.com&quot; data-og-source-url=&quot;https://sunidev.tistory.com/10&quot; data-og-url=&quot;https://sunidev.tistory.com/10&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XV8D8/hyTAGsYaHQ/UgYwYHWI7xU8rHjaAjKrmK/img.png?width=800&amp;amp;height=766&amp;amp;face=0_0_800_766,https://scrap.kakaocdn.net/dn/1x9MI/hyTAy2MOsW/2hJF89dpr7qp6dFOsagJ31/img.png?width=800&amp;amp;height=766&amp;amp;face=0_0_800_766,https://scrap.kakaocdn.net/dn/L46C8/hyTAvrsvdA/v6bfvB8O6BxeKipmVS12K0/img.jpg?width=960&amp;amp;height=1280&amp;amp;face=0_0_960_1280&quot;&gt;&lt;a href=&quot;https://sunidev.tistory.com/10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sunidev.tistory.com/10&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XV8D8/hyTAGsYaHQ/UgYwYHWI7xU8rHjaAjKrmK/img.png?width=800&amp;amp;height=766&amp;amp;face=0_0_800_766,https://scrap.kakaocdn.net/dn/1x9MI/hyTAy2MOsW/2hJF89dpr7qp6dFOsagJ31/img.png?width=800&amp;amp;height=766&amp;amp;face=0_0_800_766,https://scrap.kakaocdn.net/dn/L46C8/hyTAvrsvdA/v6bfvB8O6BxeKipmVS12K0/img.jpg?width=960&amp;amp;height=1280&amp;amp;face=0_0_960_1280');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[iOS/Swift] Custom Animation Popup 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  이번 글은 UIView.animate를 사용하여, Custom Animation Popup을 만드는 방법입니다. 해당 포스팅은 Storyboard intreface 기반 Swift 프로젝트입니다. Fade In Out Animation Popup Move Animation Popup 1. UIWindow+Ext.swift 준&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sunidev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;원리를 이해하고 따라하니 금방 적용할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;결과.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nntcA/btsqCswzyGO/266ZtAPy72rISWbYjD0AX0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nntcA/btsqCswzyGO/266ZtAPy72rISWbYjD0AX0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nntcA/btsqCswzyGO/266ZtAPy72rISWbYjD0AX0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nntcA/btsqCswzyGO/266ZtAPy72rISWbYjD0AX0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;438&quot; data-filename=&quot;결과.gif&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과물이 조금 아쉽지만 그래도 마이루틴 앱과 비슷하게 만들어 낸 것 같다^^&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음에는 프레임 워크를 사용해서 팝업뷰를 구현해 보겠다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/43</guid>
      <comments>https://eunduk2.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 8 Aug 2023 20:28:09 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Clone] 야놀자 TabBar 따라해보기</title>
      <link>https://eunduk2.tistory.com/42</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;야놀자 어플의 탭바는 항상 흥미로웠기 때문에 꼭 한번 구현해보고 싶었었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;꺾여있는 탭바와 4개의 아이템으로 구성되어 있고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가운데에는 애니메이션이 있는 버튼이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Af32/btsoKZDVYw8/vhjPG9mIrR673p4aULdjJk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Af32/btsoKZDVYw8/vhjPG9mIrR673p4aULdjJk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Af32/btsoKZDVYw8/vhjPG9mIrR673p4aULdjJk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/3Af32/btsoKZDVYw8/vhjPG9mIrR673p4aULdjJk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;200&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;꺾인 탭바와 아이템들은 다른 글에서 다뤄봤기 때문에 설명을 생략하겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 버튼에 gif를 넣기 위해 Gifu라는 라이브러리를 사용하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;라이브러리를 추가하고 아래와 같은 코드로 버튼을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1690193194667&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func makeButton() {
    let button = CustomButton(type: .custom) // 커스텀 버튼
    button.frame = CGRect(x: 167, y: -10, width: 60, height: 60) // 위치와 크기

    // gif를 설정해주고 버튼에 서브뷰로 추가
    let gifImageView = GIFImageView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
    gifImageView.animate(withGIFNamed: &quot;ya.gif&quot;)
    button.addSubview(gifImageView)

    // 버튼에 액션 추가
    button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

    // 탭바 위에 버튼 추가
    if let tabBar = self.tabBarController?.tabBar {
        tabBar.addSubview(button)
    }
}

@objc func buttonTapped() {
    print(&quot;touch&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드를 통해 gif가 적용된 버튼이 탭바 위에 추가된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 ya버튼을 누를 때 모션이 있는데 버튼을 커스텀하여 구성하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1690193486953&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomButton: UIButton {
    // 버튼을 터치할 때 이미지의 축소 비율
    private let shrinkScale: CGFloat = 0.8

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupButton()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupButton()
    }

    private func setupButton() {
        // 버튼의 이미지가 터치되었을 때의 변화를 비활성화
        adjustsImageWhenHighlighted = false
    }

    override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        // 버튼 이미지 축소
        UIView.animate(withDuration: 0.1) {
            self.transform = CGAffineTransform(scaleX: self.shrinkScale, y: self.shrinkScale)
        }
    }

    override func touchesEnded(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        // 버튼 이미지 원래 크기로 복구
        UIView.animate(withDuration: 0.1) {
            self.transform = .identity
        }
    }

    override func touchesCancelled(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        // 버튼 이미지 원래 크기로 복구
        UIView.animate(withDuration: 0.1) {
            self.transform = .identity
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;UIButton의 함수들을 재정의하여 버튼을 누를 때 크기를 변화시키는 귀여운 효과를 부여했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 야놀자 어플의 탭바를 구현해 보았는데&lt;br /&gt;코드는 별로 없지만 새로운 기술들을 공부하고 적용시키는데 조금 애를 먹었던 것 같다^^&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;시중의 어플들을 살펴보면 구현해 보고 싶은 것들이 산더미 같은데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 차근차근 구현해 보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!!&lt;/p&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/42</guid>
      <comments>https://eunduk2.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 24 Jul 2023 19:18:07 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Study] KakaoMap 여러가지 기능 구현해보기 2</title>
      <link>https://eunduk2.tistory.com/41</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저번 글에서 카카오 맵의 다양한 기능을 구현해 보았다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eunduk2.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eunduk2.tistory.com/40&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689857901270&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift/Study] KakaoMap 여러가지 기능 구현해보기 1&quot; data-og-description=&quot;저번 글에서 카카오 맵의 기본기를 적용했었다. https://eunduk2.tistory.com/39 [Swift/Study] Kakao Map API 적용해보기 평소에 카카오 맵을 Swift에 적용해보고 싶었는데 여러 문서를 참고하고 공부하여 실습해&quot; data-og-host=&quot;eunduk2.tistory.com&quot; data-og-source-url=&quot;https://eunduk2.tistory.com/40&quot; data-og-url=&quot;https://eunduk2.tistory.com/40&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMml99/hyTnKB4GhJ/XhhWuSck8w4KP3KvqmUuMk/img.gif?width=600&amp;amp;height=1300&amp;amp;face=0_0_600_1300,https://scrap.kakaocdn.net/dn/S0Wct/hyTnJ4er2H/91QgbYhKTKhuk4ELRaxW9k/img.gif?width=600&amp;amp;height=1300&amp;amp;face=0_0_600_1300&quot;&gt;&lt;a href=&quot;https://eunduk2.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://eunduk2.tistory.com/40&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMml99/hyTnKB4GhJ/XhhWuSck8w4KP3KvqmUuMk/img.gif?width=600&amp;amp;height=1300&amp;amp;face=0_0_600_1300,https://scrap.kakaocdn.net/dn/S0Wct/hyTnJ4er2H/91QgbYhKTKhuk4ELRaxW9k/img.gif?width=600&amp;amp;height=1300&amp;amp;face=0_0_600_1300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Swift/Study] KakaoMap 여러가지 기능 구현해보기 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;저번 글에서 카카오 맵의 기본기를 적용했었다. https://eunduk2.tistory.com/39 [Swift/Study] Kakao Map API 적용해보기 평소에 카카오 맵을 Swift에 적용해보고 싶었는데 여러 문서를 참고하고 공부하여 실습해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;eunduk2.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이어서 몇 개의 기능을 더 구현해보고 카카오 맵에 관한 내용은 마치려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째로 구현한 기능은&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주소를 입력받아 지도에 핀을 찍는 기능이다.&lt;/p&gt;
&lt;pre id=&quot;code_1689859381022&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import CoreLocation // CLGeocoder를 사용하기 위한 프레임워크

func addPinForAddress(_ address: String) {
    // 지리적인 정보를 처리하는 CLGeocoder 사용
    let geocoder = CLGeocoder()
    // 주소를 지리적인 정보로 변환하고
    geocoder.geocodeAddressString(address) { (placemarks, error) in
        if let error = error { // 변환 중 오류가 발생하면 오류 메시지 출력
            print(&quot;Error geocoding address: \(error.localizedDescription)&quot;)
        } else if let placemark = placemarks?.first,
                  let location = placemark.location { // 변환된 지리 정보로 위치 저장
            // 주소를 위도와 경도로 변환한 위치에 핀을 생성
            self.createPin(itemName: address, mapPoint: MTMapPoint(geoCoord: MTMapPointGeo(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude))
            , markerType: .redPin)
            // 핀이 찍힌 위치로 이동
            self.mapView.setMapCenter(MTMapPoint(geoCoord: MTMapPointGeo(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)), animated: true)
        }
    }
}

// 텍스트 필드의 주소를 가져와 위 함수 발동
@IBAction func onSearch(_ sender: Any) {
    addPinForAddress(textField.text!)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;입력받은 주소를 위도와 경도로 변환하여 핀을 찍고 지도의 중심을 그 핀으로 이동시킨다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X59uH/btson7B2BLP/FR3p1fMrJu5cAtw9adw9g0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X59uH/btson7B2BLP/FR3p1fMrJu5cAtw9adw9g0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X59uH/btson7B2BLP/FR3p1fMrJu5cAtw9adw9g0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/X59uH/btson7B2BLP/FR3p1fMrJu5cAtw9adw9g0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;800&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로는 지도를 더블 터치하면 노란 마커를 생성하고 생성된 곳의 위도와 경도 값을 가져오는 기능이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;더블 탭 액션으로 구현하다가 삽질좀 하고..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래 델리게이트 패턴으로 간단하게 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1689928444710&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 말풍선을 터치했을 때 액션
func mapView(_ mapView: MTMapView!, touchedCalloutBalloonOf poiItem: MTMapPOIItem!) {
    // 웹뷰를 담고 있는 컨트롤러를 푸시
    let ViewController2 = storyboard!.instantiateViewController(withIdentifier: &quot;ViewController2&quot;) as! ViewController2
    navigationController?.pushViewController(ViewController2, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCmBbS/btsoxvPiIvA/vtNzSVjgAzQkau1foUBvGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCmBbS/btsoxvPiIvA/vtNzSVjgAzQkau1foUBvGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCmBbS/btsoxvPiIvA/vtNzSVjgAzQkau1foUBvGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bCmBbS/btsoxvPiIvA/vtNzSVjgAzQkau1foUBvGk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;800&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-21 오후 5.36.50.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;18&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzGaXh/btsowW7Cha7/4nJsEsDbMJt1nSbQ5YLFkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzGaXh/btsowW7Cha7/4nJsEsDbMJt1nSbQ5YLFkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzGaXh/btsowW7Cha7/4nJsEsDbMJt1nSbQ5YLFkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzGaXh%2FbtsowW7Cha7%2F4nJsEsDbMJt1nSbQ5YLFkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;18&quot; data-filename=&quot;스크린샷 2023-07-21 오후 5.36.50.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;18&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝 !&lt;/p&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/41</guid>
      <comments>https://eunduk2.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 21 Jul 2023 17:39:51 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Study] KakaoMap 여러가지 기능 구현해보기 1</title>
      <link>https://eunduk2.tistory.com/40</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저번 글에서 카카오 맵의 기본기를 적용했었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eunduk2.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eunduk2.tistory.com/39&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689763868064&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift/Study] Kakao Map API 적용해보기&quot; data-og-description=&quot;평소에 카카오 맵을 Swift에 적용해보고 싶었는데 여러 문서를 참고하고 공부하여 실습해 보았다. api를 가져오는 방법은 많은 분들이 너무 잘 설명해 주셔서 생략하고 내가 실습한 내용을 정리해&quot; data-og-host=&quot;eunduk2.tistory.com&quot; data-og-source-url=&quot;https://eunduk2.tistory.com/39&quot; data-og-url=&quot;https://eunduk2.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgp90L/hyTnPiqdeP/Ic0PCoYRwSmhs7eHgMcHjk/img.png?width=800&amp;amp;height=1732&amp;amp;face=0_0_800_1732,https://scrap.kakaocdn.net/dn/F0ym1/hyTnUD1W8Q/rUIMNmQK6Tksl7U9adoXSk/img.png?width=800&amp;amp;height=1732&amp;amp;face=0_0_800_1732,https://scrap.kakaocdn.net/dn/egbUYn/hyTmuAaTxl/5RkDyNnO4V4oIPuCKMjVo1/img.png?width=1125&amp;amp;height=2436&amp;amp;face=0_0_1125_2436&quot;&gt;&lt;a href=&quot;https://eunduk2.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://eunduk2.tistory.com/39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgp90L/hyTnPiqdeP/Ic0PCoYRwSmhs7eHgMcHjk/img.png?width=800&amp;amp;height=1732&amp;amp;face=0_0_800_1732,https://scrap.kakaocdn.net/dn/F0ym1/hyTnUD1W8Q/rUIMNmQK6Tksl7U9adoXSk/img.png?width=800&amp;amp;height=1732&amp;amp;face=0_0_800_1732,https://scrap.kakaocdn.net/dn/egbUYn/hyTmuAaTxl/5RkDyNnO4V4oIPuCKMjVo1/img.png?width=1125&amp;amp;height=2436&amp;amp;face=0_0_1125_2436');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Swift/Study] Kakao Map API 적용해보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;평소에 카카오 맵을 Swift에 적용해보고 싶었는데 여러 문서를 참고하고 공부하여 실습해 보았다. api를 가져오는 방법은 많은 분들이 너무 잘 설명해 주셔서 생략하고 내가 실습한 내용을 정리해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;eunduk2.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 여러가지 기능을 공부해보고 실습해 보았다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째로 나의 위치를 가져와서 마커를 찍고 그 위치로 이동하는 기능이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위치 권한을 얻을 수 있게 info.plist에 필요한 키를 추가해주고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1689764706001&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 내 위치로 트래킹
func trackMyLocation() {
    // 사용자 위치에 마커 찍기
    mapView.showCurrentLocationMarker = true
    // 사용자 위치로 지도가 이동, 사용자가 움직이면 지도도 따라감
    mapView.currentLocationTrackingMode = .onWithoutHeading
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 함수를 발동하면 자신의 위치에 마커가 찍히고 지도가 해당 위치로 이동한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 사용자가 움직이면 지도도 움직인다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qqWJH/btsohzEo7Hf/Ntg4ZdjXD609sLPECL87pk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qqWJH/btsohzEo7Hf/Ntg4ZdjXD609sLPECL87pk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qqWJH/btsohzEo7Hf/Ntg4ZdjXD609sLPECL87pk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/qqWJH/btsohzEo7Hf/Ntg4ZdjXD609sLPECL87pk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;802&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로는 마커를 터치하면 말풍선이 생기는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 말풍선을 새로운 뷰로 커스텀하고 터치하면 다른 뷰로 넘어가는 액션을 주었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래 함수를 통해 롯데월드 핀을 생성하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 뷰에 레이블을 추가했고 poiItem.customCalloutBalloonView에 할당하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;핀의 말풍선을 커스텀 뷰로 설정하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1689768444722&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 핀 생성
func createPin(itemName: String, mapPoint: MTMapPoint, markerType: MTMapPOIItemMarkerType) {
    let poiItem = MTMapPOIItem()
    poiItem.itemName = &quot;\(itemName)&quot;
    poiItem.mapPoint = mapPoint
    poiItem.markerType = markerType

    // 커스텀 뷰를 생성하여 버튼을 추가
    let customCalloutView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    customCalloutView.backgroundColor = .white

    // 뷰에 레이블을 추가하고 itemName출력
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    label.textColor = .black
    label.textAlignment = .center
    label.text = &quot;\(itemName)&quot;

    customCalloutView.addSubview(label)

    // 커스텀 콜아웃 뷰 설정
    poiItem.customCalloutBalloonView = customCalloutView

    mapView.addPOIItems([poiItem])
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;델리게이트 패턴으로 아래 함수를 구현한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;임시로 만든 웹뷰를 담고 있는 컨트롤러를 푸시하는 동작을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1689768720184&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 말풍선을 터치했을 때 액션
func mapView(_ mapView: MTMapView!, touchedCalloutBalloonOf poiItem: MTMapPOIItem!) {
    // 웹뷰를 담고 있는 컨트롤러를 푸시
    let ViewController2 = storyboard!.instantiateViewController(withIdentifier: &quot;ViewController2&quot;) as! ViewController2
    navigationController?.pushViewController(ViewController2, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;마커 말풍선 액션.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BUwwU/btsoho37gME/iDHX0szakn0wGnmOwNN9U0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BUwwU/btsoho37gME/iDHX0szakn0wGnmOwNN9U0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BUwwU/btsoho37gME/iDHX0szakn0wGnmOwNN9U0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/BUwwU/btsoho37gME/iDHX0szakn0wGnmOwNN9U0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;800&quot; data-filename=&quot;마커 말풍선 액션.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 스테퍼를 통해 확대 축소 기능을 구현해 보겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;스테퍼의 플러스 버튼을 누르면 확대되고 마이너스 버튼을 누르면 축소되게 하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;확대 레벨 값이 작아져야 확대가 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1689770445154&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var zoomLv:Double = 0.0

@IBAction func onStepper(_ sender: UIStepper) {
    if(zoomLv &amp;lt; sender.value) { // + 버튼 터치시 확대
        mapView.setZoomLevel(mapView.zoomLevel - 1, animated: true)
    } else { // - 버튼 터치시 축소
        mapView.setZoomLevel(mapView.zoomLevel + 1, animated: true)
    }
    zoomLv = sender.value
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNpvJo/btsou03Qogn/onMoOR2fu5VLAucFkeclk0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNpvJo/btsou03Qogn/onMoOR2fu5VLAucFkeclk0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNpvJo/btsou03Qogn/onMoOR2fu5VLAucFkeclk0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cNpvJo/btsou03Qogn/onMoOR2fu5VLAucFkeclk0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;800&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 몇 개의 기능을 실습해 보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 구현해 보고 싶은 기능을 구현해 보고 다음 글에서 정리하도록 하겠다!&lt;/p&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/40</guid>
      <comments>https://eunduk2.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 19 Jul 2023 21:59:41 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Study] Kakao Map API 적용해보기</title>
      <link>https://eunduk2.tistory.com/39</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;평소에 카카오 맵을 Swift에 적용해보고 싶었는데 여러 문서를 참고하고 공부하여 실습해 보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;api를 가져오는 방법은 많은 분들이 너무 잘 설명해 주셔서 생략하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내가 실습한 내용을 정리해 보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 api 가져오는 것은 성공적으로 마쳤고 (애는 좀 많이 먹었지만ㅠㅠ)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 지도를 띄워 보겠다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689688664454&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController, MTMapViewDelegate {
    
    var mapView:MTMapView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        mapView = MTMapView(frame: self.view.bounds)
        mapView.delegate = self
        mapView.baseMapType = .standard
        self.view.addSubview(mapView)
        
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;MTMapView를 통해 지도를 만들어 기본 설정 후 서브뷰에 추가해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmW4tW/btsn0EBaB05/1VHFVYhaobgaEpZ0bKX9p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmW4tW/btsn0EBaB05/1VHFVYhaobgaEpZ0bKX9p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmW4tW/btsn0EBaB05/1VHFVYhaobgaEpZ0bKX9p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmW4tW%2Fbtsn0EBaB05%2F1VHFVYhaobgaEpZ0bKX9p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;799&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 롯데월드의 위도, 경도 값을 가져와 마커를 찍어보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1689688814819&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController, MTMapViewDelegate {
    
    var mapView:MTMapView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        mapView = MTMapView(frame: self.view.bounds)
        mapView.delegate = self
        mapView.baseMapType = .standard
        self.view.addSubview(mapView)
        
        let poiItem1 = MTMapPOIItem()
        
        poiItem1.itemName = &quot;롯데월드&quot;
        poiItem1.mapPoint = MTMapPoint(geoCoord: MTMapPointGeo(latitude: 37.511545, longitude: 127.098609))
        poiItem1.markerType = .redPin
        
        mapView.addPOIItems([poiItem1])
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마커를 생성하고 추가하는 코드는 복잡하지 않다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDwKv2/btsn8lGOsGK/vFALTiPyIWOkA6p6jMikJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDwKv2/btsn8lGOsGK/vFALTiPyIWOkA6p6jMikJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDwKv2/btsn8lGOsGK/vFALTiPyIWOkA6p6jMikJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDwKv2%2Fbtsn8lGOsGK%2FvFALTiPyIWOkA6p6jMikJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;801&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 지도를 띄우고 원하는 위치에 마커를 찍는 것까지 공부하고 실습해 보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기본적인 기능을 해보았으니 다음에는 카카오맵의 여러가지 기능을 공부해보고 글을 써보겠다!&lt;/p&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/39</guid>
      <comments>https://eunduk2.tistory.com/39#entry39comment</comments>
      <pubDate>Tue, 18 Jul 2023 23:03:01 +0900</pubDate>
    </item>
    <item>
      <title>[Swift/Study] UISearchController를 사용하여 테이블 뷰 검색 기능 구현하기</title>
      <link>https://eunduk2.tistory.com/38</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테이블 뷰의 셀을 검색하는 기능은 예전부터 구현해 보고 싶었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 UI를 구성하고 텍스트필드 델리게이트를 사용하여 구현하던 중&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;UISearchController라는 엄청난 친구를 알게 되었고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;역시 사용해보니 간단하고 편리하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러 블로그를 보면서 공부하고 사용법을 익혔다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간단하게 실습을 해봤는데 특별한 기능은 없어서 전체코드에 주석으로 설명해놓겠다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결과 화면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKoTPR/btsnZvpDXq5/ncUH0xjCbbP7yZ6inUj09k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKoTPR/btsnZvpDXq5/ncUH0xjCbbP7yZ6inUj09k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKoTPR/btsnZvpDXq5/ncUH0xjCbbP7yZ6inUj09k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cKoTPR/btsnZvpDXq5/ncUH0xjCbbP7yZ6inUj09k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;964&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;전체 코드&lt;/p&gt;
&lt;pre id=&quot;code_1689594425489&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {
    
    // 테이블 뷰를 구성할 배열 선언
    let uni:[String] = [
        &quot;서울대학교&quot;,
        &quot;연세대학교&quot;,
        &quot;고려대학교&quot;,
        &quot;한양대학교&quot;,
        &quot;성균관대학교&quot;,
        &quot;서강대학교&quot;,
        &quot;이화여자대학교&quot;,
        &quot;중앙대학교&quot;,
        &quot;경희대학교&quot;,
        &quot;한국외국어대학교&quot;
    ]
    
    @IBOutlet var table: UITableView!
    var searchController: UISearchController!
    var filteredUni: [String] = [] // 검색 결과를 담을 배열
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchControllerSetting()
    }
    // UISearchController를 만들어 적용
    func searchControllerSetting() {
        searchController = UISearchController(searchResultsController: nil)
        navigationItem.searchController = searchController // 네비게이션 바에 검색바 적용
        // 검색 내용이 바뀔때 마다 동작을 주기 위해, UISearchResultsUpdating프로토콜을 ViewController가 구현함
        searchController.searchResultsUpdater = self
        searchController.searchBar.placeholder = &quot;대학교 검색&quot; // placeholer설정
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        if searchController.isActive {
            return filteredUni.count // 검색 중이면 검색 결과를 반환
        } else {
            return uni.count // 그렇지 않으면 전체 데이터 반환
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &quot;TableViewCell&quot;, for: indexPath) as! TableViewCell
        
        if searchController.isActive {
            
            cell.label.text = filteredUni[indexPath.row] // 검색 중이면 검색 결과를 표시
        } else {
            cell.label.text = uni[indexPath.row] // 그렇지 않으면 전체 데이터 표시
        }
        
        return cell
    }
}

extension ViewController: UISearchResultsUpdating {
    // 검색 결과가 바뀔때 마다 업데이트 됨
    func updateSearchResults(for searchController: UISearchController) {
        // 검색 바의 텍스트
        guard let searchText = searchController.searchBar.text else { return }
        
        // 검색 바의 텍스트가 포함된 값들만 filteredUni에 담음
        if(searchText != &quot;&quot;) {
            filteredUni = uni.filter { $0.contains(searchText) }
            table.reloadData()
        }
    }
    
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Study/ios</category>
      <author>은덕2</author>
      <guid isPermaLink="true">https://eunduk2.tistory.com/38</guid>
      <comments>https://eunduk2.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 17 Jul 2023 20:50:28 +0900</pubDate>
    </item>
  </channel>
</rss>