解决在iOS App中集成支付宝时遇到的RSA密钥签名问题

最近在自己的iOS native App中集成支付宝功能,发现有即时到帐、手机网页、快捷支付,之间的区别说的不够清楚,接口及参数定义更是各不相同。对于Native App来说,选择快捷支付(wap)就对了。

说起集成文档及Demo,发现文档质量比起五年前没啥进步,一如既往的不知所云,甚至缺少某些关键步骤。而且Demo中的代码一直强调此代码不完全能直接使用,可是我想说,开发者看demo的意义就在于能够快速的解决问题,最好能一键集成,SDK的集成度可以做得更好。可是无论文档和代码都缺少版本控制,二者之间存在大量不一致现象,更不用说出错后的信息让人难以跟踪,跟当年Windows的出错信息有得一拼。

下面说说集成快捷支付中遇到的一些关键环节,特别是RSA加密的问题,因为快捷支付只支持RSA,不支持MD5方式。

继续阅读 More

将新浪微博 WeiboSDK 桥接集成到用Swift语言编写的iOS App

昨日女神高圆圆嫁到了台湾去,各位屌丝粉丝哭晕在厕所。别说,昨夜还真梦到了女神。于是今日发奋图强,继续进行编程工作–集成微博SDK到我的Swift程序中,用于微博授权登录。

前年曾经做过一次集成,那时还是ObjectiveC语言的天下,而今日Swift语言已成大势所趋。
由于iOS SDK是用ObjectiveC写成,在Swift中要用到语言桥接。

由于文档与代码脱节,自己摸索半天,还是遇到很多坑。特别是链接期直接抛异常出来,比如'NSInvalidArgumentException', reason: '-[__NSDictionaryM weibosdk_WBSDKJSONString]: unrecognized selector sent to instance,多是找不到链接库导致。

完整解决方案:

继续阅读 More

Apple最新Swift编程语言之闭包

Swift作为苹果为开发者最新发布的编程语言,受到热烈的追捧。与2014世界杯同热。

作为一门动态语言,吸收了大量Python语言的特性和语法(我喜欢Python),当然也有些ruby、js和C#的痕迹,同时保持了对原objective-C库的兼容。

iBook推出了教程,中文翻译也完成了。

大家需要下载最新xcode,里面有交互式的playground可以用来学习swift。为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。 打开Playground

你需要知道Swift语言闭包函数 ()->()

定义一个函数

你可以用func关键字来定义函数。函数可以接收和返回0个、1个或多个参数(tuples列表)。返回类型在->符号后面。

1
2
3
func jediGreet(name: String, ability: String) -> (farewell: String, mayTheForceBeWithYou: String) {
return ("Good bye, \(name).", " May the \(ability) be with you.")
}

调用函数

1
2
3
4
let retValue = jediGreet("old friend", "Force")
println(retValue)
println(retValue.farewell)
println(retValue.mayTheForceBeWithYou)

函数类型

1
func sum(x: Int, y: Int) -> (result: Int) { return x + y }

上述函数的函数类型为 (Int, Int) -> (Int)
函数类型可以用于嵌套函数的参数类型或者返回类型。

传递和返回函数

下列函数将另一个函数作为结果返回,可以用于赋值给变量及调用。

1
2
3
4
5
6
7
8
func jediTrainer () -> ((String, Int) -> String) {
func train(name: String, times: Int) -> (String) {
return "\(name) has been trained in the Force \(times) times"
}
return train
}
let train = jediTrainer()
train("Obi Wan", 3)

可变入参函数

可变入参函数带有可变数量的入参(表示为参数类型后的...),其内容可作为数组来访问。

1
2
3
4
5
6
func jediBladeColor (colors: String...) -> () {
for color in colors {
println("\(color)")
}
}
jediBladeColor("red","green")

定义一个闭包

闭包被置于花括号{}中,且定义为()->()类型的函数。其中->分隔了入参与返回类型,其后的in关键字分隔了闭包头与闭包体。

1
2
3
{ (params) -> returnType in
statements
}

一个例子,map函数应用于数组。

1
2
3
4
5
let padawans = ["Knox", "Avitla", "Mennaus"]
padawans.map({
(padawan: String) -> String in
"\(padawan) has been trained!"
})

类型已知的闭包

当闭包的入参类型已知时,可以这样写:

1
2
3
4
5
6
7
func applyMutliplication(value: Int, multFunction: Int -> Int) -> Int {
return multFunction(value)
}

applyMutliplication(2, {value in
value * 3
})

闭包省略入参名

闭包入参可以不用参数名而是位置($0,$1,…)来访问

1
applyMutliplication(2, {$0 * 3})

甚至,如果闭包是函数的最后一个入参时,圆括号可以这样省略掉

1
applyMutliplication(2) {$0 * 3}

(翻译自 http://fuckingswiftblocksyntax.com/

编写Apple Push Notification服务器

iPhone OS 3.0一个引入注目的新特性是Push Notifications(推送通知),它允许向已安装相关应用程序的各设备直接发送消息。苹果在新闻提示或IM应用中展示了此特性,它也十分完美地适合于我们的服务器监视服务程序Server Density。

我们的程序提供一个选项,当你设定的某个服务器事件发生时,通知会直接发送到你的iPhone上。这是非常有用的因为它提醒用户立即打开我们的程序查看引起此警示的服务器详情。

Apple提供了有关实现和处理设备上提示消息的 iPhone OS 的详细代码文档 ,但它只包括消息提供者服务器端编程指南。

作为消息提供者,我们需要与 Apple推送通知服务 (APNS)连接以发送消息到iPhone。为减少电池使用,一个设备仅需维持与APNS的一个连接。

本教程将从代码的层面介绍关于怎样建立一个推送通知服务器以连接APNS并使用推送通知到我们的服务器监视iPhone程序上。我们是使用PHP进行开发的,我们的示例都是PHP 5兼容的。

基本结构

使用唯一的SSL许可证连接到APNS
循环通过你需要发送到消息
为各消息构建有效载荷
断开与 APNS的连接

远程通知数据的流程是单向的。提供者将包括客户程序设备令牌和有效载荷的数据打包,发送到APNS,然后APNS再将通知发送给最终设备。

限制

有效载荷限制为256字节 – 它包括了消息主体以及你希望传送带其他属性。推送通知并不适于传送大量的数据。例如,我们仅仅传送一条短消息通知服务器监视的事件已经被触发了。
APNS并不提供消息发送成功与否的回馈状态。一个原因是如果一个设备无法联系那么发送给它的消息将被存于队列中,然而只有最新发送的消息被存于队列中 – 覆盖了先前发送但不成功的消息。
推送通知不适合用于发送紧急通知,因为消息仅在设备具有wifi或手机服务连接的情况下才能被发送,这也是为什么我们推荐与其它方法如email或SMS一起使用的原因。
用来与APNS通讯的SSL许可证(下面将讨论)是在程序层生成的。本教程涉及到实现方法仅适于单个iPhone程序,所以如果你有多个程序,那么你需要修改代码使之适合于使用多个许可证。

设备令牌

每条推送消息都必须针对某特定设备。这是通过使用在你的iPhone程序中由APNS产生的唯一deviceToken(设备令牌)来实现的。一旦获取了此令牌,你需要将其存储于服务器而不是你的iPhone程序内。它看上去像这样:
c9d4c07c fbbc26d6 ef87a44d 53e16983 1096a5d5 fd825475 56659ddd f715defc

在我们的 Server Density iPhone 程序中,我们在程序启动时调用相应的令牌生成方法,然后通过 HTTP API 调用 传回给我们的服务器 。这将使得deviceToken存储于服务器的有关用户的数据库中,从而我们可以使用它与持有此设备的用户进行通讯。

反馈服务

Apple 还提供了一个 反馈服务 ,你应该定期查询。它提供了一个以前使用过但不再有效的(例如用户卸载了你的iPhone程序)设备令牌列表。你可以从你的数据库中删除这些设备令牌。

本教程不涉及反馈服务的使用。

许可证

要进行推送服务的第一件事就是获取推送许可证。它用来对你通过SSL与APNS通讯进行识别。

在Mac上生成 Apple推送通知SSL许可证:

  1. 登录到 iPhone Developer Connection Portal 并点击 App IDs
  2. 创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如,我们的iPhone程序ID像这样: AB123346CD.com.serverdensity.iphone
  3. 点击App ID旁的“Configure”,然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传,最后下载生成的许可证。此步骤在 Apple文档中 也有谈到。
  4. 通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。
  5. 在Mac上启动 Keychain助手,然后在login keychain中选择 Certificates分类。你将看到一个可扩展选项“Apple Development Push Services”
  6. 扩展此选项然后右击“Apple Development Push Services” > Export “Apple Development Push Services ID123”。保存为 apns-dev-cert.p12 文件。
  7. 扩展“Apple Development Push Services” 对“Private Key”做同样操作,保存为 apns-dev-key.p12 文件。
  8. 需要通过终端命令将这些文件转换为PEM格式:

    1
    2
    openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
    openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12

  9. 如果你想要移除密码,要么在导出/转换时不要设定或者执行:

    1
    openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem

  10. 最后,你需要将键和许可文件合成为apns-dev.pem文件,此文件在连接到APNS时需要使用:

    1
    cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem

将此文件保存为一个易记的名字,你有可能以后会用到它。上述步骤同样适合于生成产品许可证。

载荷内容

载荷 格式化为遵循 RFC 4627标准的JSON格式。它由以下几部分组成:
提示 – 显示于设备上的文本串
标识 – 设备屏幕中程序图标上显示的整数
声音 – 显示消息在设备的同时发出的声音的文本名字
本教程仅处理发送简单提示文本串,但也可以发送包括诸如显示自定义按钮等在内的各种选项的字典集。

创建载荷

使用 PHP 很容易根据数组并 转换成 JSON而创建载荷:

1
2
$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload = json_encode($payload);

显示 $payload 的内容可以看到传送到APNS 的 JSON字符串:
{
“aps” : { “alert” : “This is the alert text”, “badge” : 1, “sound” : “default” }
}

这将使消息显示于设备上,触发提升声音并将“1”置于程序图标上。默认按钮“Close”和“View”同时会显示于弹出窗口上。

对于 Server Density iPhone程序而言,让用户按下“View”直接进入产生此提示的服务器是很重要的,所以我们增加了额外的自定义值:

1
2
3
$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload['server'] = array('serverId' => $serverId, 'name' => $name);
$output = json_encode($payload);

当用户按下“View”后,自定义server值将被传递到设备中的程序。JSON 值如下:
{
“aps” : { “alert” : “This is the alert text”, “badge” : 1, “sound” : “default” },
“server” : { “serverId” : 1, “name” : “Server name”)
}

256字节的限制适用于整个载荷,包括自定义字典集。

原生接口

在Server Density中,一旦产生了一条提示,将建立一个载荷并插入队列中。因此有必要时我们可以同时发送多个载荷。

Apple推荐使用这种方法,因为如果你在发送各载荷时频繁连接和断开,APNS有可能会封锁你的IP。
如Apple 描述:
原生接口使用原生socket,具有二进制内容,采用数据流技术,不产生回馈。

打开连接

打开连接的 PHP 5代码如下:

1
2
3
4
5
6
7
8
9
$apnsHost = 'gateway.sandbox.push.apple.com';
$apnsPort = 2195;
$apnsCert = 'apns-dev.pem';

$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);

$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2,
STREAM_CLIENT_CONNECT, $streamContext);

如果发送错误,你可以参考$errorString。它也包括了SSL许可证不正确时的详细信息。

许可证文件处于执行的PHP代码的当前工作目录下,如果需要你可指定其绝对路径。
注意测试时应该使用开发许可证及sandbox。成品主机名为 gateway.push.apple.com ,而且你必须使用不同的产品许可证。

发送载荷

在此,我们循环整个载荷队列进行发送。构建发送到APNS的二进制内容简单示例如下:

1
2
3
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) .
chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);

注意 $deviceToken 是从数据库中提取并去除空格得到的。我们还应该检查是否$payload超过256个字节。

$apnsMessage 包括了正确的二进制载荷,而fwrite 将载荷写入当前活动的数据流连接中。

完成后,应关闭连接:

1
2
socket_close($apns);
fclose($apns);

php-apns

有一个开源服务器库php-apns实现了以上所有功能,它依赖于 memcached。我们不想使用任何第三方代码,所以完全自己编写了自己的服务器。我们使用自定义cron系统,几秒钟运行一次。

原文见:
How to build an Apple Push Notification provider server (tutorial)

参考:
Apple 文档

命令行运行iphone模拟器以及单元测试

由于需要搭建持续集成CI平台,因此需要利用命令行进行编译和单元测试。
命令行编译倒是容易,xcodebuild即可。但是如果运行Unit test甚至启动模拟器运行应用程序,成了国内外同行的大问题,苹果还没有来得及开放或者根本不愿意开放命令行,所以只好自己摸索。环境为XCode4.2。工程名为Krowdit.

参考:
https://github.com/hborders/iphonesim/commit/27acdfdf6c900b0e9bbd061f45c904c1d18fd21d#commitcomment-484139
http://blog.carbonfive.com/2011/04/06/running-xcode-4-unit-tests-from-the-command-line/
http://longweekendmobile.com/2011/04/17/xcode4-running-application-tests-from-the-command-line-in-ios/

关于使用Jenkins CI,参见Jenkins CI 连接Mac 电脑失败

继续阅读 More

iOS网络编程

一、检测网络状态

添加源文件和framework

开发Web等网络应用程序的时候,需要确认网络环境,连接情况等信息。如果没有处理它们,是不会通过Apple的审(我们的)查的。
Apple 的例程 Reachability 中介绍了取得/检测网络状态的方法。要在应用程序程序中使用Reachability,首先要完成如下两部:

  1. 添加源文件:
    在你的程序中使用 Reachability 只须将该例程中的 Reachability.h 和 Reachability.m 拷贝到你的工程中。如下图:
  2. 添加framework:
    将SystemConfiguration.framework 添加进工程。如下图:

继续阅读 More

使用NSOperation和NSOperationQueue启动iOS多线程

在app store中的很多应用程序非常的笨重,他们有好的界面,但操作性很差,比如说当程序从网上或本地载入数据的时候,界面被冻结了,用户只能等程序完全载入数据之后才能进行操作。
当打开一个应用程序时,iphone会产生一个包含main方法的线程,所用程序中的界面都是运行在这个线程之中的(table views, tab bars, alerts…),有时候我们会用数据填充这些view,现在问 题是如何有效的载入数据,并且用户还能自如的操作程序。方法是启动新的线程,专门用于数据的下载,而主线程不会因为下载数据被阻塞。
不管使用任何编程语言,在实现多线程时都是一件很麻烦的事情。更糟糕的是,一旦出错,这种错误通常相当糟糕。然而,幸运的是apple从os x10.5在这方面做了很多的改进,NSThread的引入,使得开发多线程应用程序容易多了。除此之外,它们还引入了两个全新的类,NSOperation和NSOperationQueue。
接下来我们通过一个实例来剖析如何使用这两个类实现多线程。这里指示展示这两个类的基本用法,当然这不是使用他们的唯一办法。

继续阅读 More

ObjectiveC中的单例设计模式

注意class method中什么时候用self,什么时候用super!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#import "PostOfficeProxy.h"  
#import "HTTPPostOffice.h"
static PostOffice *_sharedPostOffice = nil;
@implementation PostOfficeProxy
@synthesize realPostOffice;
+ (PostOffice*)sharedPostOfficeProxy {
NSLog(@"shared");
@synchronized([PostOffice class]) {
if (nil == _sharedPostOffice) {
[[self alloc] init];

}
}
return _sharedPostOffice;
}
+ (id)allocWithZone:(NSZone *)zone {
NSLog(@"alloc zone");
@synchronized([PostOffice class]) {
if (nil == _sharedPostOffice) {
_sharedPostOffice= [super allocWithZone:zone];


}
}
return _sharedPostOffice;
}
- (id)init {
NSLog(@"init");
self = [super init];
if (nil != self) {
self.realPostOffice = [[HTTPPostOffice alloc] init];
}

return self;

}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain
{
return self;
}
- (unsigned)retainCount
{
return UINT_MAX;
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
@end