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

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

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容