美文网首页
《编写高质量iOS和OSX代码的52个有效方法》片段

《编写高质量iOS和OSX代码的52个有效方法》片段

作者: LiwaySun | 来源:发表于2018-09-20 15:38 被阅读10次

    1.多用类型常量,少用#define预处理指令。

    static const NSTimeInterval kAnimationDuration = 0.3
    

    使用类型常量定义常量含有类型信息,如果常量值类型不一致会有警告信息。

    2.在头文件中使用extern声明全局常量,并在相关实现文件中定义其值。

    EOCAnimatedView.h
    extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
    EOCAnimatedView.m
    const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;
    

    3.用枚举表示状态、选项、状态码

    1.如果选项表示为枚举类型,可以有多个选项可以同时使用,那么就将个选项值定义为2的幂,以便可以按位或操作将其组合。

    typedef NS_ OPTIONS(NSUInteger,EOCPermittedDirection){
    EOCPermittedDirectionUp = 1<<0,
    EOCPermittedDirectionDown = 1<<1,
    EOCPermittedDirectionLeft = 1<<2,
    EOCPermittedDirectionRight = 1<<3,
    };
    

    2.用NS_ENUM,NS_OPTIONS宏来定义枚举类型,并指明起底层数据类型,可以保证枚举是用开发者选择的底层数据类型来实现,而不会采用编译器所选类型。

    4.在对象内部尽量直接访问实例变量

    直接访问实例变量的原因:
    *由于不经过Objective-C的“方法派发”步骤,直接访问实例变量速度比较快。
    *直接访问实例变量时,不会调用“设置方法”绕过了相关属性的“内存管理语意”比如在ARC下,直接访问一copy属性,那么不会考呗该属性,只会保留新值释放旧值。
    *如果直接访问实例变量,不会触发“键值观察KVO”通知。

    1.在对象内部读取数据时,应该直接通过实例变量读取,写入数据时,通过属性来写。
    2.在初始化方法及dealloc方法中,应该直接通过实例变量读写数据。
    3.在懒加载中必须通过属性来读取数据。

    5.以“类族模式”隐藏实现细节

    这种模式最大的好处就是,可以隐藏抽象基类背后的复杂细节,使用者只需调用基类简单的方法就可以返回不同的子类实例。
    如UIButton的类方法

    + (UIButton *)buttonWithType:(UIButtonType)type;
    

    自定义个类族
    首先定义抽象基类

    typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
    EOCEmployeeTypeDeveloper, EOCEmployeeTypeDesigner, EOCEmployeeTypeFinance,
    }
    @interface EOCEmployee : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSUInteger salary;
    @end
    
    @implementation EOCEmployee
    + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type {
          switch (type) {
              case EOCEmployeeTypeDeveloper:
                   return [EOCEmployeeDeveloper new];
                   break;
               case EOCEmployeeTypeDesigner:
                    return [EOCEmployeeDesigner new];
                    break;
               case EOCEmployeeTypeFinance:
                     return   [EOCEmployeeTypeFinance new];
                     break;
    }
    
    - (void)doADaysWork{
      
    }
    

    每个子类都是从基类继承而来。

    @interface EOCEmployeeDeveloper : EOCEmployee
    @end
    
    @implementation EOCEmployeeDeveloper
    - (void)doADaysWork{
        [super doADaysWork];
        NSLog(@"%@",[[self class] description]);
    }
    @end
    

    6.消息转发机制

    在“消息传递机制”中,当对象接收到无法解析的消息时,就会启动“消息转发”机制

    1.动态方法解析

    对象在收到无法解析的消息后,首先会调用所属类的

    + (BOOL)resolveInstanceMethod:(SEL)selector;
    

    或者

    + (BOOL)resolveClassMethod:(SEL)selector;
    

    在这里可以新增一个处理该"选择子"的方法,如

    #import <Foundation/Foundation.h>
    
    @interface EOCAutoDictionary : NSObject
    @property (nonatomic, copy) NSString *string;
    @property (nonatomic, strong) NSNumber *number;
    @property (nonatomic, strong) NSDate *date;
    @property (nonatomic, strong) id opaqueObject;
    @end
    
    #import "EOCAutoDictionary.h"
    #import <objc/runtime.h>
    
    @interface EOCAutoDictionary()
    @property (nonatomic, strong) NSmutableDictionary *backingStore;
    @end
    @implementation EOCAutoDictionary
    @dynamic string, number, date, opaqueObject;
    
    - (id)init{
    if (self = [super init]){
    _backingStore = [NSMuatableDictionary new];
      }
     return self;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)selector{
          NSString *selectorString = NSStringFromSelector(selector);
          if ([selectorString hsaPrefix:@"set"]){
              class_addMethod(self, selector,(IMP)autoDictionarySetter,"v@:@)"
          } else {
              class_addMethod(self, selector,(IMP)autoDictionaryGetter,"@@:");
          }
          return YES    
    }
    
    // Getter方法实现
    id autoDictionaryGetter(id self, SEL _cmd){
        EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
        NSMutableDictionary *backingStore = typedSelf.backingStore;
        NSString *key = NSStringFromSelector(_cmd);
        return [backingStore objectForKey:key];
    
    }
    // Setter方法实现
    id autoDictionarySetter(id self, SEL _cmd, id value) {
         EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
        NSMutableDictionary *backingStore = typedSelf.backingStore;
        NSString *selectorString = NSStringFromSelector(_cmd);
        NSMutableString *key = [selectorString mutableCopy];
    // 删除结尾的:
        [key deleteCharactersInRange:NSMakeRange(key.length - 1,1)];
    // 删除头部“set”
        [key deleteCharactersInRange:NSMakeRange(0,3)];
    }
        NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
        [key replaceCharactersInRange:NSMakeRange(0,1) withString:lowercaseFirstChar];
        if (value) {
            [backingStore setObject:value forKey:key];
         } else {
            [backingStore removeObjectForKey:key];
       }
    }
    
     @end
    
    // 使用
    EOCAutoDictionary *dict = [EOCAutoDictionary new];
    dict.date = [NSDate dateWithTimeIntervalSince1970:475328000];
    NSLog(@"dict.date = %@",dict.date);
    // 输出: dict.date = 1985-01-24 00:00:00 +0000
    
    
    2.备援接收者

    如果没有实现“动态方法方法解析”则会走这一步,能不能把消息转给其他接收者来处理,对应方法为

    - (id)forwardingTargetForSelector:(SEL)selector
    

    如果找到其他对象,则将其返回,没有找到,则返回nil。
    注意:我们无法操作经由这一步的转发消息。

    3.完整的消息转发

    首先会创建NInvocation对象,把未处理的消息的全部细节都封装该对象中,包括选择子,目标,参数。在触发NSInvocation对象时,“消息派发系统”会吧消息指派给目标对象,此步骤会调用下列方法来转发消息:

    - (void)forwardInvocation:(NSInvocation *)invocation;
    

    如果还是没有处理该消息,则会调用“doesNotRecognizeSelector:”抛出异常

    7.使用自动释放池降低内存峰值

    NSArray *databaseRecords = [NSArray Array];
    NSMutableArray *people = [NSMutableArray New];
    for (NSDictionary *record in databaseRecords) {
         @autoreleasepool {
            EOCPerson *person = [EOCPerson alloc] initWithRecord:record];
            [people addObject:person];
         }
    
    }
    

    8.栈块,堆块,全局块

    // 由于定义在if和else中的两个块都分配在占栈内存中,离开相应的范围可能会被释放,
    void (^block)();
    if (some condition) {
       block = ^ {
              NSLog(@"Block A");
       };
    } else {
        block =^ {
             NSLog(@"Block B");
        };
    }
    block();
    
    // 可以给块对象发送Copy消息拷贝到堆中,防止过早释放
    void (^block)();
    if (some condition) {
       block =[ ^ {
              NSLog(@"Block A");
       } copy];
    } else {
        block =[^ {
             NSLog(@"Block B");
        } copy];
    }
    block();
    
    // 全局块声明在全局内存中,这种快不会捕捉任何状态(比如外围变量等),全局快的拷贝操作是个空操作
    void (^block)() =^{
        NSLog(@"This is a block");
    };
    

    9.多用派发队列,少用同步锁

    如果多线程同时执行同一份代码时,我们需要使用锁来实现同步访问,
    第一种方法,同步块

    - (void)synchronizedMethod{
           @synchronized(self) {
              // 安全代码
            }
    }
    // 滥用@s'ynchronized(self)则会降低代码效率,因为所有同步块都会彼此抢夺同一个锁,要是有多个属性都这么写的话,每个属性的同步块都要等其他所有同步块执行完毕才能执行,
    

    第二种方法, 使用NSLock

    _lock = [[NSLock alloc] init];
    - (void)synchronizedmethod{
         [_lock lock];
        // 安全代码
        [_lock unlock];
    }
    
    

    第三种方法,递归锁(NSRecursiveLock)

    NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
        KYS_GLOBAL_QUEUE(^{
            static void (^RecursiveBlock)(int);
            RecursiveBlock = ^(int value) {
                [lock lock];
                if (value > 0) {
                    NSLog(@"加锁层数 %d", value);
                    sleep(1);
                    RecursiveBlock(--value);
                }
                [lock unlock];
            };
            RecursiveBlock(3);
        });
    

    第二种和第三种也有缺陷,在极端情况下,同步快会导致死锁,效率也不是很高

    第四种方法, 使用串行同步队列
    把读取和写入操作都安排在同一个队列里,保证数据同步

    _syncQueue = dispatch_queue_create("com.effectiveobjective.syncQueue".NULL);
    - (NSString *)someString{
        _block NSString *localSomeString;
        dispatch_sync(_syncQueue, ^{
              localSomeString = _someString;
        });
        return localSomeString;
    }
    
    - (void)setSomeString:(NSString *)someString {
        dispatch_sync(_syncQueue, ^{
          _someString = someString;
        });
    }
    

    第五中,使用并发队列加栅栏,性能更佳

    _syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    - (NSString *)someString{
       _block NSString *localSomeString;
       dispatch_sync(_syncQueue, ^{
             localSomeString = _someString;
       });
       return localSomeStrng;
    }
    
    - (void)setSomeString:(NSString *)someString{
         dispatch_barrier_async(_syncQueue, ^{
           _someString = someString;
         });
    }
    

    10.打破NSTimer的循环引用

    // 创建NSTimer分类,使用弱引用打破循环

     #import <Foundation/Foundation.h>
    @interface NSTimer (EOCBlocksSupport)
    + (NSTimer *)etc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
    @end
    
    @implementation NSTimer (EOCBlocksSupport)
    + (NSTimer *)etc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats{
        
       return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(etc_blockinvoke:) userInfo:[block copy] repeats:repeats];
    }
    
    + (void)eoc_blockInvoke:(NSTimer *)timer {
       void (^block)() = timer.userInfo;
        if (block) {
         block();
        }
    }
    

    // 使用

    - (void)startPolling {
        __weak EOCClass *weakSelf = self;
        _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{ 
        EOCClass *strongSelf = weakSelf;
        [strongSelf p_doPoll];
        } 
        repeats:YES];
    
    }
    
    

    相关文章

      网友评论

          本文标题:《编写高质量iOS和OSX代码的52个有效方法》片段

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