iOS笔记篇-熟悉OC<二>

前言

OC是面向对象的变成语言,“对象”就是“基本构造单元”。而对象之间传递数据并执行任务的过程,就是“消息传递”。对于OC这种消息结构的语言理解“消息传递”的理解,非常有必要。
当应用程序运行起来,围棋提供相关支持的代码叫做"Object-c运行期环境"(Objective-C runtime), 它提供了一些是得对象之间能够传递信息的重要函数,并且包含创建类实例所用的全部逻辑。理解运行期环境中各个部分协同工作的原理,你的开发水平将会进一步的提升。

第一、关于属性(property)

1.property、 synthesize、 dynamic

参考一下框图已经非常详细了。


Property

2.在对象内部使用访问实例变量

笔者建议在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候采用属性来做。

直接访问实际变量优点
1.由于不经过OC的“方法派发”步骤,所以直接访问实例变量的速度比较快。
不会调用“设置方法”,绕过了相关属性的“内存管理语义”,比如:ARC下直接访问一个声明为copy的属性,那么并不会copy该属性,只会保留新值,释放旧的值。
不会触发“KVO(key-Value Observing, KVO)”通知。这样做是否会产生问题还需要取决于具体的对象的行为

通过属性来分文有助排查错误,因为可以给“获取方法”和“设置方法”中新增断点,监控调用者和调用时机。

注意点
1.在初始化方法中应该如何设置属性值,因为子类可能会覆写该方法。为了防止初始化父类用set方法调用到不确定子类的set的方法,所以在初始化方法和delloc方法中,总是通过实例变量来读写数据
2.当存在着懒加载的情况,那么一定需要使用属性的方法来读取数据。

第二、关于“对象等同性”

== 比较出来的是指针的比较,学习C语言指针的话,对此将非常容易理解。

NSString *fir = @"number:123";
NSString *sec = @"number:123";
BOOL equalA = (fir == sec);                   // NO
BOOL equalB = [fir isEqual:sec];            // YES
BOOL equalC = [fir isEqualToString:sec];    //YES

从以上的例子可以看出,==是比较指针,isEqualToString是NSString提供自己独特的等同性的判断方法。并且调用该方法比isEqual方法快。后者还需奥执行额外的步骤。

1.关于isEqual方法

如果isEqual的方法判定两个对象相等,那么其hash方法也必须返回同一个值,如果两个对象的hash方法返回同一个值,但是"isEqual:"方法未必会认为二者相等。
如果经常需要判断等同性,那么可以自己创建等同性的方法,提高性能。正如 isEqualToString一样
对于NSArray对象中,如果对应位置上的对象均相等,那么这两个数组就等同,称之为“深度等同性判定”。当然比如从数据库中创建出来的对象代用Primary Key那么我们只需要比较readonly的Primagry Key是否相等来判定等同,不需要检测所有的字段。所以只有类的编写者才可以确定两个对象实例在何种情况下可以被判定为相等。

2.工厂模式和Class Cluster

类族模式可以把所有实现的细节隐藏到一套简单的公共接口的后面。
系统框架中经常使用类族
从类族的公共抽象基类中继承子类是要当心。

3.技巧篇

在既有类中使用关联对象存放自定义数据,如下代码不过需要注意该部分被包含在头文件 中

import <objc/runtime.h>

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

@implementation Person (Property)
//使用的是不透明的指针,一般选用静态全局变量
static NSString const *kcountryKey = @"PersonCountryKey";
static NSString const *kdetailAddressKey = @"PersonDetailAddressKey";

@dynamic country, detailAddress;

-(NSString *) country{
    return objc_getAssociatedObject(self, &kcountryKey);
}

-(NSString *)detailAddress{
    return objc_getAssociatedObject(self, &kdetailAddressKey);
}

-(void) setCountry:(NSString *)country{
    objc_setAssociatedObject(self, &kcountryKey, country, OBJC_ASSOCIATION_COPY);
}

-(void) setDetailAddress:(NSString *)detailAddress{
    objc_setAssociatedObject(self, &detailAddress, detailAddress, OBJC_ASSOCIATION_COPY);
}

@end

@interface Person (Property)
@property (nonatomic, readwrite, copy) NSString* country;
@property (nonatomic, readwrite, copy) NSString* detailAddress;
@end

如何应用,如下应用,拓展了person的属性,并且能很好的处理了多个AlertView的判断问题。但是采用该方案也需要注意:block可能要捕获(capture)某些常量,这样也许会造成"保留环"。笔者建议创建一个UIAlertView的子类,把块作为子类的属性。这种做法要比关联好。

#import "ViewController.h"
#import "Person+Property.h"
#import <objc/runtime.h>

typedef void (^AlertBlock)(NSInteger buttonIndex);
@interface ViewController ()<UIAlertViewDelegate>

@end

@implementation ViewController
static NSString* const kAlertViewKey = @"MyAlertViewKey";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person* xiaoMing = [[Person alloc] initWithFirstName:@"Ren" lastName:@"XiaoMing"];
    NSString* fullName = [NSString stringWithFormat:@"你输入的名字是:%@", xiaoMing.fullName];
    NSLog(@"%@", fullName);
    
    UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"请确认" message:fullName delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    [alertView show];
    AlertBlock nameBlock = ^(NSInteger buttonIndex){
        NSLog(@"姓名确认中你选择了%ld", buttonIndex);
    };
    objc_setAssociatedObject(alertView, &kAlertViewKey, nameBlock, OBJC_ASSOCIATION_COPY);
    
    xiaoMing.country = @"China";
    xiaoMing.detailAddress = @"Shen Zhen";
    NSString* address = [NSString stringWithFormat:@"小明居住的地址是:%@ %@", xiaoMing.country, xiaoMing.detailAddress];
    NSLog(@"%@", address);
    
    UIAlertView* addressAlertView = [[UIAlertView alloc] initWithTitle:@"请确认" message:address delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    [addressAlertView show];
    AlertBlock addressBlock = ^(NSInteger buttonIndex){
        NSLog(@"地址确认中你选择了%ld", buttonIndex);
    };
    objc_setAssociatedObject(addressAlertView, &kAlertViewKey, addressBlock, OBJC_ASSOCIATION_COPY);
    
}

#pragma mark - UIAlertViewDelegate
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    AlertBlock tempblock = objc_getAssociatedObject(alertView, &kAlertViewKey);
    tempblock(buttonIndex);
}

第三、关于objc_msgSend

1. 概述

  • 用OC的话来说,这玩样叫做"传递消息(pass a message)"。消息有"名称(name)"或者"selector",可以接受的参数,而且可能还有返回值。
  • C语言是“静态绑定”,也就是说,在编译期就能决定运行时所调用的函数。如果不考虑"内联"的情况,那么编译期在编译代码的时候就知道了程序中存在的函数,并且将其硬编址,这个进行过单片机开发和仿真过的,对此将深有体会,调用的函数最终将映射到某个硬件地址。
  • OC语言使用"动态绑定",所需要调用的函数知道运行期才能确定。
  • 对于OC语言而言,如果想某个对象传递消息,那就会使用动态绑定的机制来决定需要调用的方法。在底层,所有的方法都是普通的C语言函数,然而对象接收到消息之后,究竟该调用哪个方法则完全取决于运行期,甚至可以在程序运行时改变,这些特性使得OC成为一门真正的动态语言。

id returnValue = [someObject messageName:parameter];

  • 其中someObject就是消息的接受者receiver, messageName叫做selector,而参数parameter+selector合起来就称为"消息"
  • 将其转化成一条标准的C语言函数,原型如下:
    void objc_msgSend(id self, SEL cmd, ...)
OC中消息传递图.png

简单点归纳的来说,消息由接收者、selector以及参数构成的。给某个对象“发送消息”也就是相当于在该对象上“调用方法”。
发送给对象的消息都要经过"动态消息派发系统",来处理,该系统会查出对应的方法,并执行代码。

2. 消息转发的过程

1.首先通过该方法来询问是否能处理该消息
<PS:如果想通过运行期动态插入方法,那么就需要考虑在这里插入该方法了CALayer里面就应用类似的方法实现。于是开发者能够向其中新增加自定义的属性,而这些属性的存储工作由基类来完成>

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

2.如果不能处理,接收还有第二次机会能处理未知的子,运行系统会问它,能不能把这条消息转发给其他接收者。

-(id) forwardingTargetForSelector:(SEL) selector;

如果当前接收者找到了可以处理该部分的对象的则将它返回(备援接收者), 否者返回nil.

第四、OC业界的黑魔法

1.原理概述

OC的黑魔法通常是用来调试“黑盒方法”。
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态分配系统”能够据此找应该调用的方法。这些方法均以函数指针的形式表示,这种指针叫做:IMP,原型如下:

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

比如以NSString的方法为例,如下图


NSString的映射表 .png

2. 如何实现

下面给了一个代码实现的例子

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* apple = @"apple";
    NSLog(@"执行前===================================");
    NSLog(@"Uppercase = %@", [apple uppercaseString]);
    NSLog(@"LowerCase = %@", [apple lowercaseString]);
    Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);
    NSLog(@"执行后===================================");
    NSLog(@"Uppercase = %@", [apple uppercaseString]);
    NSLog(@"LowerCase = %@", [apple lowercaseString]);
}

程序运行的输出结果:

2016-12-29 14:25:00.535 OC黑魔法[14618:194380] 执行前===================================
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] Uppercase = APPLE
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] LowerCase = apple
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] 执行后===================================
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] Uppercase = apple
2016-12-29 14:25:00.537 OC黑魔法[14618:194380] LowerCase = APPLE

总之通过该方案,开发者可以为那些“完全不知道其实现的”黑盒方法增加日记功能,这非常有助于调试。但是笔者认为应该尽量少用,要不然代码将非常不好维护,不容易懂。

第四、OC中类的本质

1.OC中类的本质

  • 从下图中可以才看出,每个对象的结构体的收个成员都是Class类的变量,该变量定义了对象所属的类,通常称之为"is a"指针。比如:NSString的isa就是指向NSString。
  • 类的结构体存放类的“元数据(metadata)”,例如类的实现实现了几个方法,具备多少个变量等信息。此结构体的首个变量也是isa指针,这个说明Class本身也是OC对象。还有一个super_class,而且二者的类型也是Class。类对象所属的类型又是另外一个类,叫做“元类(metaclass)”,用来表述类对象所具备的元数据。可以理解为,每个类仅有一个类对象,而每个类对象仅有一个之相关的元类。
//参见:objc.h
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

//参见Objc runtime.h中
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
//#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
//#endif
} OBJC2_UNAVAILABLE;

OC中的对象.png

2.判断类继承体系中查询类型信息

    NSMutableDictionary* dict = [NSMutableDictionary new];
    [dict isMemberOfClass:[NSDictionary class]];            //NO
    [dict isMemberOfClass:[NSMutableDictionary class]];     //YES
    [dict isKindOfClass:[NSDictionary class]];              //YES
    [dict isKindOfClass:[NSArray class]];                   //NO

以上这样类型查询方法是使用isa指针来获取对象所属的类,然后通过super_class在继承体系下游走。
比较两个类对象是否等同的也可以采用==操作符,而不要使用Objective-C对象常用的"isEqual:"方法来做。原因在于,类对象是“单例”,再应用程序范围内,每个类仅仅有一个实例。

总之,尽量要使用类型查询方法来确定对象类型,而不要直接比较类对象,因为某些对象实现可能实现了消息转发功能。

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

推荐阅读更多精彩内容