개발하는 뚝딱이

[swift] 스위프트를 더 스위프트스럽게 사용하기 본문

swift

[swift] 스위프트를 더 스위프트스럽게 사용하기

개발자뚝딱이 2021. 3. 11. 00:00

안녕하세요, 개발하는 뚝딱이입니다! 오늘은 Swift의 유니크한 특징을 공부했습니다.

Swift로 코드를 작성하지만, Swift의 장점을 잊고 습관처럼 코딩할 때가 많습니다.

그럴 때 한 번쯤 참고하면 좋을 글입니다.

원문은 아래 링크입니다.

medium.com/geekculture/when-you-write-code-in-swift-write-code-in-swift-abdac43d44fa

 

 

 

 

 

 

 

fore case

[Any]나 NSOrderSet의 경우, 타입을 체크할 필요 없이 아래와 같이 사용합니다.

let array: [Any] = [object1, object2, object3]

// Instead of:
for element in array {
    if let element = element as? YourClass {
        // do something
    }
}

// Do:
for case let element as YourClass in array {
    // do something
}

 

 

enumerated() 

index와 원소에 접근하기 위해선 for i in 0..<array.count를 실행하는 것보다 .enumerated()를 실행하는 것이 좋습니다.

for i in 0..<array.count를실행하는 것이 편리할 땐 당연히 쓰는 것이 좋지만, 항상 최선이 아니란 것을 기억해주세요.

let array: [Any] = [object1, object2, object3]

// Instead of:
for i in 0..<array.count {
    let element = array[i]
    // do something
}

// Do:
for (i, element) in array.enumerated() {
    // do something

 

 

first(where:)

filter는 스위프트에서 가장 인기 있는 메서드이지만, 때론 다른 메서드가 적절합니다.

조건에 해당하는 첫 번째 원소를 가져올 땐 first(where:)을 사용할 수 있습니다.

// Instead of:
if let element = array.filter { $0.title.contains(searchString) }.first {
    // do something
}

// Do:
if let element = array.first(where: { $0.title.contains(searchString) }) {
    // do something
}

 

 

contains(where:)

filter 대신에 사용할 메서드입니다.

// Instead of:
if array.filter { !$0.isActive }.count > 0 {
    // do something
}

// Do:
if array.contains(where: { !$0.isActive }) {
    // do something
}

 

 

isEmpty

배열이 비어있는지 확인하기 위해 count대신 isEmpty를 사용해보세요

// Instead of:
if array.count == 0 {
    // do something
}

// Do:
if array.isEmpty {
    // do something
}

 

 

forEach

복잡하지 않은 코드를 실행할 때 사용하면 좋은 간단한 메서드입니다

// Instead of:
for element in array {
    doSomething(with: element)
}

// Do:
array.forEach { doSomething(with: $0) }

 

 

map, compactMap, filter, ... 의 keyPaths

아래와 같은 경우 KeyPaths는 간단합니다. 메서드들에 체이닝을 걸 수 있는 장점도 있습니다

// Instead of:
let filteredArray = array.filter { $0.isActive }
let titles = filteredArray.map { $0.title }

// Do:
let filteredArray = array.filter(\.isActive)
let titles = filteredArray.map(\.title)

// Or even:
let titles = array
    .filter(\.isActive)
    .map(\.title)

 

 

guard

if문을 많이 사용하는 경우에 쓰면 좋습니다

// Instead of:
if oneCondition {
    if secondCondition {
        performAction()
    } else {
        returnError()
    }
} else {
    returnError()
}

// Or even:
if oneCondition, secondCondition {
    performAction()
} else {
    returnError()
}

// Do this:
guard oneCondition, secondCondition else {
    returnError()
    return
}
performAction()
view raw

 

 

defer

defer은 많이 유용합니다. defer 블럭 코드는 return이 실행된 후 호출될 것입니다

func pop() -> Value? {
    defer {
        head = head?.next
    }
    return head?.value
}

func loadSomething(completion: () -> Void) {
    defer {
        isLoading = false
        completion()
    }
    isLoading = true
    guard someCondition else {
        return
    }
    doSomething()
}

 

 

Calculated properties vs. methods

메서드를 만드는 것보다, calculated variable를 사용하는 것이 더 나을 때도 있습니다. 상황에 따라 다르므로 올바른 것을 선택해야 합니다.

확실하게 연산 처리를 해야 한다면 메서드를 사용하고, 그렇지 않은 경우 calculated variable를 고려해보세요.

// Instead of:
class YourManager {
    static func shared() -> YourManager {
        ...
    }
}
let manager = YourManager.shared()
// Or:
extension Date {
    func formattedString() -> String {
        // convert date to a readable string
    }
}
let string = Date().formattedString()

// Do this:
class YourManager {
    static var shared: YourManager {
        ...
    }
}
let manager = YourManager.shared
// And this:
extension Date {
    var formattedString: String {
        // convert date to a readable string
    }
}
let string = Date().formattedString

 

 

가능하면 self 사용을 피하기

굳이 self를 쓸 필요가 없을 땐 오남용 하지 않는 것이 좋습니다.

self는 꼭 써야만 하는 경우나, 코드 이해에 도움이 된다면 사용하는 것을 권장합니다

class Foo {
    var index: Int
    var name: String
    
    init(index: Int, name: String) {
        self.index = index // you have to use `self.` here
        self.name = name
    }
    
    func increment() {
        index += 1 // don't use `self.index += 1` here
    }
}

 

 

언래핑을 할 때 같은 이름으로 네이밍하기

// Instead of
closure() { [weak self] in
    guard let strongSelf = self else { return }
    strongSelf.updateUI()
}
// Or:
func update(with name: String?) {
    guard let strongName = name else { return }
    self.nameLabel.text = strongName
}

// Do this;
closure() { [weak self] in
    guard let self = self else { return }
    self.updateUI()
}
// Or this:
func updateName(_ name: String?) {
    guard let name = name else { return }
    self.nameLabel.text = name
}

 

 

강력한 protocol extension

protocl의 메서드를 구현할 때 extension을 사용할 수 있습니다

protocol YourProtocol {
    func requiredMethod()
    func optionalMethod()
}

extension YourProtocol {
    func optionalMethod() {
        print("Default implementation")
    }
}

class Foo: YourProtocol {
    func requiredMethod() {
        print("requiredMethod")
    }
}

class Foo2: YourProtocol {
    func requiredMethod() {
        print("requiredMethod")
    }
    
    func optionalMethod() {
        print("Custom implementation")
    }
}

let foo: YourProtocol = Foo()
foo.optionalMethod() // prints "Default implementation"
let foo2: YourProtocol = Foo2()
foo2.optionalMethod() // prints "Custom implementation"

 

 

Protocol vs. Subclassing

상속 대신에 프로토콜을 사용하는 건 어떨까요? 필요에 따라 여러 개의 프로토콜을 준수하고, 따라서 코드 구현을 더욱 쉽게 할 수 있습니다.

struct나 enum은 subclassing을 사용할 수 없지만, protocol을 준수할 수 있다는 장점도 있습니다

 

 

Structs vs. Classes

가능하면 struct를 사용하는게 좋습니다. 멀티 쓰레드 환경에서 안전하게 사용할 수 있고, 빠르고, 기본 이니셜라이저를 제공하는 등의 장점이 많습니다. struct는 value type인 반면, class는 reference type입니다. struct는 개별의 copy 값을 가지지만, class는 하나의 데이터에 참조값을 가진 다는 것을 기억해야만 합니다.

 

'swift' 카테고리의 다른 글

[swift] 프로답게 스위프트로 코딩하기  (0) 2021.08.17
[swift] 프로토콜 초기구현  (0) 2020.09.28
[swift] 프로토콜  (0) 2020.09.21
[swift] 상속  (0) 2020.09.05
[swift] 서브스크립트  (0) 2020.06.25