API Design for ios 译文

来源:互联网 发布:产品视频制作软件 编辑:程序博客网 时间:2024/04/28 07:36
API Design 译文
 


译文

本文来源于matt gemmell困于自己英语水平问题, 若有错误见谅。

One of the development tasks I do most often is designing the API for a reusable component. The components are usually for iOS (though sometimes they’re for OS X), and are invariably GUI controls or views of some kind.

我最常做的工作之一就是设计可重用的组件,这些组件一般用于iOS(一些时候也用在OS X)的GUI控件或者某些视图上。

I’ve designed literally dozens of component APIs over the years, including for clients like Apple, and I’ve learned quite a bit about the process. I periodically release open source components too, and the feedback I’ve had has helped me put together a set of guidelines for API design that I’d like to share with you.

多年来我设计了几十个API组件都已经成功出现在了大家的视线里,比如一些苹果风格的客户端,并且我对这个开发过程有了一定的了解。我定期也会发布一些开源的组件,并且从中获得了很多反馈让我受益良多,与此同时我整理了一套API设计的指南,现在我将成果分享给大家。

This is an important topic, whether you’re an open source contributor, or working as part of a team on a large app, or just creating your own software. Just like the first launch experience of an app, your API is part of the first impression that a developer will have with your code, and will have a huge impact on whether they use it or throw it away.

这是一个很重要的话题,不论你致力于开源,与其他成员一起完成一个大型软件,或者独自创作。就像第一次打开一个应用程序的体验,你的API将是开发者对你的代码的第一印象,他们是否使用它,或者把它扔掉,等都产生巨大的影响。

APIs are UX for developers. I’ve always been surprised that there isn’t more material written about this aspect of our work, in a way that’s specific to the popular platforms.

APIs 是对开发者的交互设计,我时常感到惊讶为什么没有很多资料提到这方面的工作,它是非常受欢迎的。

As we go through some guidelines, I’m going to use my most recently released open source GUI component, MGTileMenu, as an example where necessary. You can read all about MGTileMenu here first, if you like.

就像我们常看到的指南那样,让我来用我最近发布的开源组件MGTileMenu当做例子来描述,你可以先阅读”MGTileMenu”MGT来了解他,当然如果你愿意的话。

素质(Desirable qualities)

API design is very much like user interface and user experience design. Your target audience has different needs and characteristics, but they’re still humans who are looking to get a job done. As with a friendly, usable app UI, you’re still trying to make your API:

API设计和用户界面设计以及用户体验设计非常之相像。你的目标用户总是有着不同的需求以及特点,但是他们最终目的依然是完成工作。作为一个友好的可用性高的应用UI,你需要尝试让你的API:

  • 直观(Intuitive)
  • 灵活 Forgiving
  • 松耦合 Frictionless

As with any piece of software designed for use by humans, we have to think about the use cases. We have to make the most commonly-needed stuff easy, without undue configuration. Default behaviours should be useful as-is, and should be sensibly chosen. The software should be discoverable, and should allow the user to generalise from known paradigms. It’s all exactly the same set of principles as when we’re creating UIs.

以人为本的软件,我们需要思考实际用例,让一些常用的东西不需要过多的配置就可以容易使用。而你设计的默认行为应该是最实用最明智的那个。软件的功能应该是容易发现的,并且可以让用户从已知的行为模式中概括出来。这套准则应该和创建UI的时候完全一致。

开发接口(The developer interface)

Components are interacted with by the developer using four primary explicit means:

Components是通过下四种方式和开发者互动:

  • The class interface: its exposed properties and methods.
  • The delegate protocol, where relevant.
  • The data-source protocol, where appropriate.
  • Any provided notifications.

We have to design each of those, judiciously and deliberately, for human use. There are two key questions when you’re thinking about the API:

以人为本的设计我们需要谨慎的考虑,当你思考API设计的时候有2个关键的问题:

  • What is the control?

This influences the interface and convenience methods. Is it a button? A slider? Your interface is obvious. Your convenience methods will follow the standard semantics of the control.

这个问题会影响界面和接口方法。它是一个按钮吗?一个滑块?您的接口是否可见。您的方法将遵循的标准语义的control。

  • What is the control like?

This influences the delegate and/or data-source model and notifications. If it’s a new type of control, is it essentially very similar to something else? An outline view is a linear table. A calendar widget is a date-picker. A collection of commands presented with a unified presentation is a menu.

这个问题会影响委托和/或数据源模型和通知。如果它是一个新型的control,它本质上是非常相似的别的东西吗?大纲视图是​​一个线性表。日历小工具是一个日期选择器。用一个统一的命令集合就是一个菜单。

Our core goal is consistency with existing components and models, so that we can turn an unfamiliar control into something the developer already understands. Use standard APIs, models, and patterns wherever possible (and that’s almost always). Familiarity and intuitiveness are just as important at code level as they are for the end user.

我们的核心目标是与现有的组件和模型的保持一致,因此,我们可以把一个陌生的control,变成开发人员已经知道的东西。尽可能的使用标准的API,模型和模式,因为熟悉和直观对用户来说也相当重要。

Let’s look at the four components of the component API mentioned above.

让我们来看下由以上四点构成的API组件。

类接口Class interface

Here’s the interface file for MGTileMenu.

Before we even start talking about the specifics of the interface, we have a couple of over-arching rules:

在我们讨论具体的接口之前,这有一些涵盖范围比较广泛的规则:

Rule 1: 使用方言(Use the local dialect)

One the most common mistakes I see in API design is the use of foreign conventions. APIs belong to a platform and a developer ecosystem. You simply can’t use whatever idioms and architectures you’re used to from a different platform; to do so is to pollute your current codebase and to damage the productivity of your fellow developers.

我所看到最常见的错误是API的设计利用了外来的约定。APIs 属于固定平台和固定的开发者生态系统。你根本无法使用任何习语和你用过的其他平台的架构,这样做会污染您当前的代码库,并​​对其他开发人员的效率造成损害。

Learn your target platform’s conventions before coding. For example, on iOS or OS X, don’t use exceptions for control flow. Name your methods in an appropriate manner (which usually means sufficiently verbose, but should also of course be sufficiently succinct).

在coding之前要了解你目标平台的约定,比如,在iOS 或者 OS X,不使用异常对待control的流程 。以适当的方式命名你的方法(通常指有足够详细,但也应该有足够的简洁)。

Learn what a protocol is, and a delegate, and a category. Use that terminology throughout your code. Learn the relevant naming schemes for constructors and destructors. Obey native memory management rules. The vocabulary and the grammar are indivisible, and you’re either developing for a given platform or you’re not.

了解协议,和委托,类别分别是什么。在你的代码中使用他们。学习相关的构造函数和析构函数的​​命名方案。请遵守内存管理规则。词汇和语法是不可分割的,你要么发展为一个固定的的平台,或者你跨平台。

Rule 2: 设计解耦(Design decoupled)

Any component should be designed such that it’s not coupled to the project you created it for, and if it’s a GUI control or view, it should at least display something by default. Use the existing framework classes as a guide, and maintain loose coupling with delegate protocols, well-designed/named API methods and notifications where appropriate.

任何component的设计应该没有连接到你当前创建的项目,如果他是一个GUI control或者一个视图,它应该默认显示一些东西。使用现有的框架作为一个指南,与委托协议,精心设计的/命名的API方法和通知在适当的地方保持松耦合。

An obvious but very effective way to do this is to create a new project for each component, and develop the component literally in isolation. Force yourself to use your own API. Stay away from the temptation of tying unrelated classes together. Start as you mean to go on.

一个很明显的,但非常有效的方式,是每次为你的component创建一个项目,并逐渐的隔离开发component。强迫自己使用自己的API。远离无关的类。

With that said, let’s talk about the class interface proper. Initialisation methods are one of the most important parts of the interface, because they’re how people get started with your component. Your class will have certain required settings for initial configuration. So, an obvious rule:

接下来,让我们来适当谈谈类的接口。初始化方法的接口中最重要的部分之一,因为他们是人们如何开始使用您的组件。你的类将有一定的初始配置所需的设置。所以,一个明显的规律:

Rule 3: 必须设置初始化参数(Required settings should be initializer parameters)

If something needs to be set, don’t wait for it - require it up-front, immediately, and return nil if you don’t get something acceptable.

如果有什么需要设置的,不要等待 -需要它了就去做,如果你没有得到的东西的立即返回nil。

1- (id)initWithDelegate:(id<MGTileMenuDelegate>)theDelegate; // required parameter; cannot be nil.

Rule 4: 允许访问初始化参数(Allow access to initializer parameters)

This is a corollary to the previous rule: remember not to just swallow those parameters. Give access to them via properties, and note if they might have been massaged in any way (sanitised, or otherwise modified).

这个前一个结果的必然结果: 记住不要仅仅传入参数,应该可以通过属性或者赋值来访问他们,如果他们可以通过任何方式来一场“按摩”(修改,重写等)

1@property (nonatomic, weak, readonly) id<MGTileMenuDelegate> delegate; // must be specified via initializer method.

These previous two examples raise a further general point.

前两个例子阐述了这个观点。

Rule 5: 注释你的header文件 Comment your header files (including defaults)

Realistically, you won’t always provide separate, standalone documentation for a component. If you don’t provide documentation, your .h files (and demo app) are your docs. They should be suitably written, and by ‘suitably’ I mean:

实际上,你不总为component提供单独的文档。如果你不提供文档,你的.h文件(包括demo app)就是你的文档。他们应该适当的描述,我的意思是:

  • Sufficiently detailed, but no more so. Be succinct.
  • For professionals. Assume things that are safe to assume. Don’t waffle.

  • 足以描述,但是不是特别多,要简洁。

  • 一切是提供给专业人士,所以适当的描述别描述无关的事情。

Particularly, you should briefly note default values beside properties or accessors; it’s much easier to scan those in the header file than to try to locate your initialisation code in the implementation.

特别是,你应该简要注释在属性或访问器旁边;头文件扫描比在初始化实例的时候更容易。

123@property (nonatomic) CGGradientRef tileGradient; // gradient to apply to tile backgrounds (default: a lovely blue)@property (nonatomic) NSInteger selectionBorderWidth; // default: 5 pixels@property(nonatomic) CGGradientRef selectionGradient; // default: a subtle white (top) to grey (bottom) gradient

Rule 6: 三行内运行起来 Get up and running in 3 lines

Your class should be designed so that it requires minimal code to integrate (delegate/data-source protocol included, about which more later). Excluding delegate methods, you should aim to make it usable at least for testing purposes with only 3 lines of code.

你的类应该是这样设计的,它需要最少的代码来集成(包括委托/数据源)。你的目标应该是只用3行代码使达到测试目的,当然这一切不包括委托方法。

Those lines are:

这几行应该是:

  • Instantiate it.
  • Basically configure, so it will show and/or do something.
  • Display or otherwise activate it.

  • 实例化

  • 基本配置
  • 显示

That should be it. Anything substantially more onerous is a code smell. Here are the relevant lines from MGTileMenu’s demo app:

就是这样,这里是MGTileMenu中相应的代码:

123456// Instantiate. 初始化tileController = [[MGTileMenuController alloc] initWithDelegate:self];// Configure. 配置tileController.dismissAfterTileActivated = NO; // to make it easier to play with in the demo app.// Display. 显示[tileController displayMenuCenteredOnPoint:loc inView:self.view];

Rule 7: 臃肿的demo意味着不合格的component A fat demo usually means a broken component

Another corollary: the size of your demo harness is a quality metric for your component, where smaller is better. Demo harnesses/code should be as small and thin as possible (making suitable allowances for demos that aim to explore all of a component’s customisation or functionality).

另一个推论:您的demo的大小是衡量你component质量的标准,其值越小越好。Demo/Code 应该尽可能的小而薄的(用于演示,旨在描述所有组件的定制或功能)。

The core required code to turn an empty Xcode app template into a demo of your app should be minimised. It’s not OK to required copy-pasted boilerplate to get your component working, and having an example of it in your demo isn’t an excuse.

核心思想是当你的代码从你的空的xcode项目模板到你的demo中应该保持最小化的修改。这并不是一个好的借口当你需要复制粘贴demo来让你的component运行。

Rule 8: 预测定制化的可能性 Anticipate customisation scenarios

My standard rule for apps is don’t give the user options. Choose sensible defaults to fit the majority, and skip the Preferences window. Good software, after all, is opinionated.

我的标准规则的应用程序是不给用户选择。选择合理的默认值,以适应大多数情况,并跳过“首选项”窗口。我固执己见的认为好的软件应该如此。

The situation is a bit different with components, because the scenarios of use aren’t as clear-cut. You can certainly make a component that only fits one specific situation, but usually we want some flexibility. You never know exactly how another developer is going to use your component, so you have to build in some generality.

目前的情况是应该有一些不同的components,因为在使用目的不明确的情况下。你当然可以使一个components只适合一个特定的情况,但通常我们需要有一定的灵活性。你永远不知道究竟另一名开发人员将如何使用您的components,所以你必须建立在一些一般性。

It’s important to choose your customisation points carefully. It’s particularly important to consider dependencies - not in the compiling/linking sense, but rather the logical relationships between types of customisation. I approach this by trying not to think of customisation at the instance-variable level, but rather at the “aspect” level. What aspects of your component do you want to allow customisation of? Then you work out what specific properties to expose.

一个非常重要的事情就是小心的选择可定制的地方。这是特别重要的考虑依赖关系——而不是在编译/链接意义上,而是逻辑关系类型的定制。我的方法通过努力不去想定制的实例变量层面,而是在“方面”水平。您的组件的哪些方面你想让定制的?然后你找出特定的属性来公开。

It’s easy to cripple a certain type of customisation by not exposing sufficient configuration points. Some examples:

这很容易削弱某种类型的自定义的不足。一些例子:

  • Don’t expose width and height without considering corner radius too.
  • Don’t expose background colour without highlighted background colour.
  • Don’t expose size without spacing.

  • 不公开的宽度和高度也没有考虑圆角半径。

  • 不公开背景颜色没有突出显示的背景颜色。
  • 不公开大小没有间距。

The specifics depend on the component, but just try to consider the relationships between properties, from the point of view of either appearance or functionality. Empathise with the developer. Be flexible, without abandoning the identity of the component.

具体细节取决于component,但只尝试考虑属性之间的关系,要么外观或功能。对于开发者。是需要灵活的,没有放弃身份的component。

123456@property (nonatomic) BOOL dismissAfterTileActivated; // automatically dismiss menu after a tile is activated (YES; default)@property (nonatomic) BOOL rightHanded; // leave gap for right-handed finger (YES; default) or left-handed (NO)@property (nonatomic) NSInteger tileSide; // width and height of each tile, in pixels (default 72 pixels)@property (nonatomic) NSInteger tileGap; // horizontal and vertical gaps between tiles, in pixels (default: 20 pixels)@property (nonatomic)CGFloat cornerRadius; // corner radius for bezel and all tiles, in pixels (default: 12.0 pixels)

Let common sense be your guide. Decide what options will serve 70% or so of the usage situations you can think of, and provide those options. Let your delegate methods and code structure serve the rest.

让常识成为你的向导。这将确定你能想到的70%左右的选项,并提供这些选项。以及您的委托方法和代码结构的其余服务。

Rule 9: 更多的属性,更少的actions More properties, fewer actions

There’s a particular pattern that keeps cropping up in components that I like - some of which are from standard frameworks, some open source from third parties, and some even my own. It’s a ratio of the number of properties (or accessors, or customisation points) on a component, to the number of “do stuff” methods (i.e. all the other stuff, from initializers to state-updating).

有一个特定的模式,不断出现在我喜欢的组件中 - 其中一些是从标准的框架,也有来自第三方的,有的甚至是我自己的一些开放源码的。在一个组件中属性的数量(或访问器,或定制)和“做东西”的方法(即所有其他的东西,从初始化来状态更新)有特定的比例。

It’s pretty much always more properties, and fewer ‘actions’ (again, that’s not actions in the Interface Builder sense). MGTileMenu has an initializer, and four actual for-public-use methods (one of which is a convenience that calls another). In terms of customisation points, it has four times as many. I think that’s a good ratio, and leads to components that are both concise in actual functionality, but also flexible in customisation.

它几乎总是更多的属性,和更少的action”(再一次,这不是Interface Builder中的actions)。MGTileMenu有一个初始化器,和四个实际公共使用方法。在定制中,它达到4倍多。我认为这是一个好的比率,很简洁,但也很灵活去定制的component。

12345- (id)initWithDelegate:(id<MGTileMenuDelegate>)theDelegate; // required parameter; cannot be nil.-(CGPoint)displayMenuPage:(NSInteger)pageNum centeredOnPoint:(CGPoint)centerPt inView:(UIView*)parentView; // zero-based pageNum- (void)dismissMenu;- (void)switchToPage:(NSInteger)pageNum; // zero-based pageNum

Rule 10: 在你的控件中使用控件 Use controls in your controls

A great way to simplify both the API and implementation of your component is to use existing controls in your implementation. A unified presentation doesn’t meant that you can’t build something out of pre-existing components (indeed, that’s one of the basic principles of good software engineering).

一个很重要的方式来简化API和实现你的component是里用已有的controls在你的实现方法。标准的表现并不意味着你不能在已有的components上创造东西。(事实上,这是一个好的软件工程师的基本准则)

Consider how UITableViewCell and UIButton have simple APIs because they use sub-controls such as UIImageViews and UILabels. You can, and should, do that too - and if appropriate, expose the corresponding sub-controls to keep your class interface concise and consistent.

仔细想想UITableViewCell 和 UIButton的简化API的方式,是因为他们使用了sub-controls 例如 UIImageViews和 UILabels。你可以也应该这么做。在适当的时候,使一些相对应的sub-controls暴露在外,这样让你的class接口简洁,一致。

In MGTileMenu, for example, the tiles are regular UIButtons (not even subclasses). This drastically simplified the implementation compared to drawing the tiles within a single custom view, tracking input events, and supporting accessibility.

在MGTileMenu中,例如,贴图是标准的UIButtons(不是子类)。这极大的简化了在单一的自定义视图中绘制块,跟踪输入事件,以及支持辅助功能。

Rule 11: 方便你我 Convenient for you is convenient for me

You’ll naturally add convenience methods during implementation, and the instinct is to keep them private. Instead, consider whether you can expose them for use by those who integrate your component into their own apps.

在实施过程中你会很自然的想到增加一些便利的方法,并且本能的让他私有化。相反的,应该考虑如何公开的你的component 让别人用在他们的项目中。

Whatever made it more convenient for you to add a method or function may apply to those developers too.

无论是什么使它更便于您添加一个方法或函数应该同样适用于其他的开发者。

For example, in MGTileMenu I created these convenience functions:

例如, 在MGTileMenu中 我创建了一些便捷的函数:

123CGRect MGMinimallyOverlapRects(CGRect inner, CGRect outer, CGFloat padding);CGGradientRefMGCreateGradientWithColors(UIColor *topColorRGB, UIColor *bottomColorRGB); // assumes colors in RGB colorspace

The first helps me shift a tile menu so that it’s fully visible within its parent view (which might be handy for another developer, if they’re providing ancillary UI related to the menu), and the second returns a Core Graphics gradient from two UIColors, which I used when setting a default background for the tiles (and another developer may find handy when implementing MGTileMenu’s delegate protocol, to give tiles custom gradients).

这第一个帮助我转换一个tile菜单让他可以完完全全显示在它的父视图中(这可能方便其他开发人员如果他们相关配套的UI菜单),第二个是从2个颜色中返回一个图形渐变,用在了设置默认背景的tile(另一名开发人员可能会发现方便的时候实现MGTileMenu的委托协议,并自定义渐变tile)

Rule 12: 魔法可以,数字就算了 Magic is OK. Numbers aren’t.

Sooner or later, you’ll put magic into your component. Hopefully there’ll be plenty of the Steve Jobs type of intuitive, delightful, empowering magic, but what I’m talking about is things like numbers and other values that have special meaning in your code. A common example is -1, to indicate a unique thing in a set, or a special situation.

迟早,你将把魔法加入到你的component中。他们将会是大量乔布斯风格的直觉,令人愉快的魔法,但是我要说的事情是在你的代码中数值或者其他的值他们都特殊的意义。一个简单的例子是-1, 在集合中是一个特殊的事情,或者一个特殊的情况。

It’s fine. It’s genuinely OK to do that. What’s not OK, though, is needlessly putting mysterious raw values throughout your code, and . If you’re exposing magic, dress them up for consumption. Use #defines or a constant or something. Just make them presentable and understandable.

这个可以,诚实的来说也仅仅是OK,那什么是不OK,把一些不必要的神秘的原始值贯穿于你的代码中,尤其不正常的是把它暴露在API中。如果你想施展一些魔法,把他们包装起来再使用,用#defines 或者一个常量或者其他一些什么东西。让他们更像样,更容易理解。

12// Used for the page-switching tile in methods expecting a tile-number.#define MG_PAGE_SWITCHING_TILE_INDEX -1

委托和数据源 Delegate and data-source protocols

Delegate protocols are fantastic. They’re an easy, familiar and flexible way to embrace the MVC pattern, and they reinforce good habits of loose coupling and judicious API design.

委托协议是非常令人难以置信的功能,非常的简单,用一种常见并且灵活的方式来实现MVC 模式,同时他也是巩固了松散耦合的良好习惯以及明智了api 设计。

Here’s MGTileMenu’s delegate protocol.

这里是[MGTileMenu’s 的委托][MGTileMenu’s delegate protocol]

There are classic delegate and data-source protocols that we can draw on for almost any component. If you’re displaying data, the One True Data-Source Protocol is likely to be something very close to:

1.How many things do I have? 2.What’s the value for property Y of thing X?

它们是经典的委托和数据源让我们利用在几乎所有的component中。如果你想显示数据,一个真正的数据源很可能类似这样: 1.我有多少东西? 2.X的Y属性的值是多少

Similarly, in almost any situation, the One True Delegate Protocol is likely to take the form:

Should this thing do that? This thing is about to do that. This thing just did that. This is also known as the ShouldWill,Did protocol pattern, and it ties neatly in with the Will-Did** notification pattern too, about which more later.

同时,几乎在任何情况下,一个真正的委托需要如下这样的一个表单: 这事应该这样做? 这事要做。这事情是这样。这个也是已知的Should,Will,Did协议模式以及与Will-Did通知模式紧密联系在一起,

Let me mention something you might find controversial: I find it perfectly acceptable to conflate the delegate with the data-source (i.e. combine them into a single protocol). I do it with MGTileMenu and several other components, for example.

让我提出一些事情可能你会提出争议:我觉得把委托和数据源混合在一起是完全可以接受的(就是组合成一个协议)

I fully accept the principle of separating them, and I can think of many cases where you’d want to keep them separate. Apple keeps them separate too, generally. That’s fine.

我完全接受他们分开的原则,我能想到的许多情况下,您想要将它们区分开来。苹果一般也让他们分开。这个无所谓。

In my experience, though, in most cases it’s fine to combine them. Most people handle data-source methods and delegate methods in the same place. I’ve never had a complaint about unifying those protocols, and I can scarcely remember a situation where even existing separate protocols were handled in different places.

在我的经验中,尽管,在大多数情况下可以组合它们。大多数人处理数据源方法和委托方法在相同的地方。我从来没有收到投诉关于统一这些协议,我几乎不记得哪里有分开的protocols在不同的地方处理。

If you care about purity, or have a need to separate delegate from data-source, then obviously you should do so. I just don’t think you need to feel bad if you combine them.

如果你很关心纯粹,或者需要一个独立的于数据源的委托,那么很明显你应该这么做。我只是认为你不需要感到沮丧在你组合他们的时候。

Rule 13: 限制’required‘ 委托 Limit ‘required’ delegate methods

Be very careful when choosing which of your delegate methods are required. Too many required methods tends to indicate:

请小心的选择哪些委托是必须的,大多数的’required‘方法往往:

  • Poor choice of default behaviour.
  • Too much of your own politics are in your code.

  • 默认行为不够不充分

  • 加入了太多你的主观意见

A well-designed component should need very, very few required delegate methods - just the bare minimum to do whatever it does. Choose carefully. Equally, remember that it’s easy to add optional methods later, but it’s hard to turn optional ones into required ones (people will complain, and rightly so).

一个非常好的component 应该需要非常非常少的’required‘委托方法 - 最小化无论做什么。认真公平的去选择,记得以后会容易增加’optional‘的方法,但是以后很难从’optional‘转变成’required‘方法。

MGTileMenu has five required methods, four of which are data-source methods: MGTileMenu 有5个’required‘ 方法,其中4个数据源方法:

1234- (NSInteger)numberOfTilesInMenu:(MGTileMenuController *)tileMenu; // in total (will be shown in groups of up to 5 per page)- (UIImage *)imageForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu; // zero-based tileNumber- (NSString *)labelForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu; // zero-based tileNumber- (NSString*)descriptionForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu; // zero-based tileNumber

The first two follow the One True Data-Source Protocol. The third and fourth do too, but they also expose my politics: I think that software should be accessible, and I’m forcing you to supply a label and hint for each tile for VoiceOver to read. I’m comfortable with it.

前两个遵循一个真正的数据源协议。第三四个也是如此,但是他们也有我个人的意见:我认为软件应该是可访问的,我迫使你提供一个标签,示意用VoiceOver来读每个tile。我感到这样很舒适。

There’s also one delegate method proper:

这里也有一个委托方法:

1- (void)tileMenu:(MGTileMenuController *)tileMenu didActivateTile:(NSInteger)tileNumber; // zero-based tileNumber

That one is required because it’s how you find out that a tile was activated. If you’re not willing to pay attention to that, MGTileMenu will do nothing useful, and you might as well not be using it at all. So, it’s required.

这个一个是’required‘因为他是告诉你如何找到一个激活的‘tile’。如果你将来不注重这个,MGTileMenu不可使用,你可能也不会使用它。所以,这是必须的。

Rule 14: 设计辅助功能特性 Design for accessibility

Following immediately on from the last rule: make things accessible. Don’t tack it on at the end, either: design for accessibility from the start. If you follow the “use controls in your controls” rule, you probably get this almost for free.

遵循最后一条规则:让事情访问。不要在结束的时候思考它,从一开始注意辅助功能设计。如果你遵循“use controls in your controls”的原则,你可能已经完成了这个功能。

Delegate (or rather, data-source) methods, as shown above, are a great place to twist the arm of another developer to make them at least provide something for VoiceOver. And if you can automatically repurpose something visual (like a displayed text label) as a VoiceOver label, so much the better (again, in most cases VoiceOver already handles this for you).

委托(或相反,数据源)方法,如上所示,是一个伟大的地方的手臂扭另一个开发者来让他们至少提供一些VoiceOver。如果你可以自动改变一些视觉(就像一个显示文本标签)作为一个VoiceOver标签,那就更好了(同样,在大多数情况下已经为您处理这个VoiceOver)。

Be socially conscious. Make it hard not to support accessibility. I also wrote an article about supporting VoiceOver in iOS apps, which Apple recommends to companies who contact them about accessibility programming. I recommend it too, but then I wrote it, so you’d expect that.

想要有好的社会意识。就想方设法支持辅助功能。我也写了一篇关于在iOS应用程序中支持VoiceOver,苹果公司建议联系他们了解关于辅助功能的编程。我也推荐它,并且后来我是这么做的的,所以你会希望你这样做。

Rule 15: Use semantic objects for parameters

This doesn’t just apply to protocols, but protocols are where it’s particularly important. Use actual, first-class, semantically-appropriate objects for data, even if it’s more hassle for you to work with in your implementation.

这一条不仅仅适用于协议,虽然协议对于它特别的重要,使用合适一流并且具有合适语义的对象,即使它的出现让你的实现更加的复杂。

If you’re asking for a date, don’t accept numbers - get an actual NSDate object. There are objects or structures for just about everything, and you should use them as intended. Create a class if you need to (you probably won’t need to).

如果你需要一个日期,不接受数字,意思说你要一个实际的NSDate对象,这个对象和结构就是一切,你应该使用他们作为你的预期值。如果需要你可以创建一个类(你也许不需要)

The one standard exception, of course, is indices - there’s no reason for them to be anything but primitives, since NSNumber adds nothing that’s semantically important enough to offset the bundling/unbundling inconvenience.

一个标准的例外,当然,是指数——没有理由为他们增加语义除了原语,因为NSNumber的原有语义足以抵消打包/解包带来的不便。

Rule 16: 增大api的容量如果语义不明确 Enhance the API if semantics don’t fit

I see this all the time. I mentioned earlier how you can think of almost any new, custom control as being substantially like something that already exists (often, it’s like the already-existing thing that you’re using behind the scenes for your implementation).

我总是看到这样的问题,我早些时候提到你如何去思考任何新的自定义的控件是一些已经存在的东西。(通常,它就像你实现了已经存在的东西的幕后的方法)

That’s great, and you’re very clever, but semantics trump similarity. It’s absolutely fine (and wonderful) to layer a new API on top of an existing one, in order to make the semantics fit. For example:

这很好,你很聪明,但是语义要胜于相似。这当然很可以在已有的api上增加一层,为了让语义更加的合适比如:

  • A contact list implemented with a table should have a contacts-related API

一个基于table的联系人列表应该有一个联系人有关的API

  • A month-view calendar implemented with a grid should have a date-related API

一个基于grid的月历应该有一个日期相关的api

And so forth. Don’t force yourself (or other developers) to constantly be mentally converting between an abstract implementation API and the actual semantics of the component - make the API reflect the actual purpose of the component instead.

等等。不要强迫你自己(或其他开发人员)不断的在脑子里转换一个抽象的实现API和组件的实际语义——容易使API反映实际目的与组件相反。

MGTileMenu’s delegate protocol does that by treating the menu not as a collection of UIButtons (the implementation), but rather as a unified menu, with numbered tiles each of which have relevant display properties.

MGTileMenu’s 委托协议实现并没有采用一个UIButton的集合,而是一个统一的菜单,每一个有编号tiles都有相关的协议。

Rule 17: 高亮总是很有趣 Highlighting is interesting

I learned this one by having to go back and add new delegate methods and notifications to APIs I thought were finished. For interactive controls, highlighting is interesting. By ‘interesting’, I mean of potential significance to the surrounding app.

我知道这一点是返回增加一个委托方法并且通知APIs当我完成的时候,对于交互式的控制,高亮是有趣的。重点突出“有趣”,我的意思时候是说app的潜在意义要有趣。

Any control will inform the app (in one sense or another, perhaps just by calling an action method) when it has been fully triggered, but comparatively few will notify when they’ve been visually highlighted (selected, pressed) or unhighlighted without being triggered. It turns out that that’s actually pretty important. The app might want to:

任何控制将通知应用程序(或其他在某种意义上说,也许只是通过调用一个动作方法)时,它已经完全触发,但相对较少的通知时,他们已经被视觉上突出显示(选中,按下),或者未突出显示没有被触发。事实证明,这实际上是非常重要的。该应用程序可能希望:

  • Add, remove or reposition ancillary UI.
  • Update some other part of its display.
  • Offer some contextual help.
  • Some other thing you can’t possibly foresee.

Highlighting is certainly an example of an optional set of delegate methods, but they’re important to have, and almost always trivial to implement.

+添加删除或者重新定位UI。 +更新其中的一部分显示。 +提供一些上下文辅助。 +一些其他不可预见的情况。高亮当然是一个示例的一组可选的委托方法,但它们很重要,而且几乎总是琐碎的实现。

12- (void)tileMenu:(MGTileMenuController *)tileMenu didSelectTile:(NSInteger)tileNumber; // zero-based tileNumber- (void)tileMenu:(MGTileMenuController *)tileMenu didDeselectTile:(NSInteger)tileNumber; // zero-based tileNumber

Rule 18: Optional方法并不是一个承诺 Optional methods aren’t a commitment

Many of us approach optional delegate methods as an either-or situation: if you don’t implement them, you get the default behaviour, and if you do, then you’re totally responsible for what happens. That’s not ideal.

我们中的许多人方法可选的委托方法作为一个二选一的情况:如果你不实现它们,你会得到默认的行为,如果你这么做了,那么你完全要为发生的事情负责。这并不是理想的。

In any implementation which provides an optional delegate method, you should still fall back on the default behaviour even if the method is implemented, but doesn’t return something sensible. It sounds obvious, but it’s amazing how many components will blithely let delegate objects return any kind of craziness without sanity-checking, just because the delegate has somehow promised to behave itself by implementing the method.

在任何实现提供了一个可选的委托方法,您仍然应该依靠缺省行为即使实现了该方法,但不会返回一些明智的东西。这听起来显而易见,但令人惊奇的是,很多组件将无忧无虑地让委托对象返回任何愚蠢类型而且没有做基本检查,仅仅因为委托方法莫名其妙的实现本身方法的行为。

I’m talking particularly about visual customisations, such as background colours or images. Consider very, very carefully whether you shouldn’t intervene in that case, and fall back upon your default appearance. Did they really want to show nothing? Does that even make sense? Will it make the control look broken? If so, step in, and serve up the default just as if the delegate method was never implemented in the first place.

我说的尤其是关于视觉自定义,比如背景颜色或图片。非常、非常小心考虑你是否不应该干预在这种情况下,依靠你的默认外观。他们是否真的想要显示什么?它合理么?它会使控制显示出错?如果是这样,你就应该提供默认数值就比如委托方法没在第一时间实现。

Relatedly, have a documented, standard, unsurprising way to deliberately invoke the default behaviour via returning something like nil from each optional delegate method.

同理,有一个记录在案的标准的,不会令人感到惊讶的方式去调用默认行为从Optional委托返回一些类似nil的东西

MGTileMenu, for example, has a relatively complex hierarchy of ways you can customise tile backgrounds. You can implement any (or all, or none) of three optional delegate methods to provide a background image, gradient or colour for each tile, in that priority order. You can also opt into the default behaviour for any tile at any time, by returning nil or NULL as is appropriate to the type.

MGTileMenu 比如,有一个相对复杂的方法你可以定义tile的背景,你可以实现三个optional委托的任意(当然也可以是全部或者一个也不实现)为每一个tile去提供背景图,渐变或者颜色。在他们的优先级下,你也可以在任何时候为每个tile选择默认行为,通过适当的类型返回nil 或者NULL。

You’ll have to try fairly hard (by returning clearColor, or an empty UIImage object) to really, really make a tile’s background completely transparent.

你可以试试一些相对特殊的例子(返回透明或者一个空的image对象),使得tile的背景完全透明。

Rule 19: 总是提及是谁在讲话 Always say who’s talking

This is a simple rule, and an equally simple mistake to make. In your delegate methods, always pass the sender as a parameter. Always. Even for singletons. Even for things you cannot conceive would ever be used more than once simultaneously. No exceptions.

这是一个相对简单的规则,以及同样简单的错误,在你的委托方法中。你总要传入sender当做一个参数。即使是一个单例。即使你不能相像这个对象会同时超过一次使用。没有例外。

像这样

1- (void)tileMenu:(MGTileMenuController *)tileMenu didActivateTile:(NSInteger)tileNumber; // zero-based tileNumber

不要像这样

12- (void)tileMenuDidActivateTile:(NSInteger)tileNumber; // zero-based tileNumber// Um, WHICH menu?

Rule 20:把有特点的参数放在前面 Put distinguishing params first in query methods

The One True Data-Source Protocol should always have query methods such that the most interesting thing goes first. The specific quality or property you’re requesting a value for. Like this:

一个真正的数据源协议应该总是有这样的查询方法,最容易让人感兴趣的总是是第一个,特定的质量或属性你请求一个值像这样:

1- (UIImage *)imageForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu; // zero-based tileNumber

不要像这样:

1- (UIImage *)tileMenu:(MGTileMenuController *)tileMenu imageForTile:(NSInteger)tileNumber;

The return type should flow naturally into the first part of the method name, without causing surprise. Data-source protocols often have many similarly-named methods, so keep the unique and interesting parts at the very start. Easier to read, and easier to autocomplete.

返回类型的流程应该自然地进入第一部分的方法名称,而不造成任何惊讶。数据源通常有许多名称类似的协议方法,所以保持独特而有趣的部分一开始。更易于阅读,并且容易自动完成。

Some people have pointed out that Apple’s UITableViewDataSource protocol doesn’t do it that way, and instead puts the sender first, for example:

一些人指出,苹果UITableViewDataSource协议并不这样做,相反让发送者首先,例如:

1- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

All I can say is: I’m aware of the difference. I stand by my argument. 我能说的就是,我知道区别,但是我固执己见。

Rule 21: 通知方法不要忘记把sender放在第一个 Put the sender first in notification methods

The One True Delegate Protocol, however, isn’t for queries but rather for notifications. In this situation, you put the sender first (following our “say who’s talking” rule above).

一个真正的委托,不是为查询而是通知。在这种情况下,你把发送放在第一位(遵守我们的习惯上的“谁”在说话的规则)。

1- (void)tileMenu:(MGTileMenuController *)tileMenu willSwitchToPage:(NSInteger)pageNumber; // zero-based pageNumber

This follows how an interaction would go between two people having a conversation. You wouldn’t just jump in and say “She’s going to be late,” because the other person would have to ask “Who?”

这个遵循一个两人交谈的时候,你不会只跳出来说“Ta要迟到了”,因为另一个人会问“谁”?

Instead, you start by saying who’s talking. It’s a convention, and handily distinguishes query (data-source) from notification (delegate) methods.

相反, 你会以在讨论谁作为开始,这是一个惯例可以轻松的区分数据源和委托。

Rule 22: 如果打破公约,不如扔掉它If a convention is broken, throw it away

Having said all of the above, remember that convention and consistency must at some point bow to superior judgement - in this case, yours. If a convention is broken, skip it without worrying. Rename things, if yours is truly better.

上面说了这么多,记住,公约和一致性必须在某种程度上屈服于优秀的判断力——在这种情况下,你的。如果一个公约被打破,不必担心跳过它。重命名的事情,如果你做的更好。

As an example, there’s a pre-existing convention for menu controls whereby you can enable or disable menu-items via the delegate, using a method called validateMenuItem:. For the sake of consistency, I was tempted to use that same method name as part of my delegate protocol. I decided not to, because:

作为一个例子,有一个预先存在的公约菜单控件,您可以启用或禁用菜单项通过委托,使用了一种叫做validateMenuItem:。为了一致性,我想用同样的方法名称作为我的委托协议。我决定不去,因为:

  • It has a horrible, horrible name. “Validate”? That doesn’t say “enable” to me.
  • It’s imperative, where in my case I’m really asking a question.
  • It broke the naming scheme of my other delegate methods.

  • 态有一个可怕的名字,““Validate””?不如叫做“enabled”

  • 这个势在必行,在我的使用中我真的提到了这个问题
  • 它打破了我以往的委托方法的命名规则

Instead, I went for something simpler and more understandable, if unconventionally-named:

相反,我用了一些简单容易理解,非公约的命名:

1- (BOOL)isTileEnabled:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu; // zero-based tileNumber

We can debate the specific wording, but if you encountered that method you’d know what it was for and how to use it right away. To me, that’s better.

我们可以讨论特定的措辞,但是如果你遇到了这个方法,你就可以懂得这是什么和如何立刻使用它。对我来说,这样更好。

通知 Notifications

Notifications are the other half of delegate protocols. My position is that, if you’re using a delegate protocol (you should, if it’s at all appropriate), then it’s incomplete until you add the notifications that naturally follow from it.

通知是委托协议的另一部分,我的立场是。如果你在使用委托(你应该,如果他很恰当)那么它是不完整的,直到你自然的增加了通知。

In MGTileMenu, you can find the notifications in the interface file for MGTileMenuController.

在MGTileMenu中,你可以找到通知在[MGTileMenuController][https://github.com/mattgemmell/MGTileMenu/blob/master/MGTileMenu/MGTileMenuController.h]的接口文件中。

Rule 23: 通知要遵循委托 Notifications follow delegate methods

There’s a natural correspondence between delegate methods (proper; not data-source methods) and notifications. You use them in the same places in your code, and for exactly the same purpose.

有一个自然的通信在委托(当然不是数据源方法)和通知之间。你在同样的为了同样的目的地方使用他们。

If you have a delegate method that tells the delegate about something happening, you should usually provide a notification for that same purpose. Take your notification-like delegate methods, remove the interrogatory ones (the should methods), and you have your list of notifications to implement.

如果你有一个委托方法告诉这个委托一些东西发生,通常情况下你提供一个通知为了相同的目的。让你的通知方法更像一个委托,消除疑问,并且你要罗列你要实现的通知。

The delegate methods’ parameters should match up with the notifications’ userInfo contents, with the obvious exception that you pass the sender as the notification’s object, rather than bundled up in the info dictionary.

委托方法的参数应该和通知的userInfo相对应,很明显的例外,你传入了sender当做通知的对象,而不是绑定在info字典中。

Delegate methods:

代理方法

12- (void)tileMenuWillDisplay:(MGTileMenuController *)tileMenu;- (void)tileMenuDidDisplay:(MGTileMenuController *)tileMenu;

对应的通知

12extern NSString *MGTileMenuWillDisplayNotification; // menu will be shownextern NSString*MGTileMenuDidDisplayNotification; // menu has been shown

Rule 24: 慷慨的对待通知的userInfo Be generous with notifications’ userInfo

Give a notification the information it requires in order to be useful. Remember that notification receivers may (and almost always will) not have anything to do with the delegate or data-source chain for your component.

给一个通知它所需要的信息才会有用。记住,通知接收者可能(而且几乎总是会)没有任何委托或数据源组件链。

Ask yourself what would be useful, and provide that information. At the very least, you must ensure that all arguments provided to the corresponding delegate method are wrapped up in the userInfo object.

问问你自己什么是有用的,并且提供对应的的信息,在最后你应该确保所有的参数封装在userInfo并提供给了相应的委托方法。

Delegate methods: 代理方法:

12- (void)tileMenu:(MGTileMenuController *)tileMenu willSwitchToPage:(NSInteger)pageNumber; // zero-based pageNumber- (void)tileMenu:(MGTileMenuController *)tileMenu didSwitchToPage:(NSInteger)pageNumber; // zero-based pageNumber

And corresponding notifications: 对应的通知

1234// The following notifications have a user info key "MGPageNumber" with an NSNumber (integer, zero-based) value.#define MGPageNumberKey @"MGPageNumber"extern NSString*MGTileMenuWillSwitchToPageNotification; // menu will switch to the given pageextern NSString*MGTileMenuDidSwitchToPageNotification; // menu did switch to the given page

Rule 25: 测试他们 Test the hell out of it

Finally, something we all already know. Software engineering and professionalism 101: make sure it actually works.

最后,我们都已经知道的东西。软件工程和专业101:确保它真的有用。

Whether testing means formal TDD is up to you, but testing itself isn’t optional. Every optional delegate method. Every posted notification. Every point of customisation, in every possible combination. Components provide a thousand opportunities for subtle issues.

测试是否意味着TDD则取决于你,但测试本身不是可选的。每一个可选的委托方法。每个发布通知。每一个点的定制,在每一个可能的组合。组件提供一千的微妙问题的机会。

There will be bugs. Find them and fix them first. If you’re pushed for time, cut a feature and debug instead. Thou shalt suffer no bugs to ship.

将会有bug。第一找到他们并修复它们。如果你正在争取时间,相反减少一个特性和调试。你必不会遭受到bug的威胁。

最后的思考 Final thoughts

I’ve formulated the above rules by learning the hard way, through years of making mistakes while creating components and their APIs. I do try to practise what I preach, though inevitably there will be a hundred examples of where I haven’t.

我定制了以上这些规则在困难的学习的道路上,通过多年的犯过的错误在我建立的组件和APIs的时候,我试着联系遵循我的说法。虽然不可避免有上百个不同的例子出现。

Whilst not all rules apply to all situations, and no rule applies in every case, following as many of these as you can will give you a better chance of producing flexible, well-designed, reusable components for yourself and others to enjoy.

虽然并不是所有的规则适用于所有情况,没有规则适用于每一个案例中,如下,其中许多可以给你一个更好的机会产生灵活的、设计良好的、可重用的组件为自己和他人欣赏。

You may want to grab a quick summary of the rules, as shown below; I have the full-size version hosted on Flickr.

你可能想抓住的快速摘要规则,如下所示,我有全尺寸版本托管在Flickr。

原创粉丝点击