송치원 (곰튀김)
iamchiwon.github.io
만들면서 느껴보는 POP
Realism Programmer
Protocol Oriented Programming
내일 배포 전에
메뉴 기능 하나 없애 주세요
내일 완공 전에
화장실 하나 없애 주세요
내일 배포 전에
메뉴 기능 하나 없애 주세요
VS
Hardware
Software
Software 답게 만들기
쉽게 변경할 수 있게 만들기
Protocol Oriented Programming
If someone is oriented towards or oriented to a particular thing or
person, they are mainly concerned with that thing or person.
oriented
[ ɔːrientɪd ]
ADJECTIVE
Protocol Oriented Programming
Object Oriented Programming
Protocol Basic
Delegate 만들기
protocol JsonFetcherDelegate : AnyObject {


func onFetched(json: String)


}


class JsonFetcher {




weak var delegate: JsonFetcherDelegate?




func fetch(url: String) {


guard let url = URL(string: url) else { return }


DispatchQueue.global(qos: .background).async {


if let data = try? Data(contentsOf: url),


let json = String(data: data, encoding: .utf8) {


self.delegate
?
.
onFetched(json: json)


}


}


}


}
class Main {


var fetcher : JsonFetcher {


let o = JsonFetcher()


o.delegate = self


return o


}




func main() {


fetcher.fetch(url: "
.
.
.
")


}


}


extension Main : JsonFetcherDelegate
{


func onFetched(json: String) {


print(json)


}


}
구현 강제하기
protocol JsonFetcherDelegate : AnyObject {


func onFetched(json: String)


}


class JsonFetcher {




weak var delegate: JsonFetcherDelegate?




func fetch(url: String) {


guard let url = URL(string: url) else { return }


DispatchQueue.global(qos: .background).async {


if let data = try? Data(contentsOf: url),


let json = String(data: data, encoding: .utf8) {


self.delegate
?
.
onFetched(json: json)


}


}


}


}
class Main : JsonFetcherDelegate {


var fetcher : JsonFetcher {


let o = JsonFetcher()


o.delegate = self


return o


}




func main() {


fetcher.fetch(url: "
.
.
.
")


}


}
기본구현 넣기
protocol TypePresentable {


func typeName()
-
>
String


}


extension TypePresentable {


func typeName()
-
>
String {


"(type(of: self))"


}


}
extension Int : TypePresentable {}


extension String : TypePresentable {}


print(42.typeName())


/
/
Int


print("Hello World".typeName())


/
/
String


struct MyStruct : TypePresentable {}


print(MyStruct().typeName())


/
/
MyStruct
추상화 하기
protocol Repository {


func save(data: Data)


func load()
-
>
Data?


}


class MemRepository : Repository {


private var data: Data?




func save(data: Data) {


self.data = data


}




func load()
-
>
Data? {


return data


}




}
class FileRepository : Repository {


private var filePath: URL?




func save(data: Data) {


try! data.write(to: filePath!)


}




func load()
-
>
Data? {


return try? Data(contentsOf: filePath!)


}


}
Repository
MemRepository FileRepository
느슨한 결합
protocol Repository {


func save(data: Data)


func load()
-
>
Data?


}


class MemRepository : Repository {


private var data: Data?




func save(data: Data) {


self.data = data


}




func load()
-
>
Data? {


return data


}




}
class ViewModel {


private let repository: Repository




init(repository: Repository) {


self.repository = repository


}


}


let viewModel = ViewModel(repository: MemRepository())
ViewModel Repository
MemRepository
Q. 다음 중 올바르지 않은 것은?
protocol A {


func a()
-
>
Int


}
protocol B {


func b<T: Numeric>()
-
>
T


}
protocol C<T: Numeric> {


func c()
-
>
T


}
protocol D {


associatedtype T: Numeric


func d()
-
>
T


}
1⃣ 2⃣
3⃣ 4⃣
만들면서 느껴보기
1. 있다 치고
2. 알게 뭐야
TodoViewController
class ToDoViewController: UITableViewController {


let service: ToDoService


@IBAction func onCreateToDo(title: String) {


/
/
TODO: create TODO


tableView.reloadData()


}


}


extension ToDoViewController {


override func tableView(tableView:numberOfRowsInSection:)
-
>
Int {


/
/
TODO: get TODOs count


}


override func tableView(tableView:,cellForRowAt:)
-
>
UITableViewCell {


…


let index = indexPath.row


let item =
/
/
TODO: get TODO


cell.todo = item


return cell


}


}
TodoService
protocol ToDoService {


}
TodoViewController
class ToDoViewController: UITableViewController {


let service: ToDoService


@IBAction func onCreateToDo(title: String) {


service.create(title: title)


tableView.reloadData()


}


}


extension ToDoViewController {


override func tableView(tableView:numberOfRowsInSection:)
-
>
Int {


service.count()


}


override func tableView(tableView:,cellForRowAt:)
-
>
UITableViewCell {


…


let index = indexPath.row


let item = service.item(at: index)


cell.todo = item


return cell


}


}
TodoService
protocol ToDoService {


func create(title: String)


func count()
-
>
Int


func item(at: Int)
-
>
ToDo


}
ToDoItemTableViewCell
class ToDoItemTableViewCell: UITableViewCell {


@IBOutlet var isDone: UISwitch!


@IBOutlet var itemTitle: UILabel!


@IBOutlet var updatedAt: UILabel!




var toggable: Toggable?


@IBAction func onToggle() {


/
/
TODO: toggle done


}


}


Toggable
protocol Toggable {


}
ToDoItemTableViewCell
class ToDoItemTableViewCell: UITableViewCell {


@IBOutlet var isDone: UISwitch!


@IBOutlet var itemTitle: UILabel!


@IBOutlet var updatedAt: UILabel!


var toggable: Toggable?




@IBAction func onToggle() {


toggable
?
.
toggle(withId: todo.id)


}


}


Toggable
protocol Toggable {


func toggle(withId id: String)


}
ToDoServiceImpl
class ToDoServiceImpl: ToDoService {


private var todoItems: [ToDo] = []


func create(title: String) {


let todo = ToDo(id: UUID().uuidString,


title: title,


done: false,


createdAt: Date())


todoItems.append(todo)


}


func count()
-
>
Int {


return todoItems.count


}


func item(at index: Int)
-
>
ToDo {


return todoItems[index]


}


}
extension ToDoServiceImpl : Toggable {


func toggle(withId id: String) {


if let found = todoItems.enumerated()


.first(where: { $0.element.id
=
=
id }) {


let index = found.offset


var todo = found.element


todo.done = !todo.done


todoItems[index] = todo


}


}


}
TodoViewController
typealias ToDoToggableService = ToDoService & Toggable


class ToDoViewController: UITableViewController {


let service: ToDoToggableService


@IBAction func onCreateToDo(title: String) {


service.create(title: title)


tableView.reloadData()


}


}


extension ToDoViewController {


override func tableView(tableView:numberOfRowsInSection:)
-
>
Int {


service.count()


}


override func tableView(tableView:,cellForRowAt:)
-
>
UITableViewCell {


…


let index = indexPath.row


let item = service.item(at: index)


cell.todo = item


cell.toggable = service


return cell


}


}
ToDoServiceImpl
class ToDoServiceImpl: ToDoService {


private let repository: Repository


init(repository: Repository) {


self.repository = repository


/
/
TODO: load todos


}


deinit {


/
/
TODO: save todos


}


.
.
.


}
Repository
protocol Repository {


}
ToDoServiceImpl
class ToDoServiceImpl: ToDoService {


private let repository: Repository


init(repository: Repository) {


self.repository = repository


todoItems = repository.load()


}


deinit {


repository.save(todos: todoItems)


}


.
.
.


}
Repository
protocol Repository {


func load()
-
>
[ToDo]


func save(todos: [ToDo])


}
UserDefaultRepository
class UserDefaultRepository: Repository {


private let TodoKey = "todos"


func load()
-
>
[ToDo] {


guard let json = UserDefaults.standard.string(forKey: TodoKey),


let data = json.data(using: .utf8) else {


return []


}


return (try? JSONDecoder().decode([ToDo].self, from: data))
?
?
[]


}


func save(todos: [ToDo]) {


guard let data = try? JSONEncoder().encode(todos),


let json = String(data: data, encoding: .utf8) else {


return


}


UserDefaults.standard.set(json, forKey: TodoKey)


}


}
Codable
struct ToDo: Identifiable, Codable {


let id: String


let title: String


var done: Bool


let createdAt: Date


}
public protocol Decodable {


init(from decoder: Decoder) throws


}


public protocol Encodable {


func encode(to encoder: Encoder) throws


}


public typealias Codable = Decodable & Encodable
ToDoApp Overview
TodoViewController
UITableViewController
TodoService
TodoServiceImpl
Repository
UserDefaultRepository
Model
Toggable
ToDoItemTableViewCell
https://github.com/iamchiwon/TheToDo
Dependency Injection
class AppDelegate: UIResponder, UIApplicationDelegate {


let container = Container()




func application(application:,didFinishLaunchingWithOptions:)
-
>
Bool {


container.register(Repository.self) { _ in UserDefaultRepository() }


container.register(ToDoToggableService.self) { c in


let repository = c.resolve(Repository.self)!


return ToDoServiceImpl(repository: repository)


}




return true


}


}


func Inject<Service>(_ serviceType: Service.Type)
-
>
Service? {


(UIApplication.shared.delegate as? AppDelegate)
?
.
container.resolve(serviceType)


}


class ToDoListViewController: UITableViewController {


let service: ToDoToggableService = Inject(ToDoToggableService.self)!


.
.
.


}
https:
/
/
github.com/Swinject/Swinject
Q. 다음 중 올바르지 않은 것은?
protocol P {}


protocol PROTOCOL {}


extension PROTOCOL: P {}
protocol C {}


class CLASS {}


extension CLASS: C {}
protocol S {}


struct STRUCT {}


extension STRUCT: S {}
protocol E {}


enum ENUM {}


extension ENUM: E {}
1⃣ 2⃣
3⃣ 4⃣
🤔 OOP 에서는 Protocol 의 역할이 많구나!
😏 그렇다면 Protocol을 더 적극적으로 써보자
🧐 class, struct, enum 전부 다 Protocol 적용하게 하자!
😠 Protocol이 진짜 중요해졌네!!!
😎 그렇다면 이제부터 Protocol을 중심으로 코딩해
🤩 그걸 Protocol Oriented Programming 이라고 하자
의식의 흐름
Summary
1. Protocol 로 할 수 있는 것들 == Protocol을 사용해서 얻는 이점
•Delegate
•구현 강제
•추상화
•느슨한 결합
➡ 유연한 확장
2. 구현 과정에서 구현체 보다 Protocol 을 우선해서 생각하자
3. POP == Protocol을 적극 활용하는 OOP
Oriented Programming

20220716_만들면서 느껴보는 POP

  • 1.
    송치원 (곰튀김) iamchiwon.github.io 만들면서 느껴보는POP Realism Programmer Protocol Oriented Programming
  • 2.
    내일 배포 전에 메뉴기능 하나 없애 주세요
  • 3.
    내일 완공 전에 화장실하나 없애 주세요 내일 배포 전에 메뉴 기능 하나 없애 주세요 VS Hardware Software
  • 4.
    Software 답게 만들기 쉽게변경할 수 있게 만들기
  • 5.
    Protocol Oriented Programming Ifsomeone is oriented towards or oriented to a particular thing or person, they are mainly concerned with that thing or person. oriented [ ɔːrientɪd ] ADJECTIVE
  • 6.
  • 7.
  • 8.
  • 9.
    Delegate 만들기 protocol JsonFetcherDelegate: AnyObject { func onFetched(json: String) } class JsonFetcher { weak var delegate: JsonFetcherDelegate? func fetch(url: String) { guard let url = URL(string: url) else { return } DispatchQueue.global(qos: .background).async { if let data = try? Data(contentsOf: url), let json = String(data: data, encoding: .utf8) { self.delegate ? . onFetched(json: json) } } } } class Main { var fetcher : JsonFetcher { let o = JsonFetcher() o.delegate = self return o } func main() { fetcher.fetch(url: " . . . ") } } extension Main : JsonFetcherDelegate { func onFetched(json: String) { print(json) } }
  • 10.
    구현 강제하기 protocol JsonFetcherDelegate: AnyObject { func onFetched(json: String) } class JsonFetcher { weak var delegate: JsonFetcherDelegate? func fetch(url: String) { guard let url = URL(string: url) else { return } DispatchQueue.global(qos: .background).async { if let data = try? Data(contentsOf: url), let json = String(data: data, encoding: .utf8) { self.delegate ? . onFetched(json: json) } } } } class Main : JsonFetcherDelegate { var fetcher : JsonFetcher { let o = JsonFetcher() o.delegate = self return o } func main() { fetcher.fetch(url: " . . . ") } }
  • 11.
    기본구현 넣기 protocol TypePresentable{ func typeName() - > String } extension TypePresentable { func typeName() - > String { "(type(of: self))" } } extension Int : TypePresentable {} extension String : TypePresentable {} print(42.typeName()) / / Int print("Hello World".typeName()) / / String struct MyStruct : TypePresentable {} print(MyStruct().typeName()) / / MyStruct
  • 12.
    추상화 하기 protocol Repository{ func save(data: Data) func load() - > Data? } class MemRepository : Repository { private var data: Data? func save(data: Data) { self.data = data } func load() - > Data? { return data } } class FileRepository : Repository { private var filePath: URL? func save(data: Data) { try! data.write(to: filePath!) } func load() - > Data? { return try? Data(contentsOf: filePath!) } } Repository MemRepository FileRepository
  • 13.
    느슨한 결합 protocol Repository{ func save(data: Data) func load() - > Data? } class MemRepository : Repository { private var data: Data? func save(data: Data) { self.data = data } func load() - > Data? { return data } } class ViewModel { private let repository: Repository init(repository: Repository) { self.repository = repository } } let viewModel = ViewModel(repository: MemRepository()) ViewModel Repository MemRepository
  • 14.
    Q. 다음 중올바르지 않은 것은? protocol A { func a() - > Int } protocol B { func b<T: Numeric>() - > T } protocol C<T: Numeric> { func c() - > T } protocol D { associatedtype T: Numeric func d() - > T } 1⃣ 2⃣ 3⃣ 4⃣
  • 15.
    만들면서 느껴보기 1. 있다치고 2. 알게 뭐야
  • 16.
    TodoViewController class ToDoViewController: UITableViewController{ let service: ToDoService @IBAction func onCreateToDo(title: String) { / / TODO: create TODO tableView.reloadData() } } extension ToDoViewController { override func tableView(tableView:numberOfRowsInSection:) - > Int { / / TODO: get TODOs count } override func tableView(tableView:,cellForRowAt:) - > UITableViewCell { … let index = indexPath.row let item = / / TODO: get TODO cell.todo = item return cell } } TodoService protocol ToDoService { }
  • 17.
    TodoViewController class ToDoViewController: UITableViewController{ let service: ToDoService @IBAction func onCreateToDo(title: String) { service.create(title: title) tableView.reloadData() } } extension ToDoViewController { override func tableView(tableView:numberOfRowsInSection:) - > Int { service.count() } override func tableView(tableView:,cellForRowAt:) - > UITableViewCell { … let index = indexPath.row let item = service.item(at: index) cell.todo = item return cell } } TodoService protocol ToDoService { func create(title: String) func count() - > Int func item(at: Int) - > ToDo }
  • 18.
    ToDoItemTableViewCell class ToDoItemTableViewCell: UITableViewCell{ @IBOutlet var isDone: UISwitch! @IBOutlet var itemTitle: UILabel! @IBOutlet var updatedAt: UILabel! var toggable: Toggable? @IBAction func onToggle() { / / TODO: toggle done } } Toggable protocol Toggable { }
  • 19.
    ToDoItemTableViewCell class ToDoItemTableViewCell: UITableViewCell{ @IBOutlet var isDone: UISwitch! @IBOutlet var itemTitle: UILabel! @IBOutlet var updatedAt: UILabel! var toggable: Toggable? @IBAction func onToggle() { toggable ? . toggle(withId: todo.id) } } Toggable protocol Toggable { func toggle(withId id: String) }
  • 20.
    ToDoServiceImpl class ToDoServiceImpl: ToDoService{ private var todoItems: [ToDo] = [] func create(title: String) { let todo = ToDo(id: UUID().uuidString, title: title, done: false, createdAt: Date()) todoItems.append(todo) } func count() - > Int { return todoItems.count } func item(at index: Int) - > ToDo { return todoItems[index] } } extension ToDoServiceImpl : Toggable { func toggle(withId id: String) { if let found = todoItems.enumerated() .first(where: { $0.element.id = = id }) { let index = found.offset var todo = found.element todo.done = !todo.done todoItems[index] = todo } } }
  • 21.
    TodoViewController typealias ToDoToggableService =ToDoService & Toggable class ToDoViewController: UITableViewController { let service: ToDoToggableService @IBAction func onCreateToDo(title: String) { service.create(title: title) tableView.reloadData() } } extension ToDoViewController { override func tableView(tableView:numberOfRowsInSection:) - > Int { service.count() } override func tableView(tableView:,cellForRowAt:) - > UITableViewCell { … let index = indexPath.row let item = service.item(at: index) cell.todo = item cell.toggable = service return cell } }
  • 22.
    ToDoServiceImpl class ToDoServiceImpl: ToDoService{ private let repository: Repository init(repository: Repository) { self.repository = repository / / TODO: load todos } deinit { / / TODO: save todos } . . . } Repository protocol Repository { }
  • 23.
    ToDoServiceImpl class ToDoServiceImpl: ToDoService{ private let repository: Repository init(repository: Repository) { self.repository = repository todoItems = repository.load() } deinit { repository.save(todos: todoItems) } . . . } Repository protocol Repository { func load() - > [ToDo] func save(todos: [ToDo]) }
  • 24.
    UserDefaultRepository class UserDefaultRepository: Repository{ private let TodoKey = "todos" func load() - > [ToDo] { guard let json = UserDefaults.standard.string(forKey: TodoKey), let data = json.data(using: .utf8) else { return [] } return (try? JSONDecoder().decode([ToDo].self, from: data)) ? ? [] } func save(todos: [ToDo]) { guard let data = try? JSONEncoder().encode(todos), let json = String(data: data, encoding: .utf8) else { return } UserDefaults.standard.set(json, forKey: TodoKey) } }
  • 25.
    Codable struct ToDo: Identifiable,Codable { let id: String let title: String var done: Bool let createdAt: Date } public protocol Decodable { init(from decoder: Decoder) throws } public protocol Encodable { func encode(to encoder: Encoder) throws } public typealias Codable = Decodable & Encodable
  • 26.
  • 27.
    Dependency Injection class AppDelegate:UIResponder, UIApplicationDelegate { let container = Container() func application(application:,didFinishLaunchingWithOptions:) - > Bool { container.register(Repository.self) { _ in UserDefaultRepository() } container.register(ToDoToggableService.self) { c in let repository = c.resolve(Repository.self)! return ToDoServiceImpl(repository: repository) } return true } } func Inject<Service>(_ serviceType: Service.Type) - > Service? { (UIApplication.shared.delegate as? AppDelegate) ? . container.resolve(serviceType) } class ToDoListViewController: UITableViewController { let service: ToDoToggableService = Inject(ToDoToggableService.self)! . . . } https: / / github.com/Swinject/Swinject
  • 28.
    Q. 다음 중올바르지 않은 것은? protocol P {} protocol PROTOCOL {} extension PROTOCOL: P {} protocol C {} class CLASS {} extension CLASS: C {} protocol S {} struct STRUCT {} extension STRUCT: S {} protocol E {} enum ENUM {} extension ENUM: E {} 1⃣ 2⃣ 3⃣ 4⃣
  • 29.
    🤔 OOP 에서는Protocol 의 역할이 많구나! 😏 그렇다면 Protocol을 더 적극적으로 써보자 🧐 class, struct, enum 전부 다 Protocol 적용하게 하자! 😠 Protocol이 진짜 중요해졌네!!! 😎 그렇다면 이제부터 Protocol을 중심으로 코딩해 🤩 그걸 Protocol Oriented Programming 이라고 하자 의식의 흐름
  • 30.
    Summary 1. Protocol 로할 수 있는 것들 == Protocol을 사용해서 얻는 이점 •Delegate •구현 강제 •추상화 •느슨한 결합 ➡ 유연한 확장 2. 구현 과정에서 구현체 보다 Protocol 을 우선해서 생각하자 3. POP == Protocol을 적극 활용하는 OOP Oriented Programming