Swift的强大之处

在写任何东西之前我需要承认我是带有偏见的:我爱 Swift。我认为这是从我开始接触 Cocoa 生态系统以来这个平台上发生的最好的事情。我想通过分享我在 Swift,Objective-C 和 Haskell 上的经验让大家知道我为何这样认为。写这篇文章并不是为了介绍一些最好的实践 (写这些的时候 Swift 还太年轻,还没最好实践被总结出来),而是举几个关于 Swift 强大之处的例子。

给大家一些我的个人背景:在成为全职 iOS/Mac 工程师之前我花了几年的时间做 Haskell (包括一些其他函数式编程语言) 开发。我仍然认为 Haskell 是我所有使用过的语言中最棒的之一。然而我转战到了 Objective-C,是因为我相信 iOS 是最令人激动的平台。刚开始接触 Objective-C 的时候我有些许沮丧,但我慢慢地学会了欣赏它。

当苹果在 WWDC 发布 Swift 的时候我非常的激动。我已经很久没有对新技术的发布感的如此兴奋了。在看过文档之后我意识到 Swift 使我们能够将现有的函数式编程知识和 Cocoa API 无缝地整合到一起。我觉得这两者的组合非常独特:没有任何其他的语言将它们融合地如此完美。就拿 Haskell 来说,想要用它来使用 Objective-C API 相当的困难。同样,想用 Objective-C 去做函数式编程也是十分困难的。

在 Utrecht 大学期间我学会了函数式编程。因为是在很学术的环境下学习所以并没有觉得很多复杂的术语 (moands,applicative functors 以及很多其他的东西) 有多么难懂。我觉得对很多想学习函数式编程的人来说这些名称是一个很大的阻碍。

不仅仅名称很不同,风格也不一样。作为 Objective-C 程序员,我们很习惯于面向对象编程。而且因为大多数语言不是面对对象编程就是与之类似,我们可以看懂很多不同语言的代码。阅读函数式编程语言的时候则大不相同 – 如果你没有习惯的话看起来简直莫名其妙。

那么,为什么你要使用函数式编程呢?它很奇怪,很多人都不习惯而且学习它要花费大量的时间。并且对于大多数问题面向对象编程都能解决,所以没有必要去学习任何新的东西对吧?

对于我来说,函数式编程只是工具箱中的一件工具。它是一个改变了我对编程的理解的强大工具。在解决问题的时候它非常强大。对于大多数问题面向对象编程都很棒,但是对于其他一些问题应用函数式编程会给你带来巨大的时间/精力的节省。

开始学习函数式编程或许有些痛苦。第一,你必须放手一些老的模式。而因为我们很多人常年用面对对象的方式去思考,做到这一点是很困难的。在函数式编程当中你想的是不变的数据结构以及那些转换它们的函数。在面对对象编程当中你考虑的是互相发送信息的对象。如果你没有马上理解函数式编程,这是一个好的信号。你的大脑很可能已经完全适应了用面对对象的方法来解决问题。

例子

Read on →

Swift的函数式API

在过去的时间里,人们对于设计 API 总结了很多通用的模式和最佳实践方案。一般情况下,我们总是可以从苹果的 Foundation、Cocoa、Cocoa Touch 和很多其他框架中总结出一些开发中的范例。毫无疑问,对于“特定情境下的 API 应该如何设计”这个问题,不同的人总是有着不同的意见,对于这个问题有很大的讨论空间。不过对于很多 Objective-C 的开发者来说,对于那些常用的模式早已习以为常。

随着 Swift 的出现,设计 API 引起了更多的问题。绝大多数情况下,我们只能继续做着手头的工作,然后把现有的方法翻译成 Swift 版本。不过,这对于 Swift 来说并不公平,因为和 Objective-C 相比,Swift 添加了很多新的特性。引用 Swift 创始人 Chris Lattner 的一段话:

Swift 引入了泛型和函数式编程的思想,极大地扩展了设计的空间。

在这篇文章里,我们将会围绕 Core Image 进行 API 封装,以此为例,探索如何在 API 设计中使用这些新的工具。 Core Image 是一个功能强大的图像处理框架,但是它的 API 有时有点笨重。 Core Image 的 API 是弱类型的 - 它通过键值对 (key-value) 设置图像滤镜。这样在设置参数的类型和名字时很容易失误,会导致运行时错误。新的 API 将会十分的安全和模块化,通过使用类型而不是键值对来规避这样的运行时错误。

目标

我们的目标是构建一个 API ,让我们可以简单安全的组装自定义滤镜。举个例子,在文章的结尾,我们可以这样写:

1
2
let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor)
let result = myFilter(image)

上面构建了一个自定义的滤镜,先模糊图像,然后再添加一个颜色蒙版。为了达到这个目标,我们将充分利用 Swift 函数是一等公民这一特性。项目源码可以在 Github 上的这个示例项目中下载。

Filter 类型

CIFilterCore Image 中的一个核心类,用来创建图像滤镜。当实例化一个 CIFilter 对象之后,你 (几乎) 总是通过 kCIInputImageKey 来输入图像,然后通过 kCIOutputImageKey 获取返回的图像,返回的结果可以作为下一个滤镜的参数输入。

在我们即将开发的 API 里,我们会把这些键值对 (key-value) 对应的真实内容抽离出来,为用户提供一个安全的强类型 API。我们定义了自己的滤镜类型 Filter,它是一个可以传入图片作为参数的函数,并且返回一个新的图片。

1
typealias Filter = CIImage -> CIImage
Read on →

iOS绘图详细解析

  本文是《Programming iOS5》中Drawing一章的翻译,考虑到主题完整性,翻译版本中加入了一些书中未涉及到的内容。希望本文能够对你有所帮助。

      Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。为了从感官上对这些概念做一个入门的认识,你可以运行一下官方的example code

     iOS支持两套图形API族:Core Graphics/QuartZ 2D 和OpenGL ES。OpenGL ES是跨平台的图形API,属于OpenGL的一个简化版本。QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分。需要注意的是:OpenGL ES是应用程序编程接口,该接口描述了方法、结构、函数应具有的行为以及应该如何被使用的语义。也就是说它只定义了一套规范,具体的实现由设备制造商根据规范去做。而往往很多人对接口和实现存在误解。举一个不恰当的比喻:上发条的时钟和装电池的时钟都有相同的可视行为,但两者的内部实现截然不同。因为制造商可以自由的实现Open GL ES,所以不同系统实现的OpenGL ES也存在着巨大的性能差异。

       Core Graphics API所有的操作都在上下文中进行。所以在绘图之前需要获取该上下文并传入执行渲染的函数内。如果你正在渲染一副在内存中的图片,此时就需要传入图片所属的上下文。获得一个图形上下文是我们完成绘图任务的第一步,你可以将图形上下文理解为一块画布。如果你没有得到这块画布,那么你就无法完成任何绘图操作。有许多方式获得一个图形上下文,这里我介绍两种最为常用的获取方法。

  第一种方法就是创建一个图片类型的上下文。调用UIGraphicsBeginImageContextWithOptions函数就可获得用来处理图片的图形上下文。利用该上下文,你就可以在其上进行绘图,并生成图片。调用UIGraphicsGetImageFromCurrentImageContext函数可从当前上下文中获取一个UIImage对象。记住在你所有的绘图操作后别忘了调用UIGraphicsEndImageContext函数关闭图形上下文。

  第二种方法是利用cocoa为你生成的图形上下文。当你子类化了一个UIView并实现了自己的drawRect:方法后,一旦drawRect:方法被调用,Cocoa就会为你创建一个图形上下文,此时你对图形上下文的所有绘图操作都会显示在UIView上。

  判断一个上下文是否为当前图形上下文需要注意的几点:

  • UIGraphicsBeginImageContextWithOptions函数不仅仅是创建了一个适用于图形操作的上下文,并且该上下文也属于当前上下文。
  • drawRect方法被调用时,UIView的绘图上下文属于当前图形上下文。
  • 回调方法所持有的context:参数并不会让任何上下文成为当前图形上下文。此参数仅仅是对一个图形上下文的引用罢了。

  作为初学者,很容易被UIKit和Core Graphics两个支持绘图的框架迷惑。

  UIKit

  像UIImage、NSString(绘制文本)、UIBezierPath(绘制形状)、UIColor都知道如何绘制自己。这些类提供了功能有限但使用方便的方法来让我们完成绘图任务。一般情况下,UIKit就是我们所需要的。

使用UiKit,你只能在当前上下文中绘图,所以如果你当前处于

UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个context:参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住最后别忘了调用UIGraphicsPopContext函数恢复上下文环境。

  Core Graphics

  这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit。

  使用Core Graphics之前需要指定一个用于绘图的图形上下文(CGContextRef),这个图形上下文会在每个绘图函数中都会被用到。如果你持有一个图形上下文context:参数,那么你等同于有了一个图形上下文,这个上下文也许就是你需要用来绘图的那个。如果你当前处于UIGraphicsBeginImageContextWithOptions函数drawRect:方法中,并没有引用一个上下文。为了使用Core Graphics,你可以调用UIGraphicsGetCurrentContext函数获得当前的图形上下文。

  至此,我们有了两大绘图框架的支持以及三种获得图形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions。那么我们就有6种绘图的形式。如果你有些困惑了,不用怕,我接下来将说明这6种情况。无需担心还没有具体的绘图命令,你只需关注上下文如何被创建以及我们是在使用UIKit还是Core Graphics。

  第一种绘图形式:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。

1
2
3
4
5
- (void) drawRect: (CGRect) rect {
	UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
	[[UIColor blueColor] setFill];
	[p fill];
}
Read on →

从UIKit到APPKit

Mac 不仅是一个强大的生产平台,也十分值得你为其开发一些东西。去年我们开始构建我们的第一款 Mac 应用,成功为我们日常工作所在的平台开发点东西是一次十分美好的体验。但是,和为 iOS 系统开发应用相比,在我们了解 Mac 特性的过程中也遇到了一些困难。这篇文章总结了我们从这一过渡中得到的经验,希望能启发你们去开发自己的第一个 Mac 应用。

在这篇文章中,我们假定 OS X Yosemite 为我们默认使用的系统。今年,为了融合 iOS 和 OS X,苹果站在开发者的角度对 OS X 做出了巨大的改进。不过,我们会指出哪些特性仅适用于 Yosemite,而哪些特性也适用于之前的系统版本。

相似点

尽管 iOS 和 OS X 是两个独立的系统,它们却有很多共性。先就开发环境而言,它们使用同样的开发语言,同样的IDE。所以你会对这一切都感到非常熟悉。

更重要的是,OS X 和你已经熟悉的 iOS 共用许多框架,像 Foundation,Core Data 和 Core Animation。今年,Apple 进一步整合两个平台,并给 Mac 带来了一些之前仅能在 iOS 上面使用的框架,其中一个例子就是 Multipeer Connectivity。在更底层的地方,你立刻可以看到你熟悉的 API:Core Graphics,Core Text,libdispatch 等等。

真正开始有区别的是 UI 框架 — AppKit 早在 NeXT 时代就已面世并不断进化,而 UIKit 就像是简约版及现代版的 AppKit。出现这种情况的原因,是当 Apple 推出 iPhone 时可以从头开始,并吸取 AppKit 的经验:把已证实过可行的概念和部件拿过来用,并改进不够精良的设计。

如果你对这个转换是怎么发生的感兴趣,请观看前 Apple iOS 应用总监 Nitin Ganatra 播客上的精彩剧集:System 7 to CarbonOS X to iOS,以及 iPhone to iPad

考虑到这一点,也就不奇怪为什么 UIKit 和 AppKit 仍旧共享许多概念了。UI 是基于 window 和 view 构建起来的,消息像 iOS 一样通过响应者链传递。此外,UIViewNSViewUIControlNSControlUIImageNSImageUIViewControllerNSViewControllerUITextViewNSTextView…这样的例子不胜枚举。

看起来就像你仅需把 UI 前缀替换为 NS 前缀,你就可以用同样的方法使用这些类。但事实是在很多情况下这并不奏效。它们在实现上并没有在概念上那么相似。你在 iOS 上的经验至多能帮你大致了解构建用户界面的基础,以及使用很多设计模式,比如代理,都是类似的。但是细节是魔鬼 — 你真的应该通过阅读文档来学习如果使用这些类。

下一节,我们来看看那些常见的陷阱。

不同点

Window 和 Window Controller

Read on →

闭包,你了解多少?

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。 闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。之后,闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。 在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的 函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。 闭包一词经常和匿名函数混淆。这可能是因为两者经常同时使用,但是它们是不同的概念。

闭包和状态表达闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中。 不过,用这种方式来使用闭包时,闭包不再具有引用透明性,因此也不再是纯函数。即便如此,在某些“近似于函数式编程语言”的语言,例如Scheme中,闭包还是得到了广泛的使用。

闭包和第一类函数

典型的支持闭包的语言中,通常将函数当作第一类对象——在这些语言中,函数可以被当作参数传递、也可以作为函数返回值、绑定到变量名、就像字符串、整数等简单类型。例如以下Scheme代码:

1
2
3
4
5
; Return a list  of all books with at least THRESHOLD copies sold.
(define  (best-selling-books  threshold)
   (filter
    (lambda (book) (>= (book-sales book)  threshold))
    book-list))

在这个例子中,lambda表达式(lambda (book) (>= (book-sales book) threshold))出现在函数best-selling-books中。当这个lambda表达式被执行时,Scheme创造了一个包含此表达式以及对threshold变量的引用的闭包,其中threshold变量在lambda表达式中是自由变量。 这个闭包接着被传递到filter函数。这个函数的功能是重复调用这个闭包以判断哪些书需要增加到列表那些需要丢弃。因为闭包中引用了变量threshold,所以它在每次被filter调用时都可以使用这个变量,虽然filter可能定义在另一个文件中。 Read on →

iOS 8 体验推送

一直更新了iOS8,但是一直没有开始研究这个iOS8,今天因为项目用到了推送,于是体验了iOS8的推送,先讲讲这个推送。目前分为四个推送:用户推送,本地推送,远程推送,地理位置推送。

推送界面

用户推送

我们先开始讲这个用户推送,我们要使用之前必须先注册这个推送,用户要允许这个程序进行推送

注册过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    UIUserNotificationType  types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert ;
    UIUserNotificationSettings  *mySettings  = [UIUserNotificationSettings settingsForTypes:types categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
    return YES;
}


- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    UIUserNotificationType allowTypes = [notificationSettings types];
}


- (void)getReadyForNotification
{
    UIUserNotificationSettings *currentNotificationSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
    [self checkSetting:currentNotificationSettings];

}

总结就是三个方法进行注册

推送注册三个方法

我们现在仅仅是注册了通知的设置,还要注册推送通知的行为,在iOS8中,行为能直接在推送消息进行,如回复消息,拒绝消息等

直接在推送消息进行回复

这个真心碉堡了

我们如何能进行这些行为,首先我们需注册这些行为。

Read on →

使用VIPER构建iOS应用

无觅关联推荐,快速提升流量

建筑领域流行这样一句话,“我们虽然在营造建筑,但建筑也会重新塑造我们”。正如所有开发者最终领悟到的,这句话同样适用于构建软件。

编写代码中至关重要的是,需要使每一部分容易被识别,赋有一个特定而明显的目的,并与其他部分在逻辑关系中完美契合。这就是我们所说的软件架构。好的架构不仅让一个产品成功投入使用,还可以让产品具有可维护性,并让人不断头脑清醒的对它进行维护!

Read on →

OC的子类

这篇文章跟我以往的文章有点不一样。它主要是一些思想与模式的汇集,而不是一篇指南。下面我所写的模式几乎全都来之不易,都是我犯了错之后才学到的。我并不认为自己是子类方面的权威,但我确实想把我学到的一些东西分享出来。别把本文当做权威指南,它只是一些例子的汇集。

在被问到 OOP(面向对象编程)的时候,Alan Kay(OOP 的发明人)写到:它跟类无关,但跟消息有关。^1然而,很多人的关注点仍然还在类层次上。在本文中,我们会看几个我们可能会把注意力放在创建复杂的类结构上的例子,并给出更有用的替代方案。根据经验,这样会让代码更简单,更易维护。关于这个话题,在 Clean Code(中文版:代码整洁之道)和 Code Complete(中文版:代码大全)中已经有大量讨论。推荐你阅读这两本书。

何时用子类

首先,我们讨论几种使用子类比较合适的场景。如果你要写一个自定义布局的 UITableViewCell ,那就创建一个子类。这同样适用于几乎每个视图。一旦你开始布局,把这块代码放入子类就更合理一些,不光代码得到了更好的封装,你也能得到一个可在工程之间重用的组件。

Read on →

MVVM介绍

有时我感觉就像我不知道在做什么。虽然我知道自己的设计模式——就像任何好的编程人员那样 —— 但我太接近我在做的产品以至于不能客观地衡量我的架构决策的有效性。当队伍中来了另外一位开发者时,我意识到我们陷入困境了。

从没听过 MVC ?有人称之为 Massive View Controller(重量级视图控制器),这就是我们那时候的感觉。我不打算介绍令人汗颜的细节,但说实在的,如果我不得不再次重来一次,我绝对会做出不同的决策。

我会修改一个关键架构,并将其带入我从那时起就在开发的各种应用,即使用一种叫做 Model-View-ViewModel 的架构替换 Model-View-Controller。

所以,MVVM 到底是什么?与其专注于说明 MVVM 的来历,不如让我们看一个典型的 iOS 是如何构建的,并从那里了解 MVVM:

Typical Model-View-Controller setup

我们看到的是一个典型的 MVC 设置。Model 呈现数据,View 呈现用户界面,而 View Controller 调节它两者之间的交互。Cool!

稍微考虑一下,虽然 View 和 View Controller 是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。你什么时候看到一个 View 能够与不同 View Controller 配对?或者反过来?所以,为什么不正规化它们的连接呢?

Read on →

交互式动画

在2007年,乔布斯在第一次介绍 iPhone 的时候,iPhone 的触摸屏交互简直就像是一种魔法。最好的例子就是在他第一次滑动 TableView 的展示上。你可以感受到当时观众的反应是多么惊讶,但是对于现在的我们来说早已习以为常。在展示的后面一部分,他特别指出当他给别人看了这个滑动例子,别人说的一句话: “当这个界面滑动的时候我就已经被征服了”.

是什么样的滑动能让人有‘哇哦’的效果呢?

滑动是最完美地展示了通过触摸屏直接操作的例子。滚动视图遵从于你的手指,当你的手指离开屏幕的时,视图会自然地继续滑动直到该停止的时候停止。它用自然的方式减速,甚至在快到界限的时候也能表现出细腻的弹力效果。滑动在任何时候都保持相应,并且看上去非常真实。

动画的状态

在 iOS 中的大部分动画仍然没有按照最初 iPhone 指定的滑动标准实现。这里有很多动画一旦它们运行就不能交互(比如说解锁动画,主界面中打开文件夹和关闭文件夹的动画,和导航栏切换的动画,还有很多)。

然而现在有一些应用给我一种始终在控制动画的体验,我们可以直接操作那些我在用的动画。当我们将这些应用和其他的应用相比较之后,我们就能感觉到明显的区别。这些应用中最优秀的有最初的 Twitter iPad app, 和现在的 Facebook Paper。但目前,使用直接操作为主并且可以中断动画的应用仍然很少。这就给我们做出更好的应用提供了机会,让我们的应用有更不同的,更高质量的体验。

真实交互式动画的挑战

当我们用 UIView 或者 CAAnimation 来实现交互式动画时会有两个大问题: 这些动画会将你在屏幕上的内容和 layer 上的实际的特定属性分离开来,并且他们直接操作这些特定属性。

模型 (Model) 和显示 (Presentation) 的分离

Core Animation 是通过分离 layer 的模型属性和你在屏幕上看到的界面 (显示层) 的方式来设计的,这就导致我们很难去创建一个可以在任何时候能交互的动画,因为在动画时,模型和界面已经不能匹配了。这时,我们不得不通过手动的方式来同步这两个的状态,来达到改变动画的效果:

1
2
3
view.layer.center = view.layer.presentationLayer.center;
[view.layer removeAnimationForKey:@"animation"];
// 添加新动画

直接控制 vs 间接控制

CAAnimation 动画的更大的问题是它们是直接在 layer 上对属性进行操作的。这意味着什么呢?比如我们想指定一个 layer 从坐标为 (100, 100) 的位置运动到 (300, 300) 的位置,但是在它运动到中间的时候,我们想它停下来并且让它回到它原来的位置,事情就变得非常复杂了。如果你只是简单地删除当前的动画然后再添加一个新的,那么这个 layer 的速率就会不连续。

然而,我们想要的是一个漂亮的,流畅地减速和加速的动画。

只有通过间接操作动画才能达到上面的效果,比如通过模拟力在界面上的表现。新的动画需要用 layer 的当前速度矢量作为参数传入来达到流畅的效果。

看一下 UIView 中关于弹簧动画的 API

animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:

,你会注意到速率是个 CGFloat。所以当我们给一个移动 view 的动画在其运动的方向上加一个初始的速率时,你没法告知动画这个 view 现在的运动状态,比如我们不知道要添加的动画的方向是不是和原来的 view 的速度方向垂直。为了使其成为可能,这个速度需要用向量来表示。

解决方案

Read on →