1.代码引用和项目编译配置
不能引用项目外的代码文件,其他人会编译错误
项目一 clone 下来,编译就报这个错误了。因为你引用了你电脑上 Downloads 文件夹下的一个库,可我电脑上没有这个库,就报错了。所以得注意每次拖动文件到项目下的时候,要选择 Copy :
同时,最好时常检查一下 Build Phases 标签下的编译文件详情:
一旦你的同事说代码找不到的时候,就来这里看看每个编译文件的地址。如果是引用了项目外部的文件,其路径会特别长一点。而且也有 “../../../” 之类的,就特别容易找到。
这个问题不容易自己察觉,因为自己这边是能编译通过的。但当项目要团队合作,要开源出去的话,就值得好好重视了。
2.属性修饰符的建议
//这个地方更建议把assign换为weak
@property (nonatomic, assign) YYTextView *textView;
在arc中,基本变量之外的属性 弱引用最好使用weak,来避免野指针的出现,weak可以在指向的对象dealloc的时候自动置为nil。
//推荐将retain换为strong
@property (nonatomic, retain) NSMutableAttributedString *attrString;
在arc中强引用尽量使用strong,当然这里strong和retain的作用是一样的,但是为了保持代码的一致性,这里推荐使用strong。
3.多使用字面量语法
_comps.items = [NSArray arrayWithObjects:_photoBarButton, _mediaBarButton, _alarmBarButton, _brushBarButton, _voiceBarButton, _doneBarButton, nil];
生成一个数组,更推荐使用字面量语法来创建。
_comps.items = @[_photoBarButton, _mediaBarButton, _alarmBarButton, _brushBarButton, _voiceBarButton, _doneBarButton];
这种做法不仅简单,还更加安全。假如当中的_alarmBarButton为nil,那么通过第一种写法,数组还是会创建出来,只不过数组内只有2个对象,原因在于,“arrayWithObjects:”方法会依次处理各个参数,直到发现nil为止,而第二种写法会在插入nil指针的时候抛出异常,令程序终止运行,这比创建好数组后才发现数组内的元素个数少了要好很多。异常往往能更快地发现错误。
4.常量的声明
#define TAG_LOCK 1
#define TAG_UPLOADER 2
推荐使用类型常量,少使用define预处理指令。使用预处理指令有这么几个缺点:定义出来的常量没有类型信息,预处理过程会将碰到的所有TAG_LOCK替换为1。而是用类型常量恰巧能解决这两个问题。所以推荐的写法是:
static const NSInteger kLockTag = 100;
这里我们声明的常量是在实现文件内,所以推荐命名的时候在常量名称前加k;如果常量在类之外可见,推荐以类名为前缀。
5.推荐使用懒加载
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// compability with automaticallyAdjustsScrollViewInsets: http://stackoverflow.com/questions/20550019/compability-with-automaticallyadjustsscrollviewinsets
if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
[self initComps];
[self initToolbar];
[self initVertical];
[self initImageView];
[self initMediaPick];
[self setupSpeechRecognizer];
[self initAttributedString];
[self initTextView];
[[YYTextKeyboardManager defaultManager] addObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
代码当中UI控件和字符串的初始化都封装成了独立的init方法,这点不错。但是你的写法有这么几个问题:首先是在初始化的过程中,你一般都是直接访问了成员变量,例如toolbar,并且在初始化方法以外你访问的也是toolbar,而你声明的是一个属性,两者并不统一,这就使得可读性变得很差;在viewDidLoad之后,初始化的过程必然生成了对应的控件或者数据,无论这些控件或者数据是否立即有用,这会占用比较大的内存空间。 我们可以通过重写属性的get方法,来解决以上问题。 例如
- (void)initAttributedString {
NSLog(@"Jump into initAttributedString");
if (self.note) {
_attrString = [[NSMutableAttributedString alloc] initWithString:self.note.content];
} else {
_attrString = [[NSMutableAttributedString alloc] initWithString:@"请输入内容:"];
}
_attrString.yy_font = [UIFont fontWithName:@"Times New Roman" size:20];
_attrString.yy_lineSpacing = 4;
_attrString.yy_firstLineHeadIndent = 20;
}
我们可以将这个方法改造成:
- (NSMutableAttributedString *)attrString {
if (_attrString) {
return _attrString;
}
if (self.note) {
_attrString = [[NSMutableAttributedString alloc] initWithString:self.note.content];
} else {
_attrString = [[NSMutableAttributedString alloc] initWithString:@"请输入内容:"];
}
_attrString.yy_font = [UIFont fontWithName:@"Times New Roman" size:20];
_attrString.yy_lineSpacing = 4;
_attrString.yy_firstLineHeadIndent = 20;
return _attrString;
}
我们重写get方法之后,在其它需要访问的地方,都调用属性self.attrString,这样我们的代码风格就很统一了。
懒加载带来的收益:我们不再需要显式的生成和调用initAttributedString方法,只需要在使用的时候调用属性self.attrString,对象的实例化全部放在getter中,可以有效降低代码的耦合度;在使用属性之前,属性并不会提前生成,减少内存占用。
6.delegate需要校验传入参数
- (void)textViewDidEndEditing:(YYTextView *)textView {
self.navigationItem.rightBarButtonItem = nil;
}
如果你的delegate方法,只作为一个textView的委托回调,这种写法没有任何问题。但是如果你想扩展你的界面,在将来的界面中很可能出现另一个textView,这时你就必须区分这两个textView是谁回调了这个代理方法。此时,如果你之前并没有添加传入参数判断,那么你还需要将之前的textView变量名字找到,并将之前的这些逻辑转移到一个if分支内,然后才能处理新添加的textView逻辑,这时候你的思路很可能被打断。更糟糕的是,很有可能是你的小伙伴来做这件事情。所以在扩展之前就先加上参数校验是一个很好的习惯。
- (void)textViewDidEndEditing:(YYTextView *)textView {
if (textView == self.textView) {
self.navigationItem.rightBarButtonItem = nil;
}
}
7.通知的添加和移除
在我收集的代码中我看到有人这么添加和删除通知。
-(void)viewWillAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(keyboardShow:) name:UIKeyboardWillShowNotification object:nil] ;
[[NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(keyboardHidden:) name:UIKeyboardWillHideNotification object:nil] ;
}
-(void)viewWillDisappear:(BOOL)animated{
[[NSNotificationCenter defaultCenter ] removeObserver:self name:UIKeyboardWillHideNotification object:nil ] ;
[[NSNotificationCenter defaultCenter ] removeObserver:self name:UIKeyboardWillShowNotification object:nil ];
}
这样极容易出现问题。因为willAppear和Disappear出现的顺序并不一定是一对一的。所以有可能造成多次添加,多次移除通知。
我建议在 viewDidLoad里添加通知。在dealloc里移除通知。
8.构造易于测试的Category
[_userPhoto setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://220.175.104.19:8721%@",_model.userPhoto]]placeholderImage:[UIImage imageNamed:@"holderImage.jpg"]];
形如
[NSString stringWithFormat:@"http://220.175.104.19:8721%@",_model.userPhoto]
的语句都应该创建一个NSString的Category。
例如.
#import "NSString+PhotoURL.h"
static NSString * const kBaseURL = @"http://220.175.104.19:8721";
@implementation NSString (PhotoURL)
+ (NSString *)photoURLWithString:(NSString *)url
{
return [NSString stringWithFormat:@"%@%@",kBaseURL, url];
}
@end
相应的你可以很方便的编写Unit Test。
- (void)testphotoURLWithString
{
NSString *result = @"http://220.175.104.19:8721/useravatar.png";
NSString *avatar = @"useravatar.png";
XCTAssert([result isEqualToString:[NSString photoURLWithString:avatar]]);
}
9.声明常量
NSString *const kcircleModelDataCategoryName = @"categoryName";
如果常量不是全局的,请在前面加static
。否则可能会产生duplicate symbol的错误。
如果需要声明全局的常量,那么需要在.h
文件中加入。
FOUNDATION_EXPORT NSString * const kcircleModelDataCategoryName;