iOS APP 架構漫談

jopen 10年前發布 | 32K 次閱讀 IOS iOS開發 移動開發

最近看了一些有關server的東西,一些很簡單的東西,不外乎是一些文檔規范,另外結合最近看的wwdc的一些video,覺得對軟件架構(software architecture)認識又清楚了一些,這里記錄下來。

software architecture 聽上去是一個很大的概念,實際上也包括很多東西,里面的爭議也很多。在我看來軟件架構最好放在小的場景中理解。

問題1

我們有2個頁面。

iOS APP 架構漫談

  • 頁面A:主頁面
  • 頁面B:詳情頁面
  • </ul>

    demo code 1.0.0

    2個頁面分別顯示一個數字,這個數字應該相同。詳情會修改這個數字,這里我們發現,詳情頁面和主頁面數字不一樣。

    iOS APP 架構漫談數據不一致

    問題1 解決方法A

    這里首先的感覺就是,詳情頁面返回,主頁面數據沒有刷新,導致數據不一致。 那么Fix這個Bug的方法,就是在主頁面出現的時候刷新界面

    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];

    self.displayLabel.text = [[CUDataDAO selectData].data stringValue];
    

    }</code></pre>

    現在來看,還不錯。但是,我們調用selectData的次數則變得非常非常多。數據不是經常變化的。

    demo code 1.0.1

    問題1 解決方法B

    我們發現既然數據的改變是在頁面B進行的,那么頁面B修改這個數據的時候,應該把數據變化”通知”給頁面A,那么我們寫了一個Delegate

    <code>@protocol CUDetailViewControllerDelegate  - (void)detailVC:(CUDetailViewController )vc dataChanged:(NSNumber )data;

    @end</nsobject></code></pre>

    在頁面B修改數據之后,通過delegate 通知給頁面A。

    - (IBAction)changeButtonClicked:(id)sender {
        int value = arc4random() % 100;
        [CUDataDAO setData:value];

    self.displayLabel.text = [@(value) stringValue];
    
    if ([self.delegate respondsToSelector:@selector(detailVC:dataChanged:)]) {
        [self.delegate detailVC:self dataChanged:@(value)];
    }
    

    }</code></pre>

    到此場景1得到了不錯的解決。

    demo code 1.0.2

    問題2

    這時我們增加了另一個頁面C。這個場景會稍微抽象一點,我們定義了3個數據

    • 頁面A的數據dataA
    • 頁面B的數據dataB
    • 頁面C的數據dataC
    • </ul>

      問題1中 dataA = dataB。在問題2中dataA = dataB + dataC;

      問題2 解決方法C

      也就是說頁面C的修改,也會影響頁面A的數據,那么我們是不是也要寫一個XXXXDelegate呢?

      這時我們的大腦嗅出了一些不好的味道,如果再來個什么dataD,dataE,我們要寫這么多的Delegate么?對于多對一”通知”這種味道,很自然的想到了不用Delegate,而是用NSNotification來做。讓我們未雨綢繆一下,定義一個Notificaiton

      NSString *const kCUDataChangedNotification = @"CUDataChangedNotification";

      [[NSNotificationCenter defaultCenter] postNotificationName:kCUDataChangedNotification object:nil userInfo:nil];</code></pre>

      那這個變化broadcast到listener,看上去是一個很贊的idea。

      demo code 1.0.3

      問題3

      過了一段時間,我們發現問題2的方法有一個Bug,當界面停在頁面B的時候,切換到頁面C,修改數據,B中再返回時,數據和頁面A的數據不一致。

      iOS APP 架構漫談數據不一致

      那也可以類比解決方法B,得到了下面的方法

      解決方法D

      既然A和B的數據不一致,而A的數據比B的新,那么保留一個B的指針,然后A變化的時候,更新B就好了。

      - (void)handleDataChangedNotification {
          [self updateLabel];
          [self.vc updateLabel];
      }

      // In a storyboard-based application, you will often want to do a little preparation before navigation

      • (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"push"]) {

          CUDetailViewController *vc = [segue destinationViewController];
          if ([vc isKindOfClass:[CUDetailViewController class]]) {
              self.vc = vc;
          }
        

        } }</code></pre>

        demo code 1.0.4

        問題4

        頁面C實在是太簡單了,這次我們希望在頁面C中顯示頁面A的數據。因為上次我們就產生了一個數據不一致的問題,這次我們注意到了,那么怎么修改呢?

        解決方法E

        在看了看整個APP各種通知之后,覺得挺麻煩,準備用一個取巧的方法。可以類比解決方法A。在頁面C出現的時候,刷新數據,至于什么性能問題,不管了,先fix bug。

        - (void)viewWillAppear:(BOOL)animated {
          [self updateLabel];
        }

      • (void)updateLabel { int dataB = [[CUDataDAO selectData].data intValue]; int dataC = [[CUDataDAO selectOtherData].data intValue];

        self.dataLabel.text = [@(dataB + dataC) stringValue]; }</code></pre>

        demo code 1.0.5

        問題5

        這時的數據需要不斷的變化,我們在CUDataDAO加了一個timer 模擬數據變化,數據變化的原因可能是server push 一些數據。client 本地數據庫更新了數據,需要在頁面A、B、C中顯示。

        頁面C的數據又不一致了。。。。

        問題到底在哪里呢

        走到這里,我們需要重新思考為什么這個問題會不斷的重復出現呢?software architecture就是來解決這個問題的。但是在提出一個合理的方案之前,先思考一個概念。

        我們把數據庫中的數據,顯示到屏幕上,或是傳遞給View時,這個過程其實是對data 做了一次copy。而且只要不是通過引用或是指針這些方式,通過值傳遞的方式都是對data做了一次copy。而這個copy的過程,非常類似Cache

        iOS APP 架構漫談

        通常建立一個Cache會遇到2種問題。

        • Cache情況A: 與original Data 數據不一致,沒有及時更新
        • Cache情況B: 重復建立Cache

        讓我們用這個思路來看我們的解決方案

        解決方法A

        這是一個非常典型的Cache情況B。數據庫的數據并沒有變化,但我們卻多次重復計算cache

        解決方法B

        頁面之間的關系可以用下面來描述

        iOS APP 架構漫談

        這里我們隱隱能夠感覺到問題,A的數據變化依賴于2個地方。不急,再往后看

        解決方法C

        iOS APP 架構漫談

        解決方法D

        iOS APP 架構漫談

        事情變得更糟了

        解決方法E

        和解決方法A類似,同樣的重復計算Cache問題。

        實際上問題還會更糟

        現在還是一個簡單的Model,如果project變得很大,那么就會變成這個樣子

        iOS APP 架構漫談

        每一個X都可能是一個Bug。

        我們似乎已經找到問題了

        《Advanced iOS Application Architecture and Patterns》 中,把這個圖叫做information flow。我們的直覺會告訴我們,這個信息的傳遞,應該是自上而下的樹或是森林,而且最好是一個層次平衡結構,要清晰,每一個位置都有相對于的職責。那我們就需要制定一個規則。

        在想這個規則之前,如果把上面的圖背后的數據忘記,我們感覺這很類似內存模型。當然內存模型會比較復雜。但是我們可以借鑒很多”內存管理中的規則”,比如誰創建,誰銷毀。同樣,在我們的information flow中,我們希望誰創建Cache,誰更新Cache變化

        DAO的數據庫似乎很難做這件事情,我們引入了一個新的元素dataSource(當然他本身又是DAO的一個Cache)。其中A、B、C3個都會顯示數據,那么他們應該在一個層級,其中B、C會修改數據,他們會把這個數據返回給dataSource,而通過dataSource來把這個變化通知到A、B、C。

        iOS APP 架構漫談

        這樣帶來的好處很明顯,我們再添加一個D,也不會對其他地方的數據產生任何影響,我們的Unit Test、Mock也更加好寫。

        我們之前的思路錯在哪里呢?

        從局部來看,我們之前的思路都沒有任何問題,但是整體來看卻把問題隱藏化。關鍵的問題是在于沒有找到Truth,找到問題真正的地方。而找到真正的地方,需要我們在大腦中有一個清晰的information flow或是data flow。了解之間元素的相互關系,才能建立一個個的層。才能坐到真正的解耦,解耦并不是僅僅一個個的Manager,更重要的是建立一套清晰的flow機制,或是消息機制,如果沒有一套flow,中間引入的各種各樣的方法,即便使用了各種設計模式,整個software 依然是深度耦合

        疑問

        這個APP看上去交互非常復雜

        上面的model,有些同學還可能覺得這是交互上面的問題,這個交互看上去非常的復雜,不是一個好設計。

        我這里列舉一個實際的例子:

        A頁面要創建動畫,動畫背后包括很多數據,這些數據會在B,C甚至更多的頁面,或是后臺被修改。動畫本身實際上體現在View,而這些view可能不僅僅在A中有,B,C可能也會有部分的View。

        單例怎么樣

        當然我們可以用單例的法子。單例是個魔鬼,被很多濫用,這個場景用單例,其實僅僅是把全局變量合理的封裝在了單例下,因為這份數據,并沒有任何理由要一定是一份copy。

        recap

        在了解這個概念后,再看一些server的架構,規則時,也會更容易理解這些層之間的關系。包括

        • 為什么要規定那些層之間,不能相互調用,不能有靜態方法。
        • 一個層之間的model,不能有重疊功能,不能連表查詢。
        • 在哪個層才能調用另一個服務,而調用這個服務還必須要通過統一的接口

        software architecture 涵蓋的東西非常多。這篇只是一個引子,介紹了設計之前的準備工作。但是在實際過程中,我們的模型可能要比我這里寫的還要復雜很多。下一篇會介紹一種策略用來處理更加復雜模型的情況。

        最后附上一個完整功能的 demo code

        參考

        《Advanced iOS Application Architecture and Patterns》

        </div>
        來自:http://studentdeng.github.io/blog/2014/08/29/ios-architecture/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!