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

在我前面的兩篇文章里面分別對(duì)MVC框架中的M層的定義和構(gòu)建方法進(jìn)行了深入的介紹和探討。這篇文章則是想深入的介紹一下我們應(yīng)該如何去構(gòu)建控制層。控制層是聯(lián)系視圖層和模型層的紐帶。現(xiàn)在也有非常多的文章宣揚(yáng)所謂的去控制層或者弱化控制層的作用,覺(jué)得這部分是一個(gè)雞肋,他會(huì)使得應(yīng)用變得臃腫不堪。那么他是否有存在的必要呢?
一般的應(yīng)用場(chǎng)景里面,我們都需要將各種界面呈現(xiàn)給用戶,然后用戶通過(guò)某些操作來(lái)達(dá)到某個(gè)目標(biāo)。從上面的場(chǎng)景中可以提取出呈現(xiàn)、操作、目標(biāo)三個(gè)關(guān)鍵字。要呈現(xiàn)出什么以及要完成什么目標(biāo)我們必須要通過(guò)具體操作才能達(dá)成,也就是說(shuō)是通過(guò)操作來(lái)驅(qū)動(dòng)界面的不斷變化以及服務(wù)目標(biāo)的不斷達(dá)成,操作是聯(lián)系界面和目標(biāo)的紐帶。為了表征這種真實(shí)的場(chǎng)景,在軟件建模和設(shè)計(jì)實(shí)現(xiàn)中也應(yīng)如此。我想這也就是MVC框架這種應(yīng)用模型設(shè)計(jì)的初衷吧。在MVC框架中V負(fù)責(zé)呈現(xiàn)C負(fù)責(zé)操作而M則負(fù)責(zé)目標(biāo)。而且這種設(shè)計(jì)還有如下更多的考量:

  • 視圖界面千變?nèi)f化,會(huì)根據(jù)用戶的體驗(yàn)不停的升級(jí)和優(yōu)化,甚至同一個(gè)功能的前后兩個(gè)版本都有完全不同的差異,或者某些視圖界面會(huì)分散到其他視圖界面中去,又或原來(lái)分散的視圖界面又聚合到某個(gè)新視圖界面中來(lái)。也就是說(shuō)視圖呈現(xiàn)部分是變化最大以及持久性最短的一個(gè)部分。
  • 模型(服務(wù))則相對(duì)穩(wěn)定,他只是提供了某些具體的基礎(chǔ)業(yè)務(wù)服務(wù),而且這些服務(wù)更多的是服務(wù)水平的升級(jí)而非服務(wù)的完全改變。因此模型部分是變化最小且持久性最長(zhǎng)的一個(gè)部分。
  • 一般情況下我們對(duì)視圖界面上的操作控制需要調(diào)用多個(gè)服務(wù)來(lái)完成,或者不同的界面上的呈現(xiàn)可能會(huì)由同一個(gè)服務(wù)來(lái)支撐。因此我們不能將界面呈現(xiàn)和服務(wù)目標(biāo)進(jìn)行一對(duì)一的強(qiáng)行綁定,我們需要將呈現(xiàn)和模型進(jìn)行解耦處理。

控制層的引入正是解決了上面的這些矛盾,他將視圖和模型的關(guān)聯(lián)減少到最低,同時(shí)也是將易變的和不變這種矛盾體進(jìn)行了化解。控制層就是一個(gè)中介者(參考設(shè)計(jì)模式中的中介者模式)我們應(yīng)該把具體的操作交給控制層來(lái)完成,并且由控制層來(lái)驅(qū)動(dòng)視圖的呈現(xiàn)和服務(wù)的提供。這看來(lái)好像是一種最優(yōu)的解決方案。

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

那么控制層除了具備處理操作以及實(shí)現(xiàn)視圖和模型之間聯(lián)系的紐帶之外,還應(yīng)該具有什么特征呢?

應(yīng)用程序從使用者的角度來(lái)看他其實(shí)就是能夠提供某種能力的功能的集合。每個(gè)功能都具有對(duì)應(yīng)的展示效果以及提供對(duì)應(yīng)的服務(wù)。而且有些功能又可以細(xì)分為更多的小功能。對(duì)于開(kāi)發(fā)者來(lái)說(shuō)功能是一種應(yīng)用縱向的切分。開(kāi)發(fā)者更喜歡將他說(shuō)成為模塊單元或者說(shuō)是功能。每個(gè)功能能夠提供一個(gè)從界面到業(yè)務(wù)邏輯的完整單元,而且功能之間一般都比較獨(dú)立,功能之間通常通過(guò)接口來(lái)進(jìn)行交互。這樣設(shè)計(jì)的好處是有利于降低系統(tǒng)內(nèi)模塊之間的依賴耦合性,也有利于程序員之間的分工合作和任務(wù)劃分。因此無(wú)論從使用者還是開(kāi)發(fā)者的角度來(lái)看功能劃分都是一種非常好的應(yīng)用程序構(gòu)造方式。功能的展現(xiàn)在設(shè)計(jì)上我們可以理解為通過(guò)視圖來(lái)完成,而業(yè)務(wù)邏輯實(shí)現(xiàn)則是由模型層來(lái)完成,所以必須要存在一個(gè)實(shí)體來(lái)將這兩者關(guān)聯(lián)起來(lái),并且起到統(tǒng)籌和控制的能力。這個(gè)實(shí)體由控制層的控制器來(lái)實(shí)現(xiàn)和擔(dān)當(dāng)最合適。因此在實(shí)踐中我們對(duì)功能的實(shí)現(xiàn)和劃分也通常是以控制器為單位來(lái)構(gòu)建的,控制器是工作在控制層。也就是說(shuō)我們?cè)趯?shí)現(xiàn)某個(gè)功能時(shí)通常是為這個(gè)功能建立一個(gè)對(duì)應(yīng)的控制器來(lái)實(shí)現(xiàn)的,控制器負(fù)責(zé)視圖的構(gòu)建和業(yè)務(wù)模型的調(diào)用,而思想下的框架就是經(jīng)典的MVC框架!

控制層在各平臺(tái)下的實(shí)現(xiàn)

目前主流的iOS和Android移動(dòng)開(kāi)發(fā)平臺(tái)所提供的都是MVC應(yīng)用框架,尤其是對(duì)于控制層的實(shí)現(xiàn)更是幾乎提供了相同的能力和方式。兩個(gè)平臺(tái)的控制層的實(shí)體都是由對(duì)應(yīng)的控制器類(lèi)來(lái)實(shí)現(xiàn)的(iOS叫UIViewController, Android叫Activity)。而且這兩個(gè)平臺(tái)上都提供了控制器的構(gòu)建,視圖的呈現(xiàn)以及到控制器的銷(xiāo)毀的流程方法。這種實(shí)現(xiàn)機(jī)制是一個(gè)非常典型的模板方法設(shè)計(jì)模式,在基類(lèi)中定義了一個(gè)控制器在生命周期內(nèi)各環(huán)節(jié)的調(diào)用方法,您只需要在派生類(lèi)中重載這些方法來(lái)完成控制器生命周期內(nèi)各環(huán)節(jié)所要完成的動(dòng)作或者處理的事情。為了處理控制器之間的交互或者調(diào)用,系統(tǒng)提供了一個(gè)導(dǎo)航棧的管理類(lèi)。導(dǎo)航棧負(fù)責(zé)各功能控制器的進(jìn)入和退出,同時(shí)管理著所有的控制器。

相對(duì)于iOS的UIViewController來(lái)說(shuō)Android的Activity其實(shí)對(duì)功能封裝得更加徹底。Activity具有跨越進(jìn)程的調(diào)用能力,因此作為組件化的能力更加強(qiáng)大,同時(shí)控制器和控制器之間的耦合度也非常得低。對(duì)于控制器之間的參數(shù)傳遞都是通過(guò)序列化和反序列化來(lái)實(shí)現(xiàn)的。但是這也在某方面成為了一個(gè)缺點(diǎn),為了解決這個(gè)問(wèn)題,Android系統(tǒng)中又提供了一個(gè)叫Fragment類(lèi),這是一個(gè)較Activity而言的輕量級(jí)控制器,目的是為了解決某些大功能需要拆解為多個(gè)子功能來(lái)實(shí)現(xiàn)的問(wèn)題以及解決功能之間的參數(shù)傳遞的問(wèn)題。

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

前面大體介紹了控制層中控制器的實(shí)現(xiàn)以及控制器的生命周期,同時(shí)也介紹了功能和控制器之間的對(duì)應(yīng)關(guān)系,控制器是視圖和業(yè)務(wù)模型之間聯(lián)系的紐帶,因此控制器必須要在生命周期內(nèi)負(fù)責(zé)視圖的構(gòu)建、管理視圖的呈現(xiàn)、處理用戶的操作、以及進(jìn)行業(yè)務(wù)模型的調(diào)用等工作。為了實(shí)現(xiàn)這些能力,控制器中采用了一種模板方法的設(shè)計(jì)模式來(lái)解決這個(gè)問(wèn)題。這里面我主要想介紹一下iOS視圖控制器為解決這些問(wèn)題而所做的實(shí)現(xiàn)。我們知道iOS中的視圖控制器是叫UIViewController。在這個(gè)類(lèi)中定義了很多的方法來(lái)描述控制器所處的狀態(tài),而每個(gè)從視圖控制器派生的類(lèi)都可以重載對(duì)應(yīng)的方法以便在視圖控制器的相應(yīng)狀態(tài)下進(jìn)行邏輯的處理。下面列出了常見(jiàn)的幾種狀態(tài)下的方法以及這種狀態(tài)下我們通常應(yīng)該要做的事情:

  • init
    這個(gè)是控制器的構(gòu)造方法

  • loadView
    在這個(gè)方法中完成視圖的構(gòu)造。如果你的視圖是由Storyboard或者XIB來(lái)構(gòu)建那么你不需要重載這個(gè)方法,但是如果你的視圖是通過(guò)代碼構(gòu)建的話則應(yīng)該重載這個(gè)方法。控制器的默認(rèn)實(shí)現(xiàn)將會(huì)找到關(guān)聯(lián)的Storyboard或者XIB中的視圖布局描述信息, 如果找到了則根據(jù)布局描述來(lái)構(gòu)建要呈現(xiàn)的視圖,如果沒(méi)有找到則會(huì)構(gòu)建出一個(gè)默認(rèn)的空視圖。

  • viewDidLoad
    這個(gè)方法被調(diào)用時(shí)表示視圖已經(jīng)構(gòu)建完畢了,一般在這里構(gòu)建模型層的業(yè)務(wù)模型對(duì)象,以及一些事件的綁定,委托delegate的設(shè)置等工作。如果你是通過(guò)代碼來(lái)構(gòu)建布局時(shí),不建議在這里進(jìn)行視圖布局的構(gòu)建而應(yīng)該將構(gòu)建的代碼寫(xiě)在loadView里面去。

  • viewWillAppear
    視圖將要呈現(xiàn)時(shí)調(diào)用,只有當(dāng)將一個(gè)視圖添加到一個(gè)窗口UIWindow時(shí)視圖才會(huì)呈現(xiàn)出來(lái),因此這個(gè)方法是在將視圖添加到窗口前被調(diào)用。

  • viewDidAppear
    視圖已經(jīng)呈現(xiàn)到窗口中,這個(gè)方法會(huì)在視圖添加到窗口后被調(diào)用。

  • viewWillDisappear
    視圖將要從窗口中刪除時(shí)被調(diào)用。

  • viewDidDisappear
    視圖已經(jīng)從窗口中刪除時(shí)調(diào)用。

  • dealloc
    控制器被銷(xiāo)毀前被調(diào)用。

如何構(gòu)建您的控制層

如何構(gòu)建一個(gè)控制層是一個(gè)非常廣泛的命題,需要具體業(yè)務(wù)具體分析。雖然如此總是還能找到一些共同點(diǎn)和方法論,一個(gè)優(yōu)秀的設(shè)計(jì)方法,將不會(huì)出現(xiàn)所謂的控制器代碼膨脹的問(wèn)題。MVC本身的框架思想非常的優(yōu)秀,當(dāng)出現(xiàn)問(wèn)題時(shí)首先要考慮的并不是去替換掉現(xiàn)有的框架而是從設(shè)計(jì)的角度去優(yōu)化現(xiàn)有的代碼以及邏輯,讓整個(gè)系統(tǒng)達(dá)到一個(gè)最優(yōu)的組合。

1. 功能文件夾的劃分

在前面的論述中可以看出視圖控制器是功能實(shí)現(xiàn)的基本單元,一個(gè)視圖控制器是一個(gè)功能的載體。一個(gè)應(yīng)用中具有多個(gè)功能,而一些相似的功能通常組成一個(gè)功能集,比如一個(gè)應(yīng)用的注冊(cè)流程可能會(huì)分為好幾步;比如說(shuō)用戶體系的各種特性的設(shè)置;比如說(shuō)一個(gè)訂單的支付部分等等。為了對(duì)功能集進(jìn)行管理,可以將某些功能集下的所有功能放置到一個(gè)特定目錄中。最終的構(gòu)成一個(gè)應(yīng)用功能目錄樹(shù):

功能目錄樹(shù)

通過(guò)對(duì)功能進(jìn)行劃分管理,有利于功能的檢索和增強(qiáng)你應(yīng)用系統(tǒng)的可理解性。操作系統(tǒng)以及XCODE上的文件夾就是一種非常常見(jiàn)的功能樹(shù)目錄構(gòu)建方式。在進(jìn)行功能目錄樹(shù)劃分時(shí)注意如下幾個(gè)要點(diǎn)。

  • 如果某些功能是一些基本的功能,可能多個(gè)其他功能都會(huì)用到那么可以將這些功能提煉出來(lái)保存到一個(gè)特定的文件夾中(文件夾可以命名為Common或者Base之類(lèi)的)。比如你可以在系統(tǒng)提供的控制器的基礎(chǔ)上派生出你自己的控制器基類(lèi),然后把這些基類(lèi)也可以單獨(dú)的保存到一個(gè)文件夾中。
  • 最好不要以每個(gè)功能單獨(dú)建立文件夾來(lái)管理。有些案例里面會(huì)出現(xiàn)每個(gè)VC的.h和.m文件都給他建立文件夾,其實(shí)這樣不可取,因?yàn)橛锌赡軐?dǎo)致文件夾過(guò)多而變得查找定位更加麻煩。我們應(yīng)該以相似的功能聚集在一起來(lái)建立文件夾進(jìn)行管理。
  • 有時(shí)候某個(gè)功能集可能過(guò)于龐大,這時(shí)候我們可以對(duì)功能集進(jìn)行再次分類(lèi),并建立子文件夾進(jìn)行管理,文件夾劃分不一定是單層樹(shù)形結(jié)構(gòu)也可以是多層樹(shù)形結(jié)構(gòu)。
  • 在XCODE中可以建立兩種文件夾:真實(shí)文件夾(New Group with Folder)和虛擬文件夾(New Group)。 這里建議是最好建立虛擬的文件夾,原因是為了后續(xù)好管理,因?yàn)橛袝r(shí)候可能出現(xiàn)控制器文件從一個(gè)文件夾移動(dòng)到另外一個(gè)文件夾的情況(功能轉(zhuǎn)移)。如果你建立真實(shí)的文件夾的話,那么移動(dòng)后控制器所在的真實(shí)的文件夾就有可能會(huì)和你項(xiàng)目工程上的所在的文件夾對(duì)應(yīng)不上的情況。而用虛擬文件夾就不會(huì)出現(xiàn)這種情況的發(fā)生。
  • 功能文件夾的劃分方法有很多種,你可以從業(yè)務(wù)的角度來(lái)劃分文件夾,也可以從你的應(yīng)用界面上的展現(xiàn)來(lái)劃分文件夾。比如一個(gè)應(yīng)用中我們有商品展示體系、支付體系、用戶體系,而我們的界面展示可能是底部分為首頁(yè)、購(gòu)物車(chē),我的組成的四個(gè)Tab界面。這時(shí)候你可以按業(yè)務(wù)來(lái)分為商品、支付、用戶文件夾,也可以按展示界面來(lái)分為首頁(yè)、購(gòu)物車(chē)、我的文件夾。因此文件夾的劃分并沒(méi)有標(biāo)準(zhǔn)就看你個(gè)人的喜好而定了。唯一的要求就是同一個(gè)文件夾內(nèi)的功能要體現(xiàn)出聚合性強(qiáng)的原則,也就是在某一天甚至可以將這部分單獨(dú)抽離出來(lái)構(gòu)建一個(gè)子項(xiàng)目時(shí)而不需要進(jìn)行進(jìn)行大量的改變。

2. 基本控制器以及派生類(lèi)。

一個(gè)應(yīng)用總是會(huì)有自己獨(dú)特的設(shè)計(jì)風(fēng)格,比如標(biāo)題欄的文字和字體以及背景。或者我們要對(duì)應(yīng)用進(jìn)行整體的監(jiān)控,比如對(duì)界面進(jìn)入退出進(jìn)行埋點(diǎn)處理。因此我們需要在系統(tǒng)提供的基本控制器UIViewController, UITableviewController, UINavigationController, UICollectionViewController等控制器之上進(jìn)行派生類(lèi)的構(gòu)建,也就是實(shí)現(xiàn)某個(gè)具體功能的控制器不要從系統(tǒng)的控制器之上派生而應(yīng)該從派生的控制器基類(lèi)之上再派生出來(lái)。這樣我們就可以在我們派生的控制器基類(lèi)上增加一些具有自己特色的業(yè)務(wù)邏輯或者界面邏輯,也可以實(shí)現(xiàn)某些AOP方面的處理。比如我們可以構(gòu)建一個(gè)UINavigationController的派生基類(lèi),這樣在進(jìn)行控制器的push以及pop之前或者之后進(jìn)行一些特殊處理。 但是這樣問(wèn)題就來(lái)了,因?yàn)镺C語(yǔ)言并不支持多重繼承。而我們可能要建立上面四個(gè)系統(tǒng)控制器的派生類(lèi),并且需要在相似的地方添加同樣的代碼,比如都要在viewDidLoad中添加一段相似的代碼。為了實(shí)現(xiàn)這一點(diǎn),就需要添加四份相同的代碼比如:

@interface XXXBaseViewController:UIViewController
@end

@implementation  XXXBaseViewController

-(void)helperfn
{
    //..實(shí)現(xiàn)特定的擴(kuò)展功能。
}

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

@end


@interface XXXBaseNavigationController: UINavigationController
@end

@implementation  XXXBaseNavigationController

-(void)helperfn
{
    //..實(shí)現(xiàn)特定的擴(kuò)展功能。
}

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

@end

//...分別實(shí)現(xiàn)的UITableviewController、UICollectionViewController等等都將實(shí)現(xiàn)重復(fù)的代碼。

很明顯這是一種低效率的解決方案,因?yàn)橐坏┬枨笞兏覀兙涂赡苄枰獙?duì)helperfn方法改動(dòng)四次。怎么解決這種問(wèn)題呢?我們分為2種場(chǎng)景:

一、 所有的功能擴(kuò)展中都不需要擴(kuò)展屬性

在這種情況下,因?yàn)閿U(kuò)展的方法中都不需要用到對(duì)象的實(shí)例屬性,所以我們可以通過(guò)建立分類(lèi)(Category)的方法來(lái)實(shí)現(xiàn)這些共有的功能,我們可以為UIViewController建立出一個(gè)分類(lèi)來(lái),并在這個(gè)分類(lèi)中實(shí)現(xiàn)共有的方法,然后在每個(gè)派生類(lèi)的特定位置中調(diào)用這個(gè)共享的分類(lèi)方法。具體代碼如下:


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

@implementation  UIViewController(XXXExtend)

//一份實(shí)現(xiàn)
 -(void)helperfn
{
    //實(shí)現(xiàn)特定功能。
}

@end


@interface XXXBaseViewController:UIViewController
@end

@implementation  XXXBaseViewController

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

@end


@interface XXXBaseNavigationController: UINavigationController
@end

@implementation  XXXBaseNavigationController

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

@end

//...分別實(shí)現(xiàn)的UITableviewController、UICollectionViewController的派生類(lèi)。


當(dāng)然你也許覺(jué)得上面的方法需要在每個(gè)派生類(lèi)的特定地點(diǎn)都添加一遍代碼而感到麻煩,你也可以采用method swizzle的方法來(lái)解決上述的問(wèn)題,你可以在分類(lèi)的+load方法中實(shí)現(xiàn)代碼替換。下面是具體的代碼實(shí)現(xiàn):

@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

//...分別實(shí)現(xiàn)的UITableviewController、UICollectionViewController的派生類(lèi)。

二、有需要擴(kuò)展屬性的情況。

如果你的基類(lèi)擴(kuò)展方法中有用到屬性的話那么我們知道分類(lèi)中是不能支持編譯時(shí)擴(kuò)展屬性的(但是支持運(yùn)行時(shí)擴(kuò)展屬性的增加)。除了用運(yùn)算時(shí)擴(kuò)展屬性的方法外,還可以將共有的方法和屬性單獨(dú)提煉出來(lái)讓一個(gè)輔助類(lèi)來(lái)實(shí)現(xiàn),然后在派生基類(lèi)的初始化方法中創(chuàng)建這個(gè)輔助類(lèi),并且后續(xù)的一些方法都委托給輔助類(lèi)來(lái)實(shí)現(xiàn)。具體的結(jié)構(gòu)設(shè)計(jì)如下:


//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;  //這里可以訪問(wèn)視圖控制器的方法。
 }


@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)
    {
            //在視圖控制器的初始化里面初始化一個(gè)幫助對(duì)象。
           _helper = [[Helper alloc] initWithViewController:self];
    }

   return self;
}

//重寫(xiě)控制器中的屬性,并把真實(shí)的實(shí)現(xiàn)委托給helper來(lái)完成
-(void)setProp1:(id)prop1
{
     self.helper.prop1 = prop1;
}

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

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

@end

//在你的其他幾個(gè)派生類(lèi)中采用同樣的機(jī)制來(lái)處理。

從上面可以看出來(lái),輔助類(lèi)里面設(shè)計(jì)了一個(gè)弱引用指針來(lái)指向控制器,而控制器則是強(qiáng)引用輔助類(lèi),這樣做的目的是為了防止循環(huán)引用的發(fā)生,而且這種設(shè)計(jì)模式也是一種在實(shí)踐中非常經(jīng)典的方法:有時(shí)候我們需要將類(lèi)A的某些功能委托給類(lèi)B實(shí)現(xiàn),而B(niǎo)又有可能會(huì)在特定的地方訪問(wèn)A的屬性,為了防止相互引用而形成死鎖導(dǎo)致兩個(gè)對(duì)象都無(wú)法被釋放,這時(shí)候就需要使用強(qiáng)弱引用來(lái)解決這個(gè)問(wèn)題。上面借助輔助類(lèi)來(lái)實(shí)現(xiàn)的方法可以解決我們的派生類(lèi)中代碼重復(fù)的問(wèn)題。上面的方法缺點(diǎn)就是我們的派生類(lèi)中需要編寫(xiě)很多重復(fù)的、程式化的代碼。如何來(lái)精簡(jiǎn)呢?其實(shí)我們可以借助接口協(xié)議和OC中的forwarding機(jī)制來(lái)解決這些問(wèn)題:


//Helper.h

@protocol Helper

//請(qǐng)將接口的屬性和方法都設(shè)置為可選
@optional

 @property   id prop1;
 @property   id prop2;

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

@end

//真實(shí)實(shí)現(xiàn)接口的對(duì)象
@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;  //這里可以訪問(wèn)視圖控制器的方法。
 }

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

@end

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

//XXXBaseViewController.h

@interface XXXBaseViewController:UIViewController

@end

//XXXBaseViewController.m

//這里實(shí)現(xiàn)Helper協(xié)議,如果基類(lèi)的擴(kuò)展屬性可以被外面訪問(wèn)則應(yīng)該在.h中的類(lèi)申明里面表明實(shí)現(xiàn)了Helper協(xié)議
@interface XXXBaseViewController()<Helper>
   @property(strong) Helper *helper;
@end

@implementation  XXXBaseViewController

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

    return self;
}

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

}

//這是實(shí)現(xiàn)的關(guān)鍵點(diǎn),重載這個(gè)方法。
-(id)forwardingTargetForSelector:(SEL)aSelector
{
   //判斷這個(gè)方法是否是協(xié)議定義的方法,如果是則將方法的實(shí)現(xiàn)者設(shè)置為helper對(duì)象。
    struct objc_method_description  omd = protocol_getMethodDescription(@protocol(Helper), aSelector, NO, YES);
    if (omd.name != NULL)
    {
        return self.helper;
    }
    
    return [super forwardingTargetForSelector:aSelector];
    
}

@end

//在你的其他幾個(gè)派生類(lèi)中采用同樣的機(jī)制來(lái)處理。

可以看出我們可以借助OC的forwardingTargetForSelector方法來(lái)實(shí)現(xiàn)方法調(diào)用的轉(zhuǎn)發(fā)處理,而不必具體的定義方法的實(shí)現(xiàn)。

3.對(duì)內(nèi)和對(duì)外的接口定義

面向?qū)ο缶幊痰囊粋€(gè)主旨思想就是封裝,所謂封裝就是在進(jìn)行類(lèi)的定義和設(shè)計(jì)時(shí),我們盡可能的暴露出外面可以理解以及需要的接口或者方法,而在內(nèi)部實(shí)現(xiàn)中所用到的中間特性或者私有特性則盡可能的隱藏。盡可能的隱藏復(fù)雜的實(shí)現(xiàn)細(xì)節(jié),而把簡(jiǎn)單易用的接口暴露給外部使用。比如對(duì)于汽車(chē)的封裝,我們對(duì)外暴露的就僅僅是開(kāi)鎖、發(fā)動(dòng)、掛擋、轉(zhuǎn)向等操作,而具體內(nèi)部的組成、發(fā)動(dòng)機(jī)的原理、以及發(fā)動(dòng)機(jī)如何驅(qū)動(dòng)行走等細(xì)節(jié)都不需要開(kāi)車(chē)的人了解。正是面向?qū)ο筮@種封裝的特性就使得我們能更加從應(yīng)用層面去使用某個(gè)對(duì)象的方法而不需要知道其中的細(xì)節(jié)。因此我們?cè)陬?lèi)的設(shè)計(jì)中也要遵循這個(gè)設(shè)計(jì)的思想,把必要的東西暴露給外部,而把實(shí)現(xiàn)細(xì)節(jié)則隱藏在類(lèi)的內(nèi)部來(lái)完成。這一節(jié)所介紹的并不僅僅適用在控制器類(lèi)的設(shè)計(jì)上,所有其他系統(tǒng)也是同樣適用的。
類(lèi)的封裝實(shí)現(xiàn)在不同的語(yǔ)言上所提供的能力是不一樣的,這一點(diǎn)非常有意思。向在C/C++/OC這幾種語(yǔ)言中,類(lèi)的聲明和類(lèi)的實(shí)現(xiàn)需要在不同的文件里面完成(.h是聲明,而.m/.c/.cpp中則是實(shí)現(xiàn))而像Java和Swift等語(yǔ)言則是申明和實(shí)現(xiàn)都放在同一文件中完成。在前面的三種語(yǔ)言中因?yàn)槁暶骱蛯?shí)現(xiàn)分離,所以我們可以把一些對(duì)外暴露的方法和屬性放到頭文件中申明,而內(nèi)部的私有屬性則放到實(shí)現(xiàn)文件中申明和定義。而使用者則只需要引入共有頭文件即可。而后面兩種語(yǔ)言中因?yàn)闆](méi)有分開(kāi),所以在這些語(yǔ)言更傾向于通過(guò)接口定義和實(shí)現(xiàn)來(lái)完成這種共有屬性和私有屬性分類(lèi)的機(jī)制(您可以看出在Java中大量的使用了接口來(lái)完成整個(gè)體系架構(gòu),以及Swift中也是推崇接口編程這種理念)。

面向?qū)ο笤O(shè)計(jì)中,類(lèi)和類(lèi)之間不可能獨(dú)立存在,他們之間總是要建立一種關(guān)聯(lián),這種關(guān)聯(lián)有可能是單向的也有可能是雙向的。我們都推崇類(lèi)和類(lèi)之間的單向依賴來(lái)降低類(lèi)與類(lèi)之間的耦合性。這種單向依賴至少在明面上是如此的,也就是類(lèi)所公開(kāi)的方法和屬性是可以看出來(lái)的。但是在實(shí)際的內(nèi)部實(shí)現(xiàn)中這種單向依賴可以就會(huì)被打破。舉一個(gè)很常見(jiàn)的例子我們都知道視圖控制器UIViewController中有一個(gè)view屬性來(lái)保存控制器所管理的視圖,但是我們?cè)谝晥DUIView中卻看不見(jiàn)任何關(guān)于控制器的屬性。這樣的表象就是表明視圖控制器依賴視圖,而視圖則不依賴視圖控制器,這也是非常符合MVC中三層設(shè)計(jì)思路的。但實(shí)際中是如此嗎? 結(jié)果并不是這樣的,因?yàn)樵谙到y(tǒng)的內(nèi)部如果某個(gè)視圖是控制器的根視圖的話他可能會(huì)具有一些不同的特性以及不同的處理邏輯,因此其實(shí)在UIView的內(nèi)部私有屬性中是有一個(gè)視圖所歸屬的視圖控制器的屬性的,這個(gè)屬性就是:viewDelegate。 并且在UIView上他是定義為了id類(lèi)型的。

上面的兩段描述中我們都提到了對(duì)公和對(duì)私的方法和屬性的申明的問(wèn)題,可以看出在設(shè)計(jì)上要按照這個(gè)思路去設(shè)計(jì)我們的類(lèi),我們只需要將共有的方法和屬性暴露出來(lái),而將私有的方法和屬性則隱藏起來(lái)。那么怎么來(lái)實(shí)現(xiàn)這種共有和私有方法的定義實(shí)現(xiàn)呢?我們來(lái)看下面三個(gè)具體的場(chǎng)景:

  • 類(lèi)的某些屬性公開(kāi)某些屬性不公開(kāi)
/*一個(gè)類(lèi)里面某些屬性公開(kāi)某些屬性不公開(kāi)的實(shí)現(xiàn)可以很簡(jiǎn)單的通過(guò)類(lèi)的申明和類(lèi)的擴(kuò)展來(lái)實(shí)現(xiàn)*/

//XXXX.h

@interface XXXX
 
//對(duì)外公開(kāi)的屬性
 @property  id pubp1;
 @property  id pubp2;

//對(duì)外公開(kāi)的方法
-(void)pubfn1;
-(void)pubfn2;
 
@end

//XXXX.m

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

//對(duì)內(nèi)私有的屬性
 @property  id prip1;
 @property  id prip2;

//對(duì)內(nèi)私有的方法
-(void)prifn1;
-(void)prifn2;

@end

@implementation  XXXX
@end

  • 類(lèi)的某些屬性對(duì)外暴露的是只讀的,但是內(nèi)部實(shí)現(xiàn)確實(shí)可以被改變的。這樣做的目的是為了訪問(wèn)和使用的安全。
//XXXX.h

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

//XXXX.m

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

//在類(lèi)的內(nèi)部這些屬性是可讀寫(xiě)的
 @property  id pubp1;
 @property  id pubp1;

@end

@implementation  XXXX
@end

  1. 類(lèi)A和類(lèi)B之間有關(guān)聯(lián),A中持有B的實(shí)例并公開(kāi),而B(niǎo)則有可能在實(shí)現(xiàn)中需要用到A的內(nèi)部的方法或者屬性,同時(shí)B是不向外暴露對(duì)A的持有的情況。因?yàn)槲覀兌际峭ㄟ^(guò)頭文件引用,所以在頭文件中看不到這種互相依賴關(guān)系,但是內(nèi)部的實(shí)現(xiàn)文件則可以很清楚的看到其中的依賴了。
//A.h

@interface A

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

-(void) pubfn1;

@end

//A.m

//在A的內(nèi)部用到了B的prip1, prifn1所以要這里申明一下。
@interface B()

@property  id prip1;
-(void)prifn1;

@end

//內(nèi)部實(shí)現(xiàn)
@interface A()

  @property id prip1;
  -(void)prifn1;

@end

@implementation  A

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

    id temp = b.prip1;  //內(nèi)部屬性
}

@end

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

//B.h

//B的外部并不暴露對(duì)A的持有
@interface B

-(void)pubfn1;

@end

//B.m

@interface A()

//因?yàn)锽內(nèi)部要用到prifn1所以這里再申明一下。
-(void)prifn1;

@end


@interface B()

//B的內(nèi)部持有A。這里因?yàn)橛邢嗷コ钟兴幸幸粋€(gè)是強(qiáng)持有一個(gè)是弱持有。
@property(weak) A *a;   

//內(nèi)部實(shí)現(xiàn)的其他
@property id prip1;
-(void)prifn1;

@end

@implementation  B

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

@end



4.各種屬性的定義以及分類(lèi)擴(kuò)展

控制器用來(lái)實(shí)現(xiàn)對(duì)視圖對(duì)象和業(yè)務(wù)模型對(duì)象的建立以及管理和控制,在實(shí)現(xiàn)上控制器會(huì)擁有眾多視圖層對(duì)象的屬性,以及模型層對(duì)象的屬性,同時(shí)還會(huì)擁有自身的一些屬性。同時(shí)控制器還要在適當(dāng)?shù)臅r(shí)候?qū)τ脩舻妮斎脒M(jìn)行處理,以及在適當(dāng)?shù)臅r(shí)候調(diào)用業(yè)務(wù)模型所提供的服務(wù),還要在適當(dāng)?shù)臅r(shí)候?qū)I(yè)務(wù)模型提供服務(wù)的結(jié)果通知給視圖進(jìn)行呈現(xiàn)和更新。因此如何去組織一個(gè)控制器的代碼布局(此代碼布局非視圖的界面布局而是源代碼的布局)就非常的重要了。如何合理的定義以及放置屬性,如何合理的對(duì)控制器中的方法進(jìn)行分類(lèi),以及在何時(shí)創(chuàng)建視圖、在何時(shí)創(chuàng)建業(yè)務(wù)對(duì)象,在何時(shí)添加和銷(xiāo)毀觀察者,在類(lèi)的析構(gòu)中作如何處理等等這些其實(shí)都是有一定的規(guī)則和規(guī)范的。這一節(jié)更像是一份代碼規(guī)約方面的介紹。我將會(huì)從下面幾個(gè)點(diǎn)來(lái)分別闡述。

(一). 屬性的定義順序和規(guī)則
一個(gè)類(lèi)的設(shè)計(jì)首要構(gòu)造的就是屬性和成員變量,控制器也無(wú)外乎。前面說(shuō)到控制器管理著視圖對(duì)象和模型對(duì)象,因此我們一般要將視圖對(duì)象和業(yè)務(wù)對(duì)象作為屬性定義在控制器中。這里整理出一下幾點(diǎn):

  • 如果控制器中的屬性和成員變量只在類(lèi)內(nèi)部使用和訪問(wèn),那么我們應(yīng)該要將屬性定義在控制器的實(shí)現(xiàn)文件中的擴(kuò)展里面,而不要定義在控制器的頭文件中,除非這個(gè)屬性會(huì)被外部訪問(wèn)或者設(shè)置。比如如下代碼:
//XXX.m

@interface XXX()

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

@implement XXX
@end   


  • 如果控制器中需要訪問(wèn)某個(gè)子控件視圖那么在定義子控件視圖時(shí),屬性最好是weak而非strong。這樣做的目的一來(lái)iOS對(duì)于SB或者XIB上的子控件的屬性定義都是默認(rèn)為weak的、二來(lái)最主要的原因是有可能控制器中的根視圖有可能會(huì)在運(yùn)行時(shí)被重新構(gòu)造(比如說(shuō)我們要實(shí)現(xiàn)一個(gè)換膚功能,我們就有可能會(huì)重新構(gòu)造視圖控制器中的根視圖來(lái)實(shí)現(xiàn))這樣當(dāng)控制器中的根視圖被銷(xiāo)毀時(shí),根視圖里面的子視圖也應(yīng)該被銷(xiāo)毀,而如果你用strong來(lái)定義子視圖時(shí)就有可能導(dǎo)致子視圖的生命周期要長(zhǎng)于根視圖。另外有可能我們的子控件會(huì)采用懶加載的模式來(lái)實(shí)現(xiàn)根視圖中子視圖的建立,因此如果你用strong的話就有可能導(dǎo)致子視圖不會(huì)被重新構(gòu)建。

  • 對(duì)于NSString類(lèi)型的屬性來(lái)說(shuō)我們最好將他聲明為copy。原因是如果聲明為strong或者assign的話,那么對(duì)于NSMutableString類(lèi)型的字符串進(jìn)行賦值時(shí)就有可能會(huì)在后續(xù)的代碼中內(nèi)容被改變,從而引起異常的問(wèn)題。比如:

   NSMutableString *str1 = [@"hello" mutableCopy];
    self.userName =   str1;   //如果userName被申明為strong的話則后續(xù)對(duì)str1的內(nèi)容的更改同時(shí)也會(huì)導(dǎo)致userName的內(nèi)容的改變!!
  • 如果你的屬性不會(huì)涉及到任何多線程訪問(wèn)的場(chǎng)景那么最好不要在屬性定義上帶上atomic 修飾符。原因是如果帶上atomic修飾符的話所有屬性的賦值和讀取操作都會(huì)通過(guò)操作系統(tǒng)原子API來(lái)進(jìn)行賦值和讀取。

  • 不要將狀態(tài)以及持久數(shù)據(jù)保存到視圖對(duì)象中。

  • 如果可能最好將控制器中的視圖對(duì)象屬性和模型對(duì)象屬性分開(kāi)定義,并且把視圖對(duì)象屬性放在最上面, 控制器本地的屬性放在中間,而模型對(duì)象屬性放在最下面。

下面是一個(gè)典型的控制器屬性定義的代碼示例,僅供大家參考:

    //XXXViewController.m

@interface XXXViewController()

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

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

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

@end

@implement XXXViewController
@end   

(二). 類(lèi)中各種方法的分類(lèi)
當(dāng)你的屬性定義完畢后,我們就要實(shí)現(xiàn)方法了。在一個(gè)類(lèi)的方法中我們有構(gòu)造和析構(gòu)的方法、有需要重載的方法、有事件處理的方法、有個(gè)委托Delegate或者觀察者的方法、還有一些對(duì)外公開(kāi)的方法、以及一些私有輔助的方法。為了便于管理,我們最好能將這些方法進(jìn)行分類(lèi)擺放,這樣也有利于查找定位,對(duì)于一個(gè)控制器來(lái)說(shuō)一般就是上面所說(shuō)的幾種方法,一般情況下我們對(duì)相同性質(zhì)的方法放在一塊實(shí)現(xiàn),并用一些特定的關(guān)鍵字或者用分類(lèi)的機(jī)制來(lái)對(duì)控制器中的所有方法進(jìn)行歸類(lèi)。下面我用兩種不同的方式來(lái)對(duì)方法進(jìn)行歸類(lèi)處理:

  • 通過(guò)語(yǔ)法關(guān)鍵字。
    在OC中我們可以通過(guò) #progma mark -- 名稱(chēng) 來(lái)便于定位和查找。在實(shí)踐中控制器類(lèi)一般都要實(shí)現(xiàn):重寫(xiě)基類(lèi)的方法、公有方法、事件處理方法、Delegate中的方法、私有方法這幾種類(lèi)型,因此我們可以專(zhuān)門(mén)為這些方法定義不同的標(biāo)簽。具體如下:
//  XXXViewController.m

@implement XXXViewController

//最開(kāi)始放構(gòu)造和析構(gòu)的方法。這兩個(gè)方法放在一起就可以清楚的看出我初始化了那些東西,析構(gòu)了那些東西。
-(instanceType)initXXXXX{}
-(void)dealloc{}

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

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

//接下來(lái)放事件處理的方法,這些事件處理包括控件的觸摸事件,NSNotificationCenter、定時(shí)器事件、其他的注冊(cè)的事件。我個(gè)人比較喜歡以handle開(kāi)頭。

#pragma mark -- Handle Methods

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


//接下來(lái)是放各種Delegate的事件,我們名稱(chēng)都以Delegate協(xié)議的名稱(chēng)做標(biāo)志。

#pragma mark -- UIDataSource

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

#pragma mark -- UITableViewDelegate


//接下來(lái)是存放各種私有方法。

#pragma mark -- Private Methods

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









@end

  • 通過(guò)分類(lèi)
    如果你不想使用#pragma 帶標(biāo)簽的形式,那也可以用分類(lèi)的方式來(lái)實(shí)現(xiàn)。比如下面的例子:

//  XXXViewController.m

@implement XXXViewController

//默認(rèn)的部分實(shí)現(xiàn),構(gòu)造和析構(gòu)方法以及所有從基類(lèi)重載的方法
-(instanceType)initXXXXX{}
-(void)dealloc{}

//接下來(lái)放重載的方法。對(duì)屬性set,get重寫(xiě)的方法
-(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

請(qǐng)閱讀:MVC框架之控制層的構(gòu)建(下)


歡迎大家訪問(wèn)我的github地址簡(jiǎn)書(shū)地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容