一、load和initialize

1、load

NSObject类中都有+(void)load;方法。load方法是在main函数之前加载的,即pre-main阶段加载,通过runtime的底层源码进行分析:
1)load_images函数,源自objc-runtime-new.mm文件

  1. void load_images(const char *path __unused, const struct mach_header *mh)
  2. {
  3. if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
  4. didInitialAttachCategories = true;
  5. loadAllCategories();
  6. }
  7. // Return without taking locks if there are no +load methods here.
  8. if (!hasLoadMethods((const headerType *)mh)) return;
  9. recursive_mutex_locker_t lock(loadMethodLock);
  10. // Discover load methods
  11. {
  12. mutex_locker_t lock2(runtimeLock);
  13. prepare_load_methods((const headerType *)mh);
  14. }
  15. // Call +load methods (without runtimeLock - re-entrant)
  16. call_load_methods();
  17. }

2)prepare_load_methods 函数

  1. void prepare_load_methods(const headerType *mhdr)
  2. {
  3. size_t count, i;
  4. runtimeLock.assertLocked();
  5. // 对所有类进行遍历查找+load方法
  6. classref_t const *classlist =
  7. _getObjc2NonlazyClassList(mhdr, &count);
  8. for (i = 0; i < count; i++) {
  9. schedule_class_load(remapClass(classlist[i]));
  10. }
  11. // 查找分类中的+load方法
  12. category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
  13. for (i = 0; i < count; i++) {
  14. category_t *cat = categorylist[i];
  15. Class cls = remapClass(cat->cls);
  16. if (!cls) continue; // category for ignored weak-linked class
  17. if (cls->isSwiftStable()) {
  18. _objc_fatal("Swift class extensions and categories on Swift "
  19. "classes are not allowed to have +load methods");
  20. }
  21. realizeClassWithoutSwift(cls, nil);
  22. ASSERT(cls->ISA()->isRealized());
  23. //将分类的+load方法放入category_loadable中
  24. add_category_to_loadable_list(cat);
  25. }
  26. }

3)schedule_class_load

  1. static void schedule_class_load(Class cls)
  2. {
  3. if (!cls) return;
  4. ASSERT(cls->isRealized()); // _read_images should realize
  5. if (cls->data()->flags & RW_LOADED) return;
  6. // Ensure superclass-first ordering
  7. // 该方法会查找父类的load方法,因为递归的调用,所有会在子类之前被调用
  8. schedule_class_load(cls->getSuperclass());
  9. // 将+load方法的类加入class_loadable
  10. add_class_to_loadable_list(cls);
  11. cls->setInfo(RW_LOADED);
  12. }

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文件

  1. void call_load_methods(void)
  2. {
  3. static bool loading = NO;
  4. bool more_categories;
  5. loadMethodLock.assertLocked();
  6. // Re-entrant calls do nothing; the outermost call will finish the job.
  7. if (loading) return;
  8. loading = YES;
  9. void *pool = objc_autoreleasePoolPush();
  10. do {
  11. // 1. Repeatedly call class +loads until there aren't any more
  12. while (loadable_classes_used > 0) {
  13. call_class_loads();
  14. }
  15. // 2. Call category +loads ONCE
  16. more_categories = call_category_loads();
  17. // 3. Run more +loads if there are classes OR more untried categories
  18. } while (loadable_classes_used > 0 || more_categories);
  19. objc_autoreleasePoolPop(pool);
  20. loading = NO;
  21. }

call_class_loads()函数会将加入loadable_classes中的类遍历执行+load方法,call_category_loads,同理
5)call_class_loads

  1. static void call_class_loads(void)
  2. {
  3. int i;
  4. // Detach current loadable list.
  5. struct loadable_class *classes = loadable_classes;
  6. int used = loadable_classes_used;
  7. loadable_classes = nil;
  8. loadable_classes_allocated = 0;
  9. loadable_classes_used = 0;
  10. // Call all +loads for the detached list.
  11. for (i = 0; i < used; i++) {
  12. Class cls = classes[i].cls;
  13. load_method_t load_method = (load_method_t)classes[i].method;
  14. if (!cls) continue;
  15. if (PrintLoading) {
  16. _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
  17. }
  18. (*load_method)(cls, @selector(load));
  19. }
  20. // Destroy the detached list.
  21. if (classes) free(classes);
  22. }

(*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

  1. /***********************************************************************
  2. * class_initialize. Send the '+initialize' message on demand to any
  3. * uninitialized class. Force initialization of superclasses first.
  4. **********************************************************************/
  5. void initializeNonMetaClass(Class cls)
  6. {
  7. ASSERT(!cls->isMetaClass());
  8. Class supercls;
  9. bool reallyInitialize = NO;
  10. // 确保在开始初始化cls之前,super已完成初始化
  11. // See note about deadlock above.
  12. supercls = cls->getSuperclass();
  13. if (supercls && !supercls->isInitialized()) {
  14. initializeNonMetaClass(supercls);
  15. }
  16. //未初始化之前,发送一个setInitializing消息,将该类的元类的信息更改为CLS_INITIALIZING
  17. SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
  18. {
  19. monitor_locker_t lock(classInitLock);
  20. if (!cls->isInitialized() && !cls->isInitializing()) {
  21. // 设置为CLS_INITIALIZING
  22. cls->setInitializing();
  23. reallyInitialize = YES;
  24. // Grab a copy of the will-initialize funcs with the lock held.
  25. localWillInitializeFuncs.initFrom(willInitializeFuncs);
  26. }
  27. }
  28. if (reallyInitialize) {
  29. // 成功设置元类的CLS_INITIALIZING之后,初始化该类
  30. // 记录初始化的类,以便向它发送消息
  31. _setThisThreadIsInitializingClass(cls);
  32. if (MultithreadedForkChild) {
  33. // LOL JK we don't really call +initialize methods after fork().
  34. performForkChildInitialize(cls, supercls);
  35. return;
  36. }
  37. for (auto callback : localWillInitializeFuncs)
  38. callback.f(callback.context, cls);
  39. // 发送+initialize消息
  40. // 如果该类没有实现+initialize 则会发送给他的父类
  41. if (PrintInitializing) {
  42. _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
  43. objc_thread_self(), cls->nameForLogging());
  44. }
  45. // Exceptions: A +initialize call that throws an exception
  46. // is deemed to be a complete and successful +initialize.
  47. //
  48. // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
  49. // bootstrapping problem of this versus CF's call to
  50. // objc_exception_set_functions().
  51. #if __OBJC2__
  52. @try
  53. #endif
  54. {
  55. // 调用初始化方法
  56. callInitialize(cls);
  57. if (PrintInitializing) {
  58. _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
  59. objc_thread_self(), cls->nameForLogging());
  60. }
  61. }
  62. #if __OBJC2__
  63. @catch (...) {
  64. if (PrintInitializing) {
  65. _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
  66. "threw an exception",
  67. objc_thread_self(), cls->nameForLogging());
  68. }
  69. @throw;
  70. }
  71. @finally
  72. #endif
  73. {
  74. // Done initializing.
  75. lockAndFinishInitializing(cls, supercls);
  76. }
  77. return;
  78. }
  79. else if (cls->isInitializing()) {
  80. // We couldn't set INITIALIZING because INITIALIZING was already set.
  81. // If this thread set it earlier, continue normally.
  82. // If some other thread set it, block until initialize is done.
  83. // It's ok if INITIALIZING changes to INITIALIZED while we're here,
  84. // because we safely check for INITIALIZED inside the lock
  85. // before blocking.
  86. if (_thisThreadIsInitializingClass(cls)) {
  87. return;
  88. } else if (!MultithreadedForkChild) {
  89. waitForInitializeToComplete(cls);
  90. return;
  91. } else {
  92. // We're on the child side of fork(), facing a class that
  93. // was initializing by some other thread when fork() was called.
  94. _setThisThreadIsInitializingClass(cls);
  95. performForkChildInitialize(cls, supercls);
  96. }
  97. }
  98. else if (cls->isInitialized()) {
  99. // Set CLS_INITIALIZING failed because someone else already
  100. // initialized the class. Continue normally.
  101. // NOTE this check must come AFTER the ISINITIALIZING case.
  102. // Otherwise: Another thread is initializing this class. ISINITIALIZED
  103. // is false. Skip this clause. Then the other thread finishes
  104. // initialization and sets INITIALIZING=no and INITIALIZED=yes.
  105. // Skip the ISINITIALIZING clause. Die horribly.
  106. return;
  107. }
  108. else {
  109. // We shouldn't be here.
  110. _objc_fatal("thread-safe class init in objc runtime is buggy!");
  111. }
  112. }

2)callInitialize通过消息发送来执行initialize方法

  1. void callInitialize(Class cls)
  2. {
  3. ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
  4. asm("");
  5. }

3)lockAndFinishInitializing在完成初始化之后会调用该函数

  1. /***********************************************************************
  2. * lockAndFinishInitializing
  3. * Mark a class as finished initializing and notify waiters, or queue for later.
  4. * If the superclass is also done initializing, then update
  5. * the info bits and notify waiting threads.
  6. * If not, update them later. (This can happen if this +initialize
  7. * was itself triggered from inside a superclass +initialize.)
  8. **********************************************************************/
  9. static void lockAndFinishInitializing(Class cls, Class supercls)
  10. {
  11. monitor_locker_t lock(classInitLock);
  12. if (!supercls || supercls->isInitialized()) {
  13. // 如果父类完成初始化,则调用该方法
  14. _finishInitializing(cls, supercls);
  15. } else {
  16. // 父类未完成初始化
  17. _finishInitializingAfter(cls, supercls);
  18. }
  19. }

4)验证+initialize在调用的时候才加载

  1. @interface Person : NSObject
  2. @end
  3. @implementation Person
  4. + (void)initialize {
  5. printf("Person initialize \n");
  6. }
  7. @end
  8. int main(int argc, const char * argv[]) {
  9. @autoreleasepool {
  10. Person *p = [Person new];
  11. }
  12. return 0;
  13. }

通过实例代码打印可以知道,当Person *p = [Person new];创建时会打印日志,但是代码被注释之后,并不会做出打印。

总结:

  • +initialize方法是在main函数之后调用的;
  • +initialize方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;
  • 子类中不需要调用super方法,会自动调用父类的方法实现;调用顺序是先调用父类,在调用子类
  • +initialize只调用一次

    二、分类/扩展/协议

    1、分类

    分类可以为当前类扩展方法功能
    声明一个简单的分类:
    image.png
    .h文件中 ```objectivec

    import

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (Add)

-(void)addTest;

@end

NS_ASSUME_NONNULL_END

  1. **.m文件中**
  2. ```objectivec
  3. #import "NSObject+Add.h"
  4. @implementation NSObject (Add)
  5. -(void)addTest {
  6. NSLog(@"test 分类");
  7. }
  8. @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];

得到日志结果为
image.png
如果去除[super work];后执行结果为:
image.png
实验过程中,我们还发现是否引入Son+cate的分类文件
image.png
都会执行Son+Cate.m文件中覆盖的方法,执行结果如下:
image.png
综上得出实验的结论

  - **分类中重写父类中方法可以通过`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

执行代码,我们会发先不管我们是否引入分类头文件
image.png
最终的结果都是
image.png
由以上可以总结:

  - **分类中重写原类的方法会覆盖原类中的方法实现,不管是否引入头文件**
  - **分类中重写原类中的方法,编译器会发出警告**
  - 详细的原因需要了解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方法时打印
image.png
查看Tagert->Build Phases->Compile Sources下面如下
image.png
我们调整顺序
image.png
查看打印结果
image.png
我们会发现当我们调整加载分类文件的顺序的时候,最终执行的结果都是最后一个加载的分类文件的方法
所以可以得出结论:

  • 分类中相同方法名的方法,最终被执行的是最后一个。

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)分类底层原理

Category的本质

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
image.png
综上代码示例,也基本证实了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的使用

浅复制:不拷贝对象本身,仅仅是拷贝指向对象的指针
深复制:是直接拷贝整个对象内存到另一块内存中
image.png
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);

得到以下结果
image.png
我们可以看出,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);

image.png
我们可以看到,对可变对象进行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];

image.png
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.都需要重写forwardInvocationmethodSignatureForSelector方法

不同点:

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、可以设置代理

NSMutableDictionaryNSCache区别:

1.NSCache类有自己的自动删除策略,以确保缓存不用使用太多的系统内存。如果其他应用需要内存,则自动删除策略会从缓存中删除一些项目,从而最大限度的减少内存占用。 2.NSCache是线程安全的,我们可以从不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。其中线程安全是pthread_mutex完成的。 3.明显区别于NSMutableDictionary的是,键对象不会被retain。(键不需要实现NSCopying协议)