混合使用Objective-C,C++和Objective-C++ – 求索 – 博客频道 – CSDN.NET


混合使用Objective-C,C++和Objective-C++

.


分类:
开发语言
Mac OS/iOS

2012-09-02 23:42
7931人阅读
评论(5)
收藏
举报

c++classinterfaceextensiondestructorstring

之前有段时间,我参与了一项使用了C++库的Objective-C项目。写了一篇关于混编的文章,结果却出乎意料的成为Google搜索中关于Objective-C++的最靠前的结果之一。


后来,Apple将基于LLVM的clang做为主选编译器。其作用之一就是可以保证Objective-C的演化,而GCC的进化却太慢了。之前文章就不太适用了,而且在这个过程,我也收到了一些回馈,这些都促使我写了这篇文章。


回顾一下


简言之,如果你有一些C++代码或库,你想在Objective-C项目使用它,这就是我们要研究的问题。 通常,C++代码中会定义你要使用的一些类(class), 你可以简单的把.m文件扩展名改为.mm就可以改为Objective-C++编译,然后就可以很容易地混合使用C++和Objective-C的代码。这是一个简单的做法,但两个世界确实很不一样,如此这样的深度混合有时会变地很棘手。


你可能会想使用等价的Objective-C类型和函数将C++代码封装(wrap)起来。比方说,你有一个名为CppObject的C++类(CppObject.h):

[cpp] view plaincopyprint?

  1. #include <string>  
  2. class CppObject  
  3. {  
  4. public:  
  5.   void ExampleMethod(const std::string& str);  
  6.   // constructor, destructor, other members, etc.  
  7. };  

在Objectiv-C类允许定义C++类的成员变量,所以可以首先尝试定义一个ObjcObject封装类(ObjcObject.h):

[cpp] view plaincopyprint?

  1. #import <Foundation/Foundation.h>  
  2. #import "CppObject.h"  
  3.   
  4.   
  5. @interface ObjcObject : NSObject {  
  6.   CppObject wrapped;  
  7. }  
  8. – (void)exampleMethodWithString:(NSString*)str;  
  9. // other wrapped methods and properties  
  10. @end  

然后在ObjcObject.mm中实现这些方法。不过,此时会在两个头文件(ObjcObject.h&CppObject.h)中得到一个预处理和编译错误。问题出在#include和#import上。对于预处理器而言,它只做文本的替换操作。所以#include和#import本质上就是递归地复制和粘贴引用文件的内容。这个例子中,使用#import "ObjcObject.h"等价于插入如下代码:

[cpp] view plaincopyprint?

  1. // [首先是大量Foundation/Foundation.h中的代码]  
  2. // [无法包含<string>],因为它仅存在于C++模式的include path中  
  3. class CppObject  
  4. {  
  5. public:  
  6.   void ExampleMethod(const std::string& str);  
  7.   // constructor, destructor, other members, etc.  
  8. };  
  9.   
  10.   
  11. @interface ObjcObject : NSObject {  
  12.   CppObject wrapped;  
  13. }  
  14. – (void)exampleMethodWithString:(NSString*)str;  
  15. // other wrapped methods and properties  
  16. @end  

因为class CppObject根本不是有效的Objective-C语法, 所以编译器就被搞糊涂了。 错误通常是这样的:

Unknown type name ‘class’; did you mean ‘Class’?

 

正是因为Objective-C中没有class这个关键字. 所以要与Objective-C兼容,Objective-C++类的头文件必须仅包含Objective-C代码,绝对没有C++的代码 – 这主要是影响类型定义(就像例中的CppObject类)。


保持简洁的头文件

之前的文章已经提到一些解决方案.其中最好的一个是PIMPL,它也适用于现在的情况。这里还有一个适用于clang的新方法,可以将C++代码从Objective-C中隔开,这就是class
extensions
中ivars的。 


Class extensions (不要同categories弄混) 已经存在一段时间了: 它们允许你在class的接口外的扩展部分定义在@implementation段前,而不是在公共头文件中。 这个例子就可以声明在ObjcObject.mm中:

[cpp] view plaincopyprint?

  1. #import "ObjcObject.h"  
  2. @interface ObjcObject () // note the empty parentheses  
  3. – (void)methodWeDontWantInTheHeaderFile;  
  4. @end  
  5. @implementation ObjcObject  
  6. // etc.  

GCC也支持这个操作。不过clang还支持添加ivar块,也就是你还可以声明C++类型的实例变量,既可以在class extension中,也可以在@implementation开始的位置。本例中的ObjcObject.h可以被精简为:

[cpp] view plaincopyprint?

  1. #import <Foundation/Foundation.h>  
  2.   
  3.   
  4. @interface ObjcObject : NSObject  
  5. – (void)exampleMethodWithString:(NSString*)str;  
  6. // other wrapped methods and properties  
  7. @end  

 

去掉的部分都移到实现文件的class extension中 (ObjcObject.mm):

[cpp] view plaincopyprint?

  1. #import "ObjcObject.h"  
  2. #import "CppObject.h"  
  3. @interface ObjcObject () {  
  4.   CppObject wrapped;  
  5. }  
  6. @end  
  7.   
  8.   
  9. @implementation ObjcObject  
  10. – (void)exampleMethodWithString:(NSString*)str  
  11. {  
  12.   // NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.   
  13.   std::string cpp_str([str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);  
  14.   wrapped.ExampleMethod(cpp_str);  
  15. }  

如果我们不需要interface extension来声明额外的属性和方法,ivar块仍然可以放在@implementation开始位置:

[cpp] view plaincopyprint?

  1. #import "ObjcObject.h"  
  2. #import "CppObject.h"  
  3.   
  4.   
  5. @implementation ObjcObject {  
  6.   CppObject wrapped;  
  7. }  
  8.   
  9.   
  10. – (void)exampleMethodWithString:(NSString*)str  
  11. {  
  12.   // NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.  
  13.   std::string cpp_str([str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);  
  14.   wrapped.ExampleMethod(cpp_str);  
  15. }  

定义的CppObject实例wrapped在ObjcObject创建时,CppObject的缺省建构函数会被调用,而在ObjcObject被调用dealloc析构时,ObjcObject的析构函数也会被调用。如果ObjcObject没有提供缺省的建构函数,编译就会失败。


管理被封装C++对象的生命周期

解决方案是透过new关键字掌握建构过程, 比如:

[cpp] view plaincopyprint?

  1. @interface ObjcObject () {  
  2.   CppObject* wrapped; // 指针!会在alloc时初始为NULL.  
  3. }  
  4. @end  
  5. @implementation ObjcObject  
  6. – (id)initWithSize:(int)size  
  7. {  
  8.   self = [super init];  
  9.   if (self)  
  10.   {  
  11.     wrapped = new CppObject(size);  
  12.     if (!wrapped) self = nil;  
  13.   }  
  14.   return self;  
  15. }  
  16. //…  

如果是使用C++异常, 也可以使用 try {…} catch {…}把创建过程封装起来. 相应地,还要显式地释放封闭对象:

[cpp] view plaincopyprint?

  1. – (void)dealloc  
  2. {  
  3.   delete wrapped;  
  4.   [super dealloc]; // 如果使用了ARC,这句就要略去  
  5. }  

作者接着提到了另一个方法,显示分配一块内存,然后在它的基础上调用new来创建对象。首先声明char wrapped_mem[sizeof(CppObject)]; 再使用wrapped = new(wrapped_mem) CppObject();创建了实例wrapped。释放时if (wrapped) wrapped->~CppObject();  这样虽然可行,但不建议使用。


 总结

 一定要确保封装的方法仅返回和使用C或Objective-C类型的返回值及参数。同时不要忘记C++中不存在nil, 而NUL是不可用于解引用的。


反向:在C++代码中使用Objective-C类

这个问题同样存在于头文件中。你不能因为引入Objective-C类型而污染了C++头文件,或无法被纯C++代码所引用。比方说,我们想封装的Objective-C类ABCWidget ,在ABCWidget.h声明为:

[cpp] view plaincopyprint?

  1. #import <Foundation/Foundation.h>  
  2. @interface ABCWidget  
  3. – (void)init;  
  4. – (void)reticulate;  
  5. // etc.  
  6. @end  

这样的类定义在Objective-C++中是没有问题的,但在纯C++的代码是不允许的:

[cpp] view plaincopyprint?

  1. #import "ABCWidget.h"  
  2. namespace abc  
  3. {  
  4.   class Widget  
  5.   {  
  6.     ABCWidget* wrapped;  
  7.   public:  
  8.     Widget();  
  9.     ~Widget();  
  10.     void Reticulate();  
  11.   };  
  12. }   

一个纯粹的C++编译器在Foundation.h中的代码和ABCWidget声明位置出错。


永恒的PIMPL

有没有这样的东西作为一类扩展C + +,这样的把戏将无法正常工作。 另一方面,PIMPL,工作得很好,实际上是比较常用的纯C + +了。 在我们的例子中,我们减少到最低限度:C + +类


C++并没有之前提到的class extension,但是却有另一种较为常用的方式:PIMPL (Private Implementation, 私有实现)。这里,将C++ class的定义精简为:

[cpp] view plaincopyprint?

  1. namespace abc  
  2. {  
  3.   struct WidgetImpl;  
  4.   class Widget  
  5.   {  
  6.     WidgetImpl* impl;  
  7.   public:  
  8.     Widget();  
  9.     ~Widget();  
  10.     void Reticulate();  
  11.   };  
  12. }  

然后在Widget.mm中:

[cpp] view plaincopyprint?

  1. #include "Widget.hpp"  
  2. #import "ABCWidget.h"  
  3. namespace abc  
  4. {  
  5.   struct WidgetImpl  
  6.   {  
  7.     ABCWidget* wrapped;  
  8.   };  
  9.   Widget::Widget() :  
  10.     impl(new WidgetImpl)  
  11.   {  
  12.     impl->wrapped = [[ABCWidget alloc] init];  
  13.   }  
  14.   Widget::~Widget()  
  15.   {  
  16.     if (impl)  
  17.       [impl->wrapped release];  
  18.     delete impl;  
  19.   }  
  20.   void Widget::Reticulate()  
  21.   {  
  22.     [impl->wrapped reticulate];  
  23.   }  
  24. }   

它的工作原理是,前置声明。声明这样的结构或类对象的指针成员变量、结构或类就足够了。


需要注意的是封装的对象会在析构函数中释放。即便对于使用了ARC的项目,我还是建议你对这样的对C++/Objective-C重引用的文件屏蔽掉它。不要让C++代码依赖于ARC。在XCode中可以针对个别文件屏蔽掉ARC。Target properties->Build phase页签,展开’Compile Sources’, 为特定文件添加编译选项-fno-objc-arc。


C++中封装Objective-C类的捷径

您可能已经注意到,PIMPL解决方案使用两个级别的间接引用。 如果包装的目标类像本例中的一样简单,就可能会增大了复杂性。 虽然Objective-C的类型一般不能使用在纯C++中,不过有一些在C中实际已经定义了。id类型就是其中之一,它的声明在<objc/objc-runtime.h>头文件中。虽然会失去一些Objective-C的安全性,你还是可以把你的对象直接传到C++类中:

[cpp] view plaincopyprint?

  1. #include <objc/objc-runtime.h>  
  2. namespace abc  
  3. {  
  4.   class Widget  
  5.   {  
  6.     id /* ABCWidget* */ wrapped;  
  7.   public:  
  8.     Widget();  
  9.     ~Widget();  
  10.     void Reticulate();  
  11.   };  
  12. }  

不建议向id对象直接发送消息。这样你会失去很多编译器的检查机制,特别是对于不同类中有着相同selector名字的不同方法时。所以:

[cpp] view plaincopyprint?

  1. #include "Widget.hpp"  
  2. #import "ABCWidget.h"  
  3. namespace abc  
  4. {  
  5.   Widget::Widget() :  
  6.     wrapped([[ABCWidget alloc] init])  
  7.   {  
  8.   }  
  9.   Widget::~Widget()  
  10.   {  
  11.     [(ABCWidget*)impl release];  
  12.   }  
  13.   void Widget::Reticulate()  
  14.   {  
  15.     [(ABCWidget*)impl reticulate];  
  16.   }  
  17. }   

像这样的类型转换很容易在代码中隐藏错误,再尝试一个更好的方式。在头文件中:

[cpp] view plaincopyprint?

  1. #ifdef __OBJC__  
  2. @class ABCWidget;  
  3. #else  
  4. typedef struct objc_object ABCWidget;  
  5. #endif  
  6.   
  7.   
  8. namespace abc  
  9. {  
  10.   class Widget  
  11.   {  
  12.     ABCWidget* wrapped;  
  13.   public:  
  14.     Widget();  
  15.     ~Widget();  
  16.     void Reticulate();  
  17.   };  
  18. }   

如果这个头文件被一个mm文件引用,编译器可以充分识别到正确的类。 如果是在纯C++模式中引用,ABCWidget*是一个等价的id类型:定义为typedef struct objc_object* id; 。 #ifdef块还可以被进一步放到一个可重用的宏中:

[cpp] view plaincopyprint?

  1. #ifdef __OBJC__  
  2. #define OBJC_CLASS(name) @class name  
  3. #else  
  4. #define OBJC_CLASS(name) typedef struct objc_object name  
  5. #endif   

现在,我们可以前置声明在头文件中一行就可以适用于所有4种语言:

  OBJC_CLASS(ABCWidget);

 

转载请注明出处:http://blog.csdn.net/horkychen

 

分享到:

.

顶2
踩0
.

Objective-C 的 API 设计 – 技术翻译 – 开源中国 OSChina.NET

我最常做的开发任务是设计一个可重用的API组件。组件通常为iOS(尽管有时它们是OS X) 设计的,且总是GUI控件或某种视图。

多年来,我为客户开发了很多API组件,其中包括像Apple这样的客户,而且我已经很了解这个过程。我也定期发布开源组件,并且我把曾经对我有帮助的资料和API设计指南放在一起与大家分享。

这是一个重要的主题,无论你是一个开源贡献者,或作为团队的一员参与开发大型的应用,或者只是设计自己的软件。正如开发一个应用的过程,API接口是使用你代码的开发者对你代码的第一印象,将严重影响着开发者决定是使用或扔掉它。

APIs是开发者的用户体验。我一直惊讶,具体到这个流行平台上没有很多的资料是写我们这方面工作的。

当我们阅读一些设计指南时,必要的时候,我将要用我最近发布的开源GUI组件MGTileMenu作为一个例子。你可以在这里先阅读所有关于MGTileMenu的信息,如果你喜欢。

hyaicc
翻译于 1个月前

1人顶

 翻译的不错哦!

其它翻译版本(1)

如何令人满意

应用程序接口(API)设计和用户界面、用户体验设计很相像。你的目标用户有不同的需求和特点,但归根结底他们的目标还是把需求完成而已。就像一个设计友好、易用的应用程序的用户界面一样,你需要让你的API有以下的特点:

  1. 直观性
  2. 容错性
  3. 易用性

如同人们设计的其它的软件一样,我们首先需要考虑的是使用案列。我们的设计需要使最经常被用到的的功能简单易用,不需要过度的配置。在默认配置下软件就应该是可用的,并且具有一定的可配置性。软件的设计应该具有可探索性,而且应该允许用户从已知的的范例中推广到其他应用场景。这和我们创建一个用户界面的规则非常的相像。

WangWenjing
翻译于 1个月前

1人顶

 翻译的不错哦!

其它翻译版本(1)

开发者的界面

用于和开发者交互的元素使用四个主要的显示意味着:

  1. 类界面:暴露的属性和方法。
  2. 委托规则,相关的
  3. 数据源规则,适当的
  4. 任何可以提供的通知

我们需要把每一个都设计成:明智和慎重的,用于人类使用。这里有2个问题当你设计API的时候需要考虑:

  • 什么是控制?
    这将会影响到界面和便利的方法。这是一个按钮?一个滑动器?你的界面是很明显的。你的便利的方法将会遵循这些标准的语义控制
  • 控制长什么样?
    这影响到委托和/或数据源模型和通知。如果这是一个新类型的控制,这个是不是在基本原则上会和其他东西很像?一个大纲性的概念是一个线性表。一个日期的小工具是一个日期的选择器。在一个同一标准下的命令的集合是一个菜单。

我们的核心原则是让已有的类和模型保持一致性,以用来保证我们可以把一个开发者不熟悉的控制让他很轻松的在他可以理解的平台上使用。使用标准APIs,模型,和模式无论是不是可能(并且这个应该是总用的)。对于终端用户,熟悉和直觉性是和代码层级一样重要的。

让我们看看我们之前提到的这四个元素:

周荣冰
翻译于 1个月前

0人顶

 翻译的不错哦!

其它翻译版本(1)

类接口

Here’s the interface file for MGTileMenu.

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

Rule 1: 使用方言

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

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

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

魏涛
翻译于 1个月前

0人顶

 翻译的不错哦!

Rule 2: 设计解耦

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

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

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

魏涛
翻译于 1个月前

0人顶

 翻译的不错哦!

其它翻译版本(1)

Rule 3: 必须设置初始化参数

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

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

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

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

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

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

魏涛翻译于 1个月前

0人顶

 翻译的不错哦!

其它翻译版本(1)

Rule 5: 注释你的header文件 (包含默认值)

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

  1. 足以描述,但是不是特别多,要简洁。
  2. 一切是提供给专业人士,所以适当的描述别描述无关的事情。

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

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

魏涛翻译于 1个月前

0人顶

 翻译的不错哦!

其它翻译版本(1)

Rule 6: 习惯于运行3行代码

你的类应该设计成只需要最少的代码来集成(包括后续将用到的委托/数据源协议)。但不包括委托方法,你应该着手于用3行代码就可以达到测试的目的。

这3行代码如下:

  1. 实例化你的类
  2. 基本配置,使之能够展示一些信息
  3. 展示或激活它(类的示例)

就这样。任何实质上更繁杂的就会导致代码坏味(code smell)。下面是
MGTileMenu’s demo app中
相关的几行代码:

1 // Instantiate.
2 tileController = [[MGTileMenuController alloc] initWithDelegate:self];
3  
4 // Configure.
5 tileController.dismissAfterTileActivated = NO; // to make it easier to play with in the demo app.
6  
7 // Display.
8 [tileController displayMenuCenteredOnPoint:loc inView:self.view];

haoio翻译于 1个月前

0人顶

 翻译的不错哦!

其它翻译版本(1)

Rule 7: 臃肿的demo通常意味着组件是糟糕的

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

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

魏涛
翻译于 1个月前

0人顶

 翻译的不错哦!

Rule 8:分析特定的场景

我对于apps的准则就是:不要让用户去做选择。选择满足多数人的人性化的默认设置,略去参数设置窗口。毕竟,好的软件都是有倾向性的。

由于运用场景不是那么的清晰明确,所以不同的组件面对的情况也有些不同。你当然可以做一个只满足某种特定情况的组件,但是,通常我们都希望有些灵活性。你绝不会准确的知道另一个开发者将会怎样使用你的组件,所以你必须做到有一定的通用性。

认真的选择你的定制点是很重要的。考虑依赖关系更加的重要——不是对编译/链接的理解,而是定制类型之间的逻辑关系。我的方法就是尽量从“方面”的层次上考虑而不是实例变量的层次上。你希望你的组件的那些方面允许被定制化?那么你就知道哪些特定的属性需要暴露。

通过不暴露足够的的配置点,就可以很容易的弱化某个特定的定制类型。例如:

    1.如果没有考虑圆角半径,就不要暴露宽度和高度。

    2.如果没有高亮的背景颜色,就不要暴露背景颜色。

    3.如果没有空间,就不要暴露大小。

具体的情况取决于具体的组件,但是需要从外观或者功能角度来考虑属性之间的关系。学会理解开发者。不要禁止组件的个性化,让它灵活些。

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

让常识来指导你。确定那些能够满足70%左右你所能想到的使用场景的选项,然后提供这些选项。剩下的就让你的授权方法和代码架构来满足吧。

crazylion翻译于 1个月前

0人顶

 翻译的不错哦!

Rule 9:  多点属性,少点方法

有一个特定的模式持续出现在我所喜欢的一些来自标准库、开源的第三方以及我自己的一些代码组件。它是一个组件中属性(或者访问器,定制点)个数与方法(也就是所有其它的,从初始化到状态更新)个数的比率。

多属性少方法(再申明一次,方法不是指在Interface Builder中的那些)。MGTileMenu有一个初始化函数和四个实际上供公共使用的愿意非常(每一个都很方便调用另一个方法)。对定制点而言,它的比率有4倍之多。我认为这是一个非常好的比率,使组件不但在功能上变得简洁,而且在定制时更加灵活。

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

showme翻译于 12天前

0人顶

 翻译的不错哦!

Rule 10: 在你的控件中使用控件

一个同时简化组件API和实现的好方法就是在你的实现中使用己有的控件。具有统一的外在并不意味着你不可以使用已经存在的组件(确实,这是软件工程当中的一个基本原则)。

考虑是什么让UITableViewCell和UIButton拥有简单的API接口,发现这是因为它们使用诸如UIImageView和UILabel这样的子控件。你也可以,并且该这样去做,并且如果可行的话使用相应的子控件来使你的类接口保持简单不变。

举个例子,在MGTileMenu中,它的外表是常规的UIButtons(不只是子类)。跟在一个的view中自定义去画它的样式、处理输入事件和支持访问而言,极大地简化了它的实现。

showme

showme
翻译于 15天前

0人顶

 翻译的不错哦!

Rule 11: 于人方便就是于己方便

你会很自然地在实现的过程中加入合适的方法并下意识地将其设置为私有的。相反,应该考虑是否可以公开这些方法,使这些组件能被集成到别人的应用程序。

对你而言那些如何简单方便地加入一个方法或函数的方式,对开发者而言同样如此。

举例来说,在MGTileMenu中,我创建了这些合适的函数:

1 CGRect MGMinimallyOverlapRects(CGRect inner, CGRect outer, CGFloat padding);
2  
3 CGGradientRef MGCreateGradientWithColors(UIColor *topColorRGB, UIColor *bottomColorRGB); // assumes colors in RGB colorspace

第一个函数可以帮助我移动tile menu使它在父View完全可见(如果它们对菜单提供辅助的用户接口,便可以方便地让其它的开发者使用),第二个返回一个渐变Core Graphics给UIColors,它被我用来设置tiles的默认的背景(同时
当实现MGTileMenu的代理协议
时,其它的开发者们发现可以很方便的给tiles配置渐变色)。

showme

showme翻译于 12天前

0人顶

 翻译的不错哦!

Rule 12:魅力可以,魔数却不行。

你迟早都会在你的component中加入一些魅力。人人都希望有大量的Steve Jobs式的直观、宜人、富于掌控力的魅力,但是我所说的却是代码中的一些东西,诸如拥有特殊含义的数字或者值。例如,-1在某项设置或者某个特殊场景中,就有着某个特定的意义。

很好,那样做真的挺好的。不爽的是你的代码中充满一些莫名其妙的原始值,更不爽的是还将它们暴露在API中。如果你正在暴露一些魔数,那么为了便于使用,最好还是用#define或者常量或者其他的东西包装一下它们,这样会让魔数更加的直观和易于理解。

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

crazylion翻译于 1个月前

0人顶

 翻译的不错哦!

代理和数据源协议

代理协议是奇妙的。它们是实现MVC模式的简单的、熟悉的和灵活的方式,它们更会使你养成松耦合的好习惯并且教你明智的API设计。

这个是 MGTileMenu’s delegate protocol.

有太多经典的代理和数据源协议供我们用到几乎所有的组件中。如果你正在显示数据,这个准确的数据源协议可能更接近于:

  1. 我有多少事物?
  2. 事物X对应的属性Y的值是多少?

同样的,在几乎任何的情况中,这个准确代理协议可能采用下面的形式:

  1. 这个事物应该做那些嘛?
  2. 这个事物将要做那些。
  3. 这个事物刚好做了那些?

这也被称为Should, Will, Did协议模式,这也巧妙的连结了之后的Will-Did通知模式。

让我们来讨论一下你可能会觉得有争议的话题:我发现将代理协议合并到数据源协议中被完美的接受了(也就是说将他们整合到一个协议中)。我在MGTileMenu和几个其他的组件中这样做了。

我完全接受分离他们的原则,并且我能想到很多你想保持他们分离的例子。通常,苹果也是保持他们分离的。那好吧。

可是,就我的经验看,在大多数例子中合并他们是很好的。大多数人将数据源方法和代理方法放到同一个地方。我从来没有因为合并这些协议而抱怨过,我几乎不能记得一个存在分开的协议在不同的地方使用的情况。

如果你重视清晰,或者有将代理从数据源中分离出来的需求,那么很明显你应该那样做。我只是不认为如果你合并他们会觉得不好。

李远超
翻译于 8天前

0人顶

 翻译的不错哦!

规则 13:限制‘required’代理方法的数量

当你选择设置哪些代理方法为required(必须实现)时一定要小心。太多的required方法将会表明:

  1. 可选择的默认行为非常少。
  2. 在代码中你自己的观点非常多。

一个精心设计的组件应该有很少很少required代理方法-仅仅是那些不得不的方法。小心选择。同样的,记住再后来添加optional(可选择实现)方法是很简单的,但是添加required方法就非常的困难(很多时候人们会抱怨)。

在MGTileMenu中有5个required方法,其中有4个是数据源方法:

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

前两个属于一个真正的数据源协议。第三第四个也是,但是这也表明了我的观点:我认为软件应该是容易理解的,并且我正在强制你为每一个tile提供一个标签和表述来共读者们阅读。我很享受这些。

当然也有一个代理方法

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

这个方法是required,因为他告诉你如何来发现一个tile是激活状态的。如果你不打算实现这个方法,MGTileMenu将不会做任何有用的事情,甚至你可能就根本不能用它。所以他是必须实现的。

李远超翻译于 7天前

0人顶

 翻译的不错哦!

规则 14:设计成容易理解的

立刻跟上这最后的规则:使设计是容易理解的。不要在最后才注意到它,恰恰相反,应该是在起初就设计成容易理解的。如果你遵循了“在你的控件中运用控件”原则,那么你可能在不知不觉中就遵循了这个原则。

上面展示的代理(倒不如说是数据源)方法是另一个开发者为了使他们给语音辅助提供一些东西的地方。 如果你能够在视觉上(就像展示一个文本标签)自动地改变一些事情的意图就像一个语音辅助标签那样,那该多好啊(此外,在大多数例子当中语音辅助已经为你实现了这个)。

在交流方面保持清醒。做到不容易理解的设计倒是困难的。我也写了另外一篇关于在IOS应用中支持语音辅助功能的文章,这也是苹果向在容易理解的程序有联系的合伙人们推荐的。我也推荐它,但是我写它就是为了你可以接受它。

李远超
翻译于 7天前

0人顶

 翻译的不错哦!

规则 15:利用语义对象来做参数

这不仅仅适用于协议,但协议是尤其重要的部分。最好是在实际应用中,用合适的语义对象作为数据,虽然在你的实现中这样做可能会更加的麻烦。

如果你需要一个日期,不要用一些数字-而应该是一个真实的日期对象。对象或者结构体可以表示每一样事物,并且你应该有意地去用它们。如果需要的话可以创建一个类(你可能不会需要)。

当然一个标准的目录除外-除了基元没有任何理由可以把他们变成任何事物,自从NSNumber的加入,对于抵消打包/非打包带来的麻烦没有任何事物是语义地足够重要的。

李远超
翻译于 6天前

0人顶

 翻译的不错哦!

规则 16:如果语义不合适的话就提高API

我时刻都在注意这一点。我曾在早些时候提到过,你怎么能就像一些事物已经存在(通常,就像是已经存在了的在你的实现中无意间用到的东西)了那样考虑几乎任何新的定制的控件呢?

那非常好,并且你是很聪明的,但是不能让语义胜过相似点。为了使语义合适,在一个已经存在了的API上叠加一个新的API绝对是好的(或者漂亮的)。例如:

  • 用表格实现一个联系人列表应该有一个联系人相关的API
  • 用网格实现一个月份的日历试图应该有一个日期相关的API

诸如此类的等等。不要时常地强制你自己(或者其他的开发者)在抽象的实现API和真实的组件语义之间做精神上的转变 – 反而应该让这个API反映出组件真实的目的。

MGTileMenu 的代理协议通过不把菜单当做UIBUttons(实现了的)的集合而宁可说是菜单的统一做到了这一点,利用每个菜单中相关的有限的tile来展示内容。

李远超
翻译于 6天前

0人顶

 翻译的不错哦!

Rule 17: 高亮是有趣的

当我不得不回过头来添加一个新的代理方法和通知到我认为已经完成了的API中,我才意识到这一点。对交互控件来说,高亮是有趣的。通过“高亮”,意味着其在应用程序中潜在的重要性。

任何控件都会通知App(在某种意义上来说,可能只是通过调用一个动作或方法),当它被完全触发时;但是当它们被高亮(选中,按住)或取消高亮被触发时,只有比较少的情况才会通知。这说明它实际上非常重要。应用程序可能需要:

  1. 添加,删除或重定位辅助界面。
  2. 更新一些其它的显示部分。
  3. 提供一些上下文的帮助。
  4. 一些你可能没有预料到的其它情况。

高亮当然是
代理中的
optional方法组中的一个例子,但它非常重要值得拥有,并且常常只要小量工作既可实现。

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

showme翻译于 11天前

0人顶

 翻译的不错哦!

Rule 18: 可选的方法不是一个保证

我们大部分人都把可选的委托方法当做二选一的情形:如果你不实现他们,就使用默认的行为;如果你要实现他们,那你就要为将发生的事情全部负责。那不是理想的事情。

在任何的提供一个可选的委托方法的实现中,你应该仍然返回去默认的行为,即使这个方法已经被实现了,但不要返回一些明显的东西。这听起来很明显,但是令人惊讶到底有多少组件无忧无虑地然后委托对象返回任何类型没有经过精细检查的愚蠢东西,仅仅是因为这委托莫名奇妙地答应通过实现这个方法来管理自己的行为。

我要具体地讨论下可视化的定制,如背景颜色和图片。非常非常仔细地考虑下,你是否不应该去干涉那种情况,同时依靠于你的默认界面。他们真的不想展示些东西吗?甚至让感觉好一点?它会让控制看起来很糟糕吗?如果如此,插手介入吧,同时仅在如果委托方法从来不在开始的地方就实现的时候准备默认的。

相关地,用一个有文档的、标准的及不是很突兀的方式去慎重地从每个可选的委托方法中通过返回一些如空的东西来执行默认的行为。

例如,MGTileMenu有一个比较复杂的层次方式让你可以自定义标题的背景。你可以实现三个委托方法中的任何(或者全部,或者不)方法去为每个标题提供一个背景图片、梯度或者颜色。你也能够在任何时候为任何标题选择默认的行为,通过返回空或者NULL等适当的类型。

你将不得不尝试相当困难地去使标题的背景透明(通过返回清澈的颜色或者使用空的UIImage对象)。

throwable
翻译于 5天前

0人顶

 翻译的不错哦!

Rule 19 :总是说出谁正在说话

这是一个简单的规则,大家也可以同样简单地产生一个错误。在你的委托(delegate)方法中,总要传递一个sender参数。

不管是单例,还是你无法想象地将被不止一次同时使用的东西(,都应如此)。没有例外:

应当这样:

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

不应当这样:

1 - (void)tileMenuDidActivateTile:(NSInteger)tileNumber;
2 // zero-based tileNumber
3 // Um, 哪个菜单?

weizhe72翻译于 1个月前

0人顶

 翻译的不错哦!

Rule 20: 在查询方法中首先放置可区分的参数

一个真正的数据源协议总有将最感兴趣东西放在前边的方法。比如,你请求的指定质量或属性等。像这样:

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

不要像这样:

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

返回类型自然而然应作为方法名的第一部分,这并不会另人奇怪(请注意上边两个函数的不同处)。数据源协议中经常有许多命名相似的方法,因此我们应该最先考虑保持函数的唯一性和感兴趣部分。这样的话,(这些方法)更容易读,更容易做到自动补全。

有人指出:Apple公司的UITableViewDataSource协议并没有按照那些做,他们是把sender放在第一位的,例如:

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

对此,我想说的就是:我关注这些不同之处。我坚持我的观点。

weizhe72翻译于 1个月前

0人顶

 翻译的不错哦!

Rule 21:在通知方法中把sender放到第一位

一个真正的委托(Deletage)协议不是用来查询,而是用来通知的。在这种情况下,你应该将sender放到第一位(参考 “说出谁正在说话” 规则)

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

这是遵循两个人交流的习惯的。你不应该跳出来就说“她将会迟到”,因为另一个会问“谁?”

取而代之,你应该说出谁正在说话。这是一个习惯,可以很方便地将查询(数据源)与通知(委托)方法区分开来。

weizhe72翻译于 1个月前

0人顶

 翻译的不错哦!

规则 22:如果约定被打破了,那就抛开它吧!

上面已经说了这么多,记住约定和一致性一定是在某些时刻要屈服于优秀的观点的 – 在这个例子当中,或者你的例子中。如果约定被打破,那就好不担心的去跳过它。如果你的观点真的更好,那就去做吧!

举个例子,在菜单控件中已经存在一个约定了,靠这个约定你能够通过代理来使菜单选项可用或者不可用,利用calledvalidateMenuItem:方法。为了一致性的缘故,我曾考虑过给我代理协议中的一部分方法用相同的名字。但是我最终决定没有那样做,因为:

  • 那是一个非常可怕的名字。“Validate”?那对我是不可用的。
  • 这是必要的,在我的例子中我却是提出了一个问题。
  • 我打破了我其它代理方法的命名规则。

相反的,我继续为了更简单并且更加的容易理解而打破了约定:

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

我们能够为了特殊的语法而战,但是当你遇到那个方法时,你应该立刻就知道它是做什么的?如何来使用它。我认为,那样会更好。

李远超翻译于 5天前

0人顶

 翻译的不错哦!

通知

通知是委托协议的另一部分。我认为,如果你使用委托协议(如果合适,你应当用它),你最好加上通知,不然它不算完整。 

在 MGTileMenu 中,你可以在 MGTileMenuController 的接口文件 中找到的通知。

Khiyuan
翻译于 1个月前

0人顶

 翻译的不错哦!

规则 23:通知和代理方法并行

在代理方法(注意;不是数据源方法)和通知之间有一个剪不断理还乱的联系。在你的代码中同样的地方你会同时用到它们,并且起到同样的作用。

如果一个代理方法告诉代理发生了一些事情,你通常应该为了起到相同的作用发送一个通知。就像代理方法一样加上通知,去除掉模棱两可的方法,并且落实你的通知列表。

代理方法的参数应该和通知的userInfo的内容相匹配,这是很明显的除非你作为对象来传递sender,而不是捆绑在字典信息中。

代理方法:

1 - (void)tileMenuWillDisplay:(MGTileMenuController *)tileMenu;
2 - (void)tileMenuDidDisplay:(MGTileMenuController *)tileMenu;

与之对应的通知:

1 extern NSString *MGTileMenuWillDisplayNotification; // menu will be shown
2 extern NSString *MGTileMenuDidDisplayNotification; // menu has been shown

李远超翻译于 5天前

1人顶

 翻译的不错哦!

其它翻译版本(1)

Rule 24: 通知关联的 userInfo 要尽量详细

尽量为通知提供有用的信息。记住:通知接收方(很)可能与你组件里面的代理方法或者数据源链完全无关。

你要想想到底哪些信息会有用,并提供相应的信息。至少,你必需确保代理方法的所有参数都包含在 userInfo 对象里。

代理方法:

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

相应的通知:

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

daxiaoming200翻译于 9天前

0人顶

 翻译的不错哦!

Rule 25: 最终测试

最终有些事我们已经知道了. 软件工程和敬业精神的第101条: 确保它真地有效.

是否用正式TDD测试取决于你,但是测试是不可缺少的。每个可选的委托方法。每一个发布通知定制一个在每一个可能的组合组件提供了一千微妙的问题机会

可能有一些缺陷找到他们,解决这些问题如果你的时间,切功能而不是调试要受的苦没有出货错误

寂寞沙洲
翻译于 1个月前

0人顶

 翻译的不错哦!

最后的思考

我制定上述的规则是通过我自己这几年在创建components和APIs时所犯的错误中总结的。我也努力的尝试的遵守我制定的规则,但是难免的在某些情况下我没有。

虽然不可能在每个场景or每个事件中应用到这些规则,但是如果尽可能的遵循这些规则,你将可能创造出一个设计良好的,灵活,可重用的components。让其他人一起享用的components。

你也许想要一个简单的提纲关于这些规则,下面的图片就是。我有全尺寸的图片版本托管在Flickr上。

如果你喜欢发布自己的components给别人去使用,就像我的MGTileMenu一样。那么也许你也想去读读我发的open source code(开源代码),其中几个点也许会触及到。这篇文章也讨论了一些README文件,协议选择的相关事宜。

Lohanry

Lohanry
翻译于 1个月前

0人顶

 翻译的不错哦!

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们

iOS开发之ARC(自动引用计数) – 技术翻译 – 开源中国 OSChina.NET

自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于管理Objective-C中的对象。它废弃了显式的retain、release和autorelease消息,而且在两个平台的表现一致。

由于有限的内存以及手持设备续航能力的限制,iOS应用程序中的Objective-C对象的管理一直颇有挑战性。为了处理这些问题,苹果提出了一个方案——“自动引用计数”(ARC)。在这篇文章中,我会把ARC和显式的retain/release以及垃圾回收进行对比,除此之外还会展示如何在一个iOS项目中使用它,并且探讨一些ARC的使用准则。

读者应当拥有Objective-C和Xcode IDE的使用经验。

王宇龙
翻译于 10天前

0人顶

 翻译的不错哦!

通过消息传送来实现

首先,我通过显示的消息传送来管理ObjC对象。我用alloc和init消息来创建对象(如图 1)。我发送retain消息来保持一个对象,并且发送release消息来释放掉它。通过alloc/init创建的ObjC对象会有一个内部的值为1的引用计数。retain消息会使这个引用计数加1,然而,release消息会使这个引用计数减1.当这个引用计数为0时,这个对象会自动销毁,释放它所持有的内存。


Figure 1.

我们也可以用一个工厂方法来创建ObjC对象。这样就标记了这个对象是自动释放的,将他的指针加到自动释放池(如图 2)。我们可以通过autorelease消息来实现对alloc/init的对象达到发送release消息的目的。


Figure 2.

李远超
翻译于 8天前

0人顶

 翻译的不错哦!

在每一个事件周期中,自动释放内存池都会去检测自身的对象指针集。当它发现超出其作用域并且引用记数为1的对象,它就会通过发送一个release消息释放这个对象。当不想释放这个对象时,我们可以发送一个或多个retain消息给这个对象。否则,我们必须让这个对象发送的retain和release消息一样多,才能将它释放。

显式发送消息的方式仍然是iOS应用程序中管理ObjC对象的一种有效方法。它一般不会花费很多精力,可以很容易的定位bug,同时拥有性能好的特点。

在另一方面,显式发送消息的方式很容易导致出错。当retain和release消息不相等时,它会导致内存泄露或EXC_BAD_ACCESS错误。另外,显式地释放一个已经释放了的对象也会导致EXC_BAD_ACCESS错误。并且,对象容器(如数组,集合等)可能并不会对它包含的引用记数大于1的对象运行该对象的构造函数。

showme
翻译于 8天前

0人顶

 翻译的不错哦!

使用垃圾回收管理

MacOS X 10.5 (Leopard) 给我们另一个管理ObjC对象的方法— 垃圾回收。这里,每一个Cocoa应用程序得到自己的作为次级线程运行的收集服务  (Figure 3)。


Figure 3.

这个服务标识所有在一起动就创建的根对象,然后跟踪每一个后来创建的对象。它检查每一个对象的范围以及对根对象的强引用。如果对象有这些特性,那么收集服务将它保留下来(用蓝色标记)。否则,它使用一个finalize消息释放这个对象(用红色标记)。

纶巾客
翻译于 8天前

0人顶

 翻译的不错哦!

收集服务是保守的。当必须保证高性能时,它可以被中断,甚至暂停 。它是一个分代的服务。他假定最新被创建的对象寿命最短。

通过类NSGarbageCollector来使用收集服务。使用这个类,我能够禁用这个服务或者改变它的行为。我甚至能指定新的根对象或者重置服务本身。

垃圾回收移除了显式的保留和释放消息的需要。它能够降低野指针也能够防止空指针。换句话说,它需要所有定制的ObjC对象被更新。清除代码必须进入到finalize方法,而不是 dealloc方法。ObjC对象也必须向他的父发送一个finalize消息。

接下来,收集服务需要知道何时一个对象的引用是弱的。否则,他假设所有的引用都是强的。这可能导致循环引用和内存泄漏。这个服务也忽略使用withmalloc()创建的对象:那些对象应该被手动释放或者使用Cocoa函数NSAllocateCollectable()来创建。

最后,这个服务依然会导致性能受到影响,尽管他是保守的。这就是垃圾回收在iOS上缺席的原因。

纶巾客
翻译于 8天前

1人顶

 翻译的不错哦!

进入 ARC

ARC是一种全新的方式,它拥有很多垃圾回收机制的优点,但却没有那样的性能损耗。

从内部来看,ARC并不是一项运行时的服务。实际上它是由新的Clang front-end提供的两段过程。图4显示了这两段过程。在front-end段时,Clang检查每个预处理文件的对象和属性。然后它跟据一些固定的规则将正确的retain,release和autorelease语句加入其中。

图4.

举例来说,如果对象被分配内存并处于一个方法当中,它会在这个方法的结尾处获得一个release语句。如果是一个类属性,它的release语句会加入到类的dealloc方法中。如果这个对象是用来返回的或者它是一个容器对象,它会加入一个autorelease语句。又如果这个对象是弱引用,把它放在一边不管它。

showme
翻译于 8天前

1人顶

 翻译的不错哦!

前端也为非局部对象插入保留语句。他更新所有使用@property指示符声明的访问器。他添加了对父的dealloc的调用,并且他报告任何的显式的管理调用和任何不清晰的所有权。

在优化阶段,Clang 使修改过的代码遵从加载平衡。它统计每一个对象保留和释放的调用,然后把它们缩减到优化的最小值。这避免了过度的保留和释放,能够在性能上产生影响。

为了描述, 看看在Listing One中的实例代码。

Listing One

01 @class Bar;
02 @interface Foo
03 {
04 @private
05     NSString *myStr;
06 }
07 @property(readonly) NSString *myStr;
08   
09 - (Bar *)foo2Bar:(NSString *)aStr;
10 - (Bar *)makeBar;
11 //...
12 @end
13   
14   
15 @implementation Foo;
16 @dynamic myStr;
17   
18 – (Bar *)foo2Bar:(NSString *)aStr
19 {
20     Bar *tBar;
21       
22     if (![self.myStr isEqualToString:aStr])
23     {
24         myStr = aStr;
25     }  
26     return ([self makeBar]);
27 }
28   
29 - (Bar *)makeBar
30 {
31     Bar *tBar
32     //...
33     //... conversion code goes here
34     //...
35     return (tBar);
36 }
37 //...
38 @end

在这里,我展示了一个没有任何保留和释放消息的ObjC类。它有一个私有的属性myStr,它是一个NSString(第5行)的实例.它声明了一个只读的getter, 也命名为myStr(第7行).它定义了一个修饰符foo2Bar和一个内部函数makeBar(18-36行)。这个类也使用@class指示符导入了类Bar的header(第1行)。 

纶巾客翻译于 8天前

0人顶

 翻译的不错哦!

列表2列出了相同简单的经过ARC处理后的代码。

列表2

01 @class Bar;
02 @interface Foo
03 {
04 @private
05     NSString *myStr;
06 }
07 @property (readonly) NSString *myStr;
08   
09 - (Bar *)foo2Bar:(NSString *)aStr;
10 - (Bar *)makeBar;
11 //...
12 @end
13   
14   
15 @implementation Foo;
16 @dynamic myStr;
17   
18 – (Bar *)foo2Bar:(NSString *)aStr
19 {
20     Bar *tBar;
21       
22     if (![self.myStr isEqualToString:aStr])
23     {
24         [aStr retain];
25         [myStr release];
26         myStr = aStr;
27     }  
28     return ([self makeBar]);
29 }
30   
31 - (Bar *)makeBar
32 {
33     Bar *tBar
34     //...
35     //... conversion code goes here
36     //...
37     [tBar autorelease];
38     return (tBar);
39 }
40 //...
41   
42 - (void)dealloc
43 {
44     [myStr release];
45     [super dealloc];
46 }
47 @end

类接口并没有被改变。但是它的foo2Bar被新加入了两行代码。一个语句发送一个release消息给属性myStr(行24)。另一个发送一个retain消息给参数aStr(行25)。makeBar函数在返回之前发送一个autorelease消息给局部变量tBar,并将tBar作为返回值。最后,ARC重写了类的dealloc方法。在这个方法中,它释放了属性myStr(行44)并且调用父类的dealloc方法(行45)。如果一个dealloc方法已经存在,ARC会将它的代码作适当更新。

showme翻译于 7天前

0人顶

 翻译的不错哦!

因为ARC独自决定ObjC对象如何被管理,它省掉了开发类代码的时间。它阻止了任何野指针和空指针。它甚至能够基于文件来禁用。最后一个特征让程序员可以复用被证明是稳定的遗留代码。

但是Clang编译器被构建在LLVM 3.0中,它只能在Xcode 4.2或者更新的版本中获得.对于ARC的优化的运行时支持也仅仅出现在MacOS X 10.7 (Lion) 和 iOS 5.0。在iOS 4.3中通过粘合代码使用ARC是可能的。在后来提供的不使用任何弱指针的二进制文件中使用ARC也是可能的。

然后,ARC只对ObjC代码起作用。对于PyObjC和AppleScriptObjC代码没有任何效果。但是,它的确影响那些桥接PyObjC,ASOC类到Cocoa的底层ObjC对象。也值得注意的是,一些第三方的框架在使用ARC开启的编译的时候也可能会导致问题 。请确保在更新版本时联系框架的开发者。

纶巾客
翻译于 7天前

0人顶

 翻译的不错哦!

为ARC做准备

在一个iOS项目中有两个方法支持ARC。一个是使用ARC-enabled的模板创建项目。另一个是使用Xcode改写一个现存的项目。

假设你想开始一个新的iOS项目。启动Xcode,从文件菜单里选择新项目。在新建对话框中 (Figure 5),选择一个项目模板(在这个例子中,单视图项目),点击Next查看项目选项。在界面的字段中输入项目名称,公司ID,以及类前缀。 然后勾选Use Automatic Reference Counting (Figure 6). 点击Next查看项目位置。设置位置和源代码仓库 (可选),然后点击Create创建项目本身。


Figure 5.


Figure 6.

为了验证ARC是否被开启,在项目窗口中的组和文件面板中选择项目图标 (Figure 7).从Target Setting工具条中点击Build Settings。,然后点击那个工具条下边的All。向下滚动并且定位到设置组Apple LLVM computer 3.0 — Language。 查找条目Objective-C Automatic Reference Counting — 它的值应该是Yes.


Figure 7.

纶巾客
翻译于 8天前

0人顶

 翻译的不错哦!

如果你想将一个已经存在的iOS工程转化成ARC要怎样做呢?打开这个工程进入Xcode并且点击Edit菜单。从Refactor子菜单(Figure 8),选中Convert to Objective-C ARC…,会弹出另一个帮助对话框,进行相应的操作步骤。

图8

点击Next按钮可以看到一个构建目标列表(图9)。选中一个目标并点击Precheck来开始重构工程。如果重构失败,Xcode会警示用户,并在工程窗口中列出相应的重构错误。

图9

showme
翻译于 7天前

0人顶

 翻译的不错哦!

如果重构成功,Xcode将进入对比模式(如图 10)。这个帮助对话框分割成三个面板。左面的面板展示了涉及到的文件,默认是被选中的。中间的面板展示了源文件,右边的面板展示了修改后的文件。检查建议的改变并且点击Save提交他们或者点击Cancel放弃修改。无论如何请确保有一个代码的备份。否则,你将不能把工程文件还原到ARC之前的状态。


图片 10.

如果你想从ARC中移除一些工程文件会怎么样?如果是这样,在Xcode是对比模式时取消文件旁边复选框的选中状态(看图10)。如果这个文件可以加入到重构工程中,在文件面板的分组中选中工程图标。点击Target Settings 面板中Build Phases,并且向下滚动找到Compile Sources分组(如图 11)。点击分组头选择要移除的文件。然后点击Compile Flags列并且在模态对话框中输入-fno-objc-arc。点击Done设置标记,它将在每个文件中出现。


图片 11.

李远超
翻译于 7天前

0人顶

 翻译的不错哦!

ARC指导方针

当从头开始写ObjC代码的时候,你应该确保代码与ARC兼容。代码必须给ARC完成工作所必要的线索。否则,他可能在错误的地方插入代码,或者更糟,他可能在代码中报告错误。

这些是写ARC兼容代码的指导方针。这些Apple官方文档和博客文章被编辑放在文章的最后。 

  • 让ARC决定如何以及何时保留或者释放对象。
    如前所述,ARC前端捕获所有的显式的保留和释放消息。 他将把它们当做错误报告,并且你将不得不手动删除它们以便让项目可以编译。因此永远也不要发送保留或者释放消息给ObjC对象。永远不要使用retainCount消息去检查对象的保留状态,因为那个消息不再是可靠的。另外,不要使用@selector指令去调用对象的保留和释放方法。
    至于@property访问器,不使用assign,copy, retain特性来声明他们。让ARC决定给每一个访问器什么特性
  • 让ARC管理自动释放池和它的对象。
    再一次声明,ARC前端补货所有的显式的自动释放消息。确保从你的ObjC代码中排除掉这些消息。也不要使用NSAutoreleasePool去创建池。相反,使用@autorelease指令标记池的位置和的范围。这告诉前端插入为ARC优化过的创建和释放池的代码。
    考虑一下在Listing Three中的例子代码。

    Listing Three

    01 void main()
    02 {
    03     NSArray *tArgs = [[NSProcessInfo processInfo]
    04         arguments];
    05     unsigned tCnt, tLmt = [tArgs count];
    06       
    07     @autorelease
    08     {
    09         for (tCnt = 0; tCnt < tLmt; tCnt++)
    10         {
    11             @autorelease
    12             {
    13             NSString *tDat;
    14             NSString *tNom;
    15               
    16             tNom = [tArgs objectAtIndex:tCnt];
    17             tDat = [[[NSString alloc]
    18                 initWithContentsOfFile:tNom]
    19                 autorelease];
    20               
    21             // Process the file, creating and
    22             // autoreleasing more objects
    23             }
    24         }
    25       
    26     // Do more tasks, creating and autoreleasing
    27     // more objects
    28     }
    29       
    30     // Do whatever cleanup is needed
    31     exit (EXIT_SUCCESS);
    32 }

纶巾客翻译于 7天前

0人顶

 翻译的不错哦!

在这里,main()入口函数使用了两个自动释放池。第一个池出现在函数的开始位置(第7行),他一直保持活动状态直到函数结束(第28行). 任何被这个函数标记为自动释放的对象都进入到这个池中。

第二个池出现在每一个for循环的开始处(11-23行). 当循环结束时,他释放这个池并且创建一个新的。在每一次循环中被标记为自动释放的对象进入到这个池中,而不是第一个池。

  • 显式的声明任何弱的和非保留引用。
    ARC假定所有的对象引用都是强的。当然,有一些例外, 最好的例子就是窗口视图(Figure 12)。这个视图保持每一个子视图和他包含的widget的强引用。换句话说,每一个子视图和widget仅仅保持一个对父视图的弱引用。


    Figure 12.

纶巾客
翻译于 7天前

0人顶

 翻译的不错哦!

然而,两个对象之间相互强引用是可能的。这种情况称为循环引用。考虑列表 4中的例子。

列表 4

01 @interface FooLink
02 {
03     FooLink *fooNext;
04     FooLink *fooPrev;
05     NSData *fooData;
06     NSInteger fooID;
07 }
08 @property(readwrite, assign) FooLink *nextLink;
09 @property(readonly) FooLink *prevLink;
10  
11 - (NSData *)getData;
12 - (void)addToLink:(NSData *)aDat;
13 - (FooLink *)searchForLink:(NSInteger)anID;
14 - (BOOL)hasLink:(NSInteger )anID;
15 //...
16 @end

类FooLink实现了一个双向链表,每个节点拥有一个NSData对象。属性fooNext和fooPrev都指向另外的FooLink对象。由于两个指针形成了强引用,ARC将不会销毁这个对象。

为了阻止这种事发生,声明其中一个属性为弱引用。在列表 5中,我在属性fooPrev前面加了一个__weak指令(第四行)。这样,当fooNext指向空并且FooLink对象超出范围时,ARC可以安全的给这个对象发送一个release消息。

列表 5

01 @interface FooLink
02 {
03     FooLink *fooNext;
04     __weak FooLink *fooPrev;
05     NSData *fooData;
06     NSInteger fooID;
07 }
08 @property(readwrite, assign) FooLink *nextLink;
09 @property(readonly) FooLink *prevLink;
10  
11 - (NSData *)getData;
12 - (void)addToLink:(NSData *)aDat;
13 - (FooLink *)searchForLink:(NSInteger)anID;
14 - (BOOL)hasLink:(NSInteger )anID;
15 //...
16 @end

李远超翻译于 7天前

0人顶

 翻译的不错哦!

__weak指示符既可以声明弱引用,也可以声明空引用。另一个指示符__unsafe_unretained 声明弱引用,而不是空引用。如果你计划自己处理空引用,那么使用这个指示符。 但是一定要去处理,否则你将最终会内存泄露。

当然还有另外的一些编译器指示符,但是前面两个是你经常会用到的。

  • 避免转换ObjC对象到C指针。
    指向ObjC对象的指针式是一般的typeid的指针。为了把他们当做一个C-routine的输入,你可能可能转换这个指针如Listing Six (3-9行)所示。这里,我使用了工厂方法stringWithString创建了一个NSString实例。接着我映射它到一个整形指针,并且把它传递给doFoo。

    Listing Six

    01 // Recasting an ObjC object pointer
    02 {
    03 id tStr;
    04 int *tPtr;
    05  
    06 tStr = [NSString stringWithString:@"foobar"];
    07 tPtr = (int *)tStr;
    08  
    09 doFoo(tPtr);
    10 }
    11  
    12 // Using a CFObject
    13 {
    14 CFStringRef tStr;
    15  
    16 tStr = CFSTR("foobar");
    17 doFoo(tStr);
    18 }

纶巾客翻译于 7天前

0人顶

 翻译的不错哦!

但是,转换阻止ARC正确的管理对象。ARC将不知道什么时候去保留对象,什么时候释放对象。此外转换指针可能最终指向一个无效的对象。因此,不要转换,使用核心的基础APIs 去创建C兼容的对象。(14-17行).

  • 避免将ObjC对象作为C结构体的字段。
    在一个C结构体中我们能够使用ObjC对象作为字段。Listing Seven的示例中的struct就有两个这样的字段(4-5行):一个是NSString实例,另一个是NSURL实例。如果 ObjC对象是自定义的,我们可能我们可能使用@def指示符渲染他的属性的可见性。

    Listing Seven

    01 // A C struct with ObjC fields
    02 typedef struct FooStruct
    03 {
    04     NSString *fooName;
    05     NSURL *fooPath;
    06     int fooCount;
    07     char *fooData;
    08 } Foo;
    09  
    10 // A C struct with CoreFoundation fields
    11 typedef struct BarStruct
    12 {
    13     CFStringRef *barName;
    14     CFURLRef *barPath;
    15     int barCount;
    16     char *barData;
    17 } Bar;
    18  
    19 // The C struct rewritten as an ObjC class.
    20 @interface BarClass
    21 {
    22     NSString *fooName;
    23     NSURL *fooPath;
    24     int fooCount;
    25     char *fooData;
    26 }
    27 //...property accessors goes here
    28 @end

    但是就是在这里,ARC将不能够管理在结构体中的ObjC对象。它可能不能识别对那些对象的引用也不能为那些对象提供正确的保留,释放和自动释放语句。 同样的,使用核心的基础APIs去为结构体创建对象(13-16行)。或者将这个结构体重写为一个简单的ObjC类(20-28行)。

  • 自己管理所有的C和CF对象。

    就像垃圾回收,ARC忽略所有使用stdlib和核心基础APIs创建的对象。他将不插入被用来创建和释放结构体或者联合体的malloc()和free()调用。也不会为CF对象插入CFRetain()和CFRelease()调用。

    确保自己添加提供这些调用。小心平衡得为每一个malloc()调用一个free()并且为每一个CFRetain()调用一个CFRelease()。也确保避免在同一个对象上调用两次free()或者CFRelease()。

纶巾客翻译于 7天前

0人顶

 翻译的不错哦!

总结

自动引用计数是一个创新的管理在MacOS X 10.7和iOS 5上的ObjC对象的方法。它远离了显式的保留,释放以及自动释放消息,并且他降低了潜在的内存泄露和空指针。它提供了优化的自动释放池,避免了垃圾回收那样的性能开销。他的行为在两个平台是一致的。但是,ARC有很多方面太复杂以至于不能在这篇文章中覆盖到。为了学习了解更多的与ARC相关的东西,请查看下边的参考列表。

纶巾客
翻译于 7天前

1人顶

 翻译的不错哦!

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们

介绍一个成功的 Git 分支模型 – 技术翻译 – 开源中国 OSChina.NET

在这篇文章中,我提出一个开发模型。我已经将这个开发模型引入到我所有的项目里(无论在工作还是私人)已经一年有余,并且它被证明是非常成功的。我打算写这些已经很久了,但我一直找不到时间来做,现在终于有时间了。我不会讲任何项目的具体细节,仅是关于分支策略和释放管理相关内容。

它主要体现了Git对我们源代码版本的管理。

showme
翻译于 8天前

2人顶

 翻译的不错哦!

为何是Git?

对于Git与其他集中式代码管理工具相比的优缺点的全面讨论,请参见这里。这样的争论总是喋喋不休。作为一个开发者,与现今的其他开发工具相比较,我更喜欢Git。Git真得改变了开发者对于合并和分支的思考。我曾经使用经典的CVS/Subversion,然而每次的合并/分支和其他行为总让人担惊受怕(“小心合并里的冲突,简直要命!”)。

但是对于Git来说,这些行为非常简单和搞笑,它们被认为是日常工作中的核心部分。例如,在很多CVS/Subversion里,分支与合并总是在后面的章节中被讨论(对于高级用户使用),然而在每个Git中,在第3章就已经完全涵盖了(作为基础)。

简单和重复的特性带来的结果是:分支与合并不再是什么可以害怕的东西。分支/合并被认为对于版本管理工具比其他功能更重要。

关于工具,不再多说,让我们直接看开发模型吧。这个模型并不是如下模型:在管理软件开发进度方面,面对每个开发过程,每个队员必须按一定次序开发。

xue777hua
翻译于 8天前

1人顶

 翻译的不错哦!

分布式而非集中式

对于这种分支模型,我们设置了一个版本库,它运转良好,这是一个"事实上" 版本库。不过请注意,这个版本库只是被认为是中心版本库(因为Git是一个分布式版本管理系统,从技术上来讲,并没有一个中心版本库)。我们将把这个版本库称为原始库,这个名字对所有的Git用户来说都很容易理解。

每个开发者都对origin库拉代码和提交代码。但是除了集中式的存取代码关系,每个开发者也可以从子团队的其他队友那里获得代码版本变更。例如,对于2个或多个开发者一起完成的大版本变更,为了防止过早地向origin库提交工作内容,这种机制就变得非常有用。在上述途中,有如下子团队:Alice和Bob,Alice和David,Clair和David。

从技术上将,这意味着,Alice创建了一个Git的远程节点,而对于Bob,该节点指向了Bob的版本库,反之亦然。

xue777hua
翻译于 8天前

1人顶

 翻译的不错哦!

主分支

在核心部分,研发模型很大程度上靠其他现有模型支撑的。中心库有2个可一直延续的分支:

  • master分支
  • develop分支

每个Git用户都要熟悉原始的master分支。与master分支并行的另一个分支,我们称之为develop分支。

我们把原始库/master库认作为主分支,HEAD的源代码存在于此版本中,并且随时都是一个预备生产状态。

xue777hua
翻译于 7天前

1人顶

 翻译的不错哦!

我们把origin/develop库认为是主分支,该分支HEAD源码始终体现下个发布版的最新软件变更。有人称这个为“集成分支”,而这是每晚自动构建得来的。

当develop分支的源码到达了一个稳定状态待发布,所有的代码变更需要以某种方式合并到master分支,然后标记一个版本号。如何操作将在稍后详细介绍。

所以,每次变更都合并到了master,这就是新产品的定义。在这一点,我们倾向于严格执行这一点,从而,理论上,每当对master有一个提交操作,我们就可以使用Git钩子脚本来自动构建并且发布软件到生产服务器。

xue777hua
翻译于 5天前

1人顶

 翻译的不错哦!

辅助性分支

我们的开发模型使用了各种辅助性分支,这些分支与关键分支(master和develop)一起,用来支持团队成员们并行开发,使得易于追踪功能,协助生产发布环境准备,以及快速修复实时在线问题。与关键分支不同,这些分支总是有一个有限的生命期,因为他们最终会被移除。

我们用到的分支类型包括:

  • 功能分支
  • 发布分支
  • 热修复分支

每一种分支有一个特定目的,并且受限于严格到规则,比如:可以用哪些分支作为源分支,哪些分支能作为合并目标。我们马上将进行演练。

从技术角度来看,这些分支绝不是特殊分支。分支的类型基于我们使用的方法来进行分类。它们理所当然是普通的Git分支。

Lax
翻译于 8天前

1人顶

 翻译的不错哦!

功能分支

可能是develop分支的分支版本,最终必须合并到develop分支中。

分支命名规则:除了master、develop、release-*、orhotfix-*之外,其他命名均可。

功能分支(有时被称为topic分支)通常为即将发布或者未来发布版开发新的功能。当新功能开始研发,包含该功能的发布版本在这个还是无法确定发布时间的。功能版本的实质是只要这个功能处于开发状态它就会存在,但是最终会或合并到develop分支(确定将新功能添加到不久的发布版中)或取消(譬如一次令人失望的测试)。

功能分支通常存在于开发者的软件库,而不是在源代码库中。

Tocy
翻译于 6天前

1人顶

 翻译的不错哦!

创建一个功能分支

开始一项功能的开发工作时,基于develop创建分支。

1 $ git checkout -b myfeature develop
2 Switched to a new branch "myfeature"

合并一个功能到develop分支

完成的功能可以合并进develop分支,以明确加入到未来的发布:

1 $ git checkout develop
2 Switched to branch 'develop'
3 $ git merge --no-ff myfeature
4 Updating ea1b82a..05e9557
5 (Summary of changes)
6 $ git branch -d myfeature
7 Deleted branch myfeature (was 05e9557).
8 $ git push origin develop

–no-ff标志导致合并操作创建一个新commit对象,即使该合并操作可以fast-forward。这避免了丢失这个功能分支存在的历史信息,将该功能的所有提交组合在一起。 比较:

后一种情况,不可能从Git历史中看到哪些提交一起实现了一个功能——你必须手工阅读全部的日志信息。如果对整个功能进行回退 (比如一组提交),后一种方式会是一种真正头痛的问题,而使用–no-ffflag的情况则很容易.

是的,它会创建一个新的(空)提交对象,但是收益远大于开销。

不幸的是,我还没找到一种方法,让–no-ff时作为合并操作的默认选项,但它应该是可行的。

Lax翻译于 6天前

2人顶

 翻译的不错哦!

Release 分支

Release分支可能从develop分支分离而来,但是一定要合并到develop和master分支上,它的习惯命名方式为:release-*。

Release分支是为新产品的发布做准备的。它允许我们在最后时刻做一些细小的修改。他们允许小bugs的修改和准备发布元数据(版本号,开发时间等等)。当在Release分支完成这些所有工作以后,对于下一次打的发布,develop分支接收features会更加明确。

develop分支创建新的Release分支的关键时刻是develop分支达到了发布的理想状态。至少所有这次要发布的features必须在这个点及时合并到develop分支。对于所有未来准备发布的features必须等到Release分支创建以后再合并。

Release分支创建的时候要为即将发行版本分配一个版本号,一点都不早。直到那时,develop分支反映的变化都是为了下一个发行版,但是在Release分支创建之前,下一个发行版到底叫0.3还是1.0是不明确的。这个决定是在Release分支创建时根据项目在版本号上的规则制定的。

FGQ
翻译于 4天前

2人顶

 翻译的不错哦!

创建一个release分支

Release分支是从develop分支创建的。例如,当前产品的发行版本号为1.1.5,同事我们有一个大的版本即将发行。develop 分支已经为下次发行做好了准备,我们得决定下一个版本是1.2(而不是1.1.6或者2.0)。所以我们将Release分支分离出来,给一个能够反映新版本号的分支名。

1 $ git checkout -b release-1.2 develop
2 Switched to a new branch "release-1.2"
3 $ ./bump-version.sh 1.2
4 Files modified successfully, version bumped to 1.2.
5 $ git commit -a -m "Bumped version number to 1.2"
6 [release-1.2 74d9424] Bumped version number to 1.2
7 1 files changed, 1 insertions(+), 1 deletions(-)

创建新分支以后,切换到该分支,添加版本号。这里,bump-version.sh 是一个虚构的shell脚本,它可以复制一些文件来反映新的版本(这当然可以手动改变–目的就是修改一些文件)。然后版本号被提交。

这个新分支可能会存在一段时间,直到该发行版到达它的预定目标。在此期间,bug的修复可能被提交到该分支上(而不是提交到develop分支上)。在这里严格禁止增加大的新features。他们必须合并到develop分支上,然后等待下一次大的发行版。

FGQ翻译于 4天前

2人顶

 翻译的不错哦!

完成一个release分支

当一个release分支准备好成为一个真正的发行版的时候,有一些工作必须完成。首先,release分支要合并到master上(因为每一次提交到master上的都是一个新定义的发行版,记住)。然后,提交到master上必须打一个标签,以便以后更加方便的引用这个历史版本。最后,在release分支上的修改必须合并到develop分支上,以便未来发行版也包含这些bugs的修复。

在Git中的前两步是:

1 $ git checkout master
2 Switched to branch 'master'
3 $ git merge --no-ff release-1.2
4 Merge made by recursive.
5 (Summary of changes)
6 $ git tag -a 1.2

发行版现在已经完成,为以后引用打上标签。

编辑:你可能也想使用the-sor-u <key>flags来标记你的标签。

为了是修改保持在release分支上,我们需要合并这些到develop分支上去,在Git上:

1 $ git checkout develop
2 Switched to branch 'develop'
3 $ git merge --no-ff release-1.2
4 Merge made by recursive.
5 (Summary of changes)

这个步骤可能会导致合并冲突(可能由于改变版本号更是如此)。如果是这样,修复它然后提交。

现在我们真正的完成了,这个release分支将被删除,因为我们不再需要它了。

1 $ git branch -d release-1.2
2 Deleted branch release-1.2 (was ff452fe).

FGQ翻译于 4天前

1人顶

 翻译的不错哦!

热修复分支

可以基于master分支,必须合并回develop和master分支。
分支名约定:hotfix-*

热修复分支与发布分支很相似,他们都为新的生成环境发布做准备,尽管这是未经计划的。他们来自生产环境的处于异常状态压力。当生成环境验证缺陷必须马上修复是,热修复分支可以基于master分支上对应与线上版本的tag创建。

其本质是团队成员(在develop分支上)的工作可以继续,而另一个人准备生产环境的快速修复。

Lax
翻译于 5天前

1人顶

 翻译的不错哦!

创建修补bug分支

hotfix branch(修补bug分支)是从Master分支上面分出来的。例如,1.2版本是当前生产环境的版本并且有bug。但是开发分支(develop)变化还不稳定。我们需要分出来一个修补bug分支(hotfix branch)来解决这种情况。

1 $ git checkout -b hotfix-1.2.1 master
2 Switched to a new branch "hotfix-1.2.1"
3 $ ./bump-version.sh 1.2.1
4 Files modified successfully, version bumped to 1.2.1.
5 $ git commit -a -m "Bumped version number to 1.2.1"
6 [hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
7 1 files changed, 1 insertions(+), 1 deletions(-)

分支关闭的时侯不要忘了更新版本号(bump the version)

然后,修复bug,一次提交或者多次分开提交。

1 $ git commit -m "Fixed severe production problem"
2 [hotfix-1.2.1 abbe5d6] Fixed severe production problem
3 5 files changed, 32 insertions(+), 17 deletions(-)

lidashuang翻译于 5天前

2人顶

 翻译的不错哦!

其它翻译版本(1)

完成一个hotfix分支

完成一个bugfix之后,需要把butfix合并到master和develop分支去,这样就可以保证修复的这个bug也包含到下一个发行版中。这一点和完成release分支很相似。

首先,更新master并对release打上tag:

1 $ git checkout master
2 Switched to branch 'master'
3 $ git merge --no-ff hotfix-1.2.1
4 Merge made by recursive.
5 (Summary of changes)
6 $ git tag -a 1.2.1

编辑:你可能也会想使用 -sor-u <key>参数来对你的tag进行加密

下一步,把bugfix添加到develop分支中:

1 $ git checkout develop
2 Switched to branch 'develop'
3 $ git merge --no-ff hotfix-1.2.1
4 Merge made by recursive.
5 (Summary of changes)

规则的一个例外是:
如果一个release分支已经存在,那么应该把hotfix合并到这个release分支,而不是合并到develop分支。当release分支完成后,
bugfix分支合并回release分支也会使得bugfix被合并到develop分支。(如果在develop分支的工作急需这个bugfix,等不到release分支的完成,那你也可以把bugfix合并到develop分支)

最后,删除临时分支:

1 $ git branch -d hotfix-1.2.1
2 Deleted branch hotfix-1.2.1 (was abbe5d6).

乔学士翻译于 4天前

1人顶

 翻译的不错哦!

摘要

尽管这个分支模型没有任何震撼的新东西, 文章开头的图表在我们的项目中表现出惊人的实用性。它形成了一个优雅的思维模型,易于领悟并使团队成员发展出对分支和发布过程的共同理解。

这里提供一份高质量PDF格式图表。去吧,把它挂载墙上以便能随时快速参考。

更新: 如果有人需要: 这是主图表的gitflow-model.src.key (Apple Keynote格式).


Git-branching-model.pdf

Lax
翻译于 7天前

2人顶

 翻译的不错哦!

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们