GRDB를 사용하다가 생긴 문제점 공유

GRDB를 사용하다보니 JOIN시에 해당 구조체를 가져오는게 쉽지 않다는 문제점을 발견했다.
이를 해결하기 위해, 검색을 하다보니 GRDB를 더 깊게 이해해야 한다는 문제점에 도달했다.

Sqlite3으로 모든 작업을 돌리기로 결정.
이유는 Sqlite3은 내가 맘대로 만들고, 제어도 더 쉽다고 생각했다.
Join과 cascade등의 처리를 위해서는 Sqlite3을 직접 사용하는게 더 좋은것 같다.

일단, Sqltie3으로 개발을 완료하고 나서 추후 nosql을 사용해보겠다.

오랫만에 개인 프로젝트를 하나 해보고 있는데, 트랜드를 다시 익히려다 보니 온갖 삽질을 다 하고 있다..

GRDB를 사용해서 대부분의 것들도 사용이 가능한거 같긴한데, 사전 지식이 꽤 많이 필요한거 같다.

RxSwift 쉽지 않구나.

Swift를 지나고 Reactive를 지나서 가는 여정이 참으로 험난하다.
뭔가 깨달은것 같으면 다시 막힘에 들어서는 반복중이다.

간단한걸 RxSwift MVVM으로 만드는게 이렇게 고통스러울줄이야…
좀 자료를 정리해서 글을 남겨야 할것 같다.

어떻게 글을 정리하고 글을 써야 할지 고민을 좀 해봐야 겠다.
정리가 안된다.. ㅠ

RxTableView

RxSwift를 학습은 했는데, 쓸 때마다 기억이 안난다.
아직 익숙해지지 않아서일까.. 그래서 정리해본다.

어떻게 정리해봐야 할까 고민을 해보아야 할것 같다.
일단 방식만 나열하는 걸로…

총 네가지 방식의 사용법이 존재한다.

1. tableView.rx.items 사용하기

    func bindingTableViewItems01() {
        let cities = ["01", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items) { (tableView: UITableView, index: Int, element: String) -> UITableViewCell in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleCell.identifier) as? TitleCell else {
                return UITableViewCell()
            }
            cell.title?.text = element
            
            // CellType 변경
            // element 타입을 기준으로 셀을 리턴 가능

            return cell
        }
        .disposed(by: disposeBag)
    }

2. tableView.rx.items(cellIdentifier:String) 사용하기


    func bindingTableViewItems02() {
        let cities = ["02", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items(cellIdentifier: TitleCell.identifier))
        { (index: Int, element: String, cell: UITableViewCell) in
            if let cell = cell as? TitleCell  {
                cell.title.text = element
            }
        }
        .disposed(by: disposeBag)
    }
   

3. tableView.rx.items(cellIdendifier:String,cellType:Cell.Type) 사용하기


    func bindingTableViewItems03() {
        let cities = ["03", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items(cellIdentifier: TitleCell.identifier, cellType: TitleCell.self))
        { (index: Int, element: String, cell: TitleCell) in
            cell.title.text = element
            cell.textLabel?.text = element + "     " + element
        }
        .disposed(by: disposeBag)
    }

4. tableView.rx.items(dataSource:protocol<RxTableViewDataSourceType, UITabelViewDataSource>) 사용하기


    // tableView를 어떻게 표현할지 미리 지정한 datasource를 사용한 방법
    // pod으로 RxDataSource를 설치
    // TODO: 참고 자료 https://github.com/RxSwiftCommunity/RxDataSources
    func bindingTableViewItems04() {
        // RxDataSource에서는 SectionModelType을 따르는 SectionModel을 이미 구현해 놓았는데, 이것을 사용하면 된다.
        typealias CitySectionModel = SectionModel<String, String>
        typealias CityDataSource = RxTableViewSectionedReloadDataSource<CitySectionModel>
        
        let cities = ["03", "L", "K", "L", "K", "L", "K", "L", "K", "L", "K"]
        let sections = [
            CitySectionModel(model: "first section", items: cities),
            CitySectionModel(model: "second section", items: cities)
        ]
        
        let configureCell: (TableViewSectionedDataSource<CitySectionModel>, UITableView, IndexPath, String) -> UITableViewCell = {
            (datasource, tableView, indexPath, element) in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleCell.identifier, for: indexPath) as? TitleCell else {
                return UITableViewCell()
            }
            cell.title.text = element
            return cell
        }
        let datasource = CityDataSource.init(configureCell: configureCell)

        datasource.titleForHeaderInSection = { datasource, index in
            return datasource.sectionModels[index].model
        }
                
        Observable.just(sections)
            .bind(to: tableView.rx.items(dataSource: datasource))
            .disposed(by: disposeBag)
    }

무료 폰트를 찾아서..

앱을 만들려다 보니 저작권들이 거슬린다.
경제적 자유! 그것을 위한 우리의 여행은 끝이 나지 않는다..

아래 링크들은 유투버를 통해서 알게 된 링크이다.
라이센스를 잘 읽어보고, 글꼴을 사용하길 바란다.
경우에 따라 유료인것도 있어서…

1.검은고딕 https://github.com/zesstype/Black-Han…

2.에스코어드림 http://www.s-core.co.kr/who-we-are/font/

3.몬소리체 https://brunch.co.kr/@creative/32

4.Noto Sans https://fonts.google.com/specimen/Not…

5.잉크립퀴드체 http://www.thefaceshop.com/event/lipq…

6.tvn 즐거운 이야기체 http://tvn10festival.tving.com/playgr…

7.스웨거체 http://www.swagger.kr/font.html

8.주아체 http://font.woowahan.com/

눈누 https://noonnu.cc/

구글폰트 https://fonts.google.com/

  • 참고 유튜버 영상 링크
  • https://www.youtube.com/watch?v=7gOiGK83X-o

@escaping

다음과 같은 블럭함수를 사용할 경우에, 에러가 발생한다.
“Escaping closure captures non-escaping parameter ‘completion'”

    func testFunc(_ completion: (String?) -> Void ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion("End")
            }
        }
    }

원인은 testFunc이 종료되고 나서 completion이 호출되는데, 호출이 불가하기 때문이다.
이러한 경우에 @escaping 을 선언해주면 정상 동작을 한다.

    func testFunc(_ completion: @escaping (String?) -> Void ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion("End")
            }
        }
    }

여기까지는 모두 알고 있는 항목이였을 것이다.
굳이 뭐 이런 글을이라며….
하지만, 오늘 배운게 하나 있는데, completion이 옵셔널로 선언된다면, default값으로 @escaping이 설정된다는 것이였다.


다음과 같이 코드 작성이 가능하다는 말이다.

    func testFunc(_ completion: ((String?) -> Void)? ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion?("End")
            }
        }
    }

난 분명 까먹을것이기 때문에, 기록을 남긴다.

protocol Equatable

글 작성중입니다.
글 하나 작성하는데 상당히 오랜 시간이 필요하네요..
곧 마무리 해서 올리겠습니다.

iOS 개발을 하다보면 꽤 많은 프로토콜들이 등장한다.
대부분의 개발자들은 별 생각없이 넘어갈것이라고 생각한다.
그러다가 어느순간에 이건 도대체 뭐지? 라는 의문이 들것이고 이에 대해 알고 싶어질것이라 생각한다.
지금 내가 그렇고, 매번 궁금한게 생길때마다 다시 찾아보는 수고를 덜기 위해 정리해보려고 한다.

equatable| ikwéitəbl | 형용사 | 동일시할 수 있는.

아래 애플링크에 있는 글들을 정리를 좀 해볼께요.
좀 보기 편하게 제 스타일대로 의역을 좀 해놨습니다.
틀린부분이 있다면 알려주세요.

Equatable

Type의 같음을 비교할 수 있다.

개요

Equatable 프로토콜을 따르는 Type들은 equal-to operator (==) 또는 불균형인지를 not-equal-to operator (!=)를 사용하여 비교 할수 있다.

배열와 컬렉션은 Equatable을 만족할때, 쉽게 비교를 수행할수 있다.
예를 들면 다음과 같다.
배열의 요소들이 Equatable을 만족할 때, 제공되는 closure를 사용하여 비교하지 않고, 특별한 값을 포함하고 있는지를 contains(_:) 메소드를 사용하여 체크할수 있다.

let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]

let nameToCheck = "Kofi"
if students.contains(nameToCheck) {
    print("\(nameToCheck) is signed up!")
} else {
    print("No record of \(nameToCheck).")
}
// Prints "Kofi is signed up!"

Equatable Protocol을 따르기

Equatable을 당신이 만든 Type에 추가한다는 것은, Collection에서 특별한 인스턴스를 찾기 위해 convenient APIs를 사용한다는 뜻이다.
Equatable은 base protocol이며, Hashable과 Comparable protocols에도 사용된다.
그리고, Collection의 요소들을 sets과 sorting하여 구성하는 것처럼, custom type을 사용할수 있게 해준다.

Equtable을 만족시며 type를 생성하기 위해서는 다음 두가지를 충족시켜야 한다.

  • Struct일 경우, 모든 properties가 Equatable을 만족 시켜야 한다.
  • Enum일 경우, 모든 value들이 Equatable을 만족 시켜야 한다.
    (value없는 enum은 선언없이 만족된다. 맞는 해석인지 모르겠다. 테스트 필요.)

작성중…

  • 참고 링크
  • https://developer.apple.com/documentation/swift/equatable

Pod install시 에러 발생 [ParserError – 767]

ParserError – 767: unexpected token at ‘ ‘ #10088

Pod 설치시 에러가 나면 곤란한 경우가 많다.
처음보는 에러가 하나 발생했다.
pod repos와 캐시를 클린해주면 해결이 된다.

해결방법은 다음과 같다.

rm -rf ~/.cocoapods/repos
pod cache clean --all
pod install
  • 참고 링크
  • https://github.com/CocoaPods/CocoaPods/issues/10088

[글 작성 중] Firebase 클라우드 메시지 시작하기

Firebase에서 제공하는 푸쉬를 적용해보자.
Pod을 사용할 것이며, 해당 가이드 링크는 참고링크(하단)에 적어놓았다.
https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0

Firebase에서 프로젝트를 설정하고, 푸쉬를 사용하기 위한 방법입니다.
프로젝트 설정등은 firebase 가이드를 따라 하는 방식을 권장합니다.
따라서 firebase를 XCode에 적용하고, 푸쉬가 오는것까지 확인하는 것을 목표로 작성된 문서입니다.

다음과 같이 Pod을 설정하고 설치한다.
Pod 설정은 가이드 문서를 참고하여 각자의 환경에 맞추어 설정한다.

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'


target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!


  # Pods for SampleRxSwift
  #pod 'RxSwift', '6.0.0-rc.1'
  #pod 'RxCocoa', '6.0.0-rc.1'
  # Add the Firebase pod for Google Analytics
  pod 'Firebase/Analytics'
  # Add the pod for Firebase Cloud Messaging
  pod 'Firebase/Messaging'




  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing


    # Add the Firebase pod for Google Analytics
    pod 'Firebase/Analytics'
    # Add the pod for Firebase Cloud Messaging
    pod 'Firebase/Messaging'


  end


  target 'SampleRxSwiftUITests' do
    # Pods for testing
    # Add the Firebase pod for Google Analytics
    pod 'Firebase/Analytics'
    # Add the pod for Firebase Cloud Messaging
    pod 'Firebase/Messaging'


  end


end

Pod 설치 및 사용법은 아래 링크를 참고. 또는 가이드 문서를 참고
http://magpiebros.com/wordpress/category/dev-story/ios-개발-이야기/cocoapods/

앱 실행부, AppDelegate 정당한 위치에 아래와 같은 같은 코드를 삽입한다.

class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        print("[AppDelegate] didFinishLaunchingWithOptions")
        FirebaseApp.configure()
        firebaseRegisterForRemoteNotifications(application)


        // 메시지 대리자 설정
        Messaging.messaging().delegate = self
        
        return true
    }

FCM관련 추가

// MARK:- FCM
extension AppDelegate {
    func getFCMToken() {
        print("[AppDelegate] getFCMToken")

        Messaging.messaging().token { token, error in
            if let error = error {
                print("Error fetching FCM registration token: \(error)")
            } else if let token = token {
                print("FCM registration token: \(token)")
                //            self.fcmRegTokenMessage.text  = "Remote FCM registration token: \(token)"
            }
        }
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
        print("[AppDelegate] 파이어베이스 토큰: \(fcmToken)")
    }
    
    func firebaseRegisterForRemoteNotifications(_ application: UIApplication) {
        print("[AppDelegate] firebaseRegisterForRemoteNotifications")
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
        
        application.registerForRemoteNotifications()
    }
}

APNS 설정

// MARK:- APNS
extension AppDelegate {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("[AppDelegate] 토큰 받음", deviceToken.description)
        Messaging.messaging().apnsToken = deviceToken
    }
    
    
    // 호출되지 않음. iOS 10 미만?
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
        print("[AppDelegate] didReceiveRemoteNotification - 알람 옴")
        // If you are receiving a notification message while your app is in the background,
        // this callback will not be fired till the user taps on the notification launching the application.
        // TODO: Handle data of notification
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //      if let messageID = userInfo[gcmMessageIDKey] {
        //        print("Message ID: \(messageID)")
        //      }
        
        // Print full message.
        print(userInfo)
    }
    
    // 호출되지 않음. iOS 10 미만?
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("[AppDelegate] didReceiveRemoteNotification - fetchCompletionHandler - 알람 옴")
        // If you are receiving a notification message while your app is in the background,
        // this callback will not be fired till the user taps on the notification launching the application.
        // TODO: Handle data of notification
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //      if let messageID = userInfo[gcmMessageIDKey] {
        //        print("Message ID: \(messageID)")
        //      }
        
        // Print full message.
        print(userInfo)
        
        completionHandler(UIBackgroundFetchResult.newData)
    }
}


//
@available(iOS 10, *) 
extension AppDelegate : UNUserNotificationCenterDelegate {
    
    // Receive displayed notifications for iOS 10 devices.
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        print("[AppDelegate] UNUserNotificationCenterDelegate")

        let userInfo = notification.request.content.userInfo
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //    if let messageID = userInfo[gcmMessageIDKey] {
        //      print("Message ID: \(messageID)")
        //    }
        
        // Print full message.
        print(userInfo)
        
        // Change this to your preferred presentation option
        completionHandler([[.alert, .sound]])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        print("[AppDelegate] UNUserNotificationCenterDelegate")

        let userInfo = response.notification.request.content.userInfo
        // Print message ID.
        //    if let messageID = userInfo[gcmMessageIDKey] {
        //      print("Message ID: \(messageID)")
        //    }
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print full message.
        print(userInfo)
        
        completionHandler()
    }
}

이렇게 코드만 적어놓는것은 역시 도움이 되지 않는 글을 작성해 놓은것 같아 씁쓸한 마음이 남는다. 이것보다 좀더 도움도 되고 발전도 할 수 있는 글을 작성할 수 있도록 고민해보아야 겠다.

  • 참고 링크
  • https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0

[Xcode] wifi로 iPhone에 앱 설치하기

아이폰에 usb선을 꼽아서 빌드를 하곤한다.
하지만 wifi로 빌드를 해서 넣을수 있다면?
오늘은 이 방법에 대해 글을 남기려고 한다.

wifi망을 쓸수 없는 개발환경에서만 개발을 해서 별로 필요가 없었는데, 집에서 개발을 하려고 하다보니 몹시 불편했다.
wifi로 앱을 빌드해서 설치하는 방법을 공유한다.

Window->Devices and Simulators를 선택하면 다음과 같은 화면이 나온다.

Connect via network 체크박스를 선택해주면 완료

단, 설정시 최초 한번은 usb 연결이 필요하다.

[CocoaPods] 01. 기본 사용법

요즘(요즘이라기 보단.. 오래되었지만.. 쿨럭) 뭔가를 대충 쓰기 보다는 제대로 알고 써보고 싶다는 생각이 많아진다.
그래서 공부도 하고 레퍼런스도 남길겸, 글들을 정리해서 써보기로 마음먹었다.

“CocoaPods은 Swift와 Objective-C 코코아 프로젝트를 관리하는데 의존적으로 사용된다. 7만8천개 이상의 라이브러리가 존재하며, 이를 통해 만들어진 앱은 3백만개 이상이다. 코코아팟(CocoaPods)는 당신의 프로젝트를 우아하게 만드는데 도움이 될것이다.”라고 코코아팟 사이트에서는 알려주고 있다.

뭘 먼저 해봐야 할가? 설치 아니겠는가?
설치를 해보자.

sudo gem install cocoapods

뭐 명령어 한줄만 치면 자연스럽게 설치가 이루어진다.

이제 iOS 프로젝트 위치로 이동을 해주고 pod을 install해준다.
(‘당신의 프로젝트명’.xcodeproj 파일이 존재하는 위치여야 한다.)

cd /Users/magpiebros/Documents/dev/iOS/SampleRxSwift/SampleRxSwift
pod init

pod을 init하고 나면, Podfile이 생성된다.
아래와 같은 모양의 파일이 생성되었을 것이다.

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleRxSwift

  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'SampleRxSwiftUITests' do
    # Pods for testing
  end

end

이제 # Pods for SampleRxSwift 등의 위치에 원하는 Pod을 설정해주고 install만 해주면 모든 준비가 끝이난다.

난 RxSwift를 설치할것이기 때문에 https://cocoapods.org/pods/RxSwift 에서 가이드 문서를 한번 읽어보고 진행할것이다.
다음과 같이 설정하면, 샘플앱에서 동작하는것이 모두 동작한다고 하니, 다음과 같이 설정해놓고 진행해보겠다.
(각 라이브러리마다 사용법은 다를수 있으니, 가이드 문서를 참고해야 한다.)

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'RxSwift', '6.0.0-rc.1'
    pod 'RxCocoa', '6.0.0-rc.1'
end

# RxTest and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
end

이제 내 프로젝트에 있는 Podfile을 변경해보자.

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleRxSwift
  pod 'RxSwift', '6.0.0-rc.1'
  pod 'RxCocoa', '6.0.0-rc.1'

  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
  end

  target 'SampleRxSwiftUITests' do
    # Pods for testing
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
  end

end

그리고 pod install을 하면 모든 준비는 끝이난다.
몇가지 경고들이 나타나는데, ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES 관련 에러등이 생긴다.

하지만 사용에는 무리가 없으니, 차차 더 깊은 Pod사용법을 정리해보겠다.
아.. 아직 RxSwift는 뭘 써야 좋을지를 정하지 못한 상태다.
한번 길을 잘못가면 되돌아 오기도 쉽지 않으니, 자세히 알아보고 시작하겠다.

  • 참고 사이트
  • https://cocoapods.org
  • https://cocoapods.org/pods/RxSwift