如何為iOS項(xiàng)目開(kāi)發(fā)Framework

原文鏈接:http://ios.jobbole.com/81583/

上一篇教程里栏妖,你學(xué)到了如何創(chuàng)建一個(gè)可復(fù)用的把手控件拾氓。然而,還不是很容易讓其他開(kāi)發(fā)者方便的復(fù)用這個(gè)控件底哥。

一種共享它的方式就是直接提供源碼文件咙鞍。然而,這不是特別優(yōu)雅趾徽。有可能你不想共享代碼的實(shí)現(xiàn)細(xì)節(jié)续滋。此外,開(kāi)發(fā)者可能不想看見(jiàn)所有的東西孵奶,他們只是想繼承一部分代碼到自己的代碼庫(kù)里疲酌。

另一種方式是把你的代碼編譯成靜態(tài)庫(kù)來(lái)讓開(kāi)發(fā)者添加到他們的項(xiàng)目中去。然而這要求你來(lái)提供公共頭文件了袁,這樣顯得非常的笨拙朗恳。

你需要有一種簡(jiǎn)潔的方式來(lái)編譯你的代碼,并且它還要方便的共享和在多個(gè)項(xiàng)目間復(fù)用载绿。你需要的是只是將靜態(tài)庫(kù)和它的頭文件打包在一個(gè)文件里粥诫,然后只需要把這個(gè)文件添加到工程里就能立即開(kāi)始使用。

好消息是這篇教程就是圍繞這展開(kāi)的崭庸。通過(guò)制作 framework怀浆,能夠幫你解決這些迫在眉睫的問(wèn)題。OS X 對(duì)制作 framework 有著良好的支持怕享, Xcode 提供了一個(gè)工程模板执赡,它包含有一個(gè)默認(rèn)的構(gòu)建目標(biāo)還可以容納資源文件,例如圖片函筋,音頻和字體沙合。你能夠?yàn)?iOS 創(chuàng)建一個(gè) framework,但這有點(diǎn)棘手跌帐,如果你跟著我一步一步來(lái)首懈,你將會(huì)學(xué)到如何解決這些阻礙芳来。

完成這篇教程,你將:

用 Xcode 構(gòu)建一個(gè)基本靜態(tài)庫(kù)工程

構(gòu)建一個(gè)依賴(lài)與這個(gè)靜態(tài)庫(kù)的 app

探索如何把靜態(tài)庫(kù)轉(zhuǎn)換成一個(gè)完整的 framework

最后猜拾,你將會(huì)看到如何把一個(gè)圖片文件打包到 framework 中的資源包中

現(xiàn)在開(kāi)始

這篇教程的主要目的是解釋如何在 iOS 項(xiàng)目中創(chuàng)建一個(gè)可復(fù)用的 framework即舌,因此不會(huì)像本站中其他教程一樣,只會(huì)有少量的 Objective-C 代碼挎袜,用來(lái)闡述所涉及的概念顽聂。

這里可以先下載好?RWKnobControl?資源文件。當(dāng)你在?創(chuàng)建靜態(tài)庫(kù)項(xiàng)目?這個(gè)部分里創(chuàng)建第一個(gè)工程的過(guò)程中盯仪,你會(huì)看到如何使用它們紊搪。

你要?jiǎng)?chuàng)建的所有的這些代碼和工程文件在?Github?上都能訪問(wèn)到,并且還有每個(gè)構(gòu)建部分的獨(dú)立提交全景。

什么是 Framework耀石?

Framework 是一種資源集合,它把一個(gè)靜態(tài)庫(kù)和它的頭文件匯集成一個(gè)單一結(jié)構(gòu)爸黄,這樣 Xcode 能夠很容易的將其集成到你的工程中去滞伟。

在 OS X 中允許我們創(chuàng)建動(dòng)態(tài)鏈接庫(kù)。通過(guò)動(dòng)態(tài)鏈接炕贵,framework 能夠顯式的實(shí)時(shí)更新而無(wú)需應(yīng)用程序重新鏈接它們梆奈。在運(yùn)行時(shí),一份靜態(tài)庫(kù)的副本被所有進(jìn)程共享称开,這能夠顯著減少內(nèi)存使用提升系統(tǒng)性能亩钟。如你所見(jiàn),這是個(gè)強(qiáng)大的東西鳖轰。

在 iOS 中你不能用這種方式給系統(tǒng)添加自定義的 framework清酥,因此只能使用 Apple 提供的動(dòng)態(tài)庫(kù)。

然而蕴侣,這不意味著 framework 跟 iOS 就毫無(wú)相關(guān)了焰轻。對(duì)于在不同 app 中進(jìn)行代碼復(fù)用,靜態(tài)鏈接庫(kù)仍然是一個(gè)便捷的打包方式睛蛛。

既然 framework 本質(zhì)上是對(duì)靜態(tài)庫(kù)的一站式購(gòu)物鹦马,那這篇教程中首要事情你了解如何創(chuàng)建和使用靜態(tài)庫(kù)胧谈。當(dāng)這篇教程進(jìn)展到構(gòu)建 framework 時(shí)忆肾,你會(huì)知道接下來(lái)會(huì)發(fā)生什么。

創(chuàng)建靜態(tài)庫(kù)工程

打開(kāi) Xcode 并且通過(guò)點(diǎn)擊?FileNewProject?和?iOSFramework and LibraryCocoa Touch Static Library?來(lái)創(chuàng)建一個(gè)新的靜態(tài)庫(kù)工程菱肖。


把工程命名為?RWUIControls?并且保存工程到一個(gè)空目錄客冈。
一個(gè)靜態(tài)庫(kù)工程由頭文件和實(shí)現(xiàn)文件組成,它們由工程自己創(chuàng)建編譯稳强。

為了讓開(kāi)發(fā)者更方便的使用你的庫(kù)和框架场仲,你需要導(dǎo)入一個(gè)頭文件來(lái)訪問(wèn)所有的你希望公開(kāi)的類(lèi)和悦,好讓他們只需訪問(wèn)這個(gè)頭文件就行。

當(dāng)創(chuàng)建靜態(tài)庫(kù)工程的時(shí)候渠缕,Xcode 添加了?RWUIControls.h?和?RWUIControls.m鸽素。你不需要實(shí)現(xiàn)文件,因此右鍵?RWUIControls.m?選擇刪除亦鳞,按提示把它移到垃圾箱中馍忽。

打開(kāi)?RWUIControls.h?并且用下面的代碼替換文件內(nèi)容:

#import <UIKit/UIKit.h>

這句代碼導(dǎo)入了?UIKit?的傘型頭文件,它包含有自身所需要的庫(kù)燕差。當(dāng)你創(chuàng)建不同的組件類(lèi)時(shí)遭笋,你要把它們添加到這個(gè)文件里,這樣能夠確保它們讓這個(gè)庫(kù)的使用者能訪問(wèn)徒探。

你構(gòu)建這個(gè)工程時(shí)會(huì)依賴(lài)?UIKit瓦呼,但 Xcode 靜態(tài)庫(kù)工程沒(méi)有默認(rèn)的鏈接到?UIkit。為了修正這個(gè)問(wèn)題测暗,要添加?UIKit?作為一個(gè)依賴(lài)央串。選擇工程的導(dǎo)航器,并且在主面板選擇?RWUIControls?目標(biāo)碗啄。

單擊?Build Phases?然后展開(kāi)?Link Binary With Libraries?部分蹋辅。單擊?+?來(lái)添加一個(gè)新的框架,查找?UIKit.framework挫掏,單擊?add?添加侦另。



如果不綁定到頭文件的話,靜態(tài)包是沒(méi)有用的尉共。這些編譯好的類(lèi)和方法是包含在二進(jìn)制文件中褒傅。你創(chuàng)建的類(lèi),有一些你可以在外部使用袄友,另一個(gè)則只能在包內(nèi)使用殿托。

接下來(lái),你需要在構(gòu)件時(shí)添加引用剧蚣,把公開(kāi)的頭文件放到編譯者能使用的地方支竹。最后,你要復(fù)制這些東西到框架里鸠按。

當(dāng)你在Xcode里看到Build Phases 時(shí)礼搁,選擇 EditorAdd Build PhaseAdd Copy Headers Build Phase.

注意:如果你發(fā)現(xiàn)選項(xiàng)變灰了,試試點(diǎn)擊下方的空白區(qū)域看看目尖,然后再?lài)L試一遍馒吴。


把?RWUIControls.h?從導(dǎo)航器拖到面板的?Public?部分。這確保這個(gè)頭文件對(duì)任何使用你庫(kù)的用戶(hù)都可用。


注意:這可能有點(diǎn)多此一舉饮戳,但把包含有你工程所有公開(kāi)類(lèi)頭文件的頭文件放到公有部分非常重要豪治。否則,開(kāi)發(fā)者在企圖使用這個(gè)庫(kù)的時(shí)候會(huì)發(fā)生編譯錯(cuò)誤扯罐。這對(duì)任何人都不是開(kāi)玩笑的负拟,當(dāng) Xcode 讀取公有頭的時(shí)又不能讀取你忘記添加的公有文件。

創(chuàng)建一個(gè) UI 控件

現(xiàn)在你已經(jīng)設(shè)置好了你的工程歹河,是時(shí)候給庫(kù)添加些功能了齿椅。既然這個(gè)教程的目的是講訴如何構(gòu)建一個(gè) framework,而不是如何構(gòu)建一個(gè) UI 控件启泣,那你會(huì)借用些上篇教程的一些代碼涣脚。在你之前下載的 zip 文件你會(huì)找到 RWKnobControl 目錄。把它拖到 Xcode 的?RWUIControls?組別寥茫。

選擇?Copy items into destination group’s folder?并確保要拷貝的新文件勾選了響應(yīng)的單選框遣蚀。


這會(huì)同時(shí)把實(shí)現(xiàn)文件添加到編譯列表,默認(rèn)的頭文件在?Project group纱耻。這意味著它們都是私有的芭梯。


注意:這三個(gè)部分的命名如果不拆分開(kāi)理解會(huì)有點(diǎn)令人誤解。Public?如你所預(yù)料的弄喘。Private?頭仍然會(huì)暴露你的頭文件玖喘,這有點(diǎn)讓人困惑。Project?頭是你工程用到的特定私有文件蘑志,這有點(diǎn)諷刺累奈。因此,你會(huì)慢慢發(fā)現(xiàn)要么頭文件是放到?Public?要么放在?Project?部分急但。

現(xiàn)在你需要分享主控件頭RWKnobControl.h 澎媒,有如下幾步要做。首先從Project 組中拖拽Copy Headers 到Public 組波桩。


另一種方式戒努,當(dāng)你編輯文件的時(shí)候會(huì)發(fā)現(xiàn)更改?Target Membership?面板中的值會(huì)更方便。當(dāng)你開(kāi)發(fā)庫(kù)繼續(xù)添加文件的時(shí)候這會(huì)非常方便镐躲。

注意:在你往庫(kù)中添加新的類(lèi)時(shí)储玫,記得保持成員是最新的。盡可能減少公有的頭文件萤皂,并確保其余的在?Project?組撒穷。

用控件的頭文件做的另一件事就是把?RWUIControls.h?它添加到庫(kù)的主頭文件中。這樣開(kāi)發(fā)者使用你的庫(kù)時(shí)只需要像下面這樣包含這一個(gè)文件就行敌蚜,而不是一堆桥滨。

#import <RWUIControls/RWUIControls.h>

因此,把下面的代碼添加到?RWUIControls.h

// Knob Control? ??

#import? <RWUIControls/RWKnobControl.h>

配置 Build 設(shè)置

現(xiàn)在你非常接近這個(gè)工程的編譯部分了弛车。然而齐媒,有幾個(gè)確保庫(kù)盡可能對(duì)用戶(hù)友好的設(shè)置需要配置蛤奢。

首先害碾,你需要提供一個(gè)目錄名給你公有頭文件將要拷貝到那里去。這確保當(dāng)你使用靜態(tài)庫(kù)的時(shí)候能定位到相關(guān)的頭文件领追。

單擊工程導(dǎo)航欄的工程贫奠,然后選擇?RWUIControls?靜態(tài)庫(kù)目標(biāo)唬血。選擇?Build Setting?標(biāo)簽,然后搜索?public header唤崭。雙擊?Public Header Folder Path?設(shè)置并輸入下面的路徑:

之后你會(huì)看到這個(gè)目錄拷恨。

現(xiàn)在你需要改變一些其他的設(shè)置,尤其是那些保留在二進(jìn)制庫(kù)中的谢肾。編譯器給了你移除無(wú)用代碼的選項(xiàng)腕侄,指那些從不會(huì)訪問(wèn)到的代碼。并且你還能移除 debug 符號(hào)芦疏,例如函數(shù)名和其他 debug 時(shí)相關(guān)的細(xì)節(jié)冕杠。

既然你創(chuàng)建 framework 給其他人使用,那最好把它們都禁用了然后讓用戶(hù)自行選擇最適合他們工程的配置酸茴。要做這些的話分预,跟之前一樣使用搜索就行,更新下面的設(shè)置:

Dead Code Stripping?– 設(shè)為 NO

Strip Debug Symbols During Copy?– 設(shè)為 NO for all configurations

Strip Style – 設(shè)為 Non-Global Symbols

構(gòu)建運(yùn)行薪捍。你仍然什么東西看沒(méi)看到笼痹,但這仍然是件好事,這足以說(shuō)明工程成功的構(gòu)建的并且沒(méi)有警告和錯(cuò)誤酪穿。

要構(gòu)建的話与倡,選擇構(gòu)建目標(biāo)為?iOS Device?并按下?cmd+B?來(lái)執(zhí)行構(gòu)建。一旦完成昆稿,項(xiàng)目導(dǎo)航器的 Products 組別里的?libRWUIControls.a?會(huì)從紅色變?yōu)楹谏淖@表示文件已生成。右鍵?libRWUIControls.a?并且選擇?Show in Finder溉潭。


在這個(gè)目錄中你能看到生成的靜態(tài)庫(kù)净响,libRWUIControls.a,并且公有頭文件單獨(dú)放在?include/RWUIControls喳瓣。

創(chuàng)建一個(gè)依賴(lài)開(kāi)發(fā)項(xiàng)目

當(dāng)你不能親眼看到你在做什么的時(shí)候馋贤,為 iOS 開(kāi)發(fā)一個(gè) UI 控件庫(kù)極其的困難,現(xiàn)在似乎就是這樣畏陕。

沒(méi)人要你盲目的工作配乓,因此在這個(gè)部分你將會(huì)創(chuàng)建一個(gè)新的 Xcode 工程,它會(huì)用到你剛創(chuàng)建的庫(kù)。這能讓你通過(guò)一個(gè)示例 app 來(lái)開(kāi)發(fā) framework犹芹。自然地崎页,這個(gè) app 的代碼會(huì)完全的與庫(kù)本身的代碼分離開(kāi)來(lái),這樣一來(lái)會(huì)讓結(jié)構(gòu)更清晰腰埂。

關(guān)閉靜態(tài)庫(kù)工程飒焦。然后創(chuàng)建一個(gè)新的工程。選擇?iOS/Application/Single View Application屿笼,并取名為?UIControlDevApp牺荠。設(shè)置類(lèi)前綴為?RW?并指定僅 iPhone 可用。最后保存到?RWUIControls?相同的目錄驴一。

把?RWUIControls.xcodeproj?拖到?UIControlDevApp?組別來(lái)把?RWUIControls?作為一個(gè)依賴(lài)項(xiàng)休雌。


注意:你不能在兩個(gè)不同的窗口中打開(kāi)同一個(gè)工程。如果你發(fā)現(xiàn)你不能切換到庫(kù)工程肝断,請(qǐng)檢查你沒(méi)有在另一個(gè) Xcode 窗口中打開(kāi)它杈曲。

你可以簡(jiǎn)單的拷貝代碼而不是重新創(chuàng)建上一篇教程的 app。首先選擇?Main.storyboard孝情,RWViewController.h?和?RWViewController.m?然后刪除它們鱼蝉。接著拷貝?DevApp?文件夾到?UIControlDevApp?組別。



現(xiàn)在添加靜態(tài)庫(kù)作為示例 app 的依賴(lài)構(gòu)建:

* 在工程中選擇?UIControlDevApp?工程箫荡。

* 導(dǎo)航至?UIControlDevApp?目標(biāo)的?Build Phases?標(biāo)簽魁亦。

* 打開(kāi)?Target Dependencies?面板并單擊 + 來(lái)顯示選擇器。

* 找到?RWUIControls?靜態(tài)庫(kù)羔挡,單擊?Add?來(lái)添加洁奈。這個(gè)動(dòng)作表示當(dāng)構(gòu)建示例 app 的時(shí)候,Xcode 會(huì)檢查是否靜態(tài)庫(kù)需要重新構(gòu)建绞灼。

為了鏈接靜態(tài)庫(kù)利术,展開(kāi)?Link Binary With Libraries?面板并再次點(diǎn)擊 +。選擇?libRWUIControls.a?單擊添加低矮。

這個(gè)行為會(huì)讓 Xcode 把示例 app 與靜態(tài)庫(kù)鏈接起來(lái)印叁,就像鏈接系統(tǒng) framework 一樣比如?UIKit。


構(gòu)建運(yùn)行军掂。你會(huì)看到跟上一 篇教程中熟悉的畫(huà)面轮蜕。


嵌套工程的好處就是你能夠在不離開(kāi)示例 app 工程的情況下繼續(xù)開(kāi)發(fā)靜態(tài)庫(kù),正如你在不同的部位維護(hù)代碼一樣蝗锥。你每次構(gòu)建項(xiàng)目的時(shí)候跃洛,你也要同時(shí)檢查 public/project 頭成員是否正確設(shè)置。如果丟失了任何必須的頭文件那么示例 app 將不會(huì)成功構(gòu)建终议。

創(chuàng)建 Framework

現(xiàn)在, 你可能會(huì)不耐煩地敲打你的腳趾并且想要知道 framework 到底什么時(shí)候才會(huì)開(kāi)始汇竭。這可以理解葱蝗,因?yàn)榈侥壳盀橹鼓阕隽艘淮蠖褨|西但還沒(méi)有看到 framework。

好的细燎,某些東西要開(kāi)始變化了两曼,馬上就來(lái)了。到現(xiàn)在你還沒(méi)有創(chuàng)建一個(gè) framework 的原因是因?yàn)樗褪且粋€(gè)靜態(tài)庫(kù)和頭文件的集合 – 正是你之前所做的找颓。

制作一個(gè) framework 會(huì)有幾點(diǎn)特別的地方:

目錄結(jié)構(gòu)合愈。Frameworks 有著 Xcode 認(rèn)可的特殊目錄結(jié)構(gòu)叮贩。你會(huì)創(chuàng)建一個(gè)構(gòu)建任務(wù)击狮,這將為你創(chuàng)建這種結(jié)構(gòu)。

當(dāng)你構(gòu)建庫(kù)的時(shí)候益老,它只會(huì)生成當(dāng)前必須的架構(gòu)彪蓬,例如 i386,arm7捺萌,等等档冬。為了讓一個(gè)框架有效,在構(gòu)建的時(shí)候它需要包含所有需要運(yùn)行的架構(gòu)桃纯。你將會(huì)創(chuàng)建一個(gè)新的產(chǎn)品酷誓,它將構(gòu)建必須的架構(gòu)并把它們放到框架中。

在這個(gè)部分會(huì)有大量的神奇腳本态坦,但我會(huì)講慢點(diǎn)盐数,它們不會(huì)很復(fù)雜。

框架結(jié)構(gòu)

正如之前提到的伞梯,一個(gè)框架有著特殊的目錄結(jié)構(gòu)玫氢,看起來(lái)像是這樣


現(xiàn)在在靜態(tài)庫(kù)編譯過(guò)程中要給它添加一個(gè)腳本。選擇?RWUIControls?工程谜诫,并選擇?RWUIControls靜態(tài)庫(kù)目標(biāo)漾峡。選擇?Build Phases?標(biāo)簽并通過(guò)選擇?Editor/Add Build Phase/Add Run Script Build Phase?來(lái)添加一個(gè)新的腳本。


在 Build Phases 部分創(chuàng)建了一個(gè)新的面板喻旷,這能讓你在編譯階段的某個(gè)時(shí)刻運(yùn)行一個(gè)任意的 Bash 腳本生逸。如果你想在編譯過(guò)程中改變腳本的運(yùn)行時(shí)刻就在列表中拖動(dòng)面板。對(duì)于框架工程來(lái)說(shuō)且预,在最后運(yùn)行腳本就行槽袄,因此你可以默認(rèn)放置即可。


雙擊重命名面板標(biāo)題為?Build Framework辣之。


把下面的 Bash 腳本粘貼到腳本框中:

set -e


export FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"


# Create the path to the real Headers die

mkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers"


# Create the required symlinks

/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"

/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"

/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \

???????????? "${FRAMEWORK_LOCN}/${PRODUCT_NAME}"


# Copy the public headers into the framework

/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \

?????????? "${FRAMEWORK_LOCN}/Versions/A/Headers"


這段腳本首先創(chuàng)建了?RWUIControls.framework/Versions/A/Headers?目錄掰伸,然后創(chuàng)建了一個(gè)框架所必須的三個(gè)語(yǔ)法鏈接

Versions/Current => A

Headers => Versions/Current/Headers

RWUIControls => Versions/Current/RWUIControls

最后,公有頭文件從你之前指定的公有頭文件路徑拷貝到?Versions/A/Headers?目錄怀估。-a參數(shù)確保了在拷貝的時(shí)候編輯時(shí)間不會(huì)改變狮鸭,從而防止不必要的重新構(gòu)建合搅。

現(xiàn)在,選擇?RWUIControls?靜態(tài)庫(kù)方案和?iOS Device?構(gòu)建目標(biāo)歧蕉,然后通過(guò)?cmd+B?構(gòu)建灾部。


右鍵?libRWUIControls.a?并在?Finder?中顯示。



在構(gòu)建目錄中你可以訪問(wèn)到?RWUIControls.framework惯退,并確認(rèn)目錄的結(jié)構(gòu)顯示的是正確的:


在完成你框架的道路上這真是一個(gè)質(zhì)的飛躍赌髓,但你會(huì)發(fā)現(xiàn)仍然沒(méi)有一個(gè)靜態(tài)庫(kù)。這就是接下來(lái)要做的催跪。

多架構(gòu)構(gòu)建

iOS app 需要在不同的架構(gòu)上運(yùn)行:

arm7: 用于 iOS 7 所支持的最老的設(shè)備

arm7s: 用于 iPhone 5 和 5C

arm64: 用于 iPhone 5S 和 iPhone 6 等 64-bit ARM 處理器

i386: 用于 32-bit 模擬器

x86_64: 用于 64-bit 模擬器

每種架構(gòu)都需要不同的二進(jìn)制文件锁蠕,并且當(dāng)你構(gòu)建一個(gè) app 的時(shí)候,無(wú)論你當(dāng)前是何種設(shè)備 Xcode 都會(huì)正確的構(gòu)建相應(yīng)的架構(gòu)懊蒸。

這意味著構(gòu)建會(huì)很快荣倾。當(dāng)你歸檔 app 或構(gòu)建 release 模式的 app 時(shí),Xcode 會(huì)構(gòu)建所有的三種 ARM 架構(gòu)骑丸,從而讓 app 運(yùn)行到大部分設(shè)備上舌仍。那其他的版本呢?

自然地通危,當(dāng)你構(gòu)建框架時(shí)铸豁,你想要開(kāi)發(fā)者能夠盡可能使用所有的架構(gòu),對(duì)嗎菊碟?如果是這樣那表示你會(huì)得到同行的尊敬與敬佩节芥。

因此你需要讓 Xcode 構(gòu)建所有的五種架構(gòu)。這個(gè)過(guò)程會(huì)創(chuàng)建一個(gè)所謂的臃腫的庫(kù)框沟,它包含有每個(gè)架構(gòu)部分藏古。啊哈!

注意:其實(shí)這里強(qiáng)調(diào)的另一個(gè)原因是要?jiǎng)?chuàng)建一個(gè)依賴(lài)靜態(tài)庫(kù)的示例 app:這個(gè)庫(kù)只為示例 app 需要的架構(gòu)構(gòu)建忍燥,并只會(huì)在某些東西改變的時(shí)候才重新編譯拧晕。為什么這會(huì)令你異常興奮?因?yàn)檫@會(huì)讓開(kāi)發(fā)周期盡可能的縮短梅垄。

單擊 RWUIControls 工程厂捞,創(chuàng)建一個(gè)新的目標(biāo)(target)。


選擇?iOS/Other/Aggregate, 單擊?Next?并命名目標(biāo)為?Framework队丝。


注意:為什么要使用?Aggregate?目標(biāo)來(lái)構(gòu)建一個(gè) Framework 為什么不直接新建靡馁?因?yàn)?Frameworks 對(duì) OS X 的支持更好,這個(gè)事實(shí)體現(xiàn)在 Xcode 為 OS X 應(yīng)用提供了一個(gè)非常方便直接的 Cocoa Framework 構(gòu)建目標(biāo)机久。為了解決這個(gè)問(wèn)題臭墨,你要使用?Aggregate?構(gòu)建目標(biāo)(target)來(lái)做為編譯框架目錄結(jié)構(gòu)的 bash 腳本的鉤子(hook)。你開(kāi)始明白這里面瘋狂的地方了嗎膘盖?

無(wú)論何時(shí)創(chuàng)建一個(gè)新的 framework 目標(biāo)(target)都必須確保添加了靜態(tài)庫(kù)依賴(lài)胧弛。選擇 Framework 目標(biāo)(target)和?Build Phases?標(biāo)簽尤误。展開(kāi)?Target Dependencies?面板并添加靜態(tài)庫(kù)依賴(lài)。

這個(gè)目標(biāo)的主要構(gòu)建部分是多平臺(tái)編譯结缚,你將會(huì)用到腳本來(lái)執(zhí)行损晤。正如你之前所做的,在?Build Phases?中創(chuàng)建一個(gè)?Run Script红竭。


雙擊尤勋,把名字命名為?MultiPlatform Build。

粘貼下面的腳本到腳本框中:

set -e


# If we're already inside this script then die

if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then

??exit 0

fi

export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1


RW_FRAMEWORK_NAME=${PROJECT_NAME}

RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"

RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"

set -e?確保如果腳本的某部分失敗了那就讓整個(gè)腳本都失敗茵宪。這能幫你避免生成不完全的 framework最冰。

接下來(lái),RW_MULTIPLATFORM_BUILD_IN_PROGRESS?變量決定是否腳本有被遞歸的調(diào)用眉厨。如果有锌奴,那就退出執(zhí)行兽狭。

然后就是設(shè)置一些變量憾股。框架的名字將會(huì)跟工程名字一樣箕慧,例如?RWUIControls服球,還有靜態(tài)庫(kù)是?libRWUIControls.a。

接下來(lái)的腳本會(huì)設(shè)置些工程隨后會(huì)用到的函數(shù)颠焦。把下面的代碼添加到腳本框的底部:

function build_static_library {

????# Will rebuild the static library as specified

????#???? build_static_library sdk

????xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \

???????????????????? -target "${TARGET_NAME}" \

???????????????????? -configuration "${CONFIGURATION}" \

???????????????????? -sdk "${1}" \

???????????????????? ONLY_ACTIVE_ARCH=NO \

???????????????????? BUILD_DIR="${BUILD_DIR}" \

???????????????????? OBJROOT="${OBJROOT}" \

???????????????????? BUILD_ROOT="${BUILD_ROOT}" \

???????????????????? SYMROOT="${SYMROOT}" $ACTION

}


function make_fat_library {

????# Will smash 2 static libs together

????#???? make_fat_library in1 in2 out

????xcrun lipo -create "${1}" "${2}" -output "${3}"

}

build_static_library?需要?SDK?作為參數(shù)斩熊,例如?iphoneos7.0,然后會(huì)構(gòu)建相應(yīng)的靜態(tài)庫(kù)伐庭。大部分參數(shù)都是直接從當(dāng)前的構(gòu)建任務(wù)中傳進(jìn)來(lái)粉渠,但不同的地方在于?ONLY_ACTIVE_ARCH?是用來(lái)確保為當(dāng)前的 SDK 構(gòu)建所有的架構(gòu)。

make_fat_library?使用?lipo?把兩個(gè)靜態(tài)庫(kù)變成一個(gè)圾另。它的參數(shù)是兩個(gè)輸入庫(kù)后面緊跟著輸出位置霸株。點(diǎn)擊來(lái)了解更多關(guān)于?lilp?的信息。

下個(gè)部分的腳本確定了更多變量集乔,為了你能使用上面兩個(gè)方法去件。你需要知道其他的 SDK 是什么,例如?iphoneos7.0?應(yīng)該跳轉(zhuǎn)到?iphonesimulator7.0?反之亦然扰路,還要定位 SDK 的構(gòu)建目錄尤溜。

# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name

if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then

??RW_SDK_PLATFORM=${BASH_REMATCH[1]}

else

??echo "Could not find platform name from SDK_NAME: $SDK_NAME"

??exit 1

fi


# 2 - Extract the version from the SDK

if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then

??RW_SDK_VERSION=${BASH_REMATCH[1]}

else

??echo "Could not find sdk version from SDK_NAME: $SDK_NAME"

??exit 1

fi


# 3 - Determine the other platform

if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then

??RW_OTHER_PLATFORM=iphonesimulator

else

??RW_OTHER_PLATFORM=iphoneos

fi


# 4 - Find the build directory

if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then

??RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"

else

??echo "Could not find other platform build directory."

??exit 1

fi

這四個(gè)語(yǔ)句看起來(lái)都非常相似,它們使用字符串比較和正則表達(dá)式來(lái)確定?RW_OTHER_PLATFORM?和?RW_OTHER_BUILT_PRODUCTS_DIR?的值汗唱。

這四個(gè)?if?語(yǔ)句的詳細(xì)解釋?zhuān)?/p>

SDK_NAME將會(huì)是?iphoneos7.0?或?iphonesimulator6.1宫莱。這個(gè)正則表達(dá)式從字符串的開(kāi)頭處開(kāi)始提取非數(shù)字字符。因此哩罪,它的結(jié)果是?iphoneos?或者?iphonesimulator授霸。

這個(gè)正則表達(dá)式從?SDK_NAME?變量取得數(shù)字版本號(hào)肥印,例如 7.0 或 6.1 等等。

這是簡(jiǎn)單的?iphonesimulator?和?iphoneos?之間的字符串比較绝葡,反之亦然深碱。

從產(chǎn)品構(gòu)建目錄路徑的末尾處得到平臺(tái)名稱(chēng)并用其他平臺(tái)替換。這個(gè)確保其他平臺(tái)的構(gòu)建目錄能被找到藏畅。當(dāng)加入兩個(gè)靜態(tài)庫(kù)的時(shí)候這至關(guān)重要敷硅。

現(xiàn)在你可以為其他平臺(tái)編譯了,隨后會(huì)加入產(chǎn)生的靜態(tài)庫(kù)愉阎。

把下面的腳本添加到末尾處:

# Build the other platform.

build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"


# If we're currently building for iphonesimulator, then need to rebuild

#?? to ensure that we get both i386 and x86_64

if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then

????build_static_library "${SDK_NAME}"

fi


# Join the 2 static libs into 1 and push into the .framework

make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \

???????????????? "${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \

???????????????? "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"


首先通過(guò)之前定義好的函數(shù)來(lái)編譯其他平臺(tái)绞蹦。

如果你當(dāng)前要為模擬器編譯,那默認(rèn)的 Xcode 只會(huì)為那個(gè)系統(tǒng)編譯榜旦,例如 i386 或者 x86_64幽七。為了編譯所有的架構(gòu),第二部分調(diào)用?build_static_library?用?iphonesimulator SDK?重新編譯溅呢,來(lái)確保編譯了所有架構(gòu)澡屡。

最后調(diào)用?make_fat_library?函數(shù)把當(dāng)前構(gòu)建目錄的靜態(tài)庫(kù)和其他構(gòu)建目錄加到一起來(lái)制作完整的多架構(gòu)靜態(tài)庫(kù)。這個(gè)會(huì)放到 framework 里面咐旧。

最后是個(gè)簡(jiǎn)單的拷貝命令的腳本驶鹉。在末尾添加下面的腳本:

# Ensure that the framework is present in both platform's build directories

cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \

??????"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"


# Copy the framework to the user's desktop

ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"


第一個(gè)命令保證 framework 出現(xiàn)在多平臺(tái)的構(gòu)建目錄里。

第二個(gè)部分拷貝完成的 framework 到用戶(hù)的桌面铣墨。這是可選步驟室埋,但我發(fā)現(xiàn)把 framework 放到某個(gè)容易訪問(wèn)的地方會(huì)非常友好。

選擇?Framework?集合(aggregate) 方案伊约,并按下 cmd+B 來(lái)編譯框架姚淆。


編譯完成后桌面會(huì)出現(xiàn)?RWUIControls.framework。


為了檢查多平臺(tái)時(shí)候正確編譯屡律,啟動(dòng)終端并執(zhí)行以下操作:

$ cd ~/Desktop/RWUIControls.framework

$ RWUIControls.framework??xcrun lipo -info RWUIControls

第一行是切換到框架目錄腌逢,第二行使用了 lipo 命令來(lái)得到關(guān)于?RWUIControls?庫(kù)的相關(guān)信息。這會(huì)列出這個(gè)庫(kù)里出現(xiàn)的所有部分疹尾。


你能看到這兒有五個(gè)部分:i386, x86_64, arm7, arm7s and arm64上忍,正好是你在編譯的時(shí)候設(shè)置的。你之前運(yùn)行過(guò)?lipo -info?命令纳本,你會(huì)看到這幾個(gè)部分的子集窍蓝。

如何使用框架

好,現(xiàn)在你已經(jīng)有了一個(gè)框架和一些靜態(tài)庫(kù)繁成,它們可以?xún)?yōu)雅的解決你可能還沒(méi)遇到的問(wèn)題吓笙。但是這樣有什么意義呢?

使用框架的一個(gè)主要優(yōu)點(diǎn)就是使用起來(lái)很簡(jiǎn)單〗硗螅現(xiàn)在面睛,你使用RWUIControls.framework創(chuàng)建一個(gè)簡(jiǎn)單的IOS app絮蒿。

用Xcode創(chuàng)建一個(gè)新項(xiàng)目,選擇 File/New/Project在選擇iOS/Application/Single View Application叁鉴。調(diào)出你的新app的ImageViewer土涝;讓它只能適配iPhone 并且存放在與上兩個(gè)項(xiàng)目相同的文件夾下。這個(gè)app將給你展示如何使用RWKnobControl手動(dòng)的旋轉(zhuǎn)一張圖片幌墓。

查找ImageViewer 目錄下之前下好的zip文件但壮,里面有樣圖。把sampleImage.jpg 從finder里拖到Xcode的ImageViewer 組里常侣。


檢查Copy items into destination group’s folder 蜡饵,然后點(diǎn)擊Finish 按鈕完成導(dǎo)入。

用同樣的步驟導(dǎo)入框架胳施,把RWUIControls.framework 從桌面拖到Xcode的Frameworks 組里溯祸。同樣的,在Copy items into destination group’s folder 之前舞肆,確保你已經(jīng)檢查過(guò)


打開(kāi)RWViewController.m 并替換如下代碼:

#import "RWViewController.h"

#import <RWUIControls/RWUIControls.h>

@interface RWViewController ()

@property (nonatomic, strong) UIImageView *imageView;

@property (nonatomic, strong) RWKnobControl *rotationKnob;

@end

@implementation RWViewController

- (void)viewDidLoad

{

? ? [super viewDidLoad];

? ? // Create UIImageView

? ? CGRect frame = self.view.bounds;

? ? frame.size.height *= 2/3.0;

? ? self.imageView = [[UIImageView alloc] initWithFrame:CGRectInset(frame, 0, 20)];

? ? self.imageView.image = [UIImage imageNamed:@"sampleImage.jpg"];

? ? self.imageView.contentMode = UIViewContentModeScaleAspectFit;

? ? [self.view addSubview:self.imageView];

? ? // Create RWKnobControl

? ? frame.origin.y += frame.size.height;

? ? frame.size.height /= 2;

? ? frame.size.width? = frame.size.height;

? ? self.rotationKnob = [[RWKnobControl alloc] initWithFrame:CGRectInset(frame, 10, 10)];

? ? CGPoint center = self.rotationKnob.center;

? ? center.x = CGRectGetMidX(self.view.bounds);

? ? self.rotationKnob.center = center;

? ? [self.view addSubview:self.rotationKnob];

? ? // Set up config on RWKnobControl

? ? self.rotationKnob.minimumValue = -M_PI_4;

? ? self.rotationKnob.maximumValue = M_PI_4;

? ? [self.rotationKnob addTarget:self

? ? ? ? ? ? ? ? ? ? ? ? ? action:@selector(rotationAngleChanged:)

? ? ? ? ? ? ? ? forControlEvents:UIControlEventValueChanged];

}

- (void)rotationAngleChanged:(id)sender

{

? ? self.imageView.transform = CGAffineTransformMakeRotation(self.rotationKnob.value);

}

- (NSUInteger)supportedInterfaceOrientations

{

? ? return UIInterfaceOrientationMaskPortrait;

}

@end

這個(gè)簡(jiǎn)單的視圖控制器還需要做如下操作:

這個(gè)簡(jiǎn)單的視圖控制器還需要做如下操作:

導(dǎo)入#import? <RWUIControls/RWUIControls.h>?這個(gè)頭文件

設(shè)置UIImageView?和RWKnobControl 的私有屬性成hold

創(chuàng)建UIImageView并設(shè)置成你之前添加到項(xiàng)目里的示例圖片

創(chuàng)建RWKnobControl?焦辅,適當(dāng)?shù)恼{(diào)整它的位置。

設(shè)置下按鈕控件的屬性胆绊,包括rotationAngleChanged:方法的事件監(jiān)聽(tīng)處理

rotationAngleChanged:方法僅僅監(jiān)聽(tīng)UIImageView?的transform?屬性氨鹏,因此圖片可以隨著按鈕的移動(dòng)而旋轉(zhuǎn)。

更多的使用RWKnobControl?的細(xì)節(jié)請(qǐng)看上一章压状,里面解釋了怎么創(chuàng)建它。

Build并且運(yùn)行跟继。你就會(huì)看到一個(gè)簡(jiǎn)單的app种冬,隨著按鈕的改變而造成圖片的旋轉(zhuǎn)。


使用資源包

你注意到RWUIControls 僅由代碼和頭文件構(gòu)成了么舔糖?你沒(méi)有使用其他的資源(圖片之類(lèi))娱两。在IOS上framework只能柏涵一個(gè)頭文件和一個(gè)靜態(tài)庫(kù)。

現(xiàn)在系好安全帶金吗,我們馬上要起飛了十兢。在這一節(jié)中,你會(huì)學(xué)習(xí)到如何使用框架本身的資源包突破這個(gè)限制摇庙。

為RWUIControls 包創(chuàng)建一個(gè)新的UIView 旱物,把它放在右上角,設(shè)置圖片為一條緞帶卫袒。

創(chuàng)建bundle

資源加到bundle里之后宵呛,RWUIControls 項(xiàng)目里會(huì)形成額外的target

打開(kāi)UIControlDevApp 項(xiàng)目。選擇RWUIControls 子項(xiàng)目夕凝。點(diǎn)擊 Add Target按鈕宝穗,然后依次點(diǎn)擊OS X/Framework and Library/Bundle户秤。輸入RWUIControlsResources。 然后從框架選項(xiàng)卡中選擇Core Foundation


bundle建立之后逮矛,需要做一些設(shè)置鸡号。選擇RWUIControlsResources target然后點(diǎn)擊 Build Settings 選項(xiàng)卡。找到base sdk须鼎,選擇Base SDK這一行膜蠢,然后按下delete。這樣就能從OSX系統(tǒng)變?yōu)镮OS系統(tǒng)莉兰。


你還需要把product name改為RWUIControls. 找到product name雙擊修改挑围。把${TARGET_NAME} 替換成RWUIControls


圖片默認(rèn)情況下會(huì)有兩個(gè)分辨率,這可能造成一些有趣的結(jié)果糖荒;比如當(dāng)你包含一個(gè)retina @2x的版本時(shí)杉辙,它們會(huì)結(jié)合成一個(gè)多分辨率的TIFF格式,那并不是一件好事捶朵。找到hidpi?然后把COMBINE_HIDPI_IMAGES 設(shè)置為NO


當(dāng)你創(chuàng)建frameword時(shí)蜘矢,bundle也會(huì)建立框架并把它添加成target的附屬。選擇Framework target综看,點(diǎn)擊Build Phases選項(xiàng)卡品腹。展開(kāi)Target Dependencies 面板,點(diǎn)擊 +, 然后選擇RWUIControlsResources 添加為附屬红碑。

現(xiàn)在舞吭,在Framework target的Build Phases里,打開(kāi)MultiPlatform Build 面板析珊,添加如下代碼在末尾:

# Copy the resources bundle to the user's desktop

ditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \

??????"${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle"

這個(gè)命令會(huì)復(fù)制built bundle帶用戶(hù)桌面∠叟福現(xiàn)在,建立框架忠寻,你就能看到bundle 出現(xiàn)在桌面上了惧浴。


導(dǎo)入Bundle

為了再開(kāi)發(fā)新的bundle,也要能在示例APP里用它奕剃。就需要你把它添加為附屬衷旅,然后根據(jù)app添加到相應(yīng)的對(duì)象里。

在導(dǎo)航欄項(xiàng)目中纵朋,選擇UIControlDevApp 項(xiàng)目柿顶,然后點(diǎn)擊UIControlDevApp target。擴(kuò)展RWUIControls 組倡蝙,把RWUIControls.bundle拖到 Copy Bundle Resources 面板內(nèi)九串。

在Target Dependencies 面板點(diǎn)擊加號(hào),添加成新的附屬,然后選擇RWUIControlsResources

建立一個(gè)Ribbon 視圖

從RWUIControls 項(xiàng)目里的RWUIControls 組里拖拽你之前下載好的zip文件到RWRibbon 目錄猪钮。


勾選 Copy the items into the destination group’s folder,確保他們能復(fù)制到RWUIControls 靜態(tài)包中品山。


源碼中一個(gè)很重要的地方是如何引用圖片。如果你看一眼RWRibbonView.m 文件里的addRibbonView函數(shù)烤低,你就能看到下面這行:

UIImage *image = [UIImage imageNamed:@"RWUIControls.bundle/RWRibbon"];

bundle就像一個(gè)目錄肘交,因此引用bundle里的圖片真的很簡(jiǎn)單。

想要從bundle添加圖片扑馁,可以在右邊的面板中選擇它們涯呻,它們應(yīng)該屬于RWUIControlsResources target。


我們來(lái)討論一下框架的權(quán)限范圍應(yīng)不應(yīng)該是公開(kāi)的腻要?好复罐,現(xiàn)在你需要導(dǎo)出RWRibbon.h 頭文件,選擇這個(gè)文件雄家,然后從Target Membership 面板的下拉菜單里選擇Public 效诅。


最后,你需要添加這個(gè)頭到框架的頭文件趟济。打開(kāi)RWUIControls.h 然后添加下面代碼:

// RWRibbon

#import <RWUIControls/RWRibbonView.h>

添加Ribbon到示例APP

打開(kāi)UIControlDevApp 項(xiàng)目里的RWViewController.m 乱投。然后在@interface?區(qū)間里添加下面的變量:

RWRibbonView??*_ribbonView;

創(chuàng)建ribbon視圖,添加下面代碼到viewDidLoad 的末尾

// Creates a sample ribbon view

_ribbonView = [[RWRibbonView alloc] initWithFrame:self.ribbonViewContainer.bounds];

[self.ribbonViewContainer addSubview:_ribbonView];

// Need to check that it actually works :)

UIView *sampleView = [[UIView alloc] initWithFrame:_ribbonView.bounds];

sampleView.backgroundColor = [UIColor lightGrayColor];

[_ribbonView addSubview:sampleView];

構(gòu)建顷编,運(yùn)行UIControlDevApp 戚炫,在下面,你就能看到一個(gè)新的ribbon控制器媳纬。


在ImageViewer里使用Bundle

最后想要和你分享的是怎么樣使用另一個(gè)app里的bundle双肤,我們用你之前創(chuàng)建的ImageViewer app來(lái)示范

首先確認(rèn)下你的框架和bundle是不是最新的,之后選擇Framework 然后按下cmd+B構(gòu)建它

打開(kāi)ImageViewer 項(xiàng)目层宫,找到Frameworks組里的RWUIControls.framework 選項(xiàng)杨伙,使用 Move to Trash 刪除它。然后從你的桌面拖拽RWUIControls.framework 到你的Frameworks 組里萌腿。這是一定要做的,因?yàn)樾聦?dǎo)入的框架和你之前的有許多的不同抖苦。


注意:如果Xcode拒絕添加框架毁菱,你沒(méi)有正確的刪除之前的。發(fā)送了這種情況锌历,你可以從Finder打開(kāi)ImageViewer 目錄贮庞,找到這個(gè)框架,然后刪除它究西。

從桌面拖拽bundle導(dǎo)入到ImageViewer 組窗慎。選擇Copy items into destination group’s folder 并且確保勾選了ImageViewer 選框。


你可以添加ribbon到圖片里,要像選擇它遮斥,你可以在RWViewController.m 代碼里做幾個(gè)簡(jiǎn)單的改變峦失。

打開(kāi)UIImageView?和RWRibbonView ,修改imageView?的類(lèi)型:

@property (nonatomic, strong) RWRibbonView *imageView;

為了創(chuàng)建和配置UIImageView术吗,用下面的代碼替換viewDidLoad?方法:

[super viewDidLoad];

// Create UIImageView

CGRect frame = self.view.bounds;

frame.size.height *= 2/3.0;

self.imageView = [[RWRibbonView alloc] initWithFrame:CGRectInset(frame, 0, 20)];

UIImageView *iv = [[UIImageView alloc] initWithFrame:self.imageView.bounds];

iv.image = [UIImage imageNamed:@"sampleImage.jpg"];

iv.contentMode = UIViewContentModeScaleAspectFit;

[self.imageView addSubview:iv];

[self.view addSubview:self.imageView];


構(gòu)建運(yùn)行app尉辑,現(xiàn)在你就能看到使用了theRWUIControls 框架中的RWKnobControl 和RWRibbonView 的效果了。



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末较屿,一起剝皮案震驚了整個(gè)濱河市隧魄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌隘蝎,老刑警劉巖购啄,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘱么,居然都是意外死亡狮含,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)拱撵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)辉川,“玉大人,你說(shuō)我怎么就攤上這事拴测∨移欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵集索,是天一觀的道長(zhǎng)屿愚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)务荆,這世上最難降的妖魔是什么妆距? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮函匕,結(jié)果婚禮上娱据,老公的妹妹穿的比我還像新娘。我一直安慰自己盅惜,他們只是感情好中剩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著抒寂,像睡著了一般结啼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屈芜,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天郊愧,我揣著相機(jī)與錄音朴译,去河邊找鬼。 笑死属铁,一個(gè)胖子當(dāng)著我的面吹牛眠寿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播红选,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澜公,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了喇肋?” 一聲冷哼從身側(cè)響起坟乾,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝶防,沒(méi)想到半個(gè)月后甚侣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡间学,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年殷费,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片低葫。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡详羡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘿悬,到底是詐尸還是另有隱情实柠,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響脊奋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜允睹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦葡粒、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至呻此,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腔寡,已是汗流浹背焚鲜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忿磅。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓糯彬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親葱她。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撩扒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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