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

2022-03-25  本文已影响0人  勇往直前888

一、语言规约

命名规约

fileExistsAtPath:isDirectory:
 - (void)invokeWithTarget:(id)target; 
 - (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
- (NSSize)cellSize;
- (void)setCanHide:(BOOL)flag;      
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
- (BOOL)windowShouldClose:(id)sender;

常量定义

typedef NS_ENUM(NSInteger,NSMatrixMode) {
NSMatrixModeRadio = 0,
NSMatrixModeHighlight = 1,
NSMatrixModeList = 2,
NSMatrixModeTrack = 3
} ;
typedef NS_OPTIONS(NSUInteger,NSMatrixModeMask) {
NSMatrixModeMaskBorderless     = 0,
NSMatrixModeMaskTitled = 1 << 0,
NSMatrixModeMaskClosable = 1 << 1,
NSMatrixModeMaskMiniaturizable = 1 << 2,
NSMatrixModeMaskResizable = 1 << 3
};
const float NSLightGray;
#ifdef DEBUG
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
 NSColorListIOException
 NSColorListNotEditableException 
 NSDraggingException
 NSFontUnavailableException
 NSIllegalSelectorException
NSApplicationDidBecomeActiveNotification  
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

类定义规约

@interface Item : NSObject
@property(nonatomic, copy) NSString* name;
@end
@interface ALPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation ALPerson
- (void)setName:(NSString *)name {
    self.name = name;//死循环!
}
@end
@property (nonatomic, strong, nonnull) Sark *sark;
@property (nonatomic, copy, readonly, nullable) NSArray *friends;
+ (nullable NSString *)friendWithName:(nonnull NSString *)name;
  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. 【推荐】建议对于复杂难懂逻辑添加注释

代码组织规约

@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 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 {}
BOOL isConditionSatisfied = (1 == a.x &&  3==b.y && 2 == c.x);
if (isConditionSatisfied){
 doSomething()
}
if (!error) {
    return success;
}

文件夹规范

最佳实践

多线程

+ (instancetype)sharedInstance {
        static id sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{    
           sharedInstance = [[self alloc] init];
        });
        return sharedInstance;
     }
//多线程环境下调用
 - (NSCache *)contactCache
 {
        if (!_contactCache) {
        @synchronized(self) { 
            if (!_contactCache) {
                _contactCache = [[NSCache alloc] init];
                _contactCache.name = @"contactCache";
            }
        }
    }
    return _contactCache;
 }

异步线程默认是没有runloop的,除非手动创建;
而主线程是系统会自动创建Runloop的。
所以在异步线程调用时请先确保该线程是有Runloop的。

- (void)onMultiThreadNotificationTrigged:(NSNotification *)notify
{
    __weak typeof(self) weakSelf = self;
    __strong typeof(self) strongSelf = wself;
    if (! weakSelf) { 
        return; 
    }

    [strongSelf doSomething];
}
// 推荐
 - (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);
}
__weak typeof(self) weakSelf = self;
 [NSObject cancelPreviousPerformRequestsWithTarget:self];
 if (!weakSelf)
 {
    //NSLog(@"self被销毁");
    return;
 }

[self doOther];
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];
      });
   }
});

内存管理

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

在使用类的析构函数中调用Timer的invalidate方法为时已晚,因为timer会对其传递的目标object增加引用计数,若不调用invalidate,使用类根本得不到析构。

○ 容易出现重复创建对象,甚至crash问题
○ 在dealloc阶段,self是一个不完整的对象。

 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];
     }
 }];

○ 防止在scrollView滑动时页面退出,delegate释放,出现crash问题
○ 苹果在iOS9上已经将以上类的delegate及datasource由assign改为了weak,如果只支持9.0以上,则不需要手动置nil

-(void)dealloc{
    [self unsafeMethod:self]; 
    //因为当前已经在self所指向对象的销毁阶段,如果在unsafeMethod:中将self放到了autorelease pool中,那么self会被retain住,计划下个runloop周期再进行销毁;但是dealloc运行结束后,self对象的内存空间就直接被回收了,self变成了野指针
    //当到了下个runloop周期,self指向的对象实际上已经被销毁,会因为非法访问造成crash问题
}

集合

包括,但不限于 NSMutableDictionay,NSMutableArray,NSMutableSet

//正确的写法
- (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
     }];
}
// 正例
@property (readonly) NSArray<NSURL *> *imageURLs;

NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};

// 反例
@property (readonly) NSArray *imageURLs;

NSDictionary *mapping = @{@"a": @1, @"b": @2};

字符串

- (NSString *)dt_substringToIndex:(NSUInteger)index
 {
    //... 越界判断
    NSRange wRange = [self rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, index)];
    return [self substringWithRange:wRange];
 }

- (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

UI

// 反例
- (void)didReceiveMemoryWarning{
        [super didReceiveMemoryWarning];
    [self.view doSomething]; //如果当VC已经被创建,但是view还没有加入到view层级中时(比如Tabbar初始化之后的非选中VC),此时接收到了内存警告,那么self.view会被直接创建,没有加入到层级,导致其子view可能处于异常的状态
}

如果view不在展示时,获取window会是nil,而不是真正的app所在的window.

避免在异步线程里释放,这样可以避免在dealloc时访问view结构导致问题

drawViewHierarchyInRect截屏会消耗大内存和耗性能,不建议使用该技术方案。

NSDictionary,NSSet会对加入的对象做strong引用,而NSMapTable、NSHashTable会对加入的对象做weak引用。

Category

// 正例
@interface NSString(category)
 - (NSString*)ali_stripWhiteSpace;
 @end

// 反例
@interface NSString(category)
 - (NSString*)stripWhiteSpace;
 @end
// 正例
NSString+ALiCategory.h

// 反例
NSStringALiCategory.h
NSString_ALiCategory.h

异常

其它

object通常是指发出notification的对象,如果在发送notification的同时要传递一些信息,请使用userInfo,而不是object。

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);
上一篇下一篇

猜你喜欢

热点阅读