美文网首页
编写高质量的代码

编写高质量的代码

作者: 开心一刻_ | 来源:发表于2017-03-14 14:12 被阅读0次

    ·语法糖:计算机语言与另外一套语法等效但是开发者用起来更加方便的语法。

    NSNumer

    NSNumber *number = @3.14;
    int x = 5;
    float y = 6.32f;
    NSNumber *expressionNumber = @(x * y);
    

    NSArray

    NSArray *animals = @[@"cat", @"dog"];
    NSString *dog = animals[1];
    

    属性:

    优势:

    编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”。
    这个过程由编译器在编译期执行,所以编译器里看不到这些“合成方法”的源代码。编译器还自动向类中添加适当类型的实例变量。
    @dynamic:
    告诉编译器,不要自动创建实现属性所用的实例变量也不要为其创建存取方法。
    属性都是nonatomic的:
    历史原因是:在iOS中使用同步锁的开销较大,这会带来性能问题。而且原子性,并不能保证“线程安全”。

    对象等同性:

    简介:

    当且仅当“指针值”完全相同时,这两个对象才相等。NSObject协议中有两个用于判断等同性的关键方法:

    -  (BOOL)isEqual:(id)object;
    -  (NSInteger)hash;
    

    要点:

    • 若想检测对象的等同性,请提供“isEqual:”与hash方法。
    • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
    • 不要盲目地逐个检查每条属性,而是应该依据具体需求来制定检测方案。
    • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

    类族

    要点:

    • 类族模式可以把实现细节隐藏在一套简单的公共接口后面。
    • 系统框架经常使用类族
    • 从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。

    关联对象

    设置关联对象值:

    void objc_setAssociatedObject(id object, void *key,id value, objc_AssociationPolicy policy)
    

    根据给定的键从某对象中获取相应的关联对象值:

    id objc_getAssociatedObject(id object, void *key)
    

    移除指定对象的全部关联对象:

    void objc_removeAssociatedObjects(id object)
    

    应用场景:

    给系统 UIAlertView添加block块的回调,但是没有子类话UIAlertView然后在里面封装的好,所以所以控件最好都自己封装一下它的内部事件。

    objc_msgSend

    动态绑定:

    调用的函数直到运行期才能确定。待调用的函数地址无法硬编码到指令之中,而是要在运行期读取出来。

    给对象发消息:

    id returnValue = [someObject messageName:parameter];
    会转换成一条标准的c语言函数调用,调用的函数是消息传递机制中的核心函数,叫做objc_msgSend,其“原型”如下:
    void objc_msgSend(id self, SEL cmd, ...)
    id returnValue = objc_msgSend(someObject,
                                 @selector(messageName:),
                                 parameter);
    

    实现步骤:

    在someObject中搜寻其方法列表,如果能找到“messageName”这个方法,就跳至其实现代码。如果找不到就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发”操作。

    弊端:

    调用一个方法需要很多步骤。
    objc_msgSend会将匹配结果缓存在“快速映射表(fast map)里面”,每个类都有这样一块缓存,这种“快速执行路径”还是不如“静态绑定的函数调用操作”那样迅速。

    实现流程:

    //是否在此类中添加这个方法
    + (BOOL)resolveInstanceMethod:(SEL)selector;
    //能不能把这条消息转给其他接收者来处理。
    - (id)forwardingTargetForSelector:(SEL)selector;
    //如果来到这步,只能启用完整的消息转发机制。首先创建NSInvocation对象,把尚未处理的那条消息有关的全部细节都封装其中。
    - (void)forwardInvocation:(NSInvocation *)invocation;
    
    消息转发.png

    方法互换:

    void  method_exchangeImplementations(Method m1, Method m2);
    

    应用场景:

    可以通过这一手段来为既有的方法实现增添新功能。

    1. 比方说在调用lowercaseString的时记录某些信息,这时就可以通过方法交换来达成目的。
    2. NSArray里面添加数组的时候,加nil会崩溃,所以就可以在add方法里面加上空判断。
      这个方法最好是不用,最好是在调试的时候使用,很容易出问题。

    获取方法实现:

    Method class_getInstanceMethod(Class aClass, SEL aSelector);
    

    理解“类对象”的用意

    简介:

    对象类型并非在编译器就绑定好了,而是要在运行期查找。
    “在运行期检视对象类型”这一操作叫做“类型信息查询”。

    id类型本身定义:

    typedef struct objc_object {
      Class isa;
    } *id;
    

    isa指针描述了实例所属的类。

    isMemberOfClass:能够判断出对象是否为某个特定类的实例。
    isKindOfClass:能够判断出对象是否为某类或其派生类的实例。

    前缀

    写类的时候要加上命名前缀:

    Apple宣称其保留使用所有“两字母前缀”的权利,所以你自己选用的前缀应该是三个字母的。
    需要加前缀的地方:
    1)类命
    2)分类及分类中的方法
    3)纯c函数
    4)全局变量

    如果应用程序自身和其所用的程序库都引入了同名的第三方库:

    第三方库应该加上前缀以避免命名冲突。

    要点:

    1.选择与你的公司、应用程序或二者皆有联系之名称作为类名的前缀,并在所有代码中均使用这一前缀。
    2.若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

    全能初始化方法

    介绍:令其他初始化方法都来调用它。只有在这个方法内部才会存储内部数据。这样的话,当底层数据存储机制改变时,只需修改次方法的代码就好,无须改动其他初始化方法。

    要点:

    1)在类中提供一个全能初始化方法,并于文档里指明。其它初始化方法均应调用此方法。
    2)若全能初始化方法与超类不同,则需覆写超类中的对应方法。
    3)如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。

    description

    1)实现description方法返回一个有意义的字符串,用以描述该实例。
    2)若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription方法。

    为私有方法名加前缀

    原因:

    类的公共API不变随意改动,私有的API却可以修改。
    方式:

    - (void)p_privateMethod {
    /* ... */
    }
    

    要点:

    1)给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开。
    2)不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。

    理解oc的错误模型
    简介:

    抛出异常,那么本应再作用域末尾释放的对象现在却不会自动释放了。即使用ARC,也很难写出在抛出异常时不会导致内存泄漏的代码。
    用处:
    异常抛出以后,无需考虑恢复问题,应用程序在此时应该退出。
    异常只用于极其严重的错误。

    第26条 勿在分类中声明属性
    要点:

    把封装数据所用的全部属性都定义在主接口里。
    在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。

    原因:
    1)分类是一种手段,目标在于扩展类的功能,而非封装数据。
    2)而且重写set和get方法需要把相似的代码写很多遍,内存管理容易出问题。
    3)通过属性特质修改了某个属性的内存管理语义,而此时还要记得要设置方法中也得修改设置关联对象时所用的内存管理语义。

    第五章 引用计数

    简介:
    一个对象所占的内存在“解除分配”之后,只能放回“可用内存池”。内存不一定被复写了。

    autorelease

    在方法返回的时候调用autorelease。它会在稍后释放对象,从而给调用者留下了足够长的时间。保证对象跨越“方法调用边界”后一定存活。

    autorelease能延长对象生命期,使其跨越方法调用边界后依然存活一段时间。

    ARC

    优点:

    1)增加程序效率,比如一对release和retain会被抵消。
    2)arc以后不用考虑set方法里面的“边界情况”
    3)arc会优化处理返回aotorelease的情况。

    要点:

    • ARC之后,可省去类中许多“样板代码”。
    • ARC机制:在合适的地方插入“保留”及“释放”操作。
      变量的内存管理语义可以通过修饰符指明。
    • ARC只负责管理OC对象的内存。coreFoundation对象不归ARC管理,开发者应调用CFRetain/CFRelease.
    • 由方法返回的对象,其内存管理语义总是通过方法名来体现。这是开发者必须遵守的规则。

    块会自动保留其捕获的全部对象。

    dealloc

    注意:

    1)虽说dealloc中释放引用,但是开销较大或系统内稀缺的资源则不在此则。像是文件描述符、套接字、大块内存等。不能指望dealloc方法必定在某个特定的时机调用。
    2)不要随意在delloc里面调用方法。

    要点:

    1)delloc里面应该做的事情就是释放对象、KVO、通知等。不要做其他事情。
    2)如果对象持有大量资源,那么应该专门创建一个方法来释放此资源,这样的类要和使用者约定,用完资源后必须调用close方法。
    3)异步执行的方法不应在delloc里调用,delloc最好不要调用方法,因为此时对象已处于正在回收的状态。

    异常处理

    简介:

    @finally块,无论是否抛出异常,其中的代码都会保证运行,而且只运行一次。
    要点:
    1)捕获异常时,一定要注意将try块内所创立的对象清理干净。
    2)在默认情况下,ARC不生成安全处理异常所需的清理代码。开始编译器标志后,可生成这种代码,不过会导致程序变大,而且会降低运行效率。关键字是-fobjc-arc-exceptions,在抛出异常的文件加上这个标识。

    保留环

    weak修饰属性特质

    自动释放池

    main里面的自动释放池:理解成为最外围捕获全部自动释放对象所用的池。

    自动释放池嵌套用的好处是:可以借此控制应用程序的内存峰值,使其不致过高。

    应用场景:

    监控内存用量,判断其中有没有需要解决的问题,如果没完成这一步,就别急着优化。虽然自动释放池块的开销不太大,但毕竟还是有的,所以尽量不要建立额外的自动释放池。

    要点:

    • 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
    • 合理运用自动释放池,可降低应用程序的内存峰值。
    • @autoreleasepool这种新式写法能创建出更为轻便的自动释放池。

    僵尸对象

    简介:

    僵尸对象是调试内存管理问题的最佳方式。

    NSString和NSNumber的引用计数

    NSString:OC对NSString有特殊照顾。所有的NSString的引用计数默认初始值都会非常非常大。是一个常量。

    NSNumber也类似,它使用了一种叫做“标签指针”的概念来标注特定类型的数值。

    内存区域

    分为5个区域:

    1)栈区:由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
    优点:是快速高效
    确定:数据不灵活
    特点:栈无需释放,也就没有释放函数。
    2)堆区:需要程序员自己释放
    3)全局区(静态区):全局变量和静态变量的存储是放在一起的,结束的时候由系统释放。
    全局区又分为:初始化的和未初始化的。
    4)文字常量区:存放常量字符串,程序结束后由系统释放。
    5)程序代码区:存放函数的二进制代码。

    强大之处:
    在声明它的范围里,所有变量都可以为其所捕获。
    但是捕获的变量是不可以修改的,除非变量加上__block标识。

    捕获:

    如果块所捕获的变量是对象类型,那么就会自动保留它。
    如果通过读取或写入操作捕获了实例变量,那么就会自动把self变量一起捕获了,因为实例变量与self所指代的实例关联一起的。
    块拷贝的并不是对象本身,而是指向这些对象的指针变量。

    全局块、栈块及堆块

    全局块:这种块不会捕捉任何状态(比如外围的变量等),这种块就相当于是一个单例。
    关键点:
    如果块所捕获的对象直接或者间接地保留了块本身,那么就得当心保留环问题。
    一定要找个适当的时机解除保留环,而不能把责任推给API的调用者。

    框架

    用C语言来实现API的好处是:可以绕过OC运行期系统,从而执行速度。

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

    原因:

    若是在self对象上频繁加锁,那么程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码,这么做其实并没有必要。

    多个获取方法可以并发执行,而获取方法和设置方法之间不能并发执行。

    栅栏:在队列中,栅栏块必须单独执行,不能与其他块并行。并发队列如果接下来处理的块是个栅栏块,那么就一直等当前所有冰法快都执行完毕,才会单独执行这个栅栏快。待栅栏块执行完毕以后,再按正常方法继续向下处理。

    要点:

    派发队列可用来表述同步语义,这种做法要比使用@synchronized块或NSLock对象更简单。
    2)将同步和异步派发结合起来,可以实现与普通加锁机制一样的同步操作,而这么做却不会阻塞执行异步派发的线程。
    3)使用同步队列及栅栏块,可以令同步行为更加高效。

    NSCache胜过NSDictionary

    原因:

    1)NSCache,当系统资源将要耗尽时,它可以自动删减缓存。还会先行删减“最永久未使用的对象。”
    2)NSCache是线程安全的。开发者在不编写枷锁代码的前提下,多线程便可同时访问NSCache。
    3)

    相关文章

      网友评论

          本文标题:编写高质量的代码

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