一、语言规约
命名规约
-
【强制】文件名、自定义类、Protocol禁止以以下系统前缀开头。
【AC,AB,AS,AL,AU,AV,CX,CF,CK,CN,CA,CB,NS,CF,CG,CI,CL,CM,MIDI,CM,CS,CT,CV,EK,EA,GC,GK,GLK,GSS,HK,HM,AD,CG,IN,GS,LA,MK,MA,MP,MT,MS,MF,MTL,MTK,MDL,MC,NE,NK,NC,AL,EAGL,PK,PH,CA,QL,RP,SF,SCN,SL,SF,SK,SC,TW,UI,UN,VS,VT,WC,WK】 -
【参考】文件名、类、Protocol、常量、枚举等全局可见内容需要添加三个大写字符作为前缀,双字母前缀为 Apple 的类预留。
-
【强制】方法名、参数名、成员变量、局部变量都采用小写字符开头,名称中的单词首字符要大写的小驼峰形式。另外,请不要在方法名称中使用前缀(category方法除外)。
fileExistsAtPath:isDirectory:
- 【强制】如果方法代表一个对象执行的动作,则其名称应该以一个动词开头
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
-
【强制】请不要使用 “do”或者 “does”作为名称的一部分,因为这些辅助性的动词不能为名称增加更多的含义。同时,请不要在动词之前使用副词或者形容词。
-
【强制】如果方法返回接收者的某个属性,则以属性名称作为方法名。
- (NSSize)cellSize;
- 【强制】您可以使用情态动词(在动词前冠以“can”,"should","will"等),使得方法的名称更加明 确,但是请不要使用“do”或“does”这样的情态动词。
- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
- 【强制】如果调用某个方法是为了通知委托某个事件已经发生或者即将发生, 则请在方法名称 中使用“did”或者“will”这样的助动词。
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
- 【强制】如果调用某个方法是为了要求委托代表其他对象执行某件事,当然,您也可以在方法名 称中使用“did”或者“will”,但我们倾向于使用“should”。
- (BOOL)windowShouldClose:(id)sender;
常量定义
- 【强制】请使用NS_ENUM枚举类型来表示一群相互关联的整数值常量。枚举项以枚举类型为前缀。
typedef NS_ENUM(NSInteger,NSMatrixMode) {
NSMatrixModeRadio = 0,
NSMatrixModeHighlight = 1,
NSMatrixModeList = 2,
NSMatrixModeTrack = 3
} ;
- 【强制】请使用NS_OPTIONS定义一组相互关联的位移枚举常量。位移枚举常量是可以组合使用的。枚举项以枚举类型为前缀。
typedef NS_OPTIONS(NSUInteger,NSMatrixModeMask) {
NSMatrixModeMaskBorderless = 0,
NSMatrixModeMaskTitled = 1 << 0,
NSMatrixModeMaskClosable = 1 << 1,
NSMatrixModeMaskMiniaturizable = 1 << 2,
NSMatrixModeMaskResizable = 1 << 3
};
- 【强制】通常情况下,请不要使用#define 预处器理命令创建常量。对于整数值常量,请使用枚举类型创建,而对于浮点值常量,请使用const 修饰符创建。
const float NSLightGray;
- 【强制】有些符号,预处理器需要对其进行计算,以便决定是否要对某一代码块进行处理,则它 们应该使用大写字符表示。
#ifdef DEBUG
- 【强制】推荐使用常量来代替字符串字面值和数字。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
- 常量应该在头文件中以这样的形式暴露给外部,并在实现文件中为它赋值。
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
- 【强制】异常使用全局的 NSString 对象来标识,其名称按如下的方式进行组合:异常名称中的具有唯一性的那部分,其组成词应该拼写在一起, 并且每个单词的首字符要大写。[Prefix] + [UniquePartOfName] + Exception
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
- 【强制】Notification消息使用全局的 NSString 对象进行标识,其名称按如下的方式组合:[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
类定义规约
- 【推荐】要尽可能地使用属性定义代替无修饰的实例变量。
@interface Item : NSObject
@property(nonatomic, copy) NSString* name;
@end
-
【推荐】如果需要自定义property的getter或setter方法时,请在声明property时一起声明掉。@property(getter=my_XXX ,setter=my_setXXX) id xxx;
-
【推荐】对外暴露的属性,尽量定义为readonly。
-
【推荐】不建议使用@dynamic修饰属性,除非你真的知道自己在干什么。
-
【强制】不复写任何 + (void) load方法。所有的load方法的执行在Class的装载阶段,会延长App的启动时间.且如果存在稳定性问题,也没有可以修复的时机。
-
【强制】+(void)initialize必须判断class类型或使用dispatch_once防止执行多次. 由于任何继承类也会执行父类的initilize,所以这里一定要做类型判断,或使用dispatch_once来保障不会执行多次
-
【强制】不应该显式地调用 initialize 方法。如果需要触发初始化行为,则请调用一些无害的方法。正例:
[NSImage self];
反例:[NSImage initialize];
-
【强制】在property的setter或者getter方法里不能再显式或者隐式的调用同一个property的getter方法,会导致死循环
@interface ALPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation ALPerson
- (void)setName:(NSString *)name {
self.name = name;//死循环!
}
@end
- 【推荐】使用nonnull、nullable,__kindof来修饰方入参数、返回值、属性
@property (nonatomic, strong, nonnull) Sark *sark;
@property (nonatomic, copy, readonly, nullable) NSArray *friends;
+ (nullable NSString *)friendWithName:(nonnull NSString *)name;
- 【强制】禁止从 designated initializer 里面调用一个 secondary initializer。如果这样,调用很可能会调用一个子类重写的 init 方法并且陷入无限递归之中。
- 定义你的 designated initializer,确保调用了直接超类的 designated initializer。
- 重载直接超类的 designated initializer。调用你的新的 designated initializer。
- 为新的 designated initializer 写文档。可以用编译器的指令 attribute((objc_designated_initializer)) 来标记。用编译器指令attribute((unavailable(Invoke the new designated initializer))让父类的 designated initializer 失效.
@interface ZOCNewsViewController : UIViewController
- (instancetype)initWithNews:(ZOCNews *)news __attribute__((objc_designated_initializer));
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil __attribute__((unavailable("Invoke the designated initializer,call initWithNews:")));
- (instancetype)init __attribute__((unavailable("Invoke the designated initializer,call initWithNews:"));
@end
@implementation ZOCNewsViewController
- (id)initWithNews:(ZOCNews *)news
{
//调用直接父类的 designated initializer
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// 重载直接父类的 designated initializer
// 如果你没重载 initWithNibName:bundle: ,而且调用者决定用这个方法初始化你的类(这是完全合法的)。 initWithNews: 永远不会被调用,所以导致了不正确的初始化流程,你的类的特定初始化逻辑没有被执行。
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call the new designated initializer
return [self initWithNews:nil];
}
@end
注释规约
- 【强制】头文件中的暴露的方法或者属性都必须添加注释
- 【推荐】注释建议使用Xcode自带工具插入默认格式。option+command+/即可自动插入。
- 【强制】自动生成的代码注释中的placeholder要替换掉
- 【推荐】建议对于复杂难懂逻辑添加注释
代码组织规约
- 【推荐】当一个类功能很多时,建议使用Category的方式进行功能划分,这些Category可以放在同一个文件中。
@interface UIViewController (UIViewControllerRotation)
+ (void)attemptRotationToDeviceOrientation NS_AVAILABLE_IOS(5_0) __TVOS_PROHIBITED;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 6_0) __TVOS_PROHIBITED;
@end
@interface UIViewController (UILayoutSupport)
@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide NS_AVAILABLE_IOS(7_0);
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide NS_AVAILABLE_IOS(7_0);
@end
@interface UIViewController (UIKeyCommand)
- (void)addKeyCommand:(UIKeyCommand *)keyCommand NS_AVAILABLE_IOS(9_0);
- (void)removeKeyCommand:(UIKeyCommand *)keyCommand NS_AVAILABLE_IOS(9_0);
@end
- 【推荐】建议使用#pragma marks - 来进行方法分组,提高可读性,具体样例如下,建议把生命周期,事件,property方法以及protocol方法进行区分。
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
-
【推荐】建议合理使用group或folder来组织工程结构,而不是全部放在source里,物理group与工程中group要对应。
-
【推荐】过期方法,不要直接删除,先标记为depcrated。
-
【推荐】建议类继承关系不要超过2层,并且抽取公共逻辑到父类,尽量避免父类,子类方法调用跳跃。
-
【推荐】尽量减少继承,可以考虑组合,category,protocol等方式
-
【推荐】每个文件.m的方法数目不应该超过20个,每个方法的行数不应该超过200行。
-
【推荐】函数内嵌套不能太深,一个函数内大括号里嵌套大括号不能超过三层。
-
【推荐】建议业务bundle使用统一的前缀来标识
-
【推荐】头文件中只暴露出需要给他人调用的类、方法及属性,私有类、方法、变量放在.m中
-
【强制】Release包必须关闭Log
-
【推荐】必须清理工程中的所有warning
-
【推荐】长条件判断建议使用bool变量来代替
BOOL isConditionSatisfied = (1 == a.x && 3==b.y && 2 == c.x);
if (isConditionSatisfied){
doSomething()
}
- 【推荐】条件判断,推荐加大括号,即使一行,容易导致的错误为,当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。
if (!error) {
return success;
}
-
【推荐】对三目运算使用时,要注意简化,x=a?a:b 只要写成x=a?:b 即可;
-
【推荐】编写switch语句的时候, 一定要实现default:,防止外部异常调用,内部没有处理的情况
-
【强制】switch里每个case里需要强制有break
-
【强制】switch里每个case里都要使用{}所有代码括起来,就算只有一行
文件夹规范
-
MVC:
Asset - 资源文件
Action - Deeplink、Router、Target、JSAPI
Model - API、Model、Helper
View - UIView
VIewController - UIViewController -
MVVM:
Asset - 资源文件目录
Action - Deeplink、Router、Target、JSAPI
Model - API、Model、Helper
View - UIView
VIewController - UIViewController
ViewModel - ViewModel
最佳实践
多线程
-
【强制】自建线程必须命名。
-
【强制】多线程访问同一个对象时,必须注意临界区的保护
-
【强制】单例创建要使用线程安全模式,并且禁止在单例的init方法中使用dispatch_sync来阻塞线程,极易出现死锁
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- 【强制】在多线程环境下使用懒加载方式加载变量,会有crash风险,必须加锁保护
//多线程环境下调用
- (NSCache *)contactCache
{
if (!_contactCache) {
@synchronized(self) {
if (!_contactCache) {
_contactCache = [[NSCache alloc] init];
_contactCache.name = @"contactCache";
}
}
}
return _contactCache;
}
- 【强制】performSelector:withObject:afterDelay:要在有Runloop的线程里调用,否则调用无法生效。
异步线程默认是没有runloop的,除非手动创建;
而主线程是系统会自动创建Runloop的。
所以在异步线程调用时请先确保该线程是有Runloop的。
-
【强制】禁止随意创建长驻线程,除非是在整个app运行周期内都必须存在且有任务运行的。
-
【推荐】NSNotificationCenter在iOS 8及更老系统上存在多线程bug,selector执行到一半时可能会因为self销毁而触发crash,解决方案是在selector里开始的地方引入下面的宏。
- (void)onMultiThreadNotificationTrigged:(NSNotification *)notify
{
__weak typeof(self) weakSelf = self;
__strong typeof(self) strongSelf = wself;
if (! weakSelf) {
return;
}
[strongSelf doSomething];
}
-
【推荐】在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,而不一定是在注册观察者的那个线程中。如果发送消息的不在主线程,而接受消息的回调里做了UI操作,需要让其在主线程执行。
-
【推荐】仅当必须保证顺序执行时才使用dispatch_sync,否则容易出现死锁,应避免使用,可使用dispatch_async。
// 推荐
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_block_t block = ^() {
NSLog(@"%@", [NSThread currentThread]);
};
dispatch_async(mainQueue, block); //使用异步操作
}
// 禁止。出现死锁,报错:EXC_BAD_INSTRUCTION。原因:在主队列中同步的添加一个block到主队列中
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_block_t block = ^() {
NSLog(@"%@", [NSThread currentThread]);
};
dispatch_sync(mainQueue, block);
}
- 【参考】使用 performSelector:withObject:afterDelay:和 cancelPreviousPerformRequestsWithTarget 组合的时候要小心:
- afterDelay会增加receiver的引用计数,cancel则会对应减一
- 如果在receiver的引用计数只剩下1 (仅为delay)时,调用cancel之后会立即销毁receiver,后续再调用receiver的方法就会crash
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//NSLog(@"self被销毁");
return;
}
[self doOther];
-
【强制】禁止在非主线程中进行UI元素的操作
-
【强制】在主线程中禁止进行同步网络资源读取,使用NSURLSession进行异步获取
-
【强制】如果需要进行大文件或者多文件的IO操作,禁止主线程使用,必须进行异步处理
-
【强制】对剪贴板的读取必须要放在异步线程处理,最新Mac和iOS里的剪贴板共享功能会导致有可能需要读取大量的内容,导致读取线程被长时间阻塞
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if (pasteboard.string.length > 0) {//这个方法会阻塞线程
NSString *text = [pasteboard.string copy];
[pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
if (text == nil || [text isEqualToString:@""]) {
return ;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self processShareCode:text];
});
}
});
内存管理
-
【推荐】请慎重使用单例,避免造成产生不必要的常驻内存。
-
【推荐】单例初始化方法中尽量保证单一职责,尤其不要进行其他单例的调用。极端情况下,两个单例对象在各自的单例初始化方法中调用,会造成死锁。
-
【强制】Delegate需要用weak进行引用。
-
【强制】使用block时,需要在block访问外部weak修饰的self,内部在重新strong处理。避免RetainCycle。
-
推荐】strong引用 子实例,weak引用parent,基础类型使用assign,NSString,NSArray,block使用copy
- 假如有一个NSMutableString,现在用他给一个retain修饰 NSString赋值,那么只是将NSString指向了NSMutableString所指向的位置,并对NSMUtbaleString计数器加一,此时,如果对NSMutableString进行修改,也会导致NSString的值修改,原则上这是不允许的.
- 如果是copy修饰的NSString对象,在用NSMutableString给他赋值时,会进行深拷贝,及把内容也给拷贝了一份,两者指向不同的位置,即使改变了NSMutableString的值,NSString的值也不会改变.
- 所以用copy是为了安全,防止NSMutableString赋值给NSString时,前者修改引起后者值变化而用的.
- 【强制】对类添加属性时使用 copy 方式还是使用 retain 方式规约:
- 对实现 NSCopying 协议的对象使用 copy 方式。通常情况下,诸如NSString、NSURL, block,NSArray 这样的对象应该能被copy;
- 像UIView的对象则应该可以被保持。strong引用 子实例,weak引用parent.
- 基础类型使用assign。
-
【强制】在dealloc中要记得要remove observer, callback=null
-
强制】会循环使用的Timer(指定了repeat参数为YES),必须要在合适的时机调用invalidate方法,否则会出现内存泄漏。
对于指定了repeat参数为NO的Timer,则可以不调用invalidate方法。
在使用类的析构函数中调用Timer的invalidate方法为时已晚,因为timer会对其传递的目标object增加引用计数,若不调用invalidate,使用类根本得不到析构。
- 【强制】在 dealloc中不允许使用self访问属性(父类属性除外),只允许通过"_变量名"直接访问。
○ 容易出现重复创建对象,甚至crash问题
○ 在dealloc阶段,self是一个不完整的对象。
-
【推荐】在非init和dealloc方法中访问属性推荐通过getter方法获取,不推荐直接使用“_变量名”。
-
【推荐】在init中不需要直接使用的Property,建议使用lazyloading的方法创建。
-
【强制】在创建大量临时的 UIImage,或者 Model 之类的对象的时,用 @autoreleasepool 使 autorelease 对象在结束时间释放,缓解内存的压力。
NSMutableArray *dataList = [NSMutableArray new];
NSMutableArray *imageList = [NSMutableArray new];
[dataList enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
@autoreleasepool {
NSData *data = dataList[idx];
UIImage *image = [[UIImage alloc] initWithData:data];
//可能对 image 进行一些处理,裁剪之类的
[imageList addObject:image];
}
}];
- 【强制】在使用到 UIScrollView,UITableView,UICollectionView 的 Class 中,需要在 dealloc 方法里手动的把对应的 delegate, dataSouce 置为 nil
○ 防止在scrollView滑动时页面退出,delegate释放,出现crash问题
○ 苹果在iOS9上已经将以上类的delegate及datasource由assign改为了weak,如果只支持9.0以上,则不需要手动置nil
- 【推荐】在dealloc中,避免将self作为参数传递。如果被retain住,到下个runloop周期再次释放,则会造成多次释放crash。
-(void)dealloc{
[self unsafeMethod:self];
//因为当前已经在self所指向对象的销毁阶段,如果在unsafeMethod:中将self放到了autorelease pool中,那么self会被retain住,计划下个runloop周期再进行销毁;但是dealloc运行结束后,self对象的内存空间就直接被回收了,self变成了野指针
//当到了下个runloop周期,self指向的对象实际上已经被销毁,会因为非法访问造成crash问题
}
-
【推荐】除非是非法参数等提前判断提前return的可以写在最前面。其他的return建议有效返回值尽量只剩最后一个。提前return时,要注意是否有对象没有被释放(常见的有CF对象),是否有锁没有释放等配对问题。
-
【强制】禁止一次性申请超过10MB的内存。
○ 内存过高将会导致app被kill,并且没有crash堆栈。而申请大内存将会增加内存峰值,更容易出现内存过高而crash。
集合
包括,但不限于 NSMutableDictionay,NSMutableArray,NSMutableSet
-
【强制】插入对象需要做判空处理。
-
【强制】注意线程安全问题,必要时加锁,保障线程安全
-
【强制】先copy,再枚举操作,禁止对非临时变量的可变集合进行枚举操作,多线程情况下有可能因为可变集合在进行枚举时发生改变进而crash。
//正确的写法
- (void)checkAllValidItems{
[_arrayLock lock];
NSArray *array = [oldArray copy];
[_arrayLock unlock];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//do something using obj
}];
}
//错误的写法
-(void)checkAllValidItems{
[self.allItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//do something using obj
//如果在enumerate过程中其它线程对self.allItems进行了变更操作,这里就会引发crash
}];
}
-
【推荐】大部分情况下都不使用可变集合作为成员变量,如果确实需要进行集合的增删改操作,使用临时可变集合变量处理,之后再进行赋值操作。
-
【强制】禁止返回mutable对象,禁止mutable对象作为入参传递。
-
【推荐】如果使用NSMutableDictionary作为缓存,推荐使用NSCache代替。
-
【推荐】容器类使用泛型来指定对象的类型
// 正例
@property (readonly) NSArray<NSURL *> *imageURLs;
NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};
// 反例
@property (readonly) NSArray *imageURLs;
NSDictionary *mapping = @{@"a": @1, @"b": @2};
字符串
-
【推荐】当使用keypaths:@"xx"时候,尽量使用NSStringFromSelector(@selector(xx))方式,防止某个key被删除后没有编译感知
-
【强制】取substring的时候要考虑emoji字符的问题,防止截到中间crash
- (NSString *)dt_substringToIndex:(NSUInteger)index
{
//... 越界判断
NSRange wRange = [self rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, index)];
return [self substringWithRange:wRange];
}
锁
-
【推荐】专锁专用,一个lock对象只负责一个任务。这样可以在逻辑上进行区分,也可以避免潜在的死锁问题
-
【推荐】不同锁的使用场景:
○ 性能最好的属 pthread_mutex、dispatch_semaphore,另外dispatch_semaphore在等待的时候会释放CPU资源,所以适合用在等待耗时较长的场景;
○ @synchronized是最简单易用的递归锁,不会有忘记unlock的情况,但性能也是最低的,适合用在对性能要求不高的场景;
○ 其他的还有NSLock,性能介于上面二者之间,也有对应的条件锁NSConditionLock和递归锁NSRecursiveLock,因为是Objective-C对象,适合用在偏Objective-C编程的场景,比如需要把锁存放在NSDictionary中的场景。
NSConditionLock生产者消费者的例子 -
【强制】在使用锁的过程中如果要return,切记要先进行unlock; 如果可能有exception发生,那么需要在@finally中进行锁的释放
- (void) exclusiveMethod1{
[self.lock lock];
if (condition == true){
//这里要记得unlcok,否则下次在进入这个方法就会发生线程被死锁的问题
[self.lock unlock];
return;
}
[self.lock unlok];
}
- (void) exclusiveMethod2{
[self.lock lock];
@try{
//异常发生
}@catch(NSException* ex){
}@finally{
//此处需要进行锁的回收
[self.lock unlok];
}
}
IO
-
【参考】尽量减少使用NSUserDefault.
-
【推荐】[[NSUserDefaults standardUserDefaults] synchronize]会block当前线程直到所有UserDefault里的内容写回存储;如果内容过多,重复调用的话会严重影响性能。建议只有在合适的时候(比如退到后台)再进行持久化操作(此方法即将deprecated,可以不再调用)
-
【推荐】一些经常被读取的本地文件建议做好内存缓存,减少IO开销
-
【推荐】文件存储路径请遵循以下规则:
1、Documents 目录:您应该将所有的应用程序数据文件写入到这个目录下。这个目录用于存储用户数据。该路径可通过配置实现iTunes共享文件。可被iTunes备份。
2、AppName.app 目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。
3、Library 目录:这个目录下有两个子目录:
○ Preferences 目录:包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好.
○ Caches 目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。 可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。
4、tmp 目录:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。该路径下的文件不会被iTunes备份。
UI
- 【推荐】不要在除了viewDidLoad方法之外调用ViewController的self.view来进行view操作,特别是在一些系统通知之类的回调中,有可能造成self.view创建出来之后没有被加入到当前层级,导致子view的诡异问题.
// 反例
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
[self.view doSomething]; //如果当VC已经被创建,但是view还没有加入到view层级中时(比如Tabbar初始化之后的非选中VC),此时接收到了内存警告,那么self.view会被直接创建,没有加入到层级,导致其子view可能处于异常的状态
}
- 【推荐】如果想要获取app的window,不要view.window来获取,可以使用[[UIApplication sharedApplication] keyWindow]来获取。
如果view不在展示时,获取window会是nil,而不是真正的app所在的window.
- 【强制】UI对象只允许在主线程访问。
避免在异步线程里释放,这样可以避免在dealloc时访问view结构导致问题
-
【强制】禁止在ViewController的dealloc方法中访问self.view,会导致已经释放的view被再次重建,可能会造成各种不可预知的问题
-
【强制】显示带textfield的alert之前,一定要确保键盘不在显示状态,否则会crash
可以直接: [[[UIApplication sharedApplication].delegate window] endEditing:YES]; -
【强制】禁止使用drawViewHierarchyInRect 截屏
推荐使用 snapshotViewAfterScreenUpdates
drawViewHierarchyInRect截屏会消耗大内存和耗性能,不建议使用该技术方案。
- 【推荐】不建议将UIView类的对象加入到NSDictionary, NSSet,如有需要可以添加到NSMapTable 和 NSHashTable。
NSDictionary,NSSet会对加入的对象做strong引用,而NSMapTable、NSHashTable会对加入的对象做weak引用。
Category
- 【强制】category方法加自定义前缀。防止与其它人冲突。
// 正例
@interface NSString(category)
- (NSString*)ali_stripWhiteSpace;
@end
// 反例
@interface NSString(category)
- (NSString*)stripWhiteSpace;
@end
-
【强制】禁止category方法覆盖系统方法,防止出现方法调用的不确定性。
-
【推荐】对于一些提供category的工具库,建议根据不同类型功能拆分成不同的子bundle,方便引用方按需引用,控制App体积。
-
【强制】Category的源文件名称必须是“类名+扩展名.{h,m}”
// 正例
NSString+ALiCategory.h
// 反例
NSStringALiCategory.h
NSString_ALiCategory.h
异常
- 【强制】不要在@finally块中使用return或者@throw等导致方法执行中断的语句,会导致@try内的return失效
其它
-
【推荐】使用Method swizzle之前考虑是否有其他方法可以代替,禁止swizzle其他基础库,二方三方库的方法
-
【强制】NSNotification接口,userInfo和object的使用要规范。
object通常是指发出notification的对象,如果在发送notification的同时要传递一些信息,请使用userInfo,而不是object。
- 【推荐】在使用固定格式的dateFormatter时候,需要设置setLocale为"en_US_POSIX",防止一些不同日历下格式异常。
NSDate* now = [NSDate date];
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
fmt.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
NSString* string = "1996-12-19T16:39:57-08:00";
NSDate* date = fmt.dateFromString(string);
-
【推荐】在使用CTTelephonyNetworkInfo的时候,务必使用全局的单例实例,这个类本身存在bug,如果有多实例会存在会导致小概率的crash。
-
【强制】调用block时务必判断block是否为nil
-
【推荐】调用delegate的optional方法时,判断delegate能否响应该方法,避免crash
-
【强制】禁止访问对象的结构体变量(使用->)
-
【强制】需要使用磁盘缓存的业务,务必提供清理缓存的能力
-
【强制】对于不确定对象类型的比较,可以使用isEqual:方法,其会对类型进行判断;对于确定对象类型的比较,比如NSString,可以使用isEqualToString:,其不对类型进行判断,但相比前者性能更好。
网友评论