objc简介及其特性浅析

初识objc

全称Objective-C,OSX、IOS开发语言

HelloWorld

#import 
int main(int argc, const char * argv[]) 
{
    NSLog(@"Hello, World!"); 
    return 0;
}
  • 兼容C
  • Foundation基础类库 :类似java的java.lang,包括NSObject(根类),数据类型(NSNumber、NSDate),字符串NSString、数据结构(NSArray、NSDictionary、NSSet)
  • 常见类前缀 :NS(NextStep)、CF(Core Foundation)、CA(Core Animation)、CG(Core Graphics)、UI(IOS UIKit)

#import :#import会自动判断是否已经被导入过,""、<>语义与c一致

定义一个类

AClass.h

#import 
// 继承类interface AClass : NSObject
@property (assign, readonly, nonatomic) int type; // public只读属性
@property (strong, nonatomic) NSString* name; // public属性
- (instancetype) initWithType:(int)aType andName:(NSString*) aName; //非默认构造函数
- (void) f; // public方法
+ (BOOL) isA:(int)a greaterThanB:(int)b; // 类方法
@end

AClass.m

#import "AClass.h"
// 扩展,定义private成员
@interface AClass ()
@property (assign, nonatomic) int type; // 自己可读写@property NSString* foo;
@end

@implementation AClass
//方法分类
#pragma mark - init and property 
- (instancetype)initWithType:(int)aType andName:(NSString *)aName 
{ 
    if (self = [super init]) { // []消息传递(方法调用)
       _type = aType; // _直接赋值
       self.name = aName; // self.调用setter方法,执行特殊赋值逻辑和触发KOV
       _foo = @"foo";
    }
    return self;
}
- (void)setName:(NSString *)name { 
    NSLog(@"%@", name); //特殊逻辑 
    _name = name;
}
#pragma mark - public method
- (void)f { 
    NSLog(@"in function f");
}
#pragma mark - class method
+ (BOOL)isA:(int)a greaterThanB:(int)b { 
   return a > b;
}
@end

调用

AClass *object1 = [[AClass alloc] init];
object1.name = @"object1";
AClass *object2 = [AClass new];
object2.name = @"object2";
AClass *object3 = [[AClass alloc] initWithType:1 andName:@"bar"];[object3 f];
[AClass isA:1 greaterThanB:2];
  • 约定init开头的函数为构造函数
  • property自动增加getter和setter方法
  • property的属性:setter语义(assign,copy,retain),读写属性(readwrite,readonly)原子性(atomic,nonatomic)
  • 消息传递写法:[receiver selector]

NSObject

  • NSObject Protocol 类似于java.lang.Object,方法有
    1. 类对象:Class,SuperClass
    2. 比较与hash(用于排序与数据结构[stl]):isEqual、hash
    3. 描述:description,类似于java toString
    4. 消息传递:performSelector
    5. 内存管理:retain、release、autorelease(非ARC,自带引用计数)
  • NSObject Class
    1. 创建、拷贝、析构:alloc、init、new、copy、dealloc
    2. Runtime(方法解析、重定向、转发):resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation
    3. [反]序列化> NSObject Class继承NSObject Protocol

协议(Protocol)

相当于c++的抽象class,java的接口interface,只定义方法

类别(Category)

允许我们通过给一个类添加方法来扩充它,但不能添加新的实例变量

/*** ClassName+CategoryName.h ***/
#import 
@interface NSString (CamelCase)
-(NSString*) camelCaseString;
@end

/*** ClassName+CategoryName.m ***/
@implementation NSString (CamelCase)
-(NSString*) camelCaseString{ 
    //调用NSString的内部方法获取驼峰字符串。 
    NSString *castr = [self capitalizedString];  
    //创建数组来过滤掉空格, 通过分隔符对字符进行组合。 
    NSArray *array = [castr componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];  
    //把数组的字符输出 
    NSString *output = @""; 
    for(NSString *word in array) { 
        output = [output stringByAppendingString:word]; 
    }  
return output; 
}
@end

/*** main.m ***/
int main (int argc, const char * argv[]) {
    NSString *str = @"My name is bill."; 
    NSLog(@"%@", str); 
    str = [str camelCaseString]; 
    NSLog(@"%@", str); return 0;
}
  • 文件命名格式:ClassName+CategoryName.[h|m]
  • 声明语法:@interface ClassName (CategoryName)
  • 不改动原始类且不需要继承,就能用原始类的对象直接调用扩展方法
  • 应用场景:在没有源码(系统、第三方库)情况下扩展类方法
  • 实现原理:编译时技术,将扩展方法扩充到类对象的方法列表中。

扩展(Extension)

对有源码的类扩展,能添加成员变量和方法> 应用场景:隐藏类的私有成员变量和方法,C++要做隐藏比较复杂(增加一个私有类)

Runtime

代码已开源

Object、Class、Method

  • 结构体定义
typedef struct objc_class *Class;
typedef struct objc_object { 
    // 实例内存模型:成员变量的值 
    Class isa; //每个实例内存模型中的第一个元素都是类对象指针,is a kind of缩写
} *id; //id相当于void*

struct objc_class { 
    //类对象类,内存模型:自己的成员变量的值和类的元信息(方法列表,成员变量的名称)
    Class isa; 
    Class super_class; 
    const char *name; 
    long version; 
    long info; 
    long instance_size; 
    struct objc_ivar_list *ivars; 
    struct objc_method_list **methodLists; 
    struct objc_cache *cache; 
    struct objc_protocol_list *protocols;
};

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

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

typedef struct objc_method *Method;
struct objc_method { 
    SEL method_name; 
    char *method_types; //编译器识别的方法类型,如@v:@ 
    IMP method_imp; //方法地址
}

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

typedef id (*IMP)(id, SEL, ...);
  • object-class关系图
    objc简介及其特性浅析_第1张图片
    object-class关系图
    >
    1. meta类是类对象的isa,包含类方法>
    2. class、method等元信息的结构体是动态性的基础

C++方法绑定

C++的方法绑定是静态的(编译时)

  • 对象的第一个元素是虚表地址- 虚表最后一个元素是0,其他元素是方法地址
  • 编译时就把覆盖的虚函数地址进行了解析

Objc方法绑定方法

调用会被编译成调用objc_msgsend方法,执行步骤如下:

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
  4. 如果 cache 找不到就找一下方法分发表。
  5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
  6. 如果还找不到就要开始进入动态方法解析了,后面会提到。


    objc简介及其特性浅析_第2张图片
    方法解析

消息传递有缓存机制,且apple对缓存做过优化,所以对性能的影响很小

消息转发分为三大阶段

  1. 先征询消息接收者所属的类,看其是否能动态添加方法,以处理当前这个无法响应的 selector,这叫做动态方法解析(dynamic method resolution)
  2. 看看有没有其他对象(备援接收者,replacement receiver)能处理此消息。如果有,运行期系统会把消息转发给那个对象,转发过程结束;如果没有,则启动完整的消息转发机制。
  3. 完整的消息转发机制。运行期系统会把与消息有关的全部细节都封装到 NSInvocation 对象中,再给接收者最后一次机会,令其设法解决当前还未处理的消息。
  • 动态方法解析
/** * 如果尚未实现的方法是实例方法,则调用此函数 * * 
    @param selector 未处理的方法 * * 
    @return 返回布尔值,表示是否能新增实例方法用以处理selector 
*/
+ (BOOL)resolveInstanceMethod:(SEL)selector;

/** * 如果尚未实现的方法是类方法,则调用此函数 * * 
    @param selector 未处理的方法 * * 
    @return 返回布尔值,表示是否能新增类方法用以处理selector 
*/
+ (BOOL)resolveClassMethod:(SEL)selector;
  • 重定向
    将消息的receiver替换成其他对象
/** * 此方法询问是否能将消息转给其他接收者来处理 * * 
    @param aSelector 未处理的方法 * * 
    @return 如果当前接收者能找到备援对象,就将其返回;否则返回nil; 
*/
- (id)forwardingTargetForSelector:(SEL)aSelector;
  • 转发
/** * 消息派发系统通过此方法,将消息派发给目标对象 * * 
    @param anInvocation 之前创建的NSInvocation实例对象,用于装载有关消息的所有内容 
*/
 - (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 流程图
objc简介及其特性浅析_第3张图片
流程图
  1. 重定向和转发可以认为是多继承,从替代对象继承了一个方法>
  1. 方法调用通过增加objc_msgsend这一层的处理来实现动态

Method Swizzling

通过修改类对象的方法列表来实现:增加(class_addMethod)、替换(method_setImplementation)、交换(method_exchangeImplementations)方法,设置方法的实现(method_setImplementation)

  • 使用场景:
    1. 系统库函数的某个版本有bug,后面的某个版本修复了这个bug,但是app为了兼容低版本有bug的系统,可以替换掉库函数里有bug的实现
    2. 类似脚本语言的逻辑云端下发热替换国内的大众点评 iOS 客户端。该客户端使用了他们自己开发的基于 Wax 修改而来的 WaxPatch,WaxPatch 可以实现通过服务器更新来动态修改客户端的逻辑。而 WaxPatch 主要是修改了 wax 中的 wax_instance.m 文件,在其中加入了 class_replaceMethod 来替换原始实现,从而实现修改客户端的原有行为

其他

KVC

除了一般的赋值和取值的方法,我们还可以用Key-Value-Coding(KVC)键值编码来访问你要存取的类的属性。

[receiver setValue:object forKey:@"keypath"]; // 动态设置变量值
[receiver valueForKeyPath:@"keypath"]; // 动态获取变量值

简化代码的编写,利用了Class中的objc_ivar_list。

  • 举个例子:
@interface People: NSObject  
@property (nonatomic, strong) NSString *name; 
@property (nonatomic, strong) NSNumber *age;  
@end 

- (id)tableView:(NSTableView *)tableview  objectValueForTableColumn:(id)column row:(NSInteger)row {   
    People *people = [peoleArray objectAtIndex:row];  
    if ([[column identifier] isEqualToString:@"name"]) {  
        return [people name];  
    }  
    if ([[column identifier] isEqualToString:@"age"]) {  
        return [people age];  
    } 
    // And so on. 
}
// kvc写法
People *people = [peopleArray objectAtIndex:row]; 
return [people valueForKey:[column identifier]]; 

KVO及其相关

KVO

用于监听property的变化observer

@implementation B
- (instanceType) init { 
    [self addObserver:self.AObject forKeyPath:@"AObjectPropertyName" options:0 context:nil]; //增加监听
}
// 回调
- (void)observeValueForKeyPath:(NSString *)keyPath  ofObject:(id)object  change:(NSDictionary *)change  context:(void *)context { }
@end
  • 使用场景:model的变化实时通知view
  • 原理:当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法,isa swizzling替换了类对象的isa

delegate

/*** A.h ***/
@interface A : NSObject
@property (weak,nonatomic) id delegate;
@end@Protocol ADelegate
- (void) g;
@end

/*** A.m ***/
@implementation A
- (void) f { [self.delegate g];}

/*** B.h ***/
@interface B : NSObject
@end

/*** B.m ***/
@implementation B
- (instanceType) init { self.aObject.delegate = self;}
- (void) g { // ...}
@end

应用场景:View(A)中事件触发后交给Controller(B)处理

notification

@implementation A
- (void)notify { 
    // 发送通知 
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MyNotification" object:self];}
@end

@implementation B
// 通知回调
- (void)handleNotification:(NSNotification*)note { 
    NSLog(@"Got notified: %@", note);
}
@end

A *objectA = [[A alloc] init];
B *objectB = [[B alloc] init];
// 注册观察者
[[NSNotificationCenter defaultCenter] addObserver:objectB selector:@selector(handleNotification:) name:@"MyNotification" object:nil];
// 创建通知
[object notify];

应用场景:系统事件(键盘、电源)通知到各app

delegate、notification、KVO

对比假如A和B需要通信,B需要获取A的消息首先是delegate和notification这两个,A和B之间有相互的关联用delegate,若A和B毫无联系就该用notification。然后是KVO,delegate和notification是A和B双方合作的事情,而KVO是B单方面的事情。A有消息了,A通知B,这是delegate;A有消息了,A通知notificationCenter,notificationCenter广播给B,这是notification;A不漂亮,B无感,B偷窥A,A变漂亮了,B心动了,这是KVO。delegate是一对一强关系,notification是一对多的弱关系,KVO是单向无关系。

你可能感兴趣的:(objc简介及其特性浅析)