30 8月

苹果官方博客:从SDK详说Swift代码的改变

文 / Apple Inc.     原文链接:Apple Developer’s Blog

Xcode 6.3中,我们为Objective-C添加了新语言特性nullability注解。这个特性给Objective-C提供了表达API使用null和non-null统一的方式。相当于Swift语言中的Optional类型。Xcode 7继续为Objective-C与Swift之间更加自然地通信引入轻量级的泛型模型。泛型使得这两种语言能够可靠地融合并分享带有特定类型元素的集合。

这些特性对任何使用Swift和Objective-C这两种语言编程的人来说都比较有用。但应用程序开发人员每天使用的Objective-C代码占更大部分:这就是组成Apple SDK的那些框架。为了提升Swift和Objective-C的使用体验,全公司的范围内,我们在SDK的头文件中都提供了这样的信息。Xcode 7中,你会发现几乎所有常见框架都指定了其API的nullability注解,包括其集合类型的元素。这使得Swift代码的样子发生了变化。

变化前:

  1. class UIView : UIResponder {
  2.     init!(frame: CGRect)
  3.     var superview: UIView! { get }
  4.     var subviews: [AnyObject]! { get }
  5.     var window: UIWindow! { get }
  6.     // …
  7.     func isDescendantOfView(view: UIView!) -> Bool
  8.     func viewWithTag(tag: Int) -> UIView!
  9.     // …
  10.     var constraints: [AnyObject]! { get }
  11.     // …
  12. }

变化后:

  1. class UIView : UIResponder {
  2.     init(frame: CGRect)
  3.     var superview: UIView? { get }
  4.     var subviews: [UIView] { get }
  5.     var window: UIWindow? { get }
  6.     // …
  7.     func isDescendantOfView(view: UIView) -> Bool
  8.     func viewWithTag(tag: Int) -> UIView?
  9.     // …
  10.     var constraints: [NSLayoutConstraint] { get }
  11.     // …
  12. }

最后一个疑问是Xcode 7中将代码转换成Swift 2的工具,在Xcode的Edit菜单下,Convert > To Latest Swift Syntax。通过必要的编辑,该工具会将一个使用Swift 1.2编写的项目转换成合法的Swift 2.0代码。这些改变源于改进后的头文件信息。例如,正在重写的一个方法,其参数和结果类型更加精确,方法转换的过程中将会被更新与之匹配。

在今年的WWDC的Swift and Objective-C Interoperability视频14:30处,对Objective-C所作的改进做了详尽的描述。注意,该视频使用的是Xcode 6.3中的__nullable语法,并非更新的Xcode 7中用的_Nullable。了解更多nullability注解的信息,可以看看Nullability and Objective-C。了解Swift 2.0和Objective-C中轻量级泛型更多信息,可以看看Xcode 7 Release Notes

http://www.csdn.net/article/2015-08-17/2825473-swift-er-sdk

26 8月

探秘ES6: 类语法

来源:Mozilla Web开发者博客         文 / Eric Faust

原文链接:https://hacks.mozilla.org/2015/07/es6-in-depth-classes/

ES In Depth是一个系列,描述按照ECMAScript标准第6版加入到JavaScript语言中的新特性。简称为ES6

在领教了本系列文章前几篇的复杂程度后,我们现在得以有片刻的喘息。再没有闻所未闻的编码方式,使用生成器(generator)编码;再没有无所不能的代理对象(proxy object),为JavaScript语言内部算法实现提供了钩子函数;再没有新的数据结构,避免了用户自主开发的需要。相反,我们要说说与一个旧问题相关的语法和清理技法(idiom),那就是JavaScript中对象构造函数的创建。

问题

我们要说的是,创建面向对象设计原则中最典型的例子:Circle类。想象我们正在为Canvas库编写一个Circle类,除此之外,恐怕还需要知道如何去做以下几点:

  • 为给定的Canvas画一个Circle。
  • 记录所画Circle的个数。
  • 记录给定Circle的半径,如何给不变量(invariant)强行赋值。
  • 计算给定Circle的面积。

目前JS的惯用技法是先拿构造函数当作函数来创建,然后向函数添加任何想要添加的属性,再用一个对象替换构造函数中的prototype属性。该prototype对象包含构造函数最初所创建实例的所有属性。虽然这只是一个简单的例子,但敲出来以后,会是不少样板(boilerplate)代码:

  1. function Circle(radius) {
  2.     this.radius = radius;
  3.     Circle.circlesMade++;
  4. }
  5. Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ }
  6. Object.defineProperty(Circle, “circlesMade”, {
  7.     get: function() {
  8.         return !this._count ? 0 : this._count;
  9.     },
  10.     set: function(val) {
  11.         this._count = val;
  12.     }
  13. });
  14. Circle.prototype = {
  15.     area: function area() {
  16.         return Math.pow(this.radius, 2) * Math.PI;
  17.     }
  18. };
  19. Object.defineProperty(Circle.prototype, “radius”, {
  20.     get: function() {
  21.         return this._radius;
  22.     },
  23.     set: function(radius) {
  24.         if (!Number.isInteger(radius))
  25.             throw new Error(“Circle radius must be an integer.”);
  26.         this._radius = radius;
  27.     }
  28. });

这样的代码不仅冗长,而且不够直观。不是一下子就能理解函数是如何工作的,也不是很容易理解各个属性用什么方式绑定到所创建的实例对象的。即使这样的实现方式看起来比较复杂也不必着急。本文的主旨就是要展示一种更简单的编码方式,用来解决所有这些问题。

定义方法的语法

首次尝试去规范这个问题时,ES6给出了一种新的语法来为对象添加特殊属性。虽然很容易在上面的Circle.prototype添加area方法,但是对radius添加一对getter和setter让人感觉过于啰嗦。由于JS更加倾向于面向对象化的解决方法,所以人们变得关心如何用简洁的方式给属性添加访问器(accessor)。我们需要一种新的方式给对象添加“方法”,就像obj.prop = method那样被添加进去,而不需要用Object.defineProperty。大家希望能够轻松做到下面几件事:

  1. 给对象添加普通函数(normal function)。
  2. 给对象添加生成器函数(generator function)。
  3. 给对象添加普通访问器函数属性(accessor function property)。
  4. 给已创建的对象添加上述任何函数,好像使用方括号[]语法就能完成的样子。我们称之为计算属性名(computed property name)。

其中的一些事情之前是无法完成的。例如,之前没有办法给obj.prop定义getter或setter对其进行赋值,因此要添加新的语法功能。现在你就可以编写像下面这样的代码了。

  1. var obj = {
  2.     // Methods are now added without a function keyword, using the name of the
  3.     // property as the name of the function.
  4.     method(args) { … },
  5.     // To make a method that’s a generator instead, just add a ‘*’, as normal.
  6.     *genMethod(args) { … },
  7.     // Accessors can now go inline, with the help of |get| and |set|. You can
  8.     // just define the functions inline. No generators, though.
  9.     // Note that a getter installed this way must have no arguments
  10.     get propName() { … },
  11.     // Note that a setter installed this way must have exactly one argument
  12.     set propName(arg) { … },
  13.     // To handle case (4) above, [] syntax is now allowed anywhere a name would
  14.     // have gone! This can use symbols, call functions, concatenate strings, or
  15.     // any other expression that evaluates to a property id. Though I’ve shown
  16.     // it here as a method, this syntax also works for accessors or generators.
  17.     [functionThatReturnsPropertyName()] (args) { … }
  18. };

我们可以使用新的语法重写上面的代码段:

  1. function Circle(radius) {
  2.     this.radius = radius;
  3.     Circle.circlesMade++;
  4. }
  5. Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ }
  6. Object.defineProperty(Circle, “circlesMade”, {
  7.     get: function() {
  8.         return !this._count ? 0 : this._count;
  9.     },
  10.     set: function(val) {
  11.         this._count = val;
  12.     }
  13. });
  14. Circle.prototype = {
  15.     area() {
  16.         return Math.pow(this.radius, 2) * Math.PI;
  17.     },
  18.     get radius() {
  19.         return this._radius;
  20.     },
  21.     set radius(radius) {
  22.         if (!Number.isInteger(radius))
  23.             throw new Error(“Circle radius must be an integer.”);
  24.         this._radius = radius;
  25.     }
  26. };

严格地说,这段代码与其上面那段并不完全相同。方法定义中所使用的对象字面量(object literal)被设置成了configurable和enumerable。而在上一个代码段中的访问器则会是non-configurable和non-enumerable的。在实践中,这点很少被注意到,为了简洁,我决定忽略掉上面这两种描述。

不过,应该变得更好了对吗?很遗憾,即使有了这样新的定义方法的语法,我们对Circle的定义仍然无法做太多的事情。因为我们还没有定义函数。没有办法将属性绑定到你尚在定义的函数上去。

定义类的语法

虽然这样更好一些,但仍然无法令人满意,人们想要一种更简洁的JavaScript面向对象设计解决方案。他们说其他编程语言为了解决面向对象设计而拥有一种结构,这种结构被称为

很公平,那么就来添加类。

我们需要一个系统,允许将方法添加到已命名的构造函数当中,并还能添加系统的.prototype当中。这样这些方法就会出现在类的结构化实例当中。由于有新奇的语法可以定义方法,我们应该用一下。然后,只需要区分所添加的方法属于类的所有实例,还是专属于某个给定实例。在C++和Java语言中,解决这一问题的关键字是static。用在这里看起来也不错,用一下吧!

现在将众多方法其中的一个指定为函数,它将被称为构造函数。在C++和Java语言中,构造函数的名称要与类名一致,并且没有返回类型。由于JS没有返回类型,所以为了向后兼容,的确需要一个.constructor 属性。我们称这个方法为构造函数。

综上所述,可以重写Circle类了:

  1. class Circle {
  2.     constructor(radius) {
  3.         this.radius = radius;
  4.         Circle.circlesMade++;
  5.     };
  6.     static draw(circle, canvas) {
  7.         // Canvas drawing code
  8.     };
  9.     static get circlesMade() {
  10.         return !this._count ? 0 : this._count;
  11.     };
  12.     static set circlesMade(val) {
  13.         this._count = val;
  14.     };
  15.     area() {
  16.         return Math.pow(this.radius, 2) * Math.PI;
  17.     };
  18.     get radius() {
  19.         return this._radius;
  20.     };
  21.     set radius(radius) {
  22.         if (!Number.isInteger(radius))
  23.             throw new Error(“Circle radius must be an integer.”);
  24.         this._radius = radius;
  25.     };
  26. }

哇!我们不仅可以把与Circle相关的一切组织在一起,而且一切看起来如此地整洁。这绝对比一开始好多了。

即便如此,有些人可能还会有问题,找出极端的例子。我就试着预测一下,并解决其中的一些问题。

  • Q:分号用来做什么?A:为了“让一切看起来更像传统的类”,我们决定使用更为传统的分隔符。不喜欢它吗?这是可选的,分隔符并不是必须的。
  • Q:如果我不想要构造函数,但仍然想给已创建定对象添加方法该怎么办?A:很好!constructor方法完全是可选的。如果不这样做,默认情况就像已经敲了constructor() {}。
  • Q:“constructor”可以是生成器吗?A:不可以!不使用普通方法(normal method)添加constructor会导致TypeError,包括生成器和访问器。
  • Q:可以使用计算属性名定义constructor吗?A:遗憾的是不可以。这将非常难以探测。因此我们就不试了。如果使用计算属性名定义方法的话,最终方法会被命名为“constructor”,仍会得到一个名为 constructor的方法,而不是类的构造函数。
  • Q:如果改变Circle的值会怎样?是否会导致出现新的Circle,并且出错呢?A:不会的!就像函数表达式,类内部绑定了它们的名称。外部力量无法改变这个绑定。因此,在封闭范围内不管怎样设置Circle变量,构造函数中的Circle.circlesMade++都会按预期工作。
  • Q:好吧,但是我可以直接传入对象字面量作为函数的参数。使用新语法定义的类看起来行不通了。A:很幸运,ES6还增加类表达式!可以对其命名或者匿名。除了在声明它们的范围内不会创建变量,其行为与上面描述的函数完全相同。
  • Q:上面的这些把戏如果是可枚举的,或者有什么其他属性会怎么样?A:这样做是为了可以给对象配置方法,但是当你对对象的属性进行枚举的时候,仅得到了已经添加进对象的数据属性。因为这是合理的,所以类里所配置的方法是configurable的,但不是enumerable的。
  • Q:喂,等等……实例变量在哪里?static常量呢?A:好问题!在ES6中,类的定义目前不存在实例变量和静态常量。不过好消息是,连同参与其他规范的过程中,我都强有力地支持在类的语法中设有static和const值。事实上,这已经出现在了规范相关的会议上。我想可以期待以后出现更多与此有关的讨论。
  • Q:好吧,即便如此,这些也都是极好的!我可以使用它们了吗?A:不完全可以。存在那些可选用的polyfill(特别是Babel),你可以试着使用它们。遗憾的是,在被主流浏览器原生地实现以前,还需要一些时间。我们这里讨论的一切都在Nightly版的Firefox浏览器实现过。Edge和Chrome浏览器实现了,但是默认情况下并未启用。遗憾之处是Safari浏览器还没实现这些新特性。
  • Q:JavaC++都使用子类和super关键字,但这里并没提到,JS有相关概念吗?A:有的!但这完全是一个值得另外成文讨论的事情。回头再看我们今后有关使用子类的更新,将讨论更多有关JavaScript类的威力。

没有Jason OrendorffJeff Walden大量认真负责的代码审查和指导,我不可能实现文中的类代码。

下周,Jason Orendorff将结束为期一周的假期,开始撰写let和const主题。

http://www.csdn.net/article/2015-08-14/2825464-es6-in-depth-classes

17 8月

iOS 9 Programming Fundamentals with Swift: Swift, Xcode, and Cocoa Basics 1st Edition

book1

内容简介:

通过坚实地把握iOS 9 开发的基础知识跻身于这一领域,内容包括Xcode 7,Cocoa Touch框架和苹果的Swift编程语言。通过最新的指南学习Swift语言中面向对象的概念,了解如何使用苹果开发工具,领会Cocoa如何提供iOS应用程序所需的底层功能。

  • 探索Swift面向对象的概念:变量和函数、作用域和命名空间、对象类型和实例
  • 熟悉Swift内置类型,如数字、字符串、区间、元组、可选类型、数组和字典
  • 学习如何声明、实例化和定制Swift的对象类型——枚举、结构和类
  • 了解Swift强大的功能特性,比如协议和泛型
  • 纵览Xcode项目的生命周期,从概念到产品
  • 使用nib及其编辑器和Interface Builder创建应用程序接口
  • 理解Cocoa的事件驱动模型及其主要的设计模式和特性
  • 弄清Swift如何与Cocoa的C API和Objective-C API通讯

作者简介:

Matt Neuburg从1968年起就使用电脑编程。14岁时作为高中俱乐部不折不扣的地下成员,每周都会在银行使用原始的电传打字机来为PDP-10电脑做分时处理。他偶尔也使用普林斯顿大学的IBM-360/67大型机,直到有一天不再使用穿孔卡片,他才沮丧地放弃了IBM-360/67。曾在美国斯沃斯摩尔学院(Swarthmore College)主修希腊语,1981年,从康奈尔大学(Cornell University)获得了博士学位,在大型主机上完成了他的博士论文(关于希腊悲剧之父Aeschylus)。他开始在许多著名的高等院校讲授古典语言、文学和文化,而大多数院校为了发表大量不大可能令人感兴趣的学术文章,对他的学识视而不见。与此同时,他获得了一台Apple IIc,再次无可救药地为之着迷。并于1990年换成了麦金塔电脑。他编写了一些教育软件和免费的工具软件,成为了在线杂志TidBITS早期的定期撰稿人。1995年,他离开了学术界去MacTech Magazine做编辑。他还是一位前沿的作者,著有The Definitive Guide和REALbasic: The Definitive Guide,由O’Reilly & Associates出版发行。

13 8月

如何使用iOS SDK获取和解析JSON数据

文 / ziad tamim:资深iOS开发人员,创业公司移动战略咨询顾问。自App Store上线以来,编写超过80款应用程序。目前,他经营着一家叫做TAMIN LAB的移动开发工作室。

原文链接:http://www.appcoda.com/fetch-parse-json-ios-programming-tutorial/

编辑注:本周,ziad tamim将向你展示如何获取和解析JSON数据。在本文中,我们会关注为iOS应用程序添加对JSON的支持,通过使用Meetup API来展示如何创建一个简单应用程序。这篇文章也许比我们之前介绍的其他文章要复杂一点儿,这正是你所需要的有关JSON和设计模式的基础知识。

JSON编程指南由此开始……

首先,什么是JSON?JSON(short for JavaScript Object Notation)是一个基于文本的,轻量级的,便于数据排序和交换的一种机制。通常用于在客户端/服务器模式的应用程序中表示结构化的数据,并进行数据的交互,作为XML格式数据的替代物。我们日常中所使用的很多服务都适用了基于JSON的API。大多数iOS应用程序都使用JSON格式的网络服务将数据发送到它们的后端web服务。包括Twitter,Facebook和Flick在内都是这样做的。

json-intro-tutorial

{
    "title": "The Amazing Spider-man",
    "release_date": "03/07/2012",
    "director": "Marc Webb",
    "cast": [
        {
            "name": "Andrew Garfield",
            "character": "Peter Parker"
        },
        {
            "name": "Emma Stone",
            "character": "Gwen Stacy"
        },
        {
            "name": "Rhys Ifans",
            "character": "Dr. Curt Connors"
        }
    ]
}

不难发现,JSON数据比XML更具可读性,更容易解析。如果你对JSON一无所知,可以看看这个JSON Guide

iOS5发布以来,iOS SDK更容易获取和分析JSON数据。在这篇文章中,我们将展示如何使用内置API查询来自Meetup基于JSON的API,并恰当地处理返回值。

我们的应用程序

编写代码以前,先来看看要构建什么样的应用程序。

在本文中,我们要创建一个叫做BrowserMeetup的简单应用程序,将会使用Meetup的公共API。从来没听说过Meetup吗,它是全球最大的本地聚会网络平台。你可以免费使用它来组织在当地的聚会,还可以从上千个已经组织好的聚会中找到其中的一个。像其他社交网站一样,Meetup为你的服务端访问其数据提供了开放API。

BrowseMeetup应用程序会使用Meetup的web服务来寻找附近的聚会。应用程序会获得当前的地理位置并自动加载附近的聚会。

Screen-Shot-2013-08-24-at-11.32.09-PM-e1377937354961

注:该应用程序使用Core Location框架,如果你不了解该框架,可以参考How To Get the User Location in iPhone App

动手开发应用程序

可以开始构建和管理BrowseMeetup应用程序了。启动Xcode并创建一个主-从视图类型的应用程序(Master-Detail Application)。项目选项设置中,你要选择Storyboard和ARC(Automatic Reference Counting)选项。该应用仅提供iPhone视图,因此在设备选项中选择iPhone并保存项目。删除掉storyboard中的DetailsViewController,像下面这样设计用户界面:

Screen-Shot-2013-08-25-at-1.24.08-AM-e1377871383429

本文着重介绍JSON的获取和解析。所以为了节省时间就不去建立项目了,你可以直接下载项目模版。模版已经事先构建好了用户接口并实现了Core Location功能。

提示:如果不懂怎么用table view,想知道导航条是如何工作的,可以参考我们免费的iOS资源指南

使用Meetup的API

使用Meetup API前,请在其上先创建一个账号。打开APIs Doc,点击“Request to join this Meetup group”按钮。然后填写必要的信息,点击“Sign Up”。一步一步进行直至完成。

Screen-Shot-2013-08-26-at-10.06.27-PM

我们将使用其中的一个API(https://api.meetup.com/2/groups)来获取在某个地点举行的Meetup聚会信息。调用过程允许开发者使用经纬度来定位。可以使用API console来测试调用。

这是一段请求后简单的JSON响应信息(https://api.meetup.com/2/groups?&sign=true&lat=51.509980&lon=-0.133700&page=1):

{
"results": [
{
"lon": -0.10000000149011612,
"visibility": "public",
"organizer": {
"name": "William Brown",
"member_id": 3817216
},
"link": "http://www.meetup.com/french-32/",
"state": "17",
"join_mode": "approval",
"who": "LFM members",
"country": "GB",
"city": "London",
"id": 63974,
"category": {
"id": 16,
"name": "language/ethnic identity",
"shortname": "language"
},
"topics": [
{
"id": 185,
"urlkey": "french",
"name": "French Language"
},
{
"id": 264,
"urlkey": "friends",
"name": "Friends"
},
{
"id": 3304,
"urlkey": "expatfrench",
"name": "Expat French"
}
],
"timezone": "Europe/London",
"group_photo": {
"photo_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/600_929867.jpeg",
"highres_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/highres_929867.jpeg",
"thumb_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/thumb_929867.jpeg",
"photo_id": 929867
},
"created": 1034097734000,
"description": "<p>The London French Meetup is the biggest group of French speakers of all ages and nationalities in London. We hold regular events to meet up, talk in French and share interests in French culture whilst having a good time.</p>
<p>We have two main events per month where we have the whole of the upstairs of a pub.</p>
<p>In addition, we organise other regular events such as outings to: restaurants, trendy bars, french films, live music, sports related activities, outdoor events and more...</p>
<p>The organising team is made of volunteers from different nationalities and ages. Our members are made up of: 1/3 French nationals, 1/3 British nationals and 1/3 other nationalities and francophone countries. If you have any ideas or suggestions for events or would like to help please let us know.</p>
<p>A bientôt.</p>
<p>LFM Team.</p>",
"name": "London French Meetup",
"rating": 4.37,
"urlname": "french-32",
"lat": 51.52000045776367,
"members": 4889
}
],
"meta": {
"lon": -0.1337,
"count": 1,
"signed_url": "http://api.meetup.com/2/groups?radius=25.0&order=id&desc=false&offset=0&format=json&lat=51.50998&page=1&fields=&lon=-0.1337&sig_id=109020062&sig=4532ed8f987f940748ebfba0f483a26f756dcba3",
"link": "http://www.meetup.com/2/groups",
"next": "http://www.meetup.com/2/groups?radius=25.0&order=id&format=json&lat=51.50998&page=1&desc=false&offset=1&fields=&sign=true&lon=-0.1337",
"total_count": 4501,
"url": "http://www.meetup.com/2/groups?radius=25.0&order=id&format=json&lat=51.50998&page=1&desc=false&offset=0&fields=&sign=true&lon=-0.1337",
"id": "",
"title": "Meetup Groups v2",
"updated": 1377876449000,
"description": """",
"method": "Groups",
"lat": 51.50998
}
}

应用程序设计及工作原理

如前所述,Meetup API提供了请求某方位聚会信息的方法。响应数据将用JSON格式发送。我们需要一个对象,可以从已编码的数据中检索数据,并从中构建我们的域对象(domain object )。下面是关于应用程序设计的介绍,展示如何构建类,以及这些类如何获得meetup聚会信息:

Screen-Shot-2013-08-16-at-10.01.27-PM-e1377877588261

对于有些人来说,也许有点儿复杂。我来带你过一遍。创建MeetupManager是为了请求的Meetup聚会所获得的方位,就像门面一样。如果你没有听说过设计模式中的门面模式(Facade Pattern)的话,可以把它想象成其他类的协调者。门面模式试图为界面试图控制器提供一个简化的接口,并屏蔽掉底层的实现。

MeetupCommunicator用来与Meetup API进行通讯。一旦Meetup返回JSON格式响应,我们就将该响应传递给GroupBuilder,GroupBuilder构建了Group对象。

MasterViewController使用Core Location得到当前的方位,并通知MeetupManager得到该方位的Meetup聚会信息。MeetupManager协调其他的类来检索得到的那些聚会信息。一旦检索成功,MeetupManager会通过代理与MasterViewController通信,并将检索到的聚会信息传递过去。然后MasterViewController将传递过来的聚会信息在table view中展现出来。

创建JSON数据模型

接下来我们将要实现实体类(model层)。Group类表示BrowseMeetup应用中聚会的信息,用来存储从Meetup返回的聚会信息。下面是一个JSON响应中的Group对象示例:

{
     lon: -71.12999725341797,
     visibility: "public",
     organizer: {
          name: "Emma",
          member_id: 2161382
     },
     link: "http://www.meetup.com/bloggers/",
     state: "MA",
     join_mode: "closed",
     who: "Bloggers",
     country: "US",
     city: "Cambridge",
     id: 21458,
    category: {
          id: 34,
          name: "tech",
          shortname: "tech"
     },
     topics: [
          {
               id: 198,
               urlkey: "blog",
               name: "Blog"
          },
          {
               id: 772,
               urlkey: "writers",
               name: "Writers"
          }
     ],
     timezone: "US/Eastern",
     group_photo: {
     photo_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/600_25749.jpeg",
     highres_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/highres_25749.jpeg",
     thumb_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/thumb_25749.jpeg",
     photo_id: 25749
     },
     created: 1034097731000,
     description: "This is a group for people that are interested in blogging and meeting others who are interested in blogging. Topics discussed range from blog content to blog software. All interest levels are welcome.",
     name: "The Greater Boston Area Weblogger Meetup Group",
     rating: 3.33,
     urlname: "bloggers",
     lat: 42.38999938964844,
     members: 119
}

上面的JSON响应代表单个Meetup Group。我们不会使用所有返回来的数据。而是简单地使用“name”,“description”,“who”,“country”和“city”字段。这些字段对我们来说足够了。现在要使用Objective-C类模版来创建一个新文件,命名为Group,将它设置为NSObject的子类,并在头文件中添加如下代码:

@interface Group : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@property (strong, nonatomic) NSString *who;
@property (strong, nonatomic) NSString *country;
@property (strong, nonatomic) NSString *city;
@end

我们将使用这些属性信息,在应用程序中实现我们之前描述的最终结果。

使用Meetup API获取JSON 数据

首先,使用Objective-C的协议模版创建一个文件,并命名为MeetupCommunicatorDelegate,在文件中填充如下代码:

@protocol MeetupCommunicatorDelegate 
- (void)receivedGroupsJSON:(NSData *)objectNotation;
- (void)fetchingGroupsFailedWithError:(NSError *)error;
@end

MeetupCommunicator类负责Meetup API在获取JSON数据过程中的通讯。它依赖于MeetupCommunicatorDelegate来处理解析JSON数据的任务。通讯类本身并不知道JSON数据是如何被处理的。通讯类只关心为Meetup API和获取JSON原始结果数据的过程创建连接。

代理被创建以后,创建另一个类文件,并命名为MeetupCommunicator。打开头文件,键入如下代码:

#import <CoreLocation/CoreLocation.h>
@protocol MeetupCommunicatorDelegate;
@interface MeetupCommunicator : NSObject
@property (weak, nonatomic) id<MeetupCommunicatorDelegate> delegate;
- (void)searchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate;
@end

我们创建了一个属性,用来跟踪记录通讯代理。然后定义了一个方法,用来查找特定方位的聚会信息。接下来,打开MeetupCommunicator.m文件,替换成如下代码:

#import "MeetupCommunicator.h"
#import "MeetupCommunicatorDelegate.h"
#define API_KEY @"1f5718c16a7fb3a5452f45193232"
#define PAGE_COUNT 20
@implementation MeetupCommunicator
- (void)searchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate
{
    NSString *urlAsString = [NSString stringWithFormat:@"https://api.meetup.com/2/groups?lat=%f&lon=%f&page=%d&key=%@", coordinate.latitude, coordinate.longitude, PAGE_COUNT, API_KEY];
    NSURL *url = [[NSURL alloc] initWithString:urlAsString];
    NSLog(@"%@", urlAsString);
    [NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (error) {
            [self.delegate fetchingGroupsFailedWithError:error];
        } else {
            [self.delegate receivedGroupsJSON:data];
        }
    }];
}
@end

Meetup API需要一个key来工作。如果你注册了账号,需要从API Key页面得到key的话,那么简单地点击一下文本框旁边的?图标就可以得到key。把里面的文本拷贝出来,用自己的API key将API_KEY宏的值替换掉。

正如上面提到的,我们使用下面的Meetup API来查找指定方位的聚会信息。API接受经纬度格式的方位信息。下面就是Meetup URL连接的示例:

https://api.meetup.com/2/groups?lat=51.509980&lon=-0.133700&page=20&key=1f5718c16a7fb3a5452f45193232

在方法的实现中,我们首先使用特定的经纬度,以及聚会的数量和API key来构建API URL。为了在UI部分不卡住,我们通过使用NSURLConnection中的“sendAsynchronousRequest:”方法为URL请求异步加载数据。最终检索到JSON数据,将它传递给代理进一步进行处理。

解析JSON数据并绑定Group对象

MeetupManager收到JSON格式的数据时,使用GroupBuilder的类方法将数据转换为Group对象。使用Objective-C类模版创建一个新文件,命名为GroupBuilder。打开头文件,粘贴如下代码:

#import <Foundation/Foundation.h>
@interface GroupBuilder : NSObject
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error;
@end

接下来,打开“GroupBuilder.m”文件,实现对应的方法:

#import "GroupBuilder.h"
#import "Group.h"
@implementation GroupBuilder
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
    NSError *localError = nil;
    NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
    if (localError != nil) {
        *error = localError;
        return nil;
    }
    NSMutableArray *groups = [[NSMutableArray alloc] init];
    NSArray *results = [parsedObject valueForKey:@"results"];
    NSLog(@"Count %d", results.count);
    for (NSDictionary *groupDic in results) {
        Group *group = [[Group alloc] init];
        for (NSString *key in groupDic) {
            if ([group respondsToSelector:NSSelectorFromString(key)]) {
                [group setValue:[groupDic valueForKey:key] forKey:key];
            }
        }
        [groups addObject:group];
    }
    return groups;
}
@end

方法“groupsFromJSON”被设计为将原始的JSON数据转换成Group对象的数组。iOS 5发布以来,iOS SDK中名为NSJSONSerialization的类用来解析JSON数据。开发人员使用该类可以将JSON数据转换成Foundation框架中的对象,也可以将Foundation框架中的对象转回JSON数据。

当使用NSJSONSerialization读取JSON数据时,所有的键列表(keyed list)都被自动转换成NSDictionary对象。数组则会被转换为NSArray实例。键列表中任何带有已命名项名称的字符串都会被转换为NSString,而纯数字字符串会被转换为NSNumber对象。最后,任何为空的值都会被用NSNull表示。

在本文前面部分向你展示的响应示例中,Meetup API返回的JSON响应包括两部分,结果数据和元(meta)信息。而我们只需要“结果”部分。代码相当直观明了,我们循环遍历了所有的结果,查看了其中的每一个NSDictionary。然后创建了Group对象,对其填充了必要信息,然后将Group对象添加到可变数组当中。

用MeetupManager把各部分集中起来

你现在应该明白JSON是如何工作的了,如何解析数据并将数据转换成对象。接下来,我们要实现MeetupManager,作为底层类的协调者来使用。

首先,使用Objective-C协议模版创建一个文件,命名为MeetupManagerDelegate。在MeetupManagerDelegate.h文件中添加如下代码:

@protocol MeetupManagerDelegate
- (void)didReceiveGroups:(NSArray *)groups;
- (void)fetchingGroupsFailedWithError:(NSError *)error;
@end

这个代理声明了两个方法,当group对象需要的时候会被MeetupManager调用。当从Meetup响应中检索到的group列表被解析时,调用第一个方法。而当发生错误的时候,第二个方法会被调用。MeetupManagerDelegate将会被MasterViewController实现,我们会在随后内容中讲解。

接下来,使用Objective-C类模版创建一个新文件,命名为MeetupManager。打开头文件,添加如下代码:

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MeetupManagerDelegate.h"
#import "MeetupCommunicatorDelegate.h"
@class MeetupCommunicator;
@interface MeetupManager : NSObject<MeetupCommunicatorDelegate>
@property (strong, nonatomic) MeetupCommunicator *communicator;
@property (weak, nonatomic) id<MeetupManagerDelegate> delegate;
- (void)fetchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate;
@end

前面说过,MeetupManager扮演着门面的角色。应用程序的控制器可与实体类(Group)一起工作,不需要知道任何关于网络连接的细节,也不需要知道JSON如何获取和解析,以及group对象如何创建。控制器需要知道的仅仅是使用“fetchGroupsAtCoordinate:”方法获取Meetup聚会的信息。

我们设置一个属性来记录稍后要用到的communicator实例,还有用来追踪MeetupManagerDelegate的属性。“fetchGroupsAtCoordinate:”方法将被控制器用来获取group。

下一步,打开MeetupManager.m文件,替换成下面的代码:

#import "GroupBuilder.h"
#import "MeetupCommunicator.h"
@implementation MeetupManager
- (void)fetchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate
{
    [self.communicator searchGroupsAtCoordinate:coordinate];
}
#pragma mark - MeetupCommunicatorDelegate
- (void)receivedGroupsJSON:(NSData *)objectNotation
{
    NSError *error = nil;
    NSArray *groups = [GroupBuilder groupsFromJSON:objectNotation error:&error];
    if (error != nil) {
        [self.delegate fetchingGroupsFailedWithError:error];
    } else {
        [self.delegate didReceiveGroups:groups];
    }
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
    [self.delegate fetchingGroupsFailedWithError:error];
}

在这里实现了fetchGroupsAtCoordinate:coordinate方法,通过使用communicator的searchGroupsAtCoordinate:coordinate方法来获取group。还实现了MeetupCommunicatorDelegate下面的那些方法,用来处理communicator检索到的JSON格式的结果。

协议receivedGroupsJSON:objectNotation第一个方法中代码使用GroupBuilder的类方法将JSON数据填入Group对象,然后将Group对象通知给方法的代理。如果在处理请求的过程中出现任何问题,我们就调用代理的另一个方法(fetchingGroupsFailedWithError)来通知控制器,让控制器知道出现了问题。

显示Group列表

首要任务就是要把这些类都拿来放到一起让MeetupManager能够工作。打开MasterViewController.m文件,导入所需的头文件,更新接口,并且声明MeetupManager实例:

#import "Group.h"
#import "MeetupManager.h"
#import "MeetupCommunicator.h"
@interface MasterViewController () <MeetupManagerDelegate> {
    NSArray *_groups;
    MeetupManager *_manager;
}

随后我们就要实现在MeetupManagerDelegate中所定义的方法。但首先我们先在viewDidLoad方法中要实例化MeetupManager:

- (void)viewDidLoad
{
    [super viewDidLoad];
    _manager = [[MeetupManager alloc] init];
    _manager.communicator = [[MeetupCommunicator alloc] init];
    _manager.communicator.delegate = _manager;
    _manager.delegate = self;
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(startFetchingGroups:)
                                                 name:@"kCLAuthorizationStatusAuthorized"
                                               object:nil];
}

正在实例化一个新的manager,然后用一个新的实例给它的communicator属性赋值,最后让当前的视图控制器追踪一切变化。当警告弹出告诉用户使用定位服务开始,就调用startFetchingGroups:方法从服务端获取group,这里的观察者(observer)会捕捉一切用户的响应。

接下来,打开“MasterViewController.m”,键入如下代码开始获取信息:

- (void)startFetchingGroups:(NSNotification *)notification
{
    [_manager fetchGroupsAtCoordinate:self.locationManager.location.coordinate];
}

由于视图控制器应该遵循MeetupManagerDelegate协议,所以要实现其中的方法:

- (void)didReceiveGroups:(NSArray *)groups
{
    _groups = groups;
    [self.tableView reloadData];
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
    NSLog(@"Error %@; %@", error, [error localizedDescription]);
}

当从Meetup服务端返回的实例中获得了Meetup聚会信息,将调用“didReceiveGroups:”方法。我们这里要做的是简单地在table view里面显示新数据。

最后,构建table view的相关方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _groups.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    Group *group = _groups[indexPath.row];
    [cell.nameLabel setText:group.name];
    [cell.whoLabel setText:group.who];
    [cell.locationLabel setText:[NSString stringWithFormat:@"%@, %@", group.city, group.country]];
    [cell.descriptionLabel setText:group.description];
    return cell;
}

上段代码非常直观,我们从数组中检索那些group,然后将group的信息填入表格中。

编译和运行

现在,可以测试这个应用程序了。在模拟器中编译并运行(如果注册了iOS开发者计划,可以使用真实的iOS设备)。将模拟器的位置设置为英国伦敦,你就会得到像下面截屏中的Meetup聚会信息了。

1439437273757_757

提示:如果你不了解iPhone模拟器中的方位测试,可以看看Core Location tutorial

总结

在本文中,我们通过采用与JSON数据相配的设计模式,讲解了编程的很多方面,你应该具有了获取和解析JSON数据的实践经验。

如果你对设计模式是生手,这篇文章可能稍许复杂。但这不应该难住你而放弃在应用程序中使用JSON。iOS的SDK让这一过程变得容易了。总之就是,简简单单地创建一个URL连接,通过内置的NSJSONSerialization类获取和解析JSON数据。

在网上可以找到各种各样的免费API(比如KivaTMDb)。当作练习,尝试使用另一个API再开发一款应用程序。也就是说,你可以使用Kiva API开发一款显示近期筹贷款列表简单应用程序。

作为参考,可以在这里下载全部Xcode项目代码

09 8月

Stripe使用指南:Swift开发中的信用卡支付

文 / Ziad Tamim:资深iOS开发人员,创业公司移动战略咨询顾问。自App Store上线以来,编写超过80款应用程序。目前,他经营着一家叫做TAMIN LAB的移动开发工作室。

原文链接:http://www.appcoda.com/ios-stripe-payment-integration/

在这篇文章中,我们来谈谈Stripe的集成。Stripe提供了程序库来接受在线及移动应用支付行为,是功能最为强大的程序库之一。如果你计划在iOS应用中出售产品,或者在寻找一种支付解决方案,那么Stripe应该是你的首选。

很多iOS开发人员问我,为什么要选择Stripe而不是应用内购买(In-App Purchase)。根据苹果公司的规定,出售数字化内容需要使用应用内购买,像电子游戏附带的游戏级别,应用给予用户的虚拟物品。对于像衣服这样的实物,则允许使用像Stripe 这样的其他支付方案。所以,在这篇文章中,我会简要介绍Stripe,并解释它是如何工作的,告诉你如何使用Stripe的API构建一个示例,用来接受信用卡的支付行为。
stripe-integration-tutorial

Stripe为何物?

Stripe为开发人员构建,提供友好的API,使商家能够立即接受并管理移动支付行为。而不必为开设商业账户,设立信用卡网关而费心费力。有了Stripe,你可以轻松地实现应用的信用卡支付功能,甚至是重复付款。

Stripe意在使在线/移动交易唾手可得。你很快就会看到,在方便易用的API顶端,Stripe甚至精简了注册流程,集成过程就是这么简单!这就是该公司为什么能够如此迅速地扩张,名列2015十大创业公司行列的原因。

我们的应用程序

为了避免从头构建应用浪费时间,把注意力集中在学习Stripe上,我创建了项目模版。首先,下载项目并运行一下。

在本文中,我们将会创建一个叫做“Donate”的示例,演示如何使用Stripe接受信用卡付款。

iOS-Simulator-Screen-Shot-Jun-9-2015-5.35.27-PM-576x1024

编辑注:此应用为示例程序,根据苹果公司的App Store Review Guidelines,如果应用带有捐款功能,应用本身应当免费,必须通过网站的Safari页面或SMS服务完成公益款项的筹集。

应用的业务周期及Stripe的工作原理

深入实现之前,我们先看看应用如何与Stripe通信,如何处理事务。简而言之,用户发送支付请求的时候,应用如何工作:

1. 应用首先收集支付信息,包括电子邮件、信用卡号码、有效期、CVC(Card Validation Code)和支付表单上的支付金额。然后通过调用Stripe的API将这些信息发送给自己的服务端。

2. 如果支付信息有效,那么Stripe就会返回一个令牌(token)。此时,信用卡还没有被扣费。随后你会使用令牌执行实际的交易。

3. 现在应用获得了令牌。下一步是将其发送到本地服务器来完成扣费的过程。出于安全原因,Stripe需要通过服务器来进行扣费,而不是移动设备本身。不过不用担心,Stripe为我们提供了服务代码,可以轻松完成扣费过程。我们会在后面的小节讨论。

4. 你的本地服务器发送令牌到Stripe来执行实际的扣费操作。无论交易成功与否,Stripe都会返回结果。

5. 最后,本地服务器将有关交易结果的响应返回给应用。

stripe-flow

注:返回的响应是JSON格式的,所以如果不熟悉JSON解析的话,可以查看这个教程

创建一个Stripe测试帐号

创建一个Stripe测试帐号非常简单,可以在几秒钟内完成。Stripe可以简化整个注册过程,注册一个帐号不需要填写任何信息。我真的很喜欢这种精简的流程。如果你正在为客户开发应用,或者仅打算探索一下Stripe的API,这样的流程再好不过了。

第一步是去Stripe的网站,点击sign up按钮:

stripe-signup

你将被引导到注册页面创建一个新的帐号,在这里可以填写个人信息。但正如我之前提到的,Stripe精简了注册流程,可以在这里简单地单击“skip this step”继续。你将能够通过一个以测试为目的的临时帐号访问Stripe的Dashboard页面。

Screen-Shot-2015-06-09-at-9.05.20-PM-e1433890510184

一旦完成这个过程,Stripe的Dashboard页面就会呈现在你面前:

stripe-dashboard

接下来,点击菜单中的“Your Account”,点击“Account Settings”,然后点击“API Keys”选项卡,会显示用于应用开发的Key。

Screen-Shot-2015-06-10-at-12.05.31-AM

这就是Stripe的配置过程。从现在开始,你就可以使用Stripe的API来测试交易了。以后还可以回到Dashboard页面检查交易是否被成功处理了。

使用CocoaPods添加Stripe库

在Xcode工程使用Stripe之前,我们必须将库引入作为一个依赖项。有几个方法可以做到这一点。在示例中,我们将使用CocoaPods,它是一个著名的Swift项目依赖管理工具。就像这个示例,很多项目都依赖于第三方库工作。CocoaPods是一种工具,有助于开发人员管理所需的依赖库,确保这些库是最新的。

如果还没有安装CocoaPods,可以在终端执行下面的命令安装:

sudo gem install cocoapods

只要有耐心,只要等待几分钟的时间就能完成安装。安装完成后,你可以看到“”gems installed这样的字眼儿。

Screen-Shot-2015-06-10-at-4.09.48-PM

接下来,在Xcode工程根目录下创建一个名为“Podfile”的文件,使用你喜欢的文本编辑器打开它,把下面两行复制进来:

pod 'Stripe', '~> 4.0'
pod 'AFNetworking', '~> 2.5.4'

Podfile告诉CocoaPods我们想使用哪个库。我们这里需要是Stripe 4.0和AFNetworking 2.5.4。

现在,退出Xcode,打开Terminal,更改Xcode的根目录。根目录是“Podfile”所在目录。接下来,在Terminal键入:

pod install

CocoaPods将寻找“Podfile”,尝试安装我们所设置的依赖项。下载和安装的过程可能要持续几分钟。完成后,结果应该是这样的:

Screen-Shot-2015-06-10-at-4.28.31-PM

CocoaPods下载完毕,在Xcode项目中引入了Stripe和AFNetworking。然而,从现在起,我们不会再使用原始应用项目了。CocoaPods生成了另一个叫做Donate.xcworkspace的工作区。这个工作区包含了原始项目和CocoaPods管理的项目。

现在打开Xcode中新的工作区(也就是Donate.xcworkspace),你应该注意到一共有两个项目:Pods和Donate。

cocoapod-projects

在Swift项目中使用Objective-C

Stripe的库是用Objective-C写的,而我们的项目使用Swift。不进行适当的配置,这两种语言无法一起工作。通过建立桥接头文件(briddging header),任何Objective-C的库,项目和类都可以在Swift项目中使用。从技术角度来说,这样的关联会把头文件从Objective-C转换成Swift。

通过以下步骤可以创建一个桥接头文件:

  1. 在Donate路径中添加一个新的头文件。对准项目导航器中的Donate右击,然后点击“New File…”。
  2. 选择iOS -> Source分类,选择“Header File”模版,然后点击“Next”。
  3. 将类命名为“Donate-Bridging-Header.h”,点击继续,进行下一步并保存文件。
  4. 接下来,去“Building Settings”,找到“Objective-C Bridging Header”,将值设置为“Donate/Donate-Bridging-Header.h”。

Screen-Shot-2015-06-10-at-4.37.23-PM-1024x312

完成后,打开Donate-Bridging-Header.h文件,编写如下代码:

#import <Stripe/Stripe.h>
#import <AFNetworking/AFNetworking.h>

太棒了!终于完成了项目的配置。我们现在已经做好准备,进入示例程序的实现阶段。

设置API Key

你要做的第一件事就是配置Stripe的API Key。在Stripe的Dashboard页面,你可以点击“Account Settings” -> “API keys”来找到你的Key。现在,我们将使用用于测试的Key,这样你就可以不用真实的信用卡也可以测试交易过程了。

打开AppDelegate.swift文件,在里面插入一行代码(第3行):

  1. func application(application: UIApplication, did-FinishLaunchingWithOptions launchOptions: [NSOb-ject: AnyObject]?) -> Bool {
  2.      // Stripe Configuration
  3.      Stripe.setDefaultPublishableKey(“pk_test_IeR8DmaKtT6Gi5W7vvySoCiO”)
  4.      return true
  5. }

这里使用的是我们用于测试的Test Publishable Key,别忘了你要改成自己的。

注意:以后在实际应用程序中,你可以将Test Publishable Key换成Live Publishable Key。

收集信用卡信息

要进行交易,应用要有一个收集用户信息的环节。Stripe建议通过三种方法收集付款信息:

  • 通过Apple Pay访问用户所存储的支付信息
  • 通过Stripe内置的支付表单组件,PaymentKit
  • 通过构建自己的支付表单

在本文中,我们选择第三种方式,构建我们自己的支付表单。Stripe至少需要收集信用卡号和其有效期。但我也建议要保护CVC的安全,防止欺诈性交易,并获得用户的信任。你可能想要获得另一条信息是电子邮件地址,这样你就可以留有捐款者的记录以备后续沟通。

如果你用的是项目模版,我已经在其中建立了支付接口来收集用户的支付细节。所有的文本字段都与ViewController.swift文件中对应的outlet关联了。

donate-app-storyboard

现在,打开ViewController.swift,更新donate方法:

  1. @IBAction func donate(sender: AnyObject) {
  2.         // Initiate the card
  3.         var stripCard = STPCard()
  4.         // Split the expiration date to extract Month & Year
  5.         if self.expireDateTextField.text.isEmpty == false {
  6.             let expirationDate = self.expireDateTextField.text.componentsSeparatedByString(“/”)
  7.             let expMonth = UInt(expirationDate[0].toInt()!)
  8.             let expYear = UInt(expirationDate[1].toInt()!)
  9.             // Send the card info to Strip to get the token
  10.             stripCard.number = self.cardNumberTextField.text
  11.             stripCard.cvc = self.cvcTextField.text
  12.             stripCard.expMonth = expMonth
  13.             stripCard.expYear = expYear
  14.         }
  15. }

当用户点击Donate按钮时,该动作方法将被触发。我们首先初始化一个STPCard对象,并进行赋值。

下一步,在同样的方法中添加如下代码:

  1. var underlyingError: NSError?
  2. strip-Card.validateCardReturningError(&underlyingError)
  3.    if underlyingError != nil {
  4.         self.spinner.stopAnimating()
  5.         self.handleError(underlyingError!)
  6.    return
  7. }

STPCard类有非常方便的方法validateCardReturningError,我们就不用实现自己的验证逻辑了。类将收集到的信用卡信息发送给服务器,如果卡片信息是无效的,就会返回一个错误(error)。

像上面一样,将下面的代码添加到统一的方法中:

  1. STPAPICli-ent.sharedClient().createTokenWithCard(stripCard, completion: { (token, error) -> Void in
  2.             if error != nil {
  3.                 self.handleError(error!)
  4.                 return
  5.             }
  6.             self.postStripeToken(token!)
  7.         })

一旦确认信用卡有效,我们就调用STPAPIClient类的createTokenWithCard方法,通过安全的HTTPS请求发送信用卡数据。如果请求成功,Stripe将返回一个令牌。

这个时候用户的信用卡还没发生扣费。Stripe仅仅给了你一个一次性的令牌。稍后你将这个令牌发送到本地服务器去执行实际的扣费。

如果无法获取令牌,应用会简单地向用户显示一个错误信息。还是在同一个文件中,ViewController类中需要插入如下代码:

  1. func handleError(error: NSError) {
  2.         UIAlertView(title: “Please Try Again”,
  3.             message: error.localizedDescription,
  4.             delegate: nil,
  5.             cancelButtonTitle: “OK”).show()
  6.     }

建立本地服务器

本文刚开始的时候提到过,用户的信用卡扣费实际发生在我们自己的服务器上。出于安全原因,Stripe不直接从应用程序对信用卡进行扣费。Stripe只是生成一个令牌。应用将这个令牌传递给本地服务器进而进行实际的扣费。

所以在实现应用程序这一步之前,让我们停留片刻,建立自己的本地服务器来处理付款。为了创建自己的服务端,我们将使用PHP作为主要的编程语言,并利用Stripe提供的库。不用担心,你不必是一位Web开发的行家,跟着所描述的步骤走,就能配置处理Stripe支付所需要的服务端。

首先,下载XAMPP (for OS X) v5.6.8 (or up),在Mac上安装。接下来,下载这个压缩文件并解压缩。找到XAMPP的安装目录,通常安装在“Applications”文件夹。打开“Applications/XAMPP/htdocs”,拷贝压缩后的文件夹。

xampp-donate-htdoc

完成以后,返回XAMPP文件夹,打开“manager-osx”。在“Manage Servers”标签下点击“Start All”,服务就启动了:

xampp-manager

下一步就是去“Applications/XAMPP/htdocs/donate”,使用你喜欢的任意文本编辑器打开文件“payment.php”。

用Test Secret Key将方法中的参数替换掉,这在本文中已做描述:

\Stripe\Stripe::setApiKey("sk_test_qCTa2pcFZ9wG6jEvPGY7tLOK");

搞定了!你刚才已经完成了服务端的配置,简单吧?

给服务端发送令牌进行支付处理

现在,我们的本地服务器准备处理付款。我们回到Xcode,通过发起一个HTTP POST请求给本地服务器开始发送令牌。打开ViewController.swift,在文件中插入代码:

  1. func postStripeToken(token: STPToken) {
  2.         let URL = “http://localhost/donate/payment.php”
  3.         let params = [“stripeToken”: token.tokenId,
  4.             “amount”: self.amountTextField.text.toInt()!,
  5.             “currency”“usd”,
  6.             “description”: self.emailTextField.text]
  7.         let manager = AFHTTPRequestOperationManager()
  8.         manager.POST(URL, parameters: params, success: { (operation, responseObject) -> Void in
  9.             if let response = responseObject as? [String: String] {
  10.                 UIAlertView(title: response[“status”],
  11.                     message: response[“message”],
  12.                     delegate: nil,
  13.                     cancelButtonTitle: “OK”).show()
  14.             }
  15.             }) { (operation, error) -> Void in
  16.                 self.handleError(error!)
  17.         }
  18. }

由于我们在本地建立了服务端,那么就把URL设置为http://localhost/donate/payment.php。如果在远程主机中建立的服务端,请做相应的更改。

Stripe要求我们提交令牌来执行实际的扣费。外带付款额,币种和相关描述。你可以使用描述字段作为付款说明。在示例中,我们只是使用这个字段来存储用户的电子邮件。

一旦配置好参数,就使用AFHTTPRequestOperationManager提交一个异步的POST请求。这是AFNetworking提供的一个API。如果请求成功,我们就向用户显示支付响应。

这样就好啦!现在你可以运行应用程序并测试交易功能。出于测试目的,可以使用4242 4242 4242 4242这个信用卡号,还有任意的将来日期作为有效期(例如07/2019),包括CVC(例如222),这样可以模拟交易过程。

donate-stripe-success-300

为了进一步验证支付是否成功,可以看看Stripe的Dashboard:

Screen-Shot-2015-06-11-at-5.09.01-PM

总结

在本文中,我带着你学习了Stripe的基础知识。你应该学会了如何在应用中集成Stripe来接受信用卡的支付行为。为了进一步学习,我鼓励你去看看官方文档。Stripe提供了许多通过其API完成支付的方式,可以通过官方文档了解更多信息。

作为参考,可以从这里下载整个Donate项目,请使用Xcode 6.3(或以上)版本运行。别忘了把AppDelegate.swift文件中的Secret Key换成自己的。

http://www.csdn.net/article/2015-07-27/2825301-ios-stripe-payment-integration/1