一、load和initialize
1、load
NSObject类中都有+(void)load;
方法。load方法是在main函数之前加载的,即pre-main阶段加载,通过runtime的底层源码进行分析:
1)load_images
函数,源自objc-runtime-new.mm文件
void load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
2)prepare_load_methods
函数
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 对所有类进行遍历查找+load方法
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 查找分类中的+load方法
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
//将分类的+load方法放入category_loadable中
add_category_to_loadable_list(cat);
}
}
3)schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
// 该方法会查找父类的load方法,因为递归的调用,所有会在子类之前被调用
schedule_class_load(cls->getSuperclass());
// 将+load方法的类加入class_loadable
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
schedule_class_load(cls->getSuperclass());
方法保证了父类的+load方法总是会被执行,所以在override父类的+load
方法时不需要调用[super load]
,根据add_class_to_loadable_list(cls);
在schedule_class_load(cls->getSuperclass());
的执行之后,并且使用递归的方式执行,所以父类的+load
方法总是在子类之前被调用,优先添加到class_loadable表中
4)call_load_methods
,源自objc-loadmethod.mm文件
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
call_class_loads()函数会将加入loadable_classes中的类遍历执行+load方法,call_category_loads,同理
5)call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
(*load_method)(cls, @selector(load));
说明+load
方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend
来发送消息。也正是这句代码造就了+load方法的最大特点:类,父类与分类之间+load方法的调用是互不影响的。
通过以上的代码执行过程可以看出:
- +load方法是在main函数之前加载的
- 类中的+load方法是在分类的+load方法之前被加载
- 类中的+load会递归的方式查找父类的+load方法,所以子类中重写+load方法不需要调用[super load]方法
- 无论该类是否接收消息,都会调用
+load
方法;
2、initialize
NSObject类中都会有+ (void)initialize;
方法。在NSObject.mm实现中是+ (void)initialize {}
可以看出并没有实际的实现代码,在分析源代码的过程中会发现以下的代码中initializeNonMetaClass,从注视可以看出是通过发送’+initialize’消息后,来执行初始化操作。
1)class_initialize
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// 确保在开始初始化cls之前,super已完成初始化
// See note about deadlock above.
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
//未初始化之前,发送一个setInitializing消息,将该类的元类的信息更改为CLS_INITIALIZING
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
// 设置为CLS_INITIALIZING
cls->setInitializing();
reallyInitialize = YES;
// Grab a copy of the will-initialize funcs with the lock held.
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}
if (reallyInitialize) {
// 成功设置元类的CLS_INITIALIZING之后,初始化该类
// 记录初始化的类,以便向它发送消息
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);
// 发送+initialize消息
// 如果该类没有实现+initialize 则会发送给他的父类
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
// 调用初始化方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
objc_thread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
2)callInitialize
通过消息发送来执行initialize方法
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
3)lockAndFinishInitializing
在完成初始化之后会调用该函数
/***********************************************************************
* lockAndFinishInitializing
* Mark a class as finished initializing and notify waiters, or queue for later.
* If the superclass is also done initializing, then update
* the info bits and notify waiting threads.
* If not, update them later. (This can happen if this +initialize
* was itself triggered from inside a superclass +initialize.)
**********************************************************************/
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
// 如果父类完成初始化,则调用该方法
_finishInitializing(cls, supercls);
} else {
// 父类未完成初始化
_finishInitializingAfter(cls, supercls);
}
}
4)验证+initialize
在调用的时候才加载
@interface Person : NSObject
@end
@implementation Person
+ (void)initialize {
printf("Person initialize \n");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
}
return 0;
}
通过实例代码打印可以知道,当Person *p = [Person new];
创建时会打印日志,但是代码被注释之后,并不会做出打印。
总结:
+initialize
方法是在main函数之后调用的;+initialize
方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;- 子类中不需要调用super方法,会自动调用父类的方法实现;调用顺序是先调用父类,在调用子类
+initialize
只调用一次二、分类/扩展/协议
1、分类
分类可以为当前类扩展方法功能
声明一个简单的分类:
.h文件中 ```objectivecimport
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (Add)
-(void)addTest;
@end
NS_ASSUME_NONNULL_END
**.m文件中**
```objectivec
#import "NSObject+Add.h"
@implementation NSObject (Add)
-(void)addTest {
NSLog(@"test 分类");
}
@end
下面就带着疑问去辩证分类中常见的问题
1)分类中只能添加方法不能添加成员变量
创建一个Person类,在Person类中含有属性@property(nonatomic, copy) NSString *name;
和-(void) work
方法。在创建一个分类Person+Cate.h,给分类中添加属性age,如下代码
@interface Person (Cate)
@property(nonatomic, assign) NSInteger age;
- (void)song;
@end
@implementation Person (Cate)
- (void)song {
printf("Person (Cate) song ... \n");
}
@end
到这里,编译器并不会报错,看似可以添加属性。但是.m文件中出现警告⚠️
Property ‘age’ requires method ‘age’ to be defined - use @dynamic or provide a method implementation in this category Property ‘age’ requires method ‘setAge:’ to be defined - use @dynamic or provide a method implementation in this category
然后再调用的地方,进行给age属性赋值
Person *p = [Person new];
p.name = @"Kate";
p.age = 10;
[p song];
到这里的时候,编译器是可以通过的,并不会报错。
然后我们运行代码,发现报错了NSInvalidArgumentException
,错误如下:
Thread 1: “-[Person setAge:]: unrecognized selector sent to instance 0x10281d410”
我们修改代码,继续尝试,直接访问_age
NSLog(@"%@",p->_age);
这时候,编译器会发出错误信息
‘Person’ does not have a member named ‘_age’
由以上分析可以知道,分类中添加了属性age,但是实际没有成员变量_age,也不会生成get,set方法,需要手动实现。通过clang将分类的.m文件转化为C++文件,也可以看出并没有get,set方法的生成。
2)分类中可以使用super调用父类中的方法
测试代码如下:
创建Son类继承Person类,并创建Son+Cate.h分类文件,在该分类文件中重写Person类中的work方法
#import "Son+Cate.h"
@implementation Son (Cate)
- (void)work {
[super work];
printf("Son (Cate) work ... \n");
}
@end
通过Son的实例来调用work方法
Son *s = [Son new];
[s work];
得到日志结果为
如果去除[super work];
后执行结果为:
实验过程中,我们还发现是否引入Son+cate的分类文件
都会执行Son+Cate.m文件中覆盖的方法,执行结果如下:
综上得出实验的结论
- **分类中重写父类中方法可以通过`super`调用父类中的方法实现,无`super`则覆盖**
- **分类覆盖了父类中的方法,在子类调用该方法时,不管是否引入分类头文件,都会执行分类中重写的方法**
- **子类可以覆盖父类分类中添加的方法,需要在子类中引入父类的分类文件**
3)分类中重写了原类中的方法,则会覆盖原类中的方法
在Person类的分类Person (Cate)中重写-(void)work
方法
#import "Person+Cate.h"
@implementation Person (Cate)
- (void)work {
printf("Person (Cate) work ... \n");
}
@end
编译器发出警告⚠️
Category is implementing a method which will also be implemented by its primary class
执行代码,我们会发先不管我们是否引入分类头文件
最终的结果都是
由以上可以总结:
- **分类中重写原类的方法会覆盖原类中的方法实现,不管是否引入头文件**
- **分类中重写原类中的方法,编译器会发出警告**
- 详细的原因需要了解objc_msgSend的调用方法的机制
4)多个分类具有相同名称的方法名,最终执行取决于加载顺序
这个问题的话,依次创建Person+CateB.h,Person+CateC.h分类文件,这时候都给分类中添加song方法
Person+Cate.h
#import "Person+Cate.h"
@interface Person (Cate)
- (void)song;
@end
@implementation Person (Cate)
- (void)song {
printf("Person (Cate) song ... \n");
}
Person+CateB.h
@end
@interface Person (CateB)
- (void)song;
@end
@implementation Person (CateB)
- (void)song {
printf("Person (CateB) song ... \n");
}
@end
Person+CateC.h
@interface Person (CateC)
- (void)song;
@end
@implementation Person (CateC)
- (void)song {
printf("Person (CateC) song ... \n");
}
@end
在使用Person类实例进行调用song方法时打印
查看Tagert->Build Phases->Compile Sources下面如下
我们调整顺序
查看打印结果
我们会发现当我们调整加载分类文件的顺序的时候,最终执行的结果都是最后一个加载的分类文件的方法
所以可以得出结论:
- 分类中相同方法名的方法,最终被执行的是最后一个。
5)利用runtime特性给分类添加成员变量
分类的C语言结构体如下:
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; // 属性表
};
虽然分类无法直接添加成员变量,但是可以通过runtime中的关联对象特性来间接给分类添加能使用的属性
objc_setAssociatedObject,objc_getAssociatedObject
给Person+Cate分类中添加能使用的age属性:
@interface Person (Cate)
@property(nonatomic, assign) NSInteger age;
@end
@implementation Person (Cate)
// C语言字符串作为Key
static char *ageKey = "ageKey";
- (void)setAge:(NSInteger)age {
objc_setAssociatedObject(self, &ageKey, @(age), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)age {
return [objc_getAssociatedObject(self, &ageKey) integerValue];
}
@end
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
参数说明:
1. **id object**:被关联的对象
1. const void *key: 关联的key,要求唯一
1. id value: 关联的对象值
1. objc_AssociationPolicy policy:内存管理的策略
1. OBJC_ASSOCIATION_ASSIGN
1. OBJC_ASSOCIATION_RETAIN_NONATOMIC
1. OBJC_ASSOCIATION_COPY_NONATOMIC
1. OBJC_ASSOCIATION_RETAIN
1. OBJC_ASSOCIATION_COPY
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
参数
6)分类底层原理
2、扩展(匿名类别)
可以说成是匿名的分类,但是扩展只能出现在.m文件中,扩展私有属性,私有方法,私有变量。
扩展是实例代码如下:
@interface Person () // 匿名类别,这里面定义的所有东西都只能在当前文件内被访问
{
NSString *_desc; //私有变量
}
@property(nonatomic, assign) NSInteger height; //私有属性
- (void)test; // 私有方法
@end
@implementation Person
- (void)work {
}
- (void)test {
}
@end
3、协议
协议的关键字@protocol,协议是其他多个类不通过继承就实现的接口。
协议本身只声明接口,并没有对接口实现。
在声明协议之后,对于任何需要实现该协议的类,必须声明它们要实现该协议。只有这样编译器才会确认该类是否实现了协议所需要的方法。
协议示例代码:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol PersonActionProtocol <NSObject>
@required //遵循协议的类必须实现改方法
- (void)eat;
@optional // 非必须的
- (void)drink;
@end
NS_ASSUME_NONNULL_END
如果未实现@required的协议方法,则会发出警告⚠️
Class ‘Person’ does not conform to protocol ‘PersonActionProtocol’
协议中默认都是必须要实现的方法
协议可以继承其他协议,只需要在协议名称后面使用<协议名>
三、NSString存储原理
NSString是一个类簇(Class Clusters),最后生成的对象类型,取决于我们调用的初始化方法。
类型 | 存储区域 | 初始化方式 | 引用计数 |
---|---|---|---|
__NSCFConstantString | 常量区 | initWithString,stringWithString,@”” | -1 |
__NSCFString | 堆区 | stringWithFormat(通过format方式创建,并且字符串内容仅由数字、字母和常规ASCII字符构成,且其长度>=10) | 1 |
__NSTaggedPointerString | 栈区 | 在运行时创建字符串时,会对字符串内容及长度作判断,若内容由ASCII字符构成且长度<10,这时候创建的字符串类型就是 NSTaggedPointerString (标签指针字符串),字符串直接存储在指针的内容中 | -1 |
NSTaggedPointerString 类型的字符串引用计数同样为-1,不适用对象的内存管理策略。
NSTaggedPointerString 类型的字符串是对__NSCFString类型的一种优化。
以下代码用于测试字符串的所属类型:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *a = @"ABC";
NSString *b = [NSString stringWithString:@"ABCDEFGHIJKLMN"];
NSString *c = [NSString stringWithString:@"ABCDEF"];
NSString *d = [NSString stringWithFormat:@"%@",a];
NSString *e = [NSString stringWithFormat:@"%@",b];
NSString *f = [NSString stringWithFormat:@"%@",c];
NSString *g = [[NSString alloc] initWithString:@"ABCDEF"];
NSString *h = [[NSString alloc] initWithFormat:@"ABCDEF"];
NSLog(@"a: %@",[a class]);
NSLog(@"b: %@",[b class]);
NSLog(@"c: %@",[c class]);
NSLog(@"d: %@",[d class]);
NSLog(@"e: %@",[e class]);
NSLog(@"f: %@",[f class]);
NSLog(@"g: %@",[g class]);
NSLog(@"h: %@",[h class]);
}
return 0;
}
输出结果如下:
2021-02-19 11:08:30.198813+0800 NSString演示[3496:60259] a: __NSCFConstantString 2021-02-19 11:08:30.199291+0800 NSString演示[3496:60259] b: __NSCFConstantString 2021-02-19 11:08:30.199359+0800 NSString演示[3496:60259] c: __NSCFConstantString 2021-02-19 11:08:30.199401+0800 NSString演示[3496:60259] d: NSTaggedPointerString 2021-02-19 11:08:30.199471+0800 NSString演示[3496:60259] e: __NSCFString 2021-02-19 11:08:30.199542+0800 NSString演示[3496:60259] f: NSTaggedPointerString 2021-02-19 11:08:30.199585+0800 NSString演示[3496:60259] g: __NSCFConstantString 2021-02-19 11:08:30.199647+0800 NSString演示[3496:60259] h: NSTaggedPointerString
同时编译器也发出警告:Using 'stringWithString:' with a literal is redundant
综上代码示例,也基本证实了NSString表格中的结论。
**
四、可变类型/不可变类型
可变类型是可以修改变量所分配的内存空间中的值,或追加内存空间
不可变类型是不可以修改变量所分配的内存空间中的值
1、NSString与NSMutableString
NSString代表字符序列不可变的字符串。
NSMutableString对象代表一个字符序列可变的字符串,而且NSMutableString是NSString的子类,NSString所包含的方法,NSMutableString都可以直接使用,NSMutableString对象也可直接当成NSString对象使用。
方法 | NSString | NSMutableString |
---|---|---|
stringWithString: | ✓ | ✓ |
stringByAppendingString: | ✓ | ✓ |
isEqualToString | ✓ | ✓ |
isEqualToNumber | ✓ | ✓ |
compare | ✓ | ✓ |
uppercaseString | ✓ | ✓ |
lowercaseString | ✓ | ✓ |
substringToIndex | ✓ | ✓ |
substringFromIndex | ✓ | ✓ |
rangeOfString | ✓ | ✓ |
insertString | ✓ | |
insertString: atIndex: | ✓ | |
deleteCharactersInRange:NSMakeRange() | ✓ |
2、NSArray与NSMutableArray
NSArray是不可变数组,元素可以重复,有序。
NSMutableArray是可变数组,同时也是NSArray的子类,继承NSArray的所有方法。
3、NSDictionary与NSMutableDictionary
NSDictionary是不可变字典。
NSMutableDictionary是可变字典,是NSDictionary的子类。
4、NSSet与NSMutableSet
NSSet是不可变的集合,元素唯一不重复,无序。
NSMutableSet是可变的集合,继承于NSSet。
5、copy与mutable copy的使用
浅复制:不拷贝对象本身,仅仅是拷贝指向对象的指针
深复制:是直接拷贝整个对象内存到另一块内存中
iOS中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。
- 系统的非容器类对象
例如:NSString、NSNumber等对象
NSString *string = @"test";
NSString *newString = [string copy];
NSMutableString *mutableString = [string mutableCopy];
NSLog(@"string的内存地址:%p", string);
NSLog(@"newString的内存地址:%p", newString);
NSLog(@"mutableString的内存地址:%p", mutableString);
得到以下结果
我们可以看出,copy对象只对原对象进行了弱引用,而mutable copy则重新开辟了内存空间
再结合以下代码
NSMutableString *string = [NSMutableString stringWithString:@"test"];
NSString *newString = [string copy];
NSMutableString *mutableStringCopy = [string copy];
NSMutableString *mutableString = [string mutableCopy];
NSLog(@"string的内存地址:%p", string);
NSLog(@"newString的内存地址:%p", newString);
NSLog(@"mutableStringCopy的内存地址:%p", mutableStringCopy);
NSLog(@"mutableString的内存地址:%p", mutableString);
我们可以看到,对可变对象进行copy也会分配新的内存空间,但返回的类型是不可变的
- 系统的容器类对象
例如:NSArray,NSDictionary等,对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。通过copy和mutable copy我们只能得到容器对象的可变和不可变属性,但容器内引用的元素都是浅拷贝,即指针赋值,除非利用以下方法实现对象深拷贝。
NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject: array]];
其中deepCopyArray的拷贝中,不可变属性的值依然为指针赋值,而trueDeepCopyArray是真正意义上的对象完全拷贝
- 自定义对象
如果是我们定义的对象,那么我们自己要实现NSCopying,NSMutableCopying这样就能调用copy和mutablecopy了
@interface CopyItem : NSObject
@property (nonatomic, strong) NSMutableString *mName;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)printAllPropertyAddress;
@end
@interface CopyItem ()<NSCopying, NSMutableCopying>
@end
@implementation CopyItem
- (void)printAllPropertyAddress {
NSLog(@"mName的地址:%p", self.mName);
NSLog(@"name的地址:%p", self.name);
}
- (id)copyWithZone:(NSZone *)zone {
CopyItem *item = [[self class] allocWithZone:zone];
item.name = [self.name copy];
item.mName = [self.mName copy];
item.age = self.age;
return item;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
CopyItem *item = [[self class] allocWithZone:zone];
item.name = [self.name mutableCopy];
item.mName = [self.mName mutableCopy];
item.age = self.age;
return item;
}
@end
CopyItem *originItem = [CopyItem new];
originItem.name = @"name";
originItem.mName = [[NSMutableString alloc] initWithString:@"mName"];
originItem.age = 16;
CopyItem *newCopyItem = [originItem copy];
CopyItem *newMCopyItem = [originItem mutableCopy];
[originItem printAllPropertyAddress];
[newCopyItem printAllPropertyAddress];
[newMCopyItem printAllPropertyAddress];
tips:如果将mName设置为copy属性,则此处3个mName的地址都是一个。对于声明了copy属性的无论可变或是不可变的对象,在setter方法执行的时候都是返回了一个不可变的数据类型,因此会存在该问题
五、NSProxy与NSObject
NSObject是OC中大多数类的基类。
以下是NSObject.h文件中的代码:
/* NSObject.h
Copyright (c) 1994-2012, Apple Inc. All rights reserved.
*/
#ifndef _OBJC_NSOBJECT_H_
#define _OBJC_NSOBJECT_H_
#if __OBJC__
#include <objc/objc.h>
#include <objc/NSObjCRuntime.h>
@class NSString, NSMethodSignature, NSInvocation;
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;
@end
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
+ (void)load;
+ (void)initialize;
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
NS_DESIGNATED_INITIALIZER
#endif
;
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");
- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");
- (id)copy;
- (id)mutableCopy;
+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;
+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (NSUInteger)hash;
+ (Class)superclass;
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
+ (NSString *)description;
+ (NSString *)debugDescription;
@end
#endif
#endif
NSProxy作为OC中的一个抽象类,遵循<NSObject>
协议,主要做消息转发。
以下是NSProxy.h文件中的信息
/* NSProxy.h
Copyright (c) 1994-2019, Apple Inc. All rights reserved.
*/
#import <Foundation/NSObject.h>
@class NSMethodSignature, NSInvocation;
NS_ASSUME_NONNULL_BEGIN
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
__ptrauth_objc_isa_pointer Class isa;
}
+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;
- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
// - (id)forwardingTargetForSelector:(SEL)aSelector;
@end
NS_ASSUME_NONNULL_END
比较以上两部分的代码可以得出:
1、NSProxy比NSObject更加简洁。
2、NSProxy没有init方法,直接alloc。
3、NSProxy遵循
2、NSProxy中做消息转发的两个方法
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
NSProxy如何使用消息转发?
@interface Proxy : NSProxy
//创建初始化方法
- (instancetype)initWithTarget:(id)target;
@end
@implementation Proxy
{
id target;
}
//创建初始化方法
- (instancetype)initWithTarget:(id)target {
self->target = target;
return self;
}
// 实现消息转发的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *sig;
sig = [target methodSignatureForSelector:sel];
return sig;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:target];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([target respondsToSelector:aSelector]) {
return YES;
}
return NO;
}
@end
以上代码即是继承自NSProxy的子类,实现了初始化方法,并且重写了methodSignatureForSelector:和forwardInvocation:方法。
使用过程中的代码
NSMutableArray *array = [NSMutableArray array];
id proxy = [[Proxy alloc] initWithTarget:array];
[proxy addObject:@"ABC"];
[proxy addObject:@"EF"];
[proxy performSelector:@selector(addObject:) withObject:@"GH"];
NSLog(@"%@",array);
打印结果
( ABC, EF, GH )
打印结果中可以看出,Proxy已经实现了array方法的转发功能。
继续将Proxy编译成C/C++代码查看底层实现
struct Proxy_IMPL {
struct NSProxy_IMPL NSProxy_IVARS;
id target;
};
static instancetype _Nonnull _I_Proxy_initWithTarget_(Proxy * self, SEL _cmd, id _Nonnull target) {
(*(id *)((char *)self + OBJC_IVAR_$_Proxy$target)) = target;
return self;
}
static NSMethodSignature * _Nullable _I_Proxy_methodSignatureForSelector_(Proxy * self, SEL _cmd, SEL _Nonnull sel) {
NSMethodSignature *sig;
sig = ((NSMethodSignature *(*)(id, SEL, SEL))(void *)objc_msgSend)((id)(*(id *)((char *)self + OBJC_IVAR_$_Proxy$target)), sel_registerName("methodSignatureForSelector:"), (SEL _Nonnull)sel);
return sig;
}
static void _I_Proxy_forwardInvocation_(Proxy * self, SEL _cmd, NSInvocation * _Nonnull invocation) {
((void (*)(id, SEL, id _Nonnull))(void *)objc_msgSend)((id)invocation, sel_registerName("invokeWithTarget:"), (id)(*(id *)((char *)self + OBJC_IVAR_$_Proxy$target)));
}
static BOOL _I_Proxy_respondsToSelector_(Proxy * self, SEL _cmd, SEL aSelector) {
if (((BOOL (*)(id, SEL, SEL))(void *)objc_msgSend)((id)(*(id *)((char *)self + OBJC_IVAR_$_Proxy$target)), sel_registerName("respondsToSelector:"), (SEL)aSelector)) {
return ((bool)1);
}
return ((bool)0);
}
可以看出都是使用objc_msgSend
来实现消息转发。
NSProxy和NSObject比较
相同点:
1.都可以做消息转发 2.都需要重写
forwardInvocation
和methodSignatureForSelector
方法
不同点:
1.继承自NSObject的代理类是不会自动转发respondsToSelector:和isKindOfClass:这两个方法的, 而继承自NSProxy的代理类却是可以的。 2.NSObject的所有Category中定义的方法无法在继承NSObject的代理类中完成转发
六、NSDictionary与NSCache的区别
NSCache``是一个可变的集合
,主要用来存储key-value
对NSCache
是线程安全的,在多线程操作中,不需要对Cache进行加锁,NSCache的key只是对对象的强引用,对象不需要实现NSCopying协议,NSCache也不会像NSDictionary一样复制对象。
NSCache的特性
1、
NSCache
在系统发出低内存通知时,会自动删除缓存。 2、NSCache
可以设置数量限制和大小设置,countLimit
设置数量限制的,totalCostLimit
是设置大小的 3、可以设置代理
NSMutableDictionary
与NSCache
区别:
1.
NSCache
类有自己的自动删除策略,以确保缓存不用使用太多的系统内存。如果其他应用需要内存,则自动删除策略会从缓存中删除一些项目,从而最大限度的减少内存占用。 2.NSCache
是线程安全的,我们可以从不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。其中线程安全是pthread_mutex
完成的。 3.明显区别于NSMutableDictionary
的是,键对象不会被retain
。(键不需要实现NSCopying
协议)