初识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,方法有
- 类对象:Class,SuperClass
- 比较与hash(用于排序与数据结构[stl]):isEqual、hash
- 描述:description,类似于java toString
- 消息传递:performSelector
- 内存管理:retain、release、autorelease(非ARC,自带引用计数)
- NSObject Class
- 创建、拷贝、析构:alloc、init、new、copy、dealloc
- Runtime(方法解析、重定向、转发):resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation
- [反]序列化> 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关系图
>
- meta类是类对象的isa,包含类方法>
- class、method等元信息的结构体是动态性的基础
C++方法绑定
C++的方法绑定是静态的(编译时)
- 对象的第一个元素是虚表地址- 虚表最后一个元素是0,其他元素是方法地址
- 编译时就把覆盖的虚函数地址进行了解析
Objc方法绑定方法
调用会被编译成调用objc_msgsend方法,执行步骤如下:
- 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。
- 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
- 如果 cache 找不到就找一下方法分发表。
- 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
-
如果还找不到就要开始进入动态方法解析了,后面会提到。
消息传递有缓存机制,且apple对缓存做过优化,所以对性能的影响很小
消息转发分为三大阶段
- 先征询消息接收者所属的类,看其是否能动态添加方法,以处理当前这个无法响应的 selector,这叫做动态方法解析(dynamic method resolution)
- 看看有没有其他对象(备援接收者,replacement receiver)能处理此消息。如果有,运行期系统会把消息转发给那个对象,转发过程结束;如果没有,则启动完整的消息转发机制。
- 完整的消息转发机制。运行期系统会把与消息有关的全部细节都封装到 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_msgsend这一层的处理来实现动态
Method Swizzling
通过修改类对象的方法列表来实现:增加(class_addMethod)、替换(method_setImplementation)、交换(method_exchangeImplementations)方法,设置方法的实现(method_setImplementation)
- 使用场景:
- 系统库函数的某个版本有bug,后面的某个版本修复了这个bug,但是app为了兼容低版本有bug的系统,可以替换掉库函数里有bug的实现
- 类似脚本语言的逻辑云端下发热替换:国内的大众点评 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是单向无关系。