@property in CategoryObjective-C特性:Runtime

原稿链接
转载注明出处

Objective-C是基于C语言加入了面向对象特性信息转发机制的动态语言,这意味它不但需要一个编译器,还待Runtime系统来动态创建类及对象,进行信息发送和转账。下面通过分析Apple开源的Runtime代码(我下的本子是objc4-646.tar)来深切了解Objective-C的Runtime机制。

前言

咱们是得以Category遭遇补充加属性的,就比如以下代码

/** FaiChouView.h */
#import <UIKit/UIKit.h>

@interface FaiChouView : UIView

@end

@interface FaiChouView (fcHeight)
@property CGFloat fcHeight;
@end

/** FaiChouView.m */
#import "FaiChouView.h"

@implementation FaiChouView
@end
@implementation FaiChouView (fcHeight)

- (CGFloat)fcHeight {
    return self.fcHeight;
}
- (void)setFcHeight:(CGFloat)fcHeight {
    self.fcHeight = fcHeight;
}
@end

/** main.m */
#import "FaiChouView.h"
int main(int argc, char * argv[]) {
    @autoreleasepool {
        FaiChouView *fcView = [[FaiChouView alloc] init];
        fcView.fcHeight = 20.; 
        NSLog(@"%f", fcView.fcHeight);
    }
}

早晚,它见面崩溃的。问题时有发生以哪?如何修改?一步一步来。

Runtime数据结构

在Objective-C中,使用[receiver message]语法并无见面立即行receiver对象的message道的代码,而是向receiver出殡一长长的message信息,这条信息可能由receiver来处理,也说不定是因为转发让其他对象来拍卖,也发出或假装没接收到马上条消息使无拍卖。其实[receiver message]于编译器转化为:

id objc_msgSend ( id self, SEL op, ... );

下从有限单数据结构idSEL来逐步分析及掌握Runtime有什么样主要的数据结构。

到底只是免得以加加属性到Category

俺们好在苹果官方文档遭逢找到以下说明:

Categories can be used to declare either instance methods or class
methods but are not usually suitable for declaring additional
properties. It’s valid syntax to include a property declaration in a
category interface, but it’s not possible to declare an additional
instance variable in a category. This means the compiler won’t
synthesize any instance variable, nor will it synthesize any property
accessor methods. You can write your own accessor methods in the
category implementation, but you won’t be able to keep track of a
value for that property unless it’s already stored by the original
class.

文档中明确指出It’s valid syntax to include a property declaration in a category interface,我们得在接口文件被扬言属性,但是编译器不会见自动合成实例变量(Ivars)和存取方法。
我们应有自己实现存取方法。

咱的代码到底错在啊地方?

won’t be able to keep track of a value for that property

咱们的代码return self.fcHeight;业已力不从心无天了。

SEL

SEL是函数objc_msgSend亚个参数的数据类型,表示方式选择器,按下路径打开objc.h文件

SEL Data Structure

查看到SEL数据结构如下:

typedef struct objc_selector *SEL;

骨子里它就是投到方式的C字符串,你可由此Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来博取一个SEL列的法子选择器。
要您掌握selector对应之办法名是什么,可以透过NSString* NSStringFromSelector(SEL aSelector)措施将SEL转化为字符串,再用NSLog打印。

怎么样改代码让该正常获取view的高度?

@implementation FaiChouView (fcHeight)

- (CGFloat)fcHeight {
    // return self.fcHeight;

    return self.frame.size.height;
}
- (void)setFcHeight:(CGFloat)fcHeight {

    // self.fcHeight = fcHeight;

    CGRect newframe = self.frame;
    newframe.size.height = fcHeight;
    self.frame = newframe;
}

@end

合法文档的求证,我们在Category遭之性质是免能够保留其值的,但是咱可以自定义存取方法,fcHeight归来的凡view自我的可观,
setter术为无以价值赋值给fcHeight,而间接的受view.frame,这样在main.m中的fcView.fcHeight = 20.;只是依靠了fcHeight的存取方法给view自家的赋值。

经以上,我们证实了那么句古话

不能在Category中添加实例变量

id

对接下去看objc_msgSend首先单参数的数据类型idid凡通用项目指针,能够代表其余对象。按下路径打开objc.h文件

id Data Structure.png

查看到id数据结构如下:

/// 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;

id其实就是一个对准objc_object结构体指针,它富含一个Class isa成员,根据isa指南针就可以追溯找到靶所属的切近

注意:根据Apple的法定文档Key-Value Observing Implementation
Details提及,key-value
observing是使用isa-swizzling的技艺实现的,isa指南针在运转时受修改,指向一个中等类设无是确实的好像。所以,你免应运用isa指南针来确定类的关联,而是以class道来确定实例对象的类似。

学而不思则尚未,思而不学则几乎

如若打到runtime,任何事物还是洞察的。
有些博客看了一样合看不掌握,那便多看几举,博客讲的不过是一个端,上面来许多丛知识点,这同样当映射出的保有点没必要全部左右,发现问题的重要性,带在题材考虑,总会套到无数文化的。

丢失说话来主义,多钻研来问题。 ———— 胡适

objc所有类和目标都是c结构体,category当然为同,下面是runtime中Category的结构:

struct _category_t {
    const char *name; // 类名
    struct _class_t *cls; //
    const struct _method_list_t *instance_methods; // 实例方法 -
    const struct _method_list_t *class_methods; // 类方法 +
    const struct _protocol_list_t *protocols; // 
    const struct _prop_list_t *properties; //
};

properties此category所有的property,这吗是category里面可以定义属性之案由,不过是property不见面合成实例变量和存取方法。

一个常见的属性(fcTestProperty),经过编译器编译过后,会添加以下:

  1. _ivar_list_t面临补充加了_fcTestProperty变量
  2. _method_list_t面临补充加了fcTestPropertysetFcTestProperty少数只方式
  3. _prop_list_t遭到补充加了fcTestProperty这个property

一个Category遭受之属性(fcTestProperty),经过编译器编译后,只见面于_prop_list_t中增加fcTestProperty夫特性。

Class

isa指南针的数据类型是ClassClass代表对象所属之近乎,按下路径打开objc.h文件

Class Data Structure

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

得查阅到Class实则就算是一个objc_class结构体指针,但此腔文件找不交它们的定义,需要在runtime.h才会找到objc_class结构体的概念。

仍下路径打开runtime.h文件

objc_class Data Structure

查看到objc_class结构体定义如下:

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;
/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE举凡一个Apple对Objc系统运作版本进行约束的宏定义,主要为配合非Objective-C
2.0之残存版本,但咱以能从中得到有发因此信息。

吃咱分析部分至关重要的积极分子变量表示什么意思和呼应使用什么数据结构。

  • isa代表一个Class对象的Class,也尽管是Meta
    Class
    。在面向对象设计着,一切都是对象,Class在筹划受到我为是一个靶。我们会于objc-runtime-new.h文件找到证据,发现objc_class起以下定义:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    ......
}

有鉴于此,结构体objc_class呢是继续objc_object,说明Class在筹划被本人吗是一个目标

其实Meta Class为是一个Class,那么它们为和任何Class一样发生好的isasuper_class指南针,关系如下:

Class isa and superclass relationship from Google

上图实线super_class指针,虚线isa指南针。有几乎独举足轻重点得解释以下:

  • Root class
    (class)其实就是是NSObjectNSObject举凡绝非超类的,所以Root class(class)的superclass指向nil。
  • 每个Class都来一个isa指南针指于唯一的Meta class
  • Root
    class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
  • 每个Meta class的isa指南针都对准Root class (meta)。

  • super_class代表实例对象对应的父类
  • name意味着类名
  • ivars意味着多个成员变量,它对objc_ivar_list结构体。在runtime.h好看来她的概念:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}        

objc_ivar_list实在就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的么成员变量信息。

  • methodLists意味着法列表,它对objc_method_list结构体的二级指针,可以动态修改*methodLists的值来上加成员方法,也是Category实现原理,同样为说明Category不克互补加实例变量的原因。在runtime.h好看来它的定义:

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}      

同理,objc_method_list也是一个链表,存储多只objc_method,而objc_method布局体存储类的某某方法的信息。

  • cache就此来缓存经常看的方,它对objc_cache结构体,后面会重要出口到。
  • protocols代表类以哪些协议

如何在Category中添加实例变量呢?

MJRefresh遭逢我们得找到以下代码(摘要):

/** UIScrollView+MJRefresh.h */

#import <UIKit/UIKit.h>
#import "MJRefreshConst.h"

@class MJRefreshHeader, MJRefreshFooter;

@interface UIScrollView (MJRefresh)
/** 下拉刷新控件 */
@property (strong, nonatomic) MJRefreshHeader *mj_header;

...

@end


/** UIScrollView+MJRefresh.m */

#pragma mark - header
static const char MJRefreshHeaderKey = '\0';
- (void)setMj_header:(MJRefreshHeader *)mj_header
{
    if (mj_header != self.mj_header) {
        // 删除旧的,添加新的
        [self.mj_header removeFromSuperview];
        [self insertSubview:mj_header atIndex:0];

        // 存储新的
        [self willChangeValueForKey:@"mj_header"]; // KVO
        objc_setAssociatedObject(self, &MJRefreshHeaderKey,
                                 mj_header, OBJC_ASSOCIATION_ASSIGN);
        [self didChangeValueForKey:@"mj_header"]; // KVO
    }
}

- (MJRefreshHeader *)mj_header
{
    return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}

...

他是由此runtime中之涉嫌对象实现的,关于Associated Objects可以学习NSHipster的魔鬼的交易这一篇。

末我们的代码调整呢:

/** FaiChouView.m */
#import "FaiChouView.h"
#import <objc/runtime.h>

@implementation FaiChouView
@end

@implementation FaiChouView (fcHeight)
@dynamic fcHeight;
- (CGFloat)fcHeight {
    // return self.fcHeight;

    // return self.frame.size.height;
    return [objc_getAssociatedObject(self, @selector(fcHeight)) floatValue];
}
- (void)setFcHeight:(CGFloat)fcHeightNew {

    // self.fcHeight = fcHeight;

//    CGRect newframe = self.frame;
//    newframe.size.height = fcHeight;
//    self.frame = newframe;
    NSNumber *fcHeightFloatNumber = @(fcHeightNew);
    objc_setAssociatedObject(self, @selector(fcHeight), fcHeightFloatNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

objc_setAssociatedObjectobjc_getAssociatedObject方法绑定的实例变量和一个便的实例变量完全是两回事。

Method

Method表示类吃之某方法,在runtime.h文件被找到它们的定义:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

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

其实Method即便是一个对准objc_method结构体指针,它存储了法名(method_name)、方法类型(method_types)和方实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

参考链接

  • Category中property的命运

  • objc
    category的秘密

  • Associated
    Objects

  • 刨根问底Objective-C Runtime(3)- 消息 和
    Category%5Bnil%5D-xiao-xi-he-category/)

  • iOS
    Category中上加属性和分子变量的区分

  • iOS使用Category添加@property变量

Ivar

Ivar表示类中之实例变量,在runtime.h文件被找到它们的概念:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} 

Ivar实质上就算是一个对objc_ivar结构体指针,它富含了变量称(ivar_name)、变量类型(ivar_type)等信息。

IMP

每当上面说Method时常就是说了,IMP精神上即是一个函数指针,指向方法的落实,在objc.h找到其的定义:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

当你向某对象发送一条信息,可以由是函数指针来指定方法的兑现,它说到底便见面实施那段代码,这样好纠缠开消息传递品要失去履行另外一个方法实现。

Cache

顾名思义,Cache重点用来缓存,那其缓存什么啊?我们先以runtime.h文件看看它的定义:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache实则就是一个囤积Method的链表,主要是为着优化道调用的性能。当对象receiver调用方法message不时,首先根据目标receiverisa指南针查找到其对应的类,然后以接近的methodLists丁寻找方法,如果没找到,就应用super_class指南针到父类中之methodLists找,一旦找到就调用方法。如果无找到,有或消息转发,也恐怕忽略它。但诸如此类找方法效率太没有,因为屡屡一个近似约就生20%的艺术经常吃调用,占总调用次数的80%。所以下Cache来缓存经常调用的方式,当调用方法时,优先在Cache查找,如果无找到,再届methodLists查找。

信发送

前面从objc_msgSend作为入口,逐步深入分析Runtime的数据结构,了解每个数据结构的企图与她中涉及后,我们正式转入信息发送是主题。

objc_msgSend函数

以前方都提过,当有对象下语法[receiver message]来调用某个方法时,其实[receiver message]深受编译器转化为:

id objc_msgSend ( id self, SEL op, ... );

如今被我们看一下objc_msgSend她现实是怎么发送信息:

  1. 第一根据receiver对象的isa指南针获取其对应之class
  2. 优先在classcache查找message道,如果搜索不顶,再届methodLists查找
  3. 苟无于class找到,再到super_class查找
  4. 倘找到message其一措施,就推行其实现之IMP

Objc Message.gif

self与super

为让大家更好地了解selfsuper,借用sunnyxx博客的ios程序员6级考试一如既往道题目:下面的代码分别出口什么?

@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

self意味着手上之近乎的对象,而super凡是一个编译器标示符,和self本着同一个消息接受者。在本例中,无论是[self
class]还是[super
class],接受消息者都是Son对象,但superself差的凡,self调用class方法时,是以子类Son中觅方法,而super调用class方法时,是于父类Father中查找方法。

当调用[self class]主意时,会转化为objc_msgSend函数,这个函数定义如下:

id objc_msgSend(id self, SEL op, ...)

这儿会自此时此刻Son类的法列表中寻觅,如果没,就到Father类查找,还是没,最后当NSObject类查找到。我们得以于NSObject.mm文本中见到- (Class)class的实现:

- (Class)class {
    return object_getClass(self);
}

所以NSLog(@"%@", NSStringFromClass([self class]));会输出Son

当调用[super
class]方式时,会转化为objc_msgSendSuper,这个函数定义如下:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

objc_msgSendSuper函数第一单参数super的数据类型是一个对准objc_super的结构体,从message.h文件被翻其的概念:

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

结构体包含两单成员,第一只是receiver,表示有类的实例。第二独凡是super_class表示目前好像的父类。

这会儿首先会见组织出objc_super结构体,这个结构体第一独成员是self,第二独分子是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father好像查找class方法,查找无至,最后当NSObject查及。此时,内部以objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self
class]调用相同,所以结果或者Son

躲藏参数self和_cmd

[receiver message]调用方法时,系统会当运作时偷地动态传入两个暗藏参数self_cmd,之所以称她也躲参数,是为当源代码中并未声明与概念这有限个参数。至于对self的叙说,上面已说明好了解了,下面我们要讲解_cmd

_cmd意味着手上调用方法,其实它就是是一个办法选择器SEL。一般用于判断方法名或以Associated
Objects中绝无仅有标识键名,后面在Associated Objects会称到。

办法分析及信转发

[receiver message]调用方法时,如果当message方法在receiver对象的接近继承体系中没有找到办法,那怎么处置?一般情形下,程序于运作时虽会Crash掉,抛出*
unrecognized selector sent to
好像这样的十分信息。但于废除来老之前,还有三坏机会*本以下依次为你拯救程序。

  1. Method Resolution
  2. Fast Forwarding
  3. Normal Forwarding

Message Forward from Google

Method Resolution

率先Objective-C在运作时调用+ resolveInstanceMethod:+ resolveClassMethod:方,让您上加计的落实。如果你加加方并赶回YES,那系统于运行时便会见重复启航同潮信息发送的进程。

推选一个简单例子,定义一个好像Message,它要定义一个主意sendMessage,下面就它的宏图和贯彻:

@interface Message : NSObject

- (void)sendMessage:(NSString *)word;

@end

@implementation Message

- (void)sendMessage:(NSString *)word
{
    NSLog(@"normal way : send message = %@", word);
}

@end

若果自己于viewDidLoad方中创造Message对象并调用sendMessage方法:

- (void)viewDidLoad {
    [super viewDidLoad];

    Message *message = [Message new];
    [message sendMessage:@"Sam Lau"];
}

控制台会打印以下信息:

normal way : send message = Sam Lau

可如今本人以原本sendMessage道实现叫注释掉,覆盖resolveInstanceMethod方法:

#pragma mark - Method Resolution

/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(sendMessage:)) {
        class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
            NSLog(@"method resolution way : send message = %@", word);
        }), "v@*");
    }

    return YES;
}

控制台就见面打印以下信息:

 method resolution way : send message = Sam Lau

留神到面代码来如此一个字符串"v@*,它表示方法的参数与归值,详情请参见Type
Encodings

如果resolveInstanceMethod办法返回NO,运行时就跨反到下一样步:信转发(Message
Forwarding)

Fast Forwarding

倘目标对象实现- forwardingTargetForSelector:道,系统便会见以运作时调用这个法,只要这个措施返回的非是nilself,也会另行开消息发送的过程,把立即消息转发让其它对象来处理。否则,就会延续Normal
Fowarding

延续上面Message接近的事例,将sendMessageresolveInstanceMethod方法注释掉,然后上加forwardingTargetForSelector法的兑现:

#pragma mark - Fast Forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sendMessage:)) {
        return [MessageForwarding new];
    }

    return nil;
}

这尚缺少一个转发信息的切近MessageForwarding,这个看似的设计和落实如下:

@interface MessageForwarding : NSObject

- (void)sendMessage:(NSString *)word;

@end

@implementation MessageForwarding

- (void)sendMessage:(NSString *)word
{
    NSLog(@"fast forwarding way : send message = %@", word);
}

@end

这儿,控制台会打印以下信息:

fast forwarding way : send message = Sam Lau

此处叫Fast,是为及时无异于步不见面创造NSInvocation对象,但Normal
Forwarding会创建它,所以相对于复快点。

Normal Forwarding

假设无动用Fast Forwarding来消息转发,最后只有以Normal
Forwarding来进展信息转发。它首先调用methodSignatureForSelector:计来抱函数的参数与归值,如果回到吗nil,程序会Crash掉,并抛出unrecognized
selector sent to
instance
异常信息。如果回到一个函数签名,系统就会见创一个NSInvocation靶并调用-forwardInvocation:方法。

延续前面的事例,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelectorforwardInvocation方的兑现:

#pragma mark - Normal Forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];

    if (!methodSignature) {
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }

    return methodSignature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    MessageForwarding *messageForwarding = [MessageForwarding new];

    if ([messageForwarding respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:messageForwarding];
    }
}

至于此事例的言传身教代码请到github下载。

其三种植方式的挑

Runtime提供三栽办法来拿本来的方法实现代替掉,那该如何选择她也?

  • Method Resolution:出于Method
    Resolution不能够像消息转发那样可以交到其他对象来处理,所以特适用于在本来的切近中代替掉。
  • Fast
    Forwarding:
    她好用消息处理转发让其余对象,使用范围更宽泛,不只是制止本的靶子。
  • Normal Forwarding:其跟Fast
    Forwarding一样可以消息转发,但它们亦可由此NSInvocation对象获得再次多消息发送的信息,例如:target、selector、arguments和返回值等消息。

Associated Objects

Categories can be used to declare either instance methods or class
methods but are not usually suitable for declaring additional
properties. It’s valid syntax to include a property declaration in a
category interface, but it’s not possible to declare an additional
instance variable in a category. This means the compiler won’t
synthesize any instance variable, nor will it synthesize any property
accessor methods. You can write your own accessor methods in the
category implementation, but you won’t be able to keep track of a
value for that property unless it’s already stored by the original
class. (Programming with
Objective-C)

当思使Category对已是的类似进行扩张时,一般只能添加实例方法还是类似措施,而未称添加额外之性。虽然可当Category头文件被声明property属性,但在促成公文中编译器是力不从心synthesize任何实例变量和性能访问方法。这时要从定义属性访问方法而动Associated
Objects来深受就是的类Category添加自定义之性质。Associated
Objects提供三单API来为目标丰富、获取与去关联值:

  • void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
  • id objc_getAssociatedObject (id object, const void *key )
  • void objc_removeAssociatedObjects (id object )

其中objc_AssociationPolicy大凡个枚举类型,它可以指定Objc内存管理的援计数机制。

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. */
};

脚来个有关NSObject+AssociatedObject
Category添加属性associatedObject的示范代码:

NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)

@property (strong, nonatomic) id associatedObject;

@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject
{
    return objc_getAssociatedObject(self, _cmd);
}

@end

Associated
Objects的key要求是唯一并且是常量,而SEL凡满足这要求的,所以地方的利用隐藏参数_cmd作为key。

Method Swizzling

Method
Swizzling
就是当运行时拿一个艺术的落实代替吗其它一个方式的实现。如果能够采取好之技能,可以写有简洁、有效还维护性更好的代码。可以参照两首关于Method
Swizzling技巧的章:

  • nshipster Method
    Swizzling
  • Method Swizzling 和 AOP
    实践

Aspect-Oriented Programming(AOP)

看似记录日志、身份验证、缓存等工作特别琐碎,与工作逻辑无关,很多地方都发生,又不行麻烦抽象出一个模块,这种程序设计问题,业界为它们从了一个名字给横向关注点(Cross-cutting
concern)
,AOP企图就是是分开横向关注点(Cross-cutting
concern)
来增进模块复用性,它好于既有的代码添加一些外加的表现(记录日志、身份验证、缓存)而任由需修改代码。

危险性

Method
Swizzling
不畏比如相同将瑞士小刀,如果运用合适,它会立竿见影地缓解问题。但使用不当,将牵动许多烦劳。在stackoverflow上有人曾经提出如此一个题目:What
are the Dangers of Method Swizzling in Objective
C?,它的危险性重点体现以下几独点:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method’s arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

总结

尽管如此于平常项目不是常常用到Objective-C的Runtime特性,但当您读书有iOS开源项目时,你就见面发觉多辰光都见面为此到。所以深入理解Objective-C的Runtime数据结构、消息转发机制促进你再易于地读书和读书开源项目。

壮大阅读

玉令天下博客的Objective-C
Runtime
顾鹏博客的Objective-C
Runtime
Associated
Objects
Method
Swizzling
Method Swizzling 和 AOP
实践
Objective-C Runtime Reference
What are the Dangers of Method Swizzling in Objective
C?
ios程序员6级考试(答案和说)
Objective
C类方法load和initialize的区别

发表评论

电子邮件地址不会被公开。 必填项已用*标注