D33 - 用 Swift 和公开资讯,打造投资理财的 Apps { 台股申购功能扩充.4 }

处理申购 VC 的资料,是由 StockSubscriptionModel 处理的。

在前面,我们在 AppDelegate 的 didFinishLaunch 下载了台股上市收盘资料,并存在 UserDefaults 内,所以这个 model 在 init 的时候,理论上就可以拿到上市收盘资料了,如果没有,那你可以在这个时机点再发动一次下载。

注: 这边有优化空间,但更细腻的操作,就留待读者在实务上各自发挥

1-选择存放资料结构

在 StockSubscriptionModel 中,可以放日收的结构有 Array, Dictionary。这边我选择了 Dictionary,并把股票代号当成 key 值,value 为日收的 data model - StockDayTick。

2-实作

先在 StockSubscriptionModel 加上 key vs. value 的 property

private var twMarketDayTicks: [String: StockDayTick] = [:]

再加上日收的 manager,而且有要从 String 转成 NSNumber, Double 的地方,所以也要一个 NumberFormatter

private lazy var dayPriceManager: StockDayPriceManager = {
        return StockDayPriceManager()
    }()
    
private lazy var numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()

在 Model 一开始,就把上市日收从 UserDefaults 中拿出,并存起来

init() {
        setupDayTicks()
    }
    
    // MARK: - private func
    
    // 将 UserDefaults 中的 day ticks 拿出,并把 stockCode 当 key 值
    private func setupDayTicks() {

        let dayTicks = dayPriceManager.getTwAllStockDayPriceFromUserDefaults()
        let keys = dayTicks.map { tick in
            return tick.stockCode
        }
        for (index, tick) in dayTicks.enumerated() {
            let key = keys[index]
            self.twMarketDayTicks[key] = tick
        }
    }

然後做出让 VC 呼叫的接口,当 VC 把 subscription 输入,就可以得到价差的 String

/// 命名可以用 rename 改得更符合变数意义,请自行变更
    func getPricePercentDifferenctString(subscription: StockSubscriptionInfo) -> String? {
        
        let code = subscription.stockCode
        let subscriptionPrice = subscription.price
        if let tick = getTick(from: code),
           let subscriptionPriceDouble = numberFormatter.number(from: subscriptionPrice)?.doubleValue,
           let closePrice = tick.close {
            
            let percentage = (closePrice -  subscriptionPriceDouble) / subscriptionPriceDouble
            let string = String(format: "%.1f", percentage * 100)
            return string
        }
        
        return nil
    }

整个 Model 的 Code

//
//  StockSubscriptionModel.swift
//  ITIronMan
//
//  Created by Marvin on 2021/9/4.
//

import Foundation

protocol StockSubscriptionModelDelegate: AnyObject {
    
    func didRecieveList(_ subscriptionList: [StockSubscriptionInfo], error: Error?)
}

/// 股票申购 VC 所需的 Model
class StockSubscriptionModel {
    
    weak var delegate: StockSubscriptionModelDelegate?
    
    var subscriptionList: [StockSubscriptionInfo] = []
    
    private var twMarketDayTicks: [String: StockDayTick] = [:]
    
    private lazy var subscriptionManager: StockSubscriptionManager = {
        return StockSubscriptionManager()
    }()
    
    private lazy var dayPriceManager: StockDayPriceManager = {
        return StockDayPriceManager()
    }()
    
    private lazy var numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()
    
    var count: Int {
        return subscriptionList.count
    }
    
    init() {
        setupDayTicks()
    }
    
    // MARK: - private func
    
    // 将 UserDefaults 中的 day ticks 拿出,并把 stockCode 当 key 值
    private func setupDayTicks() {

        let dayTicks = dayPriceManager.getTwAllStockDayPriceFromUserDefaults()
        let keys = dayTicks.map { tick in
            return tick.stockCode
        }
        for (index, tick) in dayTicks.enumerated() {
            let key = keys[index]
            self.twMarketDayTicks[key] = tick
        }
    }
    
    private func filterNotAvailable(_ subscriptionList: [StockSubscriptionInfo]) -> [StockSubscriptionInfo] {
        
        let list = subscriptionList.filter { info in
            let code = info.stockCode
            let firstCharacter = code.first ?? "0"
            return firstCharacter != "A"
        }
        
        return list
    }
    
    private func getQueryYear() -> Int {
        
        let dateUtility = DateUtility()
        
        return dateUtility.getIntFromDate(component: .year)
    }
    
    private func getTick(from stockCode: String) -> StockDayTick? {
        
        if let tick = twMarketDayTicks[stockCode] {
            return tick
        }
        
        return nil
    }
    
    // MARK: - public func
    func getSubscriptionInfo(at indexPath: IndexPath) -> StockSubscriptionInfo? {
        
        let index = indexPath.row
        
        if subscriptionList.indices.contains(index) {
            return subscriptionList[index]
        }
        
        return nil
    }
    
    func requestStockSubscription() {
        
        let year = getQueryYear()
        subscriptionManager.requestStockSubscriptionInfo(year: year) { [weak self] subscriptionList, error in
            
            // 需要去掉中央债的资料
            self?.subscriptionList = self?.filterNotAvailable(subscriptionList) ?? []
            self?.delegate?.didRecieveList(subscriptionList, error: error)
        }
    }
    
    /// 命名可以用 rename 改得更符合变数意义,请自行变更
    func getPricePercentDifferenctString(subscription: StockSubscriptionInfo) -> String? {
        
        let code = subscription.stockCode
        let subscriptionPrice = subscription.price
        if let tick = getTick(from: code),
           let subscriptionPriceDouble = numberFormatter.number(from: subscriptionPrice)?.doubleValue,
           let closePrice = tick.close {
            
            let percentage = (closePrice -  subscriptionPriceDouble) / subscriptionPriceDouble
            let string = String(format: "%.1f", percentage * 100)
            return string
        }
        
        return nil
    }
}

extension StockSubscriptionModel {
    
    enum SubscriptionState {
        
        case beforeSubscription
        case duringSubscription
        case finishedSubscription
        case notDefined
    }
}

extension StockSubscriptionModel {
    
    func getSubscriptionState(info: StockSubscriptionInfo) -> SubscriptionState {
        
        let currentTime = Date().timeIntervalSince1970
        
        if let startTime = info.subscriptionStart?.timeIntervalSince1970,
           let endTime = info.subscriptionEnd?.timeIntervalSince1970 {
            
            if currentTime < startTime {
                return .beforeSubscription
            } else if currentTime > endTime {
                return .finishedSubscription
            } else {
                return .duringSubscription
            }
        }
        
        return .notDefined
    }
}

台股申购日历
IT铁人赛Demo App

下方是这次 D1 ~ D12 的完成品,可以下载来试
App Store - 台股申购日历

https://ithelp.ithome.com.tw/upload/images/20210924/20140622ypOBM0tgrZ.png


<<:  Day28 - 储存帐密及自动登入

>>:  Day29 使用 addTransceiver 单向接收串流

万事真能从影片完整复制学习吗?(教与学篇)

我们看电视综艺节目的森川葵,不管学习什麽技艺,都能很快学会,让教的人相当惊讶,且啧啧称奇,把这个电视...

[Day_10]资料储存容器(3) - 字典(dict)

字典(dict) 今天要来跟大家介绍字典(dict), 字典储存的资料为「键(key)」与「值(va...

Fluentd Bit

在 Fluentd Bit 中可以使用 read 或 socket 方式处理日志 read 用於读容...

IOS、Python自学心得30天 Day-4 TensorFlow 资料处理

前言: 到了要开始处理资料的部分,因为影像资料会有档案太大或是大小不一的问题,所以都要先预设成固定的...

失败了 还是可以进行更新

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2...