美文网首页
Object-C代码规范 2022-02-23 周三

Object-C代码规范 2022-02-23 周三

作者: 勇往直前888 | 来源:发表于2022-03-25 06:02 被阅读0次

一、语言规约

命名规约

  • 【强制】文件名、自定义类、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 方法并且陷入无限递归之中。
  1. 定义你的 designated initializer,确保调用了直接超类的 designated initializer。
  2. 重载直接超类的 designated initializer。调用你的新的 designated initializer。
  3. 为新的 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

注释规约

  1. 【强制】头文件中的暴露的方法或者属性都必须添加注释
  2. 【推荐】注释建议使用Xcode自带工具插入默认格式。option+command+/即可自动插入。
  3. 【强制】自动生成的代码注释中的placeholder要替换掉
  4. 【推荐】建议对于复杂难懂逻辑添加注释

代码组织规约

  • 【推荐】当一个类功能很多时,建议使用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 组合的时候要小心:
    1. afterDelay会增加receiver的引用计数,cancel则会对应减一
    2. 如果在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

  1. 假如有一个NSMutableString,现在用他给一个retain修饰 NSString赋值,那么只是将NSString指向了NSMutableString所指向的位置,并对NSMUtbaleString计数器加一,此时,如果对NSMutableString进行修改,也会导致NSString的值修改,原则上这是不允许的.
  2. 如果是copy修饰的NSString对象,在用NSMutableString给他赋值时,会进行深拷贝,及把内容也给拷贝了一份,两者指向不同的位置,即使改变了NSMutableString的值,NSString的值也不会改变.
  3. 所以用copy是为了安全,防止NSMutableString赋值给NSString时,前者修改引起后者值变化而用的.
  • 【强制】对类添加属性时使用 copy 方式还是使用 retain 方式规约:
  1. 对实现 NSCopying 协议的对象使用 copy 方式。通常情况下,诸如NSString、NSURL, block,NSArray 这样的对象应该能被copy;
  2. 像UIView的对象则应该可以被保持。strong引用 子实例,weak引用parent.
  3. 基础类型使用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:,其不对类型进行判断,但相比前者性能更好。

相关文章

  • Object-C代码规范 2022-02-23 周三

    一、语言规约 命名规约 【强制】文件名、自定义类、Protocol禁止以以下系统前缀开头。【AC,AB,AS,AL...

  • Object-C代码规范

    最近在整理代码,所以将以前积累的一些代码规范方面的知识分享出来,希望能对大家编码时有所帮助。一个好的代码风格的重要...

  • ios编码规范

    Object-C 开发代码规范概要Object-C是一门面向对象的动态编程语言,主要用于编写IOS和MAC应用程序...

  • IOS 编程风格及命名规范

    目的 统一规范Xcode编辑环境下 Object-C 的编程风格和标准,尽量遵循苹果公司发布代码中的主流代码风格。...

  • 枚举

    ps:这只是一个示例,命名规范请参考Object-C命名规范。

  • iOS 端编码规范文档

    iOS端编码规范 Object-C 篇 基本格式 由于 Xcode 自动补全功能,在Object-C中,命名的清晰...

  • Web 端代码规范

    目录 1、代码规范概述 2、代码规范流程搭建 3、代码规范细则 1、代码规范概述 良好的代码规范能够提供软件的可读...

  • Swift 中的 Runtime

    即使在 Swift APP 中没有一行 Object-c 的代码,每个 APP 也都会在 Object-c run...

  • iOS代码风格

    简介 Object-C 代码风格 代码结构 使用 #pragma mark - 换行 注释 仅根据需要写注释,代码...

  • iOS Object 和 Swift之间的混编使用

    1、swift 使用 Object-c 代码 1)Xcode Build Setting 设置步骤 -> ...

网友评论

      本文标题:Object-C代码规范 2022-02-23 周三

      本文链接:https://www.haomeiwen.com/subject/rekalrtx.html