https://github.com/ReactiveX/RxSwift

 

ReactiveX/RxSwift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

 

RxSwift는 Swfit로 작성된 반응형 익스텐션 버전입니다.

Observable을 만들어 스트림에서 값 및 기타 이벤트를 구독하여 로직을 분리시킬 수 있고

비동기 및 함수적 스타일 연산자를 사용하여 를 사용하여 데이터를 참조하는 방법을 제공합니다.

 

1. pod 설정

pod 'RxSwift', '6.0.0-rc.2'
pod 'RxCocoa', '6.0.0-rc.2'

 

2. Observable 만들기 

static func fetchLotto(_ order: Int) -> Observable<Lotto?> {
        return Observable.create() { emitter in
            let url = URL(string: domainUrlString + "?order=" + String(order))!
            let task = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, err: Error?) -> Void in
                guard err == nil else {
                    emitter.onError(err!)
                    return
                }
                
                let lottoDto = try? JSONDecoder().decode(Lotto.self, from: data!)
                emitter.onNext(lottoDto)
            })
            
            task.resume()
            
            return Disposables.create(){
                task.cancel()
            }
        }
    }

 

3. subscribe 사용하기 

let observable = APIService.fetchLotto(order)
        _ = observable
            .observe(on: MainScheduler.instance)
            .subscribe(onNext: {
                self.lotto = $0
                self.viewUpdate()
            })
            .disposed(by: disposeBag)

 

옵저버블은 3가지 이벤트를 방출합니다.

public enum Event<Element> {
  case next(Element) //정상
  case error(Swfit.Error) //에러
  case completed //완료 
}

 

4. 마무리

위와 같이 행위를 Api를 Observable을 만들고 사용하는 쪽에서 Observable을 return받아 사용할 수 있습니다.

이와 같은 장점은 디자인 아키텍쳐 측면에서 비즈니스 로직을 분리하고 테스트를 별도로 할 수 있고

비동기 행위들을 Observable을 만들어 사용하는 쪽에서 일과된 코드를 유지할 수 있으며 

MVVM구조와 같은 구조에서 화면에서는 ViewModel 데이터에만 집중할 수 있는 장점으로 활용할 수 있습니다. 

 

SSL이란

SSL(Secure Sockets Layer)은 암호화 기반 인터넷 보안 프로토콜입니다. 인터넷 통신의 개인정보 보호, 인증, 데이터 무결성을 보장하기 위해 Netscape가 1995년 처음으로 개발했습니다. SSL은 현재 사용 중인 TLS 암호화의 전신입니다.

 

TrustKit이란

TrustKit은 모든 iOS 10+, macOS 10.10+, tvOS 10+ 또는 watchOS 3+ 앱에서 SSL 공개 키 고정 및보고를 쉽게 배포 할 수있는 오픈 소스 프레임 워크입니다. Swift 및 Objective-C 앱을 모두 지원합니다.

수동으로 고정을 구성하는 것은 어렵고 시간이 많이 소요될 수 있습니다. TrusKit 은 코드 몇 줄만 작성 하여 인증서의 공개 키 를 확인 하는 매우 쉬운 방법 을 제공하고 추가 기능도 제공합니다.

https://github.com/datatheorem/TrustKit

https://github.com/datatheorem/TrustKit/blob/master/docs/getting-started.md

 

적용하기

1.인증서 다운로드

브라우저에서 사이트 인증서 다운로드 

 

2.인증서로부터 pin추출

https://github.com/datatheorem/TrustKit/blob/master/get_pin_from_certificate.py

python 소스 다운로드 후

 

Pin 추출 : U3xTWXJOd447ON2zOz9w35qNaPAJDpqlFO4Jt/443us= 

$ python3 get_pin_from_certificate.py --type DER \*.google.com.cer
CERTIFICATE INFO
----------------
b'subject= /C=US/ST=California/L=Mountain View/O=Google LLC/CN=*.google.com\nissuer= /C=US/O=Google Trust Services/CN=GTS CA 1O1\nSHA1 Fingerprint=E4:89:43:D9:6A:40:D5:34:B9:33:7E:E5:ED:A9:76:D2:20:1D:2E:BF\n'

TRUSTKIT CONFIGURATION
----------------------
kTSKPublicKeyHashes: @[@"b'U3xTWXJOd447ON2zOz9w35qNaPAJDpqlFO4Jt/443us='"] // You will also need to configure a backup pin

 

3.프로젝트에 TrustKit적용

pod 'TrustKit'
$ pod install

 

AppDelegate.swift

도메인 설정과 추출한 정보를 kTSKPublicKeyHashes값으로 설정

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        TrustKit.setLoggerBlock { (message) in
              print("TrustKit log: \(message)")
        }
        let trustKitConfig: [String: Any] = [
             kTSKSwizzleNetworkDelegates: false,
             kTSKPinnedDomains: [
                    "www.google.com": [
                           kTSKEnforcePinning: false,
                           kTSKIncludeSubdomains: true,
                           kTSKPublicKeyHashes: [
        //First public key -> Obtained from the Python script
        "U3xTWXJOd447ON2zOz9w35qNaPAJDpqlFO4Jt/443us=",
        //Second public key in case of the first one will expire
        "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
           ],
           kTSKReportUris:        ["https://overmind.datatheorem.com/trustkit/report"],
         ]
        ]]
        TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
        
        return true
    }

 

HomeViewController.swift

 

delegate: self를 통해 재정의한 urlSession이 호출 되도록 선언함 

lazy var session: URLSession = {
       URLSession(configuration: URLSessionConfiguration.ephemeral,
                  delegate: self,
                  delegateQueue: OperationQueue.main)
    }()
let task = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
	guard let data = data else {
		completion(nil)
		return
	}
	let str = String(decoding: data, as: UTF8.self)
	print(str)
})
task.resume()
extension HomeViewController: URLSessionDelegate {
    
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                                                
        if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false {
            // TrustKit did not handle this challenge: perhaps it was not for server trust
            // or the domain was not pinned. Fall back to the default behavior
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

URLSessionDelegate을 상속받아 urlSession에서 재정의하여 TrustKit을 통해 검증이 되도록 합니다. 

위와 같이 적용하여 정상적으로 동작하는 것을 확인합니다. 

오토레이아웃을 코딩으로 구현시 코드를 간결하게 작성하기 위해 SnapKit을 사용해 보겠습니다.

스냅킷은 DSL로 오토레이아웃을 쉽게 사용할 수 있는 기능을 제공합니다.

관련 링크는 하기와 같습니다.

https://github.com/SnapKit/SnapKit

 

SnapKit/SnapKit

A Swift Autolayout DSL for iOS & OS X. Contribute to SnapKit/SnapKit development by creating an account on GitHub.

github.com

 

변경 작업은 이전에 포스팅했던

https://xmobile.tistory.com/entry/IOS-오토레이아웃-제약조건-코딩으로-사용하기

를 활용 하도록 하겠습니다.

 

변경 전

private func addconstraints() {
        var constraints = [NSLayoutConstraint]()
        
        constraints.append(box1.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box2.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box3.heightAnchor.constraint(equalToConstant: 100))
        //box2와 box3 너비 같도록 설정 
        constraints.append(box2.widthAnchor.constraint(equalTo: box3.widthAnchor))
        constraints.append(box3.widthAnchor.constraint(equalTo: box2.widthAnchor))
        
        //box1 left : 뷰콘트롤러뷰의 왼쪽에서 20만큼 떨어진 곳에 box1 left가 위치함
        constraints.append(box1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box1 right : 뷰콘트롤러뷰의 오른쪽에서 20만큼 떨어진 곳에 box1 right가 위치함
        constraints.append(box1.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20))
        //box1 top :  뷰콘트롤러뷰의 위쪽에서 40만큼 떨어진 곳에 box1 top 위치함
        constraints.append(box1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40))


        //box2 left : 뷰콘트롤러뷰의 왼쪽에서 20만큼 떨어진 곳에 box2 left가 위치함
        constraints.append(box2.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box2 top : box1의 아래쪽에서 40만큼 떨어진 곳에 box2 top 위치함
        constraints.append(box2.topAnchor.constraint(equalTo: box1.bottomAnchor, constant: 40))


        //box3 left : box2 오른쪽에서 20만큼 떨어진 곳에 box3 left가 위치함
        constraints.append(box3.leadingAnchor.constraint(equalTo: box2.trailingAnchor, constant: 20))
        //box3 right : 뷰콘트롤러뷰의 오른쪽에서 -20만큼 떨어진 곳에 box3 right가 위치함
        constraints.append(box3.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20))
        //box3 top :  box1의 아래쪽에서 40만큼 떨어진 곳에 box3 top 위치함
        constraints.append(box3.topAnchor.constraint(equalTo: box1.bottomAnchor, constant: 40))


        //Activate(Applying)
        NSLayoutConstraint.activate(constraints)
    }

 

변경 후

private func addconstraints() {
        box1.snp.makeConstraints { (make) -> Void in
            make.height.equalTo(100)
            make.leading.equalToSuperview().offset(20)
            make.trailing.equalToSuperview().offset(-20)
            make.top.equalToSuperview().offset(40)
        }
        
        box2.snp.makeConstraints { (make) -> Void in
            make.height.equalTo(100)
            make.width.equalTo(box3.snp.width)
            make.leading.equalToSuperview().offset(20)
            make.top.equalTo(box1.snp.bottom).offset(40)
        }
        
        box3.snp.makeConstraints { (make) -> Void in
            make.height.equalTo(100)
            make.width.equalTo(box2.snp.width)
            make.leading.equalTo(box2.snp.trailing).offset(20)
            make.trailing.equalToSuperview().offset(-20)
            make.top.equalTo(box1.snp.bottom).offset(40)
        }
    }

위와 같이 소스코드가 간결해 지는것을 확인하였습니다. 

이상입니다. 

 

Realm Swift를 이용하면 효율적으로 안전하고 빠르고 지속적인 방법으로 앱의 모델 레이어를 작성할 수 있습니다.

https://realm.io/kr/docs/swift/latest/

 

Realm: 리액티브 모바일 애플리케이션을 손쉽고 빠르게 만드세요

Realm Swift is the first database built for mobile. An alternative to SQLite and Core Data that's fast, easy to use, and open source.

realm.io

의존성 설정 

pod 'RealmSwift', '~> 3.20.0'

 

사용해 보기 

1. entity 클래스 

import Foundation
import RealmSwift

class LottoEntity: Object {
    @objc dynamic var lottoid: Int = 0
    @objc dynamic var desc = ""
    @objc dynamic var url = ""
    @objc dynamic var regDate: Double = 0.0
    @objc dynamic var modDate: Double = 0.0
    
    override static func primaryKey() -> String? {
        return "lottoid"
    }
}

2. crud 만들기

import Foundation
import RealmSwift

public class DatabaseManager {
    static let shared = DatabaseManager()
    private var realm: Realm
    
    private init() {
        realm = try! Realm()
    }
    
    //id자동증가를 위한 함수 
    private func newID() -> Int {
        return realm.objects(LottoEntity.self).count+1
    }
    
    //삽입
    func insert(lottoEntity: LottoEntity) {
        lottoEntity.lottoid = newID()
        lottoEntity.regDate = Date().currentTimeMillis()
        
        try! realm.write {
            realm.add(lottoEntity)
        }
    }
    
    //업데이트 
    func update(lottoId: Int, desc: String?, url: String?) {
        let lottoEntity = selectById(lottoId: lottoId)
        if let workout = lottoEntity {
            try! realm.write {
                if let desc = desc{
                    workout.desc = desc
                }
                if let url = url{
                    workout.url = url
                }
                workout.modDate = Date().currentTimeMillis()
            }
        }
    }
    
    //삭제 object로 
    func delete(lottoEntity: LottoEntity) {
        try! realm.write {
            realm.delete(lottoEntity)
        }
    }
    
    //삭제 id로 
    func deleteById(lottiId: Int) {
        try! realm.write {
            if let entity = selectById(lottoId: lottiId) {
                realm.delete(entity)
            }
        }
    }
    
    //삭제 all
    func deleteAll() {
        try! realm.write {
            realm.deleteAll()
        }
    }
    
    //가져오기 id로 
    func selectById(lottoId: Int) -> LottoEntity? {
        let predicate = NSPredicate(format: "lottoid == %i", lottoId)
        return realm.objects(LottoEntity.self).filter(predicate).first
    }
    
    //가져오기 all
    func selectAll() -> Results<LottoEntity> {
        return realm.objects(LottoEntity.self)
    }
}

extension Date {
    func currentTimeMillis() -> Double {
        return Double(self.timeIntervalSince1970 * 1000)
    }
}

3. UNIT 테스트로 검증해보기  

func testExample() throws {
        //all 삭제
        DatabaseManager.shared.deleteAll()
        
        //insert
        var entity = LottoEntity()
        entity.url = "url"
        entity.desc = "desc"
        DatabaseManager.shared.insert(lottoEntity: entity)
        XCTAssert(DatabaseManager.shared.selectAll().count == 1)
        
        //update
        //desc 값 변경
        DatabaseManager.shared.update(lottoId: 1, desc: "desc_change", url: nil)
        print(DatabaseManager.shared.selectAll())
        if let selEntity = DatabaseManager.shared.selectById(lottoId: 1) {
            XCTAssert(selEntity.desc == "desc_change")
        }
        
        //insert
        entity = LottoEntity()
        entity.url = "url1"
        entity.desc = "desc2"
        DatabaseManager.shared.insert(lottoEntity: entity)
        XCTAssert(DatabaseManager.shared.selectAll().count == 2)
        
        //delete
        if let entity = DatabaseManager.shared.selectAll().first {
            DatabaseManager.shared.delete(lottoEntity: entity)
        }
        print(DatabaseManager.shared.selectAll())
        XCTAssert(DatabaseManager.shared.selectAll().count == 1)
        
        //delete all
        DatabaseManager.shared.deleteAll()
        XCTAssert(DatabaseManager.shared.selectAll().count == 0)
        print(DatabaseManager.shared.selectAll())
    }

하기와 같이 정상적으로 동작하는것을 확인해 보았습니다. 

NSLayoutConstraint를 이용하여 코딩을 통해서  간단한 뷰 배치를 하도록 하겠습니다.

뷰 3개를 이용하여 2가지 배치를 설정해 봅니다. 

private let box1: UIView = {
	let view = UIButton()
	view.backgroundColor = .blue
	view.translatesAutoresizingMaskIntoConstraints = false
	return view
}()
    
private let box2: UIView = {
    let view = UIButton()
    view.backgroundColor = .red
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()
    
private let box3: UIView = {
	let view = UIButton()
    view.backgroundColor = .yellow
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

...
override func viewDidLoad() {
	super.viewDidLoad()
	view.backgroundColor = .white
        
	view.addSubview(box1)
	view.addSubview(box2)
	view.addSubview(box3)
        
	addconstraints()
}

 

배치1

private func addconstraints() {
        var constraints = [NSLayoutConstraint]()
        
        constraints.append(box1.widthAnchor.constraint(equalToConstant: 100))
        constraints.append(box1.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box2.widthAnchor.constraint(equalToConstant: 100))
        constraints.append(box2.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box3.widthAnchor.constraint(equalToConstant: 100))
        constraints.append(box3.heightAnchor.constraint(equalToConstant: 100))
        
        //box1 left: 뷰콘트롤러뷰의 왼쪽에서 20만큼 떨어진 곳에 box1 left가 위치함
        constraints.append(box1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box1 top : 뷰콘트롤러뷰의 위쪽에서 40만큼 떨어진 곳에 box1의 top이 위치함  
        constraints.append(box1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40))

        //box2 left: 뷰콘트롤러뷰의 왼쪽으로부터 20만큼 떨어진 곳에 box2 left 위치함 
        constraints.append(box2.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box2 top: box1 아래쪽이 40만큼 떨어진 곳에 box2 top이 위치 
        constraints.append(box2.topAnchor.constraint(equalTo: box1.bottomAnchor, constant: 40))

        //box3 right: 뷰콘트롤러뷰의 우측에서 -20만큼 떨어진 곳에 box3의 right가 위치함  
        constraints.append(box3.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20))
        //box3 top: box2 아래쪽이 40만큼 떨어진 곳에 box3 top이 위치 
        constraints.append(box3.topAnchor.constraint(equalTo: box2.bottomAnchor, constant: 40))

        //Activate(Applying)
        NSLayoutConstraint.activate(constraints)
}

 

배치2

private func addconstraints() {
        var constraints = [NSLayoutConstraint]()
        
        constraints.append(box1.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box2.heightAnchor.constraint(equalToConstant: 100))
        constraints.append(box3.heightAnchor.constraint(equalToConstant: 100))
        //box2와 box3 너비 같도록 설정 
        constraints.append(box2.widthAnchor.constraint(equalTo: box3.widthAnchor))
        constraints.append(box3.widthAnchor.constraint(equalTo: box2.widthAnchor))
        
        //box1 left : 뷰콘트롤러뷰의 왼쪽에서 20만큼 떨어진 곳에 box1 left가 위치함
        constraints.append(box1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box1 right : 뷰콘트롤러뷰의 오른쪽에서 20만큼 떨어진 곳에 box1 right가 위치함
        constraints.append(box1.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20))
        //box1 top :  뷰콘트롤러뷰의 위쪽에서 40만큼 떨어진 곳에 box1 top 위치함
        constraints.append(box1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40))

        //box2 left : 뷰콘트롤러뷰의 왼쪽에서 20만큼 떨어진 곳에 box2 left가 위치함
        constraints.append(box2.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20))
        //box2 top : box1의 아래쪽에서 40만큼 떨어진 곳에 box2 top 위치함
        constraints.append(box2.topAnchor.constraint(equalTo: box1.bottomAnchor, constant: 40))

        //box3 left : box2 오른쪽에서 20만큼 떨어진 곳에 box3 left가 위치함
        constraints.append(box3.leadingAnchor.constraint(equalTo: box2.trailingAnchor, constant: 20))
        //box3 right : 뷰콘트롤러뷰의 오른쪽에서 -20만큼 떨어진 곳에 box3 right가 위치함
        constraints.append(box3.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20))
        //box3 top :  box1의 아래쪽에서 40만큼 떨어진 곳에 box3 top 위치함
        constraints.append(box3.topAnchor.constraint(equalTo: box1.bottomAnchor, constant: 40))

        //Activate(Applying)
        NSLayoutConstraint.activate(constraints)
    }

 

이상으로 제약조건을 코딩으로 관계에 대한 설정을 통해서 사용법을 간단하게 사용해 보았습니다.

'IOS' 카테고리의 다른 글

[IOS] RxSwfit 사용하기  (0) 2021.01.04
[IOS] TrustKit 사용해보기 (SSL적용)  (0) 2020.12.22
[IOS] SnapKit 사용해보기  (0) 2020.10.19
[IOS] Realm Database CRUD 사용해보기  (0) 2020.10.17
[IOS] CocoaPods 사용하기  (0) 2020.10.12

CocoaPods는 Swift 프로젝트에서 타사 라이브러리 의존성을 설치하고 관리하는 방법을 제공합니다.

 

준비

1. CocoaPods 설치

sudo gem install cocoapods

 

2. CocoaPods원격 저장소 파일들을 로컬로 가져옵니다. 

pod setup --verbose

 

사용

1. Xcode Project 생성 후 terminal로 해당 폴더로 이동 후 pod init 커맨드로 Podfile을 생성합니다. 

 

2 Podfile에 의존성 추가 

3. pod명령으로 의존성 설치 

pod install

 

4. .xcworkspace파일로 프로젝트 열기 

.xcworkspace 파일로 프로젝트를 열어야합니다. 그렇지 않으면 빌드 오류가 발생합니다.

이상으로 CocoaPods를 사용하여 

의존성을 추가했습니다

 

+ Recent posts