├── README.md └── TRANSLATION-CN.md /README.md: -------------------------------------------------------------------------------- 1 | iOS Good Practices 2 | ================== 3 | 4 | _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!_ 5 | 6 | Interested in other mobile platforms? Our [Best Practices in Android Development][android-best-practices] and [Windows App Development Best Practices][windows-app-development-best-practices] documents have got you covered. 7 | 8 | [android-best-practices]: https://github.com/futurice/android-best-practices 9 | [windows-app-development-best-practices]: https://github.com/futurice/windows-app-development-best-practices 10 | 11 | ## Why? 12 | 13 | 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! 14 | 15 | ## Getting Started 16 | 17 | ### Xcode 18 | 19 | [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][appcode] is arguably the most famous, but unless you're already a seasoned iOS person, go with Xcode. Despite its shortcomings, it's actually quite usable nowadays! 20 | 21 | To install, simply download [Xcode on the Mac App Store][xcode-app-store]. It comes with the newest SDK and simulators, and you can install more stuff under _Preferences > Downloads_. 22 | 23 | [xcode]: https://developer.apple.com/xcode/ 24 | [appcode]: https://www.jetbrains.com/objc/ 25 | [xcode-app-store]: https://itunes.apple.com/us/app/xcode/id497799835 26 | 27 | ### Project Setup 28 | 29 | 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: 30 | 31 | #### Why code? 32 | * Storyboards are more prone to version conflicts due to their complex XML structure. This makes merging much harder than with code. 33 | * It's easier to structure and reuse views in code, thereby keeping your codebase [DRY][dry]. 34 | * All information is in one place. In Interface Builder you have to click through all the inspectors to find what you're looking for. 35 | 36 | [dry]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself 37 | 38 | #### Why Storyboards? 39 | * 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. 40 | * Iteration is often faster since you can preview certain changes without building the project. 41 | * In Xcode 6, custom fonts and UI elements are finally represented visually in Storyboards, giving you a much better idea of the final appearance while designing. 42 | * Starting with iOS 8, [Size Classes][size-classes] allow you to design for different device types and screens without duplication. 43 | 44 | [size-classes]: http://blog.futurice.com/adaptive-view-ios8 45 | 46 | ### Ignores 47 | 48 | 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][objc-gitignore] and [Swift][swift-gitignore]. 49 | 50 | [objc-gitignore]: https://github.com/github/gitignore/blob/master/Objective-C.gitignore 51 | [swift-gitignore]: https://github.com/github/gitignore/blob/master/Swift.gitignore 52 | 53 | ### CocoaPods 54 | 55 | If you're planning on including external dependencies (e.g. third-party libraries) in your project, [CocoaPods][cocoapods] offers easy and fast integration. Install it like so: 56 | 57 | sudo gem install cocoapods 58 | 59 | To get started, move inside your iOS project folder and run 60 | 61 | pod init 62 | 63 | This creates a Podfile, which will hold all your dependencies in one place. After adding your dependencies to the Podfile, you run 64 | 65 | pod install 66 | 67 | to install the libraries and include them as part of a workspace which also holds your own project. It is generally [recommended to commit the installed dependencies to your own repo][committing-pods], instead of relying on having each developer running `pod install` after a fresh checkout. 68 | 69 | Note that from now on, you'll need to open the `.xcworkspace` file instead of `.xcproject`, or your code will not compile. The command 70 | 71 | pod update 72 | 73 | will update all pods to the newest versions permitted by the Podfile. You can use a wealth of [operators][cocoapods-pod-syntax] to specify your exact version requirements. 74 | 75 | [cocoapods]: http://www.cocoapods.org 76 | [cocoapods-pod-syntax]: http://guides.cocoapods.org/syntax/podfile.html#pod 77 | [committing-pods]: https://www.dzombak.com/blog/2014/03/including-pods-in-source-control.html 78 | 79 | ### Project Structure 80 | 81 | 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: 82 | 83 | ├─ Models 84 | ├─ Views 85 | ├─ Controllers 86 | ├─ Stores 87 | ├─ Helpers 88 | 89 | 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. 90 | 91 | #### Localization 92 | 93 | 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. 94 | 95 | -AppleLanguages (Finnish) 96 | 97 | 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][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][language-plural-rules]. 98 | 99 | Find more information about localization in [these presentation slides][l10n-slides] from the February 2012 HelsinkiOS meetup. Most of the talk is still relevant in October 2014. 100 | 101 | [stringsdict-format]: https://developer.apple.com/library/prerelease/ios/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html 102 | [language-plural-rules]: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 103 | [l10n-slides]: https://speakerdeck.com/hasseg/localization-practicum 104 | 105 | #### Constants 106 | 107 | Keep app-wide constants in a `Constants.h` file that is included in the prefix header. 108 | 109 | Instead of preprocessor macro definitions (via `#define`), use actual constants: 110 | 111 | static CGFloat const XYZBrandingFontSizeSmall = 12.0f; 112 | static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo"; 113 | 114 | Actual constants are type-safe, 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. 115 | 116 | 117 | ### Branching Model 118 | 119 | 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`][gitflow-github] 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 120 | 121 | git flow release finish 122 | 123 | [gitflow-github]: https://github.com/nvie/gitflow 124 | 125 | ## Common Libraries 126 | 127 | Generally speaking, make it a conscious decision to add an external dependency to your project. Sure, this one neat library solves your problem now, but maybe later gets stuck in maintenance limbo, with the next OS version that breaks everything being just around the corner. Another scenario is that a feature only achievable with external libraries suddenly becomes part of the official APIs. In a well-designed codebase, switching out the implementation is a small effort that pays off quickly. Always consider solving the problem using Apple's extensive (and mostly excellent) frameworks first! 128 | 129 | Therefore this section has been deliberately kept rather short. The libraries featured here tend to reduce boilerplate code (e.g. Auto Layout) or solve complex problems that require extensive testing, such as date calculations. As you become more proficient with iOS, be sure to dive into the source here and there, and acquaint yourself with their underlying Apple frameworks. You'll find that those alone can do a lot of the heavy lifting. 130 | 131 | ### AFNetworking 132 | 133 | A perceived 99.95 percent of iOS developers use this network library. While `NSURLSession` is surprisingly powerful by itself, `AFNetworking` remains unbeaten when it comes to actually managing a queue of requests, which is pretty much a requirement in any modern app. 134 | 135 | ### DateTools 136 | As a general rule, [don't write your date calculations yourself][timezones-youtube]. Luckily, in DateTools you get an MIT-licensed, thoroughly tested library that covers pretty much all your calendary needs. 137 | 138 | [timezones-youtube]: https://www.youtube.com/watch?v=-5wpm-gesOY 139 | 140 | ### Auto Layout Libraries 141 | 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][visual-format-language]. The former is extremely verbose and the latter based on strings, which effectively prevents compile-time checking. 142 | 143 | [Masonry][masonry-github] remedies this by introducing its own DSL to make, update and replace constraints. A similar approach for Swift is taken by [Cartography][cartography-github], which builds on the language's powerful operator overloading features. For the more conservative, [FLKAutoLayout][flkautolayout-github] offers a clean, but rather non-magical wrapper around the native APIs. 144 | 145 | [visual-format-language]: https://developer.apple.com/library/ios/documentation/userexperience/conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html#//apple_ref/doc/uid/TP40010853-CH3-SW1 146 | [masonry-github]: https://www.github.com/Masonry/Masonry 147 | [cartography-github]: https://github.com/robb/Cartography 148 | [flkautolayout-github]: https://github.com/floriankugler/FLKAutoLayout 149 | 150 | ## Architecture 151 | 152 | * [Model-View-Controller-Store (MVCS)][mvcs] 153 | * This is the default Apple architecture (MVC), extended by a Store layer that vends Model instances and handles the networking, caching etc. 154 | * Every Store exposes to the view controllers either `RACSignal`s or `void`-returning methods with custom completion blocks 155 | * [Model-View-ViewModel (MVVM)][mvvm] 156 | * Motivated by "massive view controllers": MVVM considers `UIViewController` subclasses part of the View and keeps them slim by maintaining all state in the ViewModel 157 | * Quite new concept for Cocoa developers, but [gaining][cocoasamurai-rac] [traction][raywenderlich-mvvm] 158 | * [View-Interactor-Presenter-Entity-Routing (VIPER)][viper] 159 | * Rather exotic architecture that might be worth looking into in larger projects, where even MVVM feels too cluttered and testability is a major concern 160 | 161 | [mvcs]: http://programmers.stackexchange.com/questions/184396/mvcs-model-view-controller-store 162 | [mvvm]: http://www.objc.io/issue-13/mvvm.html 163 | [cocoasamurai-rac]: http://cocoasamurai.blogspot.de/2013/03/basic-mvvm-with-reactivecocoa.html 164 | [raywenderlich-mvvm]: http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1 165 | [viper]: http://www.objc.io/issue-13/viper.html 166 | 167 | ### “Event” Patterns 168 | 169 | These are the idiomatic ways for components to notify others about things: 170 | 171 | * __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. 172 | * __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. 173 | * __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. 174 | * __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. 175 | * __Signals:__ _(one-to-many)_ The centerpiece of [ReactiveCocoa][reactivecocoa-github], they allow chaining and combining to your heart's content, thereby offering a way out of [callback hell][elm-escape-from-callback-hell]. 176 | 177 | [elm-escape-from-callback-hell]: http://elm-lang.org/learn/Escape-from-Callback-Hell.elm 178 | 179 | ### Models 180 | 181 | Keep your models immutable, and use them to translate the remote API's semantics and types to your app. Github's [Mantle](https://github.com/Mantle/Mantle) is a good choice. 182 | 183 | ### Views 184 | 185 | When laying out your views using Auto Layout, be sure to add the following to your class: 186 | 187 | + (BOOL)requiresConstraintBasedLayout 188 | { 189 | return YES; 190 | } 191 | 192 | Otherwise you may encounter strange bugs when the system doesn't call `-updateConstraints` as you would expect it to. 193 | 194 | ### Controllers 195 | 196 | Use dependency injection, i.e. pass any required objects in as parameters, instead of keeping all state around in singletons. The latter is okay only if the state _really_ is global. 197 | 198 | ```objective-c 199 | + [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo]; 200 | ``` 201 | 202 | ## Networking 203 | 204 | ### Traditional way: Use custom callback blocks 205 | 206 | ```objective-c 207 | // GigStore.h 208 | 209 | typedef void (^FetchGigsBlock)(NSArray *gigs, NSError *error); 210 | 211 | - (void)fetchGigsForArtist:(Artist *)artist completion:(FetchGigsBlock)completion 212 | 213 | 214 | // GigsViewController.m 215 | 216 | [[GigStore sharedStore] fetchGigsForArtist:artist completion:^(NSArray *gigs, NSError *error) { 217 | if (!error) { 218 | // Do something with gigs 219 | } 220 | else { 221 | // :( 222 | } 223 | ]; 224 | ``` 225 | 226 | This works, but can quickly lead to callback hell if you need to chain multiple requests. 227 | 228 | ### Reactive way: Use RAC signals 229 | 230 | If you find yourself in callback hell, have a look at [ReactiveCocoa (RAC)][reactivecocoa-github]. It's a versatile and multi-purpose library that can change the way people write [entire apps][groceryList-github], but you can also use it sparingly where it fits the task. 231 | 232 | There are good introductions to the concept of RAC (and FRP in general) on [Teehan+Lax][teehan-lax-rac] and [NSHipster][nshipster-rac]. 233 | 234 | [grocerylist-github]: https://github.com/jspahrsummers/GroceryList 235 | [teehan-lax-rac]: http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/ 236 | [nshipster-rac]: http://nshipster.com/reactivecocoa/ 237 | 238 | ```objective-c 239 | // GigStore.h 240 | 241 | - (RACSignal *)gigsForArtist:(Artist *)artist; 242 | 243 | 244 | // GigsViewController.m 245 | 246 | [[GigStore sharedStore] gigsForArtist:artist] 247 | subscribeNext:^(NSArray *gigs) { 248 | // Do something with gigs 249 | } error:^(NSError *error) { 250 | // :( 251 | } 252 | ]; 253 | ``` 254 | 255 | This allows us to transform or filter gigs before showing them, by combining the gig signal with other signals. 256 | 257 | ## Assets 258 | 259 | [Asset catalogs][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. 260 | 261 | [asset-catalogs]: https://developer.apple.com/library/ios/recipes/xcode_help-image_catalog-1.0/Recipe.html 262 | 263 | ### Using Bitmap Images 264 | 265 | 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 `button_large@2x.png` are now namespaced inside their image sets. However, some discipline when naming assets can make life easier: 266 | 267 | ```objective-c 268 | IconCheckmarkHighlighted.png // Universal, non-Retina 269 | IconCheckmarkHighlighted@2x.png // Universal, Retina 270 | IconCheckmarkHighlighted~iphone.png // iPhone, non-Retina 271 | IconCheckmarkHighlighted@2x~iphone.png // iPhone, Retina 272 | IconCheckmarkHighlighted-568h@2x~iphone.png // iPhone, Retina, 4-inch 273 | IconCheckmarkHighlighted~ipad.png // iPad, non-Retina 274 | IconCheckmarkHighlighted@2x~ipad.png // iPad, Retina 275 | ``` 276 | 277 | 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. 278 | 279 | ### Using Vector Images 280 | 281 | You can include the original [vector graphics (PDFs)][vector-assets] 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.) 282 | 283 | [vector-assets]: http://martiancraft.com/blog/2014/09/vector-images-xcode6/ 284 | 285 | ## Coding Style 286 | 287 | ### Naming 288 | 289 | Apple pays great attention to keeping 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][cocoa-coding-guidelines]. 290 | 291 | [cocoa-coding-guidelines]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html 292 | 293 | Here are some basic takeaways you can start using right away: 294 | 295 | A method beginning with a _verb_ indicates that it performs some side effects, but won't return anything: 296 | `- (void)loadView;` 297 | `- (void)startAnimating;` 298 | 299 | Any method starting with a _noun_, however, returns that object and should do so without side effects: 300 | `- (UINavigationItem *)navigationItem;` 301 | `+ (UILabel *)labelWithText:(NSString *)text;` 302 | 303 | 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. 304 | 305 | ### Structure 306 | 307 | [Pragma marks](http://nshipster.com/pragma/) are a great way to group your methods, especially in view controllers. Here is a common structure that works with almost any view controller: 308 | 309 | ```objective-c 310 | 311 | #import "SomeModel.h" 312 | #import "SomeView.h" 313 | #import "SomeController.h" 314 | #import "SomeStore.h" 315 | #import "SomeHelper.h" 316 | #import 317 | 318 | static NSString * const XYZFooStringConstant = @"FoobarConstant"; 319 | static CGFloat const XYZFooFloatConstant = 1234.5; 320 | 321 | @interface XYZFooViewController () 322 | 323 | @property (nonatomic, copy, readonly) Foo *foo; 324 | 325 | @end 326 | 327 | @implementation XYZFooViewController 328 | 329 | #pragma mark - Lifecycle 330 | 331 | - (instancetype)initWithFoo:(Foo *)foo; 332 | - (void)dealloc; 333 | 334 | #pragma mark - View Lifecycle 335 | 336 | - (void)viewDidLoad; 337 | - (void)viewWillAppear:(BOOL)animated; 338 | 339 | #pragma mark - Layout 340 | 341 | - (void)makeViewConstraints; 342 | 343 | #pragma mark - Public Interface 344 | 345 | - (void)startFooing; 346 | - (void)stopFooing; 347 | 348 | #pragma mark - User Interaction 349 | 350 | - (void)foobarButtonTapped; 351 | 352 | #pragma mark - XYZFoobarDelegate 353 | 354 | - (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo; 355 | 356 | #pragma mark - Internal Helpers 357 | 358 | - (NSString *)displayNameForFoo:(Foo *)foo; 359 | 360 | @end 361 | ``` 362 | 363 | The most important point is to keep these consistent across your project's classes. 364 | 365 | ### External Style Guides 366 | 367 | 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: 368 | 369 | * [GitHub](https://github.com/github/objective-c-style-guide) 370 | * [Google](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml) 371 | * [The New York Times](https://github.com/NYTimes/objective-c-style-guide) 372 | * [Ray Wenderlich](https://github.com/raywenderlich/objective-c-style-guide) 373 | * [Sam Soffes](https://gist.github.com/soffes/812796) 374 | * [Luke Redpath](http://lukeredpath.co.uk/blog/2011/06/28/my-objective-c-style-guide/) 375 | 376 | ## Diagnostics 377 | 378 | ### Compiler warnings 379 | 380 | 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][warnings-slides]. The slides also contain information on how to suppress certain warnings in specific files, or in specific sections of code. 381 | 382 | In short, add at least these values to the _“Other Warning Flags”_ build setting: 383 | 384 | - `-Wall` _(Enables lots of additional warnings)_ 385 | - `-Wextra` _(Enables more additional warnings)_ 386 | 387 | Also enable the _“Treat warnings as errors”_ build setting. 388 | 389 | [warnings-slides]: https://speakerdeck.com/hasseg/the-compiler-is-your-friend 390 | 391 | ### Clang Static Analyzer 392 | 393 | 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. 394 | 395 | You can manually run the analyzer from the _Product → Analyze_ menu item in Xcode. 396 | 397 | 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. 398 | 399 | Recommendations: 400 | 401 | - Enable _all_ of the checks in the analyzer (by enabling all of the options in the “Static Analyzer” build setting sections) 402 | - 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.) 403 | - Set the _“Mode of Analysis for ‘Analyze’”_ build setting to _Shallow (faster)_ 404 | - Set the _“Mode of Analysis for ‘Build’”_ build setting to _Deep_ 405 | 406 | ### [Faux Pas](http://fauxpasapp.com/) 407 | 408 | Created by our very own [Ali Rantakari][ali-rantakari-twitter], 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! 409 | 410 | _(Note: all Futurice employees get a free license to this — just ask Ali.)_ 411 | 412 | [ali-rantakari-twitter]: https://twitter.com/AliRantakari 413 | 414 | ### Debugging 415 | 416 | When your app crashes, Xcode does not break into the debugger by default. To achieve this, add an exception breakpoint (click the "+" at the bottom of Xcode's Debug Navigator) to halt execution whenever an exception is raised. In many cases, you will then see the line of code responsible for the exception. This catches any exception, even handled ones. If Xcode keeps breaking on benign exceptions in third party libraries e.g., you might be able to mitigate this by choosing _Edit Breakpoint_ and setting the _Exception_ drop-down to _Objective-C_. 417 | 418 | For view debugging, [Reveal][reveal] and [Spark Inspector][spark-inspector] are two powerful visual inspectors that can save you hours of time, especially if you're using Auto Layout and want to locate views that are collapsed or off-screen. Granted, Xcode offers [something very similar][xcode-view-debugging] for free, but it's iOS 8+ only and feels somewhat less polished. 419 | 420 | [reveal]: http://revealapp.com/ 421 | [spark-inspector]: http://sparkinspector.com 422 | [xcode-view-debugging]: https://developer.apple.com/library/ios/recipes/xcode_help-debugger/using_view_debugger/using_view_debugger.html 423 | 424 | ### Profiling 425 | 426 | Xcode comes with a profiling suite called Instruments. It contains a myriad of tools for profiling memory usage, CPU, network communications, graphics and much more. It's a complex beast, but one of its more straight-forward use cases is tracking down memory leaks with the Allocations instrument. Simply choose _Product_ > _Profile_ in Xcode, select the Allocations instrument, hit the Record button and filter the Allocation Summary on some useful string, like the prefix of your own app's class names. The count in the Persistent column then tells you how many instances of each object you have. Any class for which the instance count increases indiscriminately indicates a memory leak. 427 | 428 | Also good to know is that Instruments has an Automation tool for recording and playing back UI interactions as JavaScript files. [UI Auto Monkey][ui-auto-monkey] is a script that will use Automation to randomly pummel your app with taps, swipes and rotations which can be useful for stress/soak testing. 429 | 430 | [ui-auto-monkey]: https://github.com/jonathanpenn/ui-auto-monkey 431 | 432 | ## Analytics 433 | 434 | 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][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. 435 | 436 | [google-tag-manager]: http://www.google.com/tagmanager/ 437 | 438 | 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: 439 | 440 | ```objective-c 441 | 442 | - (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode 443 | { 444 | NSString *editModeString = [self nameForEditMode:editMode]; 445 | 446 | [self pushToDataLayer:@{ 447 | @"event": "addItem", 448 | @"itemIdentifier": item.identifier, 449 | @"editMode": editModeString 450 | }]; 451 | } 452 | 453 | ``` 454 | 455 | 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. 456 | 457 | ### Crash Logs 458 | 459 | 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][plcrashreporter] and your own backend) but it’s recommended that you use an existing service instead — for example one of the following: 460 | 461 | * [Crashlytics](http://www.crashlytics.com) 462 | * [HockeyApp](http://hockeyapp.net) 463 | * [Crittercism](https://www.crittercism.com) 464 | * [Splunk MINTexpress](https://mint.splunk.com) 465 | 466 | [plcrashreporter]: https://www.plcrashreporter.org 467 | 468 | 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. 469 | 470 | 471 | ## Building 472 | 473 | ### Build Configurations 474 | 475 | Even simple apps can be built in different ways. The most basic separation that Xcode gives you is that between _debug_ and _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. 476 | 477 | However, this is a bit too simple for real-world applications. You might – no, [_should!_][futurice-environments] – have different environments for testing, staging and other activities related to your service. Each might have its own base URL, log level, bundle identifier (so you can install them side-by-side), provisioning 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. 478 | 479 | [futurice-environments]: https://blog.futurice.com/five-environments-you-cannot-develop-without 480 | 481 | #### `xcconfig` files for build settings 482 | 483 | Typically build settings are specified in the Xcode GUI, but you can also use _configuration settings files_ (“`.xcconfig` files”) for them. The benefits of using these are: 484 | 485 | - You can add comments to explain things 486 | - You can `#include` other build settings files, which helps you avoid repeating yourself: 487 | - If you have some settings that apply to all build configurations, add a `Common.xcconfig` and `#include` it in all the other files 488 | - 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 489 | - Conflict resolution and merging becomes easier 490 | 491 | Find more information about this topic in [these presentation slides][xcconfig-slides]. 492 | 493 | [xcconfig-slides]: https://speakerdeck.com/hasseg/xcode-configuration-files 494 | 495 | ### Targets 496 | 497 | 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. 498 | 499 | ### Schemes 500 | 501 | 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. 502 | 503 | A suggested naming convention for schemes is `MyApp () [Environment]`: 504 | 505 | MyApp (English) [Development] 506 | MyApp (German) [Development] 507 | MyApp [Testing] 508 | MyApp [Staging] 509 | MyApp [App Store] 510 | 511 | 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. 512 | 513 | ## Deployment 514 | 515 | Deploying software on iOS devices isn't exactly straightforward. That being said, here are some central concepts that, once understood, will help you tremendously with it. 516 | 517 | ### Signing 518 | 519 | 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: 520 | 521 | * __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. 522 | * __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". 523 | 524 | ### Provisioning 525 | 526 | 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: 527 | 528 | * __Development provisioning profile:__ It contains a list of all devices that are authorized to install and run the software. 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 (*). 529 | 530 | * __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. 531 | * __Ad-Hoc:__ Just like development profiles, it contains a whitelist of devices the app can be installed to. This type of profile can be used for beta testing on 100 devices per year. For a smoother experience and up to 1000 distinct users, you can use Apple's newly acquired [TestFlight][testflight] service. Supertop offers a good [summary of its advantages and issues][testflight-discussion]. 532 | * __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. 533 | * __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. 534 | 535 | [testflight]: https://developer.apple.com/testflight/ 536 | [testflight-discussion]: http://blog.supertop.co/post/108759935377/app-developer-friends-try-testflight 537 | 538 | 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. 539 | 540 | #### Debugging Provisioning 541 | 542 | Sometimes you need to debug a provisioning issue. For instance, Xcode may refuse to install the build to an attached device, because the latter is not on the (development or ad-hoc) profile's device list. In those cases, you can use Craig Hockenberry's excellent [Provisioning][provisioning] plugin by browsing to `~/Library/MobileDevice/Provisioning Profiles`, selecting a `.mobileprovision` file and hitting Space to launch Finder's Quick Look feature. It will show you a wealth of information such as devices, entitlements, certificates, and the App ID. 543 | 544 | [provisioning]: https://github.com/chockenberry/Provisioning 545 | 546 | ### Uploading 547 | 548 | [iTunes Connect][itunes-connect] is Apple's portal for managing your apps on the App Store. To upload a build, Xcode 6 requires an Apple ID that is part of the developer account used for signing. This can make things tricky when you are part of several developer accounts and want to upload their apps, as for mysterious reasons _any given Apple ID can only be associated with a single iTunes Connect account_. One workaround is to create a new Apple ID for each iTunes Connect account you need to be part of, and use Application Loader instead of Xcode to upload the builds. That effectively decouples the building and signing process from the upload of the resulting `.app` file. 549 | 550 | After uploading the build, be patient as it can take up to an hour for it to show up under the Builds section of your app version. When it appears, you can link it to the app version and submit your app for review. 551 | 552 | [itunes-connect]: https://itunesconnect.apple.com 553 | 554 | ## In-App Purchases (IAP) 555 | 556 | When validating in-app purchase receipts, remember to perform the following checks: 557 | 558 | - __Authenticity:__ That the receipt comes from Apple 559 | - __Integrity:__ That the receipt has not been tampered with 560 | - __App match:__ That the app bundle ID in the receipt matches your app’s bundle identifier 561 | - __Product match:__ That the product ID in the receipt matches your expected product identifier 562 | - __Freshness:__ That you haven’t seen the same receipt ID before. 563 | 564 | Whenever possible, design your IAP system to store the content for sale server-side, and provide it to the client only in exchange for a valid receipt that passes all of the above checks. This kind of a design thwarts common piracy mechanisms, and — since the validation is performed on the server — allows you to use Apple’s HTTP receipt validation service instead of interpreting the receipt `PKCS #7` / `ASN.1` format yourself. 565 | 566 | For more information on this topic, check out the [Futurice blog: Validating in-app purchases in your iOS app][futu-blog-iap]. 567 | 568 | [futu-blog-iap]: http://futurice.com/blog/validating-in-app-purchases-in-your-ios-app 569 | 570 | 571 | ## More Ideas 572 | 573 | - 3x assets, iPhone 6 screen sizes explained 574 | - Add list of suggested compiler warnings 575 | - Ask IT about automated Jenkins build machine 576 | - Add section on Testing 577 | - Add "proven don'ts" 578 | 579 | [reactivecocoa-github]: https://github.com/ReactiveCocoa/ReactiveCocoa 580 | -------------------------------------------------------------------------------- /TRANSLATION-CN.md: -------------------------------------------------------------------------------- 1 | iOS 最佳实践 2 | ================== 3 | 4 | ## 译者注 5 | 6 | 本文翻译自 [futurice 公司](http://www.futurice.com/)的 [iOS Good Practices](https://github.com/futurice/ios-good-practices),译文在 [Github](https://github.com/oa414/ios-good-practices) 上进行维护,同时在 [简书](http://www.jianshu.com/p/b0bf2368fb95) 上进行发布。 7 | 8 | 本文尚未经过大牛审校,纰漏瑕疵以及语句不顺的地方,以在 [Github](https://github.com/oa414/ios-good-practices) 提出,请大家多多指正。 9 | 10 | 以下是正文 11 | 12 | -------------- 13 | 14 | _就像软件一样,如果我们不持续改进这份文档,它就会落伍。我们希望大家都来帮助我们改进它 —— 只要开一个 issue 或者发送一个 pull request!_ 15 | 16 | 对其他平台感兴趣?看看我们的 [Android 开发最佳实践][android-best-practices] 和 [Windows App 开发最佳实践][windows-app-development-best-practices]吧。 17 | 18 | [android-best-practices]: https://github.com/futurice/android-best-practices 19 | [windows-app-development-best-practices]: https://github.com/futurice/windows-app-development-best-practices 20 | 21 | ## 为什么阅读本文档 22 | 23 | 跳进了 iOS 的坑真是麻烦。无论是 Swift 还是 Objective-C, 都没有在其他地方广泛使用,而且这个平台对每个东西都几乎有它自己的命名方式,并且连要在真机上调试都充满了坎坷。无论你是刚刚入门 Cocoa 还是想纠正自己开发习惯的开发者,都能从本文档获益。不过下面写的仅仅是建议,所以如果你有一个更好的方案,那就试试吧! 24 | 25 | ## 入门 26 | 27 | ### Xcode 28 | 29 | [Xcode][xcode] 是大多数 iOS 开发者的选择,并且是 Apple 唯一官方支持的 IDE。有一些其他的选择,比如 [AppCode][appcode] 是最有名的,但是除非你是经验丰富的开发者,否则就使用 Xcode 吧。不要管它的一些小缺点啦,它现在已经蛮好用咯。 30 | 31 | 要安装 Xcode,只需要下载 [Mac App Store 中的 Xcode ][xcode-app-store]。它会同时下载最新的 SDK 和模拟器,同时你可以从 _Preferences > Downloads_ 下载更多的内容。 32 | 33 | [xcode]: https://developer.apple.com/xcode/ 34 | [appcode]: https://www.jetbrains.com/objc/ 35 | [xcode-app-store]: https://itunes.apple.com/us/app/xcode/id497799835 36 | 37 | 38 | ### 项目初始化 39 | 40 | 开始 iOS 开发的时候,一个常见的问题是用代码写所有的 view 还是使用 Interface Builder(Storyboards 或者 XIB )。两种方案都久经考量。但是,有下面几个考虑: 41 | 42 | #### 为什么使用代码? 43 | 44 | * Storyboard 因为它的复杂的 XML 结构容易带来版本冲突,这让代码合并变得困难。 45 | * 容易用代码结构化以及复用 view,让你的代码变得 [DRY][dry]。 46 | * 所有的信息汇集一处。在 Interface Builder 里面你需要点击所有的检查器来寻找你要找的东西。 47 | 48 | 49 | [dry]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself 50 | 51 | #### 为什么用 Storyboard? 52 | 53 | * 为了更少的技术要求,Storyboard 使用了一个很好的直接贡献于项目的方法,比如,通过调整颜色或者布局的 constraints。然而,它要求一个项目已经做好配置,并且开发者有一些时间掌握基础 54 | * 当你不用构建项目也能看到变化的时候,集成更快了 55 | * 在 Xcode6 里面,自定义的文字和 UI 元素在 storyboard 里面都可以可视化表示,比你在代码里面修改好多了 56 | * 从 iOS8 开始,[Size Classes][size-classes] 允许你设计不同类型设备的屏幕,不用重复一些工作 57 | 58 | [size-classes]: http://blog.futurice.com/adaptive-view-ios8 59 | 60 | ### 忽略文件 61 | 62 | 当把项目放入版本控制系统的时候,首先应该有一个好的 `.gitignore` 文件。这样,不必要的文件(用户设置,临时文件这些)都不会放进你的仓库里面。幸运的是,Github 已经给了我们 [Objective-C][objc-gitignore] 和 [Swift][swift-gitignore] 语言的模板 63 | 64 | [objc-gitignore]: https://github.com/github/gitignore/blob/master/Objective-C.gitignore 65 | [swift-gitignore]: https://github.com/github/gitignore/blob/master/Swift.gitignore 66 | 67 | ### CocoaPods 68 | 69 | 如果你计划增加外部依赖(比如,第三方库)在你的项目中,[CocoaPods][cocoapods] 提供了一个快捷的途径,就像这样: 70 | 71 | sudo gem install cocoapods 72 | 73 | 要开始使用,仅仅需要在你的 iOS 项目目录下运行: 74 | 75 | pod init 76 | 77 | 它会创建一个 Podfile,会管理你所有的依赖,在 Podfile 中加入你的依赖后,运行 78 | 79 | pod install 80 | 81 | 来安装第三方库并且将它们作为 workspace 的一部分,你的 workspace 也会包含你自己的项目。 一般推荐[提交你自己的项目的依赖][committing-pods],而不是每个开发者在一个 checkout 之后运行 `pod install` 。 82 | 83 | 注意在之后,你需要打开 `.xcworkspace` 而不是 `.xcproject`,否则你的代码就不能被编译了,命令: 84 | 85 | pod update 86 | 87 | 会升级所有的 pod 到最新版本,你可以用大量 [符号][cocoapods-pod-syntax] 来定义你期望的版本需求。 88 | 89 | [cocoapods]: http://www.cocoapods.org 90 | [cocoapods-pod-syntax]: http://guides.cocoapods.org/syntax/podfile.html#pod 91 | [committing-pods]: https://www.dzombak.com/blog/2014/03/including-pods-in-source-control.html 92 | 93 | ### 项目结构 94 | 95 | 为了组织目录里面的上百个源代码文件,最好根据你的架构来设置一些文件结构。比如,你可以用这样的: 96 | 97 | ├─ Models 98 | ├─ Views 99 | ├─ Controllers 100 | ├─ Stores 101 | ├─ Helpers 102 | 103 | 104 | 首先,将他们创建为 Group(黄色的目录),在 Xcode 的项目导航里面的你的项目中。然后对每个项目里的文件,将它们连接到真实的文件目录 —— 通过打开它右边的文件检查器,点击小小的目录图标,在你的项目目录下创建一个和 group 同名的子目录。 105 | 106 | #### 本地化 107 | 108 | 一开始就应该把所有的显示给用户的字符串放进本地化文件。这样不仅仅为了翻译方便,同时也便于查找用户看见的文本。你可以在 build Scheme 中加入一个启动参数来指定特定的语言 109 | 110 | -AppleLanguages (Finnish) 111 | 112 | 113 | 对于更复杂的翻译问题,比如复数(比如 "1 person" 和 "3 people"),你应该使用 [`.stringsdict` format][stringsdict-format] 而不是一个普通的 `localizable.strings` 文件。在有了这个强大的工具来处理比如 "one", some", "few" 和 "many" 的情况。 [当处理俄罗斯语和阿拉伯语的时候][language-plural-rules],你就不用急得抓耳挠腮了。 114 | 115 | 关于本地化的更多信息,看 2012年 2月的 Helsink iOS 会议的 [幻灯片][l10n-slides],大部分内容对现在也是适用的。 116 | 117 | [stringsdict-format]: https://developer.apple.com/library/prerelease/ios/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html 118 | [language-plural-rules]: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html 119 | [l10n-slides]: https://speakerdeck.com/hasseg/localization-practicum 120 | 121 | #### 常量 122 | 123 | 创建被 prefix header 引入的一个 `Constants.h` 文件。 124 | 125 | 不要用宏定义(用 `#define`),用实际的常量定义 126 | 127 | static CGFloat const XYZBrandingFontSizeSmall = 12.0f; 128 | static NSString * const XYZAwesomenessDeliveredNotificationName = @"foo"; 129 | 130 | 常量类型安全,并且有更明确的作用域(并不是在所有没有被引入的文件中能使用)。不能被重定义,并且可以在调试器中使用。 131 | 132 | 133 | ### 分支模型 134 | 135 | 特别是分发你的 app 的时候(如提交到 App Store),最好把分支用特别的 tag 区分。同时,新特性的开发,会引入很多 commit 的,也应该在独立的分支上提交。[`git-flow`][gitflow-github] 是一个帮助你遵从这个约定的工具。它简单地包装了 git 的分支和 tag 的命令,帮助维护一个正确的分支结构。所有的开发分支(比如 `develop`), 关于 app 版本的 tag 分支以及提交到 master 分支的: 136 | 137 | git flow release finish 138 | 139 | [gitflow-github]: https://github.com/nvie/gitflow 140 | 141 | ## 常用库 142 | 143 | 总得来说,来把一个第三方库加入到你的项目中需要慎重考虑。确实,一个灵巧的库或许能帮助你解决现在的问题,但是可能在之后陷入维护的噩梦,比如在下一个系统版本改变一些东西之后,或者一个第三方库的场景变成了官方的 API。不过在一个良好的设计的代码中,切换实现是很轻松的。尽量考虑用 Apple 的广泛(而且优秀的)框架吧~ 144 | 145 | 这个小节尽量保持简短。库的特性是为了减少模板代码(比如 AutoLayout)或者解决复杂的需要很多测试的问题,比如日期计算。当你在 iOS 中更加专业的时候,挖掘这里的源代码,通过它们底层的 Apple 框架来认识他们,你会发现这些可以做大量工作。 146 | 147 | ### AFNetworking 148 | 149 | 99.95% 的 iOS 开发者使用这个网络库,当 `NSURLSession` 自己本身也非常完善的时候, `AFNetworking` 仍然能凭借很多 app 需要的队列请求管理功能立足于不败之地。 150 | 151 | ### DateTools 日期工具 152 | 153 | 总得来说,[不要自己写日期计算][timezones-youtube]。幸运的是,有一个 DateTools 是 MIT 协议的,而且经过彻底测试的,你可以放心的在需要用日期的时候使用它。 154 | 155 | [timezones-youtube]: https://www.youtube.com/watch?v=-5wpm-gesOY 156 | 157 | ### Auto Layout 库 158 | 159 | 如果你更喜欢在代码里面写界面,你会用过 Apple 难用的 'NSLayoutConstraint' 的工厂或者 [Visual Format Language][visual-format-language] 。前者很罗嗦,后者基于字符串,不利于编译器检查。 160 | 161 | 162 | [Masonry][masonry-github] 通过它们自己的 DSL 来取代常量, Swift 中一个类似的库是 [Cartography][cartography-github],它利用了语言的丰富的操作符重载特性。如果更加保守的话,[FLKAutoLayout][flkautolayout-github] 提供了一个干净但是没有太多魔法的原生 API 包装。 163 | 164 | [visual-format-language]: https://developer.apple.com/library/ios/documentation/userexperience/conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html#//apple_ref/doc/uid/TP40010853-CH3-SW1 165 | [masonry-github]: https://www.github.com/Masonry/Masonry 166 | [cartography-github]: https://github.com/robb/Cartography 167 | [flkautolayout-github]: https://github.com/floriankugler/FLKAutoLayout 168 | 169 | ## Architecture 架构 170 | 171 | 172 | * [Model-View-Controller-Store (MVCS)][mvcs] 173 | * 这是 Apple 默认的架构(MVC),通过扩展一表示 Model 实例的存储层以及处理网络,缓存等内容。 174 | * 每一个存储通过 `RACSignal`s 或者 `void` 返回值的自定义 block 方法来返回。 175 | * [Model-View-ViewModel (MVVM)][mvvm] 176 | * 通过 "massive view controllers": MVVM 认为 `UIViewController` 子类是 view 的一部分,并且保持精简,通过在 viewmodel 里面维持状态。 177 | * 对于 Cocoa 开发者是非常新的概念,但是 [获得][cocoasamurai-rac] [推动][raywenderlich-mvvm] 178 | * [View-Interactor-Presenter-Entity-Routing (VIPER)][viper] 179 | * 值得在大型项目中一看的架构,在 MVVM 都显得复杂,而且需要关注测试的时候。 180 | 181 | [mvcs]: http://programmers.stackexchange.com/questions/184396/mvcs-model-view-controller-store 182 | [mvvm]: http://www.objc.io/issue-13/mvvm.html 183 | [cocoasamurai-rac]: http://cocoasamurai.blogspot.de/2013/03/basic-mvvm-with-reactivecocoa.html 184 | [raywenderlich-mvvm]: http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1 185 | [viper]: http://www.objc.io/issue-13/viper.html 186 | 187 | ### 事件模式 188 | 189 | 这里有一些通知其他对象的常用方法: 190 | 191 | 192 | * __Delegation (委托):__ _(一对一)_ Apple 经常用它(或者说,太多了)。用它来执行回调,比如, Model View 做一个回调 193 | * __Callback blocks (回调代码块):__ _(一对一)_ 可以更加解耦,可以维护类似的相关代码段。同时在有很多 sender 的时候比委托有更好的扩展性。 194 | * __Notification Center (通知):__ _(一对多)_ 最常用的对象来向第一个观察者发送事件的方法。非常解耦合 - 通知甚至可以全局地进行观察,而不用引用派发对象 195 | * __Key-Value Observing (KVO,键值编码):__ _(一对多)_ 不需要观察者来明确发送的时间,就像 _Key-Value Coding (KVC)_ 符合观察的键(属性)。通常不推荐使用,因为他不自然的特性以及繁琐的API。 196 | * __Signals(信号):__ _(一对多)_ [ReactiveCocoa][reactivecocoa-github] 的核心, 允许链接和组合你的内容, 提供了防止 [callback hell][elm-escape-from-callback-hell] 的一个方法。 197 | 198 | 199 | [elm-escape-from-callback-hell]: http://elm-lang.org/learn/Escape-from-Callback-Hell.elm 200 | 201 | ### Models 202 | 203 | 保持你的 model 是不可变的,以及用他们来改变远程的 API 的语义以及类型。Github 的 [Mantle](https://github.com/Mantle/Mantle) 是一个好的选择 204 | 205 | ### Views 206 | 207 | 当用 Auto Layout 布局你的 view 的时候,确保在你父类中加入了下面的代码: 208 | 209 | + (BOOL)requiresConstraintBasedLayout 210 | { 211 | return YES; 212 | } 213 | 214 | 否则当系统没有调用 `-updateConstraints` 的时候,你可能会遇到奇怪的 bug。 215 | 216 | ### Controllers 217 | 218 | 建议使用依赖注入,比如:传递任何需要的对象作为参数,而不是在一个单例中保持所有的状态。后一种方法仅仅在状态是 _真的_ 全局的时候适用。 219 | 220 | ```objective-c 221 | + [[FooDetailsViewController alloc] initWithFoo:(Foo *)foo]; 222 | ``` 223 | 224 | ## 网络 225 | 226 | ### 传统的方式:使用回调 block 227 | 228 | ```objective-c 229 | // GigStore.h 230 | 231 | typedef void (^FetchGigsBlock)(NSArray *gigs, NSError *error); 232 | 233 | - (void)fetchGigsForArtist:(Artist *)artist completion:(FetchGigsBlock)completion 234 | 235 | 236 | // GigsViewController.m 237 | 238 | [[GigStore sharedStore] fetchGigsForArtist:artist completion:^(NSArray *gigs, NSError *error) { 239 | if (!error) { 240 | // Do something with gigs 241 | } 242 | else { 243 | // :( 244 | } 245 | ]; 246 | ``` 247 | 248 | 这样运行,但是如果你有多个组合的网络请求的时候,就会进入回调嵌套的地狱。 249 | 250 | ### Reactive 的方法: 使用 RAC 信号 251 | 252 | 如果你发现已经陷入了回调的地狱,看看 [ReactiveCocoa (RAC)][reactivecocoa-github] 吧,它是一个万能而且多功能的库,能改变大家写 [整个 app][groceryList-github] 的方法,但是你可以在它适用的地方保守地使用它。 253 | 254 | 在 [Teehan+Lax][teehan-lax-rac] 和 [NSHipster][nshipster-rac] 上面有一些关于 RAC (and FRP in general) 以及函数式编程的概念的优秀介绍。 255 | 256 | [grocerylist-github]: https://github.com/jspahrsummers/GroceryList 257 | [teehan-lax-rac]: http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/ 258 | [nshipster-rac]: http://nshipster.com/reactivecocoa/ 259 | 260 | ```objective-c 261 | // GigStore.h 262 | 263 | - (RACSignal *)gigsForArtist:(Artist *)artist; 264 | 265 | 266 | // GigsViewController.m 267 | 268 | [[GigStore sharedStore] gigsForArtist:artist] 269 | subscribeNext:^(NSArray *gigs) { 270 | // Do something with gigs 271 | } error:^(NSError *error) { 272 | // :( 273 | } 274 | ]; 275 | ``` 276 | 277 | 它允许通过信号和其他信号的结合,在展示它们之前做一些改变。 278 | 279 | ## Assets 资源 280 | 281 | [Asset catalogs][asset-catalogs] 是管理你所有项目可视化资源的最好方式,它们可以同时管理通用的以及设备相关的 (iPhone 4-inch, iPhone Retina, iPad, 等) 资源,并且会自动通过它们的名字分组。告诉你的设计师如何添加它们(Xcode 有内置的 Git 客户端)可以节省很多时间,否则你会花很多时间来在从邮件或者其他渠道复制到代码库里面。它可以让设计师马上尝试资源改变的样子,并且反复实验。 282 | 283 | [asset-catalogs]: https://developer.apple.com/library/ios/recipes/xcode_help-image_catalog-1.0/Recipe.html 284 | 285 | ### Using Bitmap Images 使用位图 286 | 287 | 288 | Asset catalogs 仅仅暴露了图片的名称,图片集里面的抽象的名字。这可以避免资源名字的冲突,就像 `button_large@2x.png` 的文件的命名空间在它的图片集里面。遵守一些命名规则可以让生活更美好: 289 | 290 | ```objective-c 291 | IconCheckmarkHighlighted.png // Universal, non-Retina 292 | IconCheckmarkHighlighted@2x.png // Universal, Retina 293 | IconCheckmarkHighlighted~iphone.png // iPhone, non-Retina 294 | IconCheckmarkHighlighted@2x~iphone.png // iPhone, Retina 295 | IconCheckmarkHighlighted-568h@2x~iphone.png // iPhone, Retina, 4-inch 296 | IconCheckmarkHighlighted~ipad.png // iPad, non-Retina 297 | IconCheckmarkHighlighted@2x~ipad.png // iPad, Retina 298 | ``` 299 | 300 | 修饰后缀 `-568h`, `@2x`, `~iphone` and `~ipad` 是不必要的,但是有他们在文件里面,当把文件拖进去的时候,Xcode 会正确地处置它们。这避免赋值错误。 301 | 302 | ### 使用向量图 303 | 304 | 你可以用设计师原始的 [vector graphics (PDFs)][vector-assets] 加入到 asset catalogs,Xcode 可以自动地根据它们生成位图。这减少了你的工程的复杂性(管理更少的文件)。 305 | 306 | [vector-assets]: http://martiancraft.com/blog/2014/09/vector-images-xcode6/ 307 | 308 | ## 代码风格 309 | 310 | ### 命名 311 | 312 | 尽管命名约定很长,但是 Apple 一如既往地在 API 中遵守了命名原则。 313 | 314 | [cocoa-coding-guidelines]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html 315 | 316 | 这里有一些你应该使用的基本原则: 317 | 318 | 一个方法用 _动词_ 开头的表示它做了一些副作用,但是不会返回任何东西: 319 | `- (void)loadView;` 320 | `- (void)startAnimating;` 321 | 322 | 323 | 任何以 _名词_ 开头的方法,没有副作用而且返回了它指的对象: 324 | `- (UINavigationItem *)navigationItem;` 325 | `+ (UILabel *)labelWithText:(NSString *)text;` 326 | 327 | 尽量分离两者,比如,不要在操作数据的时候产生副作用,反之亦然。这会让你的副作用包含到代码更小的细分粒度里面,让调试变得非常困难。 328 | 329 | ### 结构 330 | 331 | [Pragma marks](http://nshipster.com/pragma/) 是把你代码分组的一个好方法,特别是在 view Controller 里。下面的结构可以适用于绝大多数 view Controller 的工作: 332 | 333 | ```objective-c 334 | 335 | #import "SomeModel.h" 336 | #import "SomeView.h" 337 | #import "SomeController.h" 338 | #import "SomeStore.h" 339 | #import "SomeHelper.h" 340 | #import 341 | 342 | static NSString * const XYZFooStringConstant = @"FoobarConstant"; 343 | static CGFloat const XYZFooFloatConstant = 1234.5; 344 | 345 | @interface XYZFooViewController () 346 | 347 | @property (nonatomic, copy, readonly) Foo *foo; 348 | 349 | @end 350 | 351 | @implementation XYZFooViewController 352 | 353 | #pragma mark - Lifecycle 354 | 355 | - (instancetype)initWithFoo:(Foo *)foo; 356 | - (void)dealloc; 357 | 358 | #pragma mark - View Lifecycle 359 | 360 | - (void)viewDidLoad; 361 | - (void)viewWillAppear:(BOOL)animated; 362 | 363 | #pragma mark - Layout 364 | 365 | - (void)makeViewConstraints; 366 | 367 | #pragma mark - Public Interface 368 | 369 | - (void)startFooing; 370 | - (void)stopFooing; 371 | 372 | #pragma mark - User Interaction 373 | 374 | - (void)foobarButtonTapped; 375 | 376 | #pragma mark - XYZFoobarDelegate 377 | 378 | - (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo; 379 | 380 | #pragma mark - Internal Helpers 381 | 382 | - (NSString *)displayNameForFoo:(Foo *)foo; 383 | 384 | @end 385 | ``` 386 | 387 | 388 | 最重要的一点是在你项目的类中保持一致性 389 | 390 | ### 其他风格指南 391 | 392 | 我们公司没有任何公司级别的代码风格指南,详细看看其他开发者的 Objective-C 风格指南很有用,即使一些内容是公司相关的或者过于激进了。 393 | 394 | 395 | * [GitHub](https://github.com/github/objective-c-style-guide) 396 | * [Google](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml) 397 | * [The New York Times](https://github.com/NYTimes/objective-c-style-guide) 398 | * [Ray Wenderlich](https://github.com/raywenderlich/objective-c-style-guide) 399 | * [Sam Soffes](https://gist.github.com/soffes/812796) 400 | * [Luke Redpath](http://lukeredpath.co.uk/blog/2011/06/28/my-objective-c-style-guide/) 401 | 402 | ## 诊断 403 | 404 | ### 编译警告 405 | 406 | 407 | 推荐你尽可能多打开编译警告,并且像对待错误一样对待编译警告。推荐 [这个PPT][warnings-slides]。这个幻灯片覆盖了如何在特定文件,或者特别代码段里面消除相关警告的内容。 408 | 409 | 410 | 简单的来说,至少需要在 _“Other Warning Flags” 编译设置里面定义下面的值: 411 | 412 | - `-Wall` _(增加很多的警告)_ 413 | - `-Wextra` _(增加更多的警告)_ 414 | 415 | 416 | 同时打开 _“Treat warnings as errors”_ 417 | 418 | [warnings-slides]: https://speakerdeck.com/hasseg/the-compiler-is-your-friend 419 | 420 | ### Clang 静态分析 421 | 422 | 423 | Clang 编译器(Xcode 使用的)有一个 _静态分析器_ 来进行你的代码控制和数据流的分析,来检测编译器不能检测的许多错误。 424 | 425 | 你可以通过在 Xcode 里面手动运行 _Product → Analyze_ 菜单项来手动执行代码分析 426 | 427 | 428 | 分析器可以用浅或者深的模式运行,后者更加慢,但是可以从跨函数的控制流和数据流上分析更多问题 429 | 430 | 431 | 推荐: 432 | 433 | - 打开 _所有_ 分析器检查 (通过在 building setting 中打开所有 “Static Analyzer” 选项) 434 | - 在 release 的编译设置里面打开 _“Analyze during ‘Build’”_ 来让分析器自动在发布的版本构建的时候运行。(这样你就不需要记住要手动运行了) 435 | - 把 _“Mode of Analysis for ‘Analyze’”_ 设置为 _Shallow (faster)_ 436 | - 把 _“Mode of Analysis for ‘Build’”_ 设置为 _Deep_ 437 | 438 | 439 | ### [Faux Pas](http://fauxpasapp.com/) 440 | 441 | 我们自己的 [Ali Rantakari][ali-rantakari-twitter] 创建的,Faux Pas 是一个极佳的静态错误检测工具,它分析你的代码并且找出那些你自己甚至都没发现的问题。在提交你的 App 到应用商店前用它吧! 442 | 443 | [ali-rantakari-twitter]: https://twitter.com/AliRantakari 444 | 445 | ### 调试 446 | 447 | 当你的 App 崩溃的时候,Xcode 不会默认进入到调试器里面。为了调试,你需要增加一个异常断点(在 Xcode 的 Debug 导航中点 “+”),来在异常发生的时候退出执行。在很多情况下,你需要看看触发这些异常的代码。它会捕捉任何异常,即使是已经处理的。如果 Xcode 在一个第三方库里面中断执行,比如,你可能需要通过选择 _Edit Breakpoint_ 并且设置 _Exception_ 为 _Objective-C_ 。 448 | 449 | 对于视图 debug,[Reveal][reveal] 和 [Spark Inspector][spark-inspector] 这两个强有力的可视化检查工具可以帮你省下很多时间,特别是在你使用 Auto Layout 并且希望定位出问题或者溢出屏幕的视图的时候。Xcode 提供了免费的[类似功能][xcode-view-debugging],但是只能适用于 iOS 8+ 并且不那么好用。 450 | 451 | [reveal]: http://revealapp.com/ 452 | [spark-inspector]: http://sparkinspector.com 453 | [xcode-view-debugging]: https://developer.apple.com/library/ios/recipes/xcode_help-debugger/using_view_debugger/using_view_debugger.html 454 | 455 | ### 分析 456 | 457 | Xcode 有一个叫 Instruments 的分析工具,它包括了许多内存分析,CPU,网络通讯,图形以及更多的工具,它有点复杂的,但是它的追踪内存泄漏的时候还是蛮直观的。只需要在 Xcode 中 选择 _Product_ > _Profile_ ,选择 Allocations, 点击 Record 按钮并且用一些有用的字符串过滤申请空间的信息,比如你自己的 app 的类名。它会在固定的列中统计,并且告诉你每个对象有多少实例。到底是什么类一直增加实例导致内存泄漏。 458 | 459 | Instruments 也有自动化的工具来进行录制并且运行 UI 交互以及 JavaScript 文件。[UI Auto Monkey][ui-auto-monkey] 是一个自动化随机点击、滑动以及旋转你的 app 的脚本,他在压力、渗透测试中很有用。 460 | 461 | [ui-auto-monkey]: https://github.com/jonathanpenn/ui-auto-monkey 462 | 463 | ## 统计 464 | 465 | 强烈推荐使用一些统计框架,他们直观地告诉你有多少人用你的应用。X 特性增加了用户么?Y 按钮很难找到么?为了回答这些问题,你可以发送事件,运行时间以及其他记录的信息到一个聚集它们并且可视化它们的服务。比如,[Google Tag Manager][google-tag-manager]。这个是一个比 Google Analytics 更加有用的地方是可以在 app 和统计之间插入数据,所以数据逻辑可以通过 web 服务进行修改,而不用更新你的 app。 466 | 467 | [google-tag-manager]: http://www.google.com/tagmanager/ 468 | 469 | 一个好的实践是创建一个简单的 helper 类,比如 `XYZAnalyticsHelper`,处理 app 内部 model 以及数据格式 (XYZModel, NSTimeInterval, …)的变换,来适配字符串为主的数据层, 470 | 471 | ```objective-c 472 | 473 | - (void)pushAddItemEventWithItem:(XYZItem *)item editMode:(XYZEditMode)editMode 474 | { 475 | NSString *editModeString = [self nameForEditMode:editMode]; 476 | 477 | [self pushToDataLayer:@{ 478 | @"event": "addItem", 479 | @"itemIdentifier": item.identifier, 480 | @"editMode": editModeString 481 | }]; 482 | } 483 | 484 | ``` 485 | 486 | 487 | 另外的优点是,你在必要的时候可以替换整个统计框架,而不用改变 app 其他部分。 488 | 489 | ### Crash Logs 崩溃日志 490 | 491 | 应该让你的 app 向一个服务发送崩溃日志。你可以手动实现,通过 [PLCrashReporter][plcrashreporter] 以及你自己的后端。但是强烈推荐你使用现有的服务,比如下面的 492 | 493 | * [Crashlytics](http://www.crashlytics.com) 494 | * [HockeyApp](http://hockeyapp.net) 495 | * [Crittercism](https://www.crittercism.com) 496 | * [Splunk MINTexpress](https://mint.splunk.com) 497 | 498 | [plcrashreporter]: https://www.plcrashreporter.org 499 | 500 | 当你配置好后,确保你 _保存了 the Xcode archive (`.xcarchive`)_ 对于每一个 app 放出的版本。这个归档中包含了构建的 app 的二进制以及调试符号(`dSYM`),你需要用每个版本特定的 app 把你的 Crash 报告符号化。 501 | 502 | ## 构建 503 | 504 | ### 构建设置 505 | 506 | 每一个简单的 app 都可以不同的方式构建,最基本的分离是 Xcode 给你 _debug_ 和 _release_ 之间的构建方案。后者在编译的时候有更多的优化,可能会导致你需要多调试一些问题。Apple 建议你在开发的时候用 _debug_ 模式,在打包的时候用 _release_ 设置。这是默认的 Scheme(Play 和 Stop 后面的下拉菜单),运行 Run 的时候会用 _debug_ 设置而运行 Archive 的时候会使用 _release_ 。 507 | 508 | 509 | 然后,对于真实的 app 这似乎太简单了。你可能,不,是 [_应该_][futurice-environments]!有不同的测试环境,staging 和其他相关的开发活动。每一个可能有自己的 URL,日志级别,bundle ID)所以你可以一起安装它们,以及描述文件。然后一个简单的 debug/release 区别不能分离这些,你可以在你项目的设置中的 Info 选项卡做更多的编译设置。 510 | 511 | [futurice-environments]: https://blog.futurice.com/five-environments-you-cannot-develop-without 512 | 513 | #### 关于构建设置的 `xcconfig` 文件 514 | 515 | 通常构建设置是 Xcode GUI 定义的,但是你同样可以用 _configuration settings files_ (“`.xcconfig` files”),优点是: 516 | 517 | 518 | - 你可以注释 519 | - 你可以 `#include` 其他构建设置文件, 能帮助你减少重复: 520 | - 如果你有一些适用于所有构建设置的设置, 增加一个 `Common.xcconfig` 并且在其他构建设置文件里面 `#include` 521 | - 如果你,比如,希望有一个 “Debug” 构建设置文件,允许编译器优化,你只需要 `#include "MyApp_Debug.xcconfig"` 来重载其他设置 522 | - 冲突解决和合并变得更轻松 523 | 524 | 更多关于这个主题的信息请看[这个 PPT][xcconfig-slides]。 525 | 526 | [xcconfig-slides]: https://speakerdeck.com/hasseg/xcode-configuration-files 527 | 528 | ### Targets 529 | 530 | 一个 target 是处于比项目更低一级的级别。比如,一个项目可能有多个 target,可能重载它的项目设置。简单地说,每个 target 和一个 app 相当。比如,你可能有几个因为国家区分的 app(从同样的代码编译)来提交到 App Store。每个会有 development/staging/release 的构建,所以最好通过构建设置而不是 target 来区分。一个 app 只有一个 target 是很少见的。 531 | 532 | ### Schemes 533 | 534 | Schemes 告诉 Xcode 在你点击 Run, Test, Profile, Analyze 或者 Archive 操作的时候应该怎么做。它们把这些操作映射到一个 target 和一个构建设置中。你可以传递启动参数,比如 app 需要运行的语言(为了测试本地化)或者一些了为了调试用的诊断标志。 535 | 536 | 537 | 一个建议的 Scheme 命名是 `MyApp () [Environment]`: 538 | 539 | MyApp (English) [Development] 540 | MyApp (German) [Development] 541 | MyApp [Testing] 542 | MyApp [Staging] 543 | MyApp [App Store] 544 | 545 | 对于大多数环境来说语言部分是不必要的,app 会可能会以非 Xcode 的方式安装,比如用 TestFlight, 启动参数会被忽略。这个情况下,为了测试本地化需要手动设置设备的语言。 546 | 547 | ## 部署 548 | 549 | 550 | 部署一个软件到 iOS 设备上并不直观。但是有一些核心观点,只要理解了,对你有很大的帮助。 551 | 552 | ### 签名 553 | 554 | 当你需要在真实设备上运行软件的时候,你需要用一个 Apple 认证的 __证书__ 签名。每一个证书是连接到一个公、私密钥对,私钥会保存在你的 Mac 的 KeyChain 里面,证书有两种类型 555 | 556 | * __开发证书:__ 每个组的开发者都有自己的证书,而且它通过请求特到。Xcode 可以帮你完成,但是最好不用点击 "Fix issue" 来完成,而是理解它到底做了什么事情。在部署开发版本到设备上的时候需要这个证书。 557 | * __发布证书:__ 可以有多个,但是最好每一个组织有一个,并且通过内部渠道共享。在提交 App 到 App Store 或者你的企业的内部 App Store 的时候需要这个证书。 558 | 559 | ### 描述文件 560 | 561 | 562 | 除了证书,还有 __描述文件__ , 它把设备和证书连接起来。而且,它分成开发和发布两种类型。 563 | 564 | 565 | * __Development provisioning profile__: 开发描述文件, 它包含了一个包含所有能安装这个 app 的设备列表。它连接了一个或者多个开发者允许这个描述文件使用的证书。描述文件可以确认特定的 app,但是对于大多数开发目的,它特别适合用一个通配符描述文件,也就是 Apple ID 以一个星号 (*) 结尾的。 566 | 567 | 568 | * __Distribution provisioning profile:__ 发布描述文件本身,对于不同使用目的也有不同的类型。每一个发布描述文件链接到一个发布证书,并且在证书过期的时候失效。 569 | * __Ad-Hoc:__ 就像开发证书一个,它包含了一个 app 可以安装的设备的白名单。这个描述文件可以用来做 beta 版本,每年可以测试 100 个设备。为了做更细致的测试以及升级到 1000 个测试用户,你可以使用 Apple 最近发布的 [TestFlight][testflight] 服务,Supertop 提供了一个 [summary of its advantages and issues][testflight-discussion]. 570 | * __App Store:__ 这个 profile 没有列出设备,任何人都可以通过苹果官方的发布来安装,所有 App Store 发布的 App 都需要这个证书 571 | * __Enterprise:__ 就像 App Store,没有设备白名单,任何通过企业内部网络的人都可以从内部“应用商店”安装。应用商店可能只是一个带连接的网站。这个描述文件只允许企业账号使用。 572 | 573 | 574 | 575 | 576 | [testflight]: https://developer.apple.com/testflight/ 577 | [testflight-discussion]: http://blog.supertop.co/post/108759935377/app-developer-friends-try-testflight 578 | 579 | 想同步所有的证书和描述文件到你的机器,可以去 Xcode 的 Preferences 的 Accounts 下,添加你的 Apple ID,然后双击你的 Team 名称。然后底部会有一个刷新按钮,有时候你需要重启 Xcode 来让东西显示出来。 580 | 581 | #### 调试描述文件 582 | 583 | 584 | 585 | 有时候你需要调试一个描述文件的问题。比如,Xcode 可能拒绝安装 app 到一个设备中,因为设备没有在开发或者 Ad-doc 发布的的描述文件设备列表中。这个情况下,你可以使用 Craig Hockenberry 的 [Provisioning][provisioning] 插件,浏览 `~/Library/MobileDevice/Provisioning Profiles`,选择一个 `.mobileprovision` 文件,并用空格键调用 Finder 的快速查看特性,它会告诉你关于设备,entitlements,证书以及 Apple ID 的信息。 586 | 587 | [provisioning]: https://github.com/chockenberry/Provisioning 588 | 589 | ### 上传 590 | 591 | 592 | [iTunes Connect][itunes-connect] 是你管理上传到 App Store 的 App 的后台网站。 Xcode 6 需要一个 Apple ID 作为开发者账号来签名并且上传二进制。如果你有多个开发者账号而且要上传它们的 app 就需要一点技巧。 因为 _一个 Apple ID 只能和一个 iTunes Connect 账号关联_ ,一个变通方案是为每个 iTunes Connect 账号创建一个新的 Apple ID,使用 Application Loader 取代 Xcode 来上传应用。这样有效地解除了上传最终 `.app` 文件构建和签名的耦合。 593 | 594 | 在上传二进制后,耐心等待,可能要花上一个小时。当你的 App 出现后,可以链接到对应的 App 版本并且提交审核 595 | 596 | [itunes-connect]: https://itunesconnect.apple.com 597 | 598 | ## 应用内购买 599 | 600 | 当验证一个 App 内购的 receipt 的时候,记住做以下步骤: 601 | 602 | - __Authenticity:__ receipt 是来自 Apple 的 603 | - __Integrity:__ receipt 没有被篡改 604 | - __App match:__ receipt 的 App bundle ID 和你的 App bundle ID 一致 605 | - __Product match:__ receipt 里面产品 ID 和你期望的一致 606 | - __Freshness:__ 你之前没有验证一样的 receipt ID 607 | 608 | 609 | 如果可能,把你的 IAP 存储销售相关的内容存储在服务器端,并且只在一个合法的经过上述检查的 receipt。这样的设计避免了常见的盗窃机制,同时,因为服务器做了验证,所以你可以使用 Apple 的 HTTP 验证服务来取代你自己的 `PKCS #7` / `ASN.1` 格式。 610 | 611 | 更多关于这个主题的信息可以看 [Futurice blog: Validating in-app purchases in your iOS app][futu-blog-iap] 612 | 613 | [futu-blog-iap]: http://futurice.com/blog/validating-in-app-purchases-in-your-ios-app 614 | 615 | [reactivecocoa-github]: https://github.com/ReactiveCocoa/ReactiveCocoa 616 | --------------------------------------------------------------------------------