转载

iOS开发,事半功倍基本心得

iOS开发,事半功倍基本心得

最近一直在为公司3.0的app加班加点,前段时间总算完成了,有空坐下来写写东西。既然是第一篇关于IOS开发的文章就先写点自己这些年学到最最基本的经验吧。一些编程中的小细节很容易被忽略,但是往往细节可以让自己变得更专业。

主要是想说下 Project的结构 。由于Project里的内容是否分组或者整洁,基本不会影响做出来的APP的效果,所以常常被忽视。其实不然,在很多工作项目中(特别是大型项目),我们都会和其他程序员或者设计师合作。而设立一个整洁的项目结构对合作开发非常有帮助,对后期Bug修复或者Code Review也都会事半功倍。Project的结构就犹如家庭装潢的内部排线,排线的好坏通常是无法从房屋的外观判断的。但是一旦需要排查问题或者修改线路时,一个好的内部系统就能让一切变得井井有条,也可以体现制作者的专业性。

就根据我个人的经验判断,好的Project 结构包括了 合理的命名规则,Project内容分组,常用代码的封装,和使用有效的Comments 。下面我们就一条一条的来看。

1. 第一是合理的命名规则。

其实这条还是比较抽象的,因为不一样的项目可能会有一些不一样的需求。但是我主要想说一些最最基本和几乎通用的规则。

非常推荐使用Class prefix name就是Class的前缀名,使用前缀名能够一定程度的避免你的Class和其他Framework / Library中的内容重名,也能够让你在Import或使用Class的时候比较方便寻找。通常都会推荐用几个大写字母来作为前缀名,Apple的iOS SDK里全部都使用了Prefix,比如我们经常会用到的以NS为前缀的Foundation和Application Kit里的Class,以UI开头的UIKit,以CL开头的Core Location 等等。

在命名Class的时候尽量保证它的名字能够涵盖他的类别和用途。比如你有一个UIViewController是用来显示User Profile的, 那你就可以把它命名为在XX(前缀)UserProfile(用途)ViewController(类别)。这样的话如果在一个比较大型的工程中,你或者你的同事只需要知道用途或者类别就可以很快的锁定目标。而且我推荐把比如使用在XXUserProfileViewController内部的自定义的subview,都用XXUserProfile来前缀,其实依然是遵守上述的命名规则,XX(前缀)UserProfileAddress(用途)Cell(类别),这样能够在后面的文件分组里面起到比较好的效果,而且可以很快查到所有和UserProfile相关的内容。

在命名Method的时候也应该选择能包括这个Method功能,所需的参数类别和关系的名字,但是尽量简洁。当然简洁只是相对的,更重要的就是能够让其他程序员包括你自己在几个月后甚至一年后一看就能大约明白这个Method的用途和条件。就连Apple提供的SDK里面都有一些超长的Method,但是重要的是一看就大概知道这个Method能干什么和需要什么参数,比如从NSBitmapImageRep里面可以找到的:

- (id)initWithBitmapDataPlanes:(unsigned char **)planes        pixelsWide:(NSInteger)width      pixelsHigh:(NSInteger)height   bitsPerSample:(NSInteger)bps samplesPerPixel:(NSInteger)spp        hasAlpha:(BOOL)alpha        isPlanar:(BOOL)isPlanar  colorSpaceName:(NSString *)colorSpaceName    bitmapFormat:(NSBitmapFormat)bitmapFormat     bytesPerRow:(NSInteger)rowBytes    bitsPerPixel:(NSInteger)pixelBits; 

Apple的程序员手册里面也对Method命名规则有不少的叙述,比如说尽量不要用缩写,尽量用意义明确的词,等。

在我们3.0的App中也有一些比较长的Method,但是我们尽量保持他的含义可以像读短文一样容易理解,比如:

+ (NSMutableArray *)animateNavBarFromColor:(UIColor *)fromColor                                      toColor:(UIColor *)toColor                                    duration:(NSTimeInterval)duration; + (NSMutableAttributedString *)generateAttributedStringFromString:(NSString *)string                                                       withFontName:(NSString *)fontName                                                           fontSize:(float)fontSize                                                      textAlignment:(NSTextAlignment)alignment                                                      lineBreakMode:(NSLineBreakMode)lineBreakMode;

这两个是我们项目中得Public Method的名字,我觉得这样的Method的名字还是比较可以接受的。哪怕对我们的项目完全不理解的人也应该可以明白这两个Method的大概功能。而且一些常用的而且意义明确的名字就可以用简短的方式来表达,比如说NavigationBar,一般称作NavBar不会有任何的歧义,也遵守了简洁的规则。

2. 接下来让我介绍下,如何较好的将一个大型Project其中的内容分组。

其实不论Project的大小,我们都应该将View Controllers, Views, Objects, Frameworks, Resources,等等不同类型的文件,都按照他们的种类和具体用途进行了归类,这样在日后修改和添加内容时候都会同样的事半功倍。因为大中型的Project中势必使用了大量的View Controllers和自定义的Object Class和Custom View. 如果成百上千个文件无须的罗列在一个项目中,那种混乱程度是可以让一个原本20分钟的Bug fix延长到一个小时。当然很多时候,如果你知道部分文件名的话,Cmd+shift+O还是我们最好的朋友(所以合理的命名规则这块是非常重要)。而且分组的重要性在你不是很确定文件名的时候,比如review你同事的code或者在一位同事离职或休假的时候有紧急的bug fix,明确的分组就好像一张好的地图一样,很容易就找到你的目标。

iOS开发,事半功倍基本心得

我相信文件分组还是被相对普遍运用的,但代码的分组常常会被忽略。如同刚才所说的,你在一堆文件中总算找到你的的目标文件了。而有时候一个View Controller可能有几百上千行的代码,如果把代码也按照类型和功能分组的话,这样能进一步的提升效率,减少不必要的痛苦。?合理的使用#pragma mark - 是代码分组最简单而且最有效的方法,好像这张图片里的,

iOS开发,事半功倍基本心得

我们把代码按照他们的基本类型和功效使用#pragma mark - 来区分,比如:

#pragma mark - View Controller Life Cycle   #pragma mark - API Calls

iOS开发,事半功倍基本心得

然后每次你只要知道大概的目标代码,就可以在导航栏里点击代码这块,很容易就能找到结果。

3. 常用代码的封装。

有些朋友可能不明白什么时候是需要将一组代码封装到一个Method中,其实最简单的就是当你复制粘贴的时候,你就应该问问自己:"是不是可以把这组代码封装到一个或者多个Methods中,以便重复使用。" 这样主要能让你的Project看上去清楚简洁,而且一定意义上也节省了你项目的大小(虽然代码不会占太大的空间)。就比如我们常用的stringWithFormat或者isEqualToString等等的Methods,在他们背后都是,一大块被封装在这个Method里面的代码。只要我们理解这个Interface Method的功能,背后的代码已经不重要了。举个很简单的例子,在我们3.0的软件中,我们几乎吧所有的用户头像就变成圆形,也许大家都知道这个很简单,只需要两行代码,

    imageView.clipsToBounds = YES;       imageView.layer.cornerRadius = image.frame.size.width/2;

而基于有轻度代码洁癖的我,只要是超过一行,而且被反复被使用的代码就应该封装到一个Method中,所有就变成了这样

+ (void)roundImage:(UIImageView *)imageView {       imageView.clipsToBounds = YES;     imageView.layer.cornerRadius = imageView.frame.size.width/2; }

让我们来看一个比较复杂的例子,在我们的项目中,反复使用了Attributed String, 主要是我们有大量的用户Review, 酒店餐厅景点的简介,我们想控制String的行间距,否则会看起来太拥挤。于是我们把所需要用到的代码都分装在一个Method中:

+ (NSMutableAttributedString *)generateAttributedStringFromString:(NSString *)string              withFontName:(NSString *)fontName             fontSize:(float)fontSize             textAlignment:(NSTextAlignment)alignment             lineBreakMode:(NSLineBreakMode)lineBreakMode {  NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc]   initWithString:string];  NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];  style.lineSpacing = fontSize / 2;  style.alignment = alignment;  style.lineBreakMode = lineBreakMode;  [mutableAttributedString addAttribute:(NSString*)kCTParagraphStyleAttributeName value:style range:NSMakeRange(0, [mutableAttributedString length])];  [mutableAttributedString addAttribute:NSFontAttributeName value:[UIFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [mutableAttributedString length])];  return mutableAttributedString; } 

这样一来,一个需要7,8行来实现的功能,简单的封装就可以缩减变成1,2行。我简单的在我们的项目里所搜了一下,我们一共有42处Call了这个Method,粗略一算我们至少节约了230多行的代码。

4. 最后就是Comments(注释)的重要性。 

其实不同的程序员对Comments有着不同的理解,有些人认为Comments是基本没有意义的,因为他们觉得好的Code应该是很容易被理解的,包括其作用,类别等等都应该一目了然。这个论调确实也有一定的道理,但我不能说100%赞同。我认为Comments的作用不是让其他程序员甚至你自己理解你这段代码的作用究竟是什么,而更多的应该把这段代码的逻辑描述出来。除非是非常简单的一两行的代码,否则都应该有当时的编写的逻辑。这些逻辑在过了几个月甚至一年,哪怕你是编写者,你回头去看,都会需要一段时间才能想起来究竟当时基于什么原因选择了这样做,更不要说其他合作的同事。比如说这段简单关于某些部分Show,某些Hide的代码:

if (myPlaceView.currentTripPlan.isTemporary) {    myPlaceView.myPlacesCountLabel.hidden = YES;  myPlaceView.activityIndicator.hidden = NO;  [myPlaceView.activityIndicator startAnimating]; } else   {  myPlaceView.activityIndicator.hidden = YES;  [myPlaceView.activityIndicator stopAnimating];  myPlaceView.myPlacesCountLabel.hidden = NO;  myPlaceView.myPlacesCountLabel.text = [myPlaceView.currentTripPlan.trip.placesCount stringValue]; } 

如果直接读的话,很难明白为什么Trip plan是temporary就需要吧PlaceCountLabel隐藏和等等东西。如果加上这些注释:

//Since when user creates a new trip plan, we will save a temporary local copy of it   //so when they come back to splashboard, they will see the new trip plan before the api responses if (myPlaceView.currentTripPlan.isTemporary) {    //in this case, our api is not responded yet  //we need to show the loader, and hide the place count  myPlaceView.myPlacesCountLabel.hidden = YES;  myPlaceView.activityIndicator.hidden = NO;  [myPlaceView.activityIndicator startAnimating]; } else   {  //if current trip plan is not temporary, that means we get the data from the api  myPlaceView.activityIndicator.hidden = YES;  [myPlaceView.activityIndicator stopAnimating];  myPlaceView.myPlacesCountLabel.hidden = NO;  myPlaceView.myPlacesCountLabel.text = [myPlaceView.currentTripPlan.trip.placesCount stringValue]; } 

看了Comments之后其实是很容易理解的,Temporary是一个新建立的trip plan所拥有的Flag,而Temporary是一个Local Copy而不是从数据库读取到的。当我们获得了数据库数据后,将把所有的信息都Update并且显示。

再比如你的代码中包含了数学计算或者公式等,和一些Hard coded的数字,添加一些注释也是非常有帮助的,否则当你过了一段时间回头来看时会被那些毫无来由的数字弄的晕头转向。

正文到此结束
Loading...