iOS编写质量代码

这是一篇读书笔记,快速记录各类高效率编程的技巧和方法。这些方法是为了提高编码质量和效率,高质量代码利于后期的维护和更新,毕竟不能一份代码到永远。nginx

因为是记录形式,固然不能把整篇内容都写下来,只记录关键性的内容,长期更新。算法

正文

Objective-C使用了消息机制代替调用方法。数据库

区别:使用消息结构的语言,其运行时缩影执行的代码由运行环境来决定。而使用函数调用的语言,则又编译器决定。编程

头文件中少引用其余文件

在头文件中使用@Class 代替直接引用其余头文件后端

多使用字面量语法

    NSNumber *intNumber = @1;    NSNumber *floatNumber = @2.5f;    NSNumber *doubleNumber = @3.1415926;    NSNumber *boolNumber = @YES;    NSNumber *charNumber = @'a';    
    int a = 3;    float b = 2.1;    NSNumber *c = @(a*b);    
    NSArray *animals = @[@"cat",@"dog",@"monkey"];    
    NSString *dog = animals[1];    NSDictionary *dataDict = @{ @"firstName" : @"aa",                                @"lastName" : @"bb",                                @"age" : @20 };    
    NSString *lastName = dataDict[@"lastName"];    
    NSMutableArray *mutableArray = animals.mutableCopy;

多用类型常量,少用#define预处理

若是只在本类使用的常量,使用static const关键字来定义常量。缓存

若是多个类都需使用到某一常量,则需将常量定义成公开的,具体方式是在类的声明文件中使用extern const关键字声明常量,在类的实现文件中使用const关键字定义常量,这样任何类只要导入了声明常量的头文件就能够直接使用定义好的常量了。安全

.h文件中声明bash

extern NSString *const XFExternalConst;

.m文件中描述服务器

NSString *const XFExternalConst = @"ko";

为避免冲突,通常都用类名作前缀。网络

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

枚举只是一种常量命名方式,某个对象所经历的各类状态能够定义为一个枚举集。

编译器会为枚举分配一个独有的编号,从0开始每一个递增长1.实现枚举所用的数据类型取决于编译器,不过其二进制位的个数必须能彻底表示枚举编号才行。

enum ConnectionState {
    ConnectionStateDisconnected,
    ConnectionStateConnecting,
    ConnectionStateConnected
};typedef enum ConnectionState ConnectingState;

还能够不使用编译器所分配的编号,手工指定某个枚举成员所对应的值。

还有一种状况应该使用枚举类型,那就是定义选项的时候。若这些选项能够彼此组合,则更应该如此。只要枚举定义的对,各选项之间就能够经过“按位或操做符”来组合。

凡是须要以按位或操做来组合的枚举都应该用NS_OPTIONS宏,若是没有组合需求,就用NS_ENUM宏。

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5};

枚举在switch语句里面的时候,不须要加default分支。

属性的概念

基本方法就不描述了。

@dynamic 关键字,表示不要自动建立实现属性全部的实例变量,也不要为其建立存取方法。即便编译器没有发现定义存取方法,也不会报错,它相信这些方法能在运行期找到。

属性的四种特质

  • 原子性

默认状况下,编译器合成的方法会锁定机制保持atomic。若是使用nonatomic,则不使用同步锁。

  • 读写权限

readwrite的属性具备gettersetter方法

readonly的属性仅具备getter方法

  • 内存管理语义

assign只针对“纯量类型”,好比CGFloat或者NSInteger

strong表示该属性定义了一种拥有关系。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,而后将新值设置上去

weak表示该属性定义另外一种非拥有关系。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。和assign相似,然而在属性所指的对象遭到摧毁时,属性值也会清空

unsafe_unretained这个和assign相同,可是它适用于对象类型,该特质表达一种非拥有关系,当目标对象遭到摧毁时,属性值不会自动清空,是不安全的

copy表达的所属关系和strong类型。而后设置方法并不保留新值,而是将其拷贝。当属性类型为NSString *时,常常用此特质来保护其封装性,由于传递给设置方法的新值可能指向一个NSMutableString类的实例。若是不是拷贝的花,那么设置完属性之后,字符串的值可能会在对象不知情的状况下遭人更改。因此这个时候须要拷贝一份不可变的字符串。

  • 方法名

getter=<name> 指定getter的方法名。若是属性是Boolean型,在方法名加上is前缀,就能够用这个方法来指定。

setter=<name> 指定setter的方法名。这个不常见。

在对象内部尽可能直接访问实例变量

懒加载是重写getter方法

理解对象等同性的概念

按照==操做符比较出来的结果未必是咱们想要的,由于该操做符比较出来的是两个指针自己,而不是指针所指的对象。应该是用NSObject协议中声明的isEqual方法来判断两个对象的等同性。来办来讲两个类型不一样的对象老是不相等的。

NSString *oneStr = @"aaa 21";NSString *twoStr = [NSString stringWithFormat:@"aaa %d",21];BOOL equalA = (oneStr == twoStr);//NOBOOL equalB = [oneStr isEqual:twoStr];//YESBOOL equalC = [oneStr isEqualToString:twoStr];//YES

两个用于判断等同性的关键方法

  • (BOOL)isEqual:(id)object;
    @property (readonly) NSUInteger hash;

默认实现是:当且仅当其指针值彻底相等时,这两个对象才相等。

几个要点

  • 若想监测对象的等同性,提供isEqual:与 hash 方法

  • 相同的对象必须具备相同的哈希码,可是两个哈希码相同的对象却未必相同

  • 不要盲目逐个检测每条属性,而是应该依照具体需求来制定监测方案

  • 编写 hash 方法时,应该是用计算速度快并且碰撞低的算法

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

核心套路就是相似UIButton,建立的时候传入一个枚举值,根据枚举值来建立子类。(这里的笔记是我看懂之后写的,不知道的朋友先搜索一下工厂模式,其实就是那个意思)

在既有类中使用关联对象存放自定义数据

有时候须要在对象中存放相关信息,这时候咱们一般会从对象所属的类中继承一个子类,而后改用这个子类对象。然而并不是全部状况下都能这么作,有时候类的实例可能由某种机制建立。Objective-C有一种强大机制叫关联对象

这种机制要当心使用,由于会使代码失控。

理解objc_msgSend的做用

原型

void objc_msgSend(id self, SEL cmd, ...)

一个例子

id returnValue = [someObject messageName:parameter];

编译器会把它转换为如下函数

id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

为了完成调用方法,该方法须要在接受者所属的类中搜寻其方法列表,若是能找到,就跳转过去。若是找不到,就沿着继承体系向上继续查找,等找到合适的再跳转。若是最终仍是找不到,就执行消息转发的操做。

一些边界状况,则交由另外一些函数处理

  • objc_msgSend_stret 若是待发送的消息要返回结构体,可交此函数处理。

  • objc_msgSend_fpret 若是消息返回的是浮点数,可交由此函数处理。

  • ojbc_msgSendSuper 若是要给超类发送消息,例如[super message:parameter],那么就就交由此函数处理。

理解消息转发机制

消息转发分为两大阶段。

第一阶段先问接受者,所属的类,看其是否能动态添加方法,以处理当前这个unknown selector,这称为dynamic method resolution

第二阶段涉及full forwarding mechanism。若是运行期系统已经把第一阶段执行完了,那么接受者本身就没法再以动态新增方法的手段来响应包含该selector的消息了。此时,运行期系统会请求接受者以其余手段来处理与消息相关的方法调用。而后又分两部。

首先,让接受者看看有没有其余对象能处理这条消息。若是有,就转发给那个对象。

若是没有,就会启动完整的消息转发机制,运行期系统会把消息有关的所有细节封装到NSInvocation对象中,再给接受者最后一次机会,让它设法解决当前还未处理的这条消息。

动态方法解析

对象收到没法解读的消息后,先调用

+ (BOOL)resolveInstanceMethod:(SEL)sel

该方法的参数就是那个未知的selector,返回Boolean类型,表示这个类是否能新增一个实例方法用来处理这个selector。在继续走下去以前,这有个机会新增一个处理的方法。

若是还没有实现的不是实例方法而是类方法,则调用

+ (BOOL)resolveClassMethod:(SEL)sel

使用他们的前提是,相关方法的实现代码已经写好,只等着运行的时候动态插入在类里面。

这个经常用来实现@dynamic 属性。

后备接收者

当前接收者还有第二次机会处理,能不能把消息转发给其余接收者

- (id)forwardingTargetForSelector:(SEL)aSelector

找获得就返回对象,找不到就返回nil

完整的消息转发

- (void)forwardInvocation:(NSInvocation *)anInvocation

先建立NSInvocation对象,把还没有处理的那条消息有关的所有细节都封在其中。此对象包含selectortarget以及参数。

继承体系中的每一个类都有机会处理此调用请求,直到NSObject。若是尚未找到,那么该方法还会继续调用doesNotRecognizeSelector:抛出异常,此异常表示最终未能处理。

这个机制属于底层机制,能够动态注入方法,甚至以前的能够动态注入属性,云后端服务商能够说基本就靠这个套路,经过KVC的样子往类里面添加属性。

用方法调配技术调试黑盒方法

黑科技。

IMP指针,改方法实现,替换系统方法,能够多添加日志打印。

类对象

OC 是一门极其动态的语言。

每一个 OC 对象实例都是指向某块内存数据的指针。

typedef struct objc_object {
    Class isa;
} *id;

每一个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,一般称为is a指针。

typedef struct objc_class *Class;struct objc_class {
    Class isa;
    Class super_class;    const char *name;    long version;    long info;    long instance_size;    struct objc_ivar_list *ivars;    struct objc_method_list **methodLists;    struct objc_cache *cache;    struct objc_protocol_list *protocols;
};

此结构体存放类的元数据,例如类的实例实现了几个方法,具有多少个实例变量等信息。
首个变量是isa指针,说明Class自己也是 OC 对象。

super_class定义了本类的超类。类对象所属的类型是另外一个类,叫作超类

每一个类仅有一个类对象,而每一个类对象仅有一个与之相关的元类

class方法所返回的类表示发起代理的对象,而非接受代理的对象。

用前缀避免命名空间冲突

开发者可能会忽视另一个容易引起命名冲突的地方,那就是类的实现文件中所用的纯 C 函数及全局变量。

提供全能初始化方法

全部对象均要初始化。

提供一个全能初始化方法,其余的几种初始化方法调用它。

若是全能初始化方法与超类不一样,则需覆写超类中的对应方法。

实现description方法

重写- (NSString *)description

控制台- (NSString *)debugDescription

尽可能使用不可变对象

尽可能把对外公布出来的属性设为只读,只在必要时候对外公布。

有时候想修改封装在对象内部的数据,可是却不想让外人所改动。这种状况须要将readonly.m文件中从新生成readwrite。可是为了不产生意外,须要在必要时经过dispatch queue来实现。

不要把可变的内容做为属性公开,而是提供相关方法,以此修改对象中的可变内容。

使用清洗而协调的命名方式

驼峰命名法

方法与变量以小写字母开头

类名以大写字母开头

不要使用str这种简称,而用string这样的全称

Boolean属性应该加is前缀,若是某方法返回非属性的Boolean值,应该根据功能选用has或者is当前缀

类与协议的命名

为类与协议的名称加上前缀,以免命名空间的冲突

委托通常使用委托的发起方名称后面跟一个Delegate

为私有方法名加前缀

通常可使用p_做为前缀,表示私有方法

不要用一个单独的下划线做为私有方法的前缀

理解Objective-C错误模型

异常NSException应该用于极其严重的错误,好比编写了某个抽象基类,它的正确用法是先从重继承一个子类,而后再使用这个子类。在这种状况下,若是有人直接使用了这个抽象基类,那么能够考虑抛出异常。

NSError的用法很灵活,封装了三条信息

  • Error domain 错误范围,类型为字符串
    错误发生的范围,一般用一个特有的全局变量来定义。

  • Error code 错误码,类型为证书
    独有的错误代码。这种错误一般采用enum来定义,好比 HTTP 请求返回的状态码。

  • User info 用户信息,类型为字典
    有关此错误的额外信息,其中或许包含一段本地化描述,或许还包含致使该错误发生的另一个错误,经由此种信息,可将相关错误传承一条chain of errors

理解NSCopying协议

使用对象时常常须要拷贝它。若是想令本身的类支持拷贝操做

- (id)copyWithZone:(NSZone *)zone;

为何会出现NSZone,之前开发的时候,会把内存分红不一样的zone,而对象会建立在某个区里面。如今不用了,每一个程序只有一个default zone

另一个NSMutableCopying协议,返回可变的副本

- (id)mutableCopyWithZone:(NSZone *)zone;

深拷贝
在拷贝对象自身时,将底层数据也一并复制过去

浅拷贝
Foundation框架中全部的容器类默认状况下执行浅拷贝,只拷贝对象自己,不复制数据
由于不是全部对象都能拷贝,并且调用者也未必须要都一一拷贝。

经过委托与数据源协议进行对象间通讯

委托属性要定义成weak,由于二者之间必须为非拥有关系

- (BOOL)respondsToSelector:(SEL)aSelector;

也能够用协议定义一套接口,令某类从该接口获取所需的数据。委托模式的这种用法是向类提供数据,因此成为dataSource。在这种模式中,信息从数据源流向类。而在常规的代理模式中,信息则从类流向受委托者。

将类的实现代码分散到便于管理的数个分类之中

把一个类中的几个不一样模块方法写到别的文件中,合理使用category

不要在分类中声明属性

除了extension外,其余的分类都没法向类中新增实例变量

声明为@dynamic ,而后动态添加

使用extension隐藏实现细节

经过协议提供匿名对象

使用匿名对象来隐藏类型名称

理解引用计数

retain 增计数
release 减计数
autorelease 待稍后清理autorelease pool时,再减小计数

对象建立出来时,其保留计数至少为1

自动释放池

循环引用

以ARC简化引用计数

若方法名如下列词语开头,则返回的对象归调用者全部

  • alloc

  • new

  • copy

  • mutableCopy

在应用程序中,可用下列修饰符来改变局部变量与实例变量的语义

__strong 默认语义,保留这个值

__unsafe_unretained 不保留这个值,这么作可能不安全,由于等到再次使用变量时,其对象可能已经回收了

__weak 不保留这个值,可是变量能够安全使用,由于若是系统把这个对象回收了,那么变量也会自动清空

__autoreleasing 把对象按引用传递给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放

好比,想令实例变量的语义与不使用 ARC 时相同,可使用__weak__unsafe_unretained修饰符

block 块会自动保留其所捕获的所有对象,而若是这其中有某个对象又保留了块自己,那么就可能致使循环引用,能够用__weak局部变量来打破这种循环引用

注意:CoreFoundation对象不归 ARC 管理,开发者必须适时调用CFRetain/CFRelease

dealloc方法中只释放引用并解除监听

把原来配置过的观测行为都清除掉,若是使用NSNotificationCenter给此对象注册过某种通知,那么通常应该在这里注销

使用弱引用来避免循环引用

理解Block

若是block所捕获的变量是对象类型,那么就会自动保留它。系统在释放这个块的时候,也会将其一并释放。这引出一个重要问题。block块自己可视为对象,也有引用计数。

若是将block块定义在实例方法中,那么除了能够访问类的全部实例变量以外,还可使用 self
变量,块总能修改实例变量,因此在声明时无需加__block。不过,若是经过读取或者写入操做捕获了实例比那两,那么也会自动把self变量一并捕获了,由于实例变量是与self所指代的实例关联在一块儿的。

全局块

定义块的时候,占的内存区域是分配在中的

给块发送copy消息拷贝,这样就能够把块从栈复制到堆了。

全局块不会捕捉任何状态,运行时也无须有状态来参与。

为经常使用的块类型建立typedef

handler块下降代码分散程度

使用block块引用所属对象不要出现引用循环

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

@synchronized(self)根据给定的对象,自动建立一个锁,并等待块中的代码执行完毕

NSLock

不过最好使用 GCD,它能更简单,更搞笑的形式为代码加锁

使用串行同步队列,将读取操做以及写入操做都安排在同一个队列里,可保证数据同步

_syncQueue = dispatch_queue_create("com.xx", NULL);dispatch_sync(_syncQueue, ^{
    _someString = someString;
});

把设置和获取操做都安排在序列化的队列里执行,这样的花全部针对属性的访问操做都同步了

dispatch_barrier_async(dispatch_queue_t queue,disaptch_block_t block);barrier中必须单独执行,不能与其余块并行

多使用 GCD,少用performSelector系列方法

延后执行某个任务

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self doSomething];
});

想把任务放在主线程上执行

dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

掌握 GCD 及操做队列的使用时机

在执行后台任务时,GCD 并不必定是最佳方式,还有一种技术叫作NSOperationQueue

好比,从服务器端下载并处理文件的动做,能够用操做来表示,而在处理其余文件以前,必须先下载清单文件,后续的下载操做,都要依赖于先下载清单文件这一操做。若是操做队列容许并发的话,那么后续的多个下载操做就能够同时执行,但前提是它们所依赖的那个清单文件下载操做已经执行完毕

NSOperation对象有许多属性都适合 KVO 来监听,isCancelled来判断任务是否取消,或者经过isFinished来判断任务是否完成。

NSOperation对象也有线程优先级

经过dispatch group机制,根据系统资源情况来执行任务

使用dispatch_once来执行只需一次的线程安全代码

单例使用

不要使用dispatch_get_current_queue

熟悉系统框架

CFNetwork,此框架提供了 C 语言级别的网络通讯能力,它将 BSD 抽象成易于使用的网络接口。而 Foundation 则将该框架李的部份内容封装为 OC 的接口

CoreAudio,此框架提供的 C 语言 API 能够用来操做设备商的音频硬件

AVFoundation,用来回访并录制音频及视频

CoreData,将对象放入数据库

CoreText,能够高效执行文字排版及渲染操做

多用块枚举,少用 for 循环

构建缓存时选用NSCache而非NSDictionary

精简initializeload的实现代码

+ (void)load,只调用一次。

+ (void)initialize,该方法在程序首次使用该类以前调用,且只调用一次

NSTimer会保留其目标对象

NSTimer很容易出现引用循环

(未完待续)

总结

纯属我的笔记,特别是底层机制颇有做用,现在iOS开发再也不仅仅是把一个内容展示出来,里面还有涉及到各类安全性能,了解根本才是持续发展之道。

相关文章
相关标签/搜索