- Effective Objective-C 2.0 读书笔记
- Effective Objective-C 2.0笔记(二)
- Effective Objective-C 2.0笔记(一)
- Effective Objective-C 2.0笔记(三)
- Effective Objective-C 2.0笔记(五)
- 《Effective Objective-C 2.0 》 阅读笔
- Effective Objective-C 2.0 无废话精简篇
- Effective Objective-C 2.0 脑图- [O
- iOS开发读书笔记:Effective Objective-C
- iOS开发读书笔记:Effective Objective-C

前言
好长时间没有更新了,年后把《Effective Objective-C 2.0》这本书看完了,上面👆是用思维导图画出来的整本书的内容。下面把书中一些重要的和难理解的知识点整理一下,不把书中的52个方法都一一列出来了。
重要知识点
第4条:多用类型常量,少用#define预处理命令
- 不要用预处理命令定义常量
- 在实现文件中使用
static const
来定义只在编译单元内可见的常量。 - 在头文件中使用
extern
来声明全局变量,并在相关实现文件中定义其值。
/*
* 若常量局限于某编译单元,也就是实现文件
* 则在其前面加`K`
*/
static const NSTimeInterval KanimationDuration = 0.3;
/*
* 若常量在类之外可见
* 则通常以类名为前缀
*/
extern const NSTimeInterval CXLAnimationViewAnimationDuration;
const NSTimeInterval CXLAnimationViewAnimationDuration = 0.3;
第7条:在对象内部尽量直接访问实例变量
- 在对象内部读取数据时,应该直接通过 实例变量来读,而写入数据时,则应通过属性来写。
- 在初始化方法及
dealloc
方法中,总是应该直接通过实例变量来读写数据。 - 使用懒加载的情况下,需要通过属性来读取数据。
第10条:在既有类中使用关联对象存放自定义数据
可以给某对象关联许多其他对象,这些对象通过 键
来区分,存储对象值得时候可以指明 存储策略
,用以维护相应的 "内存管理语义",存储策略由objc_AssociationPolicy
的枚举所定义。
关联类型 | 等效的@property属性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
以下方法可以管理关联对象:
/**
以给定的键和策略为某对象设置关联对象值
*/
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
/**
根据给定的键从某对象中获取相应的关联对象值
*/
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/**
移除指定对象的全部关联对象
*/
objc_removeAssociatedObjects(id _Nonnull object)
/** 点击按钮回调 */
typedef void (^CXLHandleBlock) (NSInteger index);
/** 使用静态全局变量做键 */
static void *CXLAlertViewKey = @"CXLAlertViewKey";
- (void)showuAlert{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"标题" message:@"消息" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定",@"知道了",nil];
CXLHandleBlock block = ^(NSInteger index){
NSLog(@"点击了--%ld",index);
};
objc_setAssociatedObject(alert, CXLAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
CXLHandleBlock block = objc_getAssociatedObject(alertView, CXLAlertViewKey);
block(buttonIndex);
}
第11条:理解objc_msgSend的作用
- 消息由
接受者
,选择子
及参数
构成。给某对象 "发送消息" 也就相当于在该对象上 "调用方法"。 - 发给某对象的全部消息都要有 "动态消息派发系统" 来处理,该系统会查出对应的方法,并执行其代码。
[self someMenthod:@"text"];
/**
编译器会把上面的例子中的消息转换为如下函数
第一个参数是接受者,第二个参数代表选择子(SEL 是选择子的类型),后面的是参数
*/
void objc_msgSend(self,
@selector(someMenthod:),
@"text");
objc_msgSend
函数会根据接收者与选择子的类型来调用适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其 "方法列表" ,如果能找到与选择子名称相符的方法,就跳至其实现代码。若找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转,如果最终还是找不到相符的方法,那就执行 "消息转发" 操作。
第12条:理解消息转发机制
当对象接收到无法解读的消息后,就会启动 "消息转发" 机制。
消息转发分为两大阶段:
- 第一阶段,先征询接收者所述的类,看其是否能动态添加方法,以处理当前这个 "未知的选择子",这叫做动态方法解析。
- 第二阶段:首先接收者看看有没有其他对象能处理这条消息,若有,则运行期系统会把消息转发给那个对象,于是消息转发过程结束,一切正常,若没有 "备援接收者",则启动完整的消息转发机制,运行期系统会把消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

第13条:用 "方法调配技术" 调试 "黑盒方法"(menthod swizzling)
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得 "动态消息派发系统" 能够据此找到应该调用的方法,这些方法均以函数指针的形式来表示,这种指针叫做IMP。
NSString类可以响应lowercaseString
,uppercaseString
,capitalizedString
等选择子,这张映射表中的每个选择子都映射到了不同的IMP之上。

Objective - C 运行期系统提供的几个方法都能用来操作这张表,开发者可以新增选择子,改变选择子对应的方法,交换选择子所映射到的指针。

+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(cxl_viewWillAppear:);
Method originalMenthod = class_getInstanceMethod(class, originalSelector);
Method swizzledMenthod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMenthod =
class_addMethod(class,
originalSelector, method_getImplementation(swizzledMenthod), method_getTypeEncoding(swizzledMenthod));
if (didAddMenthod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMenthod),
method_getTypeEncoding(originalMenthod));
}else{
method_exchangeImplementations(originalMenthod, swizzledMenthod);
}
});
}
- (void)cxl_viewWillAppear:(BOOL)animated {
[self cxl_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
- swizzling应该只在+load中完成。
- swizzling 应该只在 dispatch_once 中完成。
第14条:理解 "类对象" 的用意
- 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了继承体系。
- 每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,通常称为
isa指针
。
方法名 | 区别 |
---|---|
isMemberOfClass: | 判断对象是否为某个特定类的实例 |
isKindOfClass: | 判断对象是否为某类或其派生类的实例 |
第15条:用前缀避免命名空间冲突
- 选择与你公司,应用程序或者两者节有关联的名称作为类名的前缀,并且在所有代码中均使用这一前缀。
- 第三方库,也应该为其名称上加上前缀。
- Apple宣称其保留使用所有"两个字母前缀" 的权利,所以自己选用的前缀应该是三个字母的。
第18条:尽量使用不可变对象
- 尽量创建不可变对象。
- 若某属性仅可以在对象内部修改,则在
class-continuation分类
中将其由readonly
属性扩展为readwrite
属性。 - 不要把可变的
collection
作为属性公开,而应提供相关方法,以此修改对象中的可变collection
。
@interface ViewController : UIViewController
@property (nonatomic,copy,readonly) NSString *tipString;
@end
...
@interface ViewController ()
@property (nonatomic,copy,readwrite) NSString *tipString;
@end
第20条:为私有方法名加前缀
- 给私有方法的名称加上前缀,这样可以很容易的将其同公共方法区分开。
- 不要单独使用一个下划线作私有方法的前缀,因为这种做法是预留给苹果公司用的。
- (void)p_privateMenthod{
....
}
第22条:理解NSCopying协议
- 若想自己所写的对象具有拷贝功能,只需遵循NSCopying协议,并实现其中的方法即可。
- 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying和NSMutableCopying协议。
- 复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下应尽量执行浅拷贝。
- 如果你写的对象需要深拷贝,那么可以考虑新增一个专门执行深拷贝的方法。
@interface CXLPerson : NSObject<NSCopying>
@property (nonatomic,copy,readonly) NSString *firstName;
@property (nonatomic,copy,readonly) NSString *lastName;
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
...
@implementation CXLPerson{
NSMutableSet *_friends;
}
#pragma mark - Init Menthod
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName{
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
_friends = [NSMutableSet new];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone{
CXLPerson *person = [[[self class] alloc]initWithFirstName:_firstName lastName:_lastName];
//注意这里使用 -> 语法,因为_friends并非属性,只是个在内部使用的实例变量。
person -> _friends = [_friends mutableCopy];
return person;
}
@end
深拷贝与浅拷贝的区别?
- 浅拷贝并不拷贝对象本身,仅仅是拷贝指向对象的指针。
- 深拷贝是直接拷贝整个对象内存到另一块内存中。
- 要点:浅拷贝是指针拷贝,深拷贝是内容拷贝。
- 非集合对象拷贝:对不可变对象进行copy是指针拷贝,mutableCopy是内容拷贝。对可变对象进行copy或者mutableCopy都是内容拷贝。
- 集合对象拷贝:对不可变对象进行copy是指针拷贝,mutableCopy是内容拷贝。对可变对象进行copy和mutableCopy都是内容拷贝(集合对象的内容复制仅限于对象本身,对象中的元素仍然是指针拷贝)。
第24条:将类的实现代码分散到便于管理的数个分类之中
- 使用分类机制把类的实现代码划分成易于管理的小块。
- 将应该视为“私有”的方法归入名叫private的分类中,以隐藏实现细节。
第26条:不要在分类中声明属性
- 在
class-continuation分类
之外的其他分类中,可以定义存储方法,但尽量不要定义属性。 - 除了
class-continuation分类
之外,其他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来,虽然可以通过关联对象解决这个问题,但是不推荐怎么做。
第27条:使用 "class-continuation分类" 隐藏实现细节
class-continuation分类
的特点:
- 没有名字。
- 唯一能声明实例变量的分类。
- 没有特定的实现文件。
- 如果某属性在主接口中声明为
只读
,而类内部又要用设置方法修改此属性那么就在分类中将其扩展为可读写
。 - 如果想让类遵循的协议不为人知,则可在class-continuation分类中声明。
第31条:在dealloc方法中只释放引用并解除监听
- 在每个对象的生命周期内,此方法仅执行一次。
- 在dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的KVO和NSNotificationCenter等通知,不要做其他事情。
第34条:以 “自动释放池块” 降低内存峰值
- 自动释放池排布在栈中,对象收到
autorelease
消息后,系统将其放入最顶端的池里。 - 合理运用自动释放池,可降低应用程序的内存峰值。
-
@autoreleasepool
这种新式写法能创建出更为轻便的自动释放池。
第42条:多用GCD,少用performSelector系列方法
如果想把任务放在另一个线程上执行,那么最好不要使用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。
第43条:掌握GCD及操作队列的使用时机
NSOperationQueue
与GCD的区别:
- GCD是纯C的API,而操作队列则是Objective-C的对象,在GCD中,任务用块来表示,而块是轻量级的数据结构,与之相反操作
operation
则是个更为重量级的Objective-C对象。 - 能取消某个操作。
- 指定操作间的依赖关系,一个操作可以依赖其他多个操作。
- 通过键值观察机制监控NSOperation对象的属性。
- 指定操作的优先级。
- 重用NSOperation对象。
第50条:构建缓存时使用NSCache而非NSDictionary
NSCache
胜过NSDictionary
的优点在于:
- 当系统资源将要耗尽时,NSCache可以自动删除缓存。
- NSCache还会先行删除最久未使用的对象。
- NSCache并不会“拷贝”键,而是会“保留”它。
- NSCache是线程安全的,而NSDictionary则绝对不具备此优势。
- 可以给NSCache对象设置上限,用于限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。
第51条:精简initalize 与 load的实现代码

网友评论