作者:@請叫我汪二 授權本站轉載。
前陣子做了一個簡單地蠢萌機器人 Uther ,主要實現以下功能:
收到消息後解析情感並返回對應情感的顏文字
使用動畫切換顏文字
顯示並可編輯歷史消息
花了一周時間做了 1.0 然後上線,現在 1.2 版本剛剛審核通過。簡單分享一下 Uther 從誕生到上線的過程。
代碼開源在 Github: Uther。比較粗糙,沒有仔細整理,各位見笑啦。
AppStore 下載地址: Uther。
Project
對於一個全新的 iOS 的項目,我的流程主要分為以下幾步。
Xcode
這就不啰嗦了,直接創建一個 Swift 項目即可。不過我一般不會勾選 Xcode 自帶的 git 選項,而會在後面手動配置。
Git
通過 git init 初始化倉庫,然後在 gitignore.io 找上對應的配置,填入 .gitignore 文件。至此, git 初始化完畢。可以提交一次 commit 保存。注意,個人習慣把 Pods 文件夾目錄也放到 .gitignore 中,如果使用自動生成的配置文件,取消那一行注釋即可。
Cococapods
創建 Podfile 文件,然後通過 def 按照模塊定義,然後再配置各個 target :
platform :ios, '8.0' use_frameworks! def import_networking pod 'Alamofire' pod 'Moya' end target '__placeholder__' do import_networking end
Font
字體我以前一直用的是 Consolas ,後來在微博看到了湯哥的推薦從此成了 Monoid 的忠實粉絲。
Develop
Folder
開發過程中,我的文件夾目錄一般如下:
General |- Macro |- Models |- Tools |- LOG.swift |- GCD.swift |- Models Sections |- Main |- Preview |- Setting |- User Resources
Tool
接下來聊一聊項目中的通用工具類。
GCD
項目中使用 GCD 這個庫,封裝了一些常見操作。然後定義了一個簡單的 struct 封裝一下常用的兩個方法:
struct GCD { static func async_in_worker(closure: GCDClosure) { gcd.async(.Default, closure: closure) } static func async_in_main(closure: GCDClosure) { gcd.async(.Main, closure: closure) } }
LOG
項目中使用 XCGLogger 作為 Log 工具,配置起來十分方便,聲明一個全局常量即可:
let log: XCGLogger = { let log = XCGLogger.defaultInstance() let logPath : NSURL = cacheDirectory.URLByAppendingPathComponent("XCGLogger.Log") log.setup(logLevel: .Debug, showThreadName: true, showLogLevel: true, showFileNames: true, showLineNumbers: false, writeToFile: logPath, fileLogLevel: .Info) log.xcodeColorsEnabled = true log.xcodeColors = [ .Verbose: .lightGrey, .Debug: .darkGrey, .Info: .darkGreen, .Warning: .orange, .Error: .red, .Severe: .whiteOnRed ] return log }()
DB
數據庫方面,我使用的是 SQLite.swift 。可以用泛型方便的拼接各種 SQL 語句:
typealias Pid = Int64 struct DB { private static let db = SQLite.Database(documentsDirectory.URLByAppendingPathComponent("uther.db").absoluteString!) struct MessageDB { static let table = db["message"] // 唯一索引,主鍵 static let pid = Expression("pid") // 消息創建的時間 static let createdTime = Expression("created_time") // 消息的內容 static let content = Expression("content") } static func setupDatabase() { db.create(table: MessageDB.table, ifNotExists: true) { t in t.column(MessageDB.pid, primaryKey: true) t.column(MessageDB.createdTime) t.column(MessageDB.content) } } }
Flurry
使用 Flurry 做一些簡單的統計,通過 extension 添加了一些代碼。
比如一個全局配置的靜態方法:
extension Flurry { static func start() { Flurry.setUserID(Keychain.userId); Flurry.startSession("YOUR_SESSION_ID"); } }
比如通過 enum 打一些 error log :
// ERROR extension Flurry { enum Error: String { case Setup = "SetupError" case Wenzhi = "WenzhiError" func logError(message: String) { let error = NSError(domain: "com.callmewhy.uther", code: 1001, userInfo: ["Message": message]) Flurry.logError(self.rawValue, message: message, error: error) log.error(message) } } }
比如內嵌個 struct 來統計 Message 相關的行為數據:
// MESSAGE extension Flurry { struct Message { private static let send = "Send Message" private static let receive = "Receive Positive" static func sendMessage(l: Int) { Flurry.logEvent(send, withParameters: ["MessageLength": l]) } static func receivePositive(p: Double) { Flurry.logEvent(receive, withParameters: ["MessagePositive": p]) } } }
Localization
我們可以通過 extension 給 string 加上 Localization 的屬性,返回本地化之後的字符串:
extension String { var localized: String { let s = NSLocalizedString(self, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "") return s } }
使用的時候直接調用 "LOCALIZATION_KEY".localized 即可。
Network
網絡方面使用 Moya 作為業務和 Alamofire 的中間層。以前是自己做了個 WhyEngine 封裝了 Task 和 Request ,後來有了 Moya 就簡單多了:
// MARK: - MoyaProvider let endpointResolver = { (endpoint: Endpoint) -> (NSURLRequest) in let request: NSMutableURLRequest = endpoint.urlRequest.mutableCopy() as! NSMutableURLRequest request.timeoutInterval = 2.0 return request } let SentimentProvider = MoyaProvider(endpointResolver: endpointResolver) // TODO: extension MoyaTarget to handle respose extension MoyaProvider { typealias positiveHandler = PositiveValue? -> Void func requestPositive(endpoint: T, completion: positiveHandler) -> Cancellable { ... } } // MARK: - MoyaTarget extension Sentiment: MoyaTarget { public var baseURL: NSURL { return NSURL(string: "https://wenzhi.api.qcloud.com")! } public var path: String { return "api/sentiment/" } }
後面等上了 Swift2.0 可以擴展協議,就可以直接用 MoyaTarget 直接處理 Response 了。想直接把返回結果封裝成 JSON 也很簡單:
extension MoyaProvider { func requestJSON(endpoint: T, completion: (JSON?) -> Void) -> Cancellable { return self.request(endpoint) { (data, status, response, error) in if let d = data { let json = JSON(data: d) log.debug("\(json)") completion(json) } else { log.error("\(error)") completion(nil) } } } }
小結
前面做過幾個 Swift 項目,不過都是練練手的 Demo 級別。 Uther 算是第一個完全的 Swift 項目,沒有任何 objc 的代碼。這感覺真是爽,干淨利落。
Swift 的最佳實踐還在探索中,Uther 項目有很多可以繼續改進的地方。接下來的項目准備融入一些函數式編程的元素,進一步感受各種有趣的編程范型。
歡迎討論,多多指教~