《Effective Objective-C 2.0》摘要
前一段时间将《Effective Objective-C 2.0》这本书浏览了一遍,说一下几个觉得比较有意思的知识点。
感觉这本书是ios开发必看的一本书,最基础的,以及稍微高阶一点的oc技术基本都涉及到了。如果书中的涉及的主题能够都掌握,那么绝对可以宣称自己是一个高级oc使用者。
__objc_msgSend__ (原书Item 11)
如果有一个oc的方法调用id returnValue = [someObject messageName:parameter],那个这个调用最终会被编译器转换为一个oc runtime c函数调用,也就是objc_msgSend(id self,SEL cmd,…)。
如果你在仔细看过iOS APP的crash栈的话,一定不会感到惊奇。
oc的对象的方法的实现最终会被编译为一个c函数,objc_msgSend会去寻找selector对应的实现函数,再调用它;为了加速这个过程,runtime内部会缓存这个对应关系。
有意思的是这个函数还有几个变体,用来处理返回值无法暂存在寄存器里的情况:
objc_msgSend_stret: 如果返回值是一个struct;
objc_msgSend_fpret: 如果返回值是一个浮点数,浮点数可能能放到寄存器里面,但有些CPU架构对浮点数的处理有特殊要求;
objc_msgSendSuper: 这个是用来处理[super message]调用的,当然它也有对应 objc_msgSend_stret,objc_msgSend_fpret的变体
__Class Object__ (原书Item 14)
原文很详细的阐述了oc的Class对象是如何构成的。
一个oc对象如果当做一个struct来看待,第一个字段就是一个Class对象指针,见头文件objc.h中的定义:
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
而Class类型又被定义为:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
}
这说明Class对象本身也是一种oc对象。
再一琢磨,这个定义完全就是数据结构中的单链表节点嘛,我们所直接使用的oc对象则是那个链表头节点,那么这个链表结构到底是怎么回事?
按照原文的说法,oc对象的isa指向对应的Class对象,Class对象的isa指向一个metaClass对象。
这样设计的一个好处,就是Class对象可以完全作为一个oc对象来使用。
一个问题是metaClass对象的isa又指向什么,这个链表如何终结?通过下面这段代码,可以强行获取对象的isa字段,进而得到答案:
typedef struct ClassDump {
Class isa;
} ClassDump;
void dumpClassInfoOfClass(Class cls) {
ClassDump *dump = (__bridge ClassDump*)cls;
Class metaClass = dump->isa;
ClassDump *dumpOfMetaClass = (__bridge ClassDump*)metaClass;
Class isaOfMetaClass = dumpOfMetaClass->isa;
NSLog(@"%p %@\n",cls,NSStringFromClass(cls));
NSLog(@"%p %@",metaClass,NSStringFromClass(metaClass));
NSLog(@"%p %@\n",isaOfMetaClass,NSStringFromClass(isaOfMetaClass));
NSLog(@"----");
}
可以分别对NSObject及派生类调用dumpClassInfoOfClass,发现NSObjectClass对象的isa指向另一个Class对象,按原书的说法,就叫NSObjectMetaClass吧;NSObjectMetaClass对象的isa指向自身。
而UIViewController的Class对象的isa,指向UIViewControllerMetaClass对象,后者的isa指向NSObjectMetaClass。
可见,对于NSObject及其派生类来说,这个链表的最后一个节点就是NSObjectMetaClass(暂且这么叫吧),而且这个节点是一个自环。
Class对象的isa通过一般的手段是访问不到的,如果对一个class对象执行[classObj class]返回的是classObj 本身。
Designated Initializer (原书Item 16)
大家都知道在设计oc类时,应该有一个叫做designated initializer的init方法,该类的其他init方法都应该通过调用该init方法完成核心的初始化功能。当派生类需要定义自己的init方法时,一定要重写父类的designated initializer。
但有时候我们有不止一个designated initializer,比如当实现NSCoding协议时,这是一种完全不同的初始化机制,此时initWithCode:(NSCoder)显然也是一个designated initializer,也不该与类的其他init方法有什么关联。
除了这种特殊情况外,还是应该坚持一个类只有一个designated initializer的原则。
Block (原书 Item 37)
block其实就是一种特殊的oc对象,可以被retain,release;
一个特殊之处在于它不仅可以存储在堆上,还可以在栈上或全局数据区。开发者一般是在方法体里面定义block,此时block实际是在栈上的,超出代码作用域就失效了;要使block任然有效,就需要copy它,因此一些异步回调的block都是需要copy的。
copy一个block实际上就是把block从栈上拷贝到堆里,如果block已经在堆里了,copy就仅仅增加reference count而已。
block位于全局数据区的条件是block不捕获任何上下文变量,这样的block对象的内存映像在编译时就是可知的,完全可以当做一个全局常量来对待,对这样的block执行copy不会有任何作用。
performSelector为什么会有内存警告 (原书Item 41)
这个问题被原书放在了gcd的相关章节里面。
在arc模式下,如果我们把一个selector暂存起来,然后通过performSelector来执行,就会有这样的警告:
warning: performSelector may cause a leak because its selector is unknown.
这个警告是因为arc管理对象引用的规则,arc模式下,对于返回对象引用的方法,依然要依赖于方法的名称来自动管理对象引用计数,如果方法的名字是以copy,new,alloc开头,那么arc则认为返回了一个强引用;如果一个方法返回了强引用,却没有一个强引用变量去接受这个返回值,arch是要进行release的。
因此,performSelector由于不能在编译时知道selector的名字,因此arc处于保险起见,只好什么都不做,然后给出一个告警。
gcd的dispatch queues (原书Item 46)
GCD默认有5个queue,Main Queue是在主线程调度的queue,serial类型;其他High Priority Queue,Default Priority Queue,Low Priority Queue, Background Priority Queue都是concurrent类型,通过后台线程池进行调度。
而我们自己创建的queue实际上是基于系统默认的这些queue的,dispatch_queue_create方法的注释是这样说的:
* The target queue of a newly created dispatch queue is the default priority
* global concurrent queue.
也就是说我们自己创建的queue里面的task最终在Default Priority Queue上进行执行,只不过增加了一层调度而已。
这就是原书说不要使用dispatch_get_current_queue的原因了,因为你得到的不一定是你想要的,它的头文件注释
* Recommended for debugging and logging purposes only:
* The code must not make any assumptions about the queue returned, unless it
* is one of the global queues or a queue the code has itself created.
* The code must not assume that synchronous execution onto a queue is safe
* from deadlock if that queue is not the one returned by
* dispatch_get_current_queue().
* When dispatch_get_current_queue() is called on the main thread, it may
* or may not return the same value as dispatch_get_main_queue(). Comparing
* the two is not a valid way to test whether code is executing on the
* main thread.
这个需求一般发生在dispatch_syn调用的时候,如果调用的目标queu是一个serial queue(QueueA),此时就要小心了,如果当前正处在QueueA当中,那么就可能发生死锁,于是就会有这样的判断:
if (dispatch_get_current_queue()==QueueA) {
dosSomething()
} else {
dispatch_sync(QueueA,accessBlock)。
}
但如果上述代码调用的的外层是这样的,就会发生死锁:
dispatch_sync(queueA,^{
dispatch_sync(queueB,^{
dispatch_sync(queueA,^{
//code
});
});
});
原书给出了一个替代方案dispatch_queue_set_specific,我觉得也挺蛋疼的,这里就不说了。
最好还是避免这样的情况出现:
1)尽量不要使用自定义的serial queue,如果要创建serial queue,这个queue应该是封装在某个类的内部,它用法一定要足够单纯,不要涉及复杂的调用关系;
2)记住main queue是serial的,如果要使用dispatch_sync且目标是main queue,可以通过[NSThread isMainThread]来判断当前的执行的的线程是否主线程
类方法load和initialize(原书Item 51)
这两个类方法可以在类被使用之前进行一些初始化工作:
先说load方法,这个方法在类被加载到runtime的时候被调用,对于ios程序来说,所有类的load方法都是在启动时被调用的,而mac os程序则不一定。该方法被runtime调用且仅调用一次,程序员不应该调用这个方法。
load方法的调用规则:
- 基类的load方法,在子类的load方法之前调用;
- 类的load方法在对应category的load方法之前被调用;
- 被依赖的库中类的load方法先被调用;
- 对于同一个库中被没有继承关系的类,load方法的调用顺序是不确定的;这就说明,在一个类load方法里面去使用另外一个类的时候,要谨慎小心,也许另一个类的load方法还没被调用。
- load方法是每个类、category可以定义一个,oc方法的继承重写规则在此不适用:如果基类定义了load,子类没有定义load,子类就相当于没有load;如果一个类有多个category,且多个category都有load方法,这些load方法都会被调用;
结论:load方法可以说是runtime启动的一部分,除调试目的外,咱们实在不应该掺和。
再说initialize方法,这个方法在类第一次被使用之前调用,如果在程序的执行过称中类没有被使用,initialize方法是不会被调用的;同样程序员不应该调用这个方法。
- 相比于load方法,initialize方法调用的时候rumtime已经启动好了,因此在initialize方法理论上可以做任何事;
- runtime会保证initialize方法的线程安全性,当initialize在一个线程中执行的时候,其他的线程是不能访问这个类的;
- initialize和一般的oc方法一样,适用继承重写规则。
上面1)说理论上initialize可以做任何事,但在实际中不要这么干:
- initialize是同步执行的,如果是在主线程中被执行,就会阻碍UI;
- initialize的执行时机仍然是不可控的,runtime只承诺在类第一次被使用之前执行;
- 如果ClassA的initialize方法实现使用了ClassB的方法,那么此时ClassB的initialize方法执行,如果后者又适用ClassA的方法,而此时ClassA的initialize还没执行完毕,就可能出现意外。
结论:initialize方法可以适当使用,但必须简单,一般用来初始化几个静态变量;不应该在initialize里面调用其他方法,包括本类定义的方法。
《Effective Objective-C 2.0》摘要的更多相关文章
- 《图解 HTTP》 摘要一
学习过程对书本的内容的摘要以及总结,逐步完善,带有个人理解成分. Web 及网络基础 使用 HTTP 协议访问 Web 客户端:通过获取请求获取服务资源的 Web 浏览器等 HTTP 全称:Htype ...
- 【图解HTTP】笔记摘要
第1章 了解Web及网络基础 根据Web浏览器(Web客户端)地址栏中指定的URL,Web浏览器从Web服务器端获取文件资源(resource)等信息,从而显示出Web页面. CERN(欧洲核子研究组 ...
- 《图解HTTP》摘要
网络基础TCP/IP 使用Cookie进行状态管理 HTTP首部 确保Web安全的HTTPS 1.网络基础TCP/IP 2.使用Cookie进行状态管理:HTTP是无状态协议. 3.HTTP首部 HT ...
- 加密算法大全图解 :密码体系,对称加密算法,非对称加密算法,消息摘要, Base64,数字签名,RSA,DES,MD5,AES,SHA,ElGamal,
1. 加密算法大全: ***************************************************************************************** ...
- 《图解HTTP》读书笔记
目前国内讲解HTTP协议的书是在太少了,记忆中有两本被誉为经典的书<HTTP权威指南>与<TCP/IP详解,卷1>,但内容晦涩难懂,学习难度较大.其实,HTTP协议并不复杂,理 ...
- 用 Wireshark 图解:TCP 三次握手
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “snow warn throughout the winter” 一.什么是 Wireshar ...
- Android压力测试快速入门教程(图解)——Monkey工具
文章目录: 一.Monkey简介 二.Monkey的基本用法 三.Monkey测试示例图解 四.Monkey命令参数介绍 五.Monkey log分析 一.Monkey简介 Monkey:Androi ...
- 【转载】图解:二叉搜索树算法(BST)
原文:图解:二叉搜索树算法(BST) 摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!“岁月极美,在于它必然的流逝”“春花 秋月 夏日 冬雪”— ...
- 转:在ElasticSearch之下(图解搜索的故事)
ElasticSearch 2 (9) - 在ElasticSearch之下(图解搜索的故事) 摘要 先自上而下,后自底向上的介绍ElasticSearch的底层工作原理,试图回答以下问题: 为什么我 ...
- Orm图解教程
entity framework框架生成摘要文档为空(没有元数据文档可用)的bug解决方案 西安.王磊 2012-10-25 10:47 阅读:1234 评论:2 ORM for Net主流框架汇 ...
随机推荐
- 零基础6个月学好java月薪1w+看看他是怎么学好java的
21世纪进入信息时代,信息科技给人类的生产和生活方式带来了深刻的变革,信息产业已成为推动国家经济发展的主导产业之一,Java作为含金量极高的一门IT技术,很多人希望从事这个行业,那么想学好Java,要 ...
- CentOS 7离线安装MySQL 5.7
系列文章首发平台为果冻想个人博客.果冻想,是一个原创技术文章分享网站.在这里果冻会分享他的技术心得,技术得失,技术人生.我在果冻想等待你,也希望你能和我分享你的技术得与失,期待. 前言 网上已经有那么 ...
- 【CJOJ2482】【POI2000】促销活动
题面 Description 促销活动遵守以下规则: 一个消费者 -- 想参加促销活动的消费者,在账单下记下他自己所付的费用,他个人的详细情况,然后将账单放入一个特殊的投票箱. 当每天促销活动结束时, ...
- 【UVA 11426】gcd之和 (改编)
题面 \(\sum_{i=1}^{n}\sum_{j=1}^m\gcd(i,j)\mod998244353\) \(n,m<=10^7\) Sol 简单的一道莫比乌斯反演题 \(原式=\sum_ ...
- CSS3 translate、transform、transition区别
translate:移动, transform的一个方法 通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) ...
- 三十天学不会TCP,UDP/IP网络编程 - RST的用法
不知不觉也写了这么多了,继续我的自己的推广大业~完整版可以去gitbook(https://www.gitbook.com/@rogerzhu/)看到. 如果对和程序员有关的计算机网络知识,和对计算机 ...
- NOIP2017 总结
联赛结束,但是我并没有得到预期的结果,特写此文分析原因,希望我不会就此退役. 回顾一年,我做了什么? 2016年联赛,我水了两天,抱着挂掉的心态拿到了1=. 2016-2017寒假,参加集训,三天考试 ...
- Python基础教程3——教你用Python做个简单的加密程序(还基础什么呀,直接来练习吧,带源码)
因为发现基础教程我之前推荐的那个网站就已经很完善了,就不重复写了,所以本汪来一起做练习吧. 一.加密原理 记得当时我学c++的时候,学到输入输出流的时候,当时王老师就教我们写了一个小的加密程序,所以这 ...
- python web开发-flask中response,cookies,session对象使用详解
Response响应对象: 当一个web请求被服务器处理完后,会返回用户请求的响应,这时候就要用到响应对象,根据响应给用户的形式不同,响应对象有以下几种处理方式 如果返回的是一个合法的响应对象,它会从 ...
- Nginx配置ThinkPHP下的url重写(隐藏入口)
搭建好项目后,在网址上输入域名,只能访问首页,其他页面全是404. 在域名后面和控制器前面加上index.php就可以访问. 在tp5官网手册查找后进行配置修改. 打开nginx.conf 后 ,在s ...