Effective Objective-C 2.0阅读笔记

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

  • 在对象接口的定义中,就可以使用属性,这是一种标准的写法,能够访问封装在对象里的数据
  • 属性的优势
    1. 如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”
    2. 编译器还会自动向类中添加适当类型的实例变量(可以使用@synthesize语法来指定实例变量的名字)
    3. 可以使用@dynamic关键字,编译器就不会为上面这个类自动合成存取方法或实例变量。
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

//上面写出来的类于下面这种写法等效
- (NSString*)fullName;
-(void)setFullName:(NSString*)fullName;
  • 访问属性——点语法/直接调用存取方法,这两者等效
EOCPerson *person = [[EOCPerson alloc] init];
    
    //两种写法是一样的
    person.firstName = @"Bob";
    [person setFirstName:@"Bob"];
    
    //两种写法是一样的
    NSString *lastName = person.lastName;
    NSString *lastName1 = [person lastName];
  • 属性特质
  1. 原子性

    1. atomic:默认情况下,由编译器所合成的方法会通过锁定机制确保其原子(atomic)
    2. nonatomic: 如果属性具备nonatomic特质,则不使用同步锁。
    3. 一般都写nonatomic

    有效的属性值。若是不加锁的话(即nonatomic),那么其中一个线程正在改写某属性时,另一个线程突然闯入,就有可能读到不准确的数值。如果开发过iOS程序,你就会发现,所有的属性都声明为 nonatomic。这样做的历史原因是:在iOS程序中使用同步锁的开销较大,这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”,若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如:一个线程在连续多次读取某属性值的过程中有别的线程在改写该值,那么即便属性声明为atomic,也还是会读到不同的属性值。因此,开发iOS程序时一般都会使用nonatomic属性。但是在开发MacOS X程序时,使用atomic属性通常时没有问题的。

  2. 读写权限

    • readwrite:拥有“获取方法”(getter)和“设置方法”(setter)
    • readonly:仅拥有“获取方法”(getter)
      在这里插入图片描述

3.内存管理语义

  • assign 只会针对“纯量类型”例如CGFloat或NSInteger
  • strong 设置方法回先保留新值,并释放旧值,然后再将新值设置上去
  • weak 既不保留新值,也不释放旧值,同assign类似。指向被赋值的对象,该对象也可能被回收
  • unsafe_unretained 类似于week,不同是指针所引用的对象回收之后,该指针不会被赋值为nil,该指示符极少用。
  • copy 此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝”(copy)。NSString一般使用copy,因为有可能会是NSMutableString类的实例,字符串的值可能会在非自愿情况下被修改。只要实现属性所用的对象是“可变的”,就应该设置新属性值时拷贝一份

ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。该机制在iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 可以使用该机制。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。

iOS 属性关键字

Objective-C高级编程之引用计数,看我就够了

被无数人写过的assign,retain,strong,weak,unsafe_unretained,还有copy

  1. 方法名

可以指定存取方法的方法名

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

  1. 点语法和直接访问实例变量

    在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。

    //点语法
    self.firstName
    //直接访问
    _firstName
    
    • 直接访问比较快
    • 直接访问直接跳过内存管理语义(如copy),不会调用其“设置方法”
    • 直接访问不会触发KVO
    • 点语法访问可以在setter/getter处设置断点检验错误
  2. 在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据,因为子类可能会覆写。

  3. 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取属性来读取数据。

    如果使用来“惰性初始化”技术,那么必须通过存取方法来访问brain属性(在重写getter方法时,不能通过self.label对属性label进行访问,因为用点语法就相当于调用set或get方法,导致循环调用,在外部调用点语法时就形成无限循环。)

    iOS 懒加载_Echo &的博客-CSDN博客

第八条:理解“对象等同性”这一概念

iOS - 判断对象相等,重写isEqual,hash_爱尔兰堤坝的博客-CSDN博客

  • ==比较的是两个指针本身,而不是其指向的对象
  • isEqual来判断两个对象的等同性:一般两个类型不同的对象总是不相等的
  • 独有的等同性判断方法:比如isEqualToString,传递给该方法的对象必须是NSString

NSObject协议中有两个用于判断等同性的关键方法:

-(BOOL)isEqual:(id)object;
//1. 首先判断两个指针是否相等
//2. 比较两个对象所属的类
//3. 检验每个属性是否相等
//4.实现hash方法
- (NSUInteger)hash;

-(BOOL)isEqual:(id)object
{
    //1. 首先判断两个指针是否相等
    //2. 比较两个对象所属的类
    //3. 检验每个属性是否相等
    //4.实现hash方法
    if (self == object) return YES;
    if ([self class] != [object class]) return NO;

    Person *otherPerson = (Person*)object;
    if (![_firstName isEqualToString:otherPerson.firstName]) {
        return NO;
    }
    if (![_lastName isEqualToString:otherPerson.lastName]) {
        return NO;
    }
    if (_age != otherPerson.age) {
        return NO;
    }
    return YES;
}

//这种做法能提高效率,又能使生成的哈希码至少位于一定范围之内
-(NSUInteger)hash
{
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    return firstNameHash^lastNameHash^ageHash;
}

如果“isEqual:”方法判定两个对象相等,那么其hash方法也必须返回同一个值。但是,如果两个对象的hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。

第九条:以“类组模式”隐藏实现细节

  • 类族模式:该模式可以灵活对应多个类,将他们的实现细节隐藏在抽象基类后面,以保持接口简洁,系统框架中经常用到类族
  • 创建类族
  • 经常要像类族中新增实体子类,从公共抽象类中继承子类时要当心。
    • 子类应该继承自类族中的抽象基类
    • 子类应该定义自己的数据存储方式
    • 子类应该覆写超类文档中指明需要覆写的方法

第十条:在既有类中使用关联对象存放自定义数据

#import "ViewController.h"
#import <objc/runtime.h>
static void *MyAlertViewKey = "MyAlertViewKey";
@interface ViewController ()<UIAlertViewDelegate>
@end
@implementation ViewController

//在创建警告框视图的时候直接把处理每个按钮的逻辑都写好,可以通过关联对象来做
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"信息" delegate:self cancelButtonTitle:@"NO" otherButtonTitles:@"YES", nil];
    void (^block)(NSInteger) = ^(NSInteger buttonIndex){
        if (buttonIndex == 0) {
            NSLog(@"NO");
        } else {
            NSLog(@"YES");
        }
    };
    objc_setAssociatedObject(alert, MyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    void(^block)(NSInteger) = objc_getAssociatedObject(alertView, MyAlertViewKey);
    block(buttonIndex);
}
  • 此方法根据给定的键从某对象中获取相应的关联对象值

    objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>);

  • 此方法以给定的键和策略为弄对象设置关联对象值

    objc_setAssociatedObject(<#id_Nonnull object#>, <#const void * _Nonnull key#>, <#id
    _Nullable value#>, <#objc_AssociationPolicy policy#>);

  • 此方法移除指定对象的全部关联对象

    objc_removeAssociatedObjects(<#id _Nonnull object#>)

    关联对象

    runtime - 关联对象_CatStarXcode的博客-CSDN博客

可以通过“关联对象”机制来把两个对象连起来;

定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系(保留)”与“非拥有关系(不保留)”

只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug;

第十一条:理解objc_msgSend的作用

在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要的方法

首先给对象发送信息,

void returnValue = [someObject messageName:parameter];

在本例中,someObject叫做“接受者”,messageName叫做“选择子”,选择子与参数合起来称为“消息”。编译器看到此消息后,将其转换为一条标准的C语言函数调用

所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend,原型如下:


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

//编译器会将刚刚那个例子中的消息转换为如下函数
id returnValue = objc_msgSend(SomeObject, @selector(messageName:),parameter);

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

所幸objc_msgSend会将匹配结果缓存在“快速映射表”(fast map)里面。每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了

消息由接收者,选择子及参数构成,给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method);然后通过“动态消息派发系统“即objc_msgSend函数,查出对应的方法,并执行代码

第十二条:理解消息转发机制

当我们在.h文件中写了一个方法,但是却没写实现,你会发现你可以调用这个方法,编译器在编译器不会报错,它相信在运行期可以找到方法实现,因为并没有,所以在运行的时候会崩。

当对象收到无法解读的消息后,将启动消息转发机制,程序员可经由此过程告诉对象应该如何处理未知消息。

首先将调用其所属类的一个方法,这个方法表示这个类能否新增一个实例方法用来处理这个选择子,在继续向下执行转发机制之前,本类有机会新增一个处理此选择子的方法。

+ (BooL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    Method method = class_getInstanceMethod([self class], @selector(rush));
    if ([selectorString isEqualToString:@"rushB"]) {
        class_addMethod(self, sel, method_getImplementation(class_getInstanceMethod([self class], @selector(rush))), method_getTypeEncoding(method));
        return YES;
    }
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

在这里插入图片描述

这个方法主要接受四个参数

  • Class cls 要添加方法的类
  • SEL name 被添加方法的名字
  • IMP imp 添加的方法的实现
  • const char *types 描述方法参数类型的字符数组。

动态方法解析

  • (BOOL)resolveInstanceMethod:(SEL)selector;

备援接收者

  • (id)forwordingTargetForSelector:(SEL)selector;

完整的消息转发

  • (void)forwordInvocation:(NSInvocation *)invocation;

若对象 无法响应某个选择子,则进入消息转发流程;

通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中

对象可以把其无法解读的某些选择子转交给其他对象来处理

经过上述两步之后,如果还是没有处理选择子,那就启动完整的消息转发机制;
在这里插入图片描述

第十三条:用“方法调配技术” 调试“黑盒方法”

  • 方法调配:类的方法列表会把选择子的名称映射到相关的方法实现上。这种方法均以函数指针的形式来表示,这种指针叫做IMP

    原型

    id (*IMP) (id, SEL,...)
    

    NSString类可以响应lowercaseString,uppercaseString,capitalizedString等选择子
    在这里插入图片描述

    可以新增选择子,可以交换选择子
    在这里插入图片描述

    可以用于调试,但很少有人在调试程序之外的场合用上述“方法调配”来永久改动某个类的功能。

  • 在 Objective-C 中,selector,Method 和 implementation(IMP) 都是 Runtime 的组成部分。在实际开发中它们常常是可以相互转换来处理消息的发送的。选择子代表方法在 Runtime 期间的标识符。为 SEL 类型,虽然 SEL 是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C 的 Runtime 运行系统。

选择子其实是方法的名称,不同类中方法名相同参数不同的俩个方法,他们的选择子是相同的。

// Method
struct objc_method {
    SEL method_name; 
    char *method_types;
    IMP method_imp;
};

方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。(即类型编码)
method_imp 指向方法的实现,本质上是一个函数的指针

第十四条:理解“类对象”的用意

  1. 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系
    在这里插入图片描述
typedef struct objc_object {
Class isa;
} id;

每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,通常称为*“isa”指针**。

  1. 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知
  • -(BOOL) isKindOfClass: class-object

判断是否是这个类或者这个类的子类的实例

  • -(BOOL) isMemberOfClass: class-object

判断是否是这个类的实例

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值