行事使开发iOS

辞旧迎新,曾经就多去,未来早就赶到;新的一样年,让自己用心去接吧!

Designer News.png

天道如隙,稍纵指尖。没有安静的所见所闻,只有一定之日;生活的各个一样龙,每一刻且于转移着。在这辞旧迎新之际发表一下此时真正的描绘:毕业即两年,而自要么如此,从一无所有的结业到现在饥寒交迫的上班,只是心绪多矣多少惆怅罢了!这同一年半以来,从最初毕业毫无方向的理想满怀到本筹不展的决不方向。

前段时间在design+code购入了一个上学iOS设计及编码在线课程,使用Sketch设计App,然后以Swift语言实现Designer
News客户端。作者Meng
To都起源及Github:MengTo/DesignerNewsApp ·
GitHub。虽然实现一体Designer
News客户端基本功能,但是用臃肿MVC(Model-View-Controller)架构,不易被代码的测试和复用,于是采用ReactiveCocoa贯彻MVVM(Model-View-View
Model)架构,加上一个用Objective-C实现之BDD测试框架Kiwi来单元测试,就可表现使得开发iOS
App。

2015顿时无异年起首毕业走有校园做美工,到逐渐转向运营,在斯进程遭到一度想过去做大神(程序猿),之前大学闲暇之余还见面失掉学点代码啥的,但说到底自己意识自己虽然长之尚未那么夸张,但却不用那么坦然,感觉成神的路途无极端适合自身,至多吗便是用作一栽兴趣爱好罢了。而无心了解及即世界上还有产品经理这样一栽人,看了数乔布斯、张小龙之类的故事,感觉这就是是一个比大神还牛比的事情,顿时以为我认的世界原来不是那样。

ReactiveCocoa

ReactiveCocoa是一个用Objective-C编写,具有函数式和响应式特性的编程框架。大多数底开发者他们缓解问题之考虑方式都是何等形成任务,通常的做法就是编很多指令,然后修改要数据结构的状态,这种编程范式叫做命令式编程(Imperative
Programming)。与命令式编程不同之是函数式编程(Functional
Programming),思考问题的计是形成什么任务,怎样描述是任务。关于对函数式编程入门概念的明白,可以参考酷壳《函数式编程》这篇文章,深入浅出对函数式编程的想方式、特性以及技巧通过有些演示来上课。

今、随着社会之腾飞,科学技术的升华,整个社会之分工出现了明确的变通。统一之社会大工作分工方式已不再称给今日的社会发展,而是转而产出了社会垂直化精细分工。即作产品人,你恐怕每地方的知都亟待了解部分,但是最后想只要在这个社会立足,还是得发和好之相同技术的长才实施。

ReactiveCocoa解决什么问题?

  • 目标中状态及状态的凭过多问题
    借用ReactiveCocoa中一个例子来说明:用户在登录界面时,有一个用户称输入框和密码输入框,还有一个报到按钮。登录交互要求如下:

  • 当用户称及密码可验证格式,并且之前还无登录时,登录按钮才能够点击。

  • 当点击登录成功登录后,设置已登录状态。

民俗的做法代码如下:

static void *ObservationContext = &ObservationContext;

- (void)viewDidLoad {
   [super viewDidLoad];

   [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext];
   [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager];

   [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
   [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
   [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)dealloc {
   [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext];
   [NSNotificationCenter.defaultCenter removeObserver:self];
}

- (void)updateLogInButton {
   BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0;
   BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn;
   self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn;
}

- (IBAction)logInPressed:(UIButton *)sender {
   [[LoginManager sharedManager]
       logInWithUsername:self.usernameTextField.text
       password:self.passwordTextField.text
       success:^{
           self.loggedIn = YES;
       } failure:^(NSError *error) {
           [self presentError:error];
       }];
}

- (void)loggedOut:(NSNotification *)notification {
   self.loggedIn = NO;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
   if (context == ObservationContext) {
       [self updateLogInButton];
   } else {
       [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
   }
}

如上使用KVO、Notification、Target-Action等处理事件或信息之方编写的代码分散到各个地方,变得乱七八糟和不便明白;但是下RACSignal统一处理的话,代码更加简明和易读。使用RAC后代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    @weakify(self);

    RAC(self.logInButton, enabled) = [RACSignal
        combineLatest:@[
            self.usernameTextField.rac_textSignal,
            self.passwordTextField.rac_textSignal,
            RACObserve(LoginManager.sharedManager, loggingIn),
            RACObserve(self, loggedIn)
        ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) {
            return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue);
        }];

    [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) {
        @strongify(self);

        RACSignal *loginSignal = [LoginManager.sharedManager
            logInWithUsername:self.usernameTextField.text
            password:self.passwordTextField.text];

            [loginSignal subscribeError:^(NSError *error) {
                @strongify(self);
                [self presentError:error];
            } completed:^{
                @strongify(self);
                self.loggedIn = YES;
            }];
    }];

    RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter
        rac_addObserverForName:UserDidLogOutNotification object:nil]
        mapReplace:@NO];
}
  • 民俗MVC架构中,由于Controller承担数据印证、映射数据模型到View和操作View层次结构等多只义务,导致Controller过于臃肿,不便利代码的复用和测试。
    于人情的MVC架构中,主要发生Model,
    View和Controller三部分组成。Model主要是保留数据和拍卖工作逻辑,View将数据显示,而Controller调解关于Model和View之间的享有交互。
    当数到时,Model通过Key-Value Observation来打招呼View Controller,
    然后View Controller更新View。当View与用户交互后,View
    Controller更新Model。

Typical MVC paradigm.png

恰而你所见,View
Controller隐式承担过多专责:数据证明、映射数据模型到View和操作View层次结构。MVVM将许多逻辑从View
Controller移走及View-Model,等引见完ReactiveCocoa后会见介绍MVVM架构。还有局部关于怎样减负View
Controller好文章请参见objc中国复轻量的View
Controllers系列:

  • 再次轻量的 View
    Controllers

  • 整洁的 Table View
    代码

  • 测试 View
    Controllers

  • 采取Signal来取代KVO、Notification、Delegate和Target-Action等传递信息
    iOS开发被起多种消息传递方式,KVO、Notification、Delegate、Block和Target-Action,对于其中来什么差异以及哪些挑选要参见《消息传递机制》。但RAC提供RACSignal来归并消息传递机制,不再为怎么抉择何种传递信息方式要苦恼。

    RAC对常用UI控件事件进行封装成一个RACSignal对象,以便对发的各种风波进展监听。
    KVO示例代码如下:

// When self.username changes, logs the new name to the console.
//
// RACObserve(self, username) creates a new RACSignal that sends the current
// value of self.username, then the new value whenever it changes.
// -subscribeNext: will execute the block whenever the signal sends a value.
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
}];

Target-Action示例代码如下:

// Logs a message whenever the button is pressed.
//
// RACCommand creates signals to represent UI actions. Each signal can
// represent a button press, for example, and have additional work associated
// with it.
//
// -rac_command is an addition to NSButton. The button will send itself on that
// command whenever it's pressed.
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
    NSLog(@"button was pressed!");
    return [RACSignal empty];
}];

Notification示例代码如下:

 // Respond to when email text start and end editing
 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
      [self.emailImageView animate];
      self.emailImageView.image = [UIImage imageNamed:@"icon-mail-active"];
      self.emailTextField.background = [UIImage imageNamed:@"input-outline-active"];
  }];

 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
      self.emailTextField.background = [UIImage imageNamed:@"input-outline"];
      self.emailImageView.image = [UIImage imageNamed:@"icon-mail"];
  }];

除了,还可应用AFNetworking顾服务器后针对回数据由创始一个RACSignal。示例代码如下:

 + (RACSubject*)storiesForSection:(NSString*)section page:(NSInteger)page
{
    RACSubject* signal = [RACSubject subject];

    NSDictionary* parameters = @{
        @"page" : [NSString stringWithFormat:@"%ld", (long)page],
        @"client_id" : clientID
    };

    [[AFHTTPSessionManager manager] GET:[DesignerNewsURL stroiesURLString] parameters:parameters success:^(NSURLSessionDataTask* task, id responseObject) {
                NSLog(@"url string = %@", task.currentRequest.URL);
                [signal sendNext:responseObject];
                [signal sendCompleted];
    } failure:^(NSURLSessionDataTask* task, NSError* error) {
                NSLog(@"url string = %@", task.currentRequest.URL);
                [signal sendError:error];
    }];

    return signal;
}

稍加朋友可发有些意想不到,上面代码明明返回的是RACSubject,而休是RACSignal,其实RACSubject是RACSignal的子类,但是RACSubject写来代码更加简洁,所以下RACSubject(官方不推荐运)。等下以RAC核心类设计时,你就见面询问其中间的关系和如何选择。

当看到此事情并初步摸底后,感觉一切职业似乎还是吧团结量身定做的,我是处女座啊!我哉不曾艺术家的大开大合;没有先后猿完全理性的逻辑思考;我欣赏自己看好办事,把自己之社会风气掌控在融洽手里;喜欢带点理性又带动点感性的体会世界,想工作;懂点设计,还懂点代码;喜欢点理论还喜欢点哲学;喜欢广猎百家所长;想只要把同码业务为到极致致;我吗想做一个成品让世界都来所以他,……。艾玛,这不就是也自己量身定做的呢!然后以至于自己就毫不犹豫踏上上了当下长长的不由路,开始了有的基本技能的习。老人说得好:工欲善其事必先利其器!想如果上一个初的本行(从未了解之行业)肯定得事先控有基础,和学武打拳一样得预将马步扎好。于是乎自己单方面开在既有些工作,一边花了大体上年日错开学了连带的工具软件,当时感到产品经营真是极牛叉了,想只要从头年后特别关系一会,而实是否为着实如这样想象的这样尽如我意,下面就来拘禁过程。

ReactiveCocoa核心类设计

至于RAC核心类设计,官方文档有详实的说明:Framework
Overview

对自身个人来说:一宗工作虽然结果好重要,但经过同样为主要。我早就来大丰富一段时间特别好同词话:“尽人事听天命!”,做自我能开的,放下自己未克举行的(这句话应该要以自身初中的上看到并欣赏的,那时候起成绩不好,也无思套,于是就爱了马上句话,觉得古人云的好对,我哪怕未是阅读的口,干为非要是读呢……,当然后面好像不这样想了!)。至于后面的改变就是无多说了,每个人走过的里程不尽相同,哪怕同一家庭中的兄弟姐妹,同一班上之同班朋友,一直叫同一企业一样机关的同事等等。每个人都起温馨人生之转向点,生命遭受终究会当某某一个一眨眼以有人要有事促使你确实去改变(用同一句子话形容就是是:意外以及明天,谁也非亮谁先来),不要说您无会见转移,一个丁的转移是一个索要时刻积蓄的事体,当一系列的报应呈现于公前面的上,你本来的会失掉思维,会有变更,这吗是干吗许多人会晤为雪脑筋,然后以会醒来(说多矣……)。

Sequence和Signal基本操作

刺探完个RAC核心类设计下,要学会对Sequence和Signal基本操作,比如:用signal执行side
effects,转换streams, 合并stream和合并signal。详情请查阅官方文档:Basic
Operators

实际确实那么般美好、一帆风顺呢?带在同颗朦胧的心里,4月鼓起勇气辞职面试了出品岗位,而己之天命还非是一般的好,居然通过了面试(当然我要好还是亮之,我真正什么还非知情,除了会就此几单器……)。早上10:00来今之号面试结束,当时HR告诉自己,今天你的直属上司不以小卖部,回去等信息(其实就即令从来没有抱希望,人家肯定是腼腆直接拒绝自立白痴,在给自身寻找借口吧)。但是由一颗心的实行着,我哪怕这么带来在忐忑的心思进入了“漫长”而经人之等(还记得面试那边是4月15日-周五-裸辞第二龙),等及周末己骨子里等不下来了,主动打电话问问了面试我之HR,感觉十分尴尬的,你究竟要无设自身哟,不要你啊告知自己转啊,是吧!男子汉大大丈夫,18年晚哥又是如出一辙长条好汉(没这样重啦……)。然而谜底是HR给了我一个充分懵逼的答:你确定要来啊?要来的口舌你周一来报道!(我委蒙了,wo艹,我来面试还打电话让你而当握手豆你玩么,居然问我是否确认真的要来),然后自己以想,是未是就号和之良啊?怎么会这么问吗?或者是里面竞争激烈?或者是天天加班?或者是商家只要吃败仗啦?但思维这和好失去面试的早晚不像如果过的指南呀!我确实懵逼了!(其实这里说明,机会是上下一心争取的,只有团结主动的争取时机时才可能会见属于您;在面试结束不使不怕杳无音讯的默不作声了,积极主动的夺沟通奖可能取得任用的几带领会大大增强)

MVVM架构

MVVM high level.png

于MVVM架构中,通常还拿view和view
controller看做一个完好。相对于前MVC架构中view
controller执行很多每当view和model之间数据映射和相互的做事,现在以它交给view
model去开。
至于选择哪种体制来更新view
model或view是不曾强制的,但平常咱们还选择ReactiveCocoa。ReactiveCocoa会监听model的改动然后以这些改动映射到view
model的属性被,并且可尽有工作逻辑。
推选个例来说,有一个model包含一个dateAdded的性能,我思监听它的变型然后更新view
model的dateAdded属性。但model的dateAdded属性的数据类型是NSDate,而view
model的数据类型是NSString,所以于view
model的init方法吃进行数量绑定,但待多少类型转换。示例代码如下:

RAC(self,dateAdded) = [RACObserve(self.model,dateAdded) map:^(NSDate*date){ 
    return [[ViewModel dateFormatter] stringFromDate:date];
}];

ViewModel调用dateFormatter进行多少易,且方法dateFormatter可以复用到另外地方。然后view
controller监听view model的dateAdded属性且绑定到label的text属性。

RAC(self.label,text) = RACObserve(self.viewModel,dateAdded);

而今咱们抽象出日期变到字符串的逻辑到view
model,使得代码可以测试复用,并且帮view controller瘦身

拉动在这么懵逼的状态及同样颗惆怅的心尖,周一我来到了庄,感觉还老对的什么!在咱们及时穷乡僻壤的地方,能起这般可怜一个办公室场地,不错啊!占了上上下下一交汇楼(妈蛋,面试时来回匆匆,都尚未好好看两双眼),心基本安了下,然后开始了讲工资,年轻人嘛,虚心点(其实是愚昧),感觉自己吗都不见面嘛,能要团结就是正确了,像给了多老大好处一样,也未敢要工钱,人家问我及一样小有些,也特么老实,也就是活生生说了,其实人家还格外好心的,给了自己一个还算假如不是的薪资,年少人傻嘛!就如此干及本。好吧,也当是为青春积累经验吧!从进入企业吧,一个月份无顾好之上司,当然为即半个月基本无提到啥事(期间写了平首公司之制品体验报告)。期间以也从不见了当,也无之到底老板是一个怎么的食指,只以QQ上且过您一两次等,让自身形容了个商家去年(2015)年支付的相同缓产品的体会报告。当是真正是何都不见面,在某网站东翻翻,西翻翻,终于寻了个模版,跟着写了只现行中心看不下去的经验报告,我估摸就业主看了即想将自辞职了…..(其实是说写得是,但是好要懂好写的安,或许夸自己只是不思量让新人太老压力)。

Kiwi

Kiwi是一个iOS行为使开发(Behavior Driven
Development)的仓库。相比于Xcode提供单元测试的XCTest是自从测试的角度思考问题,而Kiwi是由行为的角度思考问题,测试用例都按三段式Given-When-Then的叙说,清晰地发挥测试用例是测试什么样的靶子要数据结构,在因什么上下文或气象,然后做出什么响应。

describe(@"Team", ^{
    context(@"when newly created", ^{
        it(@"has a name", ^{
            id team = [Team team];
            [[team.name should] equal:@"Black Hawks"];
        });

        it(@"has 11 players", ^{
            id team = [Team team];
            [[[team should] have:11] players];
        });
    });
});

咱蛮轻因上下文将那提为Given..When..Then的三段式自然语言

Given a Team, when be newly created, it should have a name, it should have 11 player

故Xcode自带的XCTest测试框架写过测试代码的对象或者体会到,以上代码更加容易阅读与了解。就算以后发生新的开发者入或者修护代码时,不需要极度特别的资金去读书与清楚代码。具体哪些行使Kiwi,请参考两篇文章:

  • TDD的iOS开发初步与Kiwi使用入门
  • Kiwi 使用上阶 Mock, Stub,
    参数捕获和异步测试

然后立即等同齐就差不多半单月,老板终于由京都总公司回到了,对于自己吧就当成好信息,其以京都立即一个月份啊咱(当时本身啊勉强算是公司产品部的同一号了咔嚓!)带会来了一个眼看拘留起“颇为不利”的类。开会说得不可开交简短(还记当时自己进这职业来与的首先个会议),需求是:根据客户和市场实际要求做一个微信端商城,然后还连后期的销售运营工作,公司由产品销售中获得销售提成(30%)。当时挺急的,项目方是京城沿的一致小海洋牧场(不造是什么吧,其实我吗不之,你就管他想念成为是蒙古放牛羊一样的哪怕哼,只是这里面养的凡“海鲜”而已……)。项目决不自己支付,用他人第三正的且得。当时一样听,这不就是是自身前多年涉嫌的尽本行吗?微信网站啥的自在实行啊,当时盛极一时底呀产生赞许、微盟、云微客、分销客、微三云之类的事物,我闭着双眼便了解其中来什么(有点夸张哈……),但是的确要命熟,而且店还配设计师,我其实都想说不用这么累啦,我一个口保守估计一个周之内于您搞定!然后就是同入我力所能及搞定的楷模接下这个“项目”(也想过,尼玛这样确实可以为?那么强之酬金,人家也甚索你吗,然后以想可能真的是钱大半丁傻啊!!!全然不知,坑偶多老……)。然而事实很快即马上了Flag,显示貌似乎真的没自己思念的那么美好。

Designer News UI

在编写Designer
News客户端代码之前,首先通过UI来打探所有App的大概。设计Designer News
UI的家伙是Sketch,想获得Designer
News UI,请点击下载Designer New
UI。

Designer News Design.png

假设拿具有的页面还相继说明如何编写,会于耗时间,所以特以登陆页面来证明自身是怎么样行事令开发iOS,但我会以总体项目之代码上流传github。

废话太多,中间经过尽管不一一描述了,这个路——也就算是自身之处女作,其实说心声做得起接触糟糕。当时收受这东东自己确实什么都非晓,搞了区区单原型出来差点吓着老板跟研发的兄长们了(还有产品的妹纸……),现在羁押起呢吓到了自家要好。我都以怀疑研发的哥哥们是怎码出的,过程非常的苦,事实证明产品并未实战的排基本在拉,上战场就全乱了阵脚。废话,直接上图:

登陆界面

出于是类别简单又只生一个口开(差不多总人口付出来说,采用Storyboard不易于代码合并),加上Storyboard可以可视化的添加UI组件和Auto
Layout的格,并且可以预览多单不等分辨率iPhone的效力,极大地提高开发界面效率。

Login.png

大洋牧场微信商城

登陆交互

登陆界面有Email输入框和密码输入框,当用户选中其他一个输入框时,左边对应的图标变成蓝色,同时会生pop动画表示用户准备而输入内容。
当用户没有输入有效之Email或密码格式时,用户是无克点击登陆按钮,只有当用户输入有效的邮件及密码格式时,才能够点击登陆按钮。

Login.gif

咱们好动用RAC由此监听Text
Field的UITextFieldTextDidBeginEditingNotificationUITextFieldTextDidEndEditingNotification的通报来处理用户选中Email输入框和密码输入框时改变图标和显示的动画片。

#pragma mark - Text Field notification
- (void)textFieldStartEndEditing
{
    // Respond to when email text start and end editing
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
        [self.emailImageView animate];
        self.emailImageView.image = [UIImage imageNamed:@"icon-mail-active"];
        self.emailTextField.background = [UIImage imageNamed:@"input-outline-active"];
    }];

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
        self.emailTextField.background = [UIImage imageNamed:@"input-outline"];
        self.emailImageView.image = [UIImage imageNamed:@"icon-mail"];
    }];

    // Respond to when password text start and end editing
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.passwordTextField] subscribeNext:^(id x) {
        [self.passwordImageView animate];
        self.passwordTextField.background = [UIImage imageNamed:@"input-outline-active"];
        self.passwordImageView.image = [UIImage imageNamed:@"icon-password-active"];
    }];

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.passwordTextField] subscribeNext:^(id x) {
        self.passwordTextField.background = [UIImage imageNamed:@"input-outline"];
        self.passwordImageView.image = [UIImage imageNamed:@"icon-password"];
    }];
}

当点击登陆按钮后,客户端向服务端发送验证请求,服务端验证了账户与密码后,用户就可成功登陆。所以,接下要了解RESTful
API的基本概念和Designer News提供的RESTful API。

扣押了是匪是不行想念生的感到,东西非常少,很粗略,但为不行Low,哈哈,要扣了后台就更思念煞了!好以拖欠片段东西还是基本无得到下,说实话,现在叫自己再次举行啊非克担保好及稍稍。

Designer News API

先是软原型出来基本无考虑用户以状况,和真需求什么的,完全是和谐想着便举行了,然后评审的结果虽无须说了,想想就理解呀!一直到第二坏、第三差以后才成为了今是法,最后文档出来测试研发一脸懵逼,标注之类的勾勒的乱七八糟的,现在友好扣在吧是亚面子懵逼。整个东西打原型到产品到呢客户做商品详情文案到结尾做营销H5,两只周时间,小白就开得真的挺累的,但是得呢或满的,虽然是类别现早已死了(当时凡是定向面向首都用户之,妈蛋人家雷同听是殊海域的海鲜直接就是转身走了……,怪不得人家吃30%之接触,所以说啊,天上没有丢馅饼的业务……),踩在这已经去世的项目头上和谐竟有了有些实战经验,勉强“进入”了活岗位。

RESTful API基本概念和计划性

REST齐全是Representational State
Transfer,翻译过来就是见层状态转化。要想的确懂得它们的含义,从几只重大字入手:Resource,
Representation, State Transfer

  • ##### Resource(资源)

资源就是网达到之实业,它可是文字、图片、声音、视频或平等种植服务。但网络发生诸如此类多资源,该怎么标识它们为?你可用URL(统一资源定位符)来唯一标识与固化它们。只要获得资源对应的URL,你就得拜它们。

  • ##### Representation(表现层)

资源是同样种植信息实体,它来多种意味方法。比如,文本可以用.txt格式表示,也得据此xml、json或html格式表示。

  • ##### State Transfer(状态转换)

客户端访问服务端,服务端处理完后返客户端,在是历程被,一般还见面唤起数据状态的更动或者撤换。
客户端操作服务端,都是经HTTP协议,而当是HTTP协议中,有几乎只动词:GET,
POST, DELETEUPDATE

  • GET表示收获资源
  • POST表示新增资源
  • DELETE表示去资源
  • UPDATE表示更新资源

明RESTful核心概念后,我们来概括了解RESTful API设计以便可以看懂Designer
News提供API。就拿Designer News获取Stories对许URL的一个例来验证:
客户端请求
GET https://api-news.layervault.com/api/v1/stories?client_id=91a5fed537b58c60f36be1sdf71ed1320e9e4af2bda4366f7dn3d79e63835278

服务端返回结果(部分结果)

{
  "stories": [
    {
      "id": 46826,
      "title": "A Year of DuckDuckGo",
      "comment": "",
      "comment_html": null,
      "comment_count": 4,
      "vote_count": 17,
      "created_at": "2015-03-28T14:05:38Z",
      "pinned_at": null,
      "url": "https://news.layervault.com/click/stories/46826",
      "site_url": "https://api-news.layervault.com/stories/46826-a-year-of-duckduckgo",
      "user_id": 3334,
      "user_display_name": "Thomas W.",
      "user_portrait_url": "https://designer-news.s3.amazonaws.com/rendered_portraits/3334/original/portrait-2014-09-16_13_25_43__0000-333420140916-9599-7pse94.png?AWSAccessKeyId=AKIAI4OKHYH7JRMFZMUA&Expires=1459149709&Signature=%2FqqLAgqpOet6fckn4TD7vnJQbGw%3D",
      "hostname": "designwithtom.com",
      "user_url": "http://news.layervault.com/u/3334/thomas-wood",
      "badge": null,
      "user_job": "Online Designer at IDG UK",
      "sponsored": false,
      "comments": [
        {
          "id": 142530,
          "body": "Had no idea it had those customization settings — finally making the switch.",
          "body_html": "<p>Had no idea it had those customization settings — finally making the switch.</p>\\n",
          "created_at": "2015-03-28T18:41:37Z",
          "depth": 0,
          "vote_count": 0,
          "url": "https://api-news.layervault.com/comments/142530",
          "user_url": "http://news.layervault.com/u/3826/matt-soria",
          "user_id": 3826,
          "user_display_name": "Matt S.",
          "user_portrait_url": "https://designer-news.s3.amazonaws.com/rendered_portraits/3826/original/portrait-2014-04-12_11_08_21__0000-382620140412-5896-1udai4f.png?AWSAccessKeyId=AKIAI4OKHYH7JRMFZMUA&Expires=1459125745&Signature=%2BDdWMtto3Q10dd677sUOjfvQO3g%3D",
          "user_job": "Web Dood @ mattsoria.com",
          "comments": []
        },
  • 协议(protocol)
    用户以及API通信采用HTTPs协议
  • 域名(domain name)
    相应尽可能部署及专用域名下https://api-news.layervault.com/,但有时会进一步扩充为https://api-news.layervault.com/api
  • 版本(version)
    应该将API版本号v1放入URL
  • 路径(Endpoint)
    路径https://api-news.layervault.com/api/v1/stories表示API具体网址,代表网同样种植资源,所以无克来动词,只有以名词来表示。
  • HTTP动词
    动词GET,表示从服务端获取Stories资源
  • 过滤信息(Filtering)
    ?client_id=91a5fed537b58c60f36be1sdf71ed1320e9e4af2bda4366f7dn3d79e63835278指定client_id的Stories资源
  • 状态码(Status Codes)
    服务器向客户端返回表示成功还是黄的状态码,状态码列表请参考Status
    Code
    Definitions
  • 错误处理(Error handling)
    服务端处理用户要失败后,一般都归error字段来代表错误信息

{
    error: "Invalid client id"
}

这儿早就是6月新了,公司也起懒散状态上了不闲散状态(整个企业了了年尽管比如泄了欺凌之皮球,一适合要充分无在的规范,听说最近点滴年还如此,一直到五六月才缓和过来,小白就几乎上也如泄了气之皮球,还没有休息过来吧),开始了召开自己之品种。也不怕是时下小白正于一部分星期日夜做客服的此东东(http://www.51nhds.com)。虽然在上一个项目上自认为已经走了很多坑,但做产品的坑却不是当时的我或者说现在的我所能真的理解的,这一年下来才真的知道自己在产品这条路上真的还只是个小白,很多坑真的得靠时间和经验来沉淀、积累。这不是一个可以一蹴而就的职业,也算是明白为什么很多人说:从现代国内的教学体系里完全不可能培养出产品经理来,相对于产品经理这样开放的职业,需要博学而多闻、需要拥有一颗“真爱之心”,是一个不断尝试、不断去创新的职业,而现在的应试教育模式是可以说完全不适合的(当然这里不是说应试教育的好与不好,我也没资格说好与不好,这里只是陈述这个事实)。

Designer News提供API

Designer News API
Reference提供依据HTTP商量遵循RESTful设计之API,并且同意应用程序通过oAuth
2授权协议来获取授权权限来拜会用户信息。

此起彼伏到如今径直接了年会大师平台下的季只稍品种:背景墙(图片/视频)、年会邀请函、礼物榜寄礼品的计划以及年会颁奖。几只项目下为被我尝试遍了出品路上的坑,毕竟是小白,加上自己立即口发一个糟糕的惯很多事情总好自己失去雕饰、自己失去印证。虽然自己吗时说站于巨人的肩膀上我们见面爬得还快、走得重新远、站的又胜似,叫借势吧,通俗点就是往旁人的阅历,向其他人请教!但自偏偏喜欢作死,因为我到底看多事情还是要好去证明才能真正明白怎么会如此,产品路上的坑对于自来说呢是这么。说实话,前面两个东西公司研发的同班估计还无思量做自我的东西了,还吓局产生几单研发组分别分开对接,而非是过渡一直接通一个组,相对来说可能于时刻和空降以及情感上且小缓和,自己也很尴尬的,自己踩坑还得带及一样堆积人随后我踩,不过总的来说经过几个种类实战下来,收获广大,现在吓多矣。

访问API工具

貌似的话,在写访问服务端代码之前,我都见面为此Paw(下载地址)工具来测试API是否可行;另一方面,用JSON文件保留服务端返回的数,用于moco依傍服务端的劳务。至于为何用moco模拟服务端,后面会教,现在通过用户登录Designer
News
是例子介绍如何以Paw来测试API。
咱们事先看Designer News提供访问用户登录的API

Designer News Login API.png

冲以上提供的音信,API的路子是https://api-news.layervault.com/oauth/token,参数有grant_typeusernamepasswordclient_secret。其中usernamepassword在Designer
News报才会博取,而client_idclient_secret用发送email到news@layervault.com报名。使用Paw发送请求与服务端返回结果如下:

New Send Request.png

先是单门类是背景墙,说起来真蛮粗略,还没有ppt强大(差多矣……)。就是一个图形/视频及背景音乐、特效的组成作为舞台背景,就这样一个概括的物,我耶能将他做出过多坑来。第一单坑:预览的坑,我们的大屏比例是按部就班规范高清大屏的尺码比例1920×1080(16:9)来举行的,因为预览图的轻重比例尚和社稷师撕了同顿,而结尾之结果是自我败下阵来,预览图被做成了1:1底方形。导致本用户上传图片后虽咨询,你们马上怎么回事,按照你们比例做的希冀什么,怎么变形了(大哭);第二独坑:上传的坑,为了解决用户达到传大体积的视频或音乐失败的题材,我们召开了断点续传(图片、视频、音乐),因为这个事延迟上丝一个两全,上线后尚在连改(大醉),这为是活及研发双方都没有足够的阅历去所予;第三只坑:为了解决用户视频转码难,不见面改之题目,在线解码的问题,也是大约是坐大家都没有经验,首先不过去阿里服务器能够更改哪些格式,纠结和验证花了一如既往龙,然后不知道原来视频转码除了担保原视频能播放、后缀以我们的解码范围外还亟需视频本身要带达相应之文件格式参数,这个是上线后才察觉的,然后就是移成后怎么通知用户的问题(直接报告状态),这么一个东东吧纠结了多平龙;第四个坑:我们究竟允许用户传多大,是否限数量,引起这个问题之来头是极其小莫可知满足要求,太特别到早晚播放会有题目,且转码什么的且亟待用,最后汇总各端情况于出了一个当下来拘禁大家还能够承受之价值(图片5M以内、视频1G中、音乐10M里),这毕竟一个分外普遍的克的,虽然还是时有发生微量用户会觉得不够,但好以未多,而缩减一下也截然能够接受;另外就是是设无使自行开缩减的问题,不举行压缩在线播放对网速和电脑硬件性能都生格外高的求,而我们才起来、没因此用户数量,目前咱们的用户之具体情况不打听(用啊电脑、什么系统、浏览器、网速等另因素……)。就如此一个简单的成效时比较严重的饶生出这么多坑,详细情况其实更严重(大哭)。

Moco模拟服务端

Moco是一个方可轻松搭建测试服务器的家伙。

深感十分简单是休是……

怎要效法服务端

作为一个走开发人员,有时出于服务端开发进度慢,空有一个iPhone应用可表达不生意向。幸好有矣Moco,只需要配备一下请与归数据,很快就可以搭建一个效仿服务,无需等待服务端支付形成才能连续出。当服务端完成后,修改访问地址即可。

偶尔服务端API应该是呀样子都还从来不了解,由于生矣moco模拟服务,在出进程遭到,可以持续调整API设计,搞懂真正好想使之API是呀样子的。就这么,在服务端代码还不曾当真动手前,已经提供相同客真正满足好欲之API文档,剩下的就算交给劳务端照着API去实现即尽了。

还有雷同栽情形就是,服务端已经勾勒好了,剩下客户端还并未完成。由于moco是地方服务,访问速度比较快,所以通过使用moco来模拟服务端,这样不光可以增长客户端的访问速度,还提高网络层测试代码访问速度的长治久安,Designer
News就是这般状况。

老二独品种是邀请函,这个事物在产品内就挑起了重的议论,是与现有的H5如易企秀、MAKA等企业合作(用他人的接口)还是自己单身开发,是召开一个想易企秀一样的东西要直接开一个SX的沙盘,是背景以及情节无涉及或者做一个更SX的只能修改字段的模版呢?这些都经少天之座谈,过程被在许多争辩,个人提出的方案是咱既无必要举行一个好企秀这样的良东西,也休想开一个太SX的模板。我们好错过爱企秀、MAKA这些平台做调研,同时分析传统邀请函的必需信息,了解用户为什么用因此是事物,然后开一些模块化的模板让用户可自由组合,但是倘若受用户一个一直会因此底底子模版(参考有赞微商城的做法,满足不同品类用户的急需),但结尾是受PASS掉,有时候自己都以怀念,是和谐太容易妥协了邪?或许是吧!但最终是事物我以腾讯旗下的“微现场”看到了,当时微现场也尚没有达标线,当初之构想基本是不谋而合。另外当里的地图导航而无苟开在线地图的接口问题呢是与研发斯了大体上上,我看十分有必不可少,研发觉得没有太非常必要,且需花费很多时空,最后老拍板说不开(结果虽是出去后老板问我胡从来不举行,三体面懵逼)。这是亚浅举行手机端的东西,第一赖就是海养牧场那个了,第二不良当手机端经验还是出若干积累的,至少没有像第一不成那样有部分要命低级的题目,同时由当下是已是9月初了,研发资源紧,给本人之时空
不多,因此还受研发砍掉了差不多一半的情节(很伤心……)。

怎使用Moco模拟服务

页面布局其实还是可以的…..

安装

倘你是使用Mac或Linux,可以品尝一下手续:

  1. 确定你安装JDK 6以上
  2. 下载脚本
  3. 将其置身你的$PATH路径
  4. 安装它可以履(chmod 755 ~/bin/moco)

现在而得运作一下令测试安装是否成

  1. 编辑配置文件foo.json,内容如下:

[
      {
        "response" :
          {
            "text" : "Hello, Moco"
          }
      }
]
  1. 运行Moco HTTP服务器
    moco start -p 12306 -c foo.json
  2. 打开浏览器访问http://localhost:12306,你扭曲看见”Hello, Moco”

老三只类别是礼物榜,这是店今年项目被绝无仅有赚了钱之事物(宝宝心中苦,但宝宝啊笑着说),虽然整体看来完全是无济于事,但出得赚点总比没有好嘛,是吧!也只好这么安慰自己了。项目举行下自我之第一觉得是直接从回来不思只要之,因为太讨厌了,效果也坏,但迅即曾是10月了,没工夫了,最后逼于岁月以及资源的涉,我以低头了——就如此吧!用途就是是当漫天年会任何时刻若是打开送礼物观众还可以呢节目演员送礼品,后台统计金币数量。表达观众对节目表演者的支持及可用于节目评选,场控人员随时可以切出礼物榜(礼物排行),而众多口于年会中怀念只要捧老板还是上司甚至自己暗恋的女神/男神什么的就是会破产钱拍,思路来直播送礼,我们吧是刚刚用观众的之思想,总的下挣了大半10w,虽然少,但这是白赚的(基本无占用多老服务器资源,因为内容是缓存到用户端本地的,后台就承担统计下多少,而服务器人家送或者未送我们还是要从头的……)。

布服务

由于发生早晚服务端返回的数据比较多,所以用服务端响应的数码独立在一个JSON文件中。以登陆为例,将数据存放于login_response.json

{
    "access_token": "4422ea7f05750e93a101cb77ff76dffd3d65d46ebf6ed5b94d211e5d9b3b80bc",
    "token_type": "bearer",
    "scope": "user",
    "created_at": 1428040414
}

设以呼吁uri路径,方法(method)和参数(queries)等安排在login_conf.json文件中

[
  {
    "request" :
      {
        "uri" : "/oauth/token",
        "method" : "post",
        "queries" : 
          {
            "grant_type" : "password",
            "username" : "liuyaozhu13hao@163.com",
            "password" : "freedom13",
            "client_secret" : "53e3822c49287190768e009a8f8e55d09041c5bf26d0ef982693f215c72d87da",
            "client_id" : "750ab22aac78be1c6d4bbe584f0e3477064f646720f327c5464bc127100a1a6d"
          }
      },
    "response" :
      {
        "file" : "./Login/login_response.json"
      }
  }
]

匪了解发生没发出在意到面uri路径不是咸路线http://localhost:12306/oauth/token,因为协议默认是http,而且一般运行于本机localhost,所以当启动拟服务经常只有需要点名端口12306就是推行。想愈详实询问哪些布置,请查阅官网的HTTP(s)
APIs
还有一个索要配置地方就是,由于实在付出被必将不止一个客户端请求,所以还需要一个配备文件settings.json来含有很有请。

[
    {
        "include" : "./Story/stories_conf.json"
    },
    {
        "include" : "./Login/login_conf.json"
    },
    {
        "include" : "./Story/story_upvote_conf.json"
    }
]

上,遮丑得管与伦比啊……

启动服务

以路径跳反到DesignerNewsForObjc/DesignerNewsForObjcTests/JSON目录,找到settings.json文件,使用命令执行来启动服务:
moco start -p 12306 -g settings.json

初步还有一个构想,借用直播的礼品体系,不只有送,还会换,提现,即取得红包的人数得报名提现(平台扣去得比例),而送礼者获得积分可用以抽奖或者直接换其他平台物品。这样可以于送的人尚未后顾之忧(目前送了咱是无尚钱,送的凡真钱白银啊……),而这样好让年会真的欢才畅起来,公司花几十万惩治年会不就是是叫大家以返家前开心一转、给闹才艺的总人口一个形的机会、提升大家的沟通交流机会,韵养公司企业文化呢?所以直播的这套路于年会现象被依旧会走通,事实证明也是这样(很多供销社听说送了是白送直接回限制员工充值……),但是最后抱总拍板不做,那就非开吧,我以降了,谁让咱们是新人为,产品前为从来不什么数据积累,大家开心就好!

动Paw验证是否配备成功

Send request to Local Server.png

季个门类颁奖功能,这是开得最好坑的一个作用,不光是物坑,我要好吧坑,研发为坑,这个东东终将局核心具有的研发转了千篇一律周。需要整合之前的外武器的多寡,统一实现为游乐与颁奖仪式发奖的效果,且要求于成品达到必须达一致性(为了统一交互,达到所谓产品的一致性我修改了三独要命版,大哭……),还未可知去动之前的铁,因为做这个事物就是11月矣,时间老窘迫了,不敢动其他东西。年会将降临,公司的运营活动现已开始,各个研发组都还有好之职责要开,这个东西了是董事长横插进来的。最后由咱们架构师讨论决定直接每个组抽调一两名成员协助完成这个类别。当时自家就是当怀念是东东估价要举行砸,事实是勿美好的政工总是如期而至。

行使开发(BDD)

自从互动需求及结尾我同谈了三次详细需求宣讲(还带动在分析数据、接口等),也移了少潮不行需求(修改了三个可怜版本,在活形态上才好不容易得了下去)。因为凡逐一组都发成员在召开,有些人仅仅是匹配接数据,因此上线必须联调,而联调的时段大家因为在联合,懵逼了!!!你的想法和自己之咋不一样的,我们得这么的数额,你于自己的非正常啊,你怎么是那样的呀!我错。尼玛完全对不达号啊!然后便是自己发生重召集所有相关人仔细为她们分析了数码逻辑,当场确认有的数据联网能不克对接上,怎么连等同样名目繁多相应有项目经理做的业务,并记下会议内容邮件抄送所有人。开了会自确实要命懵逼,当时开头做的早晚不是赤诚的报我莫问题,这些你们会好着想的啊?我怀念骂人,没道,谁受产品是咱自己的呢,再推呗。因为各个研发组之间莫关系的题材造成上线延迟近一半单月(后面研发自己联调就花了一个周到,测试联调又费了两三上)。

干什么用BDD

免懂得诸位在编写测试的当儿,有无发出思考了一个题目:我该测试什么?要应对这问题并无是那简单,在从来不抱答案之前,你还是延续以卿的想法编写测试。
-(void)testValidateEmail;
譬如说这么的测试,存在一个根本问题。它不见面告诉您应该会发啊,也未见面预期实际会产生啊。还有,当其发出误时,不见面唤起而于何出错误,错误的案由是啊,因此若用深刻代码才会知道失败的原故。这样就需大量额外和免必要的认知负荷。
此时BDD出现了,帮助开发者确定有道是测试什么,它提供DSL(Domain-specific
language,
域特定语言),测试用例都以三截式Given-When-Then的叙说,清晰地表述测试用例是测试什么样的靶子要数据结构,在冲什么上下文或气象,然后做出什么响应。
故而,我们应当关注行为,而不是测试。那行为具体是呀?当您设计app里面的其中目标时,它的接口定义方法及其依赖关系,这些艺术和靠关系决定了你的目标如何跟另外对象交互,以及她的效力是什么,定义你的靶子的行为

实际还是饱暖的…..

BDD过程

行让开发大概三只步骤:

  1. 择最为要之作为,并编写行为之测试文件。此时,由于测试对象的类似还无编制,所以编译失败。创建测试目标的近乎并编制类的地下实现,让编译通过。
  2. 实现为测试类的行,让测试通过。
  3. 若是发现代码中出再代码,重构叫测试接近来驱除重

而少不知情里面步骤细节,没有关系,继续为下读,后面来例子介绍来帮助您了解三独步骤的含义。

花费了这么多时光及生机该说至少问题不多矣咔嚓,测试呢测量了,我要好基本功能也飞了。但是尼玛上线用户采用,各种题材接踵而来,直到前几乎上,也尽管是核心用户年会都多结束了,才好不容易基本全面了,这简单天发奖功能再为从来不出题目了,中途一直发微微题目,改了这来良,改了挺来以此。总结:就是不用随意相信研发于活方面的逻辑思维能力和联络能力,他们多时光真只是实现公的法力而已,不见面去思的;至于沟通就他们沿着在以于并或者还是得你失去推动沟通,不然你见面收取什么的结果你明白的!

登陆验证

如上基本是刨坑的废话,哈哈,到此地实在乃吗可以笑,哈哈!!!

纱访问层

以下说说马上等同年的所得所失:人生来得自然起失去,不要单看见你收获的,却看无展现你失去的;也变化只望你去的设看不到而收获的!前者易傲然自满,终失去一切;后者易自惭形秽,不敢有。

DesignerNewsURL

DesignerNewsURL类似包装网络访问URL

#import <Foundation/Foundation.h>

extern NSString* const baseURL;
extern NSString* const clientID;
extern NSString* const clientSecret;

@interface DesignerNewsURL : NSObject

+ (NSString*)loginURLString;
+ (NSString*)stroiesURLString;
+ (NSString*)storyIdURLStringWithId:(NSInteger)storyId;
+ (NSString*)storyUpvoteWithId:(NSInteger)storyId;
+ (NSString*)storyReplyWithId:(NSInteger)storyId;
+ (NSString*)commentUpvoteWithId:(NSInteger)commentId;
+ (NSString*)commentReplyWithId:(NSInteger)commentId;

@end

此间还发个技巧就是是于DesignerNewsURL.m贯彻公文来只原则编译,判断是当测试环境还是产品环境来控制baseURL的价,可以死有利在测试环境与制品环境相切换。

#ifndef TEST
NSString* const baseURL = @"https://api-news.layervault.com";
#else
NSString* const baseURL = @"http://localhost:12306";
#endif

NSString* const clientID = @"750ab22aac78be1c6d4bbe584f0e3477064f646720f327c5464bc127100a1a6d";
NSString* const clientSecret = @"53e3822c49287190768e009a8f8e55d09041c5bf26d0ef982693f215c72d87da";

不久前深夜尝听好经解读:人生什么最要,定位和可行性。定位是呀?即我们今天之“地址”,要到的“地址”,至少有矣简单单点,然后才出了主旋律;最终决定你是不是到极限的庐山真面目原因未是公的快发出多快,而老而的主旋律仍不准,在错误的取向直达而偏偏见面越走越远,当然可能远方来再次美的见识。而轻经最基础的就是一定,讲究以卦位定乾坤:六阳为男性,六阴为坤。太极生两式,两仪生四象,四相生八卦,八卦衍天下。天地乾坤变幻无穷,终其平乎,卦也,卦者,位为。这是想说开同码事开之初定位非常重点。道家也生道:道生一、一生二、二生三、三生万物、万物无极;此处无极其不是说没极限,而是说勿是从来不终点。道家云:阴阳循环,佛语云:生死轮回。皆由为易经:天地无极,物极必反。

行事让开发LoginClient

每当编制代码之前,我们应有先想想如何规划LoginClient仿佛。首先根据Single
responsibility
principle(责任单一原则),LoginClient根本承担用户登录的纱访问。需要提供一个接口,只要加用户称(username)和密码(password),用户就是能登录,由于自家是利用RAC来拍卖回来结果,所以是接口返回RACSignal对象。

  • 创立一个LoginClientkiwi文件,编写对应行为。

Create LoginClient 1.png

Create LoginClient 2.png

SPEC_BEGIN(LoginClientSpec)

describe(@"LoginClient", ^{

    context(@"when user input correct username and password", ^{
      __block RACSignal *loginSignal;

      beforeEach(^{
          NSString *username = @"liuyaozhu13hao@163.com";
          NSString *password = @"freedom13";
          loginSignal = [LoginClient loginWithUsername:username password:password];
      });

      it(@"should return login signal that can't be nil", ^{
          [[loginSignal shouldNot] beNil];
      });

      it(@"should login successfully", ^{
          __block NSString *accessToken = nil;

          [loginSignal subscribeNext:^(NSString *x) {
              accessToken = x;
              NSLog(@"accessToken = %@", accessToken);
          }error:^(NSError *error) {
              [[accessToken shouldNot] beNil];
          } completed:^{
              [[accessToken shouldNot] beNil];
          } ];
      });

    });
});

SPEC_END

基于三段式Given-When-Then叙,上面代码我们可以知道吧:在被定LoginClient对象,当用户输入是的用户称以及密码时,应该登录成功。
这时候,由于还尚未创建LoginClient好像,所以会无经过编译,创建LoginClient仿佛,并编辑它的伪实现,让LoginClientSpec.m由此编译。

LoginClient.h.png

LoginClient.m.png

运行测试,测试失败。

LoginClient Failed.png

  • 落实LoginClient,通过其测试

LoginClient.m .png

LoginClient Pass Test.png

  • 鉴于无冗余代码,无需重构

管这些用在生活中、哲学里、甚至是咱的活及,同样适用。产品怎么诞生,因为来要求,当然多私需求(其实需要远非真伪,只是看数据、看现象、以及社会变迁)不以谈论范围。产品人做产品极着重的凡啊,不是一个按钮一行文字的得失与否;也未在于一个效应,一个页面高低矮丑;更主要之凡成品稳定。很多出品或者你只是于中间接手,而不是同开始你不怕与,但立刻并不矛盾。你一味需要懂得产品现在以什么位置,将来要走向如何终点(目标),不要因此啊世界变化太快的弥天大谎来诈自己,变得是路途,不是好不容易点。贵阳凡是贵阳,北京是首都,中间不论多少湖海山川、多少公路铁轨,北京还还是北京,只是不同之口活动之行程或不同、用的家伙不同而已。而活呢是这般,目标是可以既定的,运营的法可无限制应变。

Model层

鉴于这次登陆请求服务端返回数据比较简单,只是获得access_token字段数据,所以无欲model来照和储存数据。不过当取多独Stories时,就会见使用及model来拍卖。

一个活一定、目标还非清楚的活,再是耗多少人力物力,多少资源以方也无自然生好之结果,当然这个吧非克绝对,大道五十,天衍四十九,而别谁也说不清。剑走偏锋的事不属常规状态,而我们谈谈的出品刚刚休以斯。有了强烈的出品稳定,才可能发不错的活求,而连下要举行的不是直投入研发与资源去研发、推广,而是先去验证要求。

Controller与ViewModel层

controller凡拍卖用户交互的输入,通常自己都见面以拍卖用户交互的逻辑、数据绑定和数量校验都授ViewModel来精简controller代码,同时最特别程度地复用业务逻辑的代码。
咱俩事先想起用户登陆时之步骤:1.
用户优先输入email和密码,只有email和密码可格式要求时才能够点击按钮。2.
用户成功登陆后,跳反到故事列表主页。
咱先分析一下安落实步骤1,
想要针对性email和密码进行验证,必须使监听它们简单独价的变迁,所以要对emailTextFieldpasswordTextField使用RAC展开数量绑定。

创建LoginViewControllerSpeckiwi文件,测试绑定行为代码如下:

SPEC_BEGIN(LoginViewControllerSpec)

describe(@"LoginViewController", ^{
    __block LoginViewController *controller;

    beforeEach(^{
        controller = [UIViewController loadViewControllerWithIdentifierForMainStoryboard:@"LoginViewController"];
        [controller view];
    });

    afterEach(^{
        controller = nil;
    });

    describe(@"Email Text Field", ^{
        context(@"when touch text field", ^{
            it(@"should not be nil", ^{
                [[controller.emailTextField shouldNot] beNil];
            });
        });

        context(@"when text field's text is hello", ^{
            it(@"shoud euqal view model's email property", ^{
                controller.emailTextField.text = @"hello";
                [controller.emailTextField sendActionsForControlEvents:UIControlEventEditingChanged];
                [[controller.viewModel.email should] equal:@"hello"];
            });
        });
    });

    describe(@"Password Text Field", ^{
        context(@"when touch text field", ^{
            it(@"should not be nil", ^{
                [[controller.passwordTextField shouldNot] beNil];
            });
        });

        context(@"when text field' text is hello", ^{
            it(@"should equal view model's password property", ^{
                controller.passwordTextField.text = @"hello";
                [controller.passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

                [[controller.viewModel.password should] equal:@"hello"];
            });
        });
    });
});

SPEC_END

这里发生少数只根本点,一个凡是由Storyboard中加载controller,否则不克收获emailTextField和password,如果采用手写UI代码就未待了。另一个即便是emailTextField或passwordTextField必须调用sendActionsForControlEvents:UIControlEventEditingChanged术,才会触发textField的text属性改变。

编译失败后,在LoginViewController.m编写- (void)bindViewModel方法通过测试

RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal;

落实竣工数据绑定行为后,接下去要多少校验,交给LoginViewModel来处理。创建LoginViewModelSpec.m文件,提供emailpassword属性给LoginViewModel,返回验证结果的RACSignal,测试证明行为代码如下:

SPEC_BEGIN(LoginViewModelSpec)

describe(@"LoginViewModel", ^{
    // Initialize
    __block LoginViewModel *viewModel;

    beforeEach(^{
        viewModel = [[LoginViewModel alloc] init];
    });

    afterEach(^{
        viewModel = nil;
    });

    context(@"when email and password is valid", ^{
        it(@"should get valid signal", ^{
            viewModel.email = @"liuyaozhu13hao@163.com";
            viewModel.password = @"123456";

            __block BOOL result;

            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) should] beYes];
            }];
        });
    });

    context(@"when email is valid, but password is invalid", ^{
        it(@"should get invalid signal", ^{
            viewModel.email = @"liuyaozhu13hao@163.com";
            viewModel.password = @"1";

            __block BOOL result;

            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) shouldNot] beYes];
            }];
        });
    });

    context(@"when password is valid, but email is invalid", ^{
        it(@"should get invalid signal", ^{
            viewModel.email = @"liuyaozhu";
            viewModel.password = @"123456";

            __block BOOL result;
            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) shouldNot] beYes];
            }];
        });
    });
});

SPEC_END

编译失败后(已经创造LoginViewModel类),添加- (RACSignal*)checkEmailPasswordSignal连实现认证数据,通过测试

- (RACSignal*)checkEmailPasswordSignal
{
    RACSignal* emailSignal = RACObserve(self, email);
    RACSignal* passwordSignal = RACObserve(self, password);

    return [RACSignal combineLatest:@[ emailSignal, passwordSignal ] reduce:^(NSString* email, NSString* password) {
        BOOL result = [email isValidEmail] && [password isValidPassword];

        return @(result);
    }];
}

末段索要以LoginViewModel创立属性也loginButtonCommandRACCommand来处理点击登陆按钮的相。在LoginViewControllerSpec.m测试loginButton.rac_command未克也空

describe(@"Login Button", ^{
      context(@"when load view", ^{
            it(@"should be not nil", ^{
                [[controller.loginButton shouldNot] beNil];
            });

            it(@"should have rac command that not be nil", ^{
                [[controller.loginButton.rac_command shouldNot] beNil];
            });
      });
 });

测试失败,在LoginViewController.m编写- (void)bindViewModel道以下代码有

self.loginButton.rac_command = self.viewModel.loginButtonCommand;

LoginViewModel.m缓初始化loginButtonCommand属性

#pragma mark - Lazy initialization
- (RACCommand*)loginButtonCommand
{
    if (!_loginButtonCommand) {
        _loginButtonCommand = [[RACCommand alloc] initWithEnabled:[self checkEmailPasswordSignal] signalBlock:^RACSignal * (id input) {
            self.active = YES;

            return [[LoginClient loginWithUsername:self.email password:self.password] doNext:^(NSString *token) {
                self.active = NO;
                // Save the token
                [LocalStore saveToken:token];
                // Dismiss view controller and fetch data, reload
                self.dismissBlock();
            }];
        }];
    }

    return _loginButtonCommand;
}

由此测试,完成登陆基本流程,至于登陆成功后如何回到故事列表页面,这里不详细介绍,各位好透过看工代码就算得以博得答案。

小白认为产品之稳定应该来三只样子:首先从商业利益出发,没有商业价值的出品到底不上真的制品;其次从用户角度出发,没有用户参与的制品未是好之产品;最后从类型角度出发,适应当下生产力与物资的出品才是适合自己之制品。

总结

近年一段时间都重新拘留有关敏捷开发的书本(用户故事跟快速方法,硝烟中的Scrum和XP,
浅析极限编程),对高速开发很感兴趣,但发现很少公司或者博客介绍如何履行敏捷开发iOS,所以当网上收集一些材料,发现发众多地道的实行(测试驱动开发,重构,持续集成测试,增量设计,增量计划)值得去念,通过投机对快开发中各种实践的知来再次写是Designer
News,这个Designer
News功能还从来不任何形成,希望各位看罢就首稿子尝试为这样方式来成功所有app。如果自身有点意见或履理解有无意,请各位多多指导。

打字打废了……。

推而广之阅读

  • ReactiveCocoa
    ReactiveCocoa –
    iOS开发之初框架
    ReactiveCocoa2实战
    ReactiveCocoa Essentials: Understanding and Using
    RACCommand
    Test with
    Reactivecocoa
  • Kiwi
    TDD的iOS开发初步与Kiwi使用入门
    Kiwi 使用上阶 Mock, Stub,
    参数捕获和异步测试
  • RESTful API
    理解RESTful架构
    RESTful API
    设计指南
    理解OAuth
    2.0
    SSL/TLS协议运行机制的概述
  • Moco
    Moco能集成测试,还能走开;能前端开发,还能效仿Web服务器!
  • 测试
    行事让开发
    XCTest
    测试实战
    依靠注入
    坏之测试
    换成测试: Mock, Stub
    和其它

本次分享至是就是完了,作为同一名叫产品新人,会发出过多思索不周到或是表达不当之地方,欢迎大家留言评论指或者加我微信:chengshinanhai261820,凌雪在是等候与您同探究、分享、等待和天子(卿)共勉**

末了愿与诸位同事一起再接再厉,共同进步,越走越远。我们负梦想,屌丝终将逆袭!