[iOS] TIL

Zoe·2023년 10월 10일
0

iOS

목록 보기
23/39

✅ storyboard 장단점

1️⃣ 장점

  • 처음오는 개발자도 한눈에 어떤 화면들이 있는지 파악이 용이
  • UI 관련 내용이 코드와 분리가 되어 코드의 복잡도가 줄어드는 점

2️⃣ 단점

  • 협업 시 merge conflict 해결이 어려움
  • Storybaord를 열기만 해도 수정사항이 생기므로 git add . 할 때 구분 필요
  • Storyboard에 여러개의 UIViewController가 많아지면 느려지는 문제
  • property들을 살펴볼 때 오른쪽 Attributes inspector를 통해 하나하나씩 확인해야 하므로 섬세하게 property들을 파악하기가 코드 베이스보단 힘든 점

✅ Safearea

  • 세로 모드일때는 home indicator, navigation bar가 위치하는 상하로만 margin이 생긴다.
  • 가로 모드일때는 아래와 좌우에 margin이 생기는 모습

✅ Left Constraint, Leading Constraint

Apple은 공식적으로 left/right constraint 보다 leading/trailing의 사용을 권장하고 있다!
우선 left/right은 사용자가 보는 화면상의 왼쪽과 오른쪽 위치 속성이다
반면에 leading/trailing은 reading direction의 시작과 끝을 나타내는 위치 속성!

leading, trailing으로 설정해주면 알아서 위치를 flip을 해준다.
그렇기 때문에 Apple 에서도 left/right 대신 leading, trailing을 권하는 것이다.
하지만 만약 아랍권에서도 쓸 애플리케이션의 어떤 UI component가 어떤 일이 있어도,
화면상의 왼쪽 또는 오른쪽에 붙어있길 바란다면 그 땐 left, right constraint를 걸어줘야 한다.

✅ Auto Layout, Frame-based Layout

let blueView = UIView(frame: CGRect(x: 80.0, y: 244.0, width: 160.0, height: 80.0))
blueView.backgroundColor = UIColor.blue
self.view.addSubview(blueView)

✅ 성능 향상을 위한 디버깅 도구

1️⃣ Instruments

사용

  • Time Profiler: CPU 사용률을 분석, 애플리케이션 내의 병목 지점
  • Memory Allocations: 메모리 사용 패턴을 확인, 메모리 누수 체크
  • Leaks: 메모리 누수를 탐지
  • Core Animation: 애니메이션 성능을 디버깅하고 최적화
  • Energy Log: 애플리케이션의 에너지 사용량을 분석

2️⃣ LLDB Debugger

사용

  • 런타임 중 변수의 값을 검사하거나 수정
  • 코드의 특정 지점에서 실행을 일시 중단
  • break points을 설정하여 특정 조건에서만 중단점이 활성화

3️⃣ Xcode Memory Graph Debugger

사용 사례

  • 강한 참조 순환(retain cycles)을 찾고 해결
  • 객체 간의 참조 관계를 확인

4️⃣ Xcode View Debugger

사용

  • 겹치는 뷰나 숨겨진 뷰 문제를 체크
  • 뷰의 속성과 제약 조건을 실시간으로 검사

5️⃣ Sanitizers

사용

  • Address Sanitizer (ASan): 메모리 관련 버그, 더블 해제, 버퍼 오버플로우 등을 탐지
  • Thread Sanitizer (TSan): 데이터 경쟁 조건과 같은 다중 스레딩 문제를 탐지
  • Undefined Behavior Sanitizer (UBSan): 정의되지 않은 동작 탐지

✅ struct, class, enum

1️⃣ struct, class

공통점

  • 프로퍼티 정의가 가능
  • 메소드 정의가 가능
  • initializer 정의가 가능
  • extension이 가능
  • protocol이 가능

차이점

  • 구조체는 value type, 클래스는 reference type
  • 구조체는 상속이 불가능, 클래스는 단일 상속만 가능
  • 구조체에서는 AnyObject로 타입캐스팅이 불가능
  • 구조체는 생성자를 구현하지 않을 시 기본 initializer를 사용할 수 있음
  • 클래스는 reference counting으로 메모리 관리가 가능
struct UserData {
    var name: String
    var age: Int
    mutating func updateUser(_ name: String, _ age: Int) {
        self.name = name
        self.age = age
        
        print("이름 : \(name), 나이 : \(age)")
    }
}
var shark = UserData(name: "상어", age: 16)
print("이름 : \(shark.name), 나이 : \(shark.age)")
// 이름 : 상어, 나이 : 16
var candy = shark
candy.name = "캔디"
candy.age = 30
print("이름 : \(shark.name), 나이 : \(shark.age)")
// 이름 : 상어, 나이 : 16
print("이름 : \(candy.name), 나이 : \(candy.age)")
// 이름 : 캔디, 나이 : 30
shark.updateUser("띠용", 33)
// 이름 : 띠용, 나이 : 33
  • 값 복사이면서 copy-on-write이기 때문에 대입할 때 복사가 일어나는 것이 아닌,
    수정이 발생할 때 값이 복사된다.
  • 구조체는 value type이기 때문에 method안에서 property변경이 불가능하다. 그래서 mutating을 붙이면 해당 객체가 다시 생성되면서 변경이 가능하다.
class Phone {
    var name: String
    var color: String

    // struct와 달리 class는 이니셜라이즈를 지정해야합니다.
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }

    func updatePhone(name: String, color: String) {
        self.name = name
        self.color = color

        print("폰 : \(name), 색상 : \(color)")
    }
}

let iPhone8 = Phone(name: "iPhone8", color: "red")
print("폰 : \(iPhone8.name), 색상 : \(iPhone8.color)")
// 폰 : iPhone8, 색상 : red

var iPhoneXs = iPhone8
iPhoneXs.name = "iPhoneXs"
iPhoneXs.color = "grey"
print("폰 : \(iPhone8.name), 색상 : \(iPhone8.color)")
// 폰 : iPhoneXs, 색상 : grey

print("폰 : \(iPhoneXs.name), 색상 : \(iPhoneXs.color)")
// 폰 : iPhoneXs, 색상 : grey

iPhoneXs.updatePhone(name: "iPhoneX", color: "black")
// 폰 : iPhoneX, 색상 : black
  • class는 reference type이기 때문에 iPhoneXs를 iPhone8을 대입하고 iPhoneXs의 속성을 변형하면 iPhone8에 대한 속성도 함께 변형이 된다.
  • class는 method안에서 property 변경이 가능하기 때문에 mutating을 붙이지 않아도 된다.

struct는 언제 사용할까?

  • 관계된 간단한 값을 캡슐화 하기 위한 것일 때
  • 인스터스가 참조되기 보다 복사되기를 바랄 때
  • 프로퍼티가 참조되기 보다 복사되기를 바랄 때
  • 프로퍼티나 메소드 등을 상속할 필요가 없을 때

2️⃣ Enum

  • 열거해서, 내가 원하는 값들을 사용할 때
  • 상속이 불가능하다
enum Animal {
    case dog
    case cat
    case rabbit
    case etc

    func printAnimal() {
        switch self {
        case .dog:
            print("개 입니다.")
        case .cat:
            print("고양이 입니다.")
        case .rabbit:
            print("토끼 입니다.")
        default:
            print("그 외 입니다.")
        }
    }
}
Animal.etc.printAnimal()
// 그 외 입니다.
  • 아래 사용 예제
// MARK: - Struct
struct Student {
    let name: String?
    let birth: String?
    let grade: String?
    let gender: String?
}

struct Teacher {
    let name: String?
    let job: String?
}

// MARK: - Enum
enum School {
    case students(Student)
    case teachers(Teacher)
}

// MARK: - Public
func printStudent(_ student: Student) {
    guard let name = student.name,
        let birth = student.birth,
        let grade = student.grade,
        let gender = student.gender
        else { return }

    print("이름 : \(name), 생년월일 : \(birth), 학년 : \(grade), 성별 : \(gender) ")

}
func printTeacher(_ teacher: Teacher) {
    guard let name = teacher.name,
        let job = teacher.job
        else { return }
    print("선생님 성함 : \(name), 선생님 과목 : \(job)")
}

// MARK: - Property
// student 초기값 설정
let shark = Student(name: "상어",
                    birth: "1999.02.27",
                    grade: "3",
                    gender: "여") 
// teacher 초기값 설정
let candy = Teacher(name: "캔디",
                    job: "수학") 
var student: School? {
    didSet {
        // students case의 연관값을 받아옵니다.
        guard case .students(let student)? = student
            else { return }
        printStudent(student)
    }
} 

var teacher: School? {
    didSet {
        // teachers case의 연관값을 받아옵니다.
        guard case .teachers(let teacher)? = teacher
            else { return }
        printTeacher(teacher)
    }
}

student = School.students(shark)
// 이름 : 상어, 생년월일 : 1999.02.27, 학년 : 3, 성별 : 여

teacher = School.teachers(candy)
// 선생님 성함 : 캔디, 선생님 과목 : 수학
  • 연관값 타입으로는 String, Int같은 기본 데이터 타입도 가능하지만, struct이나 enum도 가능하다.

✅ class 성능 향상 방법

  • Class는 struct가 인스턴스 내부의 변수 개수에 맞추어 2words의 size로 stack에 할당되는 것과 달리 heap에 4words size로 할당된다.

  • class를 사용하게 되면, Heap allocation을 사용하기 때문에 struct보다 더 많은 비용이 필요하다!!! 그렇기 때문에 class의 특성이 필요하지 않으면 struct를 사용하는게 좋다!

1️⃣ reference counting





  • reference counting이 0이 되면 Swift는 해제해도 안전하다고 판단하고, heap을 lock하고 메모리 블럭을 반환한다.

2️⃣ method dispatch

  • Method dispatch는 프로그램이 어떤 메소드를 호출할 것인지 결정하여 그 메소드를 호출하는 과정
  • static dispatch vs. dynamic dispatch

static dispatch

  • Static method dispatch는 컴파일 시점에 컴파일러가 메소드의 실제 코드 위치를 파악할 수 있어 런타임에 찾는 과정 없이 바로 해당 코드를 실행하는 것

dynamic dispatch

  • Dynamic method dispatch는 컴파일 타임에 어떤 메소드를 호출하는지 판단할 수 없어, 런타임에 table에 구현을 참조하여 해당 메소드에 대한 정보를 가져와서 코드를 실행시키는 것을 의미

final

  • 서브클래스를 만들지 않을 클래스라면, final을 클래스 앞에 선언하여 컴파일러가 static하게 dispatch하게 할 수 있다!!!

private

  • 파일 내에서만 접근해도 되는 경우에는 private 선언
  • private로 선언하게 되면 참조 가능한 범위가 private로 선언된 파일 혹은 블록 내에서만 가능하다. 컴파일러는 private가 붙은 프로퍼티에 대해서 오버라이드가 될 수 있는지 없는지를 판단해 오버라이드하는 곳이 없다면 final 키워드로 추론하게 된다. class 앞에 private 키워드를 붙이면 클래스 내부의 모든 프로퍼티, 메서드에 private가 붙은 것으로 동작하게 된다.
    ***참고 : https://corykim0829.github.io/swift/Understanding-Swift-Performance/#

✅ Copy On Write

  • 구조체 객체를 하나 만들고 다른 변수에 복사를 하더라도, 바로 구조체 내의 모든 값이 복사되는 것이 아닌, 일단 둘 다 같은 객체를 참조하다가, 값이 변경될 때만 복사를 진행해서 메모리나, computation power를 최대한 아끼는 방식이다.

  • 변경이 없는데도 2배의 메모리를 가지고 있을 필요가 없으므로, 수정 전까지는 같은 메모리 주소를 참고하고 있는 형태인 것이다.
  • Int, Double, String, Array, Set, Dictionary 등에 COW가 구현되어 있음
  • Custom Struct에는 구현되어 있지 않아서, 만약 해당 기능을 원한다면 직접 구현해줘야 한다.

✅ convenience init

init에는 designated init과 convenience init이 있다.

그 중 Convenience init은 보조 이니셜라이저로, 내부에서 같은 클래스의 designated init을 호출하여 클래스의 원래 이니셜 라이저를 도와주는 역할을 한다.
Convenience init은 모든 프로퍼티를 받아야 하는 designated init과 달리, 필요한 부분만 파라미터로 받아서 초기화할 수 있다.

class Person {
    var name: String
    var age: Int
    var gender: String

    init(name: String, age: Int, gender: String) {
        self.name = name
        self.age = age
        self.gender = gender
    }
    convenience init(age: Int, gender: String) {
        self.init(name: "zedd", age: age, gender: gender)
    }
}
  • designated init이 꼭 먼저 선언되어 있어야 한다.
  • init의 보조이기 때문에 위에서 선언한 init을 활용해서 사용!!

✅ Any, AnyObject

  • Any : any type -> class, struct, enum, function, optional types
  • Any 타입엔 Value 타입(구조체, 열거형), Reference 타입(클래스, 클로저)이건 상관 없이 모두 섞어서 저장이 가능
var things: [Any] = []
things.append(1)
things.append(1.0)
things.append("a")
things.append(false)        
things.append(Human.init()))        
things.append({ print("I am Ann") })  
var things: [AnyObject] = []
 
things.append(1)                                // Argument type 'Int' expected to be an instance of a class
things.append(1.0)                              // Argument type 'Double' expected to be an instance of a class
things.append("sodeul")                         // Argument type 'String' expected to be an instance of a class
things.append(false)                            // Argument type 'Bool' expected to be an instance of a class
things.append(Teacher.init()))        
things.append({ print("I am Sodeul!") })        // Argument type '()->()' expected to be an instance of a class
  • 참조로 작업하는 동안에는 AnyObject를 사용하고 값 유형으로 작업할 때는 Any를 사용하는 것이 좋다
  • 참고: 가능하다면 Any와 AnyObject를 모두 피해야 한다. 타입은 구체적인 것이 좋다.
profile
iOS 개발자😺

0개의 댓글

관련 채용 정보