Effective Objective-C 的一些笔记

第5条:用枚举表示状态、选项、状态码。

1. 凡是需要使用按位或操作的组合的枚举都应使用NS_OPTIONS定义。若是不需要互相组合,则使用NS_ENUM来定义

typedef NS_ENUM (NSUInteger, EOCConnectionState) {
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};
typedef NS_OPTIONS (NSUInteger, EOCPermittedDirection) {
    EOCPermittedDirectionUp = 1 << 0,
    EOCPermittedDirectionDown = 1 << 1,
    EOCPermittedDirectionLeft = 1 << 2,
    EOCPermittedDirectionRight = 1 << 3,
}

==NS_ENUM可以用来实现 “类族模式” 隐藏实现细节。==

第6条:理解 “属性” 这一概念

1. Objecitve-C 的做法是,把实例变量当做一种存储偏移量所用的 “特殊变量”, 交由 “类对象” 保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了。这样的话,无论何时访问实例变量,总能使用正确的偏移量。==甚至可以在运行期向类中新增实例变量。这就是稳固的应用程序二进制接口(Application Binary Interface, ABI)。有了这种稳固的ABI, 我们就可以在 “class-continuation分类” 或实现文件中定义实例变量了。== 所以说,不一定要在接口中把全部实例变量都声明好,可以将某些实例变量从接口的public区段移走,以便保护与类实现有关的内部信息。

==注意:C++ 不能在实现文件中定义实例变量,而 Objective-C 则可以在实现文件中定义实例变量。靠的就是上面所说的这种机制。==

2. 使用@dynamic 关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性代码时,即使编译器发现没有定义存储方法,也不会报错,它相信这些方法能在运行期找到。比如说:从CoreData框架中的NSManagedObject类里继承了一个子类,那么就需要在运行期动态创建存取方法。继承NSManagedObject时之所以要这样做,是因为子类的某些属性不是实例变量,其数据来自后端的数据库中。

第七条:在对象内部尽量直接访问实例变量

1. 在对象内部,在读取实例变量时采用直接访问的形式,而在设置实例变量的时候可以通过属性(setter方法)来做。

2. 直接访问实例变量和通过setter、getter方法访问实例变量的区别:

i. 由于不经过 Objective-C 的 “方法派发” 步骤,所以直接访问实例变量的速度当然比较快。在这种情况下,编译器所生成的代码会直接访问对象实例变量的那块内存。
ii. 直接访问实例变量时,不会调用其 “setter方法”, 这就绕过了相关属性所定义的 “内存管理语义”。比如说:如果在ARC下直接访问了一个声明为copy 的属性,那么并不会拷贝该属性。只会保留新值,并释放旧值。(默认为strong语义)
iii. 如果直接访问实例变量,那么就不会触发 “键值观察” (Key-Value Observing, KVO)通知。
iv. 通过属性来访问有助于排查与之相关的错误,因为可以给"setter方法" 或 "getter方法"中新增断点,监控该属性的调用者及其访问时机。

==注意:在初始化方法中,尽量不要使用setter方法、getter方法访问。这种情况下,总是应该直接访问实例变量。除非:1. 如果待初始化的实例变量声明在超类中,而我们又无法在子类中直接访问此实例变量。那么就需要调用“setter方法 ” 或 “getter方法” 2. 惰性初始化方式,必须通过“setter方法” 或 “getter方法” 来访问属性。否则,实例变量永远不会初始化。==

第9条:以 “类族模式” 隐藏实现细节

//
//  EOCEmployee.h
//  EOCPersonAndEocEmployer
//
//  Created by Jeremy on 2016/10/13.
//  Copyright © 2016年 Jeremy. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
    EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesiger,
    EOCEmployeeTypeFinance,
};

@interface EOCEmployee : NSObject

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign) NSUInteger salary;

+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type;

- (void)doADaysWork;

- (instancetype)initWithName:(NSString *)name salary:(NSUInteger)salary;

@end

#import "EOCEmployee.h"
#import "EOCEmployeeDeveloper.h"
#import "EOCEmployeeDesiger.h"
#import "EOCEmployeeFinance.h"


@implementation EOCEmployee

+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type {
    
    switch (type) {
        case EOCEmployeeTypeDeveloper:
            return [EOCEmployeeDeveloper new];
            break;
        case EOCEmployeeTypeDesiger:
            return [EOCEmployeeDesiger new];
            break;
        case EOCEmployeeTypeFinance:
            return [EOCEmployeeFinance new];
            break;
    }
    
        
}

- (instancetype)initWithName:(NSString *)name salary:(NSUInteger)salary{
    
    if (self = [super init]) {
        _name = [name copy];
        _salary = salary;
        
    }
    
    return self;
}
@end

第11条:理解 objc_msgSend 的作用

1.==消息派发(message dispatch)== 中消息传递机制中的核心函数,叫做objc_msgSend,其原型如下:

void objc_msgSend(id self, SEL cmd, ...)

它将Objevitve-C 的消息调用转换为C语言函数调用。

objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其 “方法列表” (list of method),如果能找到与选择子名称相符合的方法,就调转到其实现代码。如果找到不,那就沿着继承体系向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行 ==“消息转发” (message forwarding)== 操作。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。 那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。

其他的一些==消息派发(message dispatch)== 函数

objc_msgSend_stret

objc_msgSend_fpret

objc_msgSendSuper

==注意:obj_msgSend 用到尾调用优化技术。从而避免了“栈溢出”(stack overflow)的现象==

第12条:理解消息转发(message forwarding)机制

1.消息转发(message forwarding)分为三个阶段:

i. Method resolution

objc运行时会调用

+ (BOOL)resolveInstanceMethod:(SEL)selector;   或
+ (BOOL)resolveClassMethod:(SEL)selector;

让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。使用这种办法的前提是:相关方法的实现代码已经写好,只是等着运行的时候动态的插入在类里面就可以了。此方案常用来实现@dynamic属性。

ii. Fast forwarding

如果目标对象实现了

- (id)forwardingTargetForSelector:(SEL)aselector;

Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个==NSInvocation对象== ,所以相对更快点。

iii.Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送

- (void)forwardingInvocation:(NSInvocation *)invocation;

消息给目标对象。

第14条:理解 “类对象” 用意

谁都知道,所有的对象都是由其对应的类实例化而来,殊不知类本身也是一种对象。其实在Objective-C中任何的类定义都是对象。即在程序启动的时候任何类定义都对应于一块内存。在编译的时候,编译器会给每一个类生成一个且只生成一个”描述其定义的对象”,也就是苹果公司说的类对象(class object),他是一个单例(singleton), 而我们在C++等语言中所谓的对象,叫做实例对象(instance object)。对于实例对象我们不难理解,但类对象(class object)是干什么吃的呢?我们知道Objective-C是门很动态的语言,因此程序里的所有实例对象(instace object)都是在运行时由Objective-C的运行时库生成的,而这个类对象(class object)就是运行时库用来创建实例对象(instance object)的依据。

typedef struct objc_class *Class

//类似链表的结构
struct obj_class {
    
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    
    struct objc_ivar_list *ivas;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list protocols;
    
}

typedef struct objc_class *Class

struct obj_object {
    Class isa;
}

objc对象如何进行内存布局?(考虑有父类的情况)

该objc对象的所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
每一个对象内部都有一个isa指针,指向他的==类对象(class object)== , ==类对象(class object)== 中存放着本对象的

i. 对象方法列表(struct objc_method_list **methodLists, 也即对象能够接收的消息列表,保存在它所对应的类对象中) 
ii. 成员变量的列表(struct objc_ivar_list *ivars)
iii. 遵循的协议(objc_protocol_list protocols)

类对象(class object)内部也有一个isa指针指向元类(meta class), ==元类内部存放的是类方法列表(class mothed list),== 类对象内部还有一个super_class的指针,指向他的父类对象。

每个 Objective-C 对象都有相同的结构,如下图所示:

Objective-C 对象的结构图|
---|---
isa指针 |
倒数第二层父类的实例变量 |
... |
父类的实例变量|
类的实例变量|

  • 根的类对象就是NSObject,它的superclass指针指向nil

  • 类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表(class method list),根元类的isa指针指向自己,superclass指针指向NSObject类。

如图:


image
image

==注意: 类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构,其不同仅仅是在用途上,比如其中的方法列表在类对象(instance object)中保存的是实例方法(instance method),而在元类对象(metaclass object)中则保存的是类方法(class method)==

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,109评论 0 7
  • 第1章 熟悉Objective-C 第1条 了解Objective-C语言的起源 Objective-C是一种“消...
    __silhouette阅读 592评论 0 4
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 694评论 0 2
  • 小时候,为了回家,拼全力; 长大后,回或不回,无所谓。 而现在,回到家里,想离开。 第一次在火车站熬夜通宵,敬陪我...
    小女子喵喵阅读 272评论 1 1