下面只是对读到的所有 Tips 结合我平时开发中遇到的问题进行总结,每一个 Tips 和书中的每一条对应,本文的目的是去掉书中的大部分讨论的内容,让人能够马上使用这些 Tips,建议阅读过原书后食用更佳。

CHAPTER 1 熟悉 OBJECTIVE-C

  • Tips 1 Objective-C 的起源

    • Objective-C 是从 C 语言演化而来,有 C 的一些基础会有很大帮助
  • Tips 2 头文件中减少引用

    • 减少在类的头文件中 import 其他头文件,如果使用其他类,那么使用@class ClassName;来进行Forward Declaring
    • 对于协议,每个协议放到对应的头文件,使用时候引用
    • 对于委托协议(比如 UITableViewUITableViewDelegate)因为只有与委托类放在一起才有意义,所以就不用单独分离头文件,应该放到定义 UITableView 的头文件中
  • Tips 3 使用字面量

    • 对于 NSStringNSNumberNSDictionaryNSArray 使用类似 @"String"@1@[]@{} 不要使用等价方法
  • Tips 4 使用类型常量

    • 定义常量时,使用类型常量不要使用 #define,比如:

      // 使用如下的方式定义
      static const NSInteger kInteger = ;
      // 而不是
      #define SOME_INTEGER 1

      这样可以给编译器类型信息,在编译时和开发时能够进行类型检查

    • 每一个 m 文件都是一个编译单元

    • 使用static 声明表示在本编译单元有效,若需要将变量放到全局有效,那么需要使用 extern

    • 使用 const 表示常量不会被修改

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

    • 使用 NS\_ENUM 宏定义枚举,因为枚举是按顺序的,也就是枚举值是1,2,3…… 这样的
    • 使用 NS\_OPTION 定义选项,因为选项是按位的,也就是选项是通过 1 << 01 << 1 这样来定义的,表示1右移

CHAPTER 2 对象,消息,运行时

  • Tips 6 理解属性

    • 使用属性,而不是实例变量,在代码中使用点(.)操作符访问属性

    • 属性会生成对应的实例变量,一般是属性名前加下划线,也可以在类的实现代码中通过 @synthesize 来指定,例如:@synthesize firstName = _firstName

    • 使用 @dynamic 告诉编译器不需要生成对应的getter 和 setter

    • 属性的 attribute 会影响编译器生成的代码

      • atomic / nonatomic,原子性,一般我们都使用 nonatomic 因为 iOS 的属性锁开销很大,另外 atomic 并不能保证线程安全

      • readwrite / readonly,读写或是只读

      • 内存管理要注意的

        • assign,简单类型直接赋值
        • strong,表示持有
        • weak,不持有,在对象被释放时属性将会变成 nil
        • unsafe_unretained,不持有,在对象被释放时属性不会变成 nil
        • copy,设置属性时会调用对象的 -copy 方法获得新的对象,建议所有不可变的 NSStringNSArrayNSDictionary 都使用这个方法,可变的类型不可以使用这个方法
        • getter=name / setter=name,指定 getter 和 setter 方法的名字
  • Tips 7 对象内部直接访问实例变量,设置时通过属性方法

    • 直接访问实例变量减少方法调用消耗
    • 设置通过属性方法,调用实际的 setter,能够保证写入控制和 KVO 的触发
    • 可以在 getter 和 setter 方法中加入断点方便调试
    • 惰性初始化的变量,因为需要需要重写 getter 方法,所以不能使用直接访问实例变量来访问
  • Tips 8 对象相等

    • == 只是比较对象指针是否相等,深层的比较需要使用 -isEqual: 方法
    • 如果 -isEqual: 返回返回真,那他们的 -hash 方法要返回同一个值,但是 -hash 方法返回同一个值的两个对象 -isEqual: 不一定为真
    • -hash 方法用作在集合类型中计算索引,如果所有对象的这个方法都返回同一个值,那么在集合中检索对象性能会很差,这个方法应该使用计算速度快,并且不容易碰撞的方法实现
    • 有特定相等判断方法的对象,优先使用特定相等判断方法,可以减少调用次数和对对象进行类型检查(例如 NSString-isEqualToString: 方法)
    • 放入集合对象中的对象,需要保证 -hash 方法得到的值不会变,如果放入集合后,修改集合内对象导致 -hash 的值发生变化,那么集合对象是不会知道 -hash 值有变化,并且将来会出现奇怪的错误
  • Tips 9 类簇

    • 类簇很有用,可以把实现细节隐藏在抽象的基类中
    • 对于使用到类簇的对象,需要使用 -isKindOfClass: 来判断,不可使用 [object class] == [Class class] 或者 -isMemberOfClass: 来判断
    • 若要继承类簇中的类,那么需要根据文档实现对应的方法
  • Tips 10 Associated Object

    • 可以为已有的对象创建新的属性

    • 设置方法

      • void objc\_setAssociatedObject(id object, void *key, id value, objc\_AssociationPolicy policy)
      • id objc\_getAssociatedObject(id object, void *key)
      • id objc\_removeAssociatedObject(id object)
    • 关联类型和属性的对应(上面 policy 的值)

      • OBJC_ASSOCIATION_ASSIGN:assign
      • OBJC_ASSOCIATION_RETAIN_NONATOMIC:nonatomic, retain
      • OBJC_ASSOCIATION_RETAIN:retain
      • OBJC_ASSOCIATION_COPY_NONATOMIC:nonatomic, copy
      • OBJC_ASSOCIATION_COPY:copy
    • key 的值,使用一个 opaque pointer,一般来说使用静态全局变量

  • Tips 11 理解 objc\_msgSend 的作用

    • Objective-C 中所有的方法,都是 C 函数

    • 给某个对象的消息全部都是动态发送的,如下

      • id returnValue = [someObject messageName:parameter]
      • 编译后,将会使用 objc\_msgSend 函数处理消息发送,得到 id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
      • objc\_msgSend 会去 someObject 的方法列表中对应的函数,如果找不到,那么沿着继承体系继续找,还是找不到,那么会进行消息转发操作(在 Tips 12 讲解)
    • Objective-C 的运行时已经做了很多保证让这套机制性能很好

      • 其中一个优化就是尾调用优化,Objective-C 的每个对象的方法都是 C 方法,并且有和 objc\_msgSend 一样的原型,这样在 objc\_msgSend 从对象的方法列表中找到对应函数时,可以直接跳转过去,不需要重新在调用堆栈中插入新的栈帧
  • Tips 12 理解消息转发

    • 因为 Objective-C 使用运行时来决定具体调用的方法,所以在运行之前是不知道一个对象是否能响应特点方法的

    • 消息转发是在一个对象收到无法解读的消息时触发的机制

    • 消息转发的过程

      • 第一步,进行动态方法解析——询问接收者,所属的类,看是否能动态添加方法,来处理未知的 selector
      • 第二步,第一步无法处理这个 selector 的话进行消息转发——首先,让接收者看看是否有对象能处理这个消息,如果有,那么丢给他处理;如果没有,那么运行时会把所有和消息相关的东西放到一个 NSInvocation 对象里面,最后再给接收者一次处理的机会
    • 动态方法解析

      • 过程,下面两个过程是渐进的,第一个失败了,那么执行第二个

        • 调用 + (BOOL)resolveInstanceMethod:(SEL)selector 询问类是否能新增一个实例方法处理这个消息
        • 调用 + (BOOL)resolveClassMethod:(SEL)selector 询问类是否能增加一个类方法来处理这个消息
      • 用法,相关方法实现已经写好,只等着运行时去动态插入到类里面就行了

        • 实现 @dynamic 属性
    • 消息转发

      • 备选的消息接收者

        • 调用 - (id)forwardingTargetForSelector:(SEL)selector 询问接收者是否有另一个对象来处理这个消息
        • 可以模拟多重继承,由对象内的其他对象来处理这个消息,但是从调用者看来,是被调用的对象处理的消息
        • 这样转发的消息,我们是无法进行操作或是修改消息内容的
      • 完整的消息转发

        • 创建 NSInvocation 对象,把 selector,target 和参数都放进去
        • 消息派发系统(message-dispatch system)调用 - (void)forwardInvocation:(NSInvocation *)invocation 方法吧消息指派给目标对象
        • 这个方法可以简单的只修改接收者让另一个对象去接受处理这个方法,但是这样做法和使用备选的消息接收者做法是等效的,所以一般来说更多是先修改消息内容,再触发消息
        • 这个方法的如果没有实现,那么就会调用超类的这个方法,类的继承体系中的所有类都有机会处理直到 NSObject
        • NSObject- (void)forwardInvocation:(NSInvocation *)invocation 只是简单的调用 - (void)doesNotRecognizeSelector: 方法抛出异常,所以一般向对象发送他没有实现的方法都会通过这个方法抛出异常
  • Tips 13 Method Swizzling(原书叫方法调配技术,看到这个名词,第一句话是什么鬼)

    • 很多时候,这个技术被大家称为黑魔法,但是其实他做的只是在运行时交换方法的实现而已

    • 所有的方法都是在对象中是一个 IMP 指针,指针原型 id (*IMP)(id, SEL, ...)

    • 每个类通过一张映射表来映射可相应的 selector 和对应 IMP 指针的关系

    • 可以做 AOP

    • 对方法的操作

      • 使用 void method_exchangeImplementations(Method m1, Method m2) 来交换两个方法

      • 使用 Method class_getInstanceMethod(Class aClass, SEL aSelector) 来获取类的实例方法

      • 常规的 Method Swizzling 做法

         // 在 Category 的定义文件中增加我们将要用来替换的方法
        @interface NSString (MethodSwizzling)
        - (NSString *)ms_myLowercaseString;
        @end // 在 Category 的实现文件中,进行交换
        @implementation NSString (MethodSwizzling)
        - (NSString *)ms_myLowercaseString {
        // 在调用原方法前做点其他的事情
        // 注意这里并不是递归调用,而是因为我们交换了 lowercaseString 和 ms_myLowercaseString 的实现,所以这里调用 ms_myLowercaseString 实际上是在调用 lowercaseString 方法
        NSString *s = [self ms_myLowercaseString]; // 在调用完原方法后做点其他的事情
        return s;
        } + (void)load {
        Method m1 = class_getInstanceMethod([NSString class], @selector(lowercaseString));
        Method m2 = class_getInstanceMethod([NSString class], @selector(ms_myLowercaseString));
        method_exchangeImplementations(m1, m2);
        }
        @end
  • Tips 14 理解类对象

    • 每个对象结构体的首个成员变量是 Class 类的变量
    • Class 对象是一个 objc_class 的结构体,里面保存了类的元数据
    • Class 类同样有元类(metaclass)
    • 某个类如果有超类(super class)那么他的 Class 对象的元类,也继承于该类超类的元类
    • -isMemberOfClass: 可以判断某个对象是否是某个类的实例
    • -isKindOfClass: 可以判断某个对象是否是某个类或其子类的实例
    • 使用上面说到的两个方法类判断类型,不要直接比较类对象,因为某些对象可能实现了消息装发功能

EFFECTIVE OBJECTIVE-C 2.0 TIPS 总结 CHAPTER 1 & CHAPTER 2的更多相关文章

  1. Effective Objective-C 2.0 Tips 总结 Chapter 3 & Chapter 4

    Chapter 3 接口与 API 设计 Tips 15 使用前缀避免明明空间冲突 Objective-C 没有命名空间,所以我们在起名时要设法避免命名冲突 避免命名冲突的方法就是使用前缀 应用中的所 ...

  2. 阅读《Effective Java》每条tips的理解和总结(1)

    <Effective Java>这本书的结构是90来条tips,有长有短,每条tip都值的学习.这里根据对书中每条tip的理解做简短的总结,方便日后回顾.持续更新~ 1. 考虑用静态方法代 ...

  3. iOS开发——技术精华Swift篇&Swift 2.0和Objective-C2.0混编之第三方框架的使用

    swift 语言是苹果公司在2014年的WWDC大会上发布的全新的编程语言.Swift语言继承了C语言以及Objective-C的特性,且克服了C语言的兼容性问题.Swift语言采用安全编程模式,且引 ...

  4. 执行sudo时报错:effective uid is not 0

    http://jingyan.baidu.com/article/c45ad29cd83d4b051753e232.html     今天将 / 授权给了一个普通用户 导致一些问题. 启事: 操作前一 ...

  5. [svc]执行sudo时报错:effective uid is not 0

    http://jingyan.baidu.com/article/c45ad29cd83d4b051753e232.html 今天将 / 授权给了一个普通用户 导致一些问题. 启事: 操作前一定要先在 ...

  6. 阅读《Effective Java》每条tips的理解和总结(2)(持续更新)

    15. 使类和成员的可访问性最小化 一个好用的类的属性必须要隐藏起来,干净的将它与类的api分离开来,类之间只通过api相互使用,降低他们之间的耦合性.为了做到这一点,建议根据情况选择尽可能低的访问级 ...

  7. Journal entry of the eighth chapter to chapter ten

    Chapter eight: 当我们做一个项目的时候,一开始可能会信息满满,或者说是通过一些调查分析后觉得自己的团队能完全实现用户所提出的所有要求,但是,往往在很自信的时候,我们都会处处碰壁,因为组内 ...

  8. Journal entry of the thirteenth chapter to chapter seventeenth(第十三章和十七章阅读与疑问)

    第十三章: 软件测试的意义在于: a.     发现软件错误: b.     有效定义和实现软件成分由低层到高层的组装过程: c.     验证软件是否满足任务书和系统定义文档所规定的技术要求: d. ...

  9. Journal entry of the eleventh chapter to chapter twelfth

    第十一章:正如很多人一样,觉得软件工程这个课程好像没什么用,感觉提高不了自己的写代码能力,学的都是理论知识,好像对于我们这种技术类的专业离得有点远,是这样的吗? 第十二章:每样东西都没有完美的,即使我 ...

随机推荐

  1. ecshop 设置管理员

    <?php define('IN_ECS', true); require(dirname(__FILE__) . '/includes/init.php'); $admin_name=trim ...

  2. 【解题报告】[动态规划] RQNOJ - PID15 / 采药

    原题地址:http://www.rqnoj.cn/problem/15 好久以前做的题了,是个背包问题,就不解释了. #include<stdio.h> #define MAX 100 i ...

  3. JVM——新生代与老年代

    首先看在JVM的堆中,按代的划分: Young:主要是用来存放新生的对象. Old:主要存放应用程序中生命周期长的内存对象. Permanent:是指内存的永久保存区域,主要存放Class和Meta的 ...

  4. python tile函数用法

    tile函数位于python模块 numpy.lib.shape_base中,他的功能是重复某个数组.比如tile(A,n),功能是将数组A重复n次,构成一个新的数组,我们还是使用具体的例子来说明问题 ...

  5. 【转】IOS --- OC与Swift混编

    群里大神发的网址,感觉有用就先收录了,暂时没时间看SWIFT,感觉代码简洁,但是可阅读性不是太高,有些代码让系统去判断类型,同样的,我们看代码的时候也得自己去判断类型,或许看多就习惯了,有时间再说吧, ...

  6. Linux中重定向及管道

    1重定向1.1      重定向符号 >               输出重定向到一个文件或设备 覆盖原来的文件 >!              输出重定向到一个文件或设备 强制覆盖原来的 ...

  7. 嵌入式 Linux下编译并使用curl静态库

    #x86 ./configure --disable-shared --enable-static --disable-ftp --disable-ipv6 --disable-rtsp --disa ...

  8. CURL: CURLE_COULDNT_CONNECT问题探究

    摘自::  存储系统研究: socket connect error 99(Cannot assign request address) 这是最近使用libcurl写http服务的压力测试的时候遇到的 ...

  9. 关于TCP/UDP缓存

    1.修订单个socket的缓冲区大小:通过setsockopt使用SO_RCVBUF来设置接收缓冲区,该参数在设置的时候不会与rmem_max进行对比校验,但是如果设置的大小超过rmem_max的话, ...

  10. C#中 String 格式的日期时间 转为 DateTime

    C#中并没有表示时间的变量,只有DateTime,所以要表示时间,可以用TimeSpan表示. 方法一:Convert.ToDateTime(string) string格式有要求,必须是yyyy-M ...