2017-12-27 MVC/ 前端/ iOS

iOS: .txt 小說(shuō)閱讀器功能開發(fā)的 8 個(gè)老套路

本文介紹本地 .txt 小說(shuō)閱讀器功能開發(fā)的 8 個(gè)相關(guān)技術(shù)點(diǎn)。

網(wǎng)絡(luò) .txt 小說(shuō)開發(fā),則多了下載和緩存兩步

一本書有什么,即書的數(shù)據(jù)結(jié)構(gòu)

一本書有書名,有正文,有目錄

手機(jī)書架上的書很多,需給書分配一個(gè) id,去除重復(fù)

小說(shuō)用戶的常見操作有兩種,當(dāng)前閱讀進(jìn)度記錄和書簽列表

小說(shuō)的主要模型 ReadModel

書的兩個(gè)自然屬性 ID 和目錄

( 一本書有書名,這里與 ID 合并 )

書的兩個(gè)用戶操作屬性,閱讀記錄和書簽

可看出,書的正文去哪了?

class ReadModel: NSObject,NSCoding {

    /// 小說(shuō)ID, 書名
    let bookID:String
    
    /// 目錄, 章節(jié)列表
    var chapterListModels = [ChapterBriefModel]()
    
    /// 當(dāng)前閱讀記錄
    var recordModel:ReadRecordModel?
    
    /// 書簽列表
    var markModels = [ReadMarkModel]()
    
    
}

小說(shuō)的目錄模型 ChapterBriefModel

class ChapterBriefModel{
    
    /// 章節(jié)ID    
    var id: Int!

    /// 小說(shuō)ID
    var bookID:String!
    
    /// 章節(jié)名稱
    var name:String!
}

有了目錄,要閱讀,尚缺正文

小說(shuō)的章節(jié)模型

包含具體的閱讀章節(jié)純文本 content,和用來(lái)渲染呈現(xiàn)的富文本 fullContent

含有上一章和下一章的 ID,作為一個(gè)鏈表,用于連續(xù)閱讀


class ReadChapterModel: NSObject,NSCoding {
    
    /// 小說(shuō)ID
    let bookID: String
    
    /// 章節(jié)ID
    let id: Int
    
    /// 上一章ID
    var previousChapterID: Int?
    
    /// 下一章ID
    var nextChapterID: Int?
    
    /// 章節(jié)名稱
    var name:String!
    
     /// 內(nèi)容
    /// 此處 content 是經(jīng)過排版好且雙空格開頭的內(nèi)容。 
    var content:String!
    
    /// 完整富文本內(nèi)容
    var fullContent:NSAttributedString!
    
    /// 本章有多少頁(yè)
    var pageCount: Int = 0
    
    /// 分頁(yè)數(shù)據(jù)
    var pageModels = [ReadPageModel]()
    
    /// 內(nèi)容的排版屬性
    private var attributes = [NSAttributedString.Key: Any]()
    
}

1,基礎(chǔ)呈現(xiàn):

網(wǎng)上下載了一本 《三國(guó)演義》,制作一個(gè)基本的閱讀界面

.txt 小說(shuō) -> 小說(shuō)代碼模型

小說(shuō)本身自帶,ID 、小說(shuō)名稱、章節(jié)列表和小說(shuō)正文,
章節(jié)列表和小說(shuō)正文有一個(gè)對(duì)應(yīng)關(guān)系: 章節(jié)內(nèi)容范圍數(shù)組
這里把小說(shuō)的 ID 和小說(shuō)名稱合并為一個(gè)屬性

class ReadModel: NSObject,NSCoding {

/// 小說(shuō)ID,使用書名作為 ID

/// 小說(shuō)名稱
// app 里面有很多書,有 id, 不重復(fù)
let bookID:String

/// 當(dāng)前閱讀記錄
// 用戶操作

// 初始化的時(shí)候,用戶沒操作,設(shè)置第一個(gè)章節(jié)為閱讀記錄
var recordModel:ReadRecordModel?

/// 書簽列表
// 用戶操作
var markModels = [ReadMarkModel]()

/// 章節(jié)列表
var chapterListModels = [ReadChapterListModel]()

/// 本地小說(shuō)全文
var fullText:String!

/// 章節(jié)內(nèi)容范圍數(shù)組 [章節(jié)ID:[章節(jié)優(yōu)先級(jí):章節(jié)內(nèi)容Range]]
var ranges:[String:[String:NSRange]]!

}

1.1 模型解析
1.1.1 數(shù)據(jù)結(jié)構(gòu)
1.2 視圖呈現(xiàn)
2,計(jì)算頁(yè)碼
2.1 翻頁(yè)
3,目錄
4,書簽
5,調(diào)進(jìn)度
5.1 全文的進(jìn)度展示與調(diào)節(jié)
5.2 當(dāng)前章節(jié)的進(jìn)度展示與調(diào)節(jié)
6,翻頁(yè)方式
7,更改排版方式
8,長(zhǎng)按文本復(fù)制







關(guān)鍵點(diǎn) 3:碉堡了,我大 RxSwift 的循環(huán)引用

RxSwift 的內(nèi)存管理,很少通過 weak, 通常是手動(dòng)管理,手動(dòng) dispose,

dispose , 即把相關(guān)對(duì)象置為 nil

Sink<Observer>

extension ObservableType {

    /**
     
     */
    public func withLatestFrom<Source: ObservableConvertibleType, ResultType>(_ second: Source, resultSelector: @escaping (Element, Source.Element) throws -> ResultType) -> Observable<ResultType> {
        return WithLatestFrom(first: self.asObservable(), second: second.asObservable(), resultSelector: resultSelector)
    }
}

做了一個(gè)簡(jiǎn)單的過濾,
去掉了攔截

一般的 RxSwift 的 Extension, 幾個(gè)函數(shù)搞一下

簡(jiǎn)單的否定操作符,事件流里面的每一個(gè)事件,只需要簡(jiǎn)單處理下,

沒有更多的狀態(tài)管理,只需要簡(jiǎn)單的函數(shù)層次上的處理

extension ObservableType where Element == Bool {
    /// 否定操作符
    public func not() -> Observable<Bool> {
        return self.map(!)
    }
}




從 if else 開始,

事件流的邏輯控制

filter 是選取

share 是多流


事件池,當(dāng)然是 RxSwift 實(shí)現(xiàn)的,非常好


線程鎖,
異步任務(wù),
無(wú)法保證事件完成的先后順序


socket 多線程開發(fā),話說(shuō)那無(wú)敵的左手

不用多線程,可理解為,啥事都用好用的左右手處理

使用多線程,快多了,基本也是左右開弓

怎么懟得過那誰(shuí)的三頭六臂。

?

線程的優(yōu)先級(jí)倒置,

有時(shí)候,你覺得你的 Leader 是個(gè)什么 X,
不如,換我上

線程的優(yōu)先級(jí)倒置,簡(jiǎn)單理解,配置有問題

?

用戶操作,切換線程,

線程的手動(dòng)切換



解析 YYModel 源代碼,學(xué)習(xí) Runtime



提升前端素質(zhì),心中有點(diǎn) B 樹

Screen Shot 2020-03-28 at 8.49.18 PM.png

平衡的樹,才有用,

效率要 O ( log ( n ) ), 不要 O ( n )

O ( n ) 是線性查找,也就是鏈表

?

?

B 樹,是一顆好樹

好的樹,便于查找,search 操作的復(fù)雜度是 O(log (n)

好的樹,增加和刪除節(jié)點(diǎn)后,可以維持自己的結(jié)構(gòu),這個(gè)意思是,一直都便于查找。

不可維持自己的結(jié)構(gòu)的樹,只能爽一次

維持自己的結(jié)構(gòu)的樹,search 操作的復(fù)雜度保持在 O(log (n),品質(zhì)始終如一

B 樹,一個(gè)節(jié)點(diǎn),可以包含多個(gè) KV,有效的降低了樹的高度。

B 樹,有一個(gè) degree ( n ), 也就是一個(gè)節(jié)點(diǎn)最多可以有 n 個(gè)內(nèi)容,n + 1 個(gè) children.

節(jié)點(diǎn)包含的內(nèi)容,與節(jié)點(diǎn)孩子的關(guān)系:

n 個(gè)可比較的內(nèi)容,可以分割出 n + 1 個(gè)孩子
n 個(gè)可比較的內(nèi)容,可以分割出 n + 1 段范圍,每一顆子樹,存在于指定的范圍

就像黃瓜,切四刀,可以吃五口 ( 一般的刀法下 )

?

?

B 樹,不是二叉搜索樹,譬如: AVL , 紅黑樹
B 樹的孩子挺多的

B 樹,是一顆平衡的樹,
AVL 通過記錄節(jié)點(diǎn)的高度,
紅黑樹,通過
B 樹通過持有多節(jié)點(diǎn)

B 樹的孩子挺多的
這樣有效降低了層級(jí),也就是樹的高度,降下來(lái)了

?

從下往上生長(zhǎng)

?

添加,刪除

自保持,自平衡,自我維持住,

便于查找,

查找效率高

數(shù)據(jù)庫(kù),什么系統(tǒng)的剛需

?

?

紅黑樹,

要區(qū)分,就要做標(biāo)記

?

?

劃分 MVC , 是為了 代碼 分層、 分區(qū),隔離 不同功能的代碼, 數(shù)據(jù) 流 上。
在代碼 數(shù)據(jù)流向 模塊間 豎起 明顯的隔離線。

?

感覺 , 有三個(gè) Model .

  • Service , 一統(tǒng) 外部數(shù)據(jù)。網(wǎng)絡(luò)請(qǐng)求, 本地 數(shù)據(jù)庫(kù)。

  • DataSource, 遵守 *UITableViewDataSource * Protocol , 有 ViewModel 的 感覺。
    一統(tǒng) 內(nèi)部數(shù)據(jù)。 就是 顯示數(shù)據(jù)與操作數(shù)據(jù)。 給用戶看的, 和 用的。

  • Model ,數(shù)據(jù)解析 序列化。 與后臺(tái) 約定的數(shù)據(jù)結(jié)構(gòu),申明的 一大堆屬性,
    與簡(jiǎn)單的 業(yè)務(wù)數(shù)據(jù) 處理, mapper 出,提供 我方需要的 結(jié)構(gòu)化的 數(shù)據(jù)。

??

前端 的 Controller

是 用戶

只有 View 和 Model,
就是 視頻 、 動(dòng)畫

??

MVCS
Store, Service.
配合 Aggregate Data Source.
在 dataSource 中, 處理 cell 數(shù)據(jù)。
將 數(shù)據(jù) 判斷結(jié)果 甩出去。

DataSource

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier: self.cellIdentifier forIndexPath: indexPath];
    cell.myProduct = self.myProductItems[indexPath.row];
    
    BOOL isCellContained = [self.tempSelectedArray containsObject: self.myProductItems[indexPath.row]];
    if (self.myProductsCtrlDataSourceBlock) {
        self.myProductsCtrlDataSourceBlock(cell, self.myProductItems[indexPath.row], isCellContained, indexPath);
    }
    
    return cell;
}

VC:


- (void)networkMyProductsCtrlStoreSuccess:(id)successData failure:(NSString *)failureStr;{
    if (successData) {
        NSArray *dataArray = (NSArray *)successData;
        if (dataArray.count == 0) {
            self.networkResult = EmptyData;
        }
        NSMutableArray *myProductsTempArray = [NSMutableArray array];
        for (NSDictionary *dict in dataArray) {
            MyProduct *myProducts = [MyProduct yy_modelWithDictionary:dict];
            [myProductsTempArray addObject:myProducts];
        }
        self.myProductsCtrlDataSource = [[MyProductsCtrlDataSource alloc] initWithItems: myProductsTempArray cellIdentifier: kMyProductsCell configureCellBlock:^(MyProductsCell *cell, MyProduct *MyProduct, BOOL isCellContained, NSIndexPath *indexPath) {
            if (isCellContained && self.myProductsTableView.editing) {
                [self.myProductsTableView selectRowAtIndexPath: indexPath animated:YES scrollPosition:(UITableViewScrollPositionNone)];
            }
        }];

?

相同點(diǎn),內(nèi)部有 各種 分門別類的數(shù)據(jù)源, 經(jīng)過 外部的簡(jiǎn)單參數(shù),
相當(dāng)于 各種材料 都在 手上,非常集中 ,非常爽滴 進(jìn)行 復(fù)雜運(yùn)算,數(shù)據(jù)加工,
返回給外部。
很明顯, 這就是一層
Model Layer.

一層,就是 一統(tǒng)。
高度封裝,就是高度定制化。

Store

外部數(shù)據(jù) 統(tǒng)一處理,
網(wǎng)絡(luò) 與 持久化數(shù)據(jù)庫(kù)

包括, 各種網(wǎng)絡(luò)請(qǐng)求 增刪改。
好像并沒有 直接的 關(guān)聯(lián)。
其實(shí) 就是 直接在里面 用Net API match 幾下,改下實(shí)例 存儲(chǔ)的 臨時(shí)變量, 返回給外部 結(jié)果。

?

Aggregate TableView DataSource

內(nèi)部數(shù)據(jù),統(tǒng)一處理

getter
暴露出 readonly 的 數(shù)組,供外部查詢。

setter
暴露出數(shù)據(jù)操作方法。
高度封裝。
相當(dāng)于API 調(diào)用。
外部傳幾個(gè) 很簡(jiǎn)單的 參數(shù),
內(nèi)部各種數(shù)據(jù)運(yùn)算出結(jié)果,返回外部。

?

如果是,MVC,

VC 中

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier: self.cellIdentifier forIndexPath: indexPath];
    
    cell.myProduct = self.myProductItems[indexPath.row];
   // cell.delegate = self;
    if ([self.tempSelectedArray containsObject:cell.myProduct] && self.myProductsTableView.editing) {
        [tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:(UITableViewScrollPositionNone)];
    }
    return cell;
}


不同的拆分,不同的寫法, 實(shí)質(zhì)上是不同的 邏輯思想,不僅僅是 對(duì)應(yīng) 邏輯的翻譯。就是 相同業(yè)務(wù)邏輯的翻譯,使用膠水代碼/語(yǔ)法糖

MVVM. 響應(yīng)式。data binding.
否則 在 JS 中,又要改 視圖,又要 改數(shù)據(jù)源。

?

傳值,每次都要改變的,
用函數(shù)行為參數(shù),
用 argument.

狀態(tài) 需要保存一會(huì)的,
用 屬性。

?

本地搜索,
ML,
文字匹配,
文字轉(zhuǎn)拼音 匹配

Mac three

local player

Realm , AudioKit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中。。。 其實(shí)如果沒有APIManager這一層是沒法使用delegate的,畢竟多個(gè)單...
    yhtang閱讀 5,490評(píng)論 1 23
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,688評(píng)論 18 399
  • 基礎(chǔ) 1. 為什么說(shuō)Objective-C是一門動(dòng)態(tài)的語(yǔ)言? 2. 講一下MVC和MVVM,MVP? 3. 為...
    波妞和醬豆子閱讀 3,528評(píng)論 0 46
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 前言和目錄 上一章 第二天早上阿花把小敏送回阿義家,正碰上阿義的手下兄弟正在搬東西,看見阿花來(lái)了就跑去喊阿義:"義...
    亦農(nóng)閱讀 522評(píng)論 1 9

友情鏈接更多精彩內(nèi)容