iOS的MVC框架之控制層的構建(上)

在我前面的兩篇文章裏面分別對MVC框架中的M層的定義和構建方法進行了深刻的介紹和探討。這篇文章則是想深刻的介紹一下咱們應該如何去構建控制層。控制層是聯繫視圖層和模型層的紐帶。如今也有很是多的文章宣揚所謂的去控制層或者弱化控制層的做用,以爲這部分是一個雞肋,他會使得應用變得臃腫不堪。那麼他是否有存在的必要呢? 通常的應用場景裏面,咱們都須要將各類界面呈現給用戶,而後用戶經過某些操做來達到某個目標。從上面的場景中能夠提取出呈現、操做、目標三個關鍵字。要呈現出什麼以及要完成什麼目標咱們必需要經過具體操做才能達成,也就是說是經過操做來驅動界面的不斷變化以及服務目標的不斷達成,操做是聯繫界面和目標的紐帶。爲了表徵這種真實的場景,在軟件建模和設計實現中也應如此。我想這也就是MVC框架這種應用模型設計的初衷吧。在MVC框架中V負責呈現C負責操做而M則負責目標。並且這種設計還有以下更多的考量:git

  • 視圖界面變幻無窮,會根據用戶的體驗不停的升級和優化,甚至同一個功能的先後兩個版本都有徹底不一樣的差別,或者某些視圖界面會分散到其餘視圖界面中去,又或原來分散的視圖界面又聚合到某個新視圖界面中來。也就是說視圖呈現部分是變化最大以及持久性最短的一個部分。
  • 模型(服務)則相對穩定,他只是提供了某些具體的基礎業務服務,並且這些服務更多的是服務水平的升級而非服務的徹底改變。所以模型部分是變化最小且持久性最長的一個部分。
  • 通常狀況下咱們對視圖界面上的操做控制須要調用多個服務來完成,或者不一樣的界面上的呈現可能會由同一個服務來支撐。所以咱們不能將界面呈現和服務目標進行一對一的強行綁定,咱們須要將呈現和模型進行解耦處理。

控制層的引入正是解決了上面的這些矛盾,他將視圖和模型的關聯減小到最低,同時也是將易變的和不變這種矛盾體進行了化解。控制層就是一箇中介者(參考設計模式中的中介者模式)咱們應該把具體的操做交給控制層來完成,而且由控制層來驅動視圖的呈現和服務的提供。這看來好像是一種最優的解決方案。程序員

控制器--功能的劃分邊界

那麼控制層除了具有處理操做以及實現視圖和模型之間聯繫的紐帶以外,還應該具備什麼特徵呢?github

應用程序從使用者的角度來看他其實就是可以提供某種能力的功能的集合。每一個功能都具備對應的展現效果以及提供對應的服務。並且有些功能又能夠細分爲更多的小功能。對於開發者來講功能是一種應用縱向的切分。開發者更喜歡將他說成爲模塊單元或者說是功能。每一個功能可以提供一個從界面到業務邏輯的完整單元,並且功能之間通常都比較獨立,功能之間一般經過接口來進行交互。這樣設計的好處是有利於下降系統內模塊之間的依賴耦合性,也有利於程序員之間的分工合做和任務劃分。所以不管從使用者仍是開發者的角度來看功能劃分都是一種很是好的應用程序構造方式。功能的展示在設計上咱們能夠理解爲經過視圖來完成,而業務邏輯實現則是由模型層來完成,因此必需要存在一個實體來將這二者關聯起來,而且起到統籌和控制的能力。這個實體由控制層的控制器來實現和擔當最合適。所以在實踐中咱們對功能的實現和劃分也一般是以控制器爲單位來構建的,控制器是工做在控制層。也就是說咱們在實現某個功能時一般是爲這個功能創建一個對應的控制器來實現的,控制器負責視圖的構建和業務模型的調用,而思想下的框架就是經典的MVC框架!objective-c

控制層在各平臺下的實現

目前主流的iOS和Android移動開發平臺所提供的都是MVC應用框架,尤爲是對於控制層的實現更是幾乎提供了相同的能力和方式。兩個平臺的控制層的實體都是由對應的控制器類來實現的(iOS叫UIViewController, Android叫Activity)。並且這兩個平臺上都提供了控制器的構建,視圖的呈現以及到控制器的銷燬的流程方法。這種實現機制是一個很是典型的模板方法設計模式,在基類中定義了一個控制器在生命週期內各環節的調用方法,您只須要在派生類中重載這些方法來完成控制器生命週期內各環節所要完成的動做或者處理的事情。爲了處理控制器之間的交互或者調用,系統提供了一個導航棧的管理類。導航棧負責各功能控制器的進入和退出,同時管理着全部的控制器。編程

相對於iOS的UIViewController來講Android的Activity其實對功能封裝得更加完全。Activity具備跨越進程的調用能力,所以做爲組件化的能力更增強大,同時控制器和控制器之間的耦合度也很是得低。對於控制器之間的參數傳遞都是經過序列化和反序列化來實現的。可是這也在某方面成爲了一個缺點,爲了解決這個問題,Android系統中又提供了一個叫Fragment類,這是一個較Activity而言的輕量級控制器,目的是爲了解決某些大功能須要拆解爲多個子功能來實現的問題以及解決功能之間的參數傳遞的問題。設計模式

iOS視圖控制器生命週期的介紹。

前面大致介紹了控制層中控制器的實現以及控制器的生命週期,同時也介紹了功能和控制器之間的對應關係,控制器是視圖和業務模型之間聯繫的紐帶,所以控制器必需要在生命週期內負責視圖的構建、管理視圖的呈現、處理用戶的操做、以及進行業務模型的調用等工做。爲了實現這些能力,控制器中採用了一種模板方法的設計模式來解決這個問題。這裏面我主要想介紹一下iOS視圖控制器爲解決這些問題而所作的實現。咱們知道iOS中的視圖控制器是叫UIViewController。在這個類中定義了不少的方法來描述控制器所處的狀態,而每一個從視圖控制器派生的類均可以重載對應的方法以便在視圖控制器的相應狀態下進行邏輯的處理。下面列出了常見的幾種狀態下的方法以及這種狀態下咱們一般應該要作的事情:安全

  • init 這個是控制器的構造方法bash

  • loadView 在這個方法中完成視圖的構造。若是你的視圖是由Storyboard或者XIB來構建那麼你不須要重載這個方法,可是若是你的視圖是經過代碼構建的話則應該重載這個方法。控制器的默認實現將會找到關聯的Storyboard或者XIB中的視圖佈局描述信息, 若是找到了則根據佈局描述來構建要呈現的視圖,若是沒有找到則會構建出一個默認的空視圖。多線程

  • viewDidLoad 這個方法被調用時表示視圖已經構建完畢了,通常在這裏構建模型層的業務模型對象,以及一些事件的綁定,委託delegate的設置等工做。若是你是經過代碼來構建佈局時,不建議在這裏進行視圖佈局的構建而應該將構建的代碼寫在loadView裏面去。架構

  • viewWillAppear 視圖將要呈現時調用,只有當將一個視圖添加到一個窗口UIWindow時視圖纔會呈現出來,所以這個方法是在將視圖添加到窗口前被調用。

  • viewDidAppear 視圖已經呈現到窗口中,這個方法會在視圖添加到窗口後被調用。

  • viewWillDisappear 視圖將要從窗口中刪除時被調用。

  • viewDidDisappear 視圖已經從窗口中刪除時調用。

  • dealloc 控制器被銷燬前被調用。

如何構建您的控制層

如何構建一個控制層是一個很是普遍的命題,須要具體業務具體分析。雖然如此老是還能找到一些共同點和方法論,一個優秀的設計方法,將不會出現所謂的控制器代碼膨脹的問題。MVC自己的框架思想很是的優秀,當出現問題時首先要考慮的並非去替換掉現有的框架而是從設計的角度去優化現有的代碼以及邏輯,讓整個系統達到一個最優的組合。

1. 功能文件夾的劃分

在前面的論述中能夠看出視圖控制器是功能實現的基本單元,一個視圖控制器是一個功能的載體。一個應用中具備多個功能,而一些類似的功能一般組成一個功能集,好比一個應用的註冊流程可能會分爲好幾步;好比說用戶體系的各類特性的設置;好比說一個訂單的支付部分等等。爲了對功能集進行管理,能夠將某些功能集下的全部功能放置到一個特定目錄中。最終的構成一個應用功能目錄樹:

功能目錄樹

經過對功能進行劃分管理,有利於功能的檢索和加強你應用系統的可理解性。操做系統以及XCODE上的文件夾就是一種很是常見的功能樹目錄構建方式。在進行功能目錄樹劃分時注意以下幾個要點。

  • 若是某些功能是一些基本的功能,可能多個其餘功能都會用到那麼能夠將這些功能提煉出來保存到一個特定的文件夾中(文件夾能夠命名爲Common或者Base之類的)。好比你能夠在系統提供的控制器的基礎上派生出你本身的控制器基類,而後把這些基類也能夠單獨的保存到一個文件夾中。
  • 最好不要以每一個功能單獨創建文件夾來管理。有些案例裏面會出現每一個VC的.h和.m文件都給他創建文件夾,其實這樣不可取,由於有可能致使文件夾過多而變得查找定位更加麻煩。咱們應該以類似的功能彙集在一塊兒來創建文件夾進行管理。
  • 有時候某個功能集可能過於龐大,這時候咱們能夠對功能集進行再次分類,並創建子文件夾進行管理,文件夾劃分不必定是單層樹形結構也能夠是多層樹形結構。
  • 在XCODE中能夠創建兩種文件夾:真實文件夾(New Group with Folder)和虛擬文件夾(New Group)。 這裏建議是最好創建虛擬的文件夾,緣由是爲了後續好管理,由於有時候可能出現控制器文件從一個文件夾移動到另一個文件夾的狀況(功能轉移)。若是你創建真實的文件夾的話,那麼移動後控制器所在的真實的文件夾就有可能會和你項目工程上的所在的文件夾對應不上的狀況。而用虛擬文件夾就不會出現這種狀況的發生。
  • 功能文件夾的劃分方法有不少種,你能夠從業務的角度來劃分文件夾,也能夠從你的應用界面上的展示來劃分文件夾。好比一個應用中咱們有商品展現體系、支付體系、用戶體系,而咱們的界面展現多是底部分爲首頁、購物車,個人組成的四個Tab界面。這時候你能夠按業務來分爲商品、支付、用戶文件夾,也能夠按展現界面來分爲首頁、購物車、個人文件夾。所以文件夾的劃分並無標準就看你我的的喜愛而定了。惟一的要求就是同一個文件夾內的功能要體現出聚合性強的原則,也就是在某一天甚至能夠將這部分單獨抽離出來構建一個子項目時而不須要進行進行大量的改變。

2. 基本控制器以及派生類。

一個應用老是會有本身獨特的設計風格,好比標題欄的文字和字體以及背景。或者咱們要對應用進行總體的監控,好比對界面進入退出進行埋點處理。所以咱們須要在系統提供的基本控制器UIViewController, UITableviewController, UINavigationController, UICollectionViewController等控制器之上進行派生類的構建,也就是實現某個具體功能的控制器不要從系統的控制器之上派生而應該從派生的控制器基類之上再派生出來。這樣咱們就能夠在咱們派生的控制器基類上增長一些具備本身特點的業務邏輯或者界面邏輯,也能夠實現某些AOP方面的處理。好比咱們能夠構建一個UINavigationController的派生基類,這樣在進行控制器的push以及pop以前或者以後進行一些特殊處理。 可是這樣問題就來了,由於OC語言並不支持多重繼承。而咱們可能要創建上面四個系統控制器的派生類,而且須要在類似的地方添加一樣的代碼,好比都要在viewDidLoad中添加一段類似的代碼。爲了實現這一點,就須要添加四份相同的代碼好比:

@interface XXXBaseViewController:UIViewController
@end

@implementation  XXXBaseViewController

-(void)helperfn
{
    //..實現特定的擴展功能。
}

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self helperfn];  //調用擴展方法
}

@end


@interface XXXBaseNavigationController: UINavigationController
@end

@implementation  XXXBaseNavigationController

-(void)helperfn
{
    //..實現特定的擴展功能。
}

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self helperfn];  //調用擴展方法
}

@end

//...分別實現的UITableviewController、UICollectionViewController等等都將實現重複的代碼。

複製代碼

很明顯這是一種低效率的解決方案,由於一旦需求變動咱們就可能須要對helperfn方法改動四次。怎麼解決這種問題呢?咱們分爲2種場景:

1、 全部的功能擴展中都不須要擴展屬性

在這種狀況下,由於擴展的方法中都不須要用到對象的實例屬性,因此咱們能夠經過創建分類(Category)的方法來實現這些共有的功能,咱們能夠爲UIViewController創建出一個分類來,並在這個分類中實現共有的方法,而後在每一個派生類的特定位置中調用這個共享的分類方法。具體代碼以下:

@interface UIViewController(XXXExtend)
    
     -(void)helperfn;
@end

@implementation  UIViewController(XXXExtend)

//一份實現
 -(void)helperfn
{
    //實現特定功能。
}

@end


@interface XXXBaseViewController:UIViewController
@end

@implementation  XXXBaseViewController

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self helperfn];  //調用擴展方法
}

@end


@interface XXXBaseNavigationController: UINavigationController
@end

@implementation  XXXBaseNavigationController

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self helperfn];  //調用擴展方法
}

@end

//...分別實現的UITableviewController、UICollectionViewController的派生類。


複製代碼

固然你也許以爲上面的方法須要在每一個派生類的特定地點都添加一遍代碼而感到麻煩,你也能夠採用method swizzle的方法來解決上述的問題,你能夠在分類的+load方法中實現代碼替換。下面是具體的代碼實現:

@interface UIViewController(XXXExtend)
@end

@implementation UIViewController(XXXExtend)

-(void)helperfn
{
    
}

-(void)viewDidLoadXXXExtend
{
    [self viewDidLoadXXXExtend];
    
    [self helperfn];
}


+(void)load
{
    Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
    Method swizzledMethod = class_getInstanceMethod(self, @selector(viewDidLoadXXXExtend));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end



@interface XXXBaseViewController:UIViewController
@end

@implementation  XXXBaseViewController

@end


@interface XXXBaseNavigationController: UINavigationController
@end

@implementation  XXXBaseNavigationController
@end

//...分別實現的UITableviewController、UICollectionViewController的派生類。

複製代碼
2、有須要擴展屬性的狀況。

若是你的基類擴展方法中有用到屬性的話那麼咱們知道分類中是不能支持編譯時擴展屬性的(可是支持運行時擴展屬性的增長)。除了用運算時擴展屬性的方法外,還能夠將共有的方法和屬性單獨提煉出來讓一個輔助類來實現,而後在派生基類的初始化方法中建立這個輔助類,而且後續的一些方法都委託給輔助類來實現。具體的結構設計以下:

//Helper.h

@interface Helper:NSObject

@property   id prop1;
@property   id prop2;

-(void)fn1;
-(void)fn2;

-(id)initWithViewController:(UIViewController*)vc;

@end

//Helper.m

@interface Helper()

@property(weak) UIViewController *vc;  //這裏必定要定義爲弱引用

@end

@implementation Helper

-(id)initWithViewController:(UIViewController*)vc
{
     self = [self init];
     if (self != nil)
    {
           _vc = vc;
    }

   return self;
}

-(void)fn1
 {
      //...
      self.vc.xxxx;  //這裏能夠訪問視圖控制器的方法。
 }


@end

..........................................

//XXXBaseViewController.h

@interface XXXBaseViewController:UIViewController

@property id prop1;

@end

//XXXBaseViewController.m

@interface XXXBaseViewController()

   @property(strong) Helper *helper;
@end

@implementation  XXXBaseViewController

-(id)init
{
     self = [super init];
    if (self != nil)
    {
            //在視圖控制器的初始化裏面初始化一個幫助對象。
           _helper = [[Helper alloc] initWithViewController:self];
    }

   return self;
}

//重寫控制器中的屬性,並把真實的實現委託給helper來完成
-(void)setProp1:(id)prop1
{
     self.helper.prop1 = prop1;
}

-(id)prop1
{
    return self.helper.prop1;
}

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self.helper fn1];  //調用幫助類的方法。
}

@end

//在你的其餘幾個派生類中採用一樣的機制來處理。

複製代碼

從上面能夠看出來,輔助類裏面設計了一個弱引用指針來指向控制器,而控制器則是強引用輔助類,這樣作的目的是爲了防止循環引用的發生,並且這種設計模式也是一種在實踐中很是經典的方法:有時候咱們須要將類A的某些功能委託給類B實現,而B又有可能會在特定的地方訪問A的屬性,爲了防止相互引用而造成死鎖致使兩個對象都沒法被釋放,這時候就須要使用強弱引用來解決這個問題。上面藉助輔助類來實現的方法能夠解決咱們的派生類中代碼重複的問題。上面的方法缺點就是咱們的派生類中須要編寫不少重複的、程式化的代碼。如何來精簡呢?其實咱們能夠藉助接口協議和OC中的forwarding機制來解決這些問題:

//Helper.h

@protocol Helper

//請將接口的屬性和方法都設置爲可選
@optional

 @property   id prop1;
 @property   id prop2;

-(void)fn1;
-(void)fn2;

@end

//真實實現接口的對象
@interface Helper:NSObject<Helper>

@property   id prop1;
@property   id prop2;

-(id)initWithViewController:(UIViewController*)vc;

@end

//Helper.m
@interface Helper()

@property(weak) UIViewController *vc;  //這裏必定要定義爲弱引用

@end

@implementation Helper

-(id)initWithViewController:(UIViewController*)vc
{
     self = [self init];
     if (self != nil)
    {
           _vc = vc;
    }

   return self;
}

-(void)fn1
 {
      //...
      self.vc.xxxx;  //這裏能夠訪問視圖控制器的方法。
 }

-(void)fn2
{
   //...
}

@end

..........................................

//XXXBaseViewController.h

@interface XXXBaseViewController:UIViewController

@end

//XXXBaseViewController.m

//這裏實現Helper協議,若是基類的擴展屬性能夠被外面訪問則應該在.h中的類申明裏面代表實現了Helper協議
@interface XXXBaseViewController()<Helper>
   @property(strong) Helper *helper;
@end

@implementation  XXXBaseViewController

-(id)init
{
     self = [super init];
    if (self != nil)
    {
            //在視圖控制器的初始化裏面初始化一個幫助對象。
           _helper = [[Helper alloc] initWithViewController:self];
    }

    return self;
}

-(void) viewDidLoad
{
    [super viewDidLoad];
    [self fn1];  //調用fn1
  
   id *p = self.prop1  //讀取屬性。

}

//這是實現的關鍵點,重載這個方法。
-(id)forwardingTargetForSelector:(SEL)aSelector
{
   //判斷這個方法是不是協議定義的方法,若是是則將方法的實現者設置爲helper對象。
    struct objc_method_description  omd = protocol_getMethodDescription(@protocol(Helper), aSelector, NO, YES);
    if (omd.name != NULL)
    {
        return self.helper;
    }
    
    return [super forwardingTargetForSelector:aSelector];
    
}

@end

//在你的其餘幾個派生類中採用一樣的機制來處理。

複製代碼

能夠看出咱們能夠藉助OC的forwardingTargetForSelector方法來實現方法調用的轉發處理,而沒必要具體的定義方法的實現。

3.對內和對外的接口定義

面向對象編程的一個主旨思想就是封裝,所謂封裝就是在進行類的定義和設計時,咱們儘量的暴露出外面能夠理解以及須要的接口或者方法,而在內部實現中所用到的中間特性或者私有特性則儘量的隱藏。儘量的隱藏複雜的實現細節,而把簡單易用的接口暴露給外部使用。好比對於汽車的封裝,咱們對外暴露的就僅僅是開鎖、發動、掛擋、轉向等操做,而具體內部的組成、發動機的原理、以及發動機如何驅動行走等細節都不須要開車的人瞭解。正是面向對象這種封裝的特性就使得咱們能更加從應用層面去使用某個對象的方法而不須要知道其中的細節。所以咱們在類的設計中也要遵循這個設計的思想,把必要的東西暴露給外部,而把實現細節則隱藏在類的內部來完成。這一節所介紹的並不只僅適用在控制器類的設計上,全部其餘系統也是一樣適用的。 類的封裝實如今不一樣的語言上所提供的能力是不同的,這一點很是有意思。向在C/C++/OC這幾種語言中,類的聲明和類的實現須要在不一樣的文件裏面完成(.h是聲明,而.m/.c/.cpp中則是實現)而像Java和Swift等語言則是申明和實現都放在同一文件中完成。在前面的三種語言中由於聲明和實現分離,因此咱們能夠把一些對外暴露的方法和屬性放到頭文件中申明,而內部的私有屬性則放到實現文件中申明和定義。而使用者則只須要引入共有頭文件便可。然後面兩種語言中由於沒有分開,因此在這些語言更傾向於經過接口定義和實現來完成這種共有屬性和私有屬性分類的機制(您能夠看出在Java中大量的使用了接口來完成整個體系架構,以及Swift中也是推崇接口編程這種理念)。

面向對象設計中,類和類之間不可能獨立存在,他們之間老是要創建一種關聯,這種關聯有多是單向的也有多是雙向的。咱們都推崇類和類之間的單向依賴來下降類與類之間的耦合性。這種單向依賴至少在明面上是如此的,也就是類所公開的方法和屬性是能夠看出來的。可是在實際的內部實現中這種單向依賴能夠就會被打破。舉一個很常見的例子咱們都知道視圖控制器UIViewController中有一個view屬性來保存控制器所管理的視圖,可是咱們在視圖UIView中卻看不見任何關於控制器的屬性。這樣的表象就是代表視圖控制器依賴視圖,而視圖則不依賴視圖控制器,這也是很是符合MVC中三層設計思路的。但實際中是如此嗎? 結果並非這樣的,由於在系統的內部若是某個視圖是控制器的根視圖的話他可能會具備一些不一樣的特性以及不一樣的處理邏輯,所以其實在UIView的內部私有屬性中是有一個視圖所歸屬的視圖控制器的屬性的,這個屬性就是:viewDelegate。 而且在UIView上他是定義爲了id類型的。

上面的兩段描述中咱們都提到了對公和對私的方法和屬性的申明的問題,能夠看出在設計上要按照這個思路去設計咱們的類,咱們只須要將共有的方法和屬性暴露出來,而將私有的方法和屬性則隱藏起來。那麼怎麼來實現這種共有和私有方法的定義實現呢?咱們來看下面三個具體的場景:

  • 類的某些屬性公開某些屬性不公開
/*一個類裏面某些屬性公開某些屬性不公開的實現能夠很簡單的經過類的申明和類的擴展來實現*/

//XXXX.h

@interface XXXX
 
//對外公開的屬性
 @property  id pubp1;
 @property  id pubp2;

//對外公開的方法
-(void)pubfn1;
-(void)pubfn2;
 
@end

//XXXX.m

//只在內部使用的屬性和方法定義在擴展中。
@interface XXXX()

//對內私有的屬性
 @property  id prip1;
 @property  id prip2;

//對內私有的方法
-(void)prifn1;
-(void)prifn2;

@end

@implementation  XXXX
@end

複製代碼
  • 類的某些屬性對外暴露的是隻讀的,可是內部實現確實能夠被改變的。這樣作的目的是爲了訪問和使用的安全。
//XXXX.h

@interface XXXX
 
//對外公開的屬性只讀
 @property(readonly)  id pubp1;
 @property(readonly)  id pubp2;
 
@end

//XXXX.m

//只在內部使用的屬性和方法定義在擴展中。
@interface XXXX()

//在類的內部這些屬性是可讀寫的
 @property  id pubp1;
 @property  id pubp1;

@end

@implementation  XXXX
@end

複製代碼
  1. 類A和類B之間有關聯,A中持有B的實例並公開,而B則有可能在實現中須要用到A的內部的方法或者屬性,同時B是不向外暴露對A的持有的狀況。由於咱們都是經過頭文件引用,因此在頭文件中看不到這種互相依賴關係,可是內部的實現文件則能夠很清楚的看到其中的依賴了。
//A.h

@interface A

@property(strong)  B *b;   //A的外部暴露持有B
@property  id others;

-(void) pubfn1;

@end

//A.m

//在A的內部用到了B的prip1, prifn1因此要這裏申明一下。
@interface B()

@property  id prip1;
-(void)prifn1;

@end

//內部實現
@interface A()

  @property id prip1;
  -(void)prifn1;

@end

@implementation  A

-(void)pubfn1
{
    //...
    [b prifn1];    //A內部調用B的私有方法
    [b pubfn1];

    id temp = b.prip1;  //內部屬性
}

@end

...............................

//B.h

//B的外部並不暴露對A的持有
@interface B

-(void)pubfn1;

@end

//B.m

@interface A()

//由於B內部要用到prifn1因此這裏再申明一下。
-(void)prifn1;

@end


@interface B()

//B的內部持有A。這裏由於有相互持有全部要有一個是強持有一個是弱持有。
@property(weak) A *a;   

//內部實現的其餘
@property id prip1;
-(void)prifn1;

@end

@implementation  B

-(void)pubfn1
{
    [a prifn1];
}

@end



複製代碼

4.各類屬性的定義以及分類擴展

控制器用來實現對視圖對象和業務模型對象的創建以及管理和控制,在實現上控制器會擁有衆多視圖層對象的屬性,以及模型層對象的屬性,同時還會擁有自身的一些屬性。同時控制器還要在適當的時候對用戶的輸入進行處理,以及在適當的時候調用業務模型所提供的服務,還要在適當的時候將業務模型提供服務的結果通知給視圖進行呈現和更新。所以如何去組織一個控制器的代碼佈局(此代碼佈局非視圖的界面佈局而是源代碼的佈局)就很是的重要了。如何合理的定義以及放置屬性,如何合理的對控制器中的方法進行分類,以及在什麼時候建立視圖、在什麼時候建立業務對象,在什麼時候添加和銷燬觀察者,在類的析構中做如何處理等等這些其實都是有必定的規則和規範的。這一節更像是一份代碼規約方面的介紹。我將會從下面幾個點來分別闡述。

(一). 屬性的定義順序和規則 一個類的設計首要構造的就是屬性和成員變量,控制器也無外乎。前面說到控制器管理着視圖對象和模型對象,所以咱們通常要將視圖對象和業務對象做爲屬性定義在控制器中。這裏整理出一下幾點:

  • 若是控制器中的屬性和成員變量只在類內部使用和訪問,那麼咱們應該要將屬性定義在控制器的實現文件中的擴展裏面,而不要定義在控制器的頭文件中,除非這個屬性會被外部訪問或者設置。好比以下代碼:
//XXX.m

@interface XXX()

//在擴展中定義內部使用的屬性
  @property(nonatomic, weak)  UILabel *label;
  @property(nonatomic, strong)  User *user; 
@end

@implement XXX
@end   


複製代碼
  • 若是控制器中須要訪問某個子控件視圖那麼在定義子控件視圖時,屬性最好是weak而非strong。這樣作的目的一來iOS對於SB或者XIB上的子控件的屬性定義都是默認爲weak的、二來最主要的緣由是有可能控制器中的根視圖有可能會在運行時被從新構造(好比說咱們要實現一個換膚功能,咱們就有可能會從新構造視圖控制器中的根視圖來實現)這樣當控制器中的根視圖被銷燬時,根視圖裏面的子視圖也應該被銷燬,而若是你用strong來定義子視圖時就有可能致使子視圖的生命週期要長於根視圖。另外有可能咱們的子控件會採用懶加載的模式來實現根視圖中子視圖的創建,所以若是你用strong的話就有可能致使子視圖不會被從新構建。

  • 對於NSString類型的屬性來講咱們最好將他聲明爲copy。緣由是若是聲明爲strong或者assign的話,那麼對於NSMutableString類型的字符串進行賦值時就有可能會在後續的代碼中內容被改變,從而引發異常的問題。好比:

NSMutableString *str1 = [@"hello" mutableCopy];
    self.userName =   str1;   //若是userName被申明爲strong的話則後續對str1的內容的更改同時也會致使userName的內容的改變!!
複製代碼
  • 若是你的屬性不會涉及到任何多線程訪問的場景那麼最好不要在屬性定義上帶上atomic 修飾符。緣由是若是帶上atomic修飾符的話全部屬性的賦值和讀取操做都會經過操做系統原子API來進行賦值和讀取。

  • 不要將狀態以及持久數據保存到視圖對象中。

  • 若是可能最好將控制器中的視圖對象屬性和模型對象屬性分開定義,而且把視圖對象屬性放在最上面, 控制器本地的屬性放在中間,而模型對象屬性放在最下面。

下面是一個典型的控制器屬性定義的代碼示例,僅供你們參考:

//XXXViewController.m

@interface XXXViewController()

  //不一樣層次上的屬性分開定義
  
  //視圖對象屬性放在最上面。
  @property(nonatomic, weak)  UITextField *nameLabel;
  @property(nonatomic, weak)  UITextField *passwordLabel;

//中間定義控制器自己的變量
  @property(nonatomic, copy)  NSString *name;
  @property(nonatomic, assign) BOOL isAgree;

 //底部定義模型對象屬性 
@property(nonatomic, strong)  User *user;

@end

@implement XXXViewController
@end   
複製代碼

(二). 類中各類方法的分類 當你的屬性定義完畢後,咱們就要實現方法了。在一個類的方法中咱們有構造和析構的方法、有須要重載的方法、有事件處理的方法、有個委託Delegate或者觀察者的方法、還有一些對外公開的方法、以及一些私有輔助的方法。爲了便於管理,咱們最好能將這些方法進行分類擺放,這樣也有利於查找定位,對於一個控制器來講通常就是上面所說的幾種方法,通常狀況下咱們對相同性質的方法放在一塊實現,並用一些特定的關鍵字或者用分類的機制來對控制器中的全部方法進行歸類。下面我用兩種不一樣的方式來對方法進行歸類處理:

  • 經過語法關鍵字。 在OC中咱們能夠經過 #progma mark -- 名稱 來便於定位和查找。在實踐中控制器類通常都要實現:重寫基類的方法、公有方法、事件處理方法、Delegate中的方法、私有方法這幾種類型,所以咱們能夠專門爲這些方法定義不一樣的標籤。具體以下:
//  XXXViewController.m

@implement XXXViewController

//最開始放構造和析構的方法。這兩個方法放在一塊兒就能夠清楚的看出我初始化了那些東西,析構了那些東西。
-(instanceType)initXXXXX{}
-(void)dealloc{}

//接下來放重載的方法。對屬性set,get重寫的方法
-(void)viewDidLoad{}
-(void)viewWillAppear:(BOOL)animated{}
-(void)viewDidAppear:(BOOL)animated{}
-(NSString*)name{}
-(void)setName:(NSString*)name{}

//接下來放共有的方法,也就是控制器類暴露給外面調用的方法。
#pragma mark -- Public Methods
-(void)publicFn1{}
-(void)publicFn2{}

//接下來放事件處理的方法,這些事件處理包括控件的觸摸事件,NSNotificationCenter、定時器事件、其餘的註冊的事件。我我的比較喜歡以handle開頭。

#pragma mark -- Handle Methods

-(void)handleAction1:(id)sender{}
-(void)handleAction2:(NSNotification*)noti{}
-(void)handleAction3:(NSTimer*)timer{}


//接下來是放各類Delegate的事件,咱們名稱都以Delegate協議的名稱作標誌。

#pragma mark -- UIDataSource

//..這裏添加代理的方法。。。

#pragma mark -- UITableViewDelegate


//接下來是存放各類私有方法。

#pragma mark -- Private Methods

-(void)privateFn1{}
-(void)privateFn2{}









@end

複製代碼
  • 經過分類 若是你不想使用#pragma 帶標籤的形式,那也能夠用分類的方式來實現。好比下面的例子:
//  XXXViewController.m

@implement XXXViewController

//默認的部分實現,構造和析構方法以及全部從基類重載的方法
-(instanceType)initXXXXX{}
-(void)dealloc{}

//接下來放重載的方法。對屬性set,get重寫的方法
-(void)viewDidLoad{}
-(void)viewWillAppear:(BOOL)animated{}
-(void)viewDidAppear:(BOOL)animated{}
-(NSString*)name{}
-(void)setName:(NSString*)name{}

@end

//公有方法
@implement XXXViewController(Public)
@end

//事件處理方法
@implement XXXViewController(Handle)
@end

@implement XXXViewController(UIDataSource)
@end
   
@implement XXXViewController(UITableViewDelegate)
@end

@implement XXXViewController(Private)
@end

複製代碼

歡迎你們訪問個人github地址, 關注歐陽大哥2013

相關文章
相關標籤/搜索