objc简介及其特性浅析

初识objc

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

HelloWorld

#import <Foundation/Foundation.h>
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 <Foundation/Foundation.h>
// 继承类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 <Foundation/Foundation.h>
@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关系图
    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. 如果还找不到就要开始进入动态方法解析了,后面会提到。


    方法解析

消息传递有缓存机制,且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;
  • 流程图
流程图
  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<ADelegate> delegate;
@end@Protocol ADelegate
- (void) g;
@end

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

/*** B.h ***/
@interface B : NSObject<ADelegate>
@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是单向无关系。

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

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,135评论 30 470
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,692评论 0 9
  • 1.OC里用到集合类是什么? 基本类型为:NSArray,NSSet以及NSDictionary 可变类型为:NS...
    轻皱眉头浅忧思阅读 1,368评论 0 3
  • 1.Difference between shallow copy and deep copy? 浅复制和深复制的...
    用心在飞阅读 990评论 0 9
  • 1.人活一张脸,如果一个人连脸都不要了,我们需要看看这个人有什么? 2.大道理无需多讲,只需要通过自己的身体力行去...
    湘邵铁炉阅读 486评论 7 8