iOS学习笔记iOS开发_性能优化iOS Kit

iOS如何写出高质量的代码笔记

2017-03-02  本文已影响947人  Link913

前言

最近在学习这本书,Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法,我会记录一些我不知道或者平时忽略的东西把它记下来.看了一部分感觉收获还是挺大的.

熟悉OC

1,了解OC

所有的OC对象都是在堆中,我们在栈中记录对象分配的地址.

只保存非对象类型的数据,我们可以使用结构体,避免了分配内存和释放内存所造成的额外开销.

2,在类的头文件中,尽量少引入其他头文件

将引入头文件的时机,尽量延后,只有在真正去使用它的时候再去引入头文件,这样可以缩短编译时间.如果一定要在头文件使用到这个类,但并不需要去管他具体的细节,我们可以使用向前声明(@class)来告诉编译器这是一个类.

有时无法使用向前声明,而我们却又需要遵循这个协议时,我们可以将它延后至延展时再去遵守.

协议最好写成一个文件,委托协议是不需要去单独写一个文件的,协议中如果需要使用了其他类的话请使用向前声明(@class)来标识,导入类的头文件请在实现文件导入.

3,多用字面量语法,少用与之等价的方法

NSString,NSNumber,NSArray,NSDictionary等创建时我们可以直接使用字面量语法来创建,如下图所示:

而不是这样子了来创建:

这样子创建可读性高,另外当我们给数组中或者字典中传入nil对象时,XCode会给我们警告,而如果使用第二种方法,当object2为nil时,那这个数组就已经创建完毕了,此时数组中就只有一个对象了.这样子的异常在平时编码中不是好确定的,如果我们用字面量去创建,异常可能就好发现的多了.

4尽量不要或者少使用宏这种预编译命令,而是用类型常量代替

我们用宏来代替字符串,常量,url都是这么做的:

然而这样子做是对宏的滥用,宏其实只是起到了替换的功能,他并没有缩短代码实际的运行时间,也没有检测机制,所以宏这种东西尽量的去少用.

我们在类的内部使用类型常量一般是这么去使用的:

常量名前方要加k,static表示该变量仅在定义此变量的编译单元中可见,注意要写在实现文件头部.

如果这个常量我们要在全局去使用,例如通知的名字:

在这里常量名前一般是需要添加类名的,避免全局使用时混淆,常量的定义一般是从右至左解读,例如上面的意思是:EOCStringConstant是一个常量,而这个常量是一个指针,指向了一个字符串对象.extern关键字表示全局符号表中将会有一个名叫EOCStringConstant的符号.

5,用枚举表示状态,选项,状态码

将int和NSIntger代表的状态全部使用枚举来代替,这样的代码可读性更高,枚举的名字应该通俗易懂.

如果将传递给某个方法的选项表示为枚举类型,而多个枚举又可以同时使用,那么将各选项值定义为2的幂,以便通过按位或操作组合起来,也称作位移枚举.

在编写枚举时切记要使用NS_ENUM和NS_OPTIONS来定义枚举,确保枚举是用开发者所选的底层数据类型实现出来的.一般位移枚举使用的是后者.

当处理枚举类型时要写switch语句时,不要实现default分支,这样加入新的枚举时,编译器会自动提示我们我们没有实现这个枚举.

对象,消息,运行期

6,理解属性

iOS开发时使用nonatomic,因为就算是用atomic也不能完全保证线程安全,还需要采用更为深层的锁定机制才可以,而且使用同步锁对性能的消耗更大.例如,一个线程连续多次读取某属性值的过程中有别的线程在同事改写该值,即便使用了同步锁,还是会读到不同的值.如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。Mac开发一般使用atomic是不会有性能个方面的考虑的.

类中对象的属性,当编译器遇到了访问成员变量的代码时,他会将其替换成偏移量,表示该变量距离存放对象的内存区域的起始地址有多远,当然并不是简单的这样子处理的,否则会出现地址错乱的问题,具体的剖析看下这篇文章,书上讲的也不是很清晰.文章链接

7,对象内部尽量直接访问实例变量

直接访问实例变量的速度快;直接访问实例变量时,不会调用其设置方法,这就绕过了相关属性所定义的内存管理语义;直接访问实例变量不会触发KVO,当然要看具体情况决定;通过属性来读写可以在set&&get方法中打断点,方便调试.

8,理解"对象等同性"

这种做法既能保持高效率,又能使生成的哈希值在一定长度范围内

此时就会发现set中有两个相同的元素了.

首先判断指针是否相等,再然后判断属性是否相等,若不为同一个类,则判断交由超类执行.

9,以"类族模式"隐藏实现细节

类族模式最大的好处就是用户不用关心具体是如何去实现的,保持着接口的简洁性,下面用一个例子来解释一下吧.

Person类的头文件 Person类的实现文件

我们可以看到,这边的创建对象的类方法其中只需要传入性别枚举,我们就会自动的给他创造相应的对象,并且在父类(Person)中,我们并没有对doHouseWork这个对象方法添加实质性的内容.具体的内容我们交给了Person的子类Man和Woman去分别实现.这样就可以做到在调用同一个方法时,却执行了不同的内容,算是一种比较优雅的写法,常见于系统的一些类,如:NSArray等等.

调用文件

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

在继承这种添加属性不可行时我们才会使用"关联对象这种做法",当然这种行为应该尽可能少的去使用,因为调试比较困难通常可能会因为"保留环"等造成一些自己想象不到的问题,常用的地方比如在分类中动态的添加属性.

关联对象的策略类型

如果关联对象成为了属性,那么他就具备相应的语义.

关联对象的三个方法

这里需要注意的是,当在设置关联对象值时,我们通常使用静态全局变量做键.因为若想用两个键匹配到同一个值,则二者必须是完全相同的指针才可以!

11,理解objc_msgSend的作用

OC是一门动态语言,函数的调用并不是在编译期决定的,类似如下图所示:

OC向对象发送消息的函数:


第一个参数代表接收者,第二个参数代表选择子,后续参数就是函数中的那些参数,其顺序不变.选择子就是方法的名字,编译器会转换为如下函数:

objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法,为了完成此操作该方法需要在接收者所属的类中搜寻其"方法列表",如果能找到就跳至其实现代码,如果找不到就沿着集成体系向上查找,最终依旧找不到的话就会执行消息转发.

12,消息转发

对象在收到一个无法解读的消息是会怎么做呢?OC有一个消息转发机制,第一步先征询接收者看能否动态地添加方法来处理这个未知的选择子,若不行则进入第二步看能不能被其他接收者处理.若依旧不行就只能走完整的转发流程了.

这三种消息转发有什么区别或者我们什么情况下去使用呢?

运行时处理消息我们使用第一步,动态方法解析,转发给另一个对象我们使用第二部,被援接收者,需要转发给多个对象时,我们使用第三步,完整的消息转发,这里,我根据书本上写了一个例子,用来实现当不实现属性的set和get方法时,如何将属性值存入字典,并从字典中读取.

实现文件:

    #import "Human.h"
    #import <objc/runtime.h>

    @interface Human ()

    @property (nonatomic, strong)NSMutableDictionary *dict;

    @end

    @implementation Human

    @dynamic name;

    - (instancetype)init{
        
        if (self = [super init]) {
            _dict = [NSMutableDictionary new];
        }
        return self;
    }

    + (BOOL)resolveInstanceMethod:(SEL)sel{

        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
        } else {
            class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }

    - (id)forwardingTargetForSelector:(SEL)aSelector{
        return self;
    }

    //get 函数
    id autoDictionaryGetter(id self,SEL _cmd){
        Human *typedSelf = (Human*)self;
        NSMutableDictionary *humanName = typedSelf.dict;
        NSString *key = NSStringFromSelector(_cmd);
        return [humanName objectForKey:key];
    }

    //set 函数
    void autoDictionarySetter(id self, SEL _cmd, id value){
        Human *typedSelf = (Human*)self;
        NSMutableDictionary *humanName = typedSelf.dict;
        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) {
            [humanName setObject:value forKey:key];
        } else {
            [humanName removeObjectForKey:key];
        }
    }
    @end

13,用"方法调配技术"调试"黑盒方法"

书上的例子实质上就是使用运行时交换方法,在新方法中增加一些日志等功能,切记不可滥用.

14,理解类对象

类本身其实是一个结构体,结构体的第一个变量是isa指针,该变量定义了对象所属的类,结构体里面还有super_class定义了本类的超类.类对象所属的类型,也就是isa指向的类型是另外一个类,被称为元类,用来描述类对象,类方法就位于此处,每个类仅有一个类对象,这里可以变相的理解为单例,而每个类对象仅有一个与之相关的元类.假设有个someClass从NSObject中继承而来,如下图所示:

在类继承体系中查询类型信息是,使用"isMemberOfClass:"判断对象是否是某个类的实例,使用"isKindOfClass:"判断是否为某个类或其派生类的实例,之所以不使用"=="来判断,因为有的类实现了消息转发,使用"class"返回的是发起代理的对象而非接受代理的对象.

接口与API设计

15,用前缀避免命名空间冲突

16,提供"全能初始化方法"

17,实现description方法

18,尽量使用不可变对象

19,使用清晰而协调的命名方式

20,为私有方法名加前缀

21,理解OC的错误模型

22,理解NSCopying协议

协议与分类

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

在协议的delegate的set方法中对是否实现了方法的判断进行一次缓存:

调用的时候直接从缓存判断即可

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

25,总是为第三方类的分类名称增加前缀

26,勿在分类中声明属性

27,使用"class-continuation分类",隐藏实现细节

28,通过协议提供匿名对象

内存管理

29,理解引用计数

30,以ARC简化引用计数

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

32,编写"异常安全代码"时,留意内存安全问题

33,以弱引用避免保留环

34,以"自动释放池块",降低内存峰值

自动释放池会等下一次事件循环时才释放,在这里加一个可以有效避免内存峰值,看需求

35,用"僵尸对象"调试内存管理问题

36,不要使用"retainCount"

块与大中枢派发

37,理解"块"这一概念

38,为常用的块类型创建typedef

39,用handler块降低代码

40,用块引用其所属对象时不要出现保留环

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

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

43,掌握GCD以及操作队列的使用时机

44,通过Dispatch Group机制,根据系统资源状况来执行任务

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

46,不使用dispatch_get_current_queue

系统框架

47,熟悉系统框架

48,多用块枚举,少用for循环

49,对自定义其内存管理语义的集合使用无缝桥接

50,构建缓存时,选用NSCache而非字典

51,精简initialize与load的实现代码

52,别忘了NSTimer会保留其目标对象

上一篇下一篇

猜你喜欢

热点阅读