iOS開發規範知識

原文:ios-good-practices

字數略長,將慢慢看,慢慢理解,個人en不行,藉助翻譯器加理解寫的,所以語句有些很晦澀,希望見諒,個人開發時候當參考,不一定是一定要照這個來操作的,人是萬變的,項目也不盡相同的。

Just like software, this document will rot unless we take care of it. We encourage everyone to help us on that – just open an issue or send a pull request!

此文檔就像軟件一樣如果我們不去維護它,就會落後不適用而被無人問津。我們鼓勵每個人都來幫助我們的 — — 只是打開一個問題或發送拉請求 !

Interested in Android? Our Best Practices in Android Development document has got you covered.

如果你想看Android方面的,點上面的link。

Why?

Getting on board with iOS can be intimidating. Neither Swift nor Objective-C are widely used elsewhere, the platform has its own names for almost everything, and it's a bumpy road for your code to actually make it onto a physical device. This living document is here to help you, whether you're taking your first steps in Cocoaland or you're curious about doing things "the right way". Everything below is just suggestions, so if you have a good reason to do something differently, by all means go for it!

搭載 iOS 可以令人生畏。無論是Swift還是Objective-C都被廣泛地應用在別的地方,該平臺具有它自己的名字對於幾乎一切,使您的代碼跑在一個物理設備上是一段崎嶇的道路。當你整踏入cocoaland這塊陸地或者你正好奇如何用更好地方式去做的時候,這份持續更新的文件在這裏會通過各種手段去幫助你。

Getting Started

Xcode

Xcode is the IDE of choice for most iOS developers, and the only one officially supported by Apple. There are some alternatives, of which AppCode is arguably the most famous, but unless you're already a seasoned iOS person, go with Xcode. It's actually quite usable nowadays!

To install, simply download Xcode on the Mac App Store. It comes with the newest SDK and simulators, and t > Downloads.

Xcode是iOS開發者們最好的開發工具,也是蘋果所推薦的。當然也可以選擇appcode這款非常出名的工具來開發,但如果你剛入門的話,建議還是用Xcode吧。你可以從link中輕鬆下載到Xcode,它由最新的SDK和模擬器,當然你還可以在Preferences-》download中下載安裝更多地內容。

Project Setup

A common question when beginning an iOS project is whether to write all views in code or use Interface Builder with Storyboards or XIB files. Both are known to occasionally result in working software. However, there are a few considerations:

一個常見的問題,當開始 iOS 項目時是否要在代碼中編寫所有的視圖或使用Storyboards 或 XIB 界面生成器文件。然而,有幾個注意事項:

Why code?

  • In Xcode 5, universal apps require separate Storyboards for iPhone and iPad. This can result in a lot of needless duplication. Xcode 6 fixes this by introducing Size Classes.
  • In Xcode 5, custom fonts and UI elements cannot be represented visually in Storyboards, but will have a generic appearance instead. Again, this changes in Xcode 6.
  • Storyboards are more prone to version conflicts due to their complex XML structure.
  • 在Xcode5中如果要做適配需要寫iPhone和iPad兩個Storyboards文件視圖,這可能導致很多的無用功。Xcode6引入size  classes技術解決這個問題
  • 在Xcode5中,自定義文字和UI元素不能夠直觀地表示的Storyboards中,但相反將有一個通用的外觀。再次,這將更改 Xcode 6 中。
  • 使用Storyboards由於其複雜的 XML 結構,更易發生版本衝突。

Why Storyboards?

  • For the less technically inclined, Storyboards can be a great way to contribute to the project directly, e.g. by tweaking colors or layout constraints. However, this requires a working project setup and some time to learn the basics.
  • 從技術角度來說,Storyboards可以說是一個對項目做出直接貢獻的偉大方式,如通過調整顏色或佈局約束。然而,這需要一個工作項目的經驗和一些時間來學習基礎知識。

Ignores

A good first step when putting a project under version control is to have a decent .gitignore file. That way, unwanted files (user settings, temporary files, etc.) will never even make it into your repository. Luckily, GitHub has us covered for both Objective-C and Swift.

把項目置於版本控制之下,有一個體面的.gitignore 文件就是很好的第一步。這樣一來,不需要的文件 (用戶設置、 臨時文件等) 會甚至從來沒有將它編入您的存儲庫。幸運的是,GitHub 有我們對覆蓋Objc-C 和Swift。

CocoaPods

If you're planning on including external dependencies (e.g. third-party libraries) in your project, CocoaPods is the way to go. Install it like so:

如果你打算在你的項目中包括外部依賴項 (例如第三方庫),CocoaPods 是要走的路。將它安裝就像這樣:

sudo gem install cocoapods

To get started, move inside your iOS project folder and run

若要開始,移動在你的 iOS 項目文件夾中,並運行

pod init

This creates a Podfile, which will hold all your dependencies in one place. You can then

在這裏創建Podfile文件,這將在同一個地方舉行你的依賴關係。然後你可以

pod install

to install these dependencies and include them as part of a workspace which also holds your own project. Note that from now on, you'll need to open the .xcworkspace file instead of .xcproject, or your code will not compile.

安裝這些依賴項並且將他們包括在一個工作區,其中也包含您自己的項目的一部分。請注意從現在開始,你需要打開.xcworkspace 文件而不是.xcproject,不然您的代碼將無法編譯。

Project Structure

To keep all those hundreds of source files ending up in the same directory, it's a good idea to set up some folder structure depending on your architecture. For instance, you can use the following:

這些數百個源文件相同的目錄中,需要一個好的方式來設置一些文件夾結構,具體取決於您的體系結構。例如,您可以使用以下操作:

├─ Models
├─ Views
├─ Controllers
├─ Stores
├─ Helpers

First, create them as groups (little yellow "folders") within the group with your project's name in Xcode's Project Navigator. Then, for each of the groups, link them to an actual directory in your project path by opening their File Inspector on the right, hitting the little gray folder icon, and creating a new subfolder with the name of the group in your project directory.

首先,創建它們與您的項目名稱在 Xcode 的項目導航器中的組內作爲羣體 (小黃色"文件夾")。然後,對於每個組中,將它們鏈接到您的項目路徑中實際目錄打開其文件檢查器右邊、 打小的灰色文件夾圖標,和創建新的子文件夾與您的項目目錄中的組的名稱。

Localization

Keep all user strings in localization files right from the beginning. This is good not only for translations, but also for finding user-facing text quickly. You can add a launch argument to your build scheme to launch the app in a certain language, e.g.

-AppleLanguages (Finnish)

For more complex translations such as plural forms that depending on a number of items (e.g. "1 person" vs. "3 people"), you should use the .stringsdict format instead of a regular localizable.strings file. As soon as you've wrapped your head around the crazy syntax, you have a powerful tool that knows how to make plurals for "one", some", "few" and "many" items, as needed e.g. in Russian or Arabic.

Find more information about localization in these presentation slides from the February 2012 HelsinkiOS meetup. Most of the talk is still relevant in October 2014.

Constants

Keep app-wide constants in a Constants.h file that is included in the prefix header.

Instead of preprocessor macro definitions (via #define), use actual constants:

static CGFloat const XYZBrandingFontSizeSmall = 12.0f;
static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo";

Actual constants have more explicit scope (they’re not available in all imported/included files until undefined), cannot be redefined or undefined in later parts of the code, and are available in the debugger.

Branching Model

Especially when distributing an app to the public (e.g. through the App Store), it's a good idea to isolate releases to their own branch with proper tags. Also, feature work that involves a lot of commits should be done on its own branch. git-flow is a tool that helps you follow these conventions. It is simply a convenience wrapper around Git's branching and tagging commands, but can help maintain a proper branching structure especially for teams. Do all development on feature branches (or on develop for smaller work), tag releases with the app version, and commit to master only via

git flow release finish <version>

Common Libraries

AFNetworking

A perceived 99.95 percent of iOS developers use this network library. Sure, iOS 7's NSURLSession brought some nice improvements to the rather dated native networking APIs, but AFNetworking is still unbeaten when it comes to actually managing a queue of requests, which is pretty much a requirement in any modern app.

DateTools

As a general rule, don't write your date calculations yourself. (Here's why.) Luckily, in DateTools you get an MIT-licensed, thoroughly tested library that covers pretty much all your calendary needs.

Auto Layout Libraries

If you prefer to write your views in code, chances are you've met either of Apple's awkward syntaxes – the regular 'NSLayoutConstraint' factory or the so-called Visual Format Language. The former is extremely verbose and the latter based on strings, which effectively prevents compile-time checking.

Masonry remedies this by introducing its own DSL to make, update and replace constraints. A similar approach for Swift is taken by Cartography, which builds on the language's powerful operator overloading features. For the more conservative, FLKAutoLayout offers a clean, but rather non-magical wrapper around the native APIs.

Architecture

  • Model-View-Controller-Store (MVCS)
    • This is the default Apple architecture (MVC), extended by a Store layer that vends Model instances and handles the networking, caching etc.
    • Every Store exposes to the view controllers either RACSignals or void-returning methods with custom completion blocks
  • Model-View-ViewModel (MVVM)
    • Motivated by "massive view controllers": MVVM considers UIViewController subclasses part of the View and keeps them slim by maintaining all state in the ViewModel
    • Quite new concept for Cocoa developers, but gaining traction
  • View-Interactor-Presenter-Entity-Routing (VIPER)
    • Rather exotic architecture that might be worth looking into in larger projects, where even MVVM feels too cluttered and testability is a major concern

“Event” Patterns

These are the idiomatic ways for components to notify others about things:

  • Delegation: (one-to-one) Apple uses this a lot (some would say, too much). Use when you want to communicate stuff back e.g. from a modal view.
  • Callback blocks: (one-to-one) Allow for a more loose coupling, while keeping related code sections close to each other. Also scales better than delegation when there are many senders.
  • Notification Center: (one-to-many) Possibly the most common way for objects to emit “events” to multiple observers. Very loose coupling — notifications can even be observed globally without reference to the dispatching object.
  • Key-Value Observing (KVO): (one-to-many) Does not require the observed object to explicitly “emit events” as long as it is Key-Value Coding (KVC) compliant for the observed keys (properties). Usually not recommended due to its implicit nature and the cumbersome standard library API.
  • Signals: (one-to-many) The centerpiece of ReactiveCocoa, they allow chaining and combining to your heart's content, thereby offering a way out of callback hell.

Models

Keep your models immutable, and use them to translate the remote API's semantics and types to your app. Github'sMantle is a good choice.

Controllers

Use dependency injection instead of keeping all state around in singletons. The latter is okay only if that state really is global.

+ [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo];

Networking

Traditional way: Use custom callback blocks

// GigStore.h

typedef void (^FetchGigsBlock)(NSArray *gigs, NSError *error);

- (void)fetchGigsForArtist:(Artist *)artist completion:(FetchGigsBlock)completion


// GigsViewController.m

[[GigStore sharedStore] fetchGigsForArtist:artist completion:^(NSArray *gigs, NSError *error) {
    if (!error) {
        // Do something with gigs
    }
    else {
        // :(
    }
];

This works, but can quickly lead to callback hell if you need to chain multiple requests. In that case, have a look atReactiveCocoa (RAC). It's a versatile and multi-purpose library that can change the way people write entire apps, but you can also use it sparingly where it fits the task.

There are good introductions to the concept of RAC (and FRP in general) on Teehan+Lax and NSHipster.

Reactive way: Use RAC signals

// GigStore.h

- (RACSignal *)gigsForArtist:(Artist *)artist;


// GigsViewController.m

[[GigStore sharedStore] gigsForArtist:artist]
    subscribeNext:^(NSArray *gigs) {
        // Do something with gigs
    } error:^(NSError *error) {
        // :(
    }
];

This allows us to transform or filter gigs before showing them, by combining the gig signal with other signals.

Assets

Asset catalogs are the best way to manage all your project's visual assets. They can hold both universal and device-specific (iPhone 4-inch, iPhone Retina, iPad, etc.) assets and will automatically serve the correct ones for a given name. Teaching your designer(s) how to add and commit things there (Xcode has its own built-in Git client) can save a lot of time that would otherwise be spent copying stuff from emails or other channels to the codebase. It also allows them to instantly try out their changes and iterate if needed.

Using Bitmap Images

Asset catalogs expose only the names of image sets, abstracting away the actual file names within the set. This nicely prevents asset name conflicts, as files such as [email protected] are now namespaced inside their image sets. However, some discipline when naming assets can make life easier:

IconCheckmarkHighlighted.png // Universal, non-Retina
[email protected] // Universal, Retina
IconCheckmarkHighlighted~iphone.png // iPhone, non-Retina
IconCheckmarkHighlighted@2x~iphone.png // iPhone, Retina
IconCheckmarkHighlighted-568h@2x~iphone.png // iPhone, Retina, 4-inch
IconCheckmarkHighlighted~ipad.png // iPad, non-Retina
IconCheckmarkHighlighted@2x~ipad.png // iPad, Retina

The modifiers -568h@2x~iphone and ~ipad are not required per se, but having them in the file name when dragging the file to an image set will automatically place them in the right "slot", thereby preventing assignment mistakes that can be hard to hunt down.

Using Vector Images

You can include the original vector graphics (PDFs) produced by designers into the asset catalogs, and have Xcode automatically generate the bitmaps from that. This reduces the complexity of your project (the number of files to manage.)

Coding Style

Naming

Apple pays great attention to keep naming consistent, if sometimes a bit verbose, throughout their APIs. When developing for Cocoa, you make it much easier for new people to join the project if you follow Apple's naming conventions.

Here are some basic takeaways you can start using right away:

A method beginning with a verb indicates that it performs some side effects, but won't return anything: - (void)loadView; - (void)startAnimating;

Any method starting with a noun, however, returns that object and should do so without side effects: - (UINavigationItem *)navigationItem; + (UILabel *)labelWithText:(NSString *)text;

It pays off to keep these two as separated as possible, i.e. not perform side effects when you transform data, and vice versa. That will keep your side effects contained to smaller sections of the code, which makes it more understandable and facilitates debugging.

Structure

Pragma marks are a great way to group your methods, especially in view controllers. Here is a common structure that works with almost any view controller:

#import "SomeModel.h"
#import "SomeView.h"
#import "SomeController.h"
#import "SomeStore.h"
#import "SomeHelper.h"
#import <SomeExternalLibrary/SomeExternalLibraryHeader.h>

static NSString * const XYZFooStringConstant = @"FoobarConstant";
static CGFloat const XYZFooFloatConstant = 1234.5;

@interface XYZFooViewController () <XYZBarDelegate>

@property (nonatomic, copy)

@end

@implementation XYZFooViewController

#pragma mark - Lifecycle

- (instancetype)initWithFoo:(Foo *)foo;
- (void)dealloc;

#pragma mark - View Lifecycle

- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;

#pragma mark - Auto Layout

- (void)makeViewConstraints;

#pragma mark - Public Interface

- (void)startFooing;
- (void)stopFooing;

#pragma mark - User Interaction

- (void)foobarButtonTapped;

#pragma mark - XYZFoobarDelegate

- (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo;

#pragma mark - Internal Helpers

- (NSString *)displayNameForFoo:(Foo *)foo;

@end

The most important point is to keep these consistent across your project's classes.

External Style Guides

Futurice does not have company-level guidelines for coding style. It can however be useful to peruse the Objective-C style guides of other development shops, even if some bits can be quite company-specific or opinionated:

Diagnostics

Compiler warnings

It is recommended that you enable as many compiler warnings as possible, and treat warnings as errors. This recommendation is justified in these presentation slides. The slides also contain information on how to suppress certain warnings in specific files, or in specific sections of code.

In short, add at least these values to the “Other Warning Flags” build setting:

  • -Wall (Enables lots of additional warnings)
  • -Wextra (Enables more additional warnings)

Also enable the “Treat warnings as errors” build setting.

Clang Static Analyzer

The Clang compiler (which Xcode uses) has a static analyzer that performs control and data flow analysis on your code and checks for lots of errors that the compiler cannot.

You can manually run the analyzer from the Product → Analyze menu item in Xcode.

The analyzer can work in either “shallow” or “deep” mode. The latter is much slower but may find more issues due to cross-function control and data flow analysis.

Recommendations:

  • Enable all of the checks in the analyzer (by enabling all of the options in the “Static Analyzer” build setting sections)
  • Enable the “Analyze during ‘Build’” build setting for your release build configuration to have the analyzer run automatically during release builds. (Seriously, do this — you’re not going to remember to run it manually.)
  • Set the “Mode of Analysis for ‘Analyze’” build setting to Shallow (faster)
  • Set the “Mode of Analysis for ‘Build’” build setting to Deep

Faux Pas

Created by our very own Ali Rantakari, Faux Pas is a fabulous static error detection tool. It analyzes your codebase and finds issues you had no idea even existed. Be sure to run this before shipping any iOS (or Mac) app!

(Note: all Futurice employees get a free license to this — just ask Ali.)

Reveal or Spark Inspector

These powerful visual inspectors will save you hours of time when debugging your views, especially if you're using Auto Layout (and you should). Xcode 6 will include something very similar for free, though, so maybe just hold off on that purchase until launch day.

Analytics

Including some analytics framework in your app is strongly recommended, as it allows you to gain insights on how people actually use it. Does feature X add value? Is button Y too hard to find? To answer these, you can send events, timings and other measurable information to a service that aggregates and visualizes them – for instance, Google Tag Manager. The latter is more versatile than Google Analytics in that it inserts a data layer between app and Analytics, so that the data logic can be modified through a web service without having to update the app.

A good practice is to create a slim helper class, e.g. XYZAnalyticsHelper, that handles the translation from app-internal models and data formats (XYZModel, NSTimeInterval, …) to the mostly string-based data layer:

- (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode
{
    NSString *editModeString = [self nameForEditMode:editMode];

    [self pushToDataLayer:@{
        @"event": "addItem",
        @"itemIdentifier": item.identifier,
        @"editMode": editModeString
    }];
}

This has the additional advantage of allowing you to swap out the entire Analytics framework behind the scenes if needed, without the rest of the app noticing.

Crash Logs

First you should make your app send crash logs onto a server somewhere so that you can access them. You can implement this manually (using PLCrashReporter and your own backend) but it’s recommended that you use an existing service instead — for example one of the following:

Once you have this set up, ensure that you save the Xcode archive (.xcarchive) of every build you release. The archive contains the built app binary and the debug symbols (dSYM) which you will need to symbolicate crash reports from that particular version of your app.

Building

Build Configurations

Even simple apps can be built in different ways. The most basic separation that Xcode gives you is that between debugand release builds. For the latter, there is a lot more optimization going on at compile time, at the expense of debugging possibilities. Apple suggests that you use the debug build configuration for development, and create your App Store packages using the release build configuration. This is codified in the default scheme (the dropdown next to the Play and Stop buttons in Xcode), which commands that debug be used for Run and release for Archive.

However, this is a bit too simple for real-world applications. You might – no, should! – have different environments for testing, staging and other activities related to your service. Each might have their own base URL, log levels, bundle identifier (so you can install them side-by-side), provisinging profile and so on. Therefore a simple debug/release distinction won't cut it. You can add more build configurations on the "Info" tab of your project settings in Xcode.

xcconfig files for build settings

Typically build settings are specified in the Xcode GUI, but you can also use configuration settings files (“.xcconfigfiles”) for them. The benefits of using these are:

  • You can add comments to explain things
  • You can #include other build settings files, which helps you avoid repeating yourself:
    • If you have some settings that apply to all build configurations, add a Common.xcconfig and #include it in all the other files
    • If you e.g. want to have a “Debug” build configuration that enables compiler optimizations, you can just#include "MyApp_Debug.xcconfig" and override one of the settings

Find more information about this topic in these presentation slides.

Targets

A target resides conceptually below the project level, i.e. a project can have several targets that may override its project settings. Roughly, each target corresponds to "an app" within the context of your codebase. For instance, you could have country-specific apps (built from the same codebase) for different countries' App Stores. Each of these will need development/staging/release builds, so it's better to handle those through build configurations, not targets. It's not uncommon at all for an app to only have a single target.

Schemes

Schemes tell Xcode what should happen when you hit the Run, Test, Profile, Analyze or Archive action. Basically, they map each of these actions to a target and a build configuration. You can also pass launch arguments, such as the language the app should run in (handy for testing your localizations!) or set some diagnostic flags for debugging.

A suggested naming convention for schemes is MyApp (<Language>) [Environment]:

MyApp (English) [Development]
MyApp (German) [Development]
MyApp [Testing]
MyApp [Staging]
MyApp [App Store]

For most environments the language is not needed, as the app will probably be installed through other means than Xcode, e.g. TestFlight, and the launch argument thus be ignored anyway. In that case, the device language should be set manually to test localization.

Deployment

Deploying software for iOS is a pain. That being said, there are some things to know that will help you tremendously with it.

Whenever you want to run software on an actual device (as opposed to the simulator), you will need to sign your build with a certificate issued by Apple. Each certificate is linked to a private/public keypair, the private half of which resides in your Mac's Keychain. There are two types of certificates:

  • Development certificate: Every developer on a team has their own, and it is generated upon request. Xcode might do this for you, but it's better not to press the magic "Fix issue" button and understand what is actually going on. This certificate is needed to deploy development builds to devices.
  • Distribution certificate: There can be several, but it's best to keep it to one per organization, and share its associated key through some internal channel. This certificate is needed to ship to the App Store, or your organization's internal "enterprise app store".

Besides certificates, there are also provisioning profiles, which are basically the missing link between devices and certificates. Again, there are two types to distinguish between development and distribution purposes:

  • Development provisioning profile: It contains a list of all devices that the device is authorized to be built on. It is also linked to one or more development certificates, one for each developer that is allowed to use the profile. The profile can be tied to a specific app, but for most development purposes it's perfectly fine to use the wildcard profile, whose App ID ends in an asterisk (*).

  • Distribution provisioning profile: There are three different ways of distribution, each for a different use case. Each distribution profile is linked to a distribution certificate, and will be invalid when the certificate expires.

    • Ad-Hoc: Just like development profiles, it contains a whitelist of devices the app can be installed to. This type of profile is used for beta testing (e.g. TestFlight). Note that due to Apple's acquisition of TestFlight, this is likely to change in late 2014.
    • App Store: This profile has no list of allowed devices, as anyone can install it through Apple's official distribution channel. This profile is required for all App Store releases.
    • Enterprise: Just like App Store, there is no device whitelist, and the app can be installed by anyone with access to the enterprise's internal "app store", which can be just a website with links. This profile is available only on Enterprise accounts.

To sync all certificates and profiles to your machine, go to Accounts in Xcode's Preferences, add your Apple ID if needed, and double-click your team name. There is a refresh button at the bottom, but sometimes you just need to restart Xcode to make everything show up.

More Ideas

  • https://github.com/vsouza/awesome-ios
  • Update for Xcode 6
    • No automatic precompiled header
  • Pod usage: pod install vs pod update
  • iTunes Connect etc.
  • 3x assets, iPhone 6 screen sizes explained
  • Add @interface and constants to VC Structure
  • Add list of suggested compiler warnings
  • Ask IT about automated Jenkins build machine
  • Add section on Testing
  • Add section on Debugging, e.g. exception breakpoints
  • Add "proven don'ts"

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章