alert dialog

예제를 통해 알아보자

@IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
        let alert = UIAlertController(title: "Add New Todoey Item", message: "", preferredStyle: .alert)
        var textField = UITextField()
        
        let action = UIAlertAction(title: "Add Item", style: .default) { action in
            self.itemArray.append(textField.text!)
            // 새로운 element를 추가한 뒤 반드시 reload를 해줘야 한다.
            self.tableView.reloadData()
        }
        
        //alert dialog에 textfield 추가
        alert.addTextField { alertTextField in
            alertTextField.placeholder = "Create new item"
            textField = alertTextField
        }
        
        //위에서 정의한 action을 dialog에 추가
        alert.addAction(action)
        
        //정의가 끝난 alertdialog를 현재 viewcontroller에 추가
        present(alert, animated: true, completion: nil)
    }

UserDefaults 관련 토막상식

  1. UserDefaults는 Singleton Pattern으로 만들어져서 중복으로 Instance를 만들어도 같은 것이 호출된다.
  2. UserDefaults에서 하나의 값만 가져오려고 해도 전체 UserDefaults plist를 불러오기 때문에 최대한 작게 유지해야 한다. 앱에 퍼포먼스에 큰 영향을 끼칠 것이다.
  3. Custom class를 UserDefaults에 넣으면 문제가 생긴다. 반드시 Primitive type이나 primitive type으로 이뤄진 collection만 사용해야 한다.

⭐️⭐️ TableView에서 cell 상태관리 주의점

TableView에서 reusable cell을 이용할 때 주의점

TableView DataSource method에서 tableView.dequeueReusableCell 메소드를 이용해서 cell을 구성하는 경우, 스크롤을 내리며 뷰에서 사라지는 셀들이 밑에서 재사용되고, 애초에 이런 리소스 효율성을 위해 있는 게 ReusableCell이다.

그렇다고 각 cell에 새로운 UITableViewCell object를 할당해주면, 스크롤을 내렸다가 올릴 때 설정 (e.g. checked accesary) 이 초기화 된다.
뷰에서 사라지는 순간 deallocate -> destroy되기 때문이다.

따라서 cell이 아니라 cell이 담고 있는 data를 기준으로 설정을 표시 & 유지 해줘야 한다.

NSCoder를 이용한 데이터 저장 (강의 246~248)

  1. FileManager를 통해서 저장할 path를 정한다.
  2. PropertyListEncoder() 클래스 인스턴스를 생성해서, 데이터들을 local 저장소에 write한다
    1. 주의! 데이터를 저장할 때 JSON 형식으로 plist 저장되기 때문에 저장될 데이터 모델은 Encodable 프로토콜을 채택해야 한다.
  3. 그 후 이 데이터를 가져올 땐 PropertyListDecoder()를 통해서 read 한다. 이것을 위해 데이터 모델은 Encodable과 Decodable을 합친 Codable로 선언한다.

iOS의 Local data storage들

For small datas

  • UserDefaults: 간단한 정보 (유저가 앱을 사용할 때 설정한 boolean 값들) 저장용
  • Codable: 간단한 정보지만 Primitive type이 아닐 때
  • Keychain: 간단한 정보지만 높은 보안수준을 요구할 떄 (유저 password, id, 개인정보)

For big datas (Database solution)

  • SQLite: SQL 이 익숙한 사람 추천
  • Core Data: SQLite와 비슷하지만 훨씬 많은 기능 (data monitoring)을 가진 객체 지향 데이터베이스
  • Realm: Core Data와 거의 비슷하지만 훨씬 빠르고 효율적인 프레임워크

AppDelegate의 프로퍼티에 접근하기

(UIApplication.shared.delegate as! AppDelegate) 라는 코드를 통해서 할 수 있다.
UIApplication.shared <- 현재 어플리케이션 오브젝트를 singleton 패턴으로 불러온다.
UIApplication.shared.delegate <- 현재 어플리케이션 오브젝트의 대리자. 즉 AppDelegate!

Core Data의 개념과 사용 방법

Core Data는 sqlite db를 기반으로 persistent container에 데이터를 저장할 수 있는 기능을 가진 프레임워크다. 단순 DB나 API가 아니며, local data storage 이외에도 다양한 쓰임새가 있다.

Core data는 context를 통해서 앱과 persistent store를 연결한다. 현재 정보들의 변화 상태를 context에 적용시키다가, 원하는 순간에 context.save()를 호출하면 현재 context에 있는 정보들이 core data에 그대로 저장된다. 쉽게 말해서 context는 git의 staging area 같은 것이고, context.save()는 commit과 같다고 이해하면 된다.

Good References
https://zeddios.tistory.com/987 <- 개념 및 간단 실습
https://velog.io/@leeesangheee/Core-Data-%EC%82%AC%EC%9A%A9%ED%95%B4-CRUD-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0 <- CRUD 간단 예제

개인 자료 라이브러리, API, 프레임워크

프레임워크: 개발할 때 자주 사용되는 기능을 한꺼번에 제공하며 말 그대로 뼈대 (Frame)을 잡아주는 소프트웨어
라이브러리: 기능의 종류나 목적에 따라 카테고리를 나눠서 정의한 API나 함수의 묶음
API: 프로그램과 프로그램을 연결해주는 다리 역할.

프레임워크랑 라이브러리의 다른 점: 코드의 컨트롤을 누가 하느냐.
라이브러리 : 내가 코딩을 하다가 필요할 때만 라이브러리를 import해서 마음대로 사용한다.
프레임워크: 프레임워크가 정의한 규칙에 따라 내가 코드를 맞춰서 작성한다. 프레임워크가 정의한 규칙을 공식문서를 통해서 그대로 따라가야 한다.

Core Data의 Query (NSPredicate)

extension ToDoListViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        //create request by Explicitly declaring request type (제네릭스 사용해서 명시하라고)
        let request: NSFetchRequest<Item> = Item.fetchRequest()
        
        // make a query. More info at https://academy.realm.io/posts/nspredicate-cheatsheet/
        let predicate = NSPredicate(format: "title CONTAINS[cd] %@", searchBar.text!)
        request.predicate = predicate
        
        // 정렬 방식도 지정해줄 수 있음.
        let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
        request.sortDescriptors = [sortDescriptor]
        
        // Now, fetch the request to our context!
        do {
            itemArray = try context.fetch(request)
        } catch  {
            print("Error fetching data from context \(error)")
        }
    }
}

NSPredicate Cheatsheet

https://academy.realm.io/posts/nspredicate-cheatsheet/

⭐️⭐️⭐️ Realm

https://www.mongodb.com/realm <- MongoDB Realm 공식 사이트
https://www.mongodb.com/docs/realm/tutorial/ios-swift/ <- RealmSwift 공식 문서
https://github.com/realm/realm-swift <- RealmSwift GitHub
https://velog.io/@yoonjong/Swift-Realm-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0 -> 간단한 CRUD 예제

⭐️⭐️ Deprecate된 문법들!

@Persisted 로 @objc dynamic 대체

  • @objc dynamic: 앱 runtime 중에 특정 데이터 (프로퍼티의 값)을 Realm이 동적으로 바꿀 수 있게 해줌
  • @Persisted: 기존의 @objc dynamic이 @Persisted로 변경되었다! 두가지를 혼용해서 쓰면 @objc dynamic 프로퍼티가 무시된다고 하니 Persisted를 사용하자!

LinkingObjects init 문법 변화

예전

class User: Object {
    @objc dynamic var _id: ObjectId = ObjectId.generate()
    @objc dynamic var _partition: String = ""
    @objc dynamic var name: String = ""

    // A user can have many tasks.
    let tasks = List<Task>()

    override static func primaryKey() -> String? {
        return "_id"
    }
}

class Task: Object {
    @objc dynamic var _id: ObjectId = ObjectId.generate()
    @objc dynamic var _partition: String = ""
    @objc dynamic var text: String = ""

    // Backlink to the user. This is automatically updated whenever
    // this task is added to or removed from a user's task list.
    let assignee = LinkingObjects(fromType: User.self, property: "tasks")

    override static func primaryKey() -> String? {
        return "_id"
    }
}

지금

class User: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var _partition: String = ""
    @Persisted var name: String = ""

    // A user can have many tasks.
    @Persisted var tasks: List<Task>
}

class Task: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var _partition: String = ""
    @Persisted var text: String = ""

    // Backlink to the user. This is automatically updated whenever
    // this task is added to or removed from a user's task list.
    @Persisted(originProperty: "tasks") var assignee: LinkingObjects<User>
}

Realm 파일 찾기

앱 시작시 print(Realm.Configuration.defaultConfiguration.fileURL) 라는 코드를 appdelegate에 넣으면 절대경로가 콘솔에 나오고, 파인더에서 cmd+shift+g를 누르고 해당 경로로 가서 .realm 파일을 열면 된다.

Realm 파일 읽기

Realm Browser를 사용하면 .realm 파일을 열면 아래와 같은 메시지가 나온다.

이를 회피하려면 Realm Browser의 전 버전인 Realm Studio를 이 링크에서 직접 다운로드해서 쓰면 된다.

Chameleon Framework

여러가지 색상과 그에 대비되는 textcolor를 편하게 적용할 수 있는 framework다.
링크: https://github.com/wowansm/Chameleon/tree/swift5

유의사항: swift 5 이상에서 카멜레온을 쓰려면 Podfile에 branch 명까지 정확히 기입해서

pod 'ChameleonFramework/Swift', :git => 'https://github.com/wowansm/Chameleon.git',
:branch => 'swift5'

라고 적어야 pod install이 정상적으로 된다.

⭐️⭐️⭐️ Navigation bar color를 페이지 마다 바꿀 때

평상적으로 viewDidLoad()에 네비게이션바의 색을 바꿔주는 코드를 작성하면 된다고 생각할 수 있지만 막상 실행해보면 코드가 작동되지 않는다.
view는 로딩이 끝났지만 navigationController의 navigation stack에는 아직 현재화면이 안 올라왔을 수도 있기 때문이다

따라서 ViewController의 lifecycle에서 view가 화면에 뜨기 바로 직전인 viewWillAppear function을 오버라이드해서 거기에서 색을 바꿔줘야 한다.

💡 Tip. iOS 13 이후론 navigationController?.navigationBar.barTintColor 가 아니라 navigationController?.navigationBar.backgroundColor다

Swift Access Levels Keywords

다음과 같은 키워드로 변수/함수의 Access Level을 정할 수 있다.
1. private : 해당 클래스 내에서만 사용가능하다.
2. fileprivate: 같은 파일 (i.e. 같은 .swift 파일) 내에서만 사용 가능하다.
3. internal: 현재 앱에서만 사용 가능하다. swift에서 변수/함수 선언시 default가 internal이다
4. public: 다른 앱에서도 사용 가능하다. Cocoapods와 같이 다른 앱에서도 적용 가능한 함수/변수를 작성할 때 사용한다.
5. open: 퍼블릭과 유사하지만 다른 앱에서 해당 변수/함수를 subclass과 override 시켜서 변하게 할 수 있다.

guard let VS if let

동작 상 큰 차이점이 없기 때문에 Convention에 따라 맞추면 될 것 같다.
Guard let은 말 그대로 절대로 nil이 발생하면 안되는 Optional 을 unwrap 할 때 쓰인다. 따라서 guard let의 else 부분엔 fatalError 같은 걸 넣어줘서 현재 로직에 중대한 문제가 생겼음을 개발자에게 알리는 용도로 자주 쓰이는 것 같다.

그와 반대로 못된 유저들이 동일한 버튼을 여러번 누르거나 흐름 상 nil이 나올 수도 있고, nil이 발생해도 큰 문제가 없는 부분에 주로 if let을 사용한다.

물론 if let과 guard let 둘 다 동일한 동작을 할 수 있기에 크게 중요한 부분은 아니지만, 이런 습관을 통해 코드 가독성을 향상시킬 수 있다고 본다.

profile
CJ ENM iOS 주니어 개발자

0개의 댓글