Effective Objective 2.0 读书笔记

(*useful)标记:目前觉得有用的函数 //FIXME 标记:待补充

第一章:熟悉oc

1. OC起源
 CGRect 这种结构体都存在栈上
 OC对象在堆上(无需手动管理ARC)
复制代码
2. 在类的头文件尽量少引用其他头文件
 增加编译时间,增加类之间的耦合性(引用头文件不回增加app包的大小)
复制代码
3.尽量使用语法糖
dic[@"key"]
更加简洁,直接跑出异常(创建时避免nil)
复制代码
4.多使用类型常量,少用#define
define 定义出来没有类型,例:预处理会把所有的定义ANIMATION_DURATION都替换成0.3
static const  申明内部静态变量
extend 作为外部引用(命名:类名打头,这种常量会放在“全局符号表 global symbol table”)
复制代码
5. 用枚举表示状态,选项码
typedef NS_ENUM 
typedef NS_OPTIONS  使用位由当前硬件设备iphone5s 以上64bit
语法清晰,自定义类型
多个选项同时使用,按位或操作将其组合起来
复制代码

第二章:对象,消息,runtime

6.理解属性
使用nonatomic
natomic会使用同步锁,增加性能开销,但并不能保证线程安全
复制代码
7.在对象内部尽量直接访问实例变量 _property
通过"惰性初始化"(创建成本高,不常用)的属性,必须使用 属性 来访问
在对象内部:
    1. 读取数据  直接(_ )
    2. 写入数据  属性
dealloc中直接通过 实例变量 来读写数据 (_ )
复制代码
8.对象等同性
1.  应该比较的是两个对象的指针
2.  哈希码有可能相同,对象却不同
3.  编写hash时,尽量使用速度快而且hash碰撞几率低的算法
复制代码
9.使用类族隐藏实现细节
1. 类族模式可以把实现细节隐藏在一套简单的公共接口后面
2. cocoa中的集合类型 多使用 类族
类族:+ (instancetype)buttonWithType:(UIButtonType)buttonType;
复制代码
10.在既有类中使用关联对象存放自定义数据
1.类似KV的形势进行关联对象(OBJC_ASSOCIATION_COPY指定属性)
2.会引入(retian cycle)-->关联alert使用block回调的情况下
3. 尽量少用 (runtime 在实际编程中应用都不会太多)
复制代码
11.消息传递. objc_msgSend
1. 运行时会把OC的方法转换成 void setter(id self, SEL _cmd ,id newValue)
2. objc_send 会把 调用的方法 放置到“快速映射表”(fast map)
复制代码
12.消息转发机制

2018.3.6更新:

  • 在接收到触摸事件后,会先在内存缓存中查找是否存在该实例方法。

如果在方法接受类的“list of methods”没有找到对应的方法,就继续向上查找,最终找不到就会启动 消息转发

  1.查找接收者所属的类,看其是否能动态添加方法,以处理这个“未知的方法”。(动态方法解析)
    +(BOOL) resolveInstanceMethod:(SEL)selector
  2.运行期系统把消息转给其他接收者处理。(备援接收者)
    -(id)forwardingTargetForSelector:(SEL)selector
  3.经过上述两步后,如果还是没有办法处理选择子,就启动完成的消息转发
    -(void)forwardInvocation:(NSInvocation *)invocation
复制代码

13.method swizzling
  • 这个东西不能乱用
    +(void)load
    {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(systemFontOfSize:);
        SEL swizzledSelector = @selector(XMTsystemFontOfSize:);
        
        Method originalMethod = class_getClassMethod(class, originalSelector);
        Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
        
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) { //这个添加方法不应该成功(暂时没去Debug)
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
    }
    + (UIFont *)XMTsystemFontOfSize:(CGFloat)fontSize{
          return [self XMTsystemFontOfSize:(((fontSize)/375.0)* CGRectGetWidth([UIScreen mainScreen].bounds))];
     }   
复制代码
14.理解"类对象"
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;

#if defined(Release3CompatibilityBuild)
      struct objc_method_list *methods;
#else
     struct objc_method_list **methodLists;
#endif
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
  };
复制代码
  • 解释:该结构体存放类的“元数据”。其中,isa指针定义了另外一个类——元类(metaclass),用来表述类对象本身所具备的元数据。super_class定义了本类的超类
  1. 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
  • 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法(内省)来探知。
  • 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

补充:runtime的setIvar会将操作的Ivar变为不可变的对象 (*useful)

  const char * name = [@"_name" UTF8String];
  Ivar nameIvar = class_getInstanceVariable([self class], name);
  if (nameIvar && readValue) {
     object_setIvar(self, nameIvar, readValue); //重置对象地址
  }

(*useful)类似对数组的操作 filter(), concat() 和 slice(),会返回一个新的数组
复制代码

第三章:接口与API设计

15.用前缀避免命名空间冲突
* 选择与你的公司、应用程序或二者皆有关联之名作为类名的前缀,并在所有代码中均使用这一前缀。
* 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。
复制代码
16.提供"全能初始化方法"
  • 在类中提供一个全能初始化方法,并于文档里指明。其他初始化方法均调用此方法。
  • 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
  • 如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常
第17条:实现description方法
 这个没什么好说的!方便debug(插件:AutoGenerateDescriptionPluginProd)
复制代码
第18条:尽量使用不可变对象

KVC:任何私有,不可变对象都可以间接的通过这种方式修改

  • 尽量创建不可变的对象。( readonly)
  • 若某属性仅可于对象内部修改,则在“class-continue分类”(27条)中将其由readonly属性扩展为readwrite属性
  • 不要可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。
第19条:使用清晰而协调的命名方式
  • 驼峰式命名
第20条:为私有方法名加前缀
     - (void)publicMethod{ /* --- */} 
     - (void)p_privateMethod{ /* --私有方法前面加 P_-- */}
复制代码
第21条:理解Objective-C错误模型
  • NSError对象里会封装3条信息: Error domain:错误范围,其类型为字符串。通常用一个特有的全局常量来定义。 Error code:错误码,其类型为整数。通常定义为枚举类型。 User info:用户信息,其类型为字典。

  • 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。

  • 在错误不那么严重的情况下,可以指派“委托方法”(delegate method)来处理错误,也可以把错误信息放在NSError对象里,经由“输出参数”返回给调用者。

第22条:理解NSCopying协议
  • 要点
    1. 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
    2. 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。
    3. 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
    4. 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。

第4章.协议与分类

第23条:通过委托与数据源协议进行对象间通信
这个没有什么说的详见UITableView (Api)
复制代码
第24条:将类的实现代码分散到便于管理的数个分类之中
 将独立的功能采用类目的方式进行封装如下(封装的思想)
复制代码

@interface CALayer (XMT)
-(void)setBorderColorFromInterFaceBuilder:(UIColor  *)Color;
@end
@implementation CALayer (XMT)
-(void)setBorderColorFromInterFaceBuilder:(UIColor *)Color{
self.borderColor = Color.CGColor;
}  
@end
复制代码
第25条:总是为第三方类的分类名称加前缀
降低耦合行
复制代码
第26条:勿在分类中声明属性
前面说过分类是用来 拆分功能模块(高类聚低耦合,模块化功能)
复制代码
第27条:使用 "class-cotinuation"分类隐藏实现细节
@interface :
@property(no,stro,readonly)
@interface ()
@property(no,stro,readwrite)
复制代码
     通过“class-continuation分类”向类中新增实例变量。
     如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改此属性,那么就在““class-continuation分类”中将其扩展为“可读写”。
     把私有方法的原型声明在““class-continuation分类”里面。
     若想使类所遵循的协议不为人所知,则可于““class-continuation分类”中声明。
复制代码
第28条:通过协议隐藏匿名对象
协议可在某种程度上提供*匿名类型*。具体的对象类型可以淡化成*遵从某协议的id类型*,协议里规定了对象所实现的方法。
使用匿名对象来隐藏*类型名称*(或*类名*)。
如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可使用匿名对象来表示。    
复制代码

第5章.内存管理

第29条: 引用计数

这个基本是iOS的入门基础

第30条: 以ARC简化引用计数
  • ARC只负责OC对象的内存 尤其要注意:CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFReleas
strong 保留此值
unsafe_unretained 不保持此值,做么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了
weak 不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空
autoreleasing 把对象"按引用传递"(pass by reference) 给方法时,使用这个特殊的修饰符,此值在方法返回式自动释放

复制代码
第31条: 在dealloc方法中只释放引用并解除监听
* dealloc中 不要使用属性(因为已经释放了)
* 释放KVO  NSNoficationCenter
* dealloc中不要去执行异步操作
复制代码
第32条: 编写"异常安全代码"时留意内存管理问题
哪里都需要注意内存问题,都应该重写dealloc去Debug
复制代码
第33条: 以弱引用避免保留环
@weakify(self)
__weak typeof(self) weakSelf = self;
blockCallback^{
  self.property
  self->property
  self->_property
  @strongify(self); //强持有,避免在引用时释放
}
复制代码
第34条: 以"自动释放池"降低内存峰值
for(int i = 0; i < 10000; i ++){
   @autoreleasepool{
       这里创建大量对象
   }
}

复制代码
第35条: 用"僵尸"对象 调试内存问题

工具:

  1. instrument
  2. facebook的 FBMemoryProfiler
  3. Zombie

第36条: 不要使用retainCount
retainCount这个东西 也不一定准确
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
复制代码

第6章.块与大中枢派发

第37条: 理解 "块"
C C++ OC OC++中的词法闭包

栈: void(^block)()   栈上的内存会在复用时被覆盖(unsafe),使用Copy拷贝到 堆(内存管理,引用计数)
堆(常用):copy

typedef void (^Block)();
@property (nonatomic, copy) id (^Block)(id value);
int(^block)() = ^{
        return 1;
    };
复制代码
第38条: 为常用的块类型创建typedef
typedef id  (^Block)(id value);
1. 以typedef重新定义块类型,可令块变量使用起来更加简单
2.复用
复制代码
第39条: 用handler块降低代码分散程度
1.代理模式( delegate)的回调会比较的分散(如果没有良好的变成习惯就更难受了)
2.iOS的回调逐渐开始普及Block(UIAlertViewController)
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
        for (int i = 0; i < 10; i++) {
            NSLog(@"执行并发队列 :%d",i);
        }
    }];
复制代码
第40条:用块引用其属性对象时不要出现保留环

FBRetainCycleDetecto

@weakify(self)
__weak typeof(self) weakSelf = self;
blockCallback^{ 
   self.property 
   self->property
   self->_property 
   @strongify(self); //强持有,避免在引用时释放
}
复制代码
第41条:多用派发队列,少用同步锁
1. @synchronized 同步锁 效率低
2. NSLock(tryLock)      NSRecursiveLock (支持递归)

3.派遣信号
dispatch_semaphore_t semaphonre = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphonre, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphonre);

4.dispatch_barrier 没错使用它也可以做到这一点
    dispatch_queue_t queue = dispatch_queue_create("my.lable", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2");
    });
    //前面执行后执行自身,然后执行后面
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"3");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"4");
    });
复制代码
第42条:多用GCD少用performSelector系列方法
1.performSelector系列方法在内存管理方面容易有疏失,它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法
2.performSelector系列方法所能处理的选择子太过局限,选择子的返回值类型及发送给方法的参数个数都受到限制
3.使用GCD代替(after dispatch_async)
复制代码
第43条:掌握GCD及操作队列的使用时机
OperationQueue从4.0开始就是 基于GCD了,背后的线程池已经降低了开线程带来的额外开销。
更何况一般的应用场景都会设置最大并发数,一个或者几个线程在运行没有太多性能或资源上的差异。
真正的原因是从后台触发一个异步NSOperation的时候,
需要有一个在运行的runloop接收NSURLConnection的回调(cancle)。仅此而已。
复制代码
第44条:通过Dispatch group机制根据系统资源状态来执行任务
第45条:通过Dispatch_once 来执行只需运行一次的线程安全代码
第46条:不要使用dispatch_get_current_queue
多层线程套用会产生死锁
复制代码

第7章.系统框架

第47条:熟悉系统框架
Foundation: 所有OC程序的基础,包含类似NSObject这些基类
coreFoundation:Foundation的C实现,通过(toll-free bridging)五丰桥接
CFNetworking:提供网络通信能力
....
复制代码
第48条:多用块枚举,少用for循环

在遍历dic,set时更加高效

[Arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
}];
复制代码
第49条:对自定义其内存管理语义的collection使用无缝桥接
NSArray * anNSArr = @[@1,@2,@3,@4];
CFArrayRef aCFArr = (__bridge CFArrayRef)anNSArr;
NSLog(@"size of Array = %li", CFArrayGetCount(aCFArr));

复制代码
第50条:构建缓存时选用NScache,而非NSDictionay
NSCache会在系统将要耗尽时(memory warning)他会自动删减缓存,线程安全的
复制代码
第51条:精简initilalize与load的实现代码
对于加入运行期系统中的每个类(class)及分类(category)来说,必定会调用此方法,而且仅调用一次。
+ (void)load {
  //这里面不能调其他类(其他类可能还没加载,不安全)
}

程序首次调用该类之前调用,而且只调用一次
+ (void)initialize{

}
复制代码
第52条:别忘了NSTimer会保留其目标对象
timer会循环引用,这是常识了
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值