iOS开发规范

iOS开发规范

目录

编写目的

制定开发规范,可以在团队内部形成统一的开发习惯,减少协作的理解成本。
此开发规范主要制定了以下方面的规范:

1. 统一开发环境、代码格式、注释习惯、命名规则;
2. 推荐常见性能优化解决方案;
3. 规范化 SVN/GIT 提交习惯;

适用范围

1. 客户端研发组,iOS 开发需要遵守此开发规范。
2. 客户端研发组的其他平台开发人员,在 GIT 提交习惯方面可以参考此规范。

开发环境

下载更新地址:
AppStore

编程规约

一、命名规范

统一要求

含义清楚,尽量做到不需要注释也能了解其作用,若做不到,就加注释。
使用全称,不适用缩写

类的命名

大驼峰式命名:每个单词的首字母都采用大写字母
例子:RSHomeController

后缀要求
ViewController: 使用Controller做后缀
例子: RSHomeController

View: 使用View做后缀
例子: JLBAlertView

UITableCell:使用Cell做后缀
例子: OrderListTitleCell

Protocol: 使用Delegate或者DataSource作为后缀
例子: UITableViewDelegate

UI控件依次类推

私有变量

小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写。
例子:firstName、lastName

以下划线 _ 开头,第一个单词首字母小写
例子:NSString * _myPrivateVariable

私有变量放在 .m 文件中声明

property变量

小驼峰式命名:

例子:
/**注释*/
@property (nonatomic, copy) NSString *userName;

注:禁止使用synthesize关键词

宏命名

全部大写,单词间用 下划线_ 分隔。[不带参数]
例子: #define THIS_IS_AN_MACRO  @"THIS_IS_AN_MACRO"

以字母 k 开头,后面遵循大驼峰命名。[不带参数]
例子:#define kScreenHeight  ([UIScreen mainScreen].bounds.size.height)

小驼峰命名。[带参数]
例子:#define rgbColor(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]

Enum

Enum类型的命名与类的命名规则一致

Enum中枚举内容的命名需要以该Enum类型名称开头

例子:

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
     AFNetworkReachabilityStatusUnknown = -1,
     AFNetworkReachabilityStatusNotReachable = 0,
     AFNetworkReachabilityStatusReachableViaWWAN = 1,
     AFNetworkReachabilityStatusReachableViaWiFi = 2
};

位掩码

当用到位掩码时,使用 NS_OPTIONS 宏。

举例:

typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
    NYTAdCategoryAutos      = 1 << 0,
    NYTAdCategoryJobs       = 1 << 1,
    NYTAdCategoryRealState  = 1 << 2,
    NYTAdCategoryTechnology = 1 << 3
};

Delegate命名

  • 类的实例必须为回调方法的参数之一, 如:
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

  • 回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
  • 以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  • 使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:
-(NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;

-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;

切图命名规范

为了降低设计和开发之间的交流成本,所以在此确定一下 iOS 项目切图的命名标准。

我们的命名规则的基本思想是把文件名分成三部分,第一部分是图片的逻辑归属分类,第二部分是图片的表现内容,第三部分是图片的内容的类型,有些图片还会有第四部分,表示图片表现的状态。

首先有几个规则是:

1. 用英文命名,不用拼音
2. 每一部分用下划线分隔
3. 图片名中两倍图在名字最后要加@2x,三倍图在名字最后要加@3x

逻辑分类

逻辑分类即是这张图片所属的分组,在 iOS 中大多的项目是以 Tab bar 的形式进行逻辑上的分组,以下图为例:

[图片上传失败...(image-60ebae-1519622380933)]

这张图中的 Showtime 客户端就是使用 Tab bar 进行分组的,可以看出内容分成了五个部分:HomeCategoriesLive TVMy ListSearch

所以例如在这个界面中,右上角的设置按钮,那么它的切图命名的 第一部分就是 Home

但是仔细看看上面这个界面的话有一些图其实是不属于某个分类的,比如 Tab bar 中的图标文件和 Navigation Bar 中的 Showtime 图标,对于这两种内容来讲,它们命名的规范是第一部分显示的是 navigationbar 或者是 tabbar

表现内容

表现内容就是这个图片表现的内容,同样以上图为例,界面中右上角的按钮表现的是设置按钮,那么它的表现内容就是设置,所以这个按钮在第二部分显示的就是 settings

而在上图的 Tab bar 中第二部分的名字就是它们在程序中显示的名字 ,而 Navigation Bar 中的图标的第二部分命名因为表现的内容就是 Showtime,所以显示的就是 showtime

内容类型

同样以 Showtime 的图为例,在 Navigation Bar 中的图标,它的内容类型是 icon,所以这个图片文件的两倍图完整命名就是 navigationbar_showtime_icon@2x.png
而右上角的设置按钮的话,它的类型是按钮,所以第三部分即是 button 的缩写 btn,即是 navigationbar_settings_button,下面五个 Tab bar 的图片名分别是 tabbar_home_icon,tabbar_categories_icon,tabbar_livetv_icon,tabbar_mylist_icontabbar_search_icon

图片状态

对于某些类型的切图,它可能代表的只是某个控件的一种状态,以按钮为例,正常的状态就是 normal,而点击中的状态是 highlighted,选中的状态则是 selected
Showtime 的图中,右上角的设置图标是正常的状态,所以加入图片的状态之后,它的两倍图的完整命名应该是 navigationbar_settings_btn_normal@2x.png
而下方的 Tab bar 来讲,选中的是 Home,那么当前这张图片的两倍图的完整命名就应该是 tabbar_home_icon_selected@2x.png

切图规则

我们都知道一套完整的 App 通常会有很多张切图,不管是 iPhone 需要 1x、2x、3x 图档,Android 需要至少 3 种 hdpi、xhdpi、xxhdpi。

在庞大的数量下如何让负责套图的 RD 快速找到所需图档,档名的命令方式就需要双方统一格式方便大家作业。

所以,制定一套非常有效而方便的APP切图命名规范非常有用的。

目前iPhone有10种型号,5种屏幕尺寸,再加上6plus的“降采样”(Downsampling)(1080-1920),还有iPhone6和6+上的放大模式(1125-2001)和默认模式(1242-2208),是不是感觉好恐怖?

但是不用怕,我分享一套超简单的适配方法,看完你都不信有这么简单~

美术交付给开发的资料有

  • 标注图(以640为宽度尺寸为基准标注)

  • 2x切图(以640为宽度尺寸为基准切图)

  • 3x切图(以1280为宽度尺寸为基准切图)

[图片上传失败...(image-e8f09a-1519622380933)]

开发看到这份标注图,可以自己用上面的数字,乘以1.5得出3X的数字。

为什么3x切图要以1280来为宽度?

其实iPhone6+的尺寸1242*2208作为3X,怎么算都又难记又不能整除,
我们直接640*2得到1280跟1242相差也没几十个像素,最重要的是不虚
边啊,放在真机上看(处女座除外)看不出差别的。

为什么只设宽度?

为了保持长宽比例。iPhone的这几个尺寸不是正好的,宽度放大后高度总差那么几个像素,这不要紧,千万别去改高度,
手机屏幕是可以上下滑动的嘛。不可以滑动必须保证一屏显示的除外,手动去调整好了。

为什么开发不是乘以2而是乘以1.5来算尺寸和字号?

因为大屏手机就是要显示更多内容而存在的。
纯等比放大界面看起来傻大傻大的,实验证明1.5倍是正好的。

二、私有方法及变量声明

声明位置</h>

  • 在.m文件中最上方,定义空的category进行声明

例子:

@interface OrderListTitleFrame ()
// 属性
@property (nonatomic, strong) UIBarButtonItem *backBtn;

// 私有方法
- (CGFloat)cellHeight;

@end

@implementation OrderListTitleFrame

// 私有方法的实现
- (CGFloat)cellHeight
{
    //some code
} 
@end

点语法

应该始终使用 点语法 来访问或者修改属性,访问其他实例时首选括号。

推荐:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

反对:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

条件判断

条件判断主体部分应该始终使用大括号括住来防止出错,即使它可以不用大括号(例如它只需要一行)。这些错误包括添加第二行(代码)并希望它是 if 语句的一部分时。还有另外一种更危险的,当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。此外,这种风格也更符合所有其他的条件判断,因此也更容易检查。

推荐:

if (!error) {
    return success;
}

反对:

if (!error)
    return success;
    
或者:

if (!error) return success;    

三目运算符

三目运算符,? ,只有当它可以增加代码清晰度或整洁时才使用。单一的条件都应该优先考虑使用。多条件时通常使用 if 语句会更易懂,或者重构为实例变量。

推荐:

result = a > b ? x : y;

反对:

result = a > b ? x = c > d ? c : d : y;

错误处理

当引用一个返回错误参数(error parameter)的方法时,应该针对返回值,而非错误变量。

推荐:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 处理错误
}

反对:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // 处理错误
}

变量

变量名应该尽可能命名为描述性的。除了 for() 循环外,其他情况都应该避免使用单字母的变量名。星号表示指针属于变量,例如:NSString *text 不要写成 NSString* text 或者 NSString * text,常量除外。

尽量定义属性来代替直接使用实例变量。除了初始化方法init, initWithCoder:等,dealloc方法和自定义的settersgetters内部,应避免直接访问实例变量。

推荐:

@interface NYTSection: NSObject

@property (nonatomic) NSString *headline;

@end

反对:

@interface NYTSection : NSObject {
    NSString *headline;
}

变量限定符

当涉及到在 ARC 中被引入变量限定符时,
限定符 (__strong, __weak, __unsafe_unretained, __autoreleasing) 
应该位于星号和变量名之间,如:NSString * __weak text。

init 和 dealloc

dealloc方法应该放在实现文件的最上面,并且刚好在 @synthesize@dynamic语句的后面。在任何类中,init都应该直接放在dealloc方法的下面。

init方法的结构应该像这样:

- (instancetype)init {
    self = [super init]; // 或者调用指定的初始化方法
    if (self) {
        // Custom initialization
    }

    return self;
}

字面量

每当创建 NSStringNSDictionaryNSArray,和 NSNumber 类的不可变实例时,都应该使用字面量。要注意 nil 值不能传给 NSArrayNSDictionary 字面量,这样做会导致崩溃。

推荐:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

反对:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

CGRect 函数

当访问一个 CGRectx, y, width, height 时,应该使用 CGGeometry 函数代替直接访问结构体成员。苹果的 CGGeometry 参考中说到:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

所有功能描述在这个参考CGRect中隐式数据结构作为输入规范那些矩形之前计算结果。由于这个原因,您的应用程序应该避免直接阅读和写作在CGRect中存储的数据的数据结构。相反,使用这里描述的函数来操纵矩形和检索他们的特征。

推荐:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

反对:

CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;

常量

常量首选内联字符串字面量或数字,因为常量可以轻易重用并且可以快速改变而不需要查找和替换。常量应该声明为 static 常量而不是 #define ,除非非常明确地要当做宏来使用。

推荐:

static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";

static const CGFloat NYTImageThumbnailHeight = 50.0;

反对:

#define CompanyName @"The New York Times Company"

#define thumbnailHeight 2

布尔

因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。

这使得整个文件有更多的一致性和更大的视觉清晰度。

推荐:

if (!someObject) {
}

反对:

if (someObject == nil) {
}

对于 BOOL 来说, 这有两种用法:

if (isAwesome)
if (![someObject boolValue])

反对:

if ([someObject boolValue] == NO)
if (isAwesome == YES) // 永远别这么做

如果一个 BOOL 属性名称是一个形容词,属性可以省略 is 前缀,但为 get 访问器指定一个惯用的名字,例如:

@property (assign, getter=isEditable) BOOL editable;

内容和例子来自 Cocoa 命名指南

单例

单例对象应该使用线程安全的模式创建共享的实例。

+ (instancetype)sharedInstance {
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

这将会预防有时可能产生的许多崩溃。

类构造方法

当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。

例子:

@interface Airplane  
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;  
@end  

三、注释规范

最好的代码是不需要注释的,尽量通过合理的命名、良好的代码把含义表达清楚,在必要的地方添加注释,注释需要与代码同步更新,如果做不到命名尽量的见名知意的话,就可以适当的添加一些注释或者mark

属性注释

例子:

/**学生*/
@property (nonatomic, strong) Student *student;

方法声明注释:

/**
 订单列表页接口

 @param psize [可选]: 每页条数
 @param page [可选]: 页数
 @param control 当前类
 @param success 成功回调
 @param failure 失败回调
 */
+ (void)getOrderListDataWithPsize:(NSInteger)psize
                             page:(NSInteger)page
                          control:(NSObject *)control
                          success:(void (^)(NSArray *dataArray))success
                          failure:(void (^)(NSError *error))failure;

导入

如果有一个以上的 import 语句,就对这些语句进行分组。每个分组的注释是可选的。

注:对于模块使用 @import 语法。

// Frameworks
@import QuartzCore;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

四、代码格式化

把代码风格统一化,可以让代码更加易读,降低后续维护成本。

指针 * 位置

  • 定义一个对象时,指针 "*" 靠近变量
例子: NSString *userName;

方法的声明和定义

  • 在 - 、+ 和 返回值 之间留一个空格,方法名和第一个参数之间不留空格

例子:

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil
{
    // some code
}

代码缩进

  • 使用 xcode 默认缩进,即 tab = 4空格
  • 使用 xcode 中 re-indent 功能定期对代码格式进行整理
  • 相同类型变量声明需要独行声明

例子:

CGFloatoringX = frame.origin.x;
CGFloatoringY = frame.origin.y;
CGFloatlineWidth = frame.size.width;
  • Method与Method之间空一行

例子:

 #pragma mark - private methods
 
 - (void)samplePrivateMethod
 {...}
 
 - (void)sampleForIf
 {...}

对method进行分组

  • 使用 #pragma mark - 方式对类的方法进行分组

例子:

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

#pragma mark - 静态成员的定义
static NSString * const XYZFooStringConstant = @"FoobarConstant";
static CGFloat const XYZFooFloatConstant = 1234.5;

@interface XYZFooViewController () <XYZBarDelegate>
#pragma mark - 私有成员的定义
@property (nonatomic, copy, readonly) Foo *foo;
@end

@implementation XYZFooViewController

#pragma mark - Lifecycle ViewController生命周期
- (instancetype)initWithFoo:(Foo *)foo;
- (void)dealloc;

#pragma mark - View Lifecycle view 页面 生命周期 
- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;

#pragma mark - prepareData 准备数据

- (void)prepareData;

#pragma mark - Layout UI准备和布局相关
- (void)prepareUI;
- (void)makeViewConstraints;

#pragma mark - Public Interface 公共接口
- (void)startFooing;
- (void)stopFooing;

#pragma mark - User Interaction 用户交互 按钮事件相关
- (void)foobarButtonTapped;

#pragma mark - XYZFoobarDelegate Delegate代理 
- (void)foobar:(Foobar *)foobar didSomethingWithFoo:(Foo *)foo;

#pragma mark - Internal Helpers 内部公用方法
- (NSString *)displayNameForFoo:(Foo *)foo;

@end

大括号写法

  • 对于类的method: 左括号另起一行写(遵循苹果官方文档)

例子:

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil

     bundle:nibBundleOrNil];

     if (self) {
           // Custom initialization
       }

     return self;
}
  • 对于其他使用场景: 左括号跟在第一行后边

例子:

- (void)sampleForIf
{
    BOOL someCondition = YES;
    if(someCondition) {
        // do something here
    }
}

- (void)sampleForWhile
{
    int i = 0;
    while (i < 10) {
        // do something here
        i = i + 1;
    }
}

- (void)sampleForSwitch
{
    SampleEnum testEnum = SampleEnumTwo;
    switch(testEnum) {
        caseSampleEnumUndefined:{
            // do something
            break;
        }
        caseSampleEnumOne:{
            // do something
            break;
        }
        caseSampleEnumTwo:{
            // do something
            break;
        }
        default:{
            NSLog(@"WARNING: there is an enum type not handled properly!");
            break;
        }
}
  • 任何需要写大括号的部分,不得省略

错误示例:

- (void)wrongExamples
{
    BOOLsomeCondition = YES;
    if (someCondition)
        NSLog(@"this is wrong!!!");
    while(someCondition)
        NSLog(@"this is wrong!!!");
}

Block写法

根据block的长度,有不同的书写规则:

  • 较短的block可以写在一行内。
  • 如果分行显示的话,block的右括号}应该和调用block那行代码的第一个非空字符对齐。
  • block内的代码采用4个空格的缩进。
  • 如果block过于庞大,应该单独声明成一个变量来使用。
    ^(之间,^{之间都没有空格,参数列表的右括号){之间有一个空格。
//较短的block写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];

//分行书写的block,内部使用4空格缩进
[operation setCompletionBlock:^{
    [self.delegate newDataAvailable];
}];

//使用C语言API调用的block遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
    NSString* path = [self sessionFilePath];
    if (path) {
      // ...
    }
});

//较长的block关键字可以缩进后在新行书写,注意block的右括号}和调用block那行代码的第一个非空字符对齐
[[SessionService sharedService]
    loadWindowWithCompletionBlock:^(SessionWindow *window) {
        if (window) {
          [self windowDidLoad:window];
        } else {
          [self errorLoadingWindow];
        }
}];

//较长的block参数列表同样可以缩进后在新行书写
[[SessionService sharedService]
    loadWindowWithCompletionBlock:
        ^(SessionWindow *window) {
            if (window) {
              [self windowDidLoad:window];
            } else {
              [self errorLoadingWindow];
            }
}];

//庞大的block应该单独定义成变量使用
void (^largeBlock)(void) = ^{
    // ...
};

[_operationQueue addOperationWithBlock:largeBlock];

//在一个调用中使用多个block,注意到他们不是像函数那样通过对齐的,而是同时进行了4个空格的缩进
[myObject doSomethingWith:arg1
    firstBlock:^(Foo *a) {
        // ...
    }
    secondBlock:^(Bar *b) {
        // ...
}];
    

工程规范

一、Xcode工程规范

目录结构参考

  • 为了避免文件杂乱,物理文件应该保持和 Xcode 项目文件同步。
  • Xcode 创建的任何组(group)都必须在文件系统有相应的映射。
  • 为了更清晰,代码不仅应该按照类型进行分组,也可以根据功能进行分组。
    Class/     
        ├─ Main
            ├─  Category            分类
            ├─  Controller           根控制器,
            ├─  Lib                 第三方库
            ├─  Other               配置文件和APPdelegate
            ├─  Tool                工具类
            ├─  Model               公共模型
            ├─  View                公共视图
        ├─ module1               功能模块1
            ├─ model               模型
            ├─ Controllers         控制器
            ├─ Views               视图
            ├─ Tool                工具类
        ├─ module2               功能模块2
            ├─ model               模型
            ├─ Controllers         控制器
            ├─ Views               视图
            ├─ Tool                工具类      

编译警告

简单的来说,至少需要在 _“Other Warning Flags” 编译设置里面定义下面的值:

-Wall _(增加很多的警告)_
-Wextra _(增加更多的警告)_
同时打开 “Treat warnings as errors”

Assets资源

  • Asset catalogs 是管理你所有项目可视化资源的最好方式,它们可以同事管理通用的以及设备相关的iPhone 4-inch, iPhone Retina, iPad等资源,并且会自动通过它们的名字分组。

  • 告诉你的设计师如何添加它们(Xcode有内置的 Git 客户端)可以节省很多时间,否则你会话很多时间来在从邮件或者其他渠道复制到代码库里面。它可以让设计师马上尝试资源改变的样子,并且反复实验。

Using Bitmap Images使用位图

  • Asset catalogs 仅仅暴露了图片的名称,图片集里面的抽象的名字。这可以避免资源名字的冲突,就像 button_large@2x.png 的文件的命名空间在它的图片集里面。遵守一些命名规则可以让生活更美好:
IconCheckmarkHighlighted.png // Universal, non-Retina
IconCheckmarkHighlighted@2x.png // 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

修饰后缀 -568h, @2x, ~iphone and ~ipad 是不必要的,但是有他们在文件里面,当把文件拖进去的时候,Xcode会正确地处置它们。这避免赋值错误。

使用向量图

  • 你可以用设计师原始的 vector graphics (PDFs) 加入到 asset catalogsXcode可以自动地根据它们生成位图。这减少了你的工程的复杂性(管理更少的文件)。

调试

  • 当你的 App 崩溃的时候,Xcode 不会默认进入到调试器里面。

  • 为了调试,你需要增加一个异常断点(在 XcodeDebug 导航中点 +),来在异常发生的时候退出执行。在很多情况下,你需要看看触发这些异常的代码。

  • 它会捕捉任何异常,即使是已经处理的。如果 Xcode 在 一个第三方库里面中断执行,比如,你可能需要通过选择 Edit Breakpoint 并且设置 ExceptionObjective-C

  • 对于视图 debugRevealSpark Inspector 这两个强有力的可视化检查工具可以帮你省下很多时间,特别是在你使用 Auto Layout 并且希望定位出问题或者溢出屏幕的视图的时候。Xcode 提供了免费的类似功能 ,但是只能适用于 iOS 8+ 并且不那么好用。

二、性能分析

Instruments分析

  • Xcode 有一个叫 Instruments 的分析工具,它包括了许多分析内存,CPU,网络通讯,图形以及更多的工具,它有点复杂的,但是它的追踪内存泄漏的时候还是蛮直观的。

  • 只需要在 Xcode 中 选择 Product > Profile,选择 Allocations, 点击 Record 按钮并且用一些有用的字符串过滤申请空间的信息,比如你自己的app的类名。

  • 它会在固定的列中统计,并且告诉你每个对象有多少实例。到底是什么类一直增加实例导致内存泄漏。

  • Instruments 也有自动化的工具来进行录制并且运行UI交互以及JavaScript文件。

  • UI Auto Monkey 是一个自动化随机点击、滑动以及旋转你的app的脚本,他在压力、渗透测试中很有用。

Clang静态分析

  • Clang 编译器(Xcode使用的)有一个 静态分析器 来进行你的代码控制和数据流的分析,来检测编译器不能检测的许多错误。
  • 你可以通过在 Xcode 里面手动运行 Product → Analyze 菜单项来手动执行代码分析
  • 分析器可以用浅或者深的模式允许,后者更加慢,但是可以从跨函数的控制流和数据流上分析更多问题

推荐

  • 打开 所有 分析器检查 (通过在 building setting 中打开所有 Static Analyzer 选项)
  • release 的编译设置里面打开 Analyze during 'Build' 来让分析器自动在发布的版本构建的时候允许。(这样你就不需要记住要手动运行了)
  • Mode of Analysis for ‘Analyze’ 设置为 Shallow (faster)
  • Mode of Analysis for ‘Build’ 设置为 to Deep

Faux Pas

  • 我们自己的Ali Rantakari 创建的,Faux Pas 是一个极佳的静态错误检测工具,它分析你的代码并且找出那些你自己甚至都没发现的问题。在提交你的 App 到应用商店前用它吧!

三、日志处理

Crash 崩溃查看

参考 http://www.jianshu.com/p/e428501ff278
总结作者 https://github.com/ZHThinker

  • 我们每次打包产品交付给测试部门时,通常会运行archive编译输出,输出后会产生.app 和 .dSYM文件,我们会将.app交付给测试部门。
  • 测试部门在测试时遇到崩溃,通常会给我们IPS为扩展名的崩溃日志文件,我们应该怎样查看这个文件呢?

第一步

  • 准备一个 symbolicatecrash 文件
  • symbolicatecrash文件地址命令:
find /Applications/Xcode.app -name symbolicatecrash -type f 

运行后输出的是symbolicatecrash文件的地址,复制 .symbolicatecrash 文件出来到桌面crash文件夹内,地址是: /Users/zhang/Desktop/crash/

第二步

  • 将.app和.dSYM以及.symbolicatecrash全部都放到一个文件夹 /Users/zhang/Desktop/crash/ 将IPS文件更该扩展名为.crash也存放在一起
  • 如果以下4个文件都准备好了之后
    .app、 .crash、 .dSYM、 .symbolicatecrash

第三步

  • 执行(注意路径要根据自己mac 的情况进行修改,文件名字也要修改)
./symbolicatecrash /Users/xxx/Desktop/crash/IXQ-2017-01-05-170207.crash /Users/xxx/Desktop/crash/IXQ.app.dSYM > Control_symbol.crash
  • 如果命令执行后报错
Error: "DEVELOPER_DIR" is not defined at /usr/local/bin/symbolicatecrash line 53
  • 执行以下命令:
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

第四步

  • 再次执行第三步,会得到一个Control_symbol.crash 文件,此文件为crash 日志文件,可查看详细的崩溃信息

Crash Logs崩溃日志

应该让你的 app 向一个服务发送崩溃日志。你可以手动实现,通过 PLCrashReporter 以及你自己的后端。但是强烈推荐你使用现有的服务,比如下面的

当你配置好后,确保你 保存了 the Xcode archive (.xcarchive) 对于每一个 app 放出的版本。

这个 归档中包含了构建的app的二进制以及调试符号dSYM,你需要用每个版本特定的app把你的 Crash 报告符号化。

Crash log的获取

当你的app在手机上crash的时候,会在手机上自动生成一个崩溃日志,也就是我们说的Crash Log
CrashLog的位置位于:
iPhone设备的var/mobile/Library/Logs/CrashReporter
我们要获取的就是设备中的这个CrashLog

获取用户的Crash log

注意。这里的用户指的是你的app已经上架到AppStore上后的用户。

作为开发者,你想要获取到你的用户的崩溃日志的话就得通过 iTunes Connect 了。在 iTunes Connect 上的 Manage Your Applications -> View Details -> Crash Reports

这种方式有个前提,就是用户设备同意上传相关信息,打开了诊断与用量这个选项设置->隐私->诊断与用量

获取测试机的Crash log

很多测试人员在测试途中,或者开发者在自测的途中,会遇到APP crash的情况。

一般的bug,一个合格的测试可以给出明确的重现步骤让开发者清晰地知道bug原因;

也有不少bug,很多时候是偶现的,很可能无法再次重现出来,无法重现出来的bug是开发者头疼的,测试一般会给出bug的截图和重现步骤;

而一般crash是比较严重的问题了(所以千万不能当什么都没发生过,不然会被打的233),这个时候崩溃日志就尤为重要了,把崩溃日志send给开发人员,如此才能让开发者快速定位到错误的原因和位置。

那么测试如何拿到crash日志呢?

方法一:连接电脑,通过iTools高级选项来获取崩溃日志(Mac版的找不到高级选项T.T,望赐教补充)

方法二:连接电脑,去本地目录找
Mac : ~/Library/Logs/CrashReporter/MobileDevice/<DEVICE_NAME>

Windows : C://Users/<USERNAME>/AppDataRoamingApple/ComputerLogsCrashReporterMobileDevice/<DEVICE_NAME>/

这个时候你会发现一大堆的.crash文件和.ips文件

方法三:通过Xcode获取到崩溃日志,方法是Xcode->Window->Devices

Crash Log的符号化

获取到了.crash或者.ips文件的时候(别纠结这两个文件有什么差,改下后缀名就ok),用文本编辑器打开文件是一堆十六进制的内存地址,你会郁闷的发现压根看不懂。

Q:十六进制内存地址可以改成看得懂的么?

A:当然,将这些十六进制地址转化成方法名称和行数的过程称之为Symbolication(符号化)。符号化很简单,只要你把你的.crash文件拉到上面提到过的Xcodedevice log里面,然后几秒钟后就会符号化。但是这里有个前提,就是这个发生crash的版本包必须是你自己的Xcode里面Archive出来的(这个是苹果自带的方法,会自动检测是否含有匹配的.dSYM文件和应用二进制文件)。

Crash Log的分析

接下来就让我们对已经符号化以后的crash文件进行分析。
网上已有的分类比较多,我这里直接把我目前一般找crash原因的模块展示出来,其他的就留待各位自己去研究了,分别是设备和crash信息、异常信息、线程信息

1、首先是设备和crash信息

Incident Identifier: F3573A...E2F244A              //crash的id
CrashReporter Key:   cc2298...es77eeb              //crash的设备id
Hardware Model:      iPhone7,2                     //手机型号
Process:             [AppName] [1816]              //APP的名字[进程的id]
Path:                /private/.../Application...   //APP的位置
Identifier:          com....                       //bundle ID
Version:             14 (2.3.5)                    //版本号
Code Type:           ARM-64 (Native)               //app的应用架构之类不大清楚,^_^
Parent Process:      launchd [1]

Date/Time:           2015-10-26 15:03:29.29 +0800    //crash发生时间
Launch Time:         2015-10-26 14:58:28.28 +0800    //进入应用时间
OS Version:          iOS 9.1 (13B143)                //iOS版本
Report Version:      105

当你有大量的crash文件的时候,你就可以对crash文件里面的 Hardware ModelVersionOS Version等进行分类,就可以获知到很多信息,比如说,你会知道crash一般发生原因是因为手机型号,还是App版本,或者还是手机版本的原因。

2、其次是异常信息

Exception Type:  EXC_BAD_ACCESS (SIGABRT)                      //异常的类型
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000118  //异常子类型
Triggered by Thread:  0                    //异常发生的线程(0为主线程,其他为子线程)

3、线程信息

Last Exception Backtrace:
0   CoreFoundation                    0x182780f48 __exceptionPreprocess + 124
1   libobjc.A.dylib                   0x197333f80 objc_exception_throw + 56
2   CoreFoundation                    0x182780e90 +[NSException raise:format:] + 120
3   [AppName]                            0x100c42a40 UmengSignalHandler + 144
4   libsystem_platform.dylib          0x197d6193c _sigtramp + 52
5   [AppName]                            0x1005d9f38 CScopePtr<IAVGAudioLogic>::operator IAVGAudioLogic*<IAVGAudioLogic>() (xprefc.h:165)
6   [AppName]                            0x1005d3b8c tencent::av::AVRoomMultiImpl::GetAudioLogic() (av_room_multi_impl.h:119)
7   [AppName]                            0x10057076c tencent::av::AVAudioCtrlImpl::SetAudioOutputMode(int) (av_audio_ctrl_impl.cpp:443)
8   [AppName]                            0x10044dc3c -[AVBasicManager changeSpeakerMode:] (AVManager.mm:525)
9   [AppName]                            0x100296e1c -[KTQAVRoom enableSpeakerMode:] (KTQAVRoom.m:345)
10  [AppName]                            0x1002970d0 -[KTQAVRoom settingSpeaker:] (KTQAVRoom.m:362)
11  [AppName]                            0x1003d5464 -[KTChatView onAudioNotificationReceived:] (KTChatView.m:685)

恩。。。这符号化以后应该可以看懂了吧,这个crash的问题应该是腾讯第三方的一个冲突

一般来说,通过异常信息和线程信息就可以找到crash的原因了。

补充一些异常类型信息

这里参考了很多信息,有很多的异常类型,有些没遇到过,这里就厚颜摘抄过来了(这里是原文地址:iOS Crash文件的解析,再次感谢大牛们的经验)

1、Exception Type

1)EXC_BAD_ACCESS

此类型的Excpetion是我们最长碰到的Crash,通常用于访问了不改访问的内存导致。一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。

  • SIGSEGV: 通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。
  • SIGABRT: 收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。
  • SEGV:(Segmentation Violation),代表无效内存地址,比如空指针,未初始化指针,栈溢出等;
  • SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)
  • SIGILL:尝试执行非法的指令,可能不被识别或者没有权限

2)EXC_BAD_INSTRUCTION

此类异常通常由于线程执行非法指令导致

3)EXC_ARITHMETIC

除零错误会抛出此类异常

2、Exception Code

0xbaaaaaad 此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发
0xbad22222 当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序
0x8badf00d 程序启动或者恢复时间过长被watch dog终止
0xc00010ff 程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止
0xdead10cc 程序退到后台时还占用系统资源,如通讯录被系统终止
0xdeadfa11 前面也提到过,程序无响应用户强制关闭。

总结

最后总结一些可能会对各位有用的博文:

  1. iOS应用崩溃日志分析(这最后有一个栗子很有意思)
  2. 获取 iOS crash log(分析得很详细)
  3. WWDC视频(2010年的WWDC视频)
  4. 官网文档——Analyzing iOS Application Crash Reports

四、安全规约

在开发应用的时候,数据的安全性至关重要,而仅仅用POST请求提交用户的隐私数据,还是不能完全解决安全问题。
因此:提交用户的隐私数据时,一定不要明文提交,要加密处理后再提交

  • 常见的加密算法
MD5 \ SHA \ DES \ 3DES \ RC2和RC4 \ RSA \ IDEA \ DSA \ AES
  • 加密算法的选择

一般公司都会有一套自己的加密方案,按照公司接口文档的规定去加密就好

这里主要介绍下项目中常用的加密算法MD5

MD5

  • 简单说明
MD5:全称是Message Digest Algorithm 5,译为“消息摘要算法第5版”

效果:对输入信息生成唯一的128位散列值(32个字符)
  • MD5的特点
1.输入两个不同的明文不会得到相同的输出值

2.根据输出值,不能得到原始的明文,即其过程不可逆
  • MD5的应用
由于MD5加密算法具有较好的安全性,而且免费,因此该加密算法被广泛使用

主要运用在数字签名、文件完整性验证以及口令加密等方面
  • MD5破解
MD5解密网站:http://www.cmd5.com
image
  • MD5改进
现在的MD5已不再是绝对安全,对此,可以对MD5稍作改进,以增加解密的难度

加盐(Salt):在明文的固定位置插入随机串,然后再进行MD5

先加密,后乱序:先对明文进行MD5,然后对加密得到的MD5串的字符进行乱序

总之宗旨就是:黑客就算攻破了数据库,也无法解密出正确

代码示例:

#import "HMViewController.h"
#import "NSString+Hash.h"

#define Salt @"fsdhjkfhjksdhjkfjhkd546783765"

@interface HMViewController ()

@end

@implementation HMViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self digest:@"123"]; //
    [self digest:@"abc"];
    [self digest:@"456"];
}

/**
 *  直接用MD5加密
 */
- (NSString *)digest:(NSString *)str
{
    NSString *anwen = [str md5String];
    NSLog(@"%@ - %@", str, anwen);
    return anwen;
}

/**
 *  加盐
 */
- (NSString *)digest2:(NSString *)str
{
    str = [str stringByAppendingString:Salt];
    
    NSString *anwen = [str md5String];
    NSLog(@"%@ - %@", str, anwen);
    return anwen;
}

/**
 *  多次MD5
 */
- (NSString *)digest3:(NSString *)str
{
    NSString *anwen = [str md5String];
    
    anwen = [anwen md5String];
    
    NSLog(@"%@ - %@", str, anwen);
    return anwen;
}

/**
 *  先加密, 后乱序
 */
- (NSString *)digest4:(NSString *)str
{
    NSString *anwen = [str md5String];
    
    // 注册:  123 ----  2CB962AC59075B964B07152D234B7020
    
    // 登录: 123 --- 202CB962AC59075B964B07152D234B70
    
    NSString *header = [anwen substringToIndex:2];
    NSString *footer = [anwen substringFromIndex:2];
    anwen = [footer stringByAppendingString:header];
    
    NSLog(@"%@ - %@", str, anwen);
    return anwen;
}
@end
1.直接使用MD5加密(去MD5解密网站即可破解)
image
2.使用加盐(通过MD5解密之后,很容易发现规律)
image
3.多次MD5加密(使用MD5解密之后,发现还是密文,那就接着MD5解密)
image
4.先加密,后乱序(破解难度增加)
image

注册和验证的数据处理过程

1.提交隐私数据的安全过程 – 注册
image
2.提交隐私数据的安全过程 – 登录
image

五、公共 库/类 规约

当程序书写到一定量的时候,毕竟要进行封装、重构,这样做的好处:
1.代码逻辑结构更加清晰。
2.冗余代码量减少,更加清爽。
3.维护量也较低,符合"对扩展开放,对修改关闭"的软件架构思想等等。

可以看到通过封装使一部分成员充当类与外部的接口,而将其他的成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而增强数据的安全性和简化程序的编写工作。

开闭原则

开闭原则(OCP:Open-Closed Principle)
是指在进行面向对象设计(OOD:Object Oriented Design)中,
设计类或其他程序单位时,应该遵循以下设计原则:

1.对扩展开放(open)
2.对修改关闭(closed)

开闭原则是判断面向对象设计是否正确的最基本的原理之一。

根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。

1.扩展开放:某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。

2.修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改关闭的。

这也是系统设计需要遵循开闭原则的原因:

1.稳定性:开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。

2.扩展性:开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。

六、三方库规约

通过CocoaPods来统一管理第三方库文件

CocoaPods 安装

CocoaPods 安装前准备

  • 移除Ruby默认源
gem sources --remove https://rubygems.org/  
  • 使用新的源
gem sources -a https://ruby.taobao.org/
  • 验证新源是否替换成功
gem sources -l
  • 替换成功后
*** CURRENT SOURCES * * *
https://ruby.taobao.org/

CocoaPods 安装

sudo gem install cocoapods

注意:苹果系统升级 OS X EL Capitan 后改为:

sudo gem install -n /usr/local/bin cocoapods (v1.0.0)
pod setup(//将 CocoaPods Specs repository复制到你电脑上~/.cocoapods目录下)
sudo gem update --system

CocoaPods 的使用

  • 如果你计划增加外部依赖(比如,第三方库)在你的项目中,CocoaPods 提供了一个快捷的途径.
  • 新建工程,并在终端中cd到工程文件夹内,在你的 iOS 项目目录下运行:
pod init
  • 它会创建一个 Podfile, 会管理你所有的依赖,在 Podfile 中加入你的依赖
  • 编辑文件:platform:ios, '8.0'
    pod 'AFNetworking', '~> 2.3.1' <-------第三方
  • 执行下面命令安装
pod install
  • 来安装第三方库并且将它们作为 workspace 的一部分,你的 workspace 也会包含你自己的项目。
  • 一般推荐提交你自己的项目的依赖,而不是每个开发者在一个 checkout之后运行 pod install 。
  • 注意在之后,你需要打开 .xcworkspace 而不是 .xcproject,否则你的代码就不能被编译了,命令:
pod update

会升级所有的 pod 到最新版本,你可以用大量 符号 来定义你期望的版本需求。

关于 Podfile.lock

当你执行pod install之后,除了 Podfile 外,CocoaPods 还会生成一个名为Podfile.lock的文件,Podfile.lock 应该加入到版本控制里面,不应该把这个文件加入到.gitignore中。因为Podfile.lock会锁定当前各依赖库的版本,之后如果多次执行pod install 不会更改版本,要pod update才会改Podfile.lock了。这样多人协作的时候,可以防止第三方库升级时造成大家各自的第三方库版本不一致。


将代码提交到CocoaPods

  • 如果你在github共享了一段代码,你想把代码提交到CocoaPods,希望其他用户search 你的项目的名称就可以找到你,并且 pod install 集成你的代码到项目内,你就需要学会怎样将自己的代码先提交给 CocoaPods servers.

http://www.cnblogs.com/wengzilin/p/4742530.html


七、开发流程

初始化 创建master分支

cd ~/workspace/NewProject
git init
touch README.md
git commit -m 'add README.md'
git remote add origin git@10.0.1.254:group/newproject.git git push -u origin master

以上命令完成以下操作:

  • 创建了本地git版本库,
  • 创建并提交了README.md文件,
  • 连接了git@10.0.1.254:group/newproject.git远程Git版本库origin
  • 推送当前分支到远程的 master

创建 develop 分支
初始化 GitFlow
GitFlow 一般使用默认参数配置即可,在此规定如下:

  • Production Branch 产品主分支:master
  • Development Branch 开发分支:develop
  • Feature Branch Prefix 功能分支前缀:feature/
  • Hotfix Branch Prefix 热修复分支前缀:hotfix/
  • Release Branch Prefix 发布分支前缀:release/
  • Version Tag Prefix 版本标签前缀:tag/

设置中,以/结尾的是分支前缀,在某些工具中会以文件夹的形式分组显示。
命令行:

git flow init [-d]

带有 -d 参数时,init 命令将全部使用默认配置;否则,会提示输入各分支名称。

开始开发

开发在 feature 的子分支进行。

1、Start Feature
在一个版本开发的开始阶段,创建新的Feature分支。 使用命令:

# 创建 Feature 分支
git flow feature start <name> [<base>] 
# 推送 Feature 到远程库
git flow feature publish <name>
# 拉取远程库的 Feature 分支
git flow feature pull <remote> <name>

开始一个Feature分支,其中<name>参数是 feature 分支的名称,最终创建的分 支名称为feature/<name>;<base>可选参数是 develop 分支中的一个Revision;<remote>是远程库的地址,例如

git@10.0.1.254:group/newproject.git

Feature 分支命名规范:

  1. 由小写英文字母、数字、下划线构成。
  2. 分支名称应表明当前版本主要功能。
  3. 例如:feature/http_refactor

2、Finish Feature

在当前版本的全部需求和功能开发完成之后,关闭Feature分支。 使用命令:

git flow feature finish <name>
git push

此操作将本地Feature分支合并到develop分支并删除;然后推送本地的develop分支到远程的develop分支。
Feature 分支在关闭之后,远程库的对应分支需要保留,以备不时之需。

开始测试

提测后的修改在release的子分支进行。

Start Release
Feature开发完成后,需要创建一个Release分支修改Bug。 使用命令:

git flow release start <name> [<base>]

此操作将创建分支 release/<name>

Release 命名规范:

  • 以发布版本号命名,例如:release/1.1release/2.0

Finish Release

Release分支完成修改Bug,达到本次版本发布要求之后,执行此操作。使用命令:

git flow release finish <name>

此操作会将此Release分支合并到master,然后把master代码合并到develop。本地的Release分支自动删除。

鉴于 Finish Release 之后,仍然存在发版失败的可能性,所以此时不创建 Tag。 
在本地 Release 分支删除之后,仍然保留远程 Release 分支,以备不时之需。

上线之后

热修复在 hotfix 的子分支进行。

Start Hotfix

上线之后,仍然可能发现新的Bug,此时不能中断新版本的开发,要在线上版本的基础上修改Bug,也就是“热修复”。 使用命令:

git flow hotfix start <name> [<base>]

此操作将创建分支 hotfix/<name>

Hotfix 分支命名规范:

  • 前缀:固定为hotfix/
  • 线上版本号:此热修复的线上版本号,例如 2.0/
  • 日期:开始热修复的日期,例如 20170305

完整的分支名由以上三部分构成,例如 hotfix/2.0/20170305。此方式可以将同一版本的多次 Hotfix 分组。

Finish Hotfix

解决完线上Bug,首先要在当前Hotfix分支测试验证。验证成功后,即可结束此次 热修复。 使用命令:

git flow hotfix finish <name>

此操作将名为 hotfix/<name>hotfix分支合并到 master,然后再将 master 分 支合并到 develop,然后删除了当前Hotfix分支。

鉴于Hotfix完成之后,仍然可能再次开启并继续修复,所以此时不创建Tag

正式发布版本

所谓的正式发布版本,是指产品正式更新到线上,线上用户可以下载或打开新版本的产品使用。在正式发布版本之后,需要在 master 分支的对应 Revision 处创建 Tag

可以使用命令 git tag 创建分支并 git push origin –tags,也可以在GitLab网站完成。

Tag 命名规范:

  • 使用产品的正式版本的真实版本号,作为 Tag 的名称
  • 所有的正式版本必须创建Tag
  • 完整Tagtag/ 为前缀,完整Tag名为 tag/<name>

产品的正式版本号由产品和测试确定,格式可能不尽相同。
例如:扫码枪版本号为 JLB5.0.0AndroidApp的版本号为 1.5.0,某Web产品的版本号为 V1.0.3
创建 Tag 的时候,命名与版本发布的真实版本号相同即可,并不强制所有项目使用一致的格式。

八、部署与发布

上线流程大致分为三部分:

1.证书,APPID和描述文件的配置
2.Xcode设置
3.iTunes填写APP信息

1.证书,APPID和描述文件的配置

首先我们要登陆苹果开发者中心

登陆开发者账号

进入证书、APPID和描述文件的创建页面

创建证书

点击小“+”号创建发布证书

点击页面最下面的Continue按钮

继续点击最下面的Continue按钮

CSR文件我们要从钥匙串里面生成,点开钥匙串

让钥匙串作为第一响应者,点击左上角的钥匙串访问,如图所示:

点击后填写CSR文件信息

点击继续以后,然后点击存储就OK

然后回到我们证书创建页面,选取CSR文件,然后点击继续

这时候我们的发布证书就创建好了,下载下来后双击装到钥匙串里面即可

创建APP ID
如果你已经创建了APP ID就不用再次创建,如果没有,就跟着一步步来吧

点击最下面的Continue,进入页面以后点击Register即可

创建描述文件

接下来创建描述文件(注:创建描述文件是所选择的发布证书、APP ID一定要和钥匙串里的证书、工程里的Bundle Identifier保持一致)

因为我们是发布到AppStore,这里我们选择发布的描述文件

然后给描述文件起个名字

下载后双击装到Xcode里即可

2.Xcode设置

发布要改成release模式

添加Bundle display name字段,填写应用名称

温馨提示:写到这里,工程配置基本完成,只剩下打包了,后面再介绍打包。

3.iTunes填写APP信息

首先进入iTunes

填完信息后记得保存

Xcode打包有两种方法

方法1

把下面文件放到一个比较容易找到的地方

打开iTunes,找到“应用”

把刚才的文件放到“应用”里

右键show in finder ,把生成的ipa包放到桌面上

点开Xcode,让Xcode作为第一响应者,点击右上角Xcode -> Open Developer Tool -> Application Loader

点击页面右下角的选取,把刚生成的ipa包放入即可

方法2

方法二比较简单

上传完成以后,回到我们的itunes

选中要上传的构建版本后,点击完成

保存后,点击右上角的提交以供审核

点击提交,OVER!

其他

一、关于Crash

审核的时候一旦出现闪退情况,那肯定会被拒,所以程序应尽量做到没有Crash。

Observer的移除

现在的代码里面很多需要用到Observer, 根据被观察对象的状态来相应的更新UI或者执行某个操作. 注册Observer很简单, 但是移除的时候就出问题了, 要么是忘记移除Observer了, 要么是移除的时机不对. 如果某个被观察对象已经被释放了, Observer还在, 那结果只能是Crash了, 所以切记至少在dealloc里面移除一下Observer

NSArray,NSDictionary成员的判空保护

addObjectinsertObjectNSArray或者NSDictionary时最好加一下判空保护, 尤其是网络相关的逻辑, 如果网络返回为空(jason解析出来为空), 但你还是毅然决然的addarray里面, 那么就会出现Crash

二、Xcode快捷键

图标

图标 键盘
Command
Control
Option
Shift

文件快捷键

快捷键 描述
⌘N 新文件
⇧⌘N 新项目
⌘O 打开
⌘S 保存
⇧⌘S 另存为
⌘W 关闭窗口

导航快捷键

快捷键 描述
⌘1-8 打开 Project/Symbol/Find/Issue/Test/Debug/Breakpoint/Log 导航栏
⌘0 显示/隐藏 左侧 tool panel
⇧⌘Y 显示/隐藏 控制台
⌥⌘0 显示/隐藏 右侧 tool panel
⌥⌘1-6 打开右侧导航栏
⇧⌘J 在项目导航中定位当前文件(Reveal in Project Navigator)
⌘J 焦点切换(Move Focus),可快速定位到编辑界面

搜索快捷键

快捷键 描述
⌘⇧F 使用项目搜索可以找到某个变量或方法名的被提到的次数。可以依据实例来匹配,并可忽略大小写字母。另外还可以对查找的变量名进行替换。
⌃⌘Up/Down .h 和 .m文件间的快速切换
⇧⌘O 可以直接跳转到指定的代码文件。
⌃6 (键入方法/变量名+Enter跳转)跳转至当前代码文件中的代码行。如果文件中有上千行代码,你可以通过该快捷操作来缩小查找范围,从而在需要的地方插入代码以添加新的功能。
⌃1 该快捷键可打开’Show Related Items‘弹出菜单’。倘若你已经将光标放在了任何方法中,并点击‘⌃1 ’就可以很方便地通过弹出的视图访问该方法的所有调用者和被调用者。

运行调试快捷键

快捷键 描述
⇧⌘K 清除工程(clean)
⌘B 构建应用程序
⌘R 运行应用程序
⌘. 停止运行(Stop)
⌘\ 设置/取消断点;通过鼠标点击蓝色断点来启用/禁用当前行断点。
⌘Y 全局激活或禁用所有的断点,激活进入调试模式(此时断点蓝色可见)。

编辑快捷键

快捷键 描述
⌘[/] 向前/向后缩进
⌘/ 注释选中的代码
⌥⌘←/→ 折叠当前代码块
⌥⇧⌘←/→ 折叠该文件内所有代码块(方法/函数)
⌥⌘[/] 当前行上移/下移
⌃B 前移光标
⌃F 后移光标
⌃P 移动光标到上一行
⌃N 移动光标到下一行
⌃A 移动光标到本行行首
⌃E 移动光标到本行行尾
⌃K 删除本行
⌃D 删除光标右边的字符(一个字符)
⌃L 将插入点置于窗口正中
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 前言 说是前言,其实也是本文诞生的目的。随着公司业务的不断增加,功能的快速迭代,app的业务线越来越多,代码体积变...
    梦翔_d674阅读 1,447评论 0 2
  • 前言 说是前言,其实也是本文诞生的目的。随着公司业务的不断增加,功能的快速迭代,app的业务线越来越多,代码体积变...
    隔墙送来秋千影阅读 504评论 0 0
  • 原文地址:mp.weixin.qq.com[https://mp.weixin.qq.com/s?__biz=Mj...
    半城coding阅读 689评论 0 0
  • 前言说是前言,其实也是本文诞生的目的。随着公司业务的不断增加,功能的快速迭代,app的业务线越来越多,代码体积变得...
    Mr_yinwei阅读 628评论 0 0
  • 【注意】 1、项目的基本配置以及权限的设置,杜绝私自做更改,若有需要更改请先联系leader!!!2、项目中杜绝私...
    Peter_Zhang阅读 418评论 0 3