18 8月

霍尼韦尔黑客马拉松(Honeywell Hackathon)参赛记

黑客马拉松概念源自美国。一群高手云集一堂,几十个小时里开发出一款插件。参与黑客马拉松的人,除了热衷钻研技术的软件工程师,还有来自风投公司的团队,累了或坐或卧,现场休息,在几十个小时内拿出作品,择优而录,是“世界上最酷的开发者狂欢”。

“黑客马拉松”就像程序员版的《美国偶像》,极其火爆。有了更加易用的软件编写工具,加上各公司乐于让第三方分享他们的数据,2011年美国举行了200多场“黑客马拉松”(全球近20个城市分别举行类似活动)。结果是催生了一系列创意和新兴企业,光是围绕苹果的iOS系统,从2007年开始,就开发了50多万个插件,为程序员们带来30亿美元收入。

“黑客马拉松”规模各异,可能是在酒店自助餐厅举行、只有20个程序员参加的小型比赛,也可以是在13 .3万平方英尺的仓库举行、参加者多达500人的大型赛事。

拥有102年历史的霍尼韦尔航空航天集团怎能忽视这样高大上的编程赛事呢……

本次大赛由70位工程师,产生了20项创意。其中7项创意脱颖而出,分别斩获金、银、铜以及特别奖项。我们的团队由来自Aero Service Team的三位工程师组成(Shun Wang, Yiheng Ding, Yunpeng Bai)

拿出创意

使用JSON格式的数据表示AWC公共气象信息,将这些实用的信息实时发送给飞行员。

本项目原始创意由航空工程师Andy Li提出。遗憾的是由于他忙于准备去美国总部出差的事宜而没能参赛,团队借此机会,谨致谢忱!

组织团队

共3人,Shun Wang(iOS客户端), Yiheng Ding(服务端)和我(iOS客户端、推介),另外,Shun Wang和他的爱人(UI设计师)还承担了部分界面设计工作。

实现创意

经过十几个小时的奋战,完美地实现了所有创意点。项目名称叫做“Before Boarding, BB”,意即飞行员登机前查看。还提交了应用的Logo。

演示成品

10分钟时间包括简单陈述创意内容,演示PPT,讲解潜在应用市场和商业发展潜力。

面对评判

经过数月漫长的等待……

领取奖金

比赛项目最终获得了铜牌。洪荒之力有没有?一笔种子基金(seed fund)已经发放,用于以该作品为蓝本的商业项目创意的开发。经费可自行支配,购买项目所需的任何设备。

5621302A42EBE45BE1132831920540BB

图一 比赛现场

1A7EDC51AD3C2F21E244647D013075D0

图二 专心比赛

D2D91B92F294668CA540B0B84375BCBE

图三 后勤保障有力,可以使出洪荒之力

7BB24E82DF70373756F03BF5F6E7C447

图四 颁奖典礼

F92CA29486BE38EF3AB1241FE25C1924

图五 领取奖杯

每一位工程师在参赛过程中必然会有所斩获。祝愿参加黑客马拉松的小伙伴们在今后的人生道路上深耕不辍,绽放英华!

28 6月

使用Swift构建带有地理定位功能的App

文/Eugene Trapeznikov:高级iOS开发工程师,他不断学习新技术。范围从移动开发到web敏捷实践(持续集成和行为驱动开发)。在过去的四年中,发布了超过10App,其中两款被Apple Store中的精品推荐(featured收录。

原文链接:http://www.appcoda.com/geo-targeting-ios/

地理定位是根据用户所处的地理位置,来显示不同的内容的一种方法。比如根据国家、地区、城市或者其他标准来定位。地理定位应用在很多地方。想象一下,一位客户正去造访你的竞争对手,我们可以给予他特别的优惠,把他吸引过来。再比如,如果用户在过去的几天里,跑了好几家汽车分销商,这就说明他想要买一辆新车。因此我们可以向他展示我们的汽车广告。这样有目标的广告投放总比随机投放要有效得多。

在这片文章中,我们会教你如何在iOS设备中实现地理定位,会向你介绍苹果标准库CLRegion。并且,我们还会教你如何测试不常见的功能特性。还会看到如何实现复杂的追踪逻辑。最后,我们还会教你如何创建自定义的区域,并向你解释什么情况下自定义的区域要优于CLRegion。你可以使用地理定位功能开发许多带有创新性定位功能的App

geotarget-demo

创建工程

假定要追踪用户去饭店。下面开始创建工程,创建以GeoTargeting命名的单视图应用。打开

Main.storyboard文件,在View上面拖拽一个Map Kit View。在ViewController.swift中为它创建@IBOutlet。编译的时候会报错,不过先不用管,storyboard就先这样,我们一起去看看ViewController.swift

我们要把MapKitCoreLocation的库加上去。并且在View Controller类中加上它俩的协议:

class ViewController: UIViewController, MKMapDelegate, CLLocationManagerDelegate {
    @IBOutlet weak var mapView: MKMapView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

接下来我们创建mapViewlocationManager实例:

//1. create locationManager

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        // 2. setup locationManager
        locationManager.delegate = self
        locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters
        locationManager.desiredAccuracy = kCLLocationAccuracyBest

        // 3. setup mapView
        mapView.delegate = self
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .Follow

        // 4. setup test data
        setupData()

ViewController.swift文件中做了下面这些事情:

  1. 创建了locationManager实例,用来探测用户位置的变化。
  2. locationManager的代理指向了ViewController,这样就可以通过View Controller追踪用户的位置和区域范围了。另外,将定位服务的精准度设置成了最佳精准度(kCLLocationAccuracyBest)。
  3. 创建了mapView实例,设置了它的代理,这样就可以使mapView单独绘制图形。让mapView显示用户的位置,使我们能够对其进行追踪。这也让人更容易了解用户是否在某个区域内。也可以在Storyboard中设置MapView的代理,使用showsUserLocation属性显示用户的位置信息。用代码实现更直观。
  4. 创建了测试数据,创建的方法在后面实现。

接下来就要追踪用户的地理位置了。在这之前,我们需要检查是否有权限这样做。

    override func viewDidAppear(animated: Bool) {
        // 1. status is not determined
        if CLLocationManager.authorizationStatus() == .NotDetermined {
            locationManager.requestAlwaysAuthorization()
        }
        // 2. authorization were denied
        else if CLLocationManager.authorizationStatus() == .Denied {
            //showAlert("Location services were previously denied, please enable loaction services")
        }
        // 3. we do have authorization
        else if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
            locationManager.startUpdatingLocation()
        }
    }

我们来仔细分析一下。首先,在viewDidAppear方法中检查了授权状态。用户在设置中将授权状态修改后我们还可以对授权状态再次检查一遍。

因此,可能出现下面几种情况:

  1. 如果用户从未选择或设置过授权状态,那么我们就要设置“Always”权限。在CLRegion使用状态监控,也只能这样设置。
  2. 如果定位权限之前被用户拒绝使用了。我们就要通知用户,允许使用地位服务,App会获得更佳的工作状态。这里使用了showAlert(title: String)方法。这也是UIAlertController来现实信息的便捷做法,还可以带有“Cancel”按钮。我们稍后会在几个地方用到该方法。
  3. 如果获得了定位权限,那么久可以使用startUpdatingLocation()方法了。

现在可以运行App了。你可能会问,并没有弹出授权定位的提示框呀?这需要打开…-Info.plist文件,在文件中添加一行,NSLocationAlwaysUsageDescription,然后把它作为key,添加value。例如:“Regions needs to always be able to access your location.”。当使用requestAlwaysAuthorization()方法时会用到上面的Plist信息。否则系统就会无视定位请求。这个键值对描述了系统使用定位功能的原因。

Plist里面添加了上述信息后,再次运行App就可以弹出设置的描述信息了。

当然,App中,对定位授权的情况可能有很多种,我们这里所选择的是最基本的一项。

接下来我们就要对地理位置进行监控了。

func setupData() {
        // 1. check if system can monitor regions
        if CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion) {
            // 2. region data
            let title = "Lorrenzillo' s"
            let coordinate = CLLocationCoordinate2DMake(37.703026, -121.759735)
            let regionRadius = 300.0

            // 3. setup region
            let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: coordinate.latitude,longitude: coordinate.longitude), radius: regionRadius, identifier: title)

            // 4. setup annotation
            let restaurantAnnotation = MKPointAnnotation()
            restaurantAnnotation.coordinate = coordinate;
            restaurantAnnotation.title = "\(title)";
            self.mapView.addAnnotation(restaurantAnnotation)

            // 5. setup circle
            let circle = MKCircle(centerCoordinate: coordinate, radius: resionRadius)
            self.mapView.addOverlay(circle)
        }
        else {
            print("System can't track regions")
        }

        // 6. draw circle
        func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
            let circleRenderer = MKCircleRenderer(overlay: overlay)
            circleRenderer.strokeColor = UIColor.redColor()
            circleRenderer.lineWidth = 1.0
            return circleRenderer
        }
    }

我们一步步地来看,setupData()里面都做了哪些事情:

  1. 无论如何都要检查当前设备是否支持对制定区域进行位置监控。如果用户拒绝了来自系统的定位请求,那么isMonitoringAvailableForClass就会返回false,包括把后台应用刷新的功能禁掉,或者在飞行模式的情况下。
  2. 我们在这里创建了一个restaurant用来测试,本文中这样做无可厚非,但在实际项目中,你需要为被监控对象创建单独的类。
  3. 我们还创建了监控区域,使用restaurant的名称作为被监控区域的ID。由于这事探测用户所到区域的唯一方法,我们需要这样做。不要把强引用存入CLRegion,而可能只要记下区域的ID,以备后用。
  4. 为了视觉效果更好,我们在区域的中心位置添加了说明性的信息。
  5. 同样,我们在地图上也添加了圆环,表示区域的边界。
  6. 绘制圆环调用的是MKMapViewDelegate的代理方法。

这些都是工程里的准备工作,接下来,我们就要对区域进行监控了。

苹果的CLRegion

本文中,我们在对地理位置进行监控。苹果的CLRegion类是针对已知区域特定半径的圆形区域。所以只能使用CLRegion监控圆形区域。

    // 1. user enter region
    func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
        showAlert("enter" \(region.identifier)")
    }
    // 2. user exit region
    func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
        showAlert("exit \(region.identifier)")
    }

添加这两个方法是为了通知用户进入或是离开了被监控的区域。使用简单的提醒方式只是为了方便查看。给圆形区域周边加个过渡区域,用户在边缘移动的时候,就不会收到大量进出边界的消息,这种情况下,给出进出消息的这两个方法不会被调用。

还要注意同时监控多个位置的情况。一个App最多只能监控20个位置,这些位置在用户所在位置的周边。当用户所在位置发生变化时,可以去掉较远的位置,把用户移动轨迹上的位置添加进来。如果监控的位置数量到达了上限,LocationManager会调用monitoringDidFailForRegion方法。这样做可以获得更好的用户体验。

刚才提到的两个方法,还有一些限制,都是需要注意的。

基本的方法都已经创建好了。现在你可以驾车去被监控的区域了吧!呵呵,开玩笑的,有一种便捷方法来测试功能。

如何测试

Xcode当中,有一种简便的方法,那就是使用GPX文件。在开发过程中,GPX是描述GPS的常见数据格式,是XML文件。看看再说:

<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
    <wpt lat="37.702801" lon="-121.751862"></wpt>
    <wpt lat="37.702821" lon="-121.752321"></wpt>
    <wpt lat="37.702840" lon="-121.752780"></wpt>
</gpx>

这个GPX文件中,在我们所在区域附近有三个定位点。Xcode会逐一扫描并移动用户的位置。每秒移动一个位置。在Xcode中可以自行创建GPX文件。

再次运行App,在Xcode中打开“Debug Area”(界面下方),选择“Simulate Location”,选择刚刚添加的GPX文件。再看看模拟器,用户的位置应该发生了变化。

geotarget-simulate-location

几秒钟后,你就会看到“enter Lorrenzillo’s”这样的提示信息。然后再过几秒钟,会提示“exit Lorrenzillo’s”。所以,位置监控功能已经工作了,恭喜!

geotarget-demo-app-1

给被监控区域添加复杂的业务逻辑

对于一些App来说,监控进入或离开事件已经足够了。但如果需要更复杂的业务逻辑,又该如何呢?也许你会关注用户在某个区域的停留时间,或者在该区域内移动的平均速度。在我们的示例中,会检查用户是否在饭店中停留了足够长的时间,这意味着用户是否造访了饭店,我们可以从用户那里收集反馈信息。下面对代码来做一些改进:

    var monitoredRegions: Dictionary<String, NSDate> = [:]
    // 1. user enter region
    func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
        //showAlert("enter" \(region.identifier)")

        // 2.1 Add entrance time
        monitoredRegions[region.identifier] = NSDate()
    }

    // 2. user exit region
    func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
        //showAlert("exit \(region.identifier)")

        // 2.2 Remove entrance time
        monitoredRegions.removeValueForKey(region.identifier)
    }

    // 3. Update resions logic
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        updateRegions()
    }
  1. 在字典中存储用户进入监控区的时间。
  2. 在这里实现了时间的添加和删除功能。
  3. LocationManager的代理方法didUpdateLocations会帮助我们检查用户在监控区域内是否停留了足够的时间。
func updateRegions() {
        // 1. 
        let regionMaxVisiting = 10.0
        var regionsToDelete: [String] = []
        //2.
        for regionIdentifier in monitoredRegions.keys {
            //3.
            if NSDate().timeIntervalSinceDate(monitoredRegions[restorationIdentifier!]!) > regionMaxVisiting {
                //showAlert("Thanks for visiting our restaurant")    
                regionsToDelete.append((regionIdentifier))
            }
            // 4.
            for regionIdentifier in regionsToDelete {
  monitoredRegions.removeValueForKey(regionIdentifier)
            }
        }
    }
  1. 假定10秒钟对用户来说已经足够了。我们也需要变量来存储用户的位置,就是用户停留足够长时间的区域,将来会删除掉,因为我们已经达到了目的。
  2. 遍历所有当前被监控的区域。
  3. 如果用户在被监控区域停留了足够长的时间,我们就会向用户显示特定的消息,并把该区域标记为待删除区域。
  4. 删除所有待删除区域。

现在你就可以完全用updateRegions方法来实现任意复杂度的监控方法了。

自定义监控区域

令人遗憾的是,苹果的CLRegion有很多限制。其中之一就是用户如果对App设置了“While in Use”访问权限,你就无法监控用户所在位置了。我们都知道,很多用户担心它们手机的电池寿命,从而不想让App一直开启监控。真的很难向用户解释说,一直开启位置监控会有对他们有益处。这也是为什么有时你只在用户使用App的时候才监控用户的位置。我建议你创建自定义的区域类,像CLRegion那样带有类似接口和回调方法的类。这样就会容易理解自定义类的功能,就像其它开发人员所熟悉的CLRegion类一样。

protocol RegionProtocol {
    var coordinate: CLLocation {get}
    var radius: CLLocationDistance {get}
    var identifier: String {get}

    func updateRegion()
}
protocol RegionDelegateProtocol {
    func didEnterRegion()
    func didExitRegion()
}

你可以使用上面的协议,轻松地替换掉CLRegion

CLRegion另外一项限制是我们只能监控圆形区域。有时我们需要监控多边形(四边形、五边形等)或者椭圆形区域。这种情况依旧可以使用自定义的类。只要使用RegionDelegateProtocol中的didEnterRegion方法,检查是否进入了被监控区域即可。

另外,也没有必要立刻就向用户显示进出警告信息。我们要为今后的数据分析存储这些数据,这里有很多用例可以去实现。

http://geek.csdn.net/news/detail/79643

07 2月

使用Storyboard构建Navigation Controller和Table View

文 / Simon Ng:iOS程序员,Beginning iOS 9 Programming with Swift一书的作者,AppCoda.com创始人。

原文链接:http://www.appcoda.com/use-storyboards-to-build-navigation-controller-and-table-view/

到现在为止,如果一直跟着我们的教程学习,你应该有了对UITableView基本的了解。并且知道如何利用它构建简单的应用程序了。本周,我们要说说新东西,Storyboards。这是Xcode 4.2和iOS 5 SDK引入的最令人激动的功能特性之一。作为iOS开发人员,它使你的生活更简单,可以轻而易举地设计iOS应用程序的用户接口。

本文将向你展示如何使用Storyboards构建Navigation接口,并将它与UITableView集成起来。我们会尽力保持简单,集中精力解释概念。因此就没有好玩儿的界面和漂亮的图形。以后的文章会解决艺术性的问题。

好,我们开始吧!

什么是Navigation Controller?

我们开始编码前,像以往一样,我们对Navigation Controller和Storyboards做一个简单的介绍。

就像Table View,Navigation Controller是你在iOS应用程序中另一种最常见的UI元素。它提供了逐层向下的层次内容。看一看内置的Photos,YouTube 和Contacts应用,这些应用程序都使用了Navigation Controller来显示分层的内容。通常,大多数应用程序中的Table View和Navigation Controller是一起工作的。但这并不是说一定要两个一起使用。

Photos-App-Navigation-Controller

Storyboards概览

前面说过,Storyboard是Xcode 4.2以来引入的新功能特性。它为iOS开发者提供了创建和设计用户界面提供了全新的方式。对初学者来说,介绍Storyboard之前,创建导航界面和标签界面着实困难。每个界面都存储在单独的nib文件中。在这之上,还要编写代码讲所有的界面连接到一起,描述导航如何工作。

有了Storyboards,所有的界面都存储在一个文件中。这给了你一种应用程序可见的表现方式,向你展示了这些界面是如何连接在一起的。Xcode提供了内置的编辑器来编辑Storyboards的布局。通过简单的点击,你可以定义不同界面之间的转变(也就是所谓的segues)。这并不意味着不需要为用户界面编写代码。但Storyboards显著减少了编码量。下面的示例图片展示了Storyboards在Xcode中的样子。

Storyboards-Explained

Scene和Segues

使用Storyboards时,Scene和Segues是总出现的两条术语。在Storyboard中,scene指一个单独的视图控制器和它的视图。每一个scene都有一个dock,dock主要被用来在视图控制器和它的视图之间做action和outlet关联。

Segue位于两个scene之间,管理两个scene之间的转变(transition)。Push和Modal是转变的两种常见类型。

在Storyboards中创建Navigation Controller

我们现在动手创建Storyboards。在本文中,我们将构建一个使用UITableView和UINavigationController的简单示例应用。使用Table View显示菜谱。当用户选择任何一道菜时,应用程序就跳转到下一个页面显示细节。这不难的。

首先,启动Xcode(确保使用4.2版本以上),使用“Single View application”模版创建一个新的项目。

Choose-Xcode-Template

点击“Next”继续。把下图中Xcode项目中需要填写的内容补齐。确认勾选“Use Storyboards”选项。

RecipeCookbook-Xcode-Project

点击“Next”继续。Xcode会询问把“SimpleTable”存到哪里。选择任一文件夹(例如,桌面)保存你的项目。

你可能注意到了Xcode项目中细微的差别。与之前文章中的内容比较,.xib文件(interface builder)被MainStoryboard.storyboard文件代替了。

Empty-Storyboard-in-Xcode

默认情况下,Xcode创建一个标准的视图控制器。因为我们要使用Navigation Controller控制界面导航,首先要将view controller修改为navigation controller。直接选择“Editor”菜单,选择“Embed in”,然后选择“Navigation Controller”。

Storyboard-Embed-in-Navigation-Controller

Xcode会自动给RecipeBook View Controller嵌入Navigation Controller。界面看起来是这个样子的:

Storyboard-Added-with-Navigation-Controller

继续网下进行前,我们运行一下程序,看看是什么样子的。点击“Run”按钮,你应该能看到一个添加了导航条的空白视图。这表明已经成功地把RecipeBook View Controller嵌入到了Navigation Controller中了。

RecipeBookApp-Empty

为数据添加Table View

接下来,我们会添加Table View,显示菜谱。在对象库中选择“Table View”,拖拽到“Recipe Book View Controller”。

请注意,不能把这些对象拖拽到缩小了的编辑器中,如果无法把table view拖拽到view controller,放大后再试。

Storyboard-Add-Table-View

下一件要做的事情是编写代码表格数据(也就是菜谱)。在项目导航栏,选择“RecipeBookViewController.h”,在“UIViewController”后面添加协议参数。

#import <UIKit/UIKit.h>

@interface RecipeBookViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@end

如果你读过Simple Table有关的文章,应该非常熟悉上面的代码。我就不在解释细节 了。如果理解起来有困难,查阅一下更早的文章

接下来,选择“RecipeBookViewController.m”,为保存表格数据定义实例变量(也就是菜单数组)。

@implementation RecipeBookViewController {

NSArray *recipes;

}

在“viewDidLoad”方法中,添加如下代码初始化“recipes”数组:

– (void)viewDidLoad

{

[super viewDidLoad];

//Initialize table data

recipes = [NSArray arrayWithObjects:@”Egg Benedict”, @”Mushroom Risotto”, @”Full         Breakfast”, @”Hamburger”, @”Ham and Egg Sandwich”, @”Creme Brelee”, @”White Chocolate Donut”, @”Starbucks Coffee”, @”Vegetable Curry”, @”Instant Noodle with Egg”, @”Noodle with BBQ Pork”, @”Japanese Noodle with Pork”, @”Green Tea”, @”Thai Shrimp Cake”, @”Angry Birds Cake”, @”Ham and Cheese Panini”, nil];

}

最后,我们必须实现两个数据源方法,来生成表格数据:

“tableView:numberOfRowsInSection”和“tableView:cellForRowAtIndexPath”方法。调用这两个方法是UITableViewDataSource协议的一部分。配置UITableVIew时,这两个方法是必须要实现的。第一个方法用来通知Table View区域内的行数,而第二个方法被用来填充表数据,因此我们添加如下代码。

作为参考,下面是整个“RecipeBookViewController.m”的源码。

//

//  RecipeBookViewController.m

//  RecipeBook

//

//  Created by Simon Ng on 14/6/12.

//  Copyright (c) 2012 Appcoda. All rights reserved.

//

#import “RecipeBookViewController.h”

@interface RecipeBookViewController ()

@end

@implementation RecipeBookViewController {

NSArray *recipes;

}

– (void)viewDidLoad

{

[super viewDidLoad];

// Initialize table data

recipes = [NSArray arrayWithObjects:@”Egg Benedict”, @”Mushroom Risotto”, @”Full Breakfast”, @”Hamburger”, @”Ham and Egg Sandwich”, @”Creme Brelee”, @”White Chocolate Donut”, @”Starbucks Coffee”, @”Vegetable Curry”, @”Instant Noodle with Egg”, @”Noodle with BBQ Pork”, @”Japanese Noodle with Pork”, @”Green Tea”, @”Thai Shrimp Cake”, @”Angry Birds Cake”, @”Ham and Cheese Panini”, nil];

}

– (void)viewDidUnload

{

[super viewDidUnload];

// Release any retained subviews of the main view.

}

– (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

}

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

return [recipes count];

}

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

static NSString *simpleTableIdentifier = @”RecipeCell”;

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

if (cell == nil) {

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault     reuseIdentifier:simpleTableIdentifier];

}

cell.textLabel.text = [recipes objectAtIndex:indexPath.row];

return cell;

}

@end

最后,我们建立Table View和刚刚创建的这两个方法之间的连接。返回Storyboard。按住Control键,选择“Table View”,拖拽到View Controller的图标。看起来是这个样子的:

Storyboard-TableView-Datasource

松开按钮,弹出了“dataSource”和“delegate”,选择“dataSource”,在Table View和它的数据源之间建立连接。重复上述步骤,在Table View和代理之间建立连接。

Storyboard-TableView-Connect-Datasource

测试应用程序之前,最后一件事是给导航栏添加标题。直接选择“Recipe Book View Controller”的导航栏,填写“Attributes Inspector”下的“Title”。记住,填写完成后点击ENTER,修改生效。

Storyboard-Add-Navigation-Bar-Title

可以运行代码了。点击Run按钮,测试应用程序。如果代码正确,你应该得到一个显示菜谱列表的应用程序。这个应用和之前我们构建的SimpleTable应用非常相似。这里最主要的区别就是嵌入了Navigation Controller。

Storyboard-Navigation-Bar-Title

Prototype Cell简介

还记得如何自定义表格吗?几周前,我们向你展示了如何使用Interface Builder设计自定义的表格。简要地说,你需要为表格单独创建一个nib文件,并用编程的方法将它加载到表呢。介绍了Storyboard中的Prototype Cell以后,创建自定义表格就更简单了。ProtoType Cell可以让你在Storyboard编辑器中轻松设计好表格的布局。

我们不会在本文深入谈论自定义的细节,而仅仅是在表格中添加“Disclosure Indicator”。

选择Table View,添加Prototype Cell。在“Attributes Inspector”下,把“Prototype Cells”的值从“0”改到“1”。修改完成后,Xcode立刻自动显示prototype cell。我们也把“Style”选项从“Plain”改成“Group”,显示另一个表格风格。

Storyboard-Prototype-Cell

接下来,选择“Prototype Cell”,你应该能够为表格定制风格了。为了让每个表格显示一个提示箭头,把“Accessory”改成“Disclosure Indicator”。定义重用标识符(Reuse Identifier)比较重要。你可以认为它是表格的ID。我们可以针对某个具体的prototype cell使用它。然而,这里定义为“RecipeCell”是为了与代码中的一致。

Storyboard-Edit-Prototype-Cell

现在再次运行应用程序。它看起来又发生了些变化。我们把表的风格改为“Grouped”,并添加了提示箭头。

Storyboard-Recipe-App-with-Disclosure

添加详细视图控制器

终于到了文章最后的部分。显示菜谱详细内容的详细视图控制器(Detail View Controller)还差点儿什么呢?当用户点击菜谱中的任何一项时,详细视图控制器都应该显现。

好了,我们添加一个新的View Controller作为详细视图控制器。

Storyboard-Create-Detail-View-Controller

本文的主要目的是向你展示如果实现Navigation Controller。我们会让详细视图尽量简单,就用一个标签来显示菜名。从对象库里拖拽一个标签,放到视图的中央。你可以修改标签的字体和字号,使标签更好看一些。

接下来,我们添加一个segue来连接prototype cell和新的View Controller。添加segue对象非常简单。按住control键不放,点击prototype cell,将其拖拽到View Controller。

Storyboard-Add-Segue

放开按钮,会天出三种类型的Segue(push,modal和custom)。

Storyboard-Segues

就像前面说的,segue定义了scene之间点连接。标准的导航控制器选择“Push”类型的连接。一旦完成,Xcode会自动使用segue链接两个scene。看起来这个样子:

Storyboard-Segue-Connection

现在再运行一下应用程序。选择任何一项后,应用程序会显示详细视图控制器。虽然详细视图控制器只显示一个标签,但是导航控制器已经起作用了。

Receipe-App-With-Detail-Controller

接下来讲些什么内容?

这是一篇较长的文章,终于完成了!我希望你对Storyboard有了更好的理解,知道如何设计自己的导航控制器。然而,还有一件事情没有讲:如何将菜单的名称从“Recipe Book View Controller”传递到“Detail View Controller”呢?我会在本周末发布一篇文章对此进行介绍。

这些Storyboard,UITableView和Navigation Controller是UI元素的基础,构建iOS应用程序的时候常被用到。所以要花费一些时间来通读本文,确保对此了如指掌。

[转载此篇译文,请注明译文出处]

28 10月

Swift 2.1的新变化

文 / Russ Bishop:全能型程序员,使用C#,Objective-C和Swift语言编程,开发了奇特的应用Storm Sim Free。

原文链接:http://www.russbishop.net/swift-2-1

如果你觉得苹果的Swift语言没有赶上趟,那就要重新审视这一观点了!Xcode 7.1 Beta 2版本已经支持Swift 2.1了。我没有必要在此事无巨细一一列举,你随时可以自行查看其发布说明的。

互操作性和数据类型

  • 从C语言中引入的枚举类型自动遵循Equatable协议。所以用于样式匹配的开关语句(switch)目前不再强制去写扩展(extension)声明和操作符(operator)。
  • C语言中的非匿名联合体(union)被作为结构体(struct)引入。结构体中的每一个字段与联合体中的字段相对应。Swift中的结构体大概是用来维持一种能力的,即使用联合体给底层同一数据位(bit)投影不同的字节或字。但我还没验证过Swift结构体的域(field)是否会用同一个位进行存储。
  • C语言结构体中,相对鲜为人知的位域(bitfield)也被引入了Swift,能够使用了。
  • dispatch_block_t变回了@convention(block) () -> Void的样子,因此 dispatch_block_create又能正常工作了。

人欣喜的特性

  • 字符串可以按照字符串为单位进行插入操作。这是一直困扰我的地方。如今可以这样做了:”fancy \(thing ?? “”)”。
  • 如果只是文件中的私有内容被修改,则不会诱发与之依赖的其他文件重新编译。有些时候,这样做性能会得到大幅地提升。
  • 类型检查产生了“继续改进”模样的错误消息。我不会太多关注这一点,除非明显遇到了很常见的场景,那就是一个普通的闭包本身带有错误。这基本上是说你出错了,要么是超出了范围,要么是声明了没有用的内容;我已经习惯了剪切和粘贴,从而避免了这样的问题。我也会手动给闭包传递参数和返回类型。这部分是不错的!

协变性和逆变性

函数和闭包目前都具有协变性(covariance)和逆变性(contravariance)

这一奇特的性质表示可以通过Any -> Int这样的转换,得到String -> Any这样的结果。

换句话说,当返回类型的派生程度较目标类型大时,闭包的参数派生程度就较目标类型的小。想一想这是合理的……如果函数可以接受(Any, Any)类型的参数,那么它一定能够接受(String, Int)和(AnyObject, NSURLRequest)类型的参数。如果目标类型是AnyObject,那么UIButton肯定也没问题。

修复的问题

  • 当Objective-C中的块(block)传递给Swift中的方法时,会引起内存泄漏或崩溃,这个问题被修复了。
  • 在switch语句中使用as Type处理多种类型的转换不再引起内存泄漏。
  • 使用var或let一次声明多个全局变量不会再引起内存出错。
  • while let和while case语句不再使变量作用于其下的语句块。这样一来,可能会导致编译器崩溃,因此我担心对每个人来说这都是一种破坏。

已知的问题

  • 不带优化设置编译的情况下同时开启调试信息,这样做会导致编译器崩溃。解决方法是在非调试模式构建过程中,使用-gnone禁掉调试信息。

个人说明

紧张的日程外加生病,处理事情忙得不可开交。但我期待着恢复定期写博客的状态。熬了一段时间后,有几篇文章马上要完成了。一些是关于Swift的文章,另外一些是更泛泛的内容。

http://www.csdn.net/article/2015-10-05/2825846-swift-2

03 10月

使用Swift语言自定义Pull To Refresh控件

文 / gabriel theodoropoulos:拥有20年的编程经验,2010年下半年以来,一直专注于iOS开发,长期使用多种语言在不同平台实现软件架构解决方案。

原文链接: http://www.appcoda.com/custom-pull-to-refresh/

全世界有非常多的应用程序。要开发出新的应用,并且能够吸引用户的注意力,使用应用能够脱颖而出。毫无疑问,有人会说使简单应用出类拔萃的秘笈是开发小组(开发人员和设计人员)所使用的个性化技艺,用在大多数开发人员不做处理的小细节处。其中之一就是从本文题目中可见一斑的pull-to-refresh控件。通过本文我会展示使该控件改头换面的方法。

你知道,pull-to-refresh控件是活动指示器(经常带有短小的信息),数据加载过程中,会出现在表视图的顶部。此时的表视图还未完成刷新。实际上,pull-to-refresh控件与“Please, wait…”信息提示类似,当用户等待获取和显示新的内容时出现。于此最知名的应用就是电子邮箱。向底部拖拽邮件视图,邮件列表会得到刷新。该控件自iOS 6.0引入,之后在不计其数的应用软件中频繁地被使用。

pull-to-refresh-featured-1024x533

如果想要在应用中使用pull-to-refresh控件的话,就要查找如何去用的相关信息,也一定会找到simon的文章,文章很好地诠释了你想知道的一切。而本文要说说pull-to-refresh控件的另外一面,那就是如何自定义一个pull-to-refresh控件。这样一来,你就能够在这个不起眼儿但比较重要的细节上添加不同的风格,给应用赋予不同的视角。

长话短说,接下来看看添加自定义内容和动画的技巧,来使用任何你想要添加的对象“取代”默认的pull-to-refresh控件。要注意,下面的内容是展示你要跟着做的步骤,实际的自定义内容完全由你自己决定,更确切地说,由你的想象力决定。开始吧,用不了多久,就能创建出自定义的pull-to-refresh控件了!

示例应用概览

下面的动画就是本文中自定义的pull-to-refresh控件:

t38_1_final_sample

可以看到,表视图里面有一些模拟数据。我们的目的不是真地要从服务器获取数据,而是关注刷新的过程。活动指示器是不会出现的,取而代之的是与刷新过程持续时长相同的“APPCODA”动画。

想要知道模拟刷新的动作什么时候结束的话,我只能告诉你这里使用了计时器(NSTimer)对象。四秒钟后,该对象会结束刷新动作,将自定义的pull-to-refresh控件隐藏起来。示例中的四秒钟是随机选取的时间间隔,这当然是为了在本文中演示自定义控件的方法。很多情况下数据刷新时间都比这要短(特别是有高速英特网连接的情况下)。因此,在真实的应用中不要为了给用户头脑中留下印记而使用计时器来显示自定义的pull-to-refresh控件。要知道,特别是如果应用很棒,被用户经常使用的情况下,用户会很多机会看到这个控件。

正如你所看到的,要开发一个极其简单的项目。但没有必要从头开始,和往常一样,可以先下载一个初始项目。这是在故事板(storyboard)中所作的界面设计,还有一个Interface Builder文件,叫做RefreshContents.xib。在界面中我加入了自定义的内容,用来取代普通pull-to-refresh控件来显示的内容。实际上,该自定义控件包含了七个标签(UILabel)对象。在一起组成了“APPCODA”字样。还有一个自定义视图(UIView)作为这些标签的容器。所以必要对字体格式都做好了,正确设置了约束。随后要做的就是在视图控制器中加载这些控件,并用适当的方式处理它们。

所以,先下载初始项目,用Xcode打开它。

默认的Pull-To-Refresh控件

对示例应用要做的第一件事就是显示表视图中的模拟数据。下载的初始项目中已经包含了叫做tblDemo的IBOutlet属性,用来连接故事板中的表视图。因此需要编写表视图所需的代理(delegate)方法和数据源(datasource)方法。但是,这样做之前需要指定表视图中要显示的数据。在ViewController.swift文件中,在类内添加下列代码行:

  1. var dataArray: Array<String> = [“One”“Two”“Three”“Four”“Five”]

现在,如下所示,通过添加UITableViewDelegate和UITableViewDataSource协议修改类的第一行:

  1. class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource

然后要将ViewController的类实例作为表视图的代理和数据源:

  1. override func viewDidLoad() {
  2.     …
  3.     tblDemo.delegate = self
  4.     tblDemo.dataSource = self
  5. }

现在添加表视图的方法,用来显示模拟数据:

  1. func numberOfSectionsInTableView(tableView: UITableView) -> Int {
  2.     return 1
  3. }
  4. func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  5.     return dataArray.count
  6. }
  7. func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  8.     let cell = tableView.dequeueReusableCellWithIdentifier(“idCell”, forIndexPath: indexPath) as! UITableViewCell
  9.     cell.textLabel!.text = dataArray[indexPath.row]
  10.     return cell
  11. }
  12. func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
  13.     return 60.0
  14. }

好啦!这些都没有啥难度,运行应用后就会看到表视图显示“One, Two, …”。

t38_2_tableview_data

来看看如何显示和使用默认情况下的pull-to-refresh控件吧。我们现在这种情况是ViewController或其他什么类的子类,总之不是UITable ViewController的子类。pull-to-refresh控件必须作为表视图的子类添加进来(参见Simon的相关文章,作为UITableViewController的子类添加进来的做法)。首先在类的开始处声明refreshControl。

  1. var refreshControl: UIRefreshControl!

别忘了,尽管refreshControl由一组特别的控件组成,但声明和使用的方法与其他属性和对象别无两样。所以上面的做法很正常。

首先在viewDidLoad方法中初始化refreshControl,然后将它添加到表视图当中。

  1. override func viewDidLoad() {
  2.     …
  3.     refreshControl = UIRefreshControl()
  4.     tblDemo.addSubview(refreshControl)
  5. }

再一次运行应用,当表视图被拖拽到底部的时候,你会发现旋转的图标出现了。但不要指望这个控件再隐藏回去,这个功能不是自动产生的。必须要明确地结束刷新动作,但这是稍后要做的事情。目前的亮点是刷新指示运行良好。

t38_3_default_refresh

提示一下,这个带有刷新功能的控件,其背景色和控件颜色都可以修改。例如,下面的两行代码运行后的效果就是底色是红色,旋转图标是黄色。

  1. override func viewDidLoad() {
  2.     …
  3.     refreshControl = UIRefreshControl()
  4.     refreshControl.backgroundColor = UIColor.redColor()
  5.     refreshControl.tintColor = UIColor.yellowColor()
  6.     tblDemo.addSubview(refreshControl)
  7. }

t38_4_red_yellow_refresh

自定义控件的内容

自定义pull-to-refresh控件背后的关键点是给控件本身添加自己想要的任何额外内容,所添加的内容将作为子视图。示例程序中所谓的额外内容就是RefreshContents.xib文件。更具体地说,Interface Builder文件的内容是这个样子的:

t38_5_refresh_contents

如你所见,视图对象包含了依次七个标签。每个标签与“APPCODA”字母相对应。

接下来要做的事情非常简单:通过编程的方式将.xib文件的内容赋值给属性。更确切地说,将视图对象复制给UIView属性,所有标签都会被依次放到一个数组当中。这样做就可以把这些视图做成任何我们想要的动画效果。

现在深入到细节。首先,在类的开始处添加下列两行声明语句:

  1. var customView: UIView!
  2. var labelsArray: Array<UILabel> = []

有了上面新声明的两个属性,我们来创建一个新的自定义方法,用来加载.xib文件所有的内容:

  1. func loadCustomRefreshContents() {
  2.     let refreshContents = NSBundle.mainBundle().loadNibNamed(“RefreshContents”, owner: self, options: nil)
  3. }

我们会继续改进上面的自定义函数。下一步要做的是给customView分配上面代码加载的视图对象。注意,像上面的这种方法,从一个外部.xib文件中获取子视图,获得的是包含所有这些子视图的数组。在这里,数组只包含自定义的视图对象,也就是作为自定义视图子视图的那些标签,而不是.xib中自定义视图之外单独存在的视图对象。还要注意下面的几行代码,使自定义视图的框架与带有刷新功能旧有控件的框架要相吻合。

  1. func loadCustomRefreshContents() {
  2.     …
  3.     customView = refreshContents[0] as! UIView
  4.     customView.frame = refreshControl.bounds
  5. }

拖动表视图后刷新动作被触发,上面代码中的最后一行使自定义控件尺寸的变化与已知的约束相一致。

现在将所有的标签加载到labelsArray数组中。也许你已经注意到了,RefreshContents.xib文件中的每个标签都被分配了一个标号。从左侧开始,标号范围从1到7。我们将会利用这些标号单独访问每个标签。

  1. func loadCustomRefreshContents() {
  2.     …
  3.     for var i=0; i<customView.subviews.count; ++i {
  4.         labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
  5.     }
  6. }

最后,把自定义视图作为刷新控件的子视图添加进来:

  1. func loadCustomRefreshContents() {
  2.     …
  3.     refreshControl.addSubview(customView)
  4. }

搞定!还有一件事就是调用上面的函数,当然是在viewDidLoad方法中调用:

  1. override func viewDidLoad() {
  2.     …
  3.     loadCustomRefreshContents()
  4. }

最后要做一些重要的且必须要做的修改。在viewDidLoad方法中,将刷新控件的背景色和控件颜色设置成透明色。下面是修改后最终的代码。

  1. override func viewDidLoad() {
  2.     super.viewDidLoad()
  3.     // Do any additional setup after loading the view, typically from a nib.
  4.     tblDemo.delegate = self
  5.     tblDemo.dataSource = self
  6.     refreshControl = UIRefreshControl()
  7.     refreshControl.backgroundColor = UIColor.clearColor()
  8.     refreshControl.tintColor = UIColor.clearColor()
  9.     tblDemo.addSubview(refreshControl)
  10.     loadCustomRefreshContents()
  11. }

测试一下应用,拉动刷新时,可以看到带有标签的自定义视图取代了默认情况下的图标。当然还没有动画效果,下面我们就要实现动画效果。

t38_6_custom_contents

初始化自定义动画

这就是最终要实现的动画效果:

t38_7_refresh_animation

如果仔细观察,你会发现整个动画过程由两个子过程组成:

  • 从第一个开始,每个标签略微旋转(45度),与此同时,标签文本的颜色发生变化。下一个标签开始旋转前,当前旋转的标签状态复原。
  • 所有标签旋转过程完成后恢复原状,然后一起按比例放大,再按比例缩小。

我们把每个部分的动画作为单独的自定义函数来实现,尽量保持简单易懂。动手之前,要声明一些随后要用到的新属性。

  1. var isAnimating = false
  2. var currentColorIndex = 0
  3. var currentLabelIndex = 0

下面对每个属性做简单的介绍:

  • isAnimating标志动画过程是否发生。使用该属性用来告知是否能够开始一个新的动画过程(很明显,当一个动画开始后,我们不希望开始第二个动画)。
  • currentColorIndex属性会被用在实现的另一个自定义函数中。该函数拥有一个表示颜色(控件文本的颜色)的数组,这个属性表示下一个会被用到的标签文本颜色。
  • currentLabelIndex属性代表动画效果的第一个子过程里标签的索引。这样不仅可以确定下一个要旋转和着色的标签,还可以确定动画效果的第二个子过程(按比例放大)何时应该开始。

现在来处理动画效果的第一部分。使用一个叫做animateRefreshStep1()的函数完全实现了该功能:

  1. func animateRefreshStep1() {
  2.     isAnimating = true
  3.     UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
  4.         self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
  5.         self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()
  6.         }, completion: { (finished) -> Void in
  7.             UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
  8.                 self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformIdentity
  9.                 self.labelsArray[self.currentLabelIndex].textColor = UIColor.blackColor()
  10.                 }, completion: { (finished) -> Void in
  11.                     ++self.currentLabelIndex
  12.                     if self.currentLabelIndex < self.labelsArray.count {
  13.                         self.animateRefreshStep1()
  14.                     }
  15.                     else {
  16.                         self.animateRefreshStep2()
  17.                     }
  18.             })
  19.     })
  20. }

我们来说说上面这段代码的核心部分。一开始,isAnimating标志被置为true,因此认定没有要开始的动画过程。随后会看到判断过程。接下来你会注意到,有两个产生动画效果的代码块。第二个代码块在第一个代码块执行结束的时候开始。其中的原因有两点:

  • 我们在第一个代码块中对当前标签执行旋转和更改标签文本颜色的操作(参见关于currentLabelIndex属性的描述)。
  • 动画效果的子过程结束时,想要将标签的状态复原,这个过程要优雅平缓,而不能显得突兀不自然。很明显,第二段起到动画效果的代码块起了作用。

在产生动画效果的代码块内,completion handler(完成处理程序)检查currentLabelIndex属性值是否有效。如果有效,就再次重复调用相同的函数。这样下一个标签就产生了动画效果。另外,所有标签都执行完动画过程后,就调用动画过程的第二个子过程的自定义方法(animateRefreshStep2())。

你肯定注意到了getNextColor()函数(在第一个动画效果代码块中)。之前说过,通过这个函数会得到当前带有动画效果控件的文本颜色。一会儿再看这部分。

我们现在解决动画效果第二个子过程,实现animateRefreshStep2()函数:

  1. func animateRefreshStep2() {
  2.     UIView.animateWithDuration(0.35, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
  3.         self.labelsArray[0].transform = CGAffineTransformMakeScale(1.5, 1.5)
  4.         self.labelsArray[1].transform = CGAffineTransformMakeScale(1.5, 1.5)
  5.         self.labelsArray[2].transform = CGAffineTransformMakeScale(1.5, 1.5)
  6.         self.labelsArray[3].transform = CGAffineTransformMakeScale(1.5, 1.5)
  7.         self.labelsArray[4].transform = CGAffineTransformMakeScale(1.5, 1.5)
  8.         self.labelsArray[5].transform = CGAffineTransformMakeScale(1.5, 1.5)
  9.         self.labelsArray[6].transform = CGAffineTransformMakeScale(1.5, 1.5)
  10.         }, completion: { (finished) -> Void in
  11.             UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
  12.                 self.labelsArray[0].transform = CGAffineTransformIdentity
  13.                 self.labelsArray[1].transform = CGAffineTransformIdentity
  14.                 self.labelsArray[2].transform = CGAffineTransformIdentity
  15.                 self.labelsArray[3].transform = CGAffineTransformIdentity
  16.                 self.labelsArray[4].transform = CGAffineTransformIdentity
  17.                 self.labelsArray[5].transform = CGAffineTransformIdentity
  18.                 self.labelsArray[6].transform = CGAffineTransformIdentity
  19.                 }, completion: { (finished) -> Void in
  20.                     if self.refreshControl.refreshing {
  21.                         self.currentLabelIndex = 0
  22.                         self.animateRefreshStep1()
  23.                     }
  24.                     else {
  25.                         self.isAnimating = false
  26.                         self.currentLabelIndex = 0
  27.                         for var i=0; i<self.labelsArray.count; ++i {
  28.                             self.labelsArray[i].textColor = UIColor.blackColor()
  29.                             self.labelsArray[i].transform = CGAffineTransformIdentity
  30.                         }
  31.                     }
  32.             })
  33.     })
  34. }

我们使用了两个产生动画效果的代码块。在第一个代码块中,等比例放大了所有标签。请注意,这里无法使用循环结构完成任务(例如:for语句)。循环结构的执行过程与动画过程无关,这样在所有标签等比例放大执行完毕前循环结构早就执行结束了。

在程序执行“完成处理”部分,所有标签都完成了初始化处理。因此,这些标签再一次回到了初始状态。动画效果代码块内部的“完成处理”部分使用了if语句,刷新过程还在进行中,我们就做好了重新开始整个动画的准备。通过给currentLabelIndex属性简单地设置初始值(0)就能完成这个任务,调用第一个自定义方法来执行动画。下一步我们再来处理刷新结束后的事情。但是如果刷新结束了,可以通过修改isAnimating标志表示不再执行任何动画,而通过给所有的属性(和视图)赋初值来参与动画过程。这样一来,动画过程要在下一次拉动表视图的时候重新开始。

问题出来了,自定义动画应该在哪里开始呢?如果你仔细观察过上面的动画,就会发现每次表视图拖动完成,动画就开始一次。从编程的角度说,表视图是滚动视图(scroll view)的子类,我们所关心的代理方法是scrollViewDidEndDecelerating(_:)。每次表视图滚动停止时,该方法都会被调用。这个方法起初会检查刷新过程是否在进行。就我们这里的情况而言,就是要检查isAnimating标志的值。如果没有动画在进行中,就要调用之前实现的第一个函数来做初始化。就像下面这样的代码:

  1. func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
  2.     if refreshControl.refreshing {
  3.         if !isAnimating {
  4.             animateRefreshStep1()
  5.         }
  6.     }
  7. }

值得注意的是,上述滚动视图代理方法的使用并非强制性的,这取决于自定义pull-to-refresh控件的代码逻辑。也可以考虑其他代理方法,比如scrollViewDidScroll(_:)。

还落一部分没有说,那就是getNextColor()函数的实现:

  1. func getNextColor() -> UIColor {
  2.     var colorsArray: Array<UIColor> = [UIColor.magentaColor(), UIColor.brownColor(), UIColor.yellowColor(), UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor(), UIColor.orangeColor()]
  3.     if currentColorIndex == colorsArray.count {
  4.         currentColorIndex = 0
  5.     }
  6.     let returnColor = colorsArray[currentColorIndex]
  7.     ++currentColorIndex
  8.     return returnColor
  9. }

这非常简单!首先将一些预定义的颜色(顺序完全随机)放到数组当中。然后确认currentColorIndex属性的值。如果不具有有效的值,就要赋初值(0)。使用currentColorIndex表示颜色,然后累加其值,因此,再次getNextColor()的时候后就不会得到相同的颜色。函数最后将选定的颜色返回。

现在可以再次尝试运行应用。拖动后刷新,会看到动画效果。当然,刷新控件并不会消失。这一部分还没有实现。随便怎么去用,动画的任何一部分都可以修改。

自定义动画之外的事情

为pull-to-refresh控件创建自定义动画很有趣,但不要忘了,用户刷新不是光为了观看控件有多好看,用户需要获得新的内容。这也是自定义pull-to-refresh控件时你的主旨思想。因此,完成了上面这些内容以后,下一步就要实现真正获取数据的过程。

显然,本文并未涉及获取任何数据的操作,也没有对表视图更新内容的操作。但是,这些都无法阻止我们实施之前所描述的逻辑。所以要继续完成其余的任务,那就是创建一个新的自定义函数,名为doSomething()(蛮滑稽的名字,是不是觉得是个啥也干不了的函数)。我们要在该函数中初始化一个计时器(NSTimer)对象。4秒钟的时间间隔后,就会发出刷新过程结束的信号。在真实的应用软件中,没有必要这样做。当获取数据的过程结束时,刷新过程也就结束了。

首先,回到类定义的开始,(最后一次)声明一个对象:

  1. var timer: NSTimer!

现在来“do something”:

  1. func doSomething() {
  2.     timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: “endOfWork”, userInfo: nil, repeats: true)
  3. }

4秒钟已经足够了,这样可以不止一次地看到动画效果。在上面的方法中可以看到仅有的一行代码,预设的时间间隔过后endOfWord()就会被调用。这样就会停止刷新过程,使计时器失效:

  1. func endOfWork() {
  2.     refreshControl.endRefreshing()
  3.     timer.invalidate()
  4.     timer = nil
  5. }

这个时候我们几乎大功告成了。唯一要做的就是调用doSomething()函数,要在动画过程开始前调用。因此需要再次修改滚动视图的代理方法:

  1. func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
  2.     if refreshControl.refreshing {
  3.         if !isAnimating {
  4.             doSomething()
  5.             animateRefreshStep1()
  6.         }
  7.     }
  8. }

示例程序做好了,这回再体验一把!

t38_1_final_sample

总结

你也看到了,自定义一个pull-to-refresh控件一点儿都不难。只是把好的创意用图形表现出来,仅此而已。正如我在最后的部分所讲的,主旨在于真实数据的获取过程,而不是炫耀所创建的可视化效果。还要注意的是数据一旦更新完毕,就不要惦记着延长时间而阻止刷新控件隐藏。这样做会导致你不愿意看到的用户体验,很糟糕。如果应用软件对用户非常有帮助的话,就会有很多机会让用户领略软件的自定义效果,所以不要试图更新完毕后还阻止刷新控件隐藏。文中示例程序所自定义的内容比较简单,但足以说明问题。你知道,这是编程方面的问题,可以接受很多自定义的内容和即兴创作。当然,最终的应用软件甚至可能会彼此各不相同。自定义的pull-to-refresh控件要与不同的应用相匹配。总之,本文就此告一段落,真心希望能对你有所帮助。能够帮助你愉快地开发出自定义的pull-to-refresh控件!

作为参考,可以从这里下载整个项目

http://www.csdn.net/article/2015-09-11/2825686

28 9月

System Architecture: Strategy and Product Development for Complex Systems 1st Edition

System Architecture

 

前言

我们编著本书旨在展现强大的思想。“系统架构”在认可的目光中正在发展。这一概念出现在不同领域,包括电网系统的架构,移动支付系统的架构。这使人联想到系统架构的DNA,及其所具备竞争优势的基础。如今,已经有超过100000种带有系统架构头衔的职业,还有更多拥有不同头衔的人扮演着系统架构的角色。

强大的概念常常没有明确的适用界限。我们发现许多同事,客户和学生都对系统架构问题存在共识,但在使用范围上差别却非常之大。在已有的系统中,这一术语的使用场景大相径庭。就好比说:“这两辆山地车的架构是不同的”。

系统的架构组成内容通常是存在巨大争议的。在某些领域,这一术语被用来做并不常见的区分,把两种系统类型在相对于细节而言较高的层面做区分。就像“分组交换结构”与“电路交换结构”这样的用法。在其他领域,这一术语被用来描述整个实现过程,服务于更小的细节。比如说“作为服务层架构的软件”这样的说法。

我们的目标是展现架构这一思想的强大之处,并刻画出它的适用界限。很多强大的思想都源于潜在的事物,从而形成架构间早期的“以物易物”,看懂事物的走向,辨别出什么样的制约与机会能够成为核心价值。如果架构事无巨细包罗万象,那么就不可能在早期“以物易物”,相互融通和借鉴。如果价值丢失了其重要驱动力,也不可能存在有意义的用处。

我们编著本书是要阐明Eberhardt Rechtin的理念,架构是精深的而不是宽泛的。意在展示系统架构分析与创建的方法学,并构建出系统架构的“科学”。由于所面对的系统更加复杂,所以正文中的某些地方与产品设计原则相比不是那么规范。产品开发人员高度关注设计的地方,我们则更加关注其出现的过程——诸多作用的魅力集中起来诞生出连贯的整体。

本书包含了我们过往的经验。我们有幸参与了很多复杂系统的早期开发。包括通信,运输,移动广告,金融,机器人以及医疗设备领域。复杂性的程度从农用机具到国际空间站。

另外,我们还收录了其他系统架构的研究案例,从双动力环保汽车到商用飞机的所有这些构建原则。遇到当前系统架构所面临的挑战时,仅希望能够对系统架构的推进有所帮助。

本书主要面向两类读者——专业架构人员和工科类学生。系统架构作为思想是从业人员智慧的结晶,试图将开发新框架遇到的问题和挑战整理成典。核心受众之一就是面临架构层面决策的高级架构人员。这一领域囊括了科技业界各种高级技术和管理类的职能和角色,涵盖了软件、电子产品、工业产品、航空航天、汽车和消费品领域。

本书也集中将工科类的学生作为核心受众。书中的内容源于在过去的15年中,我在麻省理工学院所讲授的研究生课程。在那里我有幸给许多政府及私营部门的领导者传道授业,对架构的放大和剖析帮助我们理解当今的系统是如何运行的。并且我们认为在管理和技术组织中,这是一项必备的能力。

复杂系统的架构和功能

系统架构是复杂系统早期决策科学的研究。本书讲述如何在早期系统决策过程中运用经验和分析方法,如何选择符合相关需求的架构,使架构易于整合,能够灵活扩展。通过顶级专家的案例研究,展示系统架构的科学性和艺术性,案例范畴从双动力环保汽车到通讯网络和飞行器。

作者简介

Edward F. Crawley:俄罗斯斯科尔科沃科学技术学院(Skolkovo Institute of Science and Technology, Skoltech)校长,麻省理工学院(MIT)航空航天工程系教授,ACX,BioScale,DataXu和Ekotrope公司的创始人。他还在四个国家的工程院担任院士。

Bruce G. Cameron:技术战略咨询公司Technology Strategy Partners(TSP)创办人,麻省理工学院系统架构实验室主任。他曾经服务过60家世界500强公司,涵盖航空航天,高科技和个人消费品领域,目前在为轨道建立硬件系统。

Daniel Selva:美国康奈尔大学(Cornell University)机械与航空航天工程系教授。他开辟了在系统架构分析中使用机器学习技术的先河,获得了美国国家航空航天局(NASA)颁发的最佳论文奖和最热文章奖。

23 9月

使用JSON和WatchKit构建简单的天气应用程序

文 / Gregory Tareyev:iOS开发者,http://iamchill.com联合创始人。

原文链接:http://www.appcoda.com/weather-watchkit-app/

编辑注:这是论坛游客Gregory Tareyev发的帖,他是iOS开发人员和Chill(iamchill.co)的联合创始人。开发的首个可穿戴通信工具让你和朋友们动动指尖就能互动。在本文中,Gregory将分享自己做Apple Watch开发的经验,向你展示如何使用第三方API和WatchKit构建简单的天气应用程序。我们已经写了两篇与WatchKit有关的文章,都是使用Swift语言完成的。一些读者提到是否能有一篇使用Objective-C的文章,这篇便是。

Gregory的文章从这里开始……

大家好,我是Gregory Tareyev(可以来tareyev.ru联系我),是iOS开发人员和Chill(iamchill.co)的联合创始人。第一款可穿戴的通信工具终于搞定了。最近我们在Product Hunt上展示的产品有着不俗的表现,使我们在社区内获得了广泛的影响力并吸引了主要的技术博客报道这次发布。我们也在讨论使用加速器的问题,并考虑使用最好的那种。为构建应用程序而着实努力着,我相信你们每个人都可以为此而努力。

55d1486e117dc_middle

在这里想要与你分享我的Apple Watch开发经验。很有趣,也不难。

为了调动你的兴趣,我要解释在可穿戴设备上进行开发为何如此重要。

  1. 市场仍然不太大,这等同于作为先行者,你可以获得更大的市场份额。
  2. 市场将会获得极大增长,这等同于相关产品规模会随着市场同步增长。

55d14acdba958_middle

来源:图表由Business Insider提供。

在本文中,构建一个简单的Apple Watch天气应用程序,要完成两件事:

  • 如何在使用WatchKit在应用程序中解析JSON数据
  • 如何使用OpenWeatherMap的API(一旦明白其中的道理,你应该能够选择任何基于JSON的API来使用)

我们开始吧!

应用程序示例

示例程序是一款非常简单的天气应用程序,会使用OpenWeatherMap的API得到特定城市(例如:伦敦)的天气信息。这是应用程序最终样例的截图。

55d14b794590b

创建Xcode工程

首先,创建一个Single View Application,并按照如下截图设置项目信息。没错儿,我们使用Objective-C来做,我觉得Objective-C仍然很重要,还是要继续使用的。

55d14bcd1445f_middle

创建一个Watch应用程序,到Xcode顶部菜单栏选取File > New > Target…,选择Apple Watch > WatchKit App。由于使用WatchKit App模版,所以会生成构建Watch应用程序所需要的一切。

55d14ccb2a8f8_middle

取消选择“Include Notification Scene”,其余项保留,点击Finish。

simple-weather-watch-app

你会得到一条警告信息问是否要激活,点击Activate就是了。然后就会看到两个新文件夹:WatchKit Extension和WatchKit App。

simple-weather-watch-scheme

设计应用程序的用户接口

接下来,我们要着手设计应用程序的用户接口。为此,点击“SimpleWeather WatchKit App”下的Interface.Storyboard文件。

首先,从对象库(Object Library)拖拽一个Lable控件,名称设置为“Weather in London”。控件上字体大小可能需要调整一下。然后拖拽一个Image控件。接着再来一个Button控件。你会发现图像和按钮都会自动布局,垂直堆放。把按钮的Title改为“Update”,颜色调为绿色并调整图像的尺寸。

simple-weather-watch-ui

标签用来显示气象类型,而图像显示气象类型的插图。Update按钮是与用户交互的唯一元素,用来更新天气信息。

Interface Builder可以让你看到Apple Watch不同版本的视图。默认情况下,Interface Controller被设置成Any Screen Size。可以点击Interface Builder下方的“Any Screen Size”按钮,在Apple Watch 38/42mm这两种尺寸间切换。如果切换到Apple Watch 42mm,就会发现图像不完全合适,要调整图像大小,把所有控件布局弄好为止。因为更改图像大小时,Xcode会自动添加对布局做特殊处理。这种情况只在选择Apple Watch 42mm的才出现。

simple-weather-watch-ui-42

理解JSON和OpenWeatherMap的API

已经说过了,我们使用OpenWeatherMap的API来得到天气数据。为了理解它是如何工作的,要打开这个链接:http://api.openweathermap.org/data/2.5/weather?q=London,uk。拷贝结果并粘贴到http://json.parser.online.fr。你会看到结构化的JSON数据。在这里,我们感兴趣的是天气类型,也就是dictionary类型数据“weather”下的“main”键。这就是我们要在屏幕上显示的信息。

openweather-api-json

作为参考,可以在http://openweathermap.org/api查看API文档。

现在,一起来看如何解析JSON数据,并把天气信息在应用程序中展现出来。

打开Assistant Editor,按住Control键拖拽Label控件到InterfaceController.h文件的代码中做关联,把outlet命名为“weatherType”。

add-label-outlet

重复上述过程,将Image控件也做好关联,把Outlet命名为“weatherImage”。在给Update按钮做关联时,不要选择Outlet,选择Action类型,并命名为“updateAction”。

add-update-action

在InterfaceController.m文件中为updateAction方法添加逻辑代码:

- (IBAction)updateAction
{
    NSURLRequest* requestForWeatherData = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://api.openweathermap.org/data/2.5/weather?q=London,uk"]];
    NSURLResponse* response = nil;
    NSError* error = nil; //do it always
 
    NSData* data = [NSURLConnection sendSynchronousRequest:requestForWeatherData returningResponse:&response error:&error]; //for saving all of received data in non-serialized view
    
    NSMutableDictionary *allData = [ NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; //data in serialized view
    NSString* currentWeather = nil;
    
    NSArray* weather = allData[@"weather"];
    
    for (NSDictionary* weatherDictionary in weather)
    {
        currentWeather = weatherDictionary[@"main"];
    }
}

该方法中,使用了NSURLConnection对OpenWeatherMap同步发出请求。可以使用NSJSONSerialization把JSON数据转换为Foundation基础库类型(例如:NSDictionary)。我们对数据进行解析以后将天气类型存入“currentWeather”变量中。

接下来,要更新标签和图像。

weather-image-if

这样代码看起来不是太美观,对吧?

为了避免对天气类型进行硬编码,可以创建如下方法。这样会使代码更灵活。

  1. -(void)setImageAndTextWithWeather:(NSString* ) weatherName
  2. {
  3.      // Use the weather type as the weather image name. For example, if the weather name is “Rainy”, the image name is set to “rainy.jpg”. 
  4.      // Set the weather type to the given weather name
  5. }

很不错!我们把方法放到真实的代码中:

  1. -(void)setImageAndTextWithWeather:(NSString* ) weatherName
  2. {
  3.         NSString* weatherNameWithoutSpaces = [weatherName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; //delete potential spaces in JSON array
  4.         [_weatherImage setImageNamed:[weatherNameWithoutSpaces stringByAppendingString:@“.jpg”]];
  5.     NSMutableAttributedString *customString = [[NSMutableAttributedString alloc] initWithString:weatherNameWithoutSpaces];
  6.     [customString addAttribute:NSFontAttributeName
  7.                          value:[UIFont systemFontOfSize:18]
  8.                   range:NSMakeRange(0, [weatherNameWithoutSpaces length])]; //Making text more readable by creating a custom string
  9.     [_weatherType setAttributedText:customString];
  10. }

最后,在updateAction方法的结尾加上这行代码:

[self setImageAndTextWithWeather:currentWeather];

在Asset Catalog中添加图像

运行应用程序之前,下载这些图像。解压缩后把这些图像全部添加到SimpleWeather WatchKit App文件夹下的Images.xcassets里。

simple-weather-images

可以随便添加更多带有天气类型名称的图像。应用程序不用更改任何代码就可以运行。

测试应用程序

差不多了!现在可以在Apple Watch模拟器中构建并运行应用程序了。选择“WatchKitDemo WatchKit App”项目,并选择合适的设备,然后点击Run按钮,测试Apple Watch应用程序。另外,还可以修改模拟器显示的尺寸,选择Hardware > External Displays > Apple Watch – 38mm。

55d151607af6f_middle

很棒!你开发完成了一款Apple Watch天气应用程序。

作为参考,可以在这里下载最终的项目

http://www.csdn.net/article/2015-08-17/2825474/2

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

27 7月

Swift 2.0概览

文 / Russ Bishop:全能型程序员,使用C#,Objective-C和Swift语言编程,开发了奇特的应用Storm Sim Free。

原文链接:http://www.russbishop.net/swift-2-0

毫无疑问,Swift 2.0在2015全球开发者大会(Worldwide Developers Conference, WWDC 2015)上被发布的消息众人皆知。我会就该语言所发生的变化撰写一系列的文章,但目前我们先说说重点。

常规变化

  • 现在全局函数和独立(free-standing)函数都和方法一样,遵循同一个参数标签规则。不再使用#这样的语法来引用外部资源。
  • 你基本上可以使用 enum SomeEnum<T,U,V>来声明 multi-payload 风格的枚举,这样就能正常运行。这用来提示未完成的指令寄存器(IR)引发的错误。
  • 条件循环语句目前的语法是 repeat { } while(cond),不再使用 do 。
  • 关键字 do 目前用来引入一个新的作用域(这对新引进的错误处理和 defer 关键字很重要)。在 C 语言中你可以用大括号,但 Swift 里就要理解为闭包(closure)。所以使用关键字 do 可以任意引入作用域。
  • guard 语句块显式地声明你要恒成立的条件语句,恒成立时跳过整个guard 语句。这样做的好处是绑定在guard语句的变量在函数的其他部分也可用。这就避免了将所有的东西都围绕一条if语句嵌套使用来解析(unwrap)可选类型的变量。执行到函数中guard语句中的else部分,函数一定会退出并抛出异常。也可能会调用带有@noreturn标记的函数。
  • 文本注释(doc comments)换成了Markdown格式,与Playgrounds统一(Playgrounds注释格式源于功能有限的reStructured Text)。
  • 编译器对冗余的协议一致性,未被使用的绑定值以及可以设为常量的变量这些情况目前会给予警告或报错。
  • Swift语言的调用约定更加智能,能够理解 API 所发生的变化和 Swift 所给出的警告。并且还可以升级(但还不是那么完美,一定还漏掉了一些东西)。
  • find函数改名为indexOf,sort则变成了sortInPlace,sorted变成了sort。
  • String不再直接遵循序列类型(SequenceType),大概是为了避免一些新的可用协议扩展。目的是迫使你使用s.characters,s.utf8或s.utf16明确你想处理的unicode编码。
  • 允许对泛型添加公共扩展。
  • 非泛型类类型可以继承泛型类(强制类型参数固定)。
  • 便利的可失败构造器(failable initializer)可以先返回nil,而不必首先调用self.init。这是有利的一面,但指定了构造器在返回nil前仍要给所有字段初始化。所以此处还有改进的余地。

内部的可见性

这解决了单元测试中的一个较大的难点。以前的做法:

  • Swift文件包含在test target中。现在不同的模块中有重复的类的定义,出现无法将“X”转换为“X”这样非常可怕的错误,有时会无法执行特定的测试。
  • 在测试中引入引入主程序(main program)作为一个模块。现在一切都声明为public,所以对于测试来说都是可见的,有时候也包括应该声明为private的内部细节。

现在可以启用testability,它就像C#中的 InternalsVisibleTo。主应用程序目标模块的内部细节对测试模块可见。

  • 在对应用或框架的测试设置中,启用testability。
  • 在单元测试中,使用@testable import {ModuleName}。

这将导致测试忽略某些优化行为并保留稍后导入到测试模块中的那些内部符号。官方文档警告说,由于阻止了某些优化,因此这只适用于调试和测试版本。

模式匹配

switch语句的模式匹配(pattern matching)语法和“if let …, …. where”语法一直在推广。可以在任何控制流中使用逗号操作符和where条件语句。还可以使用新的case条件语句,例如:if case .Silly(let a) { }。还有一种用于Optional<T>的特殊形式:if case let a? = anOptional { }。

模式匹配在循环语句中也可以使用:for case let thing? in array { }。

这又是值得单独成文的另一个特性。

Objective-C的泛型和__kindof的用法

在关于Swift的帖子里谈论这个做甚?它的作用是使某些衔接更加清晰和简便。不求在这篇文章中面面具到,我会在单起一篇文章阐述它。

错误处理

这不是我们一贯所认识的异常,这是一个使函数提前返回Result<T, Error>的操作,单隐藏了所有提前返回的对象,也隐藏了错误解析(error unwrapping)过程等内容。

  1. let systemAttributes: [NSObject: AnyObject]?
  2. do {
  3.     systemAttributes = try NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectoryPath.last!)
  4. catch _ {
  5.     systemAttributes = nil
  6. }

它完美地与Objective-C进行互操作,Swift语言中,将标记为throws的方法作为选择器。这是使用NSError的方法,-(BOOL or nullable type)someMethodTakingParam:(type)param error:(NSError **),这种样式会自动引入标记为throws的方法。

应该明白的是这并不像Java中已经被检查过的异常(checked exception)那样。Swift语言并不关心异常的类型,或者处理或者不处理。这又是值得单独成文的另一功能特性。

Defer关键字

关键字defer也很重要,因为它可以取代传统C风格的“if(err) goto cleanup”。获得资源后接着就是defer { release_resource() }。然后不管函数返回结果如何,获得的资源都将被清理。这也意味着资源的释放紧随获取资源之后。这看起来不起眼儿,实则很重要。

NS_OPTIONS和OptionSetType

位操作枚举(bitwise enumeration)与数组风格的语法相结合,而不使用管道符“ | ”按位操作,并且具有所有范围的集合操作功能。检查一下是否具有contains功能的标志,或能够执行像isSubsetOf和isDisjointWith等这样集合操作的其他功能。这是显著的改进,表达了不直接对位进行操作的意愿。

这种变化意味着位操作枚举实际上不再是枚举了。将这些位操作枚举声明为结构体,实现OptionSetType协议,提供rawValue属性。并且创建值作为结构体的静态成员。Swift便会搞定其余的一切,自动提供所有集合的操作。这是我希望将来看到的更加明了的语法内容。

协议扩展

协议如今可以被扩展了,包括与类型约束有关的通用协议。还可以自己提供协议的默认实现。

先前,你不能你说:“我要使用方法X来扩展CollectionType,但只有集合中的类型满足某些条件才可以”。现在,你可以这么做,并且很多像map,filter和sort这样的全局函数已经进行了扩展。

这样就解决了很多痛点,这也是值得单独成文的内容。同时,要看看WWDC的面向协议编程(Protocol Oriented Programming)了解一些细节。

API审计

大量的API已经进一步进行了审计而更合理。举几个例子:

  • UITableView的dequeueReusableCellWithIdentifier方法现在返回UITableViewCell?类型的对象。
  • UIKit的属性现在也被声明为了实际的属性。

用translatesAutoresizingMaskToConstraints = false 代替了setTranslatesAutoresizingMaskToConstrains(false)。

Availability属性

@available属性自Swift 1.2就存在了并且后续支持得很好。添加了一个新的陌生语法if#available(),为处理版本检查提供了支持。而不是插入你喜欢的方法。

遗憾的是你不能只声明一个属性 UISearchController并将target设置为iOS 7,然后只允许访问类中的属性。Swift希望整个类的定义都可以或者不可以。

也可以不再采用协议,除非支持target设置中所有的操作系统版本,除非将整个类标记为只在更新的操作系统版本可用。

这意味着使用if #available()存在单独的子类和对创建适当对象的保护。

尽管如此,我个人还是发现了一个Bug,应用在iOS4.0-4.1发生崩溃,由于编译器没有发出警告,方法只在iOS4.2才引入,因此我犹如与定时炸弹相伴。

C函数指针

Swift现在可以使用C函数指针,CFunctionPointer已不复存在。任何全局函数,嵌套函数和不捕获状态的闭包都可以作为一个C函数指针直接传递。你也可以调用来自C程序的函数。

你可以显示地使用新属性@convention(c),表示函数应该使用C调用约定,简单痛快!尽管我想不出在此对块(block)的支持有何用,作为所发生变化的一部分,@objc_block也被删掉了,使用@convention(block)取而代之。@convention(swift)默认支持所有函数和闭包。

这并不是编程语言所特有的。iOS 9含有不同版本的Swift标准库,并且在未来系统中将添加修正后的Swift标准库。结合新的App Thining技术,下载过程中苹果商店会将Swift标准库剥离出去的。我仍然在追根溯源地探求这究竟是如何工作的。

遗漏

明显的一个遗漏是处理异步代码。

苹果公司为我们提供了GCD,这是一个强大的基础类库,可以构建很多异步操作和并发原语。

然而,这些天我们做的每件事,构建用户接口和API都需要考虑异步性和并发性。我们把一个文件读操作锁定一段时间,对用户来说整个世界就都静止了。

这是个持续的痛点,不是多大的事儿,但如果经常性地每天重复,恐怕也是不行的。

C#和JavaScript都采用了async/await来为异步代码提供一流的语言支持。我想很多人都想知道,Swift会提供什么样的语法糖来帮助我们在实现异步操作方面确保正确性。我不知道在Swift 2.0发布的时间框架内是否会看到什么,但愿能有好的东西出现吧!

开放源码

宣布的内容中,反响最强烈的无疑是Swift开放源代码。苹果公司已经承诺在今年底前开放源码,我们也没有理由对此表示怀疑。与苹果公司编译器团队成员讨论过程中,他们看起来似乎对此由衷地兴奋,无论如何坚决要干成这件事(我有点小失望,他们没有打造出经典的苹果然后宣布开源,但我仍然对此消息发自内心地感到高兴)。

结论

Swift 2.0有很多令人喜爱之处。苹果公司的Swift团队向大家承诺他们会迅速行动。到目前为止这些承诺已经被兑现。成为苹果平台上的开发人员是一个激动人心的时刻。

http://www.csdn.net/article/2015-07-23/2825280-swift-2

23 7月

Swift Pocket Reference 1st Edition

bookCover

作者简介

Anthony Gray:在高等教育领域有长期工作经历,为学术研究人员和优秀的学生们提供技术和系统支持。他热爱教学,最热爱的学科是操作系统,计算机图形学和OpenGL动画。最近醉心于iOS移动开发。业余时间用编程来过瘾,在http://squidman.net可见一斑。他默默地期盼着你能够为6502处理器手写汇编程序,再时不时地写个模拟器的那一天。

内容介绍

无论是Swift应用程序的开发和调试,还是苹果平台的多范式编程语言,你都能迅速从中找到答案。这本袖珍参考手册是你在工作中学习现代编程语言Swift的完美工具。内容囊括了类型安全、泛型、类型推断、闭包、元组、自动内存管理以及对Unicode的支持。

可同时支持Cocoa和Cocoa Touch框架的Swift语言,能与Objective-C并肩工作。两者都可调用其他语言编写的API。Swift还在不断地改进,而毋庸置疑的是苹果公司将把Swift作为未来iOS和OS X平台的程序设计语言。

手册的主要内容:

  • Swift自有的交互调试控制台和实时调试功能
  • 支持的数据类型,如字符串、数据和字典
  • 变量和常量
  • 程序流:循环和条件式执行
  • 类、结构、枚举、函数和协议
  • 闭包:类似于Objective-C中的块和C#中的λ表达式
  • 可选类型:显式地声明无值变量
  • 运算符:运算符重载和自定义运算符
  • 访问控制:限制访问类型、方法和属性
  • 内置全局函数及其参数的要求
19 7月

Swift 2.0中的错误处理

文 / Juan Pablo Claude:来自智利首都圣地亚哥,毕业于美国北卡罗莱纳大学教堂山分校(University of North Carolina at Chapel Hill),获化学博士学位,后入阿拉巴马大学伯明翰分校(University of Alabama at Birmingham)任教。2005年底作为Cocoa和Django框架程序开发人员加入Big Nerd Ranch。此前,有过DOS环境下C编程,Windows环境下使用C++编写数据分析应用程序的经历。

原文链接:https://www.bignerdranch.com/blog/error-handling-in-swift-2/

苹果公司在今年的全球开发者大会(Worldwide Developers Conference, WWDC)上宣布推出Swift2.0,该语言的首席架构师Chris Lattner表示,Swift2.0主要在语言基本语法、安全性和格式美观度这三方面进行了改进。除了这些新的功能特性,还有对语法的优化、修饰及美化,最后是Swift 1.x中最具影响力的错误处理机制。

这是因为你根本无法回避它。如果打算使用Swift 2.0的话,必须接受错误处理这样的机制,并且错误处理机制将改变Cocoa和Cocoa Touch框架中使用NSError与方法交互的方式。

历史一瞬:不起眼的开端

我们都知道,Swift语言作为Objective-C当前替代语言被推出,是OS X和iOS应用程序开发的“通用语”。在最初的版本中,Objective-C没有原生的异常处理机制。后来通过添加NSException类,还有 NS_DURING, NS_HANDLER和 NS_ENDHANDLER宏才有了异常处理。这种方案现在被称为“经典的异常处理”,还有这些宏都是基于setjmp()和longjmp()这两个C语言函数的。

异常捕获(exception-catching)看起来如下图所示,在NS_DURING和NS_HANDLER宏之间抛出的任何异常都将会导致在NS_HANDLER和NS_ENDHANDLER宏之间执行相应的代码。

  1. NS_DURING
  2.     // Call a dangerous method or function that raises an exception:
  3.     [obj someRiskyMethod];
  4. NS_HANDLER
  5.     NSLog(@“Oh no!”);
  6.     [anotherObj makeItRight];
  7. NS_ENDHANDLER

下面是立刻能触发抛出异常的方法(现在仍然可用):

  1. – (void)someRiskyMethod
  2. {
  3.     [NSException raise:@“Kablam”
  4.                 format:@“This method is not implemented yet. Do not call!”];
  5. }

可以想象,这种手工处理异常的方式戏弄的是早期Cocoa框架程序开发人员。但是这些程序员还不至于到这份儿上,因为他们很少使用这种方式。无论在Cocoa还是Cocoa Touch框架下,异常通常都被归为灾难性的,不可恢复的错误,比如程序员造成的错误。上面的-someRiskyMethod就是很好的例子,由于实现部分没有准备好而引发了异常。在Cocoa和Cocoa Touch框架中,可恢复的错误由稍后讨论的NSError类来处理。

原生的异常处理

我想由于Objective-C中的经典异常处理机制对应的手工处理方式让人感觉闹心,于是苹果公司在Mac OS X 10.3(2003年10月)中发布了原生的异常处理机制,彼时还没有iOS系统。这本质上是将C++的异常处理嫁接到了Objective-C。异常处理的结构目前看起来是这样的:

  1. @try {
  2.     [obj someRiskyMethod];
  3. }
  4. @catch (SomeClass *exception) {
  5.     // Handle the error.
  6.     // Can use the exception object to gather information.
  7. }
  8. @catch (SomeOtherClass *exception) {
  9.     // …
  10. }
  11. @catch (id allTheRest) {
  12.     // …
  13. }
  14. @finally {
  15.     // Code that is executed whether an exception is thrown or not.
  16.     // Use for cleanup.
  17. }

原生的异常处理使你有机会为每个异常类型指定不同@catch部分。无论@try结果如何,@finally都要执行其对应的代码。

尽管原生的异常处理如所预期的那样抛出一个NSException异常,但是最明确的方法还是“@throw <expression>;”语句。通常你抛出的是NSException实例,但说不定什么对象会被抛出。

NSError

尽管Objective-C原生与经典的异常处理有许多优点,但Cocoa和Cocoa Touch框架应用程序开发人员仍然很少使用异常,而是限制程序出现程序员所导致的不可恢复的错误。使用NSError类处理可恢复的错误,这种方法早于使用异常处理。Swift 1.x也继承了NSError的样式。

在Swift 1.x中,Cocoa和Cocoa Touch的方法和函数可能不会返回一个布尔类型的false或者nil来表示一个失败(failure)的对象。另外,NSErrorPointer对象会被当作一个参数返回特定的失败信息。下面是个典型的例子:

  1. // A local variable to store an error object if one comes back:
  2. var error: NSError?
  3. // success is a Bool:
  4. let success = someString.writeToURL(someURL,
  5.                                     atomically: true,
  6.                                     encoding: NSUTF8StringEncoding,
  7.                                     error: &error)
  8. if !success {
  9.     // Log information about the error:
  10.     println(“Error writing to URL: \(error!)”)
  11. }

程序员所导致的错误可以用Swift标准库(Swift Standard Library)函数fatalError(“Error message”)来标记,将其在控制台记录为错误消息并无条件中止执行。还可以使用assert(), assertionFailure(), precondition()和preconditionFailure()这些函数。

Swift第一次发布时,一些非苹果平台开发人员已经准备好了火把和干草叉。他们声称Swift不能算是“真正的语言”,因为它缺乏异常处理。但是,Cocoa和Cocoa Touch社区对此不予理睬,我们知道NSError和NSException那个时候就存在了。就我个人而言,我相信苹果公司仍然在思考实现错误和异常处理的正确方式。我还认为直到问题解决了,苹果公司才会公开Swift源码。这一切问题在Swift 2.0中全被扫清了。

Swift 2.0中的错误处理

在Swift 2.0中,如果想要抛出错误,那么抛出的对象必须符合ErrorType协议。可能正如你所愿,NSError就符合该协议。枚举在这里用来给错误进行分类。

  1. enum AwfulError: ErrorType {
  2.     case Bad
  3.     case Worse
  4.     case Terrible
  5. }

然后如果一个可能抛出一个或多个错误的函数或方法会被抛出关键字标记:

  1. func doDangerousStuff() throws -> SomeObject {
  2.     // If something bad happens throw the error:
  3.     throw AwfulError.Bad
  4.     // If something worse happens, throw another error: 
  5.     throw AwfulError.Worse
  6.     // If something terrible happens, you know what to do: 
  7.     throw AwfulError.Terrible
  8.     // If you made it here, you can return:
  9.     return SomeObject()
  10. }

为了捕获错误,新型的do-catch语句出现了:

  1. do {
  2.     let theResult = try obj.doDangerousStuff()
  3. }
  4. catch AwfulError.Bad {
  5.     // Deal with badness.
  6. }
  7. catch AwfulError.Worse {
  8.     // Deal with worseness.
  9. }
  10. catch AwfulError.Terrible {
  11.     // Deal with terribleness.
  12. }
  13. catch ErrorType {
  14.     // Unexpected error!
  15. }

这个do-catch语句和switch语句有一些相似之处,被捕获的错误详尽无遗,因此你可以使用这种样式来捕获抛出的错误。还要注意关键字try的使用。它是为了明确地标示抛出的代码行,因此当阅读代码的时候,你能够立刻找到错误在哪里。

关键字try的的变体是“try!”。这个关键字大概也适用于那些程序员导致的错误。如果使用“try!”标记一个被调用的抛出对象中的方法,你等于告诉编译器这个错误永远不会发生,并且你也不需要捕获它。如果该语句本身产生了错误(error),应用程序会停止执行,那么你就要开始调试了。

  1. let theResult = try! obj.doDangerousStuff()

与Cocoa和Cocoa Touch框架间的交互

现在的问题是,你如何在Swift 2.0中处理爷爷级的NSError API呢?苹果公司已经Swift 2.0中为统一代码行为作了大量工作,并且已经为未来写入Swift的框架准备方法。Cocoa和Cocoa Touch中可以产生NSError实例的方法和函数有苹果公司的签名( signature),可以自动转换为Swift新的错误处理方式。

例如,这个NSString的构造器( initializer)在Swift 1.x中就有以下签名:

  1. convenience init?(contentsOfFile path: String,
  2.                   encoding enc: UInt,
  3.                   error error: NSErrorPointer)

Swift 2.0中,签名被转换成:

  1. convenience init(contentsOfFile path: String,
  2.                  encoding enc: UInt) throws

注意:在Swift 2.0中,构造器不再被标记为failable,它并不需要NSErrorPointer来做参数,而是使用抛出异常的方式显式地指示潜在的失败。

下面的例子使用了这种新的签名:

  1. do {
  2.     let str = try NSString(contentsOfFile: “Foo.bar”,
  3.                            encoding: NSUTF8StringEncoding)
  4. }
  5. catch let error as NSError {
  6.     print(error.localizedDescription)
  7. }

注意错误是如何被捕获的,并且如何被转换成了一个NSError实例,这样你就可以获取与其相似API的信息了。事实上,任何ErrorType类型的实例都可以转换成NSError类型。

最后说说@finally

细心的读者可能已经注意到,Swift 2.0引入了一个新的do-catch语句,而不是do-catch-finally。不管是否捕捉到错误的情况下,你如何指定必须运行的代码呢?为此,现在可以使用defer语句,用来推迟代码块的执行直到当前的作用域结束。

  1. // Some scope:
  2. {
  3.     // Get some resource.
  4.     defer {
  5.         // Release resource.
  6.     }
  7.     // Do things with the resource.
  8.     // Possibly return early if an error occurs.
  9. // Deferred code is executed at the end of the scope.

Swift 2.0将Cocoa和Cocoa Touch的错误处理机制凝聚为具有现代风格的用法,这是一项伟大的工作,也会使许多程序员倍感亲切。统一行为是不错的定位,会使Swift语言和其所继承的框架逐步发展。

http://www.csdn.net/article/2015-07-01/2825095/2

17 6月

Swift语言那些鲜为人知的特性

文 / Russ Bishop:全能型程序员,使用C#,Objective-C和Swift语言编程,开发了奇特的应用Storm Sim Free。

原文链接:http://www.russbishop.net/more-swift-attributes

Swift语言有各种各样缺乏(或没有)文档记录的特性(attribute)放在那里等着被使用。让我们一起看看其中的一些特性:

@inline

这个特性为编译器提供了内联提示。有效的取值是__always和never。除非我认为必须要用这两个值,否则就不会使用它(特别是__always)。到目前为止与其相关的规则还不是很明确,在有限的测试下,它可以正常地工作,但还要视具体情况而定。

进一步的解释:尽管底层虚拟机(Low Level Virtual Machine, LLVM)有强制内联的概念,但我们目前还不知道这个@inline特性是否与其直接映射,也不知道是否存在大小方面的限制,但这将会导致编译器忽略这一点而跳过内联。理论上说应该是这样的,但我不保证一定是。

注意(当优化设置关闭时)在调试模式下的构建将忽略@inline。

@transparent

我最初并未将这个特性列出来。该特性会导致编译器在管道(pipeline)中更早地将函数内联。它用于“像+(Int, Int)这样非常原始的函数”,而“不应该用于独立函数”

甚至在没有优化设置的调试模式下@transparent特性函数就会被内联,所以在调用“1+1”这样的函数的时候并不会特别慢。另外这个特性与@inline(__always)非常类似。

@availability

这个特性可以用来标识某些函数只在某些平台或版本上可用。第一个参数是平台,可以用星号(*)代表一切可用,还可以是iOS或OSX。因为如果需要针对不同的平台,就要指定多个@availability属性。

如果需要表示该函数在某个给定的平台完全不可用时,可以将第二个参数置为unavailable。此外,还可以用introduced,deprecated和obsoleted来指定一个或是多个版本的组合:obsoleted意味着该项已经删除,deprecated仅仅表示如果使用就会给予警告。最后你可以设置message的值,如果该项被使用了就由编译器输出。下面是一些例子:

  1. @availability(*, unavailable)
  2. func foo() {}
  3. @availability(iOS, unavailable, message=“you can’t call this”)
  4. func foo2() {}
  5. @availability(OSX, introduced=10.4, deprecated=10.6, obsoleted=10.10)
  6. @availability(iOS, introduced=5.0, deprecated=7.0)
  7. func foo3() {}

@noreturn

正如该特性所描述的那样:编译器可以假定这个函数是一个永远循环运行的起点,例如while true { },或者假定是函数abort或者exit进程的情况。

评论者Marco Masser指出,如果调用另一个被标志为@noreturn的函数,那么编译器会忽略掉当前函数中缺失的返回值(missing return values),因为编译器理解程序的控制流。

@asmname

该属性给出了函数、方法或属性实现的符号名称。如果你已经知道对应的函数参数及其类型,那么就可以直接调用Swift的内部标准库函数,甚至不用头文件,也可以方便地调用C语言编写的函数:

  1. @asmname(“function”) func f()

@unsafe_no_objc_tagged_pointer

上面这个仍然是个谜,但我猜测它是在告诉Swift与Objective-C联系的时候不要使用tagged pointer

@semantics

这又是另一个谜。参数看起来像是array.mutate_unknown或array.init这样的字符串数组。想必这是要告诉编译器(或静态分析器)函数是如何工作的。

结论

谁还需要乏味老套的 @objc和@autoclosure呢?还是算了吧!

我今年会去参加苹果全球开发者大会(WWDC),你也一定要去呀!

http://www.csdn.net/article/2015-06-08/2824887-more-swift-attributes

09 6月

ARC中retain cycle揭秘

文 / Ignacio Nieto Carvajal:自由开发者,精通iOS和Mac OSX应用程序开发,还包括Web服务和UX/UI设计。

原文链接:http://digitalleaves.com/blog/2015/05/demystifying-retain-cycles-in-arc/

ARC中的retain cycle就像日本B级恐怖电影一样。开始使用Cocoa或Cocoa Touch做开发时,你甚至不会在意它的存在。直到有一天应用程序由于内存泄漏而出现了崩溃现象,你才意识到它们的存在,看到像幽灵一样的retain cycle无处不在。随着岁月流逝,你学会适应它们,发现它们,避免它们……但最终恐慌还在,无孔不入。

包括我在内,对于许多开发人员来说,ARC的最令人失望之处莫过于苹果公司让ARC来管理内存。不幸的是ARC没有循环引用检测器,因此很容易出现retain cycle现象,从而迫使开发人员在编码时要采取特殊的预防措施。

对于iOS开发人员来说,retain cycle是个难点。在网上有很多误导信息[1][2],人们所给出的这些错误信息和修复方法甚至会导致应用出现新的问题,甚至崩溃掉,基于这样的情况,本文中我会阐明主题,给读者一些启发。

相关理论一瞥

Cocoa框架内存管理可以追溯到MRR(Manual Retain Release),在MRR中,开发人员在创建对象的时候,要为每个内存中的对象声明所有权。并且,当不再需要该对象时,要放弃所有权。MRR通过引用计数系统来实现这种所有权机制。每个对象都被分配一个计数器指示被“拥有”了多少次,每次加一,释放对象的时候每次减一。当引用计数变成零的时候,该对象将不复存在。对于开发人员来说,不得不手动维护引用计数真的是很烦人的事情,于是苹果公司引入了自动引用计数(Automated Reference Counting, ARC)机制,免得开发人员手动添加保留(retain)和释放(release)指令,让他们专注于解决应用程序的问题。在ARC环境下,开发人员要将一个变量定义为“strong”或“weak”。使用weak的话应用程序中被声明的对象不会被retain,而使用strong声明的对象将会被retain,并且其引用计数加一。

为什么要在乎?

ARC的问题在于容易导致retain cycle,它发生在两个不同的对象间彼此包含强引用的时候。试想一个Book对象包含一系列的Page对象,每个Page对象有个属性指向该页所在的这本书。当你释放掉指向Book和Page的变量时,Book和Page之间还存在着强引用。因此,即使没有变量指向Book和Page了,Book和Page及其所占用的内存也不会被释放掉。

不幸之处在于并非所有retain cycle都很容易被发现。对象之间的传递关系(A引用B,B转而引用C,C引用A)会导致retain cycle。更糟糕的是Objective-C里的块(block)和Swift里的闭包(closure)都被认为是独立的内存对象。因此,任何在块或闭包内对像的引用都将会对其变量做retain操作。因此,如果对象仍然retain这个块的话,就会导致潜在的retain cycle发生。

retain cycle可以成为应用程序潜在的危害,导致内存消耗过高,性能低下和崩溃。但还没有来自于苹果公司的文档,针对retain cycle可能发生的不同场景,以及如何避免进行描述。这导致了一些误解并形成了不良的编程习惯。

用例场景

那么闲话少说,我们一起来分析一些场景,确定它们是否会导致retain cycle以及如何避免:

父子对象关系

这是retain cycle的典型例子。不幸的是这也是苹果公司唯一给出相关解决方案文档的例子。就是我上面描述的Book和Page对象的例子。这种情况的典型解决方案是把Child类里面的代表父类的变量定义成weak,这样就可以避免retain cycle。

  1. class Parent {
  2.    var name: String
  3.    var child: Child?
  4.    init(name: String) {
  5.       self.name = name
  6.    }
  7. }
  8. class Child {
  9.    var name: String
  10.    weak var parent: Parent!
  11.    init(name: String, parent: Parent) {
  12.       self.name = name
  13.       self.parent = parent
  14.    }
  15. }

在Swift语言中,代表父类的变量是个弱变量的事实迫使我们将其定义为可选类型。不使用可选类型的另一种做法是将父类型对象声明为“unowned”(意味着我们不会对变量声明进行内存管理或声明所有权)。然而在这种情况下,我们必须非常仔细地确保只有一个Child实例指向Parent,Parent就不能是nil,否则程序就会崩溃:

  1. class Parent {
  2.    var name: String
  3.    var child: Child?
  4.    init(name: String) {
  5.       self.name = name
  6.    }
  7. }
  8. class Child {
  9.    var name: String
  10.    unowned var parent: Parent
  11.    init(name: String, parent: Parent) {
  12.       self.name = name
  13.       self.parent = parent
  14.    }
  15. }
  16. var parent: Parent! = Parent(name: “John”)
  17. var child: Child! = Child(name: “Alan”, parent: parent)
  18. parent = nil
  19. child.parent <== possible crash here!

一般来说公认的做法是父对象必须拥有(强引用)其子对象,这些子对象对其父对象应该只保持一个弱引用。这同样适用于集合,集合必须拥有其所包含的对象。

包含在实例变量中的块和闭包

另一个经典的例子虽然不是那么直观,但正如我们之前所说的那样,闭包和块是独立的内存对象,并retain了它们所引用的对象。因此如果我们有一个包含闭包变量的类,这个变量又恰好引用了其所拥有对象的属性或方法,由于闭包通过创建一个强引用而“捕获”了自己,就会有retain cycle发生。

  1. class MyClass {
  2.    lazy var myClosureVar = {
  3.       self.doSomething()
  4.    }
  5. }

这种情况下的解决方法是将自身定义成“weak”版本,并且将此弱引用赋给闭包或块。在Objective-C语言中,要定义个新的变量:

 

  1. – (id) init() {
  2.    __weak MyClass * weakSelf = self;
  3.    self.myClosureVar = ^{
  4.       [weakSelf doSomething];
  5.    }
  6. }

而在Swift语言中,我们只需要指定“[weak self] in”作为闭包的启动参数:

  1. var myClosureVar = {
  2.    [weak self] in
  3.    self?.doSomething()
  4. }

这样一来,当闭包快执行完毕时,self变量不会被强制retain,因此会得到释放,打破循环。注意,当声明为weak时,self在闭包内是如何变成可选类型的。

GCD中的dispatch_async

与一贯的认识相反,dispatch_async本身并不会导致retain cycle。

  1. dispatch_async(queue, { () -> Void in
  2.    self.doSomething();
  3. });

在这里,闭包对self强引用,但是类(self)的实例对闭包没有任何强引用,因此一旦闭包结束,它将被释放,也不会有环出现,然而,有的时候会被(错误地)认为这种情况会导致retain cycle。一些开发人员甚至一针见血地指出将块或闭包内所有对“self”的引用都声明为weak:

  1. dispatch_async(queue, {
  2.    [weak self] in
  3.    self?.doSomething()
  4. })

在我看来,对每种情况都这样做并不是好的做法。我们假设某个对象启动了一个长时间的后台任务(比如从网络上下载一些东西),然后调用了一个“self”方法。如果对self传递一个弱引用的话,那么类会在闭包结束之前完成它的生命周期。因此,当调用 doSomething()的时候,类的实例已经不存在了,所以这个方法永远不会被执行。这种情况下,(苹果公司)建议的解决方案是对闭包内的弱引用(???)声明一个强引用:

  1. dispatch_async(queue, {
  2.    [weak self] in
  3.    if let strongSelf = self {
  4.       strongSelf.doSomething()
  5.    }
  6. })

我不仅发现语法冗长单调,缺乏直观性,甚至令人感到厌恶,而且使闭包作为独立的处理实体的打算也落空了。我认为要理解对象的生命周期,确切地明白什么时候应该为实例声明一个内部的weak版,还要知道对象存续期间会有哪些影响。但话又说回来,这正是我解决应用程序的问题时分散我注意力的地方,如果Cocoa框架中没有使用ARC的话,这些都是没有必要写的代码。

局部的闭包和块

没有引用或包含任何实例或类变量的函数局部闭包和块本身不会导致retain cycle。常见的例子就是UIView的animateWithDuration方法:

  1. func myMethod() {
  2.    …
  3.    UIView.animateWithDuration(0.5, animations: { () -> Void in
  4.       self.someOutlet.alpha = 1.0
  5.       self.someMethod()
  6.    })
  7. }

对于dispatch_async和其他GCD相关的方法,我们不用担心没有被类实例强引用的局部闭包和块,它们不会发生retain cycle。

代理方案

代理(delegation)是使用弱引用避免retain cycle的一个典型场景。将委托声明为weak一直是一种不错的做法(并且还算安全)。在Objective-C中:

  1. @property (nonatomic, weak) id <MyCustomDelegate> delegate;

Swift中:

  1. weak var delegate: MyCustomDelegate?

在大多数情况下,对象的代理实例化了该对象,或者被认为比对象存在的时间更长久(并且对代理方法做出反应)。因此,在一个设计得很好的类中,我们不会找到与对象声明周期相关的任何问题。

使用Instruments来调试retain cycle

不管我如何努力避免retain cycle,忘记去引入一个弱引用并意外地创建一个(多谢ARC!)这样的事情迟早还有发生。幸运的是XCode套件中的Instruments应用程序是很不错的工具,用于检测和定位retain cycle。一旦开发阶段结束,在提交苹果商店之前就分析(profile)你的应用是个不错的习惯。Instruments有很多模版用来分析应用的不同方面,但我们感兴趣的是“Leaks”选项。

一旦打开Instruments,你就应该启动应用程序并作一些交互操作,特别是在要测试的区域或试图控制器上。任何检测到的泄漏都将会在“Leaks”部分出现一条红线。辅助视图包含一个区域,Instruments用来显示发生泄漏处的堆栈追踪,用来找到问题所在,甚至使你可以直接定位到造成问题的代码。

http://www.csdn.net/article/2015-05-27/2824782-demystifying-retain-cycles-in-arc

15 5月

对近期AFNetworking安全漏洞引发担忧的回应

文 / Alamofire Software Foundation(由Mattt Thompson创立)

Mattt Thompson:毕业于卡内基·梅隆大学(Carnegie Mellon University),获哲学及语言学学士学位。著名的iOS网络通信类库AFNetworking的作者,此外,他还开发了Postgres.app、ASCIIwwdc和Nomad等热门开源项目。

原文链接:https://gist.github.com/AlamofireSoftwareFoundation/f784f18f949b95ab733a

前一段时间,大量关于AFNetworking存在安全漏洞的消息被公之于众,大约1000种应用程序被指称,由于SSL存在Bug,导致这些应用程序容易遭到攻击。这些文章对此存在一些错误的,带有误导性的说法。

我们对此做出回应来澄清和纠正这些说法。

背景信息

就此事,对于那些不熟悉AFNetworking的人,这里有一些与其相关的细节需要了解。

  • AFNetworking是一个第三方的开源库,置于苹果内置框架之上,提供便利的功能。
  • AFSecurityPolicy是AFNetworking的组件之一,根据应用程序设置的规则处理验证挑战(authentication challenge)。包括通过HTTPS连接时,对服务端返回的X.509证书的评估。
  • 证书锁定(certificate pinning)是一项信息安全技术,它在标准TLS评估的基础上做了改进,通过服务器显式地发送证书来匹配包含在客户端的凭证(credentials)。AFNetworking从版本1.2.0开始一直提供证书锁定技术。
  • 中间人攻击(Man-in-the-Middle Attack, MitM)是在客户端和服务器之间插入攻击者本身,使两边都认为自己和对方在直接通信。
  • 这样的一种攻击方式在客户端和服务器之间会涉及某个不可信的Wi-Fi接入点。没有对响应进行恰当地验证,攻击者就可以拦截通讯信息,用户凭证或其他敏感信息因而会遭到泄露。
  • AFNetworking官方文档强烈建议应用程序要通过HTTPS进行通信,并且使用证书或公钥锁定技术来弱化MitM这种攻击行为。工程中所包含的示例代码遵循了这些建议,在应用程序中展示了证书锁定的使用方式。

事件的时间表

收拾心情,整理思绪。下面是与这一事件相关事件的时间表:

  • 2015年2月12日,AFNetworking 2.5.1发布。这一版本包含了一个补丁,修改了证书的安全策略验证方式,将SSLPinningMode修改为AFSSLPinningModeNone。验证挑战过程中,服务器的证书默认是不会被验证的,除非客户端存在与众不同的配置行为,比如使用SSL pinning这样的证书绑定技术。
  • 2015年3月12日,我们从这个GitHub Issue开始意识到上述的修改行为所造成的影响。
  • 2015年3月26日,来自Minded Security Research的Simone Bovi和Mauro Gentile发表了一篇博文,详细说明了AFNetworking 2.5.1潜在的MitM方面的漏洞。
  • 同样在2015年3月26日,AFNetworking 2.5.2发布。这个版本恢复了先前的证书安全策略评估方式。如果安全策略将validatesDomainName设置为YES,那么SSLPinningMode将会被修改为AFSSLPinningModeNone。
  • 2015年4月20日,AFNetworking 2.5.3发布了,该版本做了额外的修改。对所有的安全策略默认设置validatesDomainName为YES。
  • 2015年4月21日,GitHub上新开了一个Issue,要求完善AFNetworking的文档和与安全相关的功能特性。我们正就此积极努力地对参考材料做全面彻底的修改。
  • 还是在2015年4月20日,来自SourceDNA的Nate Lawson发表了一篇博文,宣称某个工具可以识别苹果商店中使用了AFNetworking2.5.1的应用程序。包括来自Ars Technica的Dan Goodin在内的许多记者,在其公布的文章中都引用了该博文并提及了博文的作者。这些公开发布的内容都没有就AFNetworking维护人员的解决方案进行整理而置评。
  • 2015年4月24日,SourceDNA在其后续发布的博文中声称,存在更多带有安全漏洞的应用程序,来自Ars Technica的Dan Goodin随后也发表了一篇带有相同效果的文章。需要强调的是,没有任何一篇公开发表的文章对AFNetworking维护人员的解决方案进行整理而置评。

AFNetworking用户力所能及的事情

下面是AFNetworking用户需要了解的力所能及的事情:

如果应用程序通过HTTPS通信,却没有启用SSL pinning技术的话,应用程序就可能容易受到所报道的MitM攻击。

AFSecurityPolicy的官方文档中的内容:

将固定的SSL证书( pinned SSL)添加到应用程序中,可以帮助应用避免中间人攻击以及存在的其他漏洞。大力鼓励应用程序在处理用户数据或财务信息的时候,所有通信途径都通过HTTPS协议,配置并启用SSL pinning技术。

无论在什么时候,遵循这些建议的应用程序都不应该存在上述安全漏洞。

如果应用使用HTTPS进行通信,并且启用了SSL pinning技术,就不容易遭到所说的MitM攻击

很大一部分应用程序使用AFNetworking是通过推荐的步骤启用了SSL证书或public key pinning机制的,这些应用程序不太不容遭到上面说的MitM攻击。

如果使用的是先前的版本AFNetworking,我们强烈推荐您升级到版本2.5.3

AFNetworking 2.5.1和2.5.2包含的默认配置不适合产品级应用程序——特别是如果不进行额外的配置,就不会提供必要的TLS评估。

AFNetworking 2.5.3默认配置更加安全,即使不使用SSL pinning也会进行域名验证。

如果使用NSURLConnection或NSURLSession代替AFNetworking的话,你仍然需要检查验证挑战的实现方式

苹果内置的NSURLConnection和NSURLSession,还有Security框架所提供的API,都具有对凭证验证的安全实现方式。但是,像任何API一样,某个应用程序的安全性取决于这些API的使用方法。

是否使用AFNetworking本身并不能保证你的应用程序能够灵活应对MitM那样的攻击。是否能够灵活应对攻击完全取决于应用程序使用可用API的方法。在产品环境下,测试应用程序的健壮性和网络安全性最终是开发人员的职责。

如果你要对某个安全漏洞进行吐槽,请发送电子邮件到security@alamofire.org吧!

我们会尽快回应并提出解决方案。

如果你想为AFNetworking更出色而做出贡献,那就在GitHub上提交一个Issue和Pull Request吧!

AFNetworking是开源项目,这意味着每个人都有机会为其更出色而贡献力量,欢迎提交IssuePull Request

对负责任的安全研究和新闻工作的看法

对于终端用户来说,安全研究人员在软件安全方面起着核心作用。研究人员与软件开发人员共同努力,通过遵循既定的负责任的漏洞披露(responsible disclosure),可以快速修复漏洞。同时,将当前用户的风险降到最低。

然而,我们对一些研究人员的做法和一些对AFNetworking的披露感到失望。作为人尽皆知的话题,信息安全从未如此重要。安全研究人员和记者拥有独特的机会来让读者了解这些事实。但不幸的是,这样的披露方式常常通过制造恐惧来增加点击量,而不是客观详实的报道。

尚未有确切的方法可以表明多少应用程序受此问题的影响;这些对安全问题严重程度的揣测摧毁了对问题准确和适度的回应。同样地,根据揣测提出的权利主张对企业和其客户也帮助甚少。

事实上,编写安全的软件一直以来都是一项巨大的挑战。这需要多学科的工程师们一起合作完成。这是一个极其重要的任务,最好由理性且富有责任心的人参与。

作为软件维护人员,我们有很多事情可以做得更好,并积极采取措施来完善自身的组织和流程。从今天起,我们期待与信息安全社区的成员紧密合作,负责任地寻找并解决任何安全漏洞。

对负责任的开源项目维护工作的看法

我们真诚地向使用AFNetworking的开发者和iOS整个开发者社区表示歉意。

作为著名开源项目的维护者,我们有责任提供与高标准相契合的软件,该软件将作为应用程序不可或缺的一部分。我们却没有对应该尽快更新的版本做出回应。我们未能向您有效传达至关重要的安全信息。这所有的一切,我们表示真诚的歉意并负全责。

在未来的几周内,我们将推出重组后的AFNetworking及其相关项目,以确保稳定的通信顺利进行。从用户的角度看,这意味着更加频繁地发布版本,更高的透明度,处理问题与合并请求过程中更多的反馈。我们为此而感到兴奋。

http://www.csdn.net/article/2015-05-12/2824671-AFNetworking

11 5月

应用下载的价值究竟在什么地方?

文 / Scott Stanchak:《纽约时报》移动业务营销总监。此前,他负责Avis Budget Group公司移动营销及产品策略。他还是Winery Passport这款热门应用的创建者,在其站点BakBurner.com撰写了移动营销的相关文章。

原文链接:http://venturebeat.com/2015/02/14/the-value-of-a-download/

下载的价值有多大?

对于我来说,下载没有太大的价值,但是给下载贴上价签以后,它的价值就产生了。

自从我在大公司负责移动营销工作开始,我遇到过许多人,他们把应用的下载量作为应用是否成功的唯一指标。这大概是因为“下载”这个词语与移动应用程序关联最紧密。但除非你的应用程序是付费下载,否则很大的安装量并不代表这些用户完成了你所期望的行动。

我非常清楚下载量和下载速度都会对应用商店排名结果产生影响。这是图表意义上的成功。然而,如果不在市场营销和拓展宣传中投入资金以保持地位,这样的排名常常是难以为继的。如果把钱花在这些地方的话,你要相信,这些钱会生出更多钱的愿景定会实现。

即使是为那些大量廉价的下载花钱做铺天盖地的广告,用来驱动其在应用商店的排名,这样的做法都比单纯的下载更能实现我们所期望的行动。提升排名背后的目的则是使下载量实现连续的增长,这会有更多的益处,非常有可能引出有意图的行动(intended action)。

行动(Action

你的“行动”是什么?这是我在做演示或与他人谈论移动营销问题时常问的一个问题。

在《纽约时报》任职期间,我主要的行动是订阅。在Avis Budget Group公司,我的行动是预定出租车。在Winery Passport公司工作的时候,我的行动是让用户通过一个应用来付费验证他们的护照。多年来,我在营销活动中花费的每一块钱都是在寻找用户的意图,这其中包括订阅者的意图,租客和买方的意图。

无论你的行动是什么,它必须带有一个指定的值。原因很简单:确定投资回报率(Return On Investment, ROI)。我提到的所有这些行动显然都是货币上的价值。但还存在很多并未对我们的产品或服务付费的用户。

工作中观察到所期望的简单行动可能是不同的——登记,电子邮件注册,还有额外的文章阅读等等。总体上了解用户在每个场景中的价值所在,不仅有助于解释递增的营销利润,而且如果这些简单行动没有达到目的,那么这种认识对理解ROI也比较关键。

测量你的行动

几年前,你对应用安装活动唯一可以测量的数据就是点击率。除此之外,你还会从Apple,Google或者其他第三方分析工具得到支持数据,但这些数据并没有与用户间交流的活动。从而无法将特定的点击下载与应用中的事件相联系。

谢天谢地,那些日子已经一去不复返了。第三方公司提出了解决方案,可以测量所有营销活动,不仅是点击,还有用户行动和其间的许多其他事件(event)。

它是这样工作的:当用户点击一个活动后,他们的设备ID将被这些第三方公司的某一家存下来。当同用户再次启动应用的时候,SDK(Software Development Kit)就会读取设备ID,并验证这个设备ID是否与某个活动关联过。如果关联过,就即刻建立起了一对一的关系。你可以追踪该用户对应用的使用状况,包括这些用户是否进行过购买的行动。

未使用这些第三方公司的工具在应用商店对应用进行追踪,应用是不会有市场的。无论是否花钱动用了媒体,或者本身就是媒体。这就像网站上被推荐的资源,有其价值所在,而在这些资源如果在手机上,则会更有价值。

通过移动应用程序成功测得用户有意图的行动,你将能够做出更明智的决定,进而会调整活动的开支,还会对网络的部署进行调整。这会使你获得更高的投资回报率,意味着花同样的钱会获得更大的下载量和更多有行动的用户。

就像漏斗,顶部较小,但底部的回报会更大。这就是作为底部的下载最为有价值的地方。

01 5月

我们需要计划,但需要评估吗?

文 / Johanna Rothman:帮助管理人员和团队解决问题并交付产品。她最近的一本著作是Manage your Project Portfolio: Increase Your Capacity and Finish More Projects。你还可以在jrothman.com阅读博文及她的著作。

原文链接:http://java.dzone.com/articles/we-need-planning-do-we-need?mz=123873-agile

我在撰写项目管理著作的过程中,领教了对大量的工作进行评估的难处。

Essays on EstimationManage It这两本书中,我推荐了几种评估方法,每种方法都表明,对于一个工程或项目,不存在一个绝对的计划日期。

你能做些什么呢?有下面几种选择:

  1. 计划和再计划。要决定对当前的工程或项目投资多少。请参见(图示中)工程/项目进展。还要决定你打算对工程/项目投资多久。
  2. 按照预定日期工作。如果你以迭代的方式或增量的方式进行开发,预定日期就最合适不过了。如果经常有内部版本发布的话,你可以看到工程/项目的进展和重新制定的计划(如果使用瀑布模型进行开发的话,就不太可能满足你想要的所有功能,也无法避免不想看到的缺陷。而如果你以迭代或增量的方式开发的话,就可以按照要达到的目标去完善计划。注意,我说的是完善计划,而不是评估计划)。
  3. 对三种不同情况的评估:任何事情都顺利的情况,正常情况和最不利的情况。这是著名的PERT(计划评审技术,Program Evaluation an Review Technique)估计。
  4. 用百分比给所做的评估一个信心值。你认为在某个特定日期前后能够发布,那么对这个评估有几分把握?这种情况再计划一下最好不过了,那样你可以更新信心值了。

这里的每一项表明所做的计划带有不确定性。工程或项目规模越大,表现出的不确定性明显。

如果你使用敏捷(agile)方法的话,可能根本就不需要进行评估。这些年来,我管理了许多工程和项目。从来没有人过问项目的成本和对进度的评估。有时候只是告诉我那些预期目标,那些目标是如此地乐观,以至于我不得不对其进行总体的评估(gross estimate),用来解释为什么不能满足这个日期要求。

然而,除了总体的评估外,我并不相信其他方式。我相信敏捷方法的路线图(roadmap)。建立项目迭代增量,观察项目进度,并且可以决定下一步做什么,这都是不错的点子。

RoadMapForProduct

当你看到这幅路线图的时候,就会了解每个月如何为内部版本做计划了。

对于内部版本,每一个人都可以看到工程或项目的进度。

另外,我们按季度发布的外部版本。此时你的工程或项目也许无法按季度向客户发布。但这应该是商务上的决定,而不是由你决定的,因为此时工程或项目未达到预期标准而不能发布。如果你没有采用敏捷方法推进项目的话,可能就无法按季度发布。但我姑且认为你会用敏捷方法进行开发的。

AgileRoadmapForProduct
在单季度的视图中,你可以看到“最简可行产品”(Minimum Viable Product, MVP)。

在这里,特别是在项目的开端。你可能需要将MVP换成MIFS(Minimum Indispensable Feature Set)。

如果总是出现这么小的“Story”的话,那就可以统计他们的数量,而不是评估这些“Story”,这样做会更加接近事实。特别是在项目初期,你就不用花时间去评估,也就不用开发相应的产品。

在工程或项目的初期,你对于存在的风险和陷阱了解得最少,甚至连MVP和MIFS都不了解。但是,如果肯向用户发布一些东西,你就可以得到自己所需的反馈。

反馈会告诉你什么:

  • 这些“Story”是否数量太多而无法统计?如果是这样的话,你创建的任何评估就都是错误的。
  • 是否提交了有价值的工作?如果是这样的话,公司将会对此继续进行投资。
  • 是否正在为最具价值的任务工作成果?在工程/项目进行的过程中,其价值所在会发生变化。有时你实现此功能可能在价值上逊色于彼功能。有的时候,你会意识到你正在实现的功能其实已经被实现了。
  • 是否要停下来?如果我们提前完成了项目发布的要求,那很棒!如果还没有做好发布的准备,我们要做些什么呢?

这都是我做评估的心得体会。如果你只拿出一套评估方案,那些经理是不会相信你的。他们会向你施压,说些“少花钱多办事”那样不合常理的话。他们会这样讲:“如果我们裁掉测试的话,你可以进度更快,对吗”?(对此类言论的回应是:“NO!在技术层面所亏欠或产生的债务越少,我们才能进行得越快”。)

但是你确实需要对项目的路线图做规划,包括所积压的工作。如果没有一个路线图,展示人们所期待的东西,这些人就不会相信你,觉得你很假。由于团队所提交的内容并不是每一项都是产品负责人期待的东西,因此你要重新计划路线图。不过没关系的,尽早从反馈中得知团队力所能及之事就很赞。

向要求做评估的人提出的两个问题:

  1. 在团队停下来之前,你愿意为这个工程/计划投入多少资金?
  2. 这个工程/项目对你有多大价值?

如果你为最有价值的工程/项目工作,那还做什么评估呢?你需要了解的是在项目进行中公司打算在这个项目上投多少资金。如果所做的不是最有价值的工程/项目,你也要知道公司打算对其投资多少。或者你要有一个预定日期,有了这个日期,你可以用迭代法或增量法逐步发布版本,直到完成目标。

这就是对评估的风险管理,还有再计划。没错儿,我就是一位“0评估”粉丝,因为我们划分的粒度越小,就越容易明白如何进行计划和再计划。

我们需要计划和再计划。如果使用迭代法和增量法的话,我认为不需要事无巨细的评估。

14 4月

组建团队:找人看重经验还是天资

文 / Mike Bushong:作家及市场营销高级管理人员。

原文链接:http://architects.dzone.com/articles/building-teams-experience-or?mz=123873-agile

很明显,在招贤纳士的时候,你会希望碰到一位集经验与天资于一身的人。但鱼和熊掌不可兼得。因此,被迫在两者间做出选择之时,你会怎样选择?选择经验还是天资?

选择经验的情况

选择经验的主要情况是为了降低风险。如果你要确定一个人是否能够胜任工作,唯一的方法是这个人能否证明之前完成过同样的工作。在这种情况下,你寻找的人有具备的能力与其过去完成的任务相匹配。

这种类型的招聘标准在很多情况下都能派得上用场。在管理者职业生涯的早期,其兴趣所在可能是建立一个稳定的基础。有一些可以依赖的稳定成员,并且这些成员不需要过多的个人管理,这就是个好的开端。在其他情况下,如果所要完成的工作主要是大家公认的基础性工作,在公司中很常见,对于这种工作,常常经验胜过天资。还有就是团队中已经有很多天资颇高而缺乏经验的成员,这些成员需要帮助,引导他们避开一些常见的陷阱。

经验不足的情况

选择有经验的人面临的挑战是很难有人告诉你这个人拥有多少经验。

例如,我们所接触到的简历中,有的人每2~3年换一次工作。在这些人当中,有人在这一领域拥有15年的经验吗?还是说更像有5段2~3年的经验?长时间在一个位置的工作较同样的事情一遍又一遍地重复做,得到的经验是不同的。当遇到在一个特定领域具有多年经验的候选人时,我们需要注意的是,年限代表经验的层次和水平。否则我们寻找拥有经验的人而最终却雇佣到的人却缺乏经验。

与经验相关的另一个问题是经验前后关联,需要因地制宜的。我以前的雇主聘请了一位从思科公司过来的高管,他的做法很快就悄无声息地在办公室不胫而走。他打算采用思科的方案,而思科方案不关注所用操作系统的版本号。事实上,思科所扮演的角色很大程度上并不适用于处于挑战者角色的公司。这意味着思科(至少是某个特定角色)的经验与当前公司关系不大。只在特定的情境下觊觎他人的经验很危险。

最后,我们要以怎样的频度雇佣领路人式的员工呢?“引领”和“带头”这样的字眼我们经常看到,并且为之兴奋。但在这里我们要注意,将要聘用的人是否真的是带头人,还是仅仅管理别人的工作而已。为什么你会认为新任高管做的第一件事是把所有旧同事找来做事呢?因为在某种程度上,这些管理者经常录用已经做过自己所需要东西的那些人。

选择天资的情况

如果说经验限制了底线能有多低,那么天资就是天花板能爬到的高度。

当你雇佣颇具天资的人时,关心更多的不是这个人做过什么,而是他们学习的速度有多快。雇佣能够迅速学习并极具驱动力的人,你会得到其远远超出预期的能力。这些潜在的能力经常可以带动团队的其他成员。尽管不具有相关的经验,作为同事间的正向压力源,这样的人最合适不过了。

一般来说,大多数人都会认同低买高卖的准则。招聘颇有天资的人理念上正好与之契合。而且这的确是持续超出预期的唯一途径。经验形成了预期,而天资可以超出这些预期。如果团队偏向了某一方面,那么你可以问自己是否正在避免失败或在促进成功。哪种做法都是可行的,都是针对不同对象的,但你应该明确地针对自己的目标做出选择。

天资不足以奏效的情况

不给颇有天资的人提供成功所必要的工具和指导,这可不是个办法。每个人都会犯同样错误的原因在于每个人都会遇到同样的问题,那些才华横溢的新员工可能也不例外。所以,如果不把这些新员工与具有经验的员工安排在一起的话,那么新员工也会犯他人犯过的错误。

这意味着你需要对这些颇有天资的雇员进行指导,但这需要协调。无论是他们直接的反馈,还是来自团队中资深员工的声音。你需要确保团队中的这些青年才俊正在学习别人的经验。当然,这些颇具天资的雇员具有可培养潜力时,这样的策略才奏效。这就是为什么需要寻找具有可塑性的人才。当你发现这两种品质都具备的时候,那这个人就可以是你所建立团队中的一员了。

你倾向于哪种选择?

这取决于你是要发展自己的事业,还是要守住事业。如果你需要确保处理方案风险更低(例如,你被找来修复一些东西)的话,那么可能会倾向于选择经验,用来控制导致问题的欠缺。但是,如果你试图超越预期,那么唯一的办法就是选择那些颇具天资的人。

这里值得注意的是,无论天资还是经验都不是年龄的代名词。你可以找到符合这两种情况的所有年龄段的人。问题在于你寻找的人是否真的曾经做过你所需的工作,或者这个人真的是颇具天资,可以帮你开拓道路。

这样的选择会对面试有何影响?

从偏好的角度讲,你做的面试可能会发生什么样的变化呢?如果你在寻找颇具天资的人,那就会很少问已有的代码中例子,会更多针对一些难于解决的问题询问思路。你想要了解的是这些应聘者的能力所在,而不是他们对以前工作所回忆起的内容。如果你对应聘者的经验感兴趣的话,那么对工作的介绍和描述会更重要。

问题的关键是面试的差别取决于你想要找什么样的人。如果对两种情况做没有区别的面试,那么你可能并没有有效地选择针对天资或经验的问题。要考虑一下如何直截了当地调整自己的策略。

底线

我个人认为你无法做到完美的平衡——哪怕是一点点,你也会出现这样或那样的倾斜。如果让我被迫选择向哪个方向倾斜,我倾向于向天资倾斜。我从来没有过自己能够完全胜任的工作,但是我的工作机会一个接着一个。自己不断地成长意味着我从来不会太安逸,这样会迫使我越来越努力。

成功的反义词不是失败,而是平庸。成功与失败有很多共同之处。真正要避免的是平庸。你周围的一切都要求你脱颖而出,但不要陷入这种永久的,千篇一律的状态。天资才是脱颖而出的关键。如果你真的想成功,就需要另辟蹊径,这可能意味着,一路走来,你必须在颇有天资的人身上多投入些资本。

http://www.csdn.net/article/2015-03-24/2824295

04 4月

2015年,数据集成进入云端

文 / George Gallegos:在2011年被任命为开源数据集成方案提供商Jitterbit公司首席执行官,曾是甲骨文公司的区域副总裁,拥有18年企业管理和销售经验。

原文链接:http://www.informationweek.com/cloud/software-as-a-service/integration-moves-into-the-cloud-in-2015/a/d-id/1318654

Jitterbit公司首席执行官George Gallegos认为,如果程序开发进入云端,那么应用程序及数据的集成也应该紧随其后。

编者按:本期内容是Jitterbit公司首席执行官Gallegos撰写的文章,其所在公司是“基于云端的数据集成平台”提供商,Jitterbit是此类服务商中的一员,除此之外还有Dell Boomi,以及开源SOA解决方案提供商WSO2等。

随着基于云端的应用、虚拟服务以及可连接设备和传感器在企业的部门间应用数量的激增,企业所使用的技术呈现离散状态。

2014年,企业意识到自身需要进行整合,将已经采用的内部部署(on-premise)系统与新的云解决方案进行整合,以此来补足自身IT部分旧有的系统设计。

已经建立起的中间件,包括企业服务总线(Enterprise Service Bus, ESB)和自定义代码(custom code)都已经被证明不足以满足需求。在新的一年,还未采取快速集成策略的企业要集中精力找到更快,更有效的方法,用以连接膨胀的数据与服务端点(endpoint)。

许多公司会建立起新的云服务中心,还有云服务代理商,也就是业内所说的云服务经纪人(Could Service Brokerage, CSB),用来提供与IT相关的服务以及API,作为其占有市场的一部分,企业用户可以使用这些服务和API,而管理则交给IT部门。这些基于云端的集成平台将为企业提供最快最简单的方式,确保公司那些新技术可以迅速与其他部分相连接,像公司内部以前部署的系统。

ESB的覆灭与实时(Real-Time)API集成的兴起

大数据和云计算的重要性迫使这些企业重新思考他们旧有的IT基础设施。这样就可以利用来自物联网、云端、社会和移动服务端点的巨大信息库了。

例如,物联网对大容量存储的要求到达了一个新的水平,对实时操作的面向外部的API也有个更高的要求。ESB集成模型是10年前构建的了,在云计算变革来临前,设计上从未考虑现代电子商务的规模和速度。相反,下一代集成技术,如PaaS集成平台,在数分钟内将会提供全新层次的敏捷性和连通性,而再用不着几个月了。

在2015年,三个方面的集成创新将会重新定义企业级架构,分别是虚拟集成,基于云端的集成以及被托管的API。

  • 虚拟集成允许应用程序从外部系统访问信息,而不用真的将数据从一个应用程序转移到另一个,系统之间直接可见。需要的存储空间更小,并且不再需要去同步冗余的数据集。
  • 云集成平台开启了混合集成的新一代。这种集成方式在一个单独的云平台上被设计并管理,而这种平台可以安全地运行在云端或防火墙之后。
  • 托管API是数字企业(digital business)新构建的内容。托管集成API不是父辈的SOA API,它们是专门为了当今社会所需的规模和安全性所设计的,可以提供巨大的吞吐量,有限流,分析,监控和集中式的生命周期管理功能。

来吧!这些新的集成创新将重新定义我们与客户,合作伙伴和员工之间的经营方式。

自助集成:终端用户的转变

随着更多业务流程利用相互关联的应用和数据,这些数据集成解决方案将会被越来越多的普通企业用户采纳。取代依靠IT专业人员在各种应用程序中去关联不同的业务流程,这些现代集成方案的浪潮将让使用此业务的用户来管理方案间的联系,这些方案是他们最了解的。由于终端用户需要更多地控制他们日常生活中用到的技术,企业的IT部门和集成服务商将会把注意力放到开放API上,这样可以让全公司的人很快地将传统的解决方案与新技术相融合。

云服务代理商的走俏表明了某种类型的企业数量,这类企业采用更加开放,包容性强的新技术。他们正通过像BlueWolfCloud Sherpas这样的云服务代理商,来调整近期来自企业的影响。这些影响源于现代化企业所需服务端点爆炸式的增长。

云服务代理商在企业级应用技术领域将扮演更重要的角色,帮助填平IT技术关键决策者与业务线上用户之间的沟壑,这些用户一直吵着要使用最新的解决方案。使用这些业务的用户对IT决策过程的影响凸显了企业级应用技术的新道路。那就是让用户满意,让他们选择自己想要的解决方案,并确保这些解决方案可以迅速、轻松地连接到企业的其他平台。

云服务中心和代理商的出现也会给基于云端的集成平台提供商带来机会。通过提供即装即用(out-of-the-box)的集成方案脱离旧有的解决方案。这样就使整个企业更容易迅速采用新技术。

根据IDG的研究报告,这些企业希望他们所管理的数据量在来年达到70%以上的增幅。这些数据对一系列用户来说都是比较重要的,不仅是相应的分析师和企业高管,还包括那些现场服务代表、销售人员、营销人员和产品经理。

随着公司的信息主管的职能从传递信息转变为交付服务,他们将需要采用专门为混合架构所设计的那些新平台,还要为数字企业以及云端的扩展来专门设计新平台,以此来为彼此创造出最大价值。

02 4月

为什么要认真对待与在线学习有关的应用

文 / Ryan Craig:University Ventures公司的执行董事,该公司专注于推动大专院校进行创新。Ryan即将出版新书College Disrupted: The Great Unbundling of Higher Education”。

原文链接:http://venturebeat.com/2015/01/17/why-online-learning-needs-to-get-serious-about-apps/

上个月,中国最大的民营教育服务提供商新东方教育科技集团与在中国拥有近5亿WeChat用户,领先的短信息服务提供商的腾讯公司,同意启动一项以网络聊天为核心的集成服务。每一所大学都应该问的问题是:这对在线学习(online learning)意味着什么?

仅在10年前,我们当中的许多人认为无论何时何地,在线学习意味着取得学位。现在有了智能手机,移动学习成为可能。学习效果却打了折扣。在线学习三位一体的构成包括:内容和讲座(content/lecture),讨论(discussion)以及评估(assessment),但这些内容都没有迁移到智能手机上。也就是说:

导航部分:在智能手机上对课程导航具有一定的挑战性。不仅因为智能手机的屏幕小,导航功能需要有足够大的按钮区域可供手指去选择,而且还有我们使用智能手机体验上的差异。如果内容加载时间超过5秒钟的话,那么相比使用PC的用户,智能手机用户更容易放弃要浏览的内容。

讨论:讨论版在智能手机上使用效果很好。到处都可以参与讨论,同步的视频讨论也不错,但也不适合所有人。会话时间也要比课堂上短得多。然而,使用智能手机提交的帖子要比曾使用的其他方式发的信息要短得多,也没有那么正式。

测试:使用智能手机,无论在课堂环境中还是在课外,对于形成性评价(formative assessment)来说,效果还不错,而终结性评价(summative assessment)效果却不怎么样。

基本脉络应该已经显现了。任何能够在短期内完成的事情,使用智能手机效果都还不错。

所以在线学习是走上坡路还是下坡路,这是否取决于我们对短期学习的整合的能力呢?就像新东方教育科技集团与腾讯公司正在做的事情那样。当然会有很多在智能手机上使用的与之类似的和教育相关的应用(application),但这不会存在正式教育中的那些评估测试和被认可的证书。这主要有两方面原因。首先,需要授予这种证书的各色课程和终结性评估无法在短时间内被整合。其次,对于和教育相关的应用来说,可能不必对这些东西进行整合,因为这些应用开辟的是一条新路。

这些应用是智能手机所面临挑战的解决方案

目前,智能手机用户使用应用所进行会话的时长是浏览网页的3倍。应用的使用频率也比网站要多得多。用户花在应用上的总时间目前以超过20%的速度逐年递增。行业知名的互联网统计公司ComScore称,对于智能手机用户来说,手机上使用应用所占时间超过了其所使用数字媒体时间的五成,18至24岁的用户是主力军。

应用是为特定目的而开发的。因此不难想象,一个是经济学入门的应用,而另一个则是心理学入门的应用。应用是有价值,有目的的模拟,也是学习体验的游戏化模式,它还将现实世界的这些内容(如学生的地理位置)作为输入数据融进了学习。

但如今像“mLearning”这样的移动教学平台忽略了这一点。当前高校中使用的应用根本没有正式的教学内容,全都是关于选课,校园定位的应用。还有就是一些无关紧要的学习内容(像医学缩略语词典)。Blackboard Mobile Learn这个站点就是个典型的例子,有一定的教育意义。所有类别产品的功能(公告、成绩)都以图片的方式展示在智能手机上,但却无法获得展板上所展示的实质课程资料。这是“mCheating”,不是“mLearning”。

目前虽然大部分在线学位课程是以学习管理系统的形式存在的,都声称自己是“移动平台”,这些平台所属机构认为,解决移动问题的方法是简单地基于传统的线上学习系统架构,让移动终端访问教学资源。这等同于认为机构的在线学习模式解决方案就是向YouTube或iTunes放置讲座视频。

26 3月

几种监督式学习算法的比较

文 / Kevin Markham:电脑工程师;热衷烹饪,痴迷戏剧,偶尔参加铁人三项运动;为General Assembly讲授为期11周的数据科学课程,在SlideRule指导学生学习数据科学,还是约翰·霍普金斯大学数据科学Coursera专项课程的社区教学助理(CTA);业余时间制作视频教程参加Kaggle的比赛。

原文链接:http://www.dataschool.io/comparing-supervised-learning-algorithms/

我所讲授的数据科学课程涵盖了该领域大部分内容,但尤其关注机器学习(machine learning。除了讲授模型的评估过程和度量方法以外,很明显,我们还讲算法本身,主要是监督式学习(supervised learning)算法。

在为期11周的课程接近尾声的时候,我们花了几个小时检查所用的课程资料。我们希望学生能够逐渐理解他们所学的东西。要掌握的技能之一就是在解决机器学习的问题时,有能力在不同的监督式学习算法中做出明智的选择。虽然使用“蛮力”(把每种情况都试一遍,看看哪种最好)的方法有其价值所在,但比这价值大得多的是能够在不同算法之间做出权衡利弊的选择

我决定为学生们组织一场比赛。我给他们一张空白的表格,列出所讲的监督式学习算法,让学生从几个不同维度对这些算法进行比较。我在网上找到了这样的表格,自己先弄一张再说,一起看看

贡献出这张表格,有两个原因:其一,它可以用来讲课或者学习下载下来拿去用吧)。其二,这张表格需要完善,人多力量大!

这张表格是集鄙人经验与研究的产物,在任何这些算法的领域,我都称不上是专家。如果你有能够改进表格的建议,给我留言哟!

  • 是否在我的这些评估中存在误导或错误?(当然啦,有些比较维度本身就带有主观性。)
  • 是否存在应该添加到表格中的其他“重要的”对比维度?
  • 是否还有其他你希望加入到这张表格的算法?(目前,表格中只有我所讲授的算法。)

我意识到每种算法的特征及相应的评价都可以基于数据的具体情况(以及数据的调优程度)发生变化。因此有人会认为试图做“客观”的比较是欠考虑的。然而,我认为作为监督式学习算法入门一般性参考,这张表仍然有其价值所在。

Duang~Duang~Duang~!

学习资源

补充说明:要做“锐推”,请点击这里,还可以来KaggleDataTau讨论!

http://www.csdn.net/article/2015-03-17/2824232

19 3月

IT部门的压力:管理、同情心以及对陌生人的友好

文 / David Wagner:在商业及科技领域10余年笔耕不辍,曾在《麻省-斯隆管理评论》(MIT,Sloan,Management,Review)做助理编辑,Enterpriseefficiency.com执行总编,还是许多顶级商业及科技咨询公司的自由撰稿人。

原文链接:
http://www.informationweek.com/strategic-cio/it-stress-management-empathy-and-the-kindness-of-strangers/a/d-id/1318657

压力会让我们更容易忽略其他人的感受,对于当今企业中的IT部门来说这是不利的。

如果你管理着一个四五个人以上的团队,那么你就要清楚压力是怎样改变团队成员之间相互回应方式的。一项研究表明,人们面临压力的时候,对于陌生人就会缺乏同情心,而不管是否喜欢这个人。在大型团队或部门中,你所管理的这些人彼此之间实际上是完全陌生的。

美国与加拿大在McGill大学共同进行的研究部分地表明,对人类和小鼠进行测试,当使用药物阻断了应激激素时,人类以及啮齿类动物显示出更强的移情作用,也就是更具有同情心。举例来说,学生被要求观看一个人正在经历痛苦,用手拿着一桶冰的场景。这些学生可能会过度估计这个人所承受的痛苦,还可能会心怀同情地去触摸自己的手。除非这些学生自己处于压力之下,否则他们不太可能感受到一个陌生人的痛苦。

那么,作为一位IT经理应该做什么呢?购买一些抗应激(抗压)药物吗?让人感到高兴的是,研究还表明,如果在测试前被研究对象与一个陌生人玩有趣的视频游戏,那么这两个被测试对象可能会更有同情心。研究人员认为,单纯地减小压力完全可以使人更具有同情心。

IT从业者们的工作地点日益分散,常常跨时区,甚至跨越大洋。自己的团队成员往往会是陌生人。即使不是,IT从业者也比以往任何时候更多地被要求在不同职能团队工作,其工作与“业务”关联更紧密,与一些陌生人一起工作。

我们经常建议信息主管和IT经理们,对IT部门进行管理的最佳方式是感知企业的痛处。对IT管理来说,管理缺乏同情心的员工是不太好。如果员工处于这样的状态,那么你很可能会听到他们取笑其他部门,对其他部门的服务意识淡薄,而且很少会主动在业务上为这些部门服务。

如果你碰巧在客场为客户提供服务,那样风险可能会更大。有些人可能不会认为在同一家公司工作的人是陌生人,但如果在压力下工作的员工去客户的办公场地办公的话,那么在他眼里一切都是陌生人。

当然,你不能总是邀请你的客户或者业务线主管来玩游戏。你必须要为自己的团队找到一种管理压力的方式。美国职业压力协会表示,25%的人认为他们的工作是压力的主要来源。百分之四十的员工称,他们的工作“非常紧张或极其紧张”。八成的人表示多少都有一点工作压力。令人震惊的是,14%的人说他们感觉像是跟同事在打架。

对工作量进行管理会对此有所帮助,稍做休息也是可以的。在合理的范围内鼓励工作的时候听音乐或者做一些有趣的事情。最重要的是,你需要帮助自己的员工注意到这个问题。这样做就很容易摆脱办公室中的流言蜚语,还有团队之间的对抗。但有时候你还需要团队静下心来,关注一下别人的需要。在驱车前往客户驻地时,你要鼓励团队成员听有趣的音乐,告诉他们在参加大型会议前要找到自己满意的位置。只要意识到压力的影响也许就足以克服其最糟的一面。

换句话说,对你的团队要带点同情心。他们最需要快乐的时候让他们开心,当你最需要合作的时候,他们会展现出自己最好的一面。你怎么看呢?在你的管理方式中,同情心是否是其中的一部分呢?你又如何给自己的IT团队缓解压力呢?你是否认为你的团队成员在公司内外可以对陌生人多一点同情心呢?

04 3月

Open Cloud 2015 to focus on technology innovation and applied practice

大会包含“2015 OpenStack峰会”,“2015 Spark技术峰会”,“2015 Container技术峰会”三大技术峰会及多场深度行业实战培训。主题聚焦技术创新及应用实践,讲师荟萃了国内外真正的云计算专家。 这里都是一线接地气的干货,扎实的产品、技术、服务和平台。Open Cloud 2015,懂行的人都在这里

A total of three Technology Summits including “OpenStack APAC Conference”, “Spark Summit China” and “Container Conference 2015”, and more industry experience in-depth training, bringing together worldwide leading experts in cloud computing, aims to provide a genuine technical feast by discussions over products, technologies, services and platforms. Open Cloud 2015, THE EXPERTs ARE HERE!

02 3月

可穿戴设备制造商们,这才是今年我们想要的产品

原文链接:http://venturebeat.com/2015/01/06/hey-wearables-makers-heres-what-we-expect-from-you-this-year/

文 / Elliott Chenger:移动平台开发人员,热衷于使用新兴技术构建连接彼此的产品。

2015年了,我对可穿戴设备有了更多的期许。虽然现在这些设备可以记录我走路,睡觉,消耗的卡路里还有心率,但是我仍然被那些在我看来没有意义的数据弄得不知所措,搞不懂这些可穿戴设备到底能给我带来什么好处?

就拿Jawbone Up24这款智能手环来说吧,我是很喜欢的。它能够记录我的行踪,睡眠,饮食,甚至是聪明程度(哈哈,我开玩笑的,这个不能滴)。但所有的这些功能只是告诉你正在干什么,却没有说你该如何进行调整及其原因。比如这手环自带的应用没说怎么样去获得更好的睡眠,也没能因地制宜地帮你从锻炼中进行调理。

在新的一年,可穿戴设备需要针对这样的局限进行改进,切实地告诉大家如何才能有效地提高生活质量。这需要有带情境、可操作且精准的数据作为前提来对我们的日程行为进行调整。

情境数据
情境数据会将时间定位到一天的某个时刻后呈现给你。这不仅可以帮助你从杂乱的数据中进行快速筛选,还能让你对自己在一段时间内的状况有更好的认识。

如果数据杂乱且没有正确的情境,接触太多这样的数据就不一定有用了。就拿记录睡眠来说,我们要从睡眠数据中得到更多的信息,就需要知道白天以及就寝前哪些时候需要调整,还需要从周围环境得到一些其他数据来协助验证影响睡眠的方式及原因。

比如说,我们睡觉的时候,有一个连接到可穿戴设备的温度调节器来显示室内温度的变化。这种变化要附带睡眠模式,这样才可以帮助我们定量了解室温对睡眠质量的影响。

由于这些设备变得更加个性化,关联更紧密,因此我们所得到的数据关联性也会更强。如果设备能够将数据情境化,这样就会让我们容易知道如何改善周围的环境,采取恰当的行动。最终手机或者可穿戴设备就能根据这些情境信息以我们的喜好来调节环境。

可操作的数据
没有接下来要说的可操作的数据,情境数据就孤掌难鸣。不要再看我们睡了几小时,失眠了几小时,可操作数据会告诉我们如何让睡眠质量更好。这样做的高明之处在于我们可以看到实时的调整建议。比如可以监视温度调节器,然后根据我们睡眠模式的变化和夜间时间来调节温度。

早期可操作数据的例子就是Jawbone的某款产品,该产品可以根据室内的温度和湿度来提高或降低风扇的转速。

在2015年,消费者需要更多这样的产品,能够通过科技将用户体验与真正的价值相结合给生活带来便捷的产品。企业应该以满足消费者的期待来求得生存。

Whistle是一款宠物狗行踪追踪器。它能帮助宠物医生了解宠物的活动量。这样就可以给宠物的主人提供有益于改善宠物健康的建议。就像HealthKit移动应用平台那样,开发人员可以开发类似的功能,把数据信息传送给医生,医生可以给出可操作的建议,提高我们的生活质量。然而,所有这些功能都需要在精准的设备上工作。

精准程度
可穿戴设备目前并没有那么精准。很多用户都有这样的经历,所测量的步数和心率的变化很诡异,心率的变化好像一会儿心脏停跳了,一会儿又像刚跑完马拉松似的。设备制造商也意识到了这样的问题,他们正努力改进硬件以提高设备的精准度。

美国苹果公司声称,其推出的Apple Watch会像医院里大多数医疗设备那样精准,忠实地去记录使用者的心率。如果这些可穿戴设备像制造商承诺的那样精准,那就真的会使我们的生活更美好。

随着家电设备互联业界和可穿戴设备市场都揣摩着如何融入我们生活的方方面面,我们将会看到两者更多的合作,也会有更多的开发者开放平台出现。这会使设备间可以互相对话,一起工作。奠定这样的基础是下一个情境数据为王计算时代的一部分,而以单纯的数量作为度量标准的做法则会成为过去式。随着企业为消费者提供的用户体验更加个性化,大数据技术也将退居二线。

15 2月

你为什么不是史蒂夫•乔布斯

原文链接 :https://medium.com/@ericzelermyer/why-you-are-not-steve-jobs-869e0bbb8d60

文 / Eric Zelermyer:iOS开发人员,目前供职于Resy Network。

自乔布斯逝世三年以来,关于他个人成就的某些细节已经逐渐在人们的记忆中淡化,在谈论他当年的辉煌时,一些说法也冒了出来。大的科技企业似乎在一夜之间都出现了,像Facebook的扎克伯格,亚马逊的执行长Jeff Bezos,还有特斯拉创始人Elon Musk都是让人心怀崇敬的人。对乔布斯一种常见的漠视是认为他只不过是个脑子灵活,做事霸道的家伙,是一个出色的销售人员罢了,并不比当前这些技术领导者、社交媒体新星更胜一筹。我最近耳闻一个成功的企业家声称自己与乔布斯是路数相近,因此他对乔布斯毫无兴趣。虽然这只是随口一说,但却在我的心里发了芽,我百思不得其解。

我一直困惑人们为啥痴迷乔布斯的演讲和销售技巧。他早期的产品(最早的Mac和iPhone)展示演讲虽然使人着迷,让个人魅力和演讲技巧展露无遗。但我不太相信这样的产品展示对产品的成功会有什么作用。我怀疑重视苹果产品营销的人与把苹果产品与相应Windows/Android平台软件产品进行比较的是一拨人,他们使用不同的数量级比较相同的数据,得出相同甚至价格更优的结论。唯一的区别是苹果产品使用了乔布斯的营销大法,这个东西至今被乔布斯的继任者推崇着。

口若悬河式的产品营销,凭借个人魅力来吸引和左右观众的能力固然难能可贵,但这样的人并不少见。这也是很多商务人士、政治家、励志大师、拳击赛发起人以及蛇油推销员所具有的共同特质。如果所有业界处于领导地位的标杆企业都有一位睿智的演讲者作为企业领导者的话,那么HP公司应该早该让善于表演的Jon Hamm作为公司的CEO,那样我们早就用上HP的PhonePad了。苹果公司推出失败的产品也不是什么新鲜事儿,照样有不少乔布斯式的煽情营销也没能使上劲的烂产品。

乔布斯的实质意义在于他是一个符号,是一个融合了传统商业领导者所有品质的符号。包括市场的洞察力,深知市场竞争的严酷性,财务的管理能力,还有营销能力。营销能力包括对审美的苛求,对用户体验的极致追求。对所有产品的任何细节细致入微,一丝不苟,乔布斯是名声在外的,从字间距到icon的颜色。因为他虔诚地信奉这些东西会使用户体验大为不同,进而影响他们的生活质量。他走的不是纯粹的管理路线。他会亲自构思设计苹果旗舰店的玻璃楼梯,而这只是他诸多贡献的其中之一。

分秒不断地对产品设计进行改进,随着时间的推移,换来的是千千万万“果粉”的追捧,苹果公司在乔布斯的带领克服了短期利益的诱惑,把不断改进产品放在了首位。所以,即使苹果公司曾经一度面临歇业,Mac电脑也没有被塞满尽管会带来巨额收入而用户却不想用的软件。乔布斯宁愿看到自己的产品死掉,也不愿意在用户体验上打折扣。

很难想象,如今还有哪一位企业的领导者具备苹果公司那样对产品吹毛求疵的意愿和能力?亚马逊的Jeff Bezos会关心Kindle难看的字体,糟糕的算法和做作的用户体验吗?亚马逊无疑是个成功的大公司,但如果产品价格上涨,送货时间变长,还会有多少忠实的用户会留下来?还会有人再对亚马逊投入感情吗?有人敢说扎克伯格领导下的Facebook相对从用户个人信息中榨取广告收入来说,更重视用户体验吗?

对于我们很多进行创造性活动的人来说,我们可以看到自己拥有几乎与商业领域成功所需特质相反的能力。自我推销能力,财务上的严谨,经营管理的敏锐,还要有竞争意识。而这些特质都是我们希望拥有却又感到陌生的。一个人拥有所有这些特质到了高处不胜寒的程度,同时还要具备极佳的创意能力,设计上的判断力世界第一,并且孜孜不倦地执行他的审美标准近乎极致。那只是个传说,是极其罕见的基因突变。所以,对那些自认为与乔布斯一样伟大的人,我只能说:你没有那么幸运。

http://www.csdn.net/article/2015-01-29/2823765

12 2月

使用AWS管理跨多个环境的资源

文 / AWS Activate

原文链接:https://medium.com/aws-activate-startup-blog/managing-resources-across-multiple-environments-in-aws-7e139351f9c7

为了开发出优秀的应用程序,处于初创期的企业通常需要多环境(multiple environments)的支持,如产品环境、开发环境、QA环境和预备(staging)环境。但在多环境中对资源进行跟踪和管理可能是个挑战,特别是在业务与日俱增的情况下。

例如,随着企业研发人员数量的增加,企业所耗费资源的速度可能比预期的要快得多。这时你会发现自己需要更加清晰地认识到资源与环境的匹配问题。

有多少实例(Amazon Web Services中的虚拟机被称为实例)属于QA(Quality Assurance)?谁有权开启和关闭产品实例?开发环境的成本有多少?随着初创企业的发展,你可能需要找到这类问题的答案。

使用Amazon Web Services(AWS)不仅可以建立多环境的实例,而且还可以跟踪、管理和控制在不同环境间访问资源的方式。我们一起来看看那些能为你所用的相关功能。

标签

你可以使用标签来标记跨多个环境(甚至多个AWS账户)的AWS资源。标签是由一个键和一个值构成的label,你可以对大多数AWS资源进行标记,可以对每个资源添加的标签数量多达10个。

由于使用标签显然会带来很多好处,所以你要标记能够标记的每个资源。例如,你可以对下列任务使用标签。

  • 查找、组织资源
  • 资源的访问控制
  • 查看详细计费报告

此外,对资源进行标记会让你更有效地利用下列AWS的功能特性。

资源组

资源组(Resource Group)是AWS管理控制台(AWS Management Console)的一个酷酷的新功能特性。它允许你创建、查看和维护带有相同标签的资源集。你还可以创建属于自己的视图,这些视图可以带有不同的环境标签。你可以查看带有同一标签的资源,如Environment=Prod,在支持标签的所有服务中都是如此。你也可以查看所有共享多个标签的资源,如下面的屏幕截图所示。

1

详情请参阅资源组的使用

将标签用于身份识别和访问管理

你想为一组标记为Prod的特定用户锁定服务和资源吗?你可以将标签与AWS的身份识别和访问管理(Identity and Access Management, IAM)结合使用,来对用户或组的访问进行控制。例如,你可以控制哪些IAM用户或组可以对特定的EC2实例做开始、停止、重启和终止的操作。通过这样的方式,你可以让开发人员只能对标记为Environment=Dev的实例进行访问控制。

查看支持IAM资源标签的完整服务列表,点击IAM支持的AWS服务

将标签用于计费报告

你想知道一个特定的环境成本是多少吗?可以使用成本分配标签来对AWS的成本进行分类和跟踪。当你对AWS资源(比如Amazon的EC2实例、S3 bucket)使用标签时,AWS会生成一个由逗号将内容分隔开的成本分配报告(CSV文件),使用情况和所消耗成本按照标签被聚合在一起。你可以使用代表业务类别的标签跨多个服务来组织你所消耗的成本。

成本分配报告包括你使用AWS服务在每个结算期的全部成本。

该报告包括已标记和未标记的资源,所以你可以清楚地对这些资源所产生的费用进行管理。例如,如果将某个应用程序名作为资源的标记,那么你就可以跟踪单个应用程序在这些资源上运行的总成本。

有关更多信息,请参见每月成本分配报告的设置

维护标签

现在我们知道了对资源使用标签的好处,那么我们如何确保一切都能够被标记呢?如何确保一切已经被标记了呢?要做到这一点,你可以使用AWS管理控制台中的一些有关标记的功能特性。例如,向实例添加批量标签。下面的屏幕截图就展示了这种类型的标签管理。

2

然而,当新的资源被加进来的时候,你确实需要登录到控制台去手动更新这些资源。随着时间的推移,这种做法就会显得有些笨拙。以下是一些标记资源的其他方法。

创建自己的启动脚本

为团队成员提供一份或一组脚本来创建他们所需的环境。这样做是确保资源被正确标记的好办法。这还使团队成员的工作变得更轻松。他们会很快得到自己想要的环境,分分钟就被创建起来了。

因为每个AWS服务都有一个API,因此有很多方法去构建用来创建资源的脚本。你可以使用AWS命令行接口(AWS CLI)创建一份脚本,还可以使用平台中众多可用SDK中的某一个创建脚本。诸如此类的做法非常简单。关于SDK的更多信息,请参见开始使用AWS进行开发

你还可以从GitHub这样的网站上找到无数的可用脚本,可以拿过来直接用。

使用CloudFormation模版

模版像脚本一样,为团队提供一种在AWS上创建资源的方式,这种方式简单,并且重现性好。你可以创建一个模版,描述想要的所有AWS资源(如Amazon EC2实例、RDS DB实例),AWS CloudFormation将会为你提供和配置这些资源。

几乎每一个支持标签的资源在CloudFormation模版中也支持标签。你可以针对不同环境创建模版,还可以使用条件语句为多个环境使用一个单独的模版。

使用自动伸缩服务

自动伸缩服务(Auto Scaling Service)为你提供了若干个很给力的功能。当应用程序需求增加时,你可以对EC2实例做横向扩展(scale out)。你还可以减少不必要的实例,替换反映变得迟钝的实例。

你也可以对自动伸缩的实例使用标签。已经启动的实例作为自动伸缩功能的一部分在启动后被自动标识,免得你还要创建一种机制来做到这一点。

使用配置管理

许多常见的配置管理工具都支持在创建资源的时候对其进行标记。如Chef、Puppet、SaltStack和Ansible。这些配置工具通常还为多个环境提供有条件的分离。如果你正在使用这些工具中的某一个,那么可以很容易地对已创建资源进行标记。

发现未加标签的资源

你可能会时不时地想要审核自己的账户来识别未加标签的资源,或者去验证资源是否被正确地标记。看看下面建立审核资源的选项设置。

使用AWS管理控制台

AWS管理控制台提供了一种查看资源的简便方法。这些资源全部基于标签,尤其是EC2实例。你可以为大多数资源视图使用标签来创建列。这样就能够使你发现标记不正确或者根本没有进行标记的资源。如下面截图所示。

3

创建自己的脚本

如前所述,AWS提供了访问服务的API。你可以使用相同的API去查询资源列表所提供的服务。你还可以使用这些列表找到没有标记或被错误标记的资源。

总结

你可以使用标签跨多个环境跟踪和管理那些资源,对这些资源做访问控制。在AWS中使用标签的选择不是唯一的。例如,你可以使用AWS管理控制台、CloudFormation模版,还有自定义脚本。无论你选择哪种方法,你都会发现标签是管理资源不可或缺的方法。

http://www.csdn.net/article/2015-02-11/2823926

09 2月

Matt Cutts:我早年在Google学到的10条经验

文 / Todd Hoff:High Scalability创始人 。

原文链接:http://highscalability.com/blog/2015/2/4/matt-cutts-10-lessons-learned-from-the-early-days-of-google.html

【编者按】本文来自Google公司资深工程师Matt Cutts。加入Google以前,Cutts在北卡罗来纳大学教堂山分校攻读计算机图形学方向博士学位。他写的文章深入浅出,简明易懂,实用价值很高,因此他在互联网上具有相当高的名气。 日前,High Scalability创始人Todd Hoff整理了其与Cutts的谈话,总结了后者早年在Google学到的10条经验,值得很多人学习。以下为原文:

我所认识的Matt Cutts是这样的,他是Google的老员工(2000年入职),目前担任Google公司Webspam团队的负责人,他与This Week in Tech(TWiT)创始人Leo Laporte曾在TWiT一起亮相,从中我们可以看到他一贯的敏锐、体贴,是个真正的好男人。走过路过,不要错过!

出乎意料的是他所做的谈话,早年在Google学到的经验。此番谈话也使Matt看起来异常风趣,像是个讲故事的好手。谈话讲述的是他早期在Google一些故事。故事呈现了一个非常人性化的Google。当你认为在Google所做的一切都是人工智能搞定的一种计算之时,Matt提醒大家,那些都是人工所做的判断,通常只是这些人尽力而为的结果。

谈话的核心内容是借助创造力来进行创新,来解决问题。当你陷入进退维谷的境地时,就要变得富有创造力。质疑自己的设想,下面的谈话也许能帮到你。

谈话虽短,但值得一看。很多有趣的小细节,只有身经百战、远见卓识者才能拥有。谈话中亦充满了智慧。这就是我对Matt谈话的评注!

1. 创造力导致天壤之别

Matt Cutts在Google头一个大型项目是开发色情网站过滤器。孤军奋战了一段时间后,他发现没人能帮到他。于是他的妻子烘焙了一些饼干。任何在服务器上找到色情信息的人,都会得到他发给的饼干。这样的战术相当奏效,以至于这些饼干被周围的人称为色情饼干,其他团队也采用了这个策略。抛出一个难题的同时给予人们小小的、象征性的奖励,这样做能够创造奇迹。

2. 当面对相互矛盾的制约时,一个优秀的管理者常常能够找到冲突双方都满意的创造性解决方案。

撒旦是与上帝为敌的魔王!Google首次面对的重大争议是要找比撒旦更邪恶人,而找到的答案却是微软?当然不是啦!面临的争议是基于《数字千年法案》(The Digital Millennium Copyright Act, DMCA)的删除请求。去年Google收到大约3.5亿条DMCA删除请求。显然,你无法手动去搞定这一切,而同时也很难知道一个请求是合理的还是不合理的。对带有【REEFER MADNESS】(赤裸之城)这样信息的内容来说,删除请求似乎是合法的,但由于它不受版权限制,因此这样的请求是不合理的。

Google最初收到的删除请求来自山达基教会,目的是为了压制对其进行的批评。他们试图压制的网站在挪威,而网站的所有者不愿意处理反向通知(counter notification),因为他们不想承担在美国的诉讼风险。Google应该怎么办呢?

差则思勤,Google是这样解决的:

  • 删除了页面并添加了说明:“由于DMCA删除请求的原因,搜索结果予以删除”。
  • 投诉了chillingeffects.org,这是一项由北美多间大学联合进行的学术研究项目。本项目最关注的是言论自由和知识产权问题。

Google开始对每一项不得不删除的合法请求开始做同样的事情。

3. 要积极主动。没人关心你的职业生涯,相对你所做的事,也没人关心你赚多少钱。要自己想要的。否则你就会完全偏离方向。

Matt是“自告奋勇”去做广告产品前端工作的,他为此大约工作了一年时间。在这期间,Matt看到了人们开始向Google发送垃圾邮件,于是他跑到主管工程的副总裁那里说“我想去做反垃圾信息处理这块内容”,这位副总裁答应他了。就这样一路走来。而在这之前Matt很大程度上依赖于别人指使他应该做什么。

你会惊讶,仅仅告诉别人“我想做某事”,竟然会产生这么大区别。假如你是经理就请记住,如果有人想要做一些事情,那么他们就会加倍地去努力。

4. 明确你的设想,进行反思

如果你完全用传统思维想问题,那很难使你从人群中脱颖而出。很难有冲劲儿,也很难变得与众不同。这些地方往往是最让人拿不准的,也是最好的机会。你相信别人不相信的事情吗?

5. 质疑设想的训练

我们通常不善于质疑自己的设想。近似的一种方法就是拿当前的一些事情问自己:发生了什么变化?与过去相比,世界有哪些不同?

例如,对于美国平价医疗法案来说,有两点显著区别:1)可以使用对已存在疾病的保险获得医疗服务;2)你不必非要从雇主那里上保险,而可以自行去交易所购买。

影响:1)自己经商的人数可能会增加;2)像Uber那样依靠独立承包商运转的公司,可能会支持这项法案。

像医疗保险那样的东西可能会发生非常大的涟漪效应(ripple effect)

拿Google来说,以前Google的搜索引擎依靠规模庞大的真实物理机,都是一些价格昂贵的机器。Google对廉价的商品硬件(commodity hardware)做了横向扩展(scale out)。这就意味着Google可能付出更低廉的代价而获得成长。这也意味着机器越多出现的错误就越多。因此你需要知道如何让整体比局部更可靠,这可不是一件容易事。

这也不仅仅是购买廉价的硬件就能解决的问题。在一些转折点你可以有很多认识和领悟。例如,机械硬盘的寻道时间是10毫秒。如果你在RAM中做同样的事情,每秒钟可以做很多次寻道。把网站全部索引都放到RAM里代价高昂,但你可以获得更高的吞吐量。因此权衡来讲这样做还是值得的。

6. 商品硬件是Google取得成功的原因吗?

不!成功是数以百计创新的结果。这样的成功不是灵感乍现而后万事大吉。因此成功不仅源于廉价的硬件,也不仅源于网页排名。成功源于Map Reduce模型,Spanner数据库等创新性的技术。成就一个成功的公司需要许多创新,成就一番个人事业,也需要很多创新。

7. 设想受到质疑,环境发生变化,你必须适应。使用数据你可以做很多非常酷的东西。

在很长一段时间里,我被人工智能难住了。1999年的时候,人工智能还很笨,不能做任何事情。如今状况已经大为改观。很大程度上是因为世界上有了更多的数据。

“Google Brain”是Google运用深度神经网络算法所建立的系统,用来观看YouTube,看看能学习什么。“Google Brain”具有自身可以识别猫咪的神经网络。这项技术同样被用于地构建更出色的词语识别系统。因此,每部Android手机都具有更佳的语音识别能力,这要归功于深度学习算法。这项技术使错误率下降30%,具有紧凑的模型,可以应用在电话上。

如今,技术已经先进到可以指出所识别的景致身处何方。像瀑布、建筑、美国加利福尼亚州中部的约塞米蒂国家公园、棕榈树、大海和雪景。计算机现在甚至还能给图片加标题。

8. 事情会向不好的方向发展,你必须对此有所准备。

在Google也并不全是成功和甜点这样美好的事情。Matt记得所有的诉讼和证词,这对一位工程师来说挺没意思的。这样的情况源于其他公司的诉讼,而这些诉讼来自于各个国家。

会有苦日子,少不了困难,所以自己要做好准备。

9. 不管将要身处何境,都要与快乐相伴,按动快门,使瞬间永恒。

如果你乐意的话,可以记录每一个会议。这没有多少成本。十年后,你要记住八个伙伴围坐的乒乓球桌,一起谈论如何让Google的搜索质量更好。你要记住那条大狗,要记住过去的美好时光。

碰到有趣或是离奇的事情,拍些照片。比如有人曾经为了再次被搜索收录而寄送过来的一块儿巨大的饼干,其他的例子还有四月的愚人节恶作剧以及万圣节的传统。

Matt曾经与他的团队成员打赌,他们可以对自己的头发任意处置。团队成员出色地拦截了四分之一的垃圾邮件,将Matt的头发全剪掉了

每周例会上的趣事是员工可以盘问公司高层,他们为什么做出这样那样的决定。

10. 不管你正在做什么,尽量确保这件事有价值,确保是你所在乎的事情,是人们想要的东西。

Matt一直认为Google是一个工具,而Google的员工一直尽其所能使Google成为最棒的工具。

Fred Brooks在他的论文The computer scientist as toolsmith(计算机科学家的使命是制造工具)中写到:

如果我们正确地认识自己的角色,那么我们就可以更清晰地认识到成功的标准:工具制造者的成功在于,也只在于,工具的使用者在他的帮助下成功。

http://www.csdn.net/article/2015-02-06/2823866

2015.2.06~2015.02.08CSDN首页焦点大图推荐:

LessonsFromGoogle2

28 1月

我们为什么四个月徒劳,使用OpenStack又为什么失败

文 / David Laube:充满热情的互联网基础设施构建者,工作涉及托管服务,基础设施自动化和可扩展平台的部署。目前担任packet.net主管平台系统的副总裁。

原文链接:https://www.packet.net/blog/how-we-failed-at-openstack

去年初夏,我的同事Zac,也是公司的CEO,向我求助如何构建一个现代化的,任何东西都不安装的云托管平台。我回想自己以往的主要从业经历,包括构建,支持和使用可扩展的基础设施的经历,不禁犯起了嘀咕。我问自己,真的需要这样做吗?不是有很多不错的IaaS(Infrastructure as a Service),基础设施即服务可以拿来用吗?

随着沟通的深入,我最终意识到现在很多云服务不是用户友好型的,使用起来存在很大的困难。另外,我是Docker的早期用户,Docker是应用容器引擎,这种容器支持的部署方案会使高质量的物理裸机在运维工作方面更加给力。但某些公有云的虚拟化情况,还有一些托管服务商存在的问题,都没能与复杂多变的物理硬件发展的需求相匹配。于是我觉得需要为此做一些工作。接下来咱们随着packet.net的部署旅程一起过把瘾吧!

开始安装之旅

我一头扎进了部署packet.net的工作。还同时忙着关注部署策略和云自动化的相关动态,从头到尾地检查特定安装程序,还有所有的开源云平台,以及我们已经安装的那些服务。

Voxel是被Internap收购的一款云主机托管平台,我们在使用的时候部署了很多自己的程序,在这过程中既看到了带来的好处,又体验了自己拥有软件平台的感觉。服务器的安装工作看起来似乎特别容易,好像一旦完成,一劳永逸,对吧?但这是绝对的错觉!因为安装完成后会出现数不清的网络问题,还有随时发生的硬件调整,以及各种操作系统存在的差异。在这样的情况下为用户提供不折不扣的自动化服务,安装并管理数千台服务器,并确保这些服务器正常工作,在五分钟之内还能响应Zac做出的决定。这对我来说可不是件轻松的事情。

为了使 packet.net到达预期的目标,数千台服务器7×24小时不断地安装和启动,并要在数月后上线。我开始关注OpenStack在互联网基础设施方面的独特之处,它可以被当作我们构建服务的手段。这包括联网业务的自动化,IP地址的管理,安装过程的监控,以及硬件的调换和安装。如果我能依靠OpenStack这些核心项目完成工作的话,那么我的团队就可以更加专注于能给用户带来更多价值的事情,像硬件分析,还有对容器机制的应用引擎提供技术支持。

别人提醒过我OpenStack存在的一些隐患,但我还是自己花了数周时间去阅读近期的版本记录,混迹于好几个维基的IRC官方聊天频道,并且玩了一下OpenStack的安装脚本DevStack。我开始对OpenStack的核心项目不再那么陌生。在过去的两年中,DevStack已经发展得非常成熟,而且所逢时机也刚刚好。全球领先的托管服务器及云计算提供商Rackspace最近发布了OnMetal物理裸机服务器部署方案,并公开撰写博客指出如何在其物理机上使用Ironic进行部署。而美国时间2014年10月16日,OpenStack的一个重要的版本,Juno版也正式发布了。

所以我觉得应该使用OpenStack来为公司的物理服务器进行部署。

部署的过程

我知道学习OpenStack的过程不会平坦,并且明白这需要拼命努力学习其中的每一个项目,而不只是安装。我细致深入地研究OpenStack每一个项目,尽力去了解Nova的动态,还有Ironic的驱动程序,特别是Neutron。我们不仅要在物理服务器上安装Ironic,还要支持packet.net托管服务的网络模型,特别是要用Layer3取代Layer2和VLAN层主机的功能。

这个时候你可能说:“喂,要阅读和学习的文档那么多啊”!在过去的一个月里,我明显能感觉到我们所接触到的文档不是过时的就是有错误的。这让我不得不去从以前优质的文档中去删选内容,比如从维基上的文章,IRC(一种聊天工具)的日志,还有版本提交记录,从这些地方去寻找最新的正确信息。这些基础工作完成后,我要用python去做大量的调试工作,去验证各种与文档描述不一致的功能。比如这个是否工作,那个是否正确,这是很漫长的过程。

值得一提的是,存在着那么一群人和公司,他们依靠OpenStack生存,组成一个很大的共生系统,特别是OpenStack的Nova和标准的Neutron项目相关的部分。尽管从规模上这群体可以与其他开源项目进行匹敌,但其实对于Ironic来说,他们很难有人能够达到产品级的使用水平。我就碰到过这样的情况,我向其核心开发人员咨询了一些实施的问题,他们居然答不上来。并且我从Google搜索这些问题,也仅能得屈指可数的几条与问题有关的信息。

经验一:OpenStack规模不小 ,新兴并发展迅速,但要了解一些过去的基本信息,会感到相关的文档良莠不齐。

我把Neutron部分交给了我的同事去处理,而自己又深入地了解了Ironic。但实际的情况是,我们需要OpenStack每个部分特定的开发人员,让他们帮助我们去理解代码库,才能跟上OpenStack每个项目更新的脚步。那我们又怎么去恰如其分地满足自己的需要呢?于是我就通过IRC和来自Rackspace的OnMetal团队成员接触,还通过邮件联系。去逛OpenStack开发者论坛。我敢打保票,自己阅读了每一个相关文档,还有论坛里的每个帖子,而且还通过Google搜索出的相关信息去调试Ironic,这些我都做到了!

尽管对于先前那种Ironic项目来说OpenStack Nova版的物理服务器部署方案取得了突破性进展,但是OpenStack还是以为虚拟化技术为核心进行设计的。仍然存在很多功能和文档的修改还介于Nova的物理机部署方案和带有驱动的Ironic部署方案之间。我把这种情况反馈给了力量有限的Ironic技术支持部门,却硬被要求使用与虚拟技术相关的openvswitch和linuxbridge。我们的网络模型与此存在严重的冲突。于是我发现,OpenStack的Neutron项目不仅缺乏针对特定网络产品厂商的技术支持,也缺乏对不同网络模型的扩展能力。

对OpenStack的核心代码有更深了解的大用户(最典型的就是Rackspace公司),依靠将OpenStack的那些项目高度定制化后,使之能够在实际的物理网络上部署物理机。其中有几个补丁是已经发布了的,但很多重要的补丁都没有公开,需要用户自己重新编写,同时还要对以后新发布的版本进行维护。

经验二:OpenStack是基于虚拟化技术的平台,如果你用的不是虚拟技术,那就要再考虑了!

到了这份儿上,我已经对使用OpenStack部署公司服务产生了严重的怀疑。这么多需要了解的东西,还有要做与每个项目保持同步的工作,这样的情况令人望而生畏。并且,我开始认识到要对Nova和Ironic所做的定制化工作并不是小事一桩,这会抵消掉OpenStack在开源方面所带给我们的好处。

但我还是觉得完全了解Neutron的细节非常重要,这是我目前唯一的念想儿。

对于物理交换机和服务器来说,安装部署服务器并不太困难,而且解决方案十分成熟可靠。而自动化工作却需要很多工具配合工作才能完成。从我的经历来看,大多数基础设置部署工作最容易出错的部分就是网络部分的自动化。你看,物理交换机的操作系统还存在很多不足之处。对当前的自动化工作和API的交互的支持显得捉襟见肘。其实,我用过的另外一款网络自动化工具的蹩脚表现是让我考虑使用OpenStack的主要原因。Neutron项目有非常令人振奋的使命:可以按照需求提供可扩展,不受制于任意一项技术的服务,包括相关的库。我也希望是这样呀!

但现实并不像所承诺的那样。根据软件定义网络(SDN,Software Defined Networking)的说法,大多数在基于虚拟机监视器(hypervisor)的虚拟网络下工作的项目并不是真实的交换机。不仅是因为对于交换机厂商来说严重过时的Neutron驱动,而且OpenStack最新的Juno版本的支持工作也力量有限。另外,Neutron使用了自身并不完善的IP地址管理器(IPAM),根本没有任何自己分配外部访问方式的概念,也没有提供关于IP地址管理方面的书面说法和权限。牺牲用户体验来适应Neutron这些不足,这是不能接受的。

经验三:OpenStack的Neutron项目支持工作并不那么完备、系统。使用之前要先看看自己的交换机能否适应。

这样一来,我们要如何应对?

长话短说。在圣诞节的前一周,我们丢掉了OpenStack,然后又花了三周的时间开发了一套定制化的自动化部署平台。在十二月初搭建好自己的IP管理系统后,团队就卯足了劲要将系统搭建自己定制工具上。而每个新项目都会有自身的使命。作为一家公司,我们的愿景是不断进取,并且我们觉得,在调查和部署OpenStack的过程中,解决了存在的大部分问题:构建了一个灵活且能提供服务功能的IPAM系统(我们管它叫Magnum IP)。在设施管理平台和物理基础设施之间,我们还建立了用户和权限模型。

有时现存的东西并不一定是最好的,也不一定能满足自己的需要。我们使用OpenStack部署packet.net的过程就完全说明了这个道理。同时,我们也会努力发布自己的Neutron插件,与OpenStack项目的发展相适应,我们现在正在做。

之后的一周时间,我们最终完成了CoreOS系统的安装(这也是在考察了Ubuntu,Debian和CentOS后做出的决定)。工作精益高效,对变化反应迅速,对系统记录详尽,这样我们可以做一些高级功能和高可用性工作,而又不会影响到用户体验,这让我感到激动不已。我能说自己工作学习两不误吗?

http://www.csdn.net/article/2015-01-26/2823694

18 1月

在2015年,我们会看到SaaS怎样的转变?

文 / Omri Erel,walkme.com广告与绩效营销的负责人,让SaasAddict致力于寻找新的,创新性的道路来充实软件服务,使初创企业获得了成功。

原文链接:http://saasaddict.walkme.com/saas-2015-new-shifts-will-see/

又到了回首过去,展望未来的时刻了。对大事小事来说,做这样那样的预测都让人觉得有点儿傻。科学技术瞬息万变,变化之中体现出了发展的趋势。某项新技术的应用过程会比最初预想来得要缓慢,所以有时站在全局的角度审视事物也是很重要的。新年就是这样的好时机。

在2015年,我们大致可以看到SaaS(软件即服务)的发展趋势会遵循这样的轨迹:企业和客户之间会存在更多的选择。尽管我们心里企盼平稳,但是在市场,销售和产品研发领域,特别是云计算相关领域仍然会发生持续的变化。

我们在新的一年里会看到SaaS五大发展趋势:

1. 企业会在个体消费研究方面加大投资规模
目前很多对消费者的研究还停留在静态的方式上,比如通过问卷调查和对原始数据进行分析。更多的企业会在个性化定制服务方面加大投入。这些企业往往会通过社交网络,大数据技术的应用以及直接的接触(电子邮件和社交媒体)来了解客户的需要。像购买动机,生活方式以及内心需求这样的细节都很重要。

营销策略的关键是要提高客户的满意度,激发客户的品牌价值意识,所以营销不仅仅是一种服务。

2. 云数据服务将会赶超传统意义上的存储
Forrester Research分析,相对传统的前置应用,微软公司将会从其云服务领域获得更大收益。传统的前置应用受限于自身的前置存储空间,而云数据服务则更加开放。尽管云数据服务成本相对低廉,但企业为了获得有效的发展,还会研究去缩减其开支。

云数据服务需要提防的一点是其合法性问题。希望企业花重金来做好数据的安全工作,避免数据外泄。

3. 更多的SaaS应用会行业化定制
像医疗卫生行业,制造业以及零售业将会开发出更多适用其领域的应用程序。这样做面临的挑战之一就是要承担起客户更深层次,更复杂的体验工作。但在开发新的功能时,企业在特定领域SaaS所具有的用户基础会使企业抢得先机。同时对用户也有好处。这种趋势不容小觑的原因是用户对特定领域相关应用的需要日益增长。在任何一个领域,通用的应用软件都会避免变得过于复杂。过于复杂会提供给用户不切实际的服务而与用户脱节。

4. 多重租用的可选方案将会出现
允许多个用户共享一个应用对于管理云服务数据是有行之有效的,而传统认识是让多个用户使用,具有各自的界面。多重租用则具有更个性化的用户体验。例如,salesforce.com为企业所提供的新服务“Superpod”。这使企业在自己的数据中心拥有自己专用的基础架构,而不是连接到一个单独的服务器。

这些新的混合服务给企业通向未来提供了更多可选项,为系统的开发工作提供了更多的创新空间,这样就解决了云服务市场存在的瓶颈,也为用户提供了更多的选择。

5. 大数据分析更显突出
IDC的报告指出,在2015年DaaS(数据及服务)的应用量会呈现上升的趋势,消费额将达到2150亿美元。DaaS将利用云来提供服务。他们还预测,会有更多的企业使用大数据分析技术作为其商业及开放数据集的一部分。

云存储为企业的接入和整体存储容量提供了更高的灵活性。由于每单位云存储的相对成本在下降,越来越多的企业对大数据分析技术变得兴致有加,该技术则是实施开放数据集的绝佳机会。

http://www.csdn.net/article/1970-01-01/2823495

08 1月

编程语言易用性最低,却是迄今为止最强大的人机接口

文 / Andrew J.Ko:美国西雅图华盛顿大学信息学院助理教授。

原文链接:http://blogs.uw.edu/ajko/2014/03/25/programming-languages-are-the-least-usable-but-most-powerful-human-computer-interfaces-ever-invented/

这样的说法是真的吗?当自己破译一些神秘的错误信息,调试一些悄无声息的故障,或者给一些没有很好存档的函数提供正确的参数时,我经常会思考。上周我在对一个难以理解的PHP错误信息绞尽脑汁时,把这样的说法发到了推特上。但是,编程语言是否真的易用性差呢?

是还是不是,在推特上仁智所见。但我认为,只有部分缺陷是根本。举例来说,Jakob Nielsen[1]经典的易用性推测法,是关于易用性的一个粗略表征。用户界面其中一个最为突出的问题是缺乏系统状态的可见性:可用的接口应该提供用户输入状态及时和清晰的反馈,以便用户知道系统的状态以及接下来要做什么。当你编写程序时,会发现某个人所编写的指令集与这些指令对应的输出和对周围的影响有很大差异。事实上,即使一个简单的程序也可以有很多分支,也存在一些编写指令集的程序员无法看到的执行路径,而只有后来执行程序的人才能看到。再来说说延迟的反馈!具有复现性的全部文字记录,测试以及调试弥合了程序的命令与执行动作之间的脱节,更好地展现了一个程序执行时的行为。这些工具顶多为程序员认识程序提供了信息,因为程序执行过程固有的复杂性,这种认识需要花费大量的努力。

另一条准则是Nielsen的“系统应该对真实世界进行映射”:系统应当使用用户熟悉的概念,短语和比喻。而使用计算机术语表达思想是编程语言的本份。这可以通过标识符良好的命名,语言范式和结构的选择得到改善。你可能认为编程语言的发展是一个缓慢的过程,因而需要刻意去定义更好的抽象模型语意。但用计算机术语表达事物并不像人类思想那样凌乱和含糊其词。

编程语言可以产出非常实用的工具。事实上,编程语言研究人员向语言添加新的抽象,语义,以及尽力形式化的内容,是为了尽可能减少错误,最大化地使错误信息易于理解,有些人可能会争辩说,编程语言研究人员实际上在做什么?比如静态类型检查就是从根本上提供具体的,可操作的及时反馈。同样,Nielsen的准则“有助于用户认识错误、判断错误并从错误中恢复”已经不是通过语言设计来体现,而是通过精心设计语法高亮显示,源文件结构视图,类层次结构视图等这样的特征来体现。

还有编程语言可能超越图形用户接口易用性的其他易用性原则。举例来说,有什么更好的用户接口比编程语言更能支持撤销,重做和取消操作?当今的文本编辑器和版本控制器中,至少在设计期间,什么样的修改是不能够被撤销,重做或取消的?最优秀的编程语言大概是现有的最一致,灵活,简约和美观的接口了。大多数图形用户接口很难平衡这些设计原则,因为实现这些需要做出牺牲,以更好地适应现实世界。

因此,编程语言在某些方面可能缺乏易用性,但在工具设计中付出一定努力,编程语言可以接近图形界面的易用性。易用性方面的这些改进,能够大大提高易达性(accessibility),易学性(learnability)以及用户的使用效率。

现在来说第二部分:编程语言真的是有史以来发明的最“强大”用户接口吗?这取决于我们对强大的定义。这些编程语言当然是我们拥有的最具表达力用户接口,可以比其他任何用户接口创造出更多东西。

但是如果我们把强大理解为可以直接促进人们的特定目标,那么就会有很多任务,使编程语言变成一种蹩脚的工具。像发送邮件,视频聊天,玩游戏,阅读新闻什么的。这些事情最好由围绕这些活动及其相关概念进行设计的应用程序来支持,而不是由编程语言更低层的抽象来完成。从这个角度讲,编程语言可能是最不强大的用户接口。

如果说这个帖子有什么实在的东西,那么就是其隐含的概念,编程语言只是另一种类型的人机接口,是丰富且多样的用户接口范式设计领域。这里有一些有趣的暗示。例如,程序员也是用户,像HCI(Human-Computer Interaction)研究人员那样,他们一样需要像非程序员使用非编程语言接口那样去思考。在这两个领域我们没有发现更多不同的方法和文化,但问题和相关现象却显著地一致。

对于了解我和我工作的人来说,这些说法应该是不足为奇的。我所有研究的前提是编程语言即用户接口。这就是为什么,尽管我事实上主要学习代码,编码和编码器,但在攻读HCI方向的博士学位,而不是PL(Programming Language),软件工程或者其他一些与这些现象有关的领域。编程语言现在并将永远是我最着迷去学习和改造的一种用户接口。

[1] Web易用性专家,在丹麦科技大学获得了人机交互方向的博士学位。

29 12月

移动平台,一切仍然是敞开的

文 / Benedict Evans:拥有英国剑桥大学历史专业文学硕士学位,做过战略咨询以及移动运营行业股票分析师。

编译 / 白云鹏

原文链接:http://ben-evans.com/benedictevans/2014/4/7/in-mobile-everything-is-still-wide-open

移动平台对抗是非常有趣的,也是获得页访问量的可靠途径。但也逐渐形成为其次要面对的问题。到目前为止,苹果和谷歌两家公司以不同的方式平分秋色。斗转星移,苹果公司可能会推出价格低廉的手机,或者苹果开发者抢先转去做安卓开发。但这些都是老生常谈的话题,不必太在意。

对我来说,首先要面对的问题就是移动应用市场的庞大规模。从1.5亿各类非移动PC用户到3亿纯个人智能移动终端,这意味着互联网规模增大了两三倍,乃至四倍。这也意味着,互联网可以吞噬很多新领域,比如零售或支付。

基于这样的现实,我认为个人台式电脑与移动终端有三大区别:

“先Netscape”——桌面互联网很快成为了“web和其他一切”,20年都没有发生重大变化。移动终端不具备单独统一的交互模型。另外,我们拥有应用程序和应用商店,还有消息系统以及iBeacon,但都无一例外地还在变化。我们没有将其整合为一个稳定的方式。

“先PageRank”——这样复杂的局面,造成了移动平台的供求双方相互是敞开的。在Google的PageRank算法出现之前,我们还没有这样一个方法用于移动平台。对于任何相对PC网络浏览器更复杂和先进智能手机平台来说,我们也许从未有过一个统一的工具。

身份——智能手机是PC不曾充当的社交平台。它拥有地址簿和许多其它功能,但并不具有这些功能集合的清晰身份。PSTN号码?Email地址?Facebook账号?还是某些功能的一些乱七八糟的变换?

作为平台的所有者,苹果和谷歌两家公司将在塑造平台方面发挥作用。而事实平台之争已经结束,一切仍然是敞开的。你所见到的,以及自己是如何做的,拿出来分享吧,寻找和发现这些观点的大门都是敞开的。

28 11月

你能听到我说话吗?不行,我没安装那个应用!

文 / Matthew W. Meyer:软件开发人员,以优等成绩毕业,获计算机科学副学士学位。

编译 / 白云鹏

原文链接:http://www.mwmeyer.com/blog/can-you-hear-me-now/

在过去的10年左右的时间里,无论我们的沟通方式,还是我们的日常生活方式,都发生了很多变化。互联网的普及使人与人彼此联系更紧密,也改变了彼此交往的方式。我们目前正处于数字网络初创期的“淘金热”。我始终将新近的应用程序更新到最新,自称使自己轻松的生活更轻松。

结果是我对技术的现状根本不屑一顾。传统的载波通信技术(语音/短信)有很多方式可以去改进,使之现代化。每次我起身懒洋洋地用手机去回复短信时,都忍不住会抱怨。从技术角度说,我肯定可以做这件事。

软件方面的事情就是有些微妙。MightyText只是提供这类功能的众多应用中的一例。这就不得不说OTT[1]应用的崛起。当电信运营商把持他们的用户来赚钱,而非改进其通信体验时,不计其数的软件公司则腾出手来打造基于这些运营商及整个因特网的“Over The Top”服务。像GroupMe,Viber,WhatsApp,iMessage,Hangouts和Skype。这些只是业内规模较大的参与者。

如果我要跟一位朋友视频的话,那就糟了。我首先要确认对方下载了应用,创建了帐号并且登录,确认正常工作等等。对于电信运营商来说,这个问题似乎很早以前就解决了。而事实上,蹩脚的短信服务仍旧占领主导地位的唯一原因是,它有效保证了每个人很容易访问该服务。

未来的运营商只作为网关的作用越来越明显。无论是当今基于超文本传输协议的互联网,还是未来演变出的什么网络,光纤和光谱为因特网提供了连接。主要的市场资产运营商经过多年的发展,耗费了数百万美元,持有物理基础设置。这也是为什么像AT&T和Comcast这样的公司,初创公司在相同的商业模式下无法与其匹敌的原因。

显然,当电信运营商开发健壮的软件来提高自身服务的同时,他们就无力维护这些基础设施。这样一来,以软件为核心的公司间展开了迅速竞争。遗憾的是,人与人之间的沟通在互联网上一直在演变,传统的语音电话,短信已经被淘汰。

这些以软件为核心的公司没有去和其他竞争者互操作的动机。这对于一个封闭性的企业来说也许是好事,但对于全球性通信公司来说就不太好了。

虽然我觉得满足了互联网富通信(语音,消息和视频聊天)互操作性的某种核心通信标准还可以做得更好,但这不是必须的。我无法在业界提出一套可以解决这些互操作性问题的有力意见。而这对于向第三方开发人员提供API的OTT应用来说,看起来是向正确的方向迈了一大步。

说了这么多就一句话,我主张开放的统一通信。统一通信服务不仅让我轻而易举地在任何设备上进行通信,还可以统一三种主要的通信格式(语音,视频和消息)到一个平台。通过发布他们的协议,维护访问平台数据API,开放的通信服务就能让我选择不同的第三方应用和客户端。

数字通信迫切需要具有互操作性的发展方式,可悲的是,我还没有看到相关迹象。

 

[1]OTT是指”over-the-top”服务,通常是指内容或服务建构在基础电信服务之上从而不需要网络运营商额外的支持。

08 11月

我不想成为真正的程序员

文 / Anas Ambri:就读于加拿大Concordia University,是一位志向远大的软件开发者。

编译 / 白云鹏

原文链接:http://verybadalloc.com/blog/2014/03/18/i-dont-want-to-be-a-real-programmer/

我偶然发现了一篇博文,博主提出了成为一位更出色程序员的方法。据他所述,使用最热门的技术和语言不会使你成为一位更棒的程序员,那只会让你学习新工具。我对此深表认同,感触颇多。就拿满眼已经被开发出的东西来说,我并没有机会参与其中。

我们大致梳理一下:我非常喜欢使用橡皮鸭调试法(Rubber Duck Debugging),三年前,这个过程变成了过山车般刺激的编程之夜,还有第二天调试程序时的沮丧,以及项目结束时聚会的释然。在业余时间,我花费在编码上的几个小时是一个礼拜以来最愉快的时光。我喜欢尝试新鲜事物,乐于在白天多次提交代码,对编程马拉松(hackathon)不厌其烦。总之,我已经尽力尝试了“软件工程”中的许多美味,从嵌入式软件到web和移动应用的开发。

冒充者

然而,像许多其他程序员同胞一样,我感到自己毫无价值。这并不是某一天冒充者综合症(imposter syndrome)让我感觉自己所做的一切都是运气使然。甚至在写上一段文字的时候,我都不禁思考自己可能是自我感觉良好。尽管我已经习惯了这种持续恐惧带来的糟糕感觉,但与之相伴的不悦之感却时常困扰着我。

因为我们都很差劲 

你知道TJ Holowaychuk吗?他被个人崇拜的光环笼罩,很高产,自己一手搞定了Node.js中的很多类库。

如果你有哪一点像我,也想变成一位像TJ Holowaychuk那样出色的人,你要更舍得为掌握新的编程语言付出额外的努力。为一些额外的项目搭上无数的夜晚,并无处不在地讨论这些项目。拥有生活比起献身技艺已经微不足道。为了还未完成的项目坐卧不安。如果你有哪一点像我,那你才是想成为一位真正的程序员。 

真正的程序员

一位真正的程序员,是那种热爱编程并乐意为此付出的人。真正的程序员对编程乐此不疲,不会停下额外的项目。真正的程序员会掌握所有形式的编程方法,并且对自己所喜爱平台的API可以倒背如流。要想成为一位真正的程序员,你必须要沉下心去增长自己的知识和经验,并对此不计回报。

在过去的三年中,我时常梦想成为一位真正的程序员。谁又不想呢?你的代码不仅可以给带来名誉和荣耀,而且可以影响许多程序员同胞的生活。相当一段时间,我感觉自己就像一位真正的程序员。

然而,我学的新东西越多,我就越觉得自己不行。当我与JavaScript语法错误奋战的时候,其他人则在重定义我们编写客户端所使用的方式。类似的问题,我无能为力。无论我花多长时间去探求,我永远不会接近成为一位真正的程序员。

结语

我也许只是正在理顺这样一个严峻的事实,自己是无法成为一位真正的程序员。当然,周围也有很多真正的程序员,他们情愿每周80多个小时的工作量,我感觉这些人愿意将他们的人生投身于编程,我对此钦佩有加。

我?我会继续做自己的事情:尽力在这几个小时内产出更多代码。另外,对成为一位真正的程序员这件事评价也过高;显然,当前最热门的新事物是掌握精益软件开发(lean software development)

http://www.csdn.net/article/2014-05-30/2820031

14 9月

编程为什么困难

文 / Joe Armstrong:Erlang语言最初的设计者和实现者,他拥有瑞典皇家理工学院博士学位,是容错系统开发领域的世界级专家。

编译 / 白云鹏

原文链接:http://joearms.github.io/2014/02/07/why-programming-is-difficult.html

很多年前,我认为编程很容易,随着岁月的流逝,我已经意识到编程并不那么容易。这是围绕编程是什么,程序员做什么这两方面感知上的缓慢转变。

起初我还以为编程只是告诉计算机做什么,这样的编程是相对容易的。编了二十多年程序后,我感觉这种编程相当简单。

c

定义一:程序是将输入转化为输出的东西
程序员是编写程序的人,编程是编写程序的行为。

现在让我们给程序定义添加一些约束。

定义二:程序是将输入转化为输出,并受到以下限制的东西。

  • 该程序的输出结果要悠美。
  • 该程序的输入要悠美。
  • 该程序要悠美。
  • 该程序的输入要恰当并被正确归档。
  • 该程序本身要恰当并被正确归档。
  • 该程序通过测试并被证明是正确的。
  • 该程序正在解决中的问题要恰当地说明。
  • 该程序存在的其他问题要恰当地说明。

带着这些约束条件进行编程就变得十分困难了。

对于特定的问题,一些限制可以放宽。

不需要维护的程序
我们通常编写只要输出结果的程序,这种情况,输入和程序本身将来就不需要维护,所以不必有特别漂亮或很好地描述。

我写的关于Erlang语言的书就是这类情况。书一旦出版,就不再需要撰写和维护。结果看起来不错,但输入是一堆乱糟糟的XML文件和不会再被维护的测试程序。

在重印的时候,这本书的勘误信息和必要的修改只涉及输入微小且简单的修改,即使输入没有被很好地归档。

必须要维护的程序
对于必须要维护的程序来说,程序的输入和程序本身,则要悠美并很好地归档。

我与一位Web程序顾问聊天。他说只要程序的输出看起来是正确的,那么客户就会认为项目结束了,项目经理就会投身于下一个项目。

这里没有认识到,在开始下个项目前,网站不仅应该看起来不错,而且代码也要整洁并被归档。当然,也没有时间去这样做。因为这是要在未来进行维护的项目。

使编程困难的其他原因

  • 修复本来以为不可能坏的东西
  • 没有时间学习
  • 恶劣的编程环境

这些因素都是“时间的盗贼”。

修复本来以为不可能坏的东西

通常,我会使用自己并不十分了解的软件去解决特定的问题。

如果运气好,我使用的程序有准确的使用描述。而更多的情况是程序要么没有描述,要么是错误的描述。

如果文档里面的描述与事实不符,你会怎样?如果写文档的人就在你旁边还好,否则只能借助Google或者从源码中寻找答案了。

进了Google这赌场想搏一记,这样的查错经历真是不堪回首。好容易Google到一篇帖子,有个倒霉鬼恰巧和我遭遇了完全一样的问题。我的心脏激动得跳个不停。我用颤抖的指尖把这找来的咒语输入,以为这样诅咒就能解除……结果,还是不行。问题依旧。

为什么我的修复工作这么不顺呢,难道恶意之神有意盯着我?或者我身处区域的物理规律与别处不同不成?其实不然,两台机器的初始状态是不同的,所以在此机器上修复了缺陷的高招,在状态不同的彼机器上就不一定灵验。

修复坏掉的东西会令人感到加倍沮丧的,因为即使问题消失了,你也不知道是否解决了这个问题,或目前的效果是否是你所做的修改导致的。

顺便说一句,这样的问题需要我大部分的时间去处理。我曾经用一周多的时间去修复坏掉的LDAP服务器,而我的老板禁止我自己去做新的。但后来我还是自己做了一个。

说实话,这并不算一个完整的LDAP服务器。我想要的仅仅是两行命令就能工作,这是很容易修复的。

如今,我觉得去实现那些过时的和有悖常情的协议没有什么特别的喜悦,而重新实现它们往往是最快的进步方式。

不通过学习解决问题
我很懒,是个没有出息的懒鬼。当我试图使用LaTex对一个图表做排版时,我不想去阅读长达391页的说明书。我知道你会指责我懒惰,品德不健全。我明白自己应该首先阅读说明书,但我为了文档中的一个图表用十分钟读完一个391页的文档是不现实的。

恶劣的编程环境
工作场所的设计使编程更加困难,无隔板的开放式办公室那嘈杂的环境,破坏了我们的注意力,移动电话的打扰,以及互联网都会分散我们的注意力。

幸运的是我们还可以有可去的地方,那就是睡觉。很多编程问题是在睡觉过程中解决的。

有两种方法。首先将考虑的问题记住,然后睡觉,第二天醒来一些问题就被解决了,很容易。

另一种方法是睡觉前用tweet,在网上发个帖子,第二天就有人将解决方法发给你了。

做一名优秀的程序员需要很长的时间,你需要学习很多东西,当遇到问题的时候,需要知道向谁请教。

令人吃惊的事实
我写完这篇文章的时候,拼写检查工具竟然罢工了。就在我抱怨自己花费大半辈子修复的东西本不应该坏掉时,Emacs上的语法检查工具就坏掉了。

我的Emacs拼写检查器在这台机器上老老实实地工作了好几年。

我并不相信恶意之神,也不认为我这里的物理规律有什么不同。

一切都正常,我不知道为什么拼写检查器会坏掉,而我什么都没做。

幸运的是,用了十一分钟在Google这赌场搏一把后见效了,其中第二条方法解决了我的问题,而我仍然不知道Emacs中为什么找不到Aspell了。人生短暂而有太多不甚明了的事情。

我想有些东西我们永远都不会知道。

从长远来看,选择快速的方法处理问题是个灾难。

拿制作文档来说,我就在TeX/LaTex、XSLT-FO和我的Erlguten之间犹豫不定。

大约每三年一次,我都会有强烈的愿望,将自己所有的文档直接写在附录中,而后唯一做的事情却是深呼气,等待这种感觉消失掉。

如今因为我们可以让机器去做枯燥和危险的事情,我们反而没有更多的时间去做事。

我问过我的老板,他是否需要漂亮的幻灯片来演讲。他说需要,但要我明天之前给他。我权衡了需要时间去学习的几个方案,最终选择了PowerPoint。

http://www.csdn.net/article/2014-03-31/2819060

26 8月

我并非不爱交际,而是很忙

文 / Marco Tabini:作家,也是位半路出家的商人,近些年在php[architect]Macworld发表过文字。

原文链接:http://blog.tabini.ca/i-am-not-an-introvert–i-am-just-busy-/

编译 / 白云鹏

数周以来,我一直在与一个非常奇怪的BUG做斗争。我创建了一个服务端进程,用来打开一个与服务端的持久连接,来验证终端用户,然后执行一系列的流操作。

令人匪夷所思的是,这个进程发生了套接字泄露。尽管不是很频繁,但却是个问题。运行一段时间后,机器的资源就要被耗尽,由于队列请求得不到满足,也变得不稳定。

这让我感到崩溃,我无法弄清楚问题所在,把时间花在了重启服务和与代码对视上。且听我慢慢道来……

我不在办公室,也不在办公桌前,电脑也找不到了,这是什么鬼地方?

周围还有一些人,非常嘈杂,看起来像是个什么聚会。

我的手为什么是湿的?你瞧,冒着泡儿的黑色饮料,看起来像是可口可乐。就是可乐。喝起来冰冰凉,但却没有冰。白色的纸巾已经被浸透,边缘部分也被我撕掉了,我猜想我应该到这里会儿了。

我想起来了。这是Dan邀请我去参加的公司聚会。Dan这个人不错,但我们极为不同,他很健谈,我想这大概是因为他是位保险从业人员吧。

我至少用不着穿正装去参加聚会,也不会穿得像那个等电梯的人那样呆板,不过说真的,谁会穿个帽衫去参加公司聚会呢?
没人在乎的,这个人在等电梯,所以他可能已经上了电梯。不过你瞧,老兄,别再看iPhone上的Facebook帐号啦,否则你会错过电梯的。灯已经灭掉了,电梯马上就到……可你还是错过了,你可真够笨的……

不会吧!

这个人根本没注意电梯,他还是错过了。

我敢说你已经明白我的代码问题出在哪里了。如果远程服务挂起,而我却等待自己的认证令牌,我的应用就不会提醒,报错,这样就会发生套接字泄漏。

就是这样,两个星期的痛苦挣扎后,在满是保险销售人员的办公室我想出了解决问题的办法。

我需要马上离开去验证这种办法是否奏效。我正准备悄悄离开的时候,Dan和另一个人面带微笑地走了过来。

诶,我想我是走不了了。不知道他们是不是冲我来的。可你知道吗,Dan,我不能再聊了,我的想法就像被挂起的线程一样,不能再废话了,否则随着时候的推移,我会忘记的。

嗨,这位是CEO。嗯,很高兴见到你。一番寒暄后,我不能忘记那该死的电梯和认证令牌的事儿。没错儿,Dan是个不错的家伙。哦,他跟您说过我吗?真不错,我敢肯定他没跟您说过您实际上正在阻止我解决我那该死的问题。

别管怎么说,他们离开了,我可能搞砸了,他们可能认为我不可思议,不合群。我真的不需要在意这些,因为我最终修复了那个愚蠢的BUG。

在其他人用什么高招变着法儿地浪费我更多时间之前,我就要按下电梯的按钮。我肯定会这么做。我还有BUG要修呢!

http://www.csdn.net/article/2014-04-15/2819317

26 7月

我的第24个生日

文 / Arpit Jalan:一位科班出身的电气工程师,还是一位有激情的电脑软件工程师。
原文链接:http://techapj.com/24-birthday/

编译 / 白云鹏

此刻我正一边喝茶,一边装模作样地阅读Hacker News,但即将到来的24岁生日伴随着恐惧侵入的我脑海。

我并不希望过24岁生日。这是为什么呢?

在印度,对于一位初入社会,24岁,并拥有学士学位的人来说,他将会是位完美的新郎候选人,所有的亲属都在问同样的三个问题:

你在哪儿工作?

你的薪水是多少?

你打算结婚吗?

我是说,我需要一些私人空间,我才24岁,还没有做好这些担当的准备。

在印度,人们很难相信通过自己创业获得成功。“创业者”几乎与“失业”划等号。而如果你没有工作的话,那就准备听到那些流言蜚语吧。

这里绝大多数人没有听说过Github,没有人关心其上的开源代码所做的贡献,也没人关心代码的质量有多好。这里只关心两件事:

  •  你毕业于哪所学校
  •  你所在公司的品牌名称

如果你达不到上述任何一项要求,那么你将毫无价值。

幸运的是,我的父母支持我从电气行业转到软件行业,激励我追求自己的梦想。

直到18岁我才感到责任的降临,这以后的每个生日都会提醒自己还有许多目标需要实现。

18岁以后,我会思考过往的经历,对现状是否满意,这是我所期待的生活吗?我无愧于自己和父母吗?

直至最近三年,我才回答了上面的问题,但是答案是“否”,我对过往的经历和彼时的状态都不满意。因为我没有追随自己的直觉。

但这个生日我有一种满足感,我能感觉到自己在正确的方向,做自己坚信的事情。对我来说,这是最重要的事情,我正在做自己所热爱的事情(开发软件,研发新技术等),别无他求。

在这一点上,我想引述乔布斯那段最鼓舞人心的颂文:

你的时间有限,别浪费生命过别人的生活。不要被教条所束缚,因为那是别人生活的目的。别让其他人的不同意见压过你自己内心的声音。最重要的是,要勇于追随自己的心和直觉,它们其实早已知道你想要成为什么,除此以外的都是次要的。

此刻,我已年满24岁,伴随着脸上极度激动的表情,我在终端键入了这条命令:

git push origin master

此帖终于大功告成,舒了一口气!

18 6月

10岁起编程,并不认为自己是“黑客”

文 /  Liz Denys:毕业于麻省理工学院,数学和计算机科学与工程双料学士。

原文链接:http://blog.lizdenys.com/2014/01/03/i-do-not-feel-like-a-hacker/

编译:白云鹏

我10岁的时候在学校接触到了Logo语言,用它几乎对每一个练习进行泛化(这都是一些同学没兴趣做的事)。但我并没有受到老师的表扬反而被批评不务正业,因为他们认为这更难去评分。

第二次接触编程是13岁时,我开了博客,穿梭于其他人的博客之间,学习了HTML和CSS标记语言以及PHP,当时我还没意识到这是编程的另外一种形式。

第三次接触编程是16岁时,帮高中学校辅导员开发一套内部注册系统。这是我第一次做被称为“编程”的事情。虽然我承担了大部分工作,但另一个人却得到了几乎所有的称赞,这使我感到沮丧。

大学一年级,第一次所有人对我在编程方面表露出肯定。我果断从数学与经济学转到数学与计算机专业。我意识到,之前所经历的消极氛围,不是因为世人对编程不感兴趣,而因为我是女孩。

我比当时年龄相仿的女性有更多的机会,但经历却不是那么“正面”,这使我感到气馁。许多成年女性从未意识到“编程”或“黑客”是女人可以做的事。对她们来讲完全是被忽视的领域。我有时会认为软件领域的性别歧视不复存在,至少正在隐去。但可悲的是,这些问题并未消失。

美国著名技术作家Paul Graham曾说:“天知道如何让一位13岁的女孩对电脑感兴趣。我们无法使这些女性以黑客视角或从Facebook来看世界,因为在过去的10年间,她们根本没有过黑客行为。”

我不认为Paul Graham所说恰如其分。很多女性在过去没有“黑客”行为的问题,很多男性工程师也同样存在。有些人是有机会在早期成为“黑客”的,我便是幸运儿,但彼时我并不晓得。我认为自己有资格称为“黑客”的理由是早年有广泛的编程经历,但又感到永远无法成为真正“黑客”,因为我不符合人们对“黑客”的传统印象,比如我的着装、规律的作息

以及我不是男性。我想知道,过去10年一直编程的女性,有多少人一直以来都没能意识到自己是黑客呢?

女性在“黑客”群体中缺乏代表性不是因为她们缺乏兴趣。上面所说的被忽视以及性别歧视问题逐步地将女性排挤出了这个圈子。我不知道如何完美地回答怎样让13岁女孩对计算机着迷,但我明白首先要让她们在这个领域不被忽视。除了无法自我认同“黑客”的刻板印象,开始编程的年龄过晚也导致需要加倍努力工作,快马加鞭成为一名“黑客”。女性和还没有成为“黑客”的人真的需要花费必要的时间去追赶。

http://www.csdn.net/article/2014-03-17/2818795

25 5月

蔡格尼克记忆效应:做好工作的科学关键

文 / Alina Vrabie:喜欢发现生活小妙招儿,并与他人分享。以给他人的生活带来便捷为己任。乐于各种形式的沟通,来自罗马尼亚。

编译 / 白云鹏

原文链接:http://blog.sandglaz.com/zeigarnik-effect-scientific-key-to-better-work/

如果你和我们一样,一直在寻找更有效的工作方式的话,那么,你要真心感谢蔡氏效应给了我们答案。这里不得不提一位在立陶宛出生的心理学家的名字,Bluma Zeigarnik。20世纪20年代,她在博士论文中首次描述这种效应。在餐厅观察服务员工作时她发现,当食品摆到客人面前后,似乎只有未完成的订单被服务员记住。

她回到实验室后对此进行了科学实验。实验表明,较未完成的任务,90%被中断的任务被成年人较好地回忆了起来。孩子们则更容易回忆起未完成的任务。

如果你留意周围,会发现蔡氏效应无处不在。特别是在媒体和广告中。正如作家海明威曾经所说,“等待下一天的到来是很难熬的”。但蔡氏效应实际上可以对你的工作效率产生积极的影响。

蔡格尼克记忆效应与生产力

生产力的关键是一段时间专注地工作,同时避免做多个工作和被打断。你放弃一项未完成的任务转而去做其他事情会使你感到焦虑。由于多项任务很容易将你的注意力从一项任务转移到另一项去,由于离开了上一个未完成的任务,因此大脑无法完全专注于新的任务。这也是像番茄时间管理法(Pomodoro Technique)这样的高效方法学行之有效的原因。

 拖沓者的好消息

蔡氏效应对拖沓者是个好消息:如果你真的打算完成一项任务,那就不大可能拖沓。一旦开始,你便更倾向于完成某件事。在面对一个大的项目时,不要想着从最难的部分着手,而要先从当下易于把控的部分开始。蔡氏效应告诉我们击败拖沓的关键是开始的地方。

 预期回报与蔡格尼克记忆效应:为什么8小时工作制不奏效

一项研究表明,蔡氏效应会被预期回报削弱。好比8小时工作制,一天工作的结束就像任务被中断。一旦任务被中断,那么8小时的工作酬劳就是预期回报。研究表明,预期回报会削弱蔡氏效应。换句话说,8小时工作制实际上使我们疏于工作。对付这种自满情绪的方法是进行更灵活的工作安排并提供工作与生活平衡的健康奖赏方式。

http://www.csdn.net/article/2014-03-28/2819029

27 4月

我的第一份工作:首日之中失而复得

文 /  Steve Blank:美国加州大学旧金山分校讲师

编译 / 白云鹏

原文链接:

http://www.linkedin.com/today/post/article/20131029100222-95015-my-first-job-fired-and-rehired-on-day-1?trk=tod-home-art-list-small_1

我的第一份工作是被聘为ESL实验室的技术人员,去支持培训部门。打理好在密歇根的生活后,我花了五天时间驱车前往加州开始工作。

报到当天,一位满怀歉意的经理对我说,“我们上周一直试图与您取得联系,而打算雇佣您的培训部经理是没有得到授权这样做的,他已经被解雇了。对不起,我们事实上没有雇佣您的意向。

此刻,我惊呆了。因为我已经辞掉了工作,处理掉了房子,将我所拥有的一切打包在我的车里了,并且在硅谷举目无亲,身上只有200美元现金。我要求与新任培训部门经理通话,对方的答复是他们没有预算雇佣一名实验室技术人员,但需要一名培训讲师。

工作失而复得

数次会议后,我认为培训部门前任经理被解雇,主要的原因是:

  1. ESL将重大军事情报收集系统部署到了韩国。
  2. 没有培训陆军安全局如何对系统进行维护。
  3. 没有编写为期10周的培训课程。
  4. 培训应该被安排在6周后。

与上级的谈话中,我指出,我曾经在空军做过一些非正式的教学活动,了解所需军用维护培训的情况。我最终说服他们给出了不错的价码——以一位实验室技术人员的薪资来聘请我作为培训讲师。因此,我的工作在ELS失而复得,而此时的身份是一名培训讲师。

虽然得到了这份工作,让人为难的是,我需要在6周内编写一个为期10周的培训课程。还有运维手册。我的时间要被设计工程师、测试和部署团队分开来用。我领悟了系统原理图后,想出了把系统理论、运营和维护融合成一门课程的办法。

在这之后,我则如饥似渴地投入到工作当中。部署团队的负责人将当时还单身的我指派到了他的团队,一起去了韩国工作。

我的一位室友对我说:“你并不是特别聪明,只是在很多方面有所表露”。这就是他对我得到越来越多有趣工作的见解。这些年来我意识到他是对的。

而彼刻的我已经在没有任何人扶持的情况下独立生活,独自来决定自己下一步该做什么,面对和处理生活中的不确定性。

没有人会告诉你,你在从事一个没有前途的工作,也没有人会说,你要继续下去,或者告诉你要拿出更多的时间来陪你的家人等等。而职业道路并非一条直线,乔布斯说过:Stay Hungry,Stay Foolish。

与你分享的经验教训:

  • 相信你的直觉;
  • 展现自己,增加你的胜算;
  • 相信自己的职业节点会被连接起来;
  • 投入激情,而不要热衷于名片上的头衔。

http://www.csdn.net/article/2014-03-24/2818944

20 4月

有生活品味地编程

文 / Andy Brice:在计算机软件行业拥有逾25年专业经验,兴趣领域包括跨平台软件开发、软件优化和商业软件开发等。目前经营自己的公司并提供IT商业咨询服务。

编译 / 白云鹏

原文链接:http://successfulsoftware.net/2013/11/06/lifestyle-programming/

“如果一个人日出而作日落而息,并且随心所欲,那么他就是一位成功的人”——鲍勃•迪伦(Bob Dylan),一位极具历史影响力的美国摇滚、民谣艺术家。

我就是这样一位富有生活品味的程序员,自己开办了由个人经营的软件生意,自由、有意义还可以养活我自己。

“lifestyle business”这个词经常被那些风险投资者们糟蹋。他们会不断地嗅寻给予他们十倍或百倍回报的下一块“肥肉”。通过投资一些高风险初创型企业,他们就可以将自身的整体风险降低到令人心安的程度。而对于被投资人,他们的整体风险则大相径庭。硅谷创业之父、风险投资家、《Hackers & Painters》(《黑客与画家》)作者Paul Graham也承认:

“在每轮投资中最多只有一家公司对我们的收益产生重大影响,而其余的公司仅仅是陪练”。

然而高风险,高回报的风险项目令人垂涎三尺。谁又能抗拒像沃兹(Stephen Gary Wozniak)乔布斯(Steve Jobs)这对奇特的搭档,在他们的车库中缔造了全球最具价值的公司这一英雄史实的诱惑呢?

实际上,多少钱是你所需要的呢?钱能够让你获得快乐吗?一个人一天吃几顿饭?而且,你又能一口气开几辆车呢?大量的研究表明,一旦满足了生活的必须(吃穿和住所),幸福与金钱并无太大关系。“快乐水车”理论(hedonic treadmill)也印证了没有什么样的享受可以使人持久快乐。

经营一家可以养活自己或几个人的小软件公司根本不需要很多钱去购买硬件和软件,需要的仅仅是一个想法,良好的开发技能和充足的时间和毅力。许多lifestyle business开始的时候都是创始人利用晚上和周末的时间开发产品,而同时做了一份全职工作。

那么lifestyle business需要多少钱呢?这得具体情况具体分析。许多失败的例子,通常是因为缺乏市场。但我还认识一些少数将买卖做了起来的lifestyle programmer。我相信他们中的很多人都赚得盆满钵满。就我个人而言,通过销售我自己的软件,相对与我曾经为别人打工的时候,收入平均上涨了很多。

如果你有足够的钱来支撑花销,那做一位lifestyle programmer将会是很美好的生活,可以比较随意地支配自己的生活,随心所欲。

如果你想拥有任何真正的成功机会,那么你就需要在市场营销方面花费大量的时间。我兼任的客户支持工作使我高兴地知道很多人都在用我的软件。总而言之,这是很棒的生活方式,朝九晚五的工作无法替代。

http://www.csdn.net/article/2014-03-21/2818897

29 3月

敏捷已死,敏捷性万岁

文 / Dave Thomas:敏捷软件开发宣言创始人之一,《程序员修炼之道》与《Programming Ruby》的作者。

译 / 白云鹏

原文链接:http://pragdave.me/blog/2014/03/04/time-to-kill-agile/

十三年前,为了分享共同的软件开发理念,我们十七位中年人聚集在犹他州的滑雪胜地雪鸟(Snowbird)雪场。我们想知道是否能把这些理念描述出来。

用了不到一天的时间,我们将这些理念简单罗列,作为敏捷软件开发宣言(Manifesto for Agile Software Development)将其公布:

个体与交互胜于流程和工具

可用的软件胜于详尽的文档

客户合作胜于合同谈判

响应变化胜于遵循计划

我为我们的所作所为感到自豪。我想这个宣言可以帮助开发者摆脱一些八九十年代出现的不良做法。

这次会议以后,我就再也没参加过任何关于敏捷的活动,也不是敏捷联盟的成员,也不做任何“敏捷”咨询业务。也没有参加宣言发表10周年庆祝活动。

这是为什么呢?因为我觉得任何这些事情都不符合我们所发表宣言的精神,有关敏捷的会议与芭蕾舞会没什么两样,并且让我吃惊的是,围绕宣言的四点形成了一个产业群。

遗憾的是,事实证明了我是对的。“敏捷”这个词已经到了被颠覆的地步,敏捷社区看起来像是顾问和商家兜售服务和产品的大舞台。

所以“敏捷”这个词该下课了。

“敏捷”不应该是名词,而应该是一个形容词,它有其相应的含义。

一旦宣言走红,就像环保和天然一样,敏捷这个词就会变成营销术语。因为它变成了一种品牌,会被滥用而失去原有的含义。

这伤害的是每个人,我则对开发者的伤害尤其敏感。敏捷不是简单地编写代码,而是开发者本能地寻求可以帮助他们更有效创造价值的方法。我则仍然坚信,信守这个宣言会有意于开发者。

而一旦敏捷这个词变了味儿,开发者就不会再用它作为实践中的有效指南了。

转向右边

我们再来看看宣言中的四项理念:

个体与交互胜于流程和工具

可用的软件胜于详尽的文档

客户合作胜于合同谈判

响应变化胜于遵循计划

左边的短语代表理想,左右之间选择,敏捷软件开发者则偏爱左边。

从顾问和商家那里看到的则是“敏捷”这个词的贬值和滥用。当然,对于一些顾问,事实可能也不尽然。

回归根本

下面是敏捷方法应该做的事情:

做什么?

  • 找到问题
  • 朝着自己的目标迈出一小步
  • 基于获取的信息,调整自己的认识
  • 重复上述步骤

如何做?

当面对两个类似的选项时,选择容易修改的那个。

上面的四条准则和一项实践原则概括了高效的软件开发方法。

这些准则和原则都是动词短语,它们告诉了我们做什么,如何做。

我也要说两句。让我们摒弃没有敏捷精神的说法,换成一个描述我们应该做哪些事情的词语。

让开发带有敏捷性

你不是一位敏捷程序员,而你是一位具有敏捷性的程序员。 你所在的团队不是敏捷团队,而你的团队显露出敏捷性。 你不使用敏捷工具,而你使用工具增强自己的敏捷性

敏捷这个词很容易联系任何事物,而敏捷性则不容易被挪用。

你不能购买经验,只能自己去实践。

让付出得到保护

总之,行胜于言,但好的称谓有助于高效沟通。

我们已经失去了敏捷。让我们守住敏捷性,让它保持原本的含义。

31 1月

软件测试人员的基本修养

见到题目,你或许会想起电影《喜剧之王》中尹天仇所看的《演员的自我修养》一书,还有那句经典的台词:我是一个演员,跑龙套的也是演员!更会对影片中周星驰所扮演角色对梦想成为一名出色的演员而孜孜以求的情节记忆犹新。人们说行行相通,一通百通。我们这里就说说测试人员的基本修养。

代码编写,不可或缺

乔布斯说:Design is not just what it looks like and feels like, design is how it works(设计不仅是外形和感觉,设计关乎如何运作)。那么可以说测试亦是如此,测试不是简单地拿过来用一用。当开发人员将开发完成的软件提交到测试人员那里以后,测试人员首先需要做的是迅速透彻地理解软件的功能。你会说这是需求讨论阶段已经介入的工作,没错,但除了理想状况,很多时候是赶鸭子上架,容不得按常理出牌。或者你会说要先做版本验证测试(BVT)查看其可测性,但这都是理想状况。

而无论如何,你首先要搞明白提交过来的东西具备哪些功能以及是如何工作的?事先准备好满足测试需要的软硬件环境自然不必多说。开发经验的作用不光局限于对编码及相关技术的理解,还会使你更加了解开发人员的心理感受,从编码心理和工作习惯的角度,更好地弄懂软件是如何工作的。这一点多多少少有点儿只可意会不易言传的感觉。我在工作中切身体会到,有些朋友搞定编码的思路,可以使人强烈感受到一股强大的、严密的逻辑气息。那思路和风格从头到尾自成一体——气派、美妙,令人赞叹不已。

世界著名计算机科学家,1984年图灵奖获得者Niklaus Wirth提出“算法+数据结构=程序”。清代人薛雪所撰《一瓢诗话》中有:如此体会,则诗神诗旨,跃然纸上。那么我要说:如此体会,则码神码旨,亦跃然纸上。

全面深入,T型路线

T字型知识架构是指在细分领域细致专精,相关技术领域也要有所了解。测试人员真的需要了解相关技术吗?答案是肯定的。这里说的相关技术并非指测试相关,而是指开发所用的相关技术。说得再直白些,最好是懂得相关技术,甚至是该领域的技术专家。

我曾亲身经历过这样一件事情:在一个有着广泛市场影响的项目中,新版本发布增加了新的功能,在HTML页面中使用JavaScript来控制控件的显现。而发布时间紧迫,不允许有更多的时间使用正向用例来验证功能的正确性。尽管如此,我们也针对这小小的控件设计了将近百条用例。涉及的方面包括从页面的正反向跳转来验证控件的版本升级,到控件的跨域调用、浏览器的兼容、服务设置及干扰,如此种种,无一不需要通过了解相关技术,才能设计出有价值的用例。当然,有些有价值的用例来自于使用习惯,这可以说是很难有章可循的,需要靠经验的积累。最后,还要检查JavaScript文件内容是否正确。这样一来,最大限度地保证了产品上线后该功能点万无一失。

理清思路,有的放矢

很多人会认为,在测试工作中引入巧妙的编程技巧或者使用酷炫无比的技术手段,就代表测试水平高超。这种做法显然舍本求末,没有明白测试行为本身的目的。对于专业测试人员,这点误区可以理解,但不可接受。软件测试的目的,一方面是为了尽可能发现软件存在的缺陷,追踪直至解决这些缺陷;另一方面是为了度量被测试对象质量的优劣程度,对可能出现的问题从技术和其他方面采取相应的措施。两者都是为了降低潜在的商业风险。

一般来说,我们首先会根据软件系统本身的特点,其应用场景及开发人员等相关资源,去制订相应的测试策略,其中包括制订测试计划、分配测试资源、设计测试用例等。测试工作前期的大部分内容,不仅需要相关的技术知识,还包括更多的相关应用领域的知识和经验,以及分析能力。而这一切行为皆为降低产品潜在的商业风险所服务。诚然,使用优美的代码和酷炫的技术完成测试任务无可厚非,而无论如何,主旨不可偏离。

积基树本,夯实基础

好比说,找来一些帮手来垒墙,这自然不需要什么高深的建筑理论,但要做对整体工程进行把控的建筑工程师则需要读过建筑理论,掌握相关的基础知识。计算机科学领域中的基础知识,包括数据结构、操作系统、编译原理、数据库原理等。基础知识越是夯实饱满,也才越容易被融会贯通、结合实践从而得到宝贵的升华。数据库产品种类繁多,各类软件开发框架也层出不穷,而不变的永远是基础知识和基本原理。假如你明白高级语言应用开发学习的内容无外乎语法、框架和类库这三部分,学习起来自然不会眉毛胡子一把抓。

在计算机科学领域,如果涉及性能优化(时间复杂度、空间复杂度、数据库、操作系统、网络、并行计算、向量计算等)、复杂的数据结构、协议模型等特殊的问题,那么基础知识也就成了解决问题的必要条件。不用多说,作为专业技术人员,牢牢掌握这些知识是走向一流水平的不二法门。顺便说句题外话,这些基础知识同时也被看做试金石,可以帮助你进入一流水平的研发团队。

与人分享,谈吐有致

与人打交道,就难免涉及人际方面的事宜。沟通的技巧和方式自然是举不胜举,说上三天三夜也未必穷尽。所以在这里对此高谈阔论多少会显得有些捉襟见肘。但很重要且有效的一点沟通技巧可能会被忽略,那就是“不抱怨,找方法”。当团队之间、成员之间需要就某个问题进行交涉,甚至可能会发生争论乃至争吵时,最好少说多做,提出解决办法并且付诸行动。这里向大家推荐阅读卡耐基的《人性的弱点》以及费希尔的《沟通力》。希望能汲取其中的营养,完善性格的弱点,潜移默化地在无形之中大显神威。

一丝不苟,持之以恒

在软件测试的整个周期中,可能会出现一些不是总能重现的问题,这类问题的处理方式可大有讲究。从工程学的角度说,遇到这样的问题,不能及时找到原因而修复的话,需要降低该问题的优先级,等待再次重现,保留现场抓取的相关记录。这样既不会影响当前版本的发布,又毫无疏漏地追踪了曾经偶然出现的问题。某个问题一旦出现,是不能轻易放过的。既然不是总能重现,那如何证明此问题是否已经解决呢?当然,反复验证是重要的一方面。经过反复验证,其实还不能有把握地说这类问题已经修复。是不是心里还是没底呢?那就去看一看源码。

每天反复做一件事,坚持10年,任何人都会有所成就。当企业和项目负责人,等待你那封Test Signoff邮件发出的时候,你是否可以满怀信心地点击Send按钮呢?是否可以对发布前提交的版本做到胸有成竹,锦囊之中自有乾坤呢?百年三万六千日,朝着自己的人生目标,努力过好每一天。修养的形成不在于猛攻,而在于点滴的积累和润物无声地打磨。

本文发表在《程序员》杂志2014年1月刊:
http://www.csdn.net/article/2014-01-08/2818071-Tester

2014年1月8日CSDN首页焦点大图推荐:
IMG_0586

15 11月

上海游记

难得有机会在这个年龄在短暂的时间内完成了诸多人生的第一次。

第一次坐火车,还是高铁。别笑我,怎么连火车都没坐过?是的!但这的的确确是第一次去北京以外且不是必须坐飞机或者坐城际长途车也能搞定的城市——上海。讲老实话,身处北京,我对上海并没有太多的向往,知道关于上海的信息,最多也就是从长辈口中闲聊得到的。中国的高铁列次以G打头,即汉语拼语Gao的首字母,一般来说高铁列次发车不会太早,亦不会太晚,这有点儿像远郊地区的长途客车啊!所以,为了在上海停留的时间更长,回来的时候自然选择了飞机。这全世界的列车大概都差不多,这点没有太多的差异感,高铁没有想象的那么酷,尽管很多朋友推荐,个人感觉,还是选择飞机更便捷。

第一次吃上海的馒头。这是有备而来的。我知道馒头这东西,是从一本书中读到的,数年后认识了上海人,才知道上海人所谓的馒头,其实是包子。这样一来,我就更好奇了。所幸此次下榻宾馆附近就有巴比馒头,我无论如何也先买来了一个,尽管一会儿去吃下面要说的,更是有备而来的功德林。为什么只买一个?留着肚子呗!这食品应该不算什么高档货,但是一口下去,外面的包子皮像棉花桃,里面的汁水甜甜的,也不油腻,好吃!这个时候已经朝地铁方向走出了200米,果断回头,又买了两个,一下吃光。有点儿遗憾,此馒头非生煎,看到过这样一段描述生煎馒头的文字:“皮薄不破又不焦,二分酵头靠烘烤,鲜馅汤汁满口来,底厚焦枯是败品。”这不仅介绍了生煎馒头的优点,也提醒食客,凡是“底厚焦枯”的可以不买,其至“罢吃”。底酥、皮薄、肉香。一口咬上去,肉汁裹着肉香、油香、葱香、芝麻香喷薄而出,味道一级!下次再来,但是,巴比馒头,我爱你!

第一次坐了上海的地铁。尽管全世界的地铁模样相似,但是,比如我们是双轨,有些国家是单轨(monorails)。北京的地铁刷卡处的开关门是一开一闭的方式,上海的是个三根铁杆旋滚,容易被硌。买票的时候,哗啦啦,找给了我不少一元硬币,沉,费衣兜儿。上海地铁分段计价,换乘倒是不单收费。但这些不足北京地铁全无。当然,喜欢找硬币这不能算是地铁的专利,上海地区就是流行找硬币,我在全家(FamilyMart)买水也是如此。

第一次大致了解了上海的行政区域划分情况。上海不像北京,以天安门为中心画同心圆。而是被黄浦江隔成了两部分——浦西和浦东。上海地区貌似没有类似北京的内外环一说,比较繁华的区域有:豫园人民广场、南京东路步行街、淮海路、外滩、浦东陆家嘴徐家汇等。我这次没有机会去浦东更多的地方,下次要补上。

第一次乘坐船长5号游轮夜游黄浦江,尽赏魔都靓景。如果北京像个爷们儿,那上海就是位丽人,秀外慧中、国色天香。可谓玉宇妍姝,此处就不太过粉饰夸赞了。乘坐环绕陆家嘴的观光大巴,浏览东方明珠塔金茂大厦环球金融中心以及建设中的上海中心大厦,初次相见而似曾相识。当天早些时候,还去了宋庆龄陵园,这里是以前的上海万国公墓,早年从家父处便耳闻这个地方,因此也是初次见面而似曾相识的感觉。

第一次独自行走上海的夜路。路边的小饭馆与北京别无两样,在宾馆对面,也有和北京很像的大排档,过去看了看都有什么货,差不多,价格也差不多,但是,臭豆腐,北京的大排档没有卖。

功德林南京西路店是此次旅行的尾声。老友调侃我,让我来上海一定去品尝我念念不忘的功德林美食。我如约而至。顺路将一件优衣库(UNIQLO)轻型羽绒背心照单收下。功德林餐馆虽烹调素食,但是社会餐馆,素菜荤烧,规定厨房不允许带进荤腥儿。我点了挂炉烤鸭、菜心海参、四喜烤麸和椒盐排条。什么?不是素的?都是素菜,所谓的烤鸭是用油豆皮制成,海参的原料是香菇和魔芋,形态逼真,味道鲜美绵甜。很赞的!登机前,相识多年却未曾谋面的朋友赶来机场与我见面,一番热情专注的交谈后,彼此道别并期待再见。

最后要说说对上海的城市印象:气候比北京湿润,风格比北京西化,楼高路窄。选择11月份去上海的朋友,还是少穿点儿,不妨提前看看天气预报。连续颠簸,四处游览,好像记不清过了几个时日,但这几个第一次是忘不掉的。

30 9月

《测试驱动开发实战与模式解析》出版说明

本书早在2002年由培生教育出版集团首次出版以来,十余年间,敏捷方法爱好者、业内专业人士将它口口相传,经久不衰。原因何在?我认为这是人们追寻更好的方法,期以获得更丰厚回报的天性使然。测试驱动开发(Test-Driven Development)始于20世纪90年代,历经岁月,成于2003年,也即本书首次出版之时。

从早期的面向过程程序开发到后来的面向对象软件开发,随之诞生了软件构件化、工程化的开发方法。在软件开发方法学中,测试驱动开发较之其他开发方法学可谓历久弥新,自诞生之日就一直受到业内高度关注,也从不乏勇于尝试和探索的有识之士。正如本书作者Kent Beck先生所说,测试驱动开发是一种正确的做事方法。这种方法打破了传统软件开发流程,提出了测试优先(Test First)的编程模式,其中的优点无须赘言。

本人也在实践当中尝试了书中的理论。整个过程轻巧安全,步步推进,给编码工作带来了耳目一新的感觉。译竣之后,也深感此书犹如测试驱动开发之蓝本,不负盛名。

本书(ISBN: 978-7-111-42386-7)由我负责翻译并整理,此过程中的辛酸苦辣一言难尽,但也感到与Kent Beck这样一位出色的业界领袖巨笔际会是一个享受的过程。在成书过程中,机械工业出版社的吴怡编辑给予了莫大的支持和鼓励。承荷兰威科集团(Wolters Kluwer)庾戈华工程师的悉心帮助,解决了不少翻译过程中遇到的困惑。微软美国总部的香钦柏工程师、美国密歇根大学安娜堡分校的葛瑾女士也审阅了书稿,提出了不少中肯建议。借此机会谨致谢忱。还要感谢给予了我很多支持和关心的家人,希望本书的出版,给你们带来快乐!

04 11月

为你用心,致你的信,我们如今

为你用心

候机厅里甜筒咔嚓的响声。

背包后那个萌到老的毛熊。

临别时留下的叮嘱,

每天不变的问候。

天冷了也不觉得孤独。

花开叶落……

夜航飞机带来了所有,

你看那日夜思念的双眸。

我只朝着出口,

穿过拥挤的人流。

看到了你挥舞的手,

甜甜的笑容。

一路上,

你说你等了好久,

就在今天摆酒。

阳光和空气作证,

我们天长地久。

致你的信

记得在大本钟下踩着绵软的夕阳,

还有手牵手迎来的霞光。

大口呼吸着彼此的味道,

那团暖阳照亮了你我的心窗。

半块披萨的牙印儿记录了曾经时光。

百年的钟声传向了蕴满憧憬的远方。

海港一座座的塔吊,

它们的客人来自我们的故乡。

走过的斜坡,飞翔的海鸥,金色的湖面,那是满满的思念。

京华烟雨梦里尝,谁会淡薄了你的模样。

往昔今朝,是一样的暖阳。

我们如今

我们如今有了宝贝,

那是你对我的鼓励。

让我说声谢谢你,

每每可以听到他幸福的哭啼。

剑桥边的小树苗,

你是否也在享受同样的秋季?

我们曾经一起种下你,

希望你长成绿树林。

我努力,

像从前一样威武神气,

保护你和咱们的窝儿,

让它天天有生气。

有件事,

我总是会想起,

阳光和空气做过我们的司仪!

你看那远方高高的天际,

满满都是好福气。

于农历壬辰年孟秋

24 10月

《软件测试人员·(Java)高级》出版说明

承蒙中国劳动社会保障出版社给此机会,编写这本软件测试高级教材,现已完成,付梓出版(ISBN 978-7-5045-9993-3)。全书先后历经5次易稿,两度春秋,既有除夕之夜奋笔疾书的辛勤,也不乏大洋彼岸的春雨中沁润成文的唯美。对概念和基本原理的把握尤甚。本书内容覆盖了所在细分领域的绝大多数实用方法,特别融入了一线的工作经验以及所流行的技术和理念,以飨读者。

全书内容共分为四个章节,主要包括:软件测试基础、软件测试技术及其应用、Java编程与测试实践和测试自动化技术。

书中加入了可达性测试(Accessibility Test)等内容,使技术要点更加全面且具有前瞻性,这归功于一位忘年交——微软美国总部的香钦柏工程师,是我在书稿完结之时请他审阅,他提出的中肯建议。上海企顺信息系统有限公司总经理单迎春先生费心费力审阅书稿内容,不断提出修改意见,自然功不可没。中国劳动社会保障出版社的编辑在成书过程中体现出的专业水准和敬业精神令人心生敬意。策划编辑袁佩佩女士则更是与这本书结下了不解之缘,因为本书与她可爱的小女儿一同诞生。在编写过程中,太多的朋友给予了悉心指导和各种帮助,在此衷心地表示感谢。我的家人亦给予了许多支持和鼓励,希望本书的出版给你们带来快乐。

“太上有立德,其次有立功,其次有立言,虽久不废,此之谓不朽”。写此文字,没有妄自居功之意。自我鞭策,继续前行。

07 7月

从悟空跳槽到“悟能”基金说起

公元二零一零年某日,阳光明媚,浮云淡薄,湛湛蓝天。由猪八戒主要出资,观世音参股合作的基金正式成立,双方在北京宣布该基金秉承大唐时期所行除恶扬善之精神,继往开来。取名“悟能”基金。

翌年,央行连连加息,购房政策缩紧,房地产行业步入寒冬……

销售业绩上不去,走投无路的孙悟空被撵出了公司,继续找工作。总是无巧不成书。就这么着,悟空到了悟能基金。用八戒的话说,这是照顾了昔日的手足情。

八戒给了悟空高薪职位,葫芦里卖的神马药木有人知道。又是一个阳光明媚的日子,悟空来到悟能基金入职,签署劳动合同,内容如下:

悟空小姐/先生:

您好,很荣幸地通知您,您所应聘的齐天大圣七十二变之鸡精经理(职位)已获通过,恭喜您成为我们公司的一员,首先我代表弓丝(屌丝的哥哥,发音“公司”)及所有淫员欢迎您的加盟!

1.由于我公司小心驶得万年船,欲与猴哥再度联手,取得真金,您的试用期是八年

2.您的基本工资是1元,绩效工资是22222222222222222222222222“刀乐儿”。试用期满,经评估合格后,根据您的能力和表现,绩效工资给您加成倍的“刀乐儿”。

3.如果您不能在我公司规定时间报到的话,请联系人力资源不(不怎么样,发音“部”)的Lady GuanGuan女士。

                                                   您诚挚的:师弟八戒

                                                   人力资源部负责人:Lady GuanGuan(观世音)

                                                   联系电话:87168074(八格耶鲁,把你气死)

孙:你这不是霸王条款嘛,你个呆子!

猪:哼~哼~ 猴儿哥,我哪霸王了~?这不写得真真儿的“刀乐儿”,这哪儿的话呀这是~哼哼~

孙:去~!这不都你说了算,到时说我表现不好,那绩效工资不全泡汤了,跟我玩儿这个,你丫的是不是得改个姓儿呀!

猪:你瞧猴儿哥,这不能够,没有功劳也有苦劳,没有苦劳也有疲劳,这,哼哼……不会呀

孙:就信你一回!

悟空径直走了出去,进了隔壁的成都小吃。点了地三鲜盖饭正吃,这时进来三个人,“矬、胖、丑、黑、旧”,不是中国人呀!搞不清哪里人。嘴里叼着红万(原味儿Marlboro),“两公一母儿”,典型的菲佣形象。真叫人纳闷儿,怎么金鱼池里面跳进来仨松鼠儿鸡!

那女人坐下,牙齿狠命咬着红万的头儿,好像要嘬出来什么似的,左脚撇向窗户,右膝顶住腮帮,腿叉开的尺度超级夸张,这架势像是要用私处把眼前这张饭桌吞下去。那俩矬男反带了个西瓜帽儿,嘚吧嘚满脸淫笑,吞吐大黄。

悟空想起了大唐时期自己在国外入乡随俗,彬彬有礼,想起了宣武英国佬强奸门,列车上俄国佬儿的粗口儿门,还有那与洋男伴儿狼狈为奸的贱女人,心里骂了一句:cnmd!

出了成都小吃,门口儿立着一对儿对儿关系暧昧的男男女女。说不清楚是什么关系。女人秀着呼之欲出天地有双白里带着骚腥味儿的Q奶。再看男人,说不清是什么状态,目似瞑意暇甚满脸一副不学好的样子,再看看下面,好像没有勃起,但那脸上像是开了一朵儿比勃起还兴奋娇艳的桃花儿……

悟空这时想起了在某本教科书上学到的,耳熟能详一段儿文字(大意):

“资本主义生产关系的实质是以生产资料私有制为基础的雇佣劳动制度。生产资料的占有者对劳动者进行剥削。劳动者忍受饥寒交迫不得不去资本家工厂做工……”。

(一年后,基金市场遭遇股债双杀……)

关:孙猴子,根据公司相关规定,你的绩效考核最末,我们认为你不能胜任目前的工作,于是决定将你调岗,工资调整为基本工资,1元。

孙:我告你们丫的去!

关:呸!你当衙门口儿朝着猴儿屁股开的!你告去!

关:有任何疑问,请您致电悟能基金人力资源部!撒油哪拉~

门外偷听的猪八戒露着牙花子嘿嘿地得意地笑。