【译】iOS 架构模式--浅析MVC, MVP, MVVM 和 VIPER

作者:Bohdan Orlov 原文地址:https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52在iOS开发过程中你是否对MVC的使用感觉很别扭?你是否对转向MVVM有疑惑?你听说过VIPER,但不清楚这个东西是否值得一试。 接着读下去,你会找到上面这些问题的答案。如果读完仍不能解惑,欢迎到评论区捶我。 接下来你将在iOS环境下构建关于架构模式的知识体系。我们将简要构建一些经典的例子,并在理论和实践上进行比较他们的不同。如果你需要更多关于任何一个特定的细节,请关注我。掌握设计模式容易让人上瘾,所以要注意:阅读本文之前要问自己几个问题:谁应该持有网络请求:Model还是Controller? 如何在一个新的View中向ViewModel传递Model 谁创建了一个新的VIPER模块:Router还是Presenter为什么应该关心选用何种架构 因为如果你不这么做的话,总有一天,等这个类庞大到同时处理十几种事务,你会发现你根本无法从中找到对应代码并修改bug。当然,将这整个类了然于心是很难的,你会常常忘记一些重要的细节。如果你的程序已经处于这种状态了,那它很可能具有下面这些特征:这个类是UIViewController的子类 你的数据直接在UIViewController中进行存储 你的UIView基本什么都没做 你的Model只是一个单纯的数据结构 你的单元测试没有覆盖任何代码即使你认为自己遵守了苹果的指导,并按照苹果推荐的MVC设计规范进行开发,但还是遇到了这些问题。不要担心,这是因为苹果的MVC本身就存在一些问题,我们稍后会再来讨论它。 让我们定义一下好的架构应该具备的特点: 1、平衡的分配实体和具体角色的职责 2、把可测试性放在第一位(通过合适的架构,这将很容易实现) 3、易用性和低维护成本 为什么要分配职责 职责的分配能让我们在尝试搞清楚事情如何运作这一过程中保持一个正常的负荷。你可能会认为你投入的精力越多你的大脑越能适应更加复杂的东西,这没错。但是这个能力是非线性的,而且会很快达到临界点。所以降低复杂性的最好的方式是,根据职责单一原则将它的功能(职责)分配到多个实体中。 为什么要可测试性 对于那些已经添加了单元测试的项目来说,当他们增加一个新的功能或者重构一个复杂的类时会由单元测试告知失败与否,这多让人很放心啊。同时这也意味着这些测试项将在运行时帮助开发者找到问题,而如果这些问题发生在用户设备上的,解决他们通常会花费一周。 为什么要易用性 这并不需要答案,但值得一提的是,最好的代码是那些从未写过的代码。所以,代码越少,bug就越少。这意味着,编写更少代码的愿望不应该仅仅由开发人员的懒惰来解释,而且你不应该为了采用更好的解决方案,而对其维护成本视而不见。 MV(X)的要素 如今我们又很多可选的架构方案:MVC MVP MVVM VIPER 前三项方案是把应用程序的实体分为三类: Modes -- 负责数据域和操作数据的数据访问层,例如'Person’类, 'PersonDataProvider'类。 Views -- 负责展示层(GUI),对于iOS环境就是指所有已‘UI’开头的类。 Controller/Presenter/ViewModel -- 是Model和View的中介,通常的职责是通过响应用户在View的操作改变Model,然后根据Model的变化更新View。这些实体的分割帮助我们:更好的理解他们 重用他们(通常是View和Model) 单独测试他们让我们开始讲解MV(X)模式,随后是VIPER MVC 它原本是什么样的 在讨论苹果的MVC版本之前,让我们看一下传统的MVC模式:这个模式下,View是无状态的。它只是简单的被Controller渲染当Model变化的时候。想一下Web页面,当你点一个链接尝试跳转时,整个页面都会重新渲染。尽管可以在iOS应用程序中实现传统的MVC,但由于架构问题,这并没有多大意义—— 所有三个实体都是紧密耦合的,每个实体都知道其他两个。这正好降低了他们的重用性,而这又是你在程序中不想看到的。因为这个原因,我们将跳过编写传统MVC代码的示例。传统的MVC似乎不适合现代的iOS开发苹果的MVC 预期效果Controller是View和Model的中介,因此它俩互相不知道对方。可重用性最差的就是Controller,因为我们必须为复杂的业务逻辑提供一个位置,Model又不适合。 理论上,这看起来很简单,但是你总感觉有什么地方不对,是吧?你甚至听到人们解读MVC为Massive View Controller。也因此,视图控制器的简化成了iOS开发一个重要的课题。苹果只是采用传统的MVC并对其进行一些改进,为什么会出现这种情况呢? 现实情况Cocoa MVC鼓励你编写大量的视图控制器,因为它们是视图生命周期的一部分,很难说它们是独立的。尽管你有能力转移一些业务逻辑和数据转换工作到Model中,当需要转移工作到View时你仍然没有太多选择,因为大多数情况View的职责就是发送行为到Controller。视图控制器最终将成为一个所有东西的委托、数据源、负责调度和取消网络请求,等等。 这种代码,你肯定见过很多少次了: var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell userCell.configureWithUser(user)这个cell是View,直接通过Model进行配置,这明显违反了MVC的要求,但这种事情却经常发生,而且认为还不认为这是错的。如果你严格按照MVC的 做法,你应该在Controller里面配置cell,而不是将Model直接传递给View,但这样就会增加Controller的大小。Cocoa MVC 被称为 Massive View Controller是多么合理啊。这个问题还不是那么明显,直到提到单元测试(希望它存在于你的项目)。因为你的视图控制器跟View是紧耦合的,这将使得测试非常困难。所以你应该让你的业务逻辑和视图布局代码尽可能分割开来。 让我们看一个简单的例子: import UIKitstruct Person { // Model let firstName: String let lastName: String }class GreetingViewController : UIViewController { // View + Controller var person: Person! let showGreetingButton = UIButton() let greetingLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside) } func didTapButton(button: UIButton) { let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName self.greetingLabel.text = greeting } // layout code goes here } // Assembling of MVC let model = Person(firstName: "David", lastName: "Blaine") let view = GreetingViewController()这样根本没法测试,对吧?我们可以把greeting的赋值移到一个新的类GreetingModel中,然后分开测试它。但是我们无法在不直接调用UIView相关方法(viewDidLoad, didTapButton)的情况下测试任何外在的逻辑,而如果这样做,这些方法就导致所有view的刷新,所以这本身就是一个不好的单元测试。 事实上,在一个模拟器上加载和测试UIViews表现正常,不代表它在别的设备依然这样。所以我建议测试时移除单元测试对“宿主程序”的依赖,而直接测试代码。View和Controller之间的交互行为无法通过Unit Tests进行。根据上面的说法,Cocoa MVC是一个相当不好的架构方案。让我们再来根据文章开头定义的好架构应有的特性来评价下它:职责分离 -- View和Model是分离的,但View和Controller是紧耦合关系。 可测试性 -- 由于不好的职责分离特性,只有Model层是可以测试的。 易用性 -- 这几种架构模式中它的代码量是最少的。每个人都很熟悉这种模式,即使是一个经验有限的开发者也可以很容易的维护这份代码。如果你不打算投入很多事情在架构上,或者你感觉对于你们的小项目来说不值得投入过多维护成本,那你应该选择Cocoa MVC。Cocoa MVC 是开发速度最快的一种架构。MVP这是不是更苹果的MVC非常像?确实是这样的,它的名字叫做MVP。等一下,这是不是意味着苹果的MVC事实上就是MVP?不。你可以再观察下这个结构,View和Controller是紧耦合关系,作为MVP的中介者 -- Presenter并没有管理视图控制器的生命周期,它里面也不含有布局代码,它的职责是根据数据的状态变化更新View,所以呢,View这一层就可以很简单的抽出来。我会告诉你,UIViewController也是View在MVP模式下,UIViewController的子类实际上是Views而不是Presenters。这种区别提供了极好的可测试性,但这是以开发速度为代价的,因为你必须手动绑定数据和时间,就像这个例子: import UIKitstruct Person { // Model let firstName: String let lastName: String }protocol GreetingView: class { func setGreeting(greeting: String) }protocol GreetingViewPresenter { init(view: GreetingView, person: Person) func showGreeting() }class GreetingPresenter : GreetingViewPresenter { unowned let view: GreetingView let person: Person required init(view: GreetingView, person: Person) { self.view = view self.person = person } func showGreeting() { let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName self.view.setGreeting(greeting) } }class GreetingViewController : UIViewController, GreetingView { var presenter: GreetingViewPresenter! let showGreetingButton = UIButton() let greetingLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside) } func didTapButton(button: UIButton) { self.presenter.showGreeting() } func setGreeting(greeting: String) { self.greetingLabel.text = greeting } // layout code goes here }关于装配(assembly)的重要说明 MVP是第一个揭露三层模型装配问题的模式。我们不想让View和Model互通,因为在试图控制器(View)中执行装配操作是明显不对的,所以我们只能换个地方放装配的代码。例如,我们可以做一个应用范围的Router服务,它负责装配工作和View到View的展示。这个问题的出现不仅要在MVP中解决,在以下的几个模式中也都要解决。 我们看下MVP的特性:职责分离 -- 我们将大部分职责分配给了Presenter和Model,而视图则什么都不需要做(上面的Model也是什么都不用做) 可测试性 -- 非常好,我们可以通过静态的View测试大多数逻辑。 易用性 -- 在我们上个简单示例中,代码量是MVC的两倍,但是它的逻辑是很清晰的。在iOS中MVP模式意味着良好的可测试性和大量代码。MVP这是另一个MVP的样式 -- 由视同控制器担当管理的MVP。这个变体中,View和Model是直接绑定的,Presenter(担当管理的控制器)仍然处理着来自View的操作,并且能够改变View。 但是通过上面的学习我们已经知道了,将View和Model紧耦合处理,这种不明确的职责分离是很糟糕的。这与Cocoa桌面开发中的工作原理类似。 跟传统MVC一样,我找不到要为这个有缺陷的架构写示例的理由。 MVVM 最新而且是最好的一个MV(X)类型 MVVM是最新的MV(X)类型,希望它能解决我们之前讨论过的问题。 MVVM理论上看起来是很好的,View和Model我们已经很熟悉了,它俩之间的中介者由View Model表示。这和MVP很像:MVVM也是把视图控制器当做View 在View和Model之间没有紧耦合关系此外它的绑定逻辑很像MVP的监管版本;但是这次不是View和Model,而是View和View Model之间的绑定。 所以iOS当中的View Model到底是什么呢?它是UIKit独立于视图及其状态的表示。View Model调用Model执行更改,然后根据Model的更新再更新自己,因为我们绑定了View和View Model,第一个模型将相应的更新。 绑定 我在MVP部分明确提到过绑定,这次让我们再来讨论一下。绑定出自于MacOS开发,在iOS中是没有的。我们虽然可以通过KVO和通知完成这一过程,但是这样的绑定方式并不方便。 如果我们不想自己实现的话,有两个选项可供参考:一个是基于KVO的绑定库像RZDataBinding,SwiftBond 完整的函数式编程工具,像ReactiveCocoa, RxSwift, PromiseKit。如今当你听到“MVVM”,就应该想到ReactiveCocoa。因为它可以让你用很简单的绑定方式构建MVVM,几乎涵盖所有MVVM中的逻辑。 但是使用响应式框架会面临一个不好的现实:能力越大责任越大。使用reactive很容易将事情复杂化。也就是说,如果发生了一处错误,你需要花费很多时间去调试问题,可以简单看下响应式的调用堆栈。在我们的示例中,响应式框架甚至KVO都是多余的,我们将使用showGreeting方法显式地要求View Model更新,并使用greetingDidChange回调函数的简单属性。 import UIKitstruct Person { // Model let firstName: String let lastName: String }protocol GreetingViewModelProtocol: class { var greeting: String? { get } var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change init(person: Person) func showGreeting() }class GreetingViewModel : GreetingViewModelProtocol { let person: Person var greeting: String? { didSet { self.greetingDidChange?(self) } } var greetingDidChange: ((GreetingViewModelProtocol) -> ())? required init(person: Person) { self.person = person } func showGreeting() { self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName } }class GreetingViewController : UIViewController { var viewModel: GreetingViewModelProtocol! { didSet { self.viewModel.greetingDidChange = { [unowned self] viewModel in self.greetingLabel.text = viewModel.greeting } } } let showGreetingButton = UIButton() let greetingLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside) } // layout code goes here } // Assembling of MVVM再次回来用这三个特征验证一下:职责分离 -- 这在我们的小示例还无法体现,但是MVVM的View比着MVP的View有着更多的职责。因为前者通过View Model建立绑定更新它的状态,后者仅仅是把所有事件都交给Presenter,不更新自己。 可测试性 -- View Model是不知道View的,这可以让我们很容易的对它进行测试。View可能也需要测试,但因为它依赖UIKit,你可能想跳过它。 易用性 -- 它有河MVP模式相同的代码量,但是实际项目中,你不得不把所有事件通过View传给Presenter,然后还要手动更新View,比较而言,MVVM使用绑定将更加简洁。MVVM是很有吸引力的,因为它包含了前面提到的优点,此外通过View层的绑定,也不需要额外的代码处理View更新。测试性也还不错。VIPER 把乐高的搭建流程应用到iOS设计模式 VIPER是我们最后一个候选模式,有趣的一点是它不属于MV(X)类型。 目前为止,你必须同意职责的粒度是很重要的。VIPER在划分职责层面又做了一次迭代,它将项目划分成5层。Interactor-- 包含跟数据(Entities)和网络相关的业务逻辑,像是创建新的实例对象后者从服务器拉取数据。出于这些目的,你也可以使用Services和Mananger类完成功能,但这就不属于VIPER模块,而是外部依赖类。 Presenter -- 包含UI相关的业务逻辑,调用Interactor中的方法。 Entities -- 普通的数据对象,不是数据访问层,因为这是Interactor的责任。 Router -- 负责VIPER模块之间的切换。基本上,VIPER模块可以是一整屏内容,也可以是你应用中完整的用户行为 -- 想一下授权行为,它可以在一个或者几个相关联的界面。“乐高”方块应该多小呢?这取决于你。 如果我们将它和MV(X)类比,会发现一些在职责划分上的区别:Model(数据交互)逻辑转移到了包含Entities数据结构的Interactor中。 只有Controller/Presenter/ViewModel这种UI表示层的职责转移到了Presenter中,不包含数据。 VIPER是第一个明确导航职责的模式,并通过Router解决这个问题。在iOS应用中用一个优雅的方式处理跳转问题确实是一个挑战,MV(X)模式没有处理这个问题。该示例不涉及模块之间的路由或交互,因为MV(X)模式根本不涉及这些主题。 import UIKitstruct Person { // Entity (usually more complex e.g. NSManagedObject) let firstName: String let lastName: String }struct GreetingData { // Transport data structure (not Entity) let greeting: String let subject: String }protocol GreetingProvider { func provideGreetingData() }protocol GreetingOutput: class { func receiveGreetingData(greetingData: GreetingData) }class GreetingInteractor : GreetingProvider { weak var output: GreetingOutput! func provideGreetingData() { let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer let subject = person.firstName + " " + person.lastName let greeting = GreetingData(greeting: "Hello", subject: subject) self.output.receiveGreetingData(greeting) } }protocol GreetingViewEventHandler { func didTapShowGreetingButton() }protocol GreetingView: class { func setGreeting(greeting: String) }class GreetingPresenter : GreetingOutput, GreetingViewEventHandler { weak var view: GreetingView! var greetingProvider: GreetingProvider! func didTapShowGreetingButton() { self.greetingProvider.provideGreetingData() } func receiveGreetingData(greetingData: GreetingData) { let greeting = greetingData.greeting + " " + greetingData.subject self.view.setGreeting(greeting) } }class GreetingViewController : UIViewController, GreetingView { var eventHandler: GreetingViewEventHandler! let showGreetingButton = UIButton() let greetingLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside) } func didTapButton(button: UIButton) { self.eventHandler.didTapShowGreetingButton() } func setGreeting(greeting: String) { self.greetingLabel.text = greeting } // layout code goes here }// Assembling of VIPER module, without Router let view = GreetingViewController() let presenter = GreetingPresenter() let interactor = GreetingInteractor() view.eventHandler = presenter presenter.view = view presenter.greetingProvider = interactor让我们再一次对比那几个特征:职责分离 -- 毫无疑问,VIPER是职责分离做的最好的。 可测试性 -- 职责分离越好,可测试性当然也会更好 易用性 -- 你可能已经猜到了,上面两项意味着维护成本的提升。你必须写很多处理各个类之间交互的代码。所有这个乐高模式到底怎么样呢? 当使用VIPER时,如果你感觉就像是通过乐高方块搭建帝国大厦,这就意味着出现了问题。不应该过早在你的应用中使用VIPER,你需要考虑简便性。有些人不注意简便性,直接使用VIPER,会有点大材小用。我猜测很多人是这么想的,他们的应用迟早都会发展到适用VIPER的复杂程度,所以早晚都会做的事,即使现在维护成本高也应该继续做下去。如果你就是这么想的,我推荐你试一下Generamba -- 一个生成VIPER组件的工具。虽然对我个人来说,这感觉就像使用自动瞄准系统而不是简单的弹射。 总结 我们已经讲解了几个架构模式,希望你能解答曾经困扰你的问题。我敢肯定你也意识到了架构模式的选择没有最好这一说,它取决于你在特定环境下权衡利弊之后做的选择。 所以,在一个应用中出现混合一种混合的架构模式也是很常见的。例如,你一开始使用MVC,然后你发现有一个界面的逻辑变得很复杂,然后你转向了MVVM,但也是仅限于这个界面。你不必重构别的使用MVC的界面,因为它原本就是工作的好好的,而且这两个架构模式是很容易兼容的。事情应该力求简单,不过不能过于简单 -- 阿尔伯特·爱因斯坦

感谢大家的帮助

好消息 开始水滴筹之后,父亲治病的消息大家就都知道了,期间收到了很多来自亲友的支持和帮助。先告诉大家好消息,父亲手术完了,并且身体恢复一切正常,已于11月8号中午从重症监护室转到无菌病房,还需要在里面待够五天才转到普通病房。关于看病所需要的筹款,也得到了解决。今天晚上7点左右,我已经终止了这次筹款。 看病资金问题能快速解决,除了来自水滴筹的筹款,还有就是公司的大力支持。三位老板帮助了2万元,还破格提前给我发了年终奖,算上水滴筹中的63588元,一直到出院应该是没什么问题了。感谢公司 真的非常感谢公司给予的支持,啥也不说了,我要夸一夸他。我们公司叫乐信圣文,是一家全球领先的移动出海应用开发商,致力于为全球用户提供卓越的移动应用,作为中国移动互联网公司出海的新锐领军者,产品在全球移动端月活超过2000万,累计用户超过3亿。目前正在招聘Android、iOS、Unity、web前端、Python等开发工程师,另外中高级测试、数据分析师、产品运营、MG动画师也都职位空缺。大家有要换工作的或者朋友换工作的,可以推荐来我们这。这么好的老板,这么有前景的公司还等什么?详情可以问我或者关注公司微信公众号:乐信圣文Learnings。 感谢所以提供帮助的小伙伴 其次来自小伙伴的支持也让我非常感动,很多感动瞬间,其中有两个场景让我特别难忘。一是刚开始发起水滴筹时,当时心里慌慌的,根本不知道接下来事情会怎么发展,也不敢看手机。但不到5分钟时间,大学室友就开始齐刷刷的给我打钱,给我加油,当时铺天盖地的收款和问候消息直接就给我感动哭了,我真是爱死你们了。还有一件事是来自于一个前同事,当时我们只有QQ这一种联系方式,因为QQ用的很少,直到昨天才看到他的祝福和转账。他因为公司问题两个月都没收到工资了,知道我的情况之后,还执意给我打钱。真的是折煞我也!不知道说什么好了。 其实每次转发时我都怕打扰到大家,因为水滴筹里的各种求转发的术语,很像牛皮癣广告。但你们却对我很宽容,帮我转发,号召别人帮我转发,很多人还多次打款,真的非常感谢。我知道大家都不容易,各种生活琐事,结婚,买房,都是花钱的地方,我用你们的钱,也很过意不去,这里给大家道个歉,给你们添麻烦了。我想说的是,你们帮助我,给我爸治病这些钱不是捐给我的而是借给我的,我会在之后经济允许的情况下把钱都一一还给大家。 大家一定要给我面子啊,这笔钱我是要还的 上午我用了将近一个小时时间把水滴筹的捐款记录反复核对了两遍,只要是能对应上名字的人我都用小本本把人名和捐款数额记录下来。本次捐款收到了750次帮助,我记录下来的人名有120多个。也就是说有很大一部分人没有联系到,这其中有一大部分来自于我爸的朋友、同行,教会的人,一部分亲戚,还有就是所有参与转发水滴筹扩散之后的朋友圈成员。好像要把所有人都确认下身份也不现实,所以对于那些无法确知是谁且捐款数额较多的,我都在捐款记录的下面留了言,询问联系方式。如果能联系上,钱还是会还给你的。 这里的还钱是除了老板们的2万哈,不是不还,是因为我想换一种还法,通过努力工作的方式回报公司,帮你们多宣传多招人(主要还是有点多,他们又都是大佬,肯定不差钱)。 可能很多人捐钱时就没打算再要回来,但是我是有打算还的。我选择还大家钱是因为我知道挣钱的不易。我有困难你们帮我,我困难过去了,理应把钱还给你们。这是我的心愿,不还你钱我会不舒服的,所以大家为了照顾我的感受不能推脱啊。 当然了这个还钱过程肯定是不会快的,这段时间确实用钱厉害,等过了这个坎,我会慢慢地开始还大家钱。如果你因为什么事情着急用钱,可以先找我;如果你的钱迟迟不给,排除被遗漏的可能性,那就说明在我眼里你是土豪,土豪的钱就最后还啦。 更多近况 另外关于这几天的详细进展,以及我在七院里的一些见闻,大部分是记录下来了。大家有兴趣的话可以访问这个网站进行了解:zhangferry.com

关于水滴筹

很多朋友因为我的原因关注并参与到水滴筹中,有些小伙伴因为担心水滴筹有手续费用就直接把钱打给我。为了打消大家的顾虑,我花了些时间理解水滴筹背后的一些事情。水滴筹的背景 水滴筹是由水滴公司于2016年推出的一项针对疾病救助的筹款业务。在水滴筹之前的筹款平台领头羊是轻松筹(2014年推出),当时包括轻松筹在内的平台都会收取2%左右的手续费。水滴筹却一开始就不收取任何手续费,筹款所得资金全部归收款人(目前仅收取微信提现产生的手续费:0.1%)。免手续费肯定更多人使用,慢慢的水滴筹不断从轻松筹手里抢占市场,目前市场份额已经第一(轻松筹现在也免手续费了)。 水滴的发展 说水滴筹的发展就不得不说水滴的创始人沈鹏,他是美团的第10号员工。在美团,沈鹏战功卓著:早期带着5万元单枪匹马杀到天津,第二个月就将天津市场份额从第七提升到第一;23岁升任大区经理,通管北京、天津、山东同400人团队,参与“千团大战”;26岁,跟随王慧文立项美团外卖,带领团队在一年多的时间里把美团外卖做的行业第一。所以他的市场把控和感知能力是非常强的。 线下地推是美团的强项,这个强项也被带到了水滴公司,很多早期水滴员工都是沈鹏从美团挖来的。早期水滴筹获取流量的重要砝码就是地推,他们下沉到渗透率并不高的三四五线城市和农村地区,招募大量兼职人员,志愿者辅导当地人筹款;他们在各个医院,各个科室留下名片,传单,立起易拉宝。不光鼓励患者加入,他们还会免费指导筹钱金额怎么写,求助说明怎么写,怎么转发(一次发三条可以占满手机一屏)效果好,甚至哪些时间点转发效率更高都会说。完美复制美团“农村包围城市”的策略。 这种形式让水滴筹快速获取了大量用户,也带来了巨大的流量。快速发展的水滴公司,目前估值近30亿,腾讯是最大的投资方。 盈利模式 前面说了水滴筹是免手续费的,地推人员也都是需要发工资的,那它怎么赚钱呢? 首先按照水滴筹的规则,一次筹款是30天,在这30天内只要不发起提现,水滴筹有权将这笔款项委托给第三方进行资金托管,水滴筹至今累计筹款200多亿,平均每月就将近5亿的流水,这产生的利息是非常高的。 另外水滴筹共有超过2.5亿名爱心人士参与帮助,累计产生6.5亿人次捐助活动。这巨大的流量推动宣传了水滴公司的另外两项业务:水滴互助和水滴保。这两项业才是赚钱的,它们的区别是:水滴筹,捐款人,是来做慈善的;水滴互助,互助会员,是希望加入一个低成本的互助计划。水滴保,主动跑来的投保人,是希望找到性价比高的保险产品。 其中水滴互助是水滴公司发展的第一项业务(后来支付宝出了个相互宝,跟它的模式基本一样,一人生病,大家帮忙分摊),水滴筹的产生更深层的目的是给水滴互助导流。所以水滴公司的商业变现模式就是通过水滴筹这种公益互助类产品聚集高黏性的用户和流量,然后再通过水滴互助、水滴保等商业业务板块进行变现。 所以他们肯定是赚钱的啦,只不过是一面做“慈善”,一面做生意。 目前问题及现状 水滴筹目前已经过了市场掠夺期,但仍有些问题需要解决。水滴筹在前期的审核是相对宽松的,最主要的诊断证明和身份证号,只要没问题,就可以进入审核阶段,说是5分钟,有时基本1分钟就好了。可以让你快速的开始募捐,提现时才会需要你出示更加详细的诊断报告,病历,医院消费清单,银行卡信息等。这些都只是确认你真的生病了,但是是否贫困,其实是没有确认的,因为为了一个筹款的人就去当地调查也不现实,这也不好确认。不过这也会带来问题,有可能导致“滥用众筹”,“滥用爱心”,此前某德云社相声演员名下有房有车却筹款惹来的争议就是此类。 虽然水滴筹的审核机制受到争议,但我们并不能否认其存在的社会价值,它切切实实的帮助了很多人。在未来的监管中,要在防范风险与鼓励创新之间寻求适当的平衡,可能是监管部门和水滴公司需要认真思考和探讨的问题。如今,对于渴望疾驰的水滴公司来说,要想持续发展,关键还是“不忘初心”。

为什么会得尿毒症

看了父亲的病历,再结合之前了解的一些情况,我试着分析一下为什么会得尿毒症。 一些医学知识 关于肾脏:肾脏有很重要的代谢排毒功能,可以清除体内经由食物消化所残留的尿素、尿素氮、肌酸酐等,也会排出水分,维持体内的电解质平衡,甚至是控制血压高低起伏、活化维生素 D 的重要器官。此外,肾脏与肝脏一样,也是体内清除药物的重要器官,这也是为何肾功能不好时,就容易发生药物过量或中毒后遗症的原因。可见肾脏是何等的重要!等到肾脏的功能持续恶化到只剩下不到正常的 10% 时,才较容易出现包括恶心、水肿、高血压、倦怠无力、抽筋等症状,一旦造成更严重的尿毒症时,只能透析治疗。其中衡量肾脏功能健康程度有个很重要的指标,肌酐:肌酐是肌肉在人体内代谢的产物,临床常用来衡量肾功能的健康程度。正常范围是54-106umol/L。痛风:痛风是一种单钠尿酸盐(MSU)沉积所致的晶体相关性关节病,与嘌呤代谢紊乱及(或)尿酸排泄减少所致的高尿酸血症直接相关,属代谢性风湿病范畴。痛风可并发肾脏病变,严重者可出现关节破坏、肾功能损害,常伴发高脂血症、高血压病、糖尿病、动脉硬化及冠心病等。 因为痛风常发于指关节,痛起来是那种深入骨头的痛,取自“痛疯”的谐音。时间线 2007年 2007年,爸爸来北京打工已经7、8年左右了。凭借着勤奋和聪明,他从一个蹬板车买花的小贩变成了一个接小区别墅建筑项目的小老板,生意上虽然越来越好,很多事上爸爸还是喜欢亲力亲为。 那时正赶上夏季,爸爸要给人做葡萄架,一个葡萄架工期差不多是10人天,因为没有跟别的事赶在一起,爸爸就一个人做了。做木工活,使用频率最高的工具就是电锯。他对流程和工具的使用已经是很熟练了,但那次可能是走神了或者预估错误,没有把握好电锯的切割幅度,一下切中了小拇指。虽然没有整个切掉,但是骨头都切断了,很血腥的一幕。紧接着送去医院,医生在手指中间接了一根钢针,保住了手指。 在调养期间,为了防止发炎,打了很多抗生素也吃了很多消炎药。我们总说伤筋动骨一百天,这个调养也将近持续了一百天。这一点虽然没有体现的病历中,但我隐隐感觉,这可能就是后面一系列事件的引子。 2008-2009年 2008年左右,父亲感觉左脚大拇指外侧疼痛,频繁几次之后就去了北京解放军301医院做检查。当时检查为肌酐80(umol/L),被医生诊断为通风,并建议控制饮酒。当时吃了别嘌醇等药物进行治疗,很快疼痛的症状消失,就没有再去医院进行检查。 一直以来父亲都爱喝啤酒,因为北京这边亲戚朋友也比较多,大家不忙的时候常会一块聚聚。父辈那些人基本都爱喝酒,坐到一起,打牌,喝酒,吃肉,经常几天就一次,一喝就是酩酊大醉。虽然母亲经常批评父亲不要喝酒,但父亲感觉不疼了就是病好了,再加上朋友劝酒,自己又想喝,也没有加以控制。为了这事他被母亲不知吵了多少次。 关于痛风,目前还无法根治的,只能通过调养,延缓病情。而这其中最重要的就是要限制嘌呤的摄入,啤酒嘌呤虽然含的不多,但容易喝过量也就导致体内嘌呤大量增加。 2010-2011年 大概一年之后,爸爸又出现了左脚大拇指疼痛的症状,而且这次要比上一次严重。再去北京解放军301医院检查,肌酐已经涨到200,被诊断为“慢性肾功能不全”。痛风患者中大约有40%的人会患有慢性肾病,因为饮酒的原因由痛风发展到了慢性肾病。 之后父亲也意识到了问题的严重性,开始慢慢戒啤酒。也是从这时开始,爸爸之后吃药就没再断过。确诊为“慢性肾功能不全”之后,从医院就拿了一个月的药。这期间效果一直一般,爸妈决定换个医院试试。然后去了北京武警医院,医生对病情的诊断是一致的,但对于病情的治疗并不乐观,说慢性肾病也是一个需要调理的疾病,并不能吃某种药就根治。这次在这里又拿了1个月的药。 对于医生不能彻底根治的说法,他们有些灰心。后来在电视上看到有家中医院关于治疗慢性肾病的广告,他们宣传的效果非常好,这给他们带来了一些希望。虽然知道大医院才更靠谱一些,他们还是去了那个医院,并开了3个月的中药。中药熬出来之后都是非常苦的,但是为了治病,父亲皱着眉头也都坚持把药喝下去的。期间药吃完了就再去买,都是一买几个月的。 虽然父亲一直生病,但是并不影响干活,只要脚不疼他忙起来依旧生龙活虎的。这段时间家里的经济状况也越来越好,当时很多亲戚结婚或者盖房来我家借钱,父亲都是慷慨解囊。 2012年 到2012年暑假,我高考完来北京。爸爸身体已经出现一些症状:特别容易困,脚浮肿。有一天上午他需要到一个客户那里办事,我陪他一起去,车刚开到一半,父亲就说,开不了了,需要休息一会。然后停到一个地方,睡了20分钟,再继续走。当时脚也经常性的水肿,鞋都只能穿宽松的。 病情持续恶化,一天中午,父亲躺在床上不起来,被母亲发现时,他眼睛里面布满了血丝。我当时正在场,看到这个场景,只感觉心里被重重击打了一下,很恐慌,想哭却哭不出来。我背着他坐上车,那天直接去了北大医院,进入重症监护室。 医生已经下了病危通知书,他冷酷无情地告诉我跟母亲,病人随时有可能遭遇不测,请提前做好心理准备。我跟妈妈都哭了出来,那是我第一次近距离接触死亡。 过了几个小时,医生告诉我们父亲已经脱离了危险,并被确诊为尿毒症(慢性肾脏病5期),肌酐达到了1200。进入尿毒针阶段就只有两种方法维持生命了,透析和肾移植。因为肾移植手术不是想做就能做的,需要等待合适肾源,所以前期只能通过透析维持。度过危险期之后,由于北大医院病房紧张,父亲被转到了北京航空医院进行透析治疗。当时在航空医院住了将近一个月时间才恢复身体出院。 分析 1、饮酒和一些药物 病根在痛风上,但痛风的病因和发病机制尚不清楚。但有些诱发因素,例如大量摄入动物内脏,贝类海鲜等高嘌呤食物,还有酒精的摄入,特别是啤酒。当然还有可能导致血尿酸增高的药物。 因为已经无从查证父亲当时手指受伤都吃了哪些药,动物内脏和贝类他也不喜欢吃,只能猜测那时的用药和父亲本身喜欢喝啤酒共同造成了痛风的发生。 2、中药要慎重,去大医院看病 痛风发展成为慢性肾炎,是有很大概率的,但是保养得当也可以控制的很好。但是在短短两年多的时间 就从慢性肾脏病1期发展到5期确实太快了,很多人根本不会发展到5期,或者是很多年之后才会发展恶化到尿毒症阶段。所以我就十分怀疑是吃中药阶段导致的病情加重。 关于中药治疗肾病的效果,我看了一些网上的讨论,是还不错的,有用中药调理这种说法。那就大概率是被那个广告医院给坑了,喝中药阶段一点改善没有,而且中药本身会含有很多非药效的杂质,喝到体内会大大增加肾的负担。 3、定期检查 还有一点是如果不能保证吃的药一定就对病情有帮助,那就需要定期去医院检查,一个是了解病情处在哪个阶段,一个就是能看出来当时再吃的那个药是否有作用。 更重要的一点一定要去大医院啊。

七院第三天(11月5号)

手术完成就是渡过了最大的难关,其他时间爸爸都是在重症监护室由专门护工照顾的。实际也不需要那么多人手,先后送走了三姨姨父,弟弟,还有两位叔叔,就我自己留在医院。平常需要买什么吃的,里面护士直接给我打电话,我买好送过来。这天早上是鸡蛋小米粥,已经不需要萝卜水了,中午要吃肉丝面。听到爸爸要吃肉丝面很开心,因为这是除了小米粥鸡蛋之外第一个非常规食物。但是可能是我没考虑周到,面太长了,汤也有点少,他吃的不方便,只吃了两口。之后根据护士交代给他买了一包口香糖。 再晚些时候护士打电话说他想看书。因为里面不让玩手机,也没什么娱乐措施,除了睡觉吃饭没别的事,很容易无聊。附近就一家卖教辅资料的书店,在一堆小学生读物中,我挑了本《中外名人故事》。 下午探视时状态好很多,声音也不再那么沙哑了。感觉状态越来越好了。 三姨一直在家盼着,说你爸有什么情况随时汇报,姥爷每天上午打来一次电话询问情况,大姨,三姨,小姨也都经常打电话过来。 我这几天还是因为睡眠问题,状态不佳,特别是到了下午。困得不行就坐在台阶,往腿上一趴睡着了,但20分钟就醒了,那石板台阶是真的硬。 在监护室外面一直待到晚上十点钟,见里面一直没有消息,就回到了附近租的房子准备休息。洗个澡,躺下睡觉,这可能是近几年来睡得最香的一个晚上了。

七院第二天(11月4号)

订饭 五点半起床。医生嘱咐手术完第二天需要喝米油(小米粥虑掉小米),还有喝萝卜水。一般来说术后6小时宜服用一些排气类食物,如萝卜汤,帮助因麻醉而停止蠕动的胃肠道保持运作,以肠道排气作为可以进食的标志。送完这两样东西,我们就在外面等着了。因为这个是整个医院的手术室,大大小小的手术都从这里进入。一上午有陆续五个人先后进入手术室,还有一名孕妈妈,没多久她被推出来要转到别的病房。病床从旁边经过,我看到母亲安静的闭着眼睛,小宝宝被裹的严严实实的只漏一个小脑袋,被放在母亲腿间,后面一群家人跟着病床,多么美好的画面啊! 中午监护室大夫打电话说爸爸需要喝小米粥,因为不再是米油,就让我高兴了一下。 视频通话 下午5点到了探视时间,有一间专门的小小的探视房间,里面有两部电话,两台电视。可以通过这里接通各个病床上面的监视器,看到病人的情况,然后通过电话跟病人对讲。我看到前面两个人的通话场景,一个清晰,一个稍微模糊一点,还跑到了画面清晰的那一对排着。离手术完成已经19个小时了,非常想看看爸爸现在的情况。满怀期待地接通电话之后,大夫说,张中线床头摄像坏了,等会通过护士手机进行视频。 通过护士手机接通视频,看爸爸状态还可以,我问他感觉怎么样,他说,还好。虽然是“还好”但听他声音沙哑,不免还有些担心,不敢跟他讲太多话。问旁边大夫声音沙哑问题,他说全麻手术喉部插管引起的肿胀,需要一段时间的恢复。晚上吃饭还需要喝萝卜水。挂完电话就给爸爸准备萝卜水去了。 把跟爸爸通话的情况拍上照片发给亲戚们,告诉他们恢复的挺好。 开始水滴筹 三姨给我打电话催我赶紧弄水滴筹。一开始我是有些抵触这个事情的,因为手术费手术费凑齐了,还有感觉水滴筹这个事情有点抹不开面子。但是想到重症监护室每天一万多的花费,下个月4万的信用卡,再之后的无菌监护室5天,继续住院10天,还有后期每周一次检查,抗排异药要吃好几年,这些都是很大一笔花费。现在筹钱不管多难都比以后为钱发愁好。想通了这些我联系了一个附近水滴筹的对接人,跟他说明了情况,也问了一些他们那边关于审核,筹款,提现的手续。然后就开始了朋友圈里水滴筹。 发出去后我还恍恍惚惚的,很快就会被大家注意到,不知道即将到来的是什么,我甚至都不敢看手机。5分钟后大学室友那几个小伙子联系到我,他们五个齐刷刷的给我打钱,并祝福我爸早日康复。那会回复都回复不过来,被他们的行为感动的不行。真的,我们609无论做什么事都是齐齐整整的,毕业前这样,毕业后也是这样,很多次聚会我们的团结都让其他同学们羡慕,我们是我一辈子的好兄弟。 可能这段时间确实太累了,将近两天基本没怎么休息过,一直在顶着各种压力前进,那么一瞬间因为室友的关怀而放下压力的时候,竟不自觉哭了出来。 再过了一会张弛给我打电话说,你急用钱先借你1万,我刚买完房手头不是很宽裕,如果不够再开口。张弛是高中同学,大学各忙各的基本没怎么交流过,毕业之后又几年了,有事情还惦记着我,真的很感动。 然后再去看水滴筹上面的筹款情况,亲戚,公司领导,同事,同学,朋友都有帮助并留下祝福的话。我翻看着,感觉到一股一股的能量不断注入体内。我会好好加油的,伴着大家的祝福,一定会顺利度过这道难关。 然后跟女朋友打电话,跟她说水滴筹的事情,她表示很理解,还说之前也想过这个事,但感觉我爱面子不会同意。她也很心疼我,看我这样还数落我,你每天工作时间那么长,家里的事还就你一个人扛,工作几年攒的钱全搭进去了,你多大能耐啊。我理解她,因为本来今年许诺她买房结婚的,因为我爸的事肯定也耽误了。其实这一圈最对不起的就是她。她可能也感觉话说重了,又反过来安慰我照顾好自己,照顾好我爸。 不知从什么时候开始,无论发生什么事情,我都不再埋怨,而只是去想怎么处理好它。命运是太不公平了,把一个完整的家破坏的零零碎碎,母亲病故,父亲大病需要治疗,弟弟结婚几年还一事无成,所有的事情都需要我一人承担。之前我也会恨的用拳头砸墙,但是现在不会了,我知道这是我应该做的,我感觉自己可以做到,然后就去做了。

七院第一天(11月3号)

抵达郑州 二号中午我爸打电话来医院这边配型成功,让三号早上来做手术。我得知这个消息有些开心也有些担忧,开心是因为盼了八个多月的肾源终于有了消息,担忧是紧接着将是一场大手术,不管医院怎么保证手术成功率,肾移植手术本身都是一项大手术。 我和弟弟是二号晚上9点半的火车,因为当天才进行买票,只有硬座了,三号早上5点45到的郑州。爸爸于前一天晚上在三姨和姨父的陪同下到的医院。见到爸爸,看他精神状态挺不错,我安心不少。他说需要做的检查都已经做了,身体条件一切正常,就等医生上班安排手术时间了。准备手术钱 去到住院部,医生首先问费用是否准备齐全,肾源费15万必须现金,住院账户上至少有6万可用费用,这个账户可以用支付宝微信银行卡充值。手术之前如果这些钱不够是不给手术的。我跟我爸简单核对了下现在有多少钱,加上我之前的积蓄,差不多够。基金里有3万左右,因为这个提现有几天延迟,我担心后面有别的情况出现,也申请了提现。 护士提示我们先准备现金,15万属于大额,有些银行必须要预约还需要预约。这一点确实超出我的预期,就赶紧带着卡跟我弟一块去取钱。楼下ATM取了两万就超额不让取了。附近有个建设银行,工作人员一听要跨行转账,而且这么多直接拒绝了我们。我的钱在招商卡里面,就又去了较远的招商银行。柜员问是否有预约,我怕因为这个不给取就赶紧说了这是做手术急用的钱。他犹豫了一下同意了,就开始在柜台那边操作。包括另一张银行卡的跨行提现,他说不知道能不能行,但可以帮我们试一下。虽然最后确实不行,但这个态度挺让我满意,前后对比两家银行,建行给我的印象就更差了。 15万放在包里真挺重的,我背着沉甸甸的钱回到了医院,就在护士的待领取下去缴费。我看柜台人员点钱,15万现金,一万一万的点,要数3-4次左右,光在那等点钱等了七八分钟。然后是凑手术和住院费的钱,我这边微信,支付宝,都交了进去,一直到预存金达到6万。此时手里的钱已经不足一万了,但好在达到了手术要求的费用。 术前准备 回到医护室,护士交代,要买10支白蛋白,是术后用的。还给了我一张名片,让我打电话去找这个人买。不明白为什么医院不自己开,还要通过外面才能买?我没时间考虑太多这种事情,就按照她的要求,说什么做什么,要什么给什么。接过电话,那边先说价格380一支,我好像也没别的选择,就让他送了十支。 再之后护士又给了一张纸条,上面分两段列了需要购买的东西这些。 洗脸盆、大便器、小便器、痰盂、毛巾、牙刷杯、牙刷、牙膏、奶瓶、卫生纸一提、成人尿垫一大包、湿巾一包,消毒湿巾一包、压力绷带两个、雾化吸氧面罩一个、呼吸训练器一个。这些是重症监护室需要用的。 便携式体重秤、温度计、小药箱、小本和笔、口罩一包、输液报警器一个、扣背器一个。这些是从重症监护室出来用的。 听大夫说整个过程是晚上6点左右手术,手术完会进重症监护室,在里面观察恢复5天左右,由专门护工照顾,每天有一次探视的机会,可以通过摄像头跟病人交流几分钟。五天之后从重症监护室转到无菌病房,再有五天可以转到普通病房。普通病房阶段就可以自由看望,甚至可以下床简单活动。 买完必备东西之后,爸爸开始了透析。因为正常是一周透析三次,一三五,隔一天一透,今天周日,上次透析是周五,隔的时间较长,需要补一次。透析时我还跟爸爸说,这可能是你最后一次透析了,以后再想透析也没了。 和爸爸一起接受手术的还有一个人,安徽阜阳的,他也属鸡比我爸小一轮,身高体重血型两人也都基本一致。他们俩是要接受同一个供体。问了下还是基督家庭,三姨也感叹这是何种的缘分,冥冥之中可能就是上帝促成的这件事吧。 确定了晚上6点手术之后,爸爸从早上10点开始就被要求不能进食。因为透析的缘故他饥饿感更强了,但也没办法了。这时护士过来说你们余额不足了,需要充钱。问清楚之后我才知道,不是一共交6万而是卡内余额要有6万,一上午已经花了3万多了,就说还要充进去4万才行。这可让我很发愁,想到了这是医院账号,试着用了1万花呗,充值成功,然后是用3万信用卡的额度。这已经是穷尽了力气了,总算筹齐了做手术的钱。 手术前还有一项是洗肠,不知因为什么缘故我爸需要做两次才行,莫名有些心疼起来。之后又等了一段时间,两位叔叔也来了,也说看我爸精神状态不错。我知道每临大事时最紧张的阶段都是知道大事降临,等待事情发生的那段时间。只盼望时间可以过的快一点,不要让我们,不要让我爸煎熬太久。术前量血压,我爸高压170,他本身有一些高血压,也可能是紧张的原因,有点过高了。依照大夫的指示要吃两片降压药,此时还需要控制饮水,只让抿一小口够吃药就行。这一点水下口,可是解了他的瘾,爸爸说水真是太好喝了,想喝却不敢喝。 手术之前需要签字,医生一上来跟我说了一大堆可能的不良反应和意外情况,手术之后可能肾不工作,恢复不好还需要透析辅助,术后肺部容易感染,伤口愈合前如果翻身不小心还可能伤口破裂,术后免疫力会非常低,特别需要照顾好别感冒,抗排异药容易引起情绪焦躁,需要配合医生。想着医院原来的话,手术成功率高达99%,本来还很有信心的我一下紧张和不安起来。但医生不管这些,只是把所有可能的坏情况给我说一遍,然后说,都清楚了吧,签个字吧。这就是一个免责说明书,我毫无还手之力,签了字匆匆离开,只希望手术能够顺顺利利完成。 进行手术 傍晚6点半左右进了手术室,在手术室门口,接待的医生已经是一身蓝绿色衣服了。他简单问了一下情况,然后记着一些东西,手势动作都很熟练。虽然是简单的步骤,但他的穿着,语气和动作,却让我感受到一种专业性。专业这个东西也是我一直向往和努力的目标。因着这种专业感我很快踏实下来,相信医生,他们肯定会好好给我爸治病。 医生说手术将近三个小时。亚茹姐也信基督,组织了一个小团队,在北京为爸爸祷告。郑老师,张阿姨还有老家的一个教会也在为爸爸祷告。很多人都在祝福着这场手术,盼望着它顺利进行。爸爸进入手术室半个小时之后,临床要一起进行手术的那个人也进了手术室。 这段时间感觉过的异常慢,7点,8点,9点,这个时间点左右差不多该出来了,我就一直盯着手术室的门。想象着它打开的那个瞬间。九点半左右,门开了,但是是跟他一起手术的那个人。他本身比我爸晚进去却早出来,又让我紧张起来。二十分钟左右,想像中的那个画面才出现:医生护士,推着病床,告诉我们手术一切顺利,已经排尿了。我问爸爸感觉怎么样,他声音沙哑说不出来,跟我们见了大概5秒中,很快就被医生推着进到重症监护室了。 此时一直悬着的心才放下来,向所有关注这场手术的人报了平安之后,我心里默默说着,感谢主。虽然身边很多人都是基督徒,我也是基督家庭,我却一直没有真正成为一名基督徒,但还是很感激,我相信这是一场被神祝福了的手术。 手术做完送了两支白蛋白,按照流程基本没什么事了。但是担心有别的情况,所以重症监护室外面得一直有人看着。我跟弟弟说好一替一会在这守着,这时我俩都已经三十多个小时没合眼了。我在监护室外待到夜里两点半,期间在凳子上眯了一会,然后弟弟来替我。

如何提升Mac下生产效率

工具篇 效率神器Alfred可以从Alfred官网下载,免费版没有workflow功能,付费购买Powerpack可以使用。建议大家购买正版,如果想找破解版,网上也有很多,自行搜索。 这有一篇比较详细的Alfred的使用教程,大部分都被涵盖进去了。这里主要介绍几种对开发帮助比较大的workflow插件,官方workflows地址在这里,可以根据需要选择自己喜欢的插件使用。Youdao 其实在开发过程中经常遇到一些与英文打交道的场景,除了系统自带的字典功能,我们常用的就是有道了。直接调起客户端去搜也很方便,但是如果是频繁操作或者是在全屏下操作,还需要跑到有道里面搜一个单词,搜一句话还是会显得麻烦。 这里有一个Youdao的workflow插件。下载地址。安装之后可以通过在Alfred中键入yd {query}调出有道字典,并直接搜索翻译结果。使用Enter复制翻译结果,支持多种语言和句子翻译。CodeVar 生成可用的代码变量,解决你不知道该如何命名一个函数,类型,属性的烦恼。支持小驼峰(xt)、大驼峰(dt)、下划线(xh)、中划线(zh)、常量命名(cl)这几种命名方式。 下载地址Github 可以直接在Alfred的输入框搜索github仓库。 下载地址代码片段 Snippets。可以将自己常用的代码片段放进去,让后通过快捷键调出Snippets,直接使用代码。 func test<#Name#>() { <#statements#> }更多插件 Workflow官方插件 packal 第一个插件库,好像很久不维护了,很多插件链接都失效了。第二个packal是alfred的一个活跃论坛,比较推荐到这里发现适合你的好东西。另外github,各个博主的推荐文章也有很多,基本你能想到的搜索主题都可以找到对应的workflow插件,这里就不得不佩服社区力量的强大,还有就是这个自定义插件功能做的太棒了。 Dash 文档查询,下载地址 遇到问题最好的解决方式就是查文档,API描述能消除大部分自己的疑虑,Dash支持几十种语言的文档查询,还支持文档注释,tag功能。Dash+Alfred Dash本身支持很多IDE的插件,其中就包括Alfred插件,可以在Preferences->Integration中找到Alfred。使用方式如下:命令和快捷键篇 几个常用命令行工具 say say命令可以读出英文 $ say hello worldecho echo是输出命令 # 输出shell本身的文件名 $ echo $0将一段内容重定向至某一文件 $ echo "5.0" > .swift-versionman man命令可以查看某个命令行的详细使用 $ man echocurl curl为client url缩写。 $ curl www.baidu.com #查看网页源码 $ curl -i www.baidu.com #显示头文件 $ curl -o example.html https://www.example.com #将服务器回应保存成文件常用终端快捷键快捷键 含义Ctrl+a 跳到行首Ctrl+e 跳到行尾Ctrl+u 删除光标之前到行首的字符Ctrl+k 删除光标之前到行尾的字符Ctrl+w 删除从光标位置前到当前所处单词(Word)的开头Command+D 垂直分屏Ctrl+D 关闭垂直分屏自己动手写一个工具 AppleScript 编写自动化工具就要选一中脚本语言,这里选择了AppleScript。 这是因为:它语法简单,并接近自然语言。(没有标点符号) 语法查询十分方便。(系统原生提供语法查询字典) mac自带Script Editor可以实现编写运行并检查语法 mac上主流应用都含有AppleScript的语法支持 关于AppleScript这有一份简单的教程。 我们摘取其中几点,简要讲下AppleScript的语法。tell命令 tell application "Safari" activate end tell 告诉Safari启动。 tell命令可以嵌套使用。 set命令 定义变量到剪贴板。 set myVariables to clipboardAppleScript Suite AppleScript Suite就是 AppleScript 类(class),及其元素(element)和属性(property)的集合。这个就是OmniFocus在AppleScript上定义的Suite。脚本字典 打开「脚本编辑器」 → 新建一个脚本 → 用快捷键 Command+Shift+O 打开 AppleScript 字典(Dictionary)。这个打开之后就能看到各个应用的Suite,我们可以通过这些说明,来完成功能的使用。 编写脚本 有了上面的基础我们来编写一个选中文件夹,然后让iTerm2跳转到指定目录的脚本。 定位文件路径 tell application "Finder" set pathFile to selection as text --POSIX是mac中的根目录变量 set pathFile to get POSIX path of pathFile --防止目录存在空格跳转不了 set pathFile to quoted form of pathFile set pathFile to "cd " & pathFile end tell现在我们通过tell和set命令已经获取到了cd pathFile这段文本。 在iTerm中输入命令 上一步获取到了文本命令,接下来我们需要将这段命令在iTerm中执行。那怎么将上面的文本写入iTerm中呢? 大致思路为:打开iTerm > 创建窗口 > 写入命令。 接下来就是如何实现这几个步骤,通过查看iTerm的Suite:我们可以找到create window with default profile,write命令。有可能这就是我们需要的也有可能不是,所以有些时候是需要尝试出来的。最终实现上面步骤的代码为: tell application "iTerm" create window with default profile tell current session of current window write text pathFile end tell end tell然后完整代码为: tell application "Finder" -- get selection path set pathFile to selection as text set pathFile to get POSIX path of pathFile -- fix space problem in the directory set pathFile to quoted form of pathFile tell application "iTerm" create window with default profile tell current session of current window write text pathFile end tell end tell end tellAppleScript+Alfred 上面过程我们编写了脚本,但是怎么方便的使用它呢?可以利用Alfred。 在workflow中,我们增加一个Hotkeys。然后绑定快捷键Command+O,从快捷键脱出一条线,选择Actions > Run Script,选择Language为AS(AppleScript),粘贴我们刚才写的代码,保存即可。之后我们可以选中文件夹,触发热键Command+O就能直接在iTerm中定位至改目录了。

《图解TCP/IP》总结

最近刚把《图解TCP/IP》翻了一遍,是有很多收获,但是还有很多东西不是太懂。又因为计算机网络涉及的内容多且杂,所以有了这篇记录性质的文章。网络基础知识 计算机网络最重要的一个概念就是协议,简单来说,协议就是计算机与计算机之间通过网络实现通信时事先达成的一种“约定”。这种“约定”使那些由不同厂商的设备,不同的CPU以及不同操作系统组成的计算机之间,只要遵循相同的协议就能实现通信。 那这个协议由谁来规定呢,ISO(国际标准化组织)制定的一个国际标准OSI(开放式通信系统互联参考模型)。本书将要说明的TCP/IP并非ISO所制定的某种国际标准,而是由IETF(互联网工程任务组)所建议的致力于推进标准化作业的一种协议。这里提一下,通常OSI只是一种参考模型,他将网络分层,但是它并没有规定任何具体的协议,协议内容则归属为TCP/IP。 OSI参考模型中各个分层的作用:传输方式的分类 面向有连接型: 在发送数据之前,需要在收发主机之间连接一条通信线路。比如打电话,必须对方接通才能开始通话。 面向无连接型: 不要求建立连接和断开连接,发送端可以任何时候发送数据,接收端也永远不知道自己何时会收到数据。 分组交换: 让连接到通信电路的计算机将所要发送的数据分成多个数据包,按照一定的顺序排列之后分别发送,就是分组交换。有了分组交换,数据被细分,所有计算机可以一起收发数据,这样也就提高了通信线路的利用率。TCP/IP正是采用了分组交换技术。 单播(Unicast): 一对一通信,早先的固定电话就是单播通信的典型例子。 广播: 将消息从1台主机发送到与之相连的所有其他主机。典型例子就是电视播放。 多播 与广播类似,将消息发到多个接收主机,不同之处在于多播要限定某一组主机作为接收端,最典型的例子就是电话会议。 任播 从目标主机群中选择一台最符合网络条件的主机作为目标主机发送消息,通常,所被选中的那台特定主机将返回一个丹波信号,随后发送端主机会只跟这台主机进行通信。任播在实际网络中的应用有DNS根域名解析服务器。 地址 TCP/IP通信中使用MAC地址、IP地址、端口号等信息作为地址标识。 地址很重要的两个属性是唯一性和层次性。 MAC地址由设备的制造厂商对每块网卡进行分别制定。人们可以通过制造商识别号、制造商内部产品编号以及产品通用编号确保MAC地址的唯一性。但并不具有层次性。IP地址因具有网络号和主机号而具有层次性。 TCP/IP基础知识 TCP/IP的诞生TCP/IP的具体含义 从字面意义上讲,有时这就是指TCP和IP两种协议,但是更多情况下,他是利用IP进行通信时所必须用到的协议群统称。TCP/IP规范--RFC 前面提到的TCP/IP的协议由IEFT讨论制定,被人们列入RFC(Request For Comment)文档并公布到互联网上。RFC不仅记录了协议规范内容,还包含了协议的实现和运用的相关信息,以及实验方面的信息。可以通过RFC Editor查看RFC所有内容。 TCP/IP协议分层模型这个就是常被问及网络分层模型,它有两种,一种是OSI7层模型;一种是TCP/IP的5层模型,也有一种分法是4层模型,是将5层模型中的网卡层和硬件层合为网络接口层。 物理层(硬件) TCP/IP的最底层是负责数据传输的硬件。这种硬件就相当于以太网或电话线路等物理层的设备。 主要功能是:利用传输介质为数据链路层提供物理连接,负责处理数据传输并监控数据出错率,以便数据流的透明传输。 数据链路层(网络接口层) 网络接口层(有时人们也将网络接口层与硬件层合并起来称作网络通信层。) 利用以太网中的数据链路层进行通信,因此属于接口层。也就是说,把它当做让NIC起作用的“驱动程序”也无妨。 网络层(互联网层)网络层(Network layer)是参考模型的第3层。主要功能是:为数据在结点之间传输创建逻辑链路,通过路由选择算法为分组通过通信子网选择最适当的路径,以及实现拥塞控制、网络互联等功能。 IP IP是跨越网络传送数据包,使整个互联网都能收到数据的协议。IP协议使数据能够发送到地球的另一端,这期间它使用IP地址作为主机的标识。 ICMP(Internet Control Message Protocol) IP数据包在发送途中一旦发生异常导致无法到达对端目标地址时,需要给发送端发送一个发生异常的通知。ICMP就是为这一功能而制定的。它有时也被用来诊断网络的健康状况。 ARP(Address Resolution Protocol) 地址解析协议,从分组数据包的IP地址中解析出物理地址(MAC地址)的一种协议。 传输层传输层最主要的功能就是能够让应用程序之间实现通信。计算机内部,通常同一时间运行着多个程序。为此,必须分清是哪些程序与哪些程序在进行通信。识别这些应用程序的是端口号。 TCP TCP是一种面向有连接的传输层协议。它可以保证两端通信主机之间的通信可达。TCP能够正确处理在传输过程中丢包、传输顺序乱掉等异常情况。此外,TCP还能够有效利用带宽,缓解网络拥堵。 TCP首部其中控制位是用于连接管理的标记位,字段长为8位,每一位从左至右分别为CWR、ECE、URG、ACK、PSH、RST、SYN、FIN。这些控制标志也叫做控制位。当它们对应位上的值为1时,具体含义如下所示。连接管理 TCP会在数据通信之前,通过TCP首部发送一个SYN包作为建立连接的请求等待确认应答(TCP中发送第一个SYN包的一方叫做客户端,接收这个的一方叫做服务端。) 。如果对端发来确认应答,则认为可以进行数据通信。如果对端的确认应答未能到达,就不会进行数据通信。此外,在通信结束时会进行断开连接的处理(FIN包)。 可以使用TCP首部用于控制的字段来管理TCP连接。一个连接的建立与断开,正常过程至少需要来回发送7个包才能完成(建立一个TCP连接需要发送3个包。这个过程也称作“三次握手”。) 。UDP UDP是User Datagram Protocol的缩写。 UDP有别于TCP,它是一种面向无连接的传输层协议。UDP不会关注对端是否真的收到了传送过去的数据,如果需要检查对端是否收到分组数据包,或者对端是否连接到网络,则需要在应用程序中实现。 由于UDP面向无连接,它可以随时发送数据。再加上UDP本身的处理既简单又高效,因此经常用于以下几个方面:包总量较少的通信(DNS、SNMP等) 视频、音频等多媒体通信(即时通信) 限定于LAN等特定网络中的应用通信 广播通信(广播、多播)UDP首部应用层(会话层及以上分层) TCP/IP的分层中,将OSI参考模型中的会话层、表示层和应用层的功能都集中到了应用程序中实现。这些功能有时由一个单一的程序实现,有时也可能会由多个程序实现。因此,细看TCP/IP的应用程序功能会发现,它不仅实现OSI模型中应用层的内容,还要实现会话层与表示层的功能。 FTP 在FTP中进行文件传输时会建立两个TCP连接,分别是发出传输请求时所要用到的控制连接与实际传输数据时所要用到的数据连接(这两种连接的控制管理属于会话层的功能。) 远程登录(TELNET和SSH) 远程登录是指登录到远程的计算机上,使那台计算机上的程序得以运行的一种功能。TCP/IP网络中远程登录常用TELNET(TELetypewriter NETwork的缩写。有时也称作默认协议。) 和SSH(SSH是Secure SHell的缩写。) 两种协议。 发送数据包 假设甲给乙发送电子邮件,内容为:“早上好”。而从TCP/IP通信上看,是从一台计算机A向另一台计算机B发送电子邮件。这一过程可以用如下图示表示:应用协议 HTTP 当用户在浏览器的地址栏里输入所要访问Web页的URI以后,HTTP的处理即会开始。HTTP中默认使用80端口。它的工作机制,首先是客户端向服务器的80端口建立一个TCP连接,然后在这个TCP连接上进行请求和应答以及数据报文的发送。 HTTP中常用的有两个版本,一个HTTP1.0,另一个是HTTP1.1。在HTTP1.0中每一个命令和应答都会触发一次TCP连接的建立和断开。而从HTTP1.1开始,允许在一个TCP连接上发送多个命令和应答(这种方式也叫保持连接(keep-alive)。) 。由此,大量地减少了TCP连接的建立和断开操作,从而也提高了效率。多媒体实现技术 由于TCP具有流控制、拥塞控制、重发机制等功能,有时应用所发出去的数据可能无法迅速到达对端目标主机。然而在互联网电话(使用的VoIP(Voice Over IP的缩写。) )和电视会议当中,即使有少许丢包,也希望系统延时少一点,非常注重系统的即时性。因此,在实时多媒体通信当中采用UDP。 然而,只使用UDP还不足以达到进行实时多媒体通信的目的。例如,在互联网电视电话议会中需要提供查询对方号码、模拟电话机的拨号以及以什么形式交互数据等功能。为此,需要一个叫做“呼叫控制”的支持。呼叫控制主要采用H.323与SIP协议。此外,还需要RTP协议(结合多媒体数据本身的特性进行传输的一种协议)和压缩技术(在网络上传输音频、视频等大型多媒体数据时进行压缩)的支持。 H.323 H.323是由ITU开发用于在IP网上传输音频、视频的一种协议。起初,它主要是作为接入ISDN网和IP网之上的电话网为目的的一种规范而被提出的。 H.323定义了4个主要组件。它们分别是终端(用户终端)、网关(吸收用户数据压缩顺序的不一致性)、网闸(电话本管理、呼叫管理)以及多点控制单元(允许多个终端同时使用)。SIP 与H.323相对的TCP/IP协议即是SIP(Session Initiation Protocol)协议。 “终端之间进行多媒体通信时,需要具备事先解析对方地址、呼出对方号码并对所要传输的媒体信息进行处理等功能。此外,还需要具备中断会话和数据转发的功能。这些功能(呼叫控制与信令)都被统一于SIP协议中。它相当于OSI参考模型中的会话层。RTP和RTCP UDP不是一种可靠性传输协议。因此有可能发生丢包或乱序等现象。因此采用UDP实现实时的多媒体通信需要附加一个表示报文顺序的序列号字段,还需要对报文发送时间进行管理。这些正是RTP(Real-Time Protocol)的主要职责。 RTP为每个报文附加时间戳和序列号。接收报文的应用,根据时间戳决定数据重构的时机。序列号则根据每发出一次报文加一的原则进行累加。RTP使用这个序列号对同一时间戳的数据(尤其是对于视频的数据。视频中一个帧的数据往往要超过一个包,然而它们发送的时间戳一致。此时就可以使用同一时间戳内不同的序列号加以区分。) 进行排序,掌握是否有丢包的情况发生。 RTCP(RTP Control Protocol)是辅助RTP的一种协议。通过丢包率等线路质量的管理,对RTP的数据传送率进行控制。 TLS/SSL与HTTPS 对于一些涉及机密信息的网络连接需要进行加密处理,Web中可以通过TLS/SSL(Transport Layer Security/Secure Sockets Layer。由网景公司最早提出的名称叫SSL,标准化以后被称作TLS。使用TLS/SSL的HTTP通信叫做HTTPS通信。 HTTPS中采用对称加密方式。而在发送其公共密钥时采用的则是公钥加密方式(对称加密虽然速度快,但是密钥管理是巨大的挑战。公钥加密密钥管理相对简单,但是处理速度非常慢。TLS/SSL将两者进行取长补短令加密过程达到了极好的效果。由于谁都可以发送公钥,使得密钥管理更为简单。) 。 确认公钥是否正确主要使用认证中心(CA(Certificate Authority) )签发的证书,而主要的认证中心的信息已经嵌入到浏览器的出厂设置中。如果Web浏览器中尚未加入某个认证中心,那么会在页面上提示一个警告信息。此时,判断认证中心合法与否就要由用户自己决定了。IEEE802.1X IEEE(The Institute of Electronical and Electronics Engineers,美国电子和电气工程师协会)委员会中,依据不同的工作小组制定了各种局域网技术标准。因于1980年2月启动局域网国际标准化项目,所以命名为802。 IEEE802.1X是为了能够接入LAN交换机和无线LAN接入点而对用户进行认证的技术(包括我们常用的WiFi) 并且它只允许被认可的设备才能访问网络。虽然它是一个提供数据链路层控制的规范,但是与TCP/IP关系紧密。一般,由客户端终端、AP(无线基站)或2层交换机以及认证服务器组成。蓝牙 蓝牙与IEEE802.11b/g类似,是使用2.4GHz频率无线电波的一种标准(因此,当IEEE802.b/g等设备与蓝牙设备一起使用时,无线电波信号削减有可能导致通信性能的下降。) 。数据传输速率在V2中能达到3Mbps(实际最大吞吐量为2.1Mbps)。通信距离根据无线电波的信号的强弱,有1药看看吧3 B、10药看看吧3 B、100药看看吧3 B三种类型。通信终端最多允许8台设备(其中一台为主节点,其他1~7台为受管节点。这种网络也叫做piconet,微微网。) 。 如果说IEEE802.11是针对笔记本电脑这样较大的计算机设备的标准,那么蓝牙则是为手机或智能手机、键盘、鼠标等较小设备而设计的标准。

【译】Swift World:设计模式--中介者模式

原文:https://medium.com/swiftworld/swift-world-design-patterns-mediator-e6b3c35d68b0 作者:Peng今天我们讨论一下中介者模式(Mediator)。这次不从抽象定义开始,而是用现实世界中的一个场景来解释它。在一个团队里,有产品经理,开发工程师,质量工程师。当开发完成了某些功能,将代码提交到仓库。相关环节人员,像质量工程师和产品经理需要被通知。protocol Collogue { var id: String { get } func send(message: String) func receive(message: String) } class Developer: Collogue { var id: String var qe: QE var pm: PM init(qe: QE, pm: PM) { self.id = "Developer" self.qe = qe self.pm = pm } func send(message: String) { qe.receive(message: message) pm.receive(message: message) } func receive(message: String) { print(message) } } class QE: Collogue { var id: String var developer: Developer var pm: PM init(developer: Developer, pm: PM) { self.id = "QE" self.developer = developer self.pm = pm } func send(message: String) { developer.receive(message: message) pm.receive(message: message) } func receive(message: String) { print(message) } } class PM: Collogue { var id: String var developer: Developer var qe: QE init(developer: Developer, qe: QE) { self.id = "PM" self.developer = developer self.qe = qe } func send(message: String) { developer.receive(message: message) qe.receive(message: message) } func receive(message: String) { print(message) } }每个角色都需要持有另一个角色的实例,这种连接方式是高耦合的,且很不容易修改。现在我们需要一个中介者帮助我们简化这个系统。中介者的目的是帮助对象之间相互交流。它让每个对象都是跟自己进行交互而不是其他对象。当前对象不需要持有别的对象,而是持有中介者。这样将解耦系统,它的结构图如下所示:我们来写一下代码: protocol Mediator { func send(message: String, sender: Colleague) } class TeamMediator: Mediator { var colleagues: [Colleague] = [] func register(colleague: Colleague) { colleagues.append(colleague) } func send(message: String, sender: Colleague) { for colleague in colleagues { if colleague.id != sender.id { colleague.receive(message: message) } } } }通过持有中介者,那几个角色对象变成了这样: protocol Colleague { var id: String { get } var mediator: Mediator { get } func send(message: String) func receive(message: String) } class Developer: Colleague { var id: String var mediator: Mediator init(mediator: Mediator) { self.id = "Developer" self.mediator = mediator } func send(message: String) { mediator.send(message: message, sender: self) } func receive(message: String) { print("Developer received: " + message) } } class QE: Colleague { var id: String var mediator: Mediator init(mediator: Mediator) { self.id = "QE" self.mediator = mediator } func send(message: String) { mediator.send(message: message, sender: self) } func receive(message: String) { print("QE received: " + message) } } class PM: Colleague { var id: String var mediator: Mediator init(mediator: Mediator) { self.id = "PM" self.mediator = mediator } func send(message: String) { mediator.send(message: message, sender: self) } func receive(message: String) { print("PM received: " + message) } }这样一来,整个结构就变成了下面这样:让我们用新的方式来使用它: //usage let mediator = TeamMediator() let qe = QE(mediator: mediator) let developer = Developer(mediator: mediator) let pm = PM(mediator: mediator) mediator.register(colleague: developer) mediator.register(colleague: qe) mediator.register(colleague: pm) mediator.send(message: "Hello world!", sender: developer)另一个相似的例子就是非常受欢迎的Notification(NSNotification)。你可以在网上找到很多相关的代码。