(*useful)标记:目前觉得有用的函数
//FIXME 标记:待补充
第一章:
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
语法清晰,自定义类型
多个选项同时使用,按位或操作将其组合起来
第二章:
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定义了本类的超类。
- 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
- 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法(内省)来探知。
- 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
补充:runtime的setIvar会将操作的Ivar可变属性重置为readValue (*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协议
- 要点
- 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
- 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。
- 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
- 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
第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条: 用"僵尸"对象 调试内存问题
工具:
- instrument
- facebook的 FBMemoryProfiler
-
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条:用块引用其属性对象时不要出现保留环
@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会循环引用,这是常识了