NSString *theString = @"Hello World";
NSString *theString2 = @"Hello World";
NSLog(@"theString:%p --- theString:2%p",theString,theString2);
复制代码
打印结果:程序员
theString:0x11bb0d158 --- theString:20x11bb0d158
复制代码
两个变量为指向同一块内存的相同指针。此时将 theString2
赋值为 “Hello World !!!!”objective-c
theString2 = @"Hello World !!!!";
NSLog(@"theString:%p --- theString:2%p",theString,theString2);
复制代码
打印结果:算法
theString:0x12002e158 --- theString:20x12002e198
复制代码
此时,二者变为不一样的内存地址。因此,对象的本质是指向某一块内存区域的指针,指针的存储位置取决于对象声明的区域和有无成员变量指向。若在方法内部声明的对象,内存会分配到栈中,随着栈帧弹出而被自动清理;若对象为成员变量,内存则分配在堆区,声明周期须要程序员管理。 另外在探寻对象本质的过程当中发现对象的本质为声明为isa的指针,一枚指针在32位计算机占4字节,64位计算机占8字节,真正在iOS系统中,isa指针实际占用了16字节的内存区域,在此文中经过 clang
将OC代码转化为 C++
代码探究了一个对象所占的实际内存大小,详细可参阅 iOS底层原理探究- NSObject 内存大小数组
//Student.h
@class Book; //向前引用,避免在 .h 里导入其余文件
@interface Student : NSObject
@property (nonatomic, strong) BOOK *book;
@end
//student.m
#import "Book.h"
@implementation Student
- (void)readBook {
NSLog(@"read the book name is %@",self.book);
}
@end
复制代码
NSNumber *number = [NSNumber numberWithInteger:10086];
复制代码
改成缓存
NSNumber *number = @10086;
复制代码
NSArray *books = [NSArray arrayWithObjects:@"算法图解",@"高性能iOS应用开发",@"Effective Objective-C 2.0", nil];
NSString *firstBook = [books objectAtIndex:0];
复制代码
改成安全
NSArray *books = @[@"算法图解",@"高性能iOS应用开发",@"Effective Objective-C 2.0"];
NSString *firstBook = books[0];
复制代码
NSDictionary *info1 = [NSDictionary dictionaryWithObjectsAndKeys:@"极客学伟",@"name",[NSNumber numberWithInteger:18],@"age", nil];
NSString *name1 = [info1 objectForKey:@"name"];
复制代码
改成bash
NSDictionary *info2 = @{
@"name":@"极客学伟",
@"age":@18,
};
NSString *name2 = info2[@"name"];
复制代码
[arrayM replaceObjectAtIndex:0 withObject:@"new Object"];
[dictM setObject:@19 forKey:@"age"];
复制代码
改成框架
arrayM[0] = @"new Object";
dictM[@"age"] = @19;
复制代码
NSMutableArray *arrayM = @[@1,@"123",@"567"].mutableCopy;
复制代码
#define
预处理指令static const
来定义“只在编译单元内可见的常量”。因为此类常量不在全局符号表中,因此无需为其名称加前缀。extern
来声明全局常量,并在相关实现文件中定义其值。这种常量要出如今全局符号表中,因此名称应该加以区隔,一般用与之相关的类名作前缀。预处理指令是代码拷贝,在编译时会将代码中全部预处理指令展开填充到代码中,减小预处理指令也会加快编译速度。函数
.m
static const NSTimeInterval kAnimationDuration = 0.3;
复制代码
.h
extern NSString * const XWTestViewNoticationName;
.m
NSString * const XWTestViewNoticationName = @"XWTestViewNoticationName";
复制代码
NS_ENUUM
与 NS_OPTIONS
宏来定义枚举类型,并指明其底层数据类型。这样作能够确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选类型。switch
语句中不要实现 default
分支。这样的话,加入新枚举以后,编译器就会提示开发者:switch
语句并未处理因此枚举。/// 位移枚举
typedef NS_OPTIONS(NSUInteger, XWDirection) {
XWDirectionTop = 0,
XWDirectionBottom = 1 << 0,
XWDirectionLeft = 1 << 1,
XWDirectionRight = 1 << 2,
};
/// 常量枚举
typedef NS_ENUM(NSUInteger, SexType) {
SexTypeMale,
SexTypeFemale,
SexTypeUnknow,
};
复制代码
@property
语法来定义对象中所封装的数据。使用属性编译器会自动生成实例变量和改变量的get方法和set方法。 同时可使用 @synthesize
指定实例变量的名称,使用 @dynamic
使编译器不自动生成get方法和set方法。 属性可分为四类,分别:性能
atomic
原子性,系统默认。并非线程安全,release
方法不受原子性约束.nonatomic
非原子性readwrite
可读可写,同时拥有get方法和set方法。readonly
只读,仅有 get 方法。assign
简单赋值,用于基本成员类型strong
表示“拥有关系”,设置新值时会保留新值,释放旧值,再把新值设置给当前属性。weak
表示“非拥有关系”,设置新值时既不保留新值,也不释放旧值。同 assign
相似,所指对象销毁时会置nilunsafe_unretained
表示一种非拥有关系,语义同 assign
,仅适用于对象类型。当目标对象被销魂时不会自动清空。copy
表达的关系和 strong
相似。区别在于设置新值时不会保留新值,而是将其 拷贝 后赋值给当前属性。getter=<name>
指定获取方法(getter)的方法名, 如: @property (nonatomic, getter=isOn) BOOL on;
setter=<name>
指定设置方法(setter)的方法名。dealloc
方法中,应该直接经过实例变量来读写数据。在对象内部直接使用成员变量比使用点语法的优点在于,前者不须要通过 Objective-C 的方法派发过程,执行速度会更快,这时编译器会直接访问保存对象实例变量的那块内存。不过直接访问成员变量不会触发 KVO
,因此使用点语法访问属性仍是直接使用成员变量取决于具体行为。
isEqual:
与 hash
方法。hash
方法时,应该使用计算速度快并且哈希码碰撞概率低的算法。常规比较相等的方式 ==
比较的是两个对象指针是否相同。 在自定义对象重写 isEqual
方法可以使用此方式:
- (BOOL)isEqualToBook:(Book *)object {
if (self == object) return YES;
if (![_name isEqualToString:object.name]) return NO;
if (![_author isEqualToString:object.author]) return NO;
return YES;
}
复制代码
在自定义对象重写 hash
方法可以使用此方式:
@implementation Book
- (NSUInteger)hash {
NSUInteger nameHash = [_name hash];
NSUInteger authorHash = [_author hash];
return nameHash ^ authorHash;
}
@end
复制代码
例如声明一本书做为基类,经过“类族模式“建立相关的类,对应类型的在子类中实现相关方法。以下:
.h
typedef NS_ENUM(NSUInteger, BookType) {
BookTypeMath,
BookTypeChinese,
BookTypeEnglish,
};
@interface Book : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *author;
+ (instancetype)bookWithType:(BookType)type;
- (void)read;
@end
复制代码
.m
@interface BookMath : Book
- (void)read;
@end
@implementation BookMath
- (void)read {
NSLog(@"read The Math");
}
@end
@interface BookChinese : Book
- (void)read;
@end
@implementation BookChinese
- (void)read {
NSLog(@"read The Chinese");
}
@end
@interface BookEnglish : Book
- (void)read;
@end
@implementation BookEnglish
- (void)read {
NSLog(@"read The English");
}
@end
@implementation Book
+ (instancetype)bookWithType:(BookType)type {
switch (type) {
case BookTypeMath:
return [BookMath new];
break;
case BookTypeChinese:
return [BookChinese new];
break;
case BookTypeEnglish:
return [BookEnglish new];
break;
}
}
@end
复制代码
关联对象的语法:
#import <objc/runtime.h>
// Setter 方法
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
// Getter 方法
id objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
// 移除指定对象的全部关联对象值
void objc_removeAssociatedObjects(id _Nonnull object)
复制代码
实例一:使用关联对象将声明和执行进行 聚合 原写法
- (void)testAlertAssociate {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"要培养哪一种生活习惯?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"早起",@"早睡", nil];
[alertView show];
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSLog(@"你要早起");
}else if (buttonIndex == 2) {
NSLog(@"你要晚睡");
}else{
NSLog(@"取消");
}
}
复制代码
使用 “关联对象改写” 改写为:
static void *kAlertViewKey = "kAlertViewKey";
- (void)testAlertAssociate {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"要培养哪一种生活习惯?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"早起",@"早睡", nil];
[alertView show];
void(^AlertBlock)(NSUInteger) = ^(NSUInteger buttonIndex){
if (buttonIndex == 1) {
NSLog(@"你要早起");
}else if (buttonIndex == 2) {
NSLog(@"你要早睡");
}else{
NSLog(@"取消");
}
};
objc_setAssociatedObject(alertView, kAlertViewKey, AlertBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void(^AlertBlock)(NSUInteger) = objc_getAssociatedObject(alertView, kAlertViewKey);
AlertBlock(buttonIndex);
}
复制代码
如此可将实现和声明在一块儿处理,在回调处取出所关联的代码块执行。可以使得代码更易读。
实例二:为分类添加属性 众所周知,在 Objective-C 的分类中声明属性只能自动生成该属性的 getter 方法和 setter 方法 的声明,没有具体实现。因此真正给分类添加属性,使用关联对象是比较好的一种方式。
//NSTimer+XW.h
@interface NSTimer (XW)
@property (nonatomic, assign) NSUInteger tag;
@end
//NSTimer+XW.m
#import "NSTimer+XW.h"
#import <objc/runtime.h>
@implementation NSTimer (XW)
static void *kXW_NSTimerTagKey = "kXW_NSTimerTagKey";
#pragma mark - tag / getter setter
/// setter
- (void)setTag:(NSUInteger)tag {
NSNumber *tagValue = [NSNumber numberWithUnsignedInteger:tag];
objc_setAssociatedObject(self, kXW_NSTimerTagKey, tagValue, OBJC_ASSOCIATION_ASSIGN);
}
/// getter
- (NSUInteger)tag {
NSNumber *tagValue = objc_getAssociatedObject(self, kXW_NSTimerTagKey);
return tagValue.unsignedIntegerValue;
}
@end
复制代码
objc_msgSend
的做用objc_msgSend
执行流程
众所周知, OC 中方法调用的本质是发送消息 objc_msgSend
,其原型为:
/// self:消息接受者,cmd:选择子即执行方法,...:其余参数
void objc_msgSend(id self, SEL cmd, ...);
复制代码
举个例子🌰:
// xx类
id returnValue = [self doSomething:@"param"];
实质为:
id returnValue = objc_msgSend(xx类, @selector(doSomething:),@"param");
复制代码
其中OC在实现此机制的同时设计了缓存机制,每次调用一个方法会将此方法进行缓存,再次执行相同方法会提升执行效率,使其和静态绑定调用方法的速度相差不会那么悬殊。
消息转发的全流程:
假若调用一个没有实现的方法,控制台会抛出以下经典错误信息: unrecognized selector sent to instance 0xxx
在方法调用和抛出异常中间还经历了一段不为人知的历程,名曰:消息转发机制。上述错误提示即是调用没实现的方法以后底层转发给 NSObject
的 doedNotRecognizeSelector:
方法所抛出的。 消息转发的具体过程,首先:
/// 调用了未实现的类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
/// 调用了未实现的实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
复制代码
表示是否能够新增一个实例方法用以处理此方法,前提此类须要在程序中提早写好,可用Runtime 的 class_addMethod动态添加。
/// 调用了未实现的实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
/// 调用了未实现的 test 方法,动态添加一个 trendsMethod 方法,使其转发给新加的方法 trendsMethod
// 参数1:添加到的类, 参数2:添加新方法在类中的名称, 参数3:新方法的具体实现
// 参数4:新方法的参数返回值说明,如 v@: - 无参数无返回值 i@: - 无参数返回Int i@:@ - 一个参数返回Int
class_addMethod(self, sel, (IMP)class_getMethodImplementation([self class], @selector(trendsMethod)), "v@:");
return YES; //此处返回 YES or NO 均可以
}
return [super resolveInstanceMethod:sel];
}
- (void)trendsMethod {
NSLog(@"这是动态添加的方法");
}
复制代码
/// 可将未实现的实例方法转发给其余类处理
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod)) {
return [Chinese new]; // 消息转发给可以处理该实例方法的类的对象
}
return [super forwardingTargetForSelector:aSelector];
}
/// 可将未实现的类方法转发给其余类处理
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(testClassMethod)) {
return [Chinese class]; // 消息转发给可以处理该类方法的类
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
若上述过程都没有处理,程序会有最后一次处理机会,即是:
/// 方法签名,定义 返回值,参数
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
/// NSInvocation 封装了一个函数调用
//anInvocation.target - 方法调用者
//anInvocation.selector - 方法名
//anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#> - 获取第 index 个参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testInstanceMethod:)) {
return [anInvocation invokeWithTarget:[Chinese new]];//将实现转给另一个实现了此方法的对象进行处理
}
return [super forwardInvocation:anInvocation];
}
复制代码
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testClassMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testClassMethod:)) {
return [anInvocation invokeWithTarget:[Chinese class]];//将实现转给另一个实现了此方法的对象进行处理
}
return [super forwardInvocation:anInvocation];
}
复制代码
如上方法其实在实现 forwardingTargetForSelector
方法进行转发就能够实现相同的功能,何须到最后这步处理呢。因此,他的功能不止于此。实际能够函数中直接对未处理方法进行实现,以下:
/// 方法签名,定义 返回值,参数
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
// 转发方法最终实现
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testInstanceMethod:)) {
/// 能够在此处理, 未实现的方法
NSLog(@"这个方法 %s Student 没有实现!!!",sel_getName(anInvocation.selector));
id param;
[anInvocation getArgument:¶m atIndex:2];
NSLog(@"传进来的参数为: %@ - 可使其搞事情",param);
return;
}
return [super forwardInvocation:anInvocation];
}
复制代码
咱们可使用消息转发的机制,使程序永远不会出现 unrecognized selector sent to instance 0xxx
这种崩溃。并在控制台输出具体信息,咱们能够实现一个 NSObject
的分类 以下:
#import "NSObject+XWTool.h"
@implementation NSObject (XWTool)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {/// 已实现不作处理
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 类中, 调用了没有实现的实例方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {/// 已实现不作处理
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 类中, 调用了没有实现的类方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
复制代码
本质是使用 runtime
在运行时实现方法的替换:
/// 动态交换 m1 和 m2 两个方法的实现
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
复制代码
方法的实现可经过以下方法获取:
/// 获取方法的实现 cls: 方法所在的对象, name: 方法名
Method class_getInstanceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name)
复制代码
//UIViewController+XWDebug.m
#import "UIViewController+XWDebug.h"
#import <objc/runtime.h>
@implementation UIViewController (XWDebug)
#ifdef DEBUG
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/// 交换 class 的 viewDidLoad 方法
Method originViewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
Method xwViewDidLoad = class_getInstanceMethod(self, @selector(xw_viewDidLoad));
method_exchangeImplementations(originViewDidLoad, xwViewDidLoad);
/// 交换 class 的 viewDidAppear方法
Method originViewDidAppear = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method xwViewDidAppear = class_getInstanceMethod(self, @selector(xw_viewDidAppear:));
method_exchangeImplementations(originViewDidAppear, xwViewDidAppear);
});
}
- (void)xw_viewDidLoad {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"********* %@ **** viewDidload ****",self);
});
[self xw_viewDidLoad];
}
- (void)xw_viewDidAppear:(BOOL)animated {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"********* %@ **** viewDidAppear ****",self);
});
[self xw_viewDidAppear:animated];
}
#else
#endif
@end
复制代码
判断对象是否为某个类实例:
- (BOOL)isMemberOfClass:(Class)aClass;
复制代码
判断对象是否为某类或其派生类的实例:
- (BOOL)isKindOfClass:(Class)aClass;
复制代码
例如判断 一个 NSDictionary
的实例:
NSMutableDictionary *dict = @{@"key":@"value"}.mutableCopy;
BOOL example1 = [dict isMemberOfClass:[NSDictionary class]]; // NO
BOOL example2 = [dict isMemberOfClass:[NSMutableDictionary class]]; // NO
BOOL example3 = [dict isKindOfClass:[NSDictionary class]]; // YES
BOOL example4 = [dict isKindOfClass:[NSMutableDictionary class]]; // YES
BOOL example5 = [dict isKindOfClass:[NSArray class]]; // NO
// BOOL example6 = [dict isKindOfClass:[__NSDictionaryM class]]; // YES
复制代码
注意,在 [dict isMemberOfClass:[NSMutableDictionary class]]
的判断中,实际上返回的 NO,虽然咱们声明 dict
为 NSMutableDictionary
的实例,但实际上 dict
为 __NSDictionaryM
类的一个实例,在控制台可验证:
(lldb) po [dict isMemberOfClass:[__NSDictionaryM class]]
YES
复制代码
《Effective Objective-C 2.0》书中所写的实例是错误的!!
故 尽信书不如无书,相信实际所验证的,这也启发读者在读书过程当中须要尽可能将实例验证一下,说不定做者在写书时也是想固然的落笔。
前两章完结,后续几天会陆续发表其他篇章的读书/实战笔记,笔者期待和众大神一块儿学习,共同进步。
未完待续...