IOS基础使用:OC的Foundation语法(上)

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、公共用法
    • 1、加载及初始化类
    • 2、分配内存空间及初始化对象
    • 3、给对象发送消息(执行方法)
    • 4、复制对象
    • 5、获取Class
    • 6、判断方法
    • 7、重写对象的系统方法
    • 8、SharedApplication
    • 9、NSProxy
  • 二、头部声明
    • 1、常量和宏
    • 2、枚举
    • 3、属性关键字
  • 三、基本数据类型
    • 1、Null/nil
    • 2、BOOL
    • 3、NSNumber
    • 4、NSData
    • 5、CG类型
    • 6、路径
  • 四、字符串
    • 1、范围
    • 2、格式
    • 3、拷贝
    • 4、替换
    • 5、比较
    • 6、路径
    • 7、字符串转变
    • 8、字符串属性
    • 9、可变字符串
    • 10、富文本
  • 五、集合
    • 1、NSSet
    • 2、NSDictionary
    • 3、NSArray
    • 4、可变数组的实现原理
  • Demo
  • 参考文献

1、公共用法

1、加载及初始化类

// 运行时加载类或分类调用该方法, 每个类只会调用一次
+ (void)load;

// 类实例化使用前需要先初始化, 一个类调用一次, 如果子类没有实现该方法则会调用父类方法
+ (void)initialize;

load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。

load每个类只会调用一次,initialize也只调用一次,但是如果子类没有实现initialize方法则会调用父类的方法,因此作为父类的initialize方法可能会调用多次。


2、分配内存空间及初始化对象

Student *student = [Student new];
Student *student2 = [[Student alloc] init];
Student *student3 = [[Student allocWithZone:nil] init];

创建新对象时,首先调用alloc为对象分配内存空间,再调用init初始化对象,如[[NSObject alloc] init]。而new方法先给新对象分配空间然后初始化对象,因此[NSObject new]等同于[[NSObject alloc] init]。关于allocWithZone方法,官方文档解释该方法的参数是被忽略的,正确的做法是传nil或者NULL参数给它。


3、给对象发送消息(执行方法)

a、直接调用
// 调用无参无返回值方法
[student running];

// 调用有参无返回值方法
[student readingWithText:@"Hello World!"];

// 调用有参有返回值方法
NSNumber *sum = [student sumWithNum:@(2) num2:@(3)];

我们通常都采用这种直接调用的方式,给对象发消息执行方法。这种方式调用编译时会自动校验方法、参数、返回值是否正确。因此我们必须在头文件中声明方法的使用。


b、使用 performSelector 执行
// 先判断对象是否能调用方法,再执行调用方法
if ([student respondsToSelector:@selector(running)]) {
    // 调用无参无返回值方法
    [student performSelector:@selector(running)];
}

if ([student respondsToSelector:@selector(readingWithText:)]) {
    // 调用有参无返回值方法
    [student performSelector:@selector(readingWithText:) withObject:@"Hello World"];
}
if ([student respondsToSelector:@selector(sumWithNum:num2:)]) {
    // 调用有参有返回值方法
    NSNumber *sum = [student performSelector:@selector(sumWithNum:num2:) withObject:@(2) withObject:@(8)];
}

使用performSelector:是运行时系统负责去找方法,在编译时候不做任何校验;因此在使用时必须先使用respondsToSelector:检查对象是否能调用方法,否则可能出现运行崩溃。performSelector:常用于调用运行时添加的方法,即编译时不存在,但是运行时候存在的方法。另外需要注意的是performSelector:系统提供最多接受两个参数的方法,而且参数和返回都是id类型,并不支持基础数据类型(如:intfloat等)。


c、使用 IMP 指针调用
// 创建SEL
SEL runSel = @selector(running);
SEL readSel = NSSelectorFromString(@"readingWithText:");
SEL sumSel = NSSelectorFromString(@"sumWithNum:num2:");

// 调用无参无返回值方法
IMP rumImp = [student methodForSelector:runSel];
void (*runFunc)(id, SEL) = (void *)rumImp;
runFunc(student, runSel);

// 调用有参无返回值方法
IMP readImp = [[student class] instanceMethodForSelector:readSel];
void (*speakFunc)(id, SEL, NSString *) = (void *)readImp;
speakFunc(student, readSel, @"Hello World");

// 调用有参有返回值方法
IMP sumImp = [student methodForSelector:sumSel];
NSNumber *(*sumFunc)(id, SEL, NSNumber *, NSNumber *) = (void *)sumImp;
NSNumber *sum3 = sumFunc(student, sumSel, @(6), @(6));

SEL 是方法的索引。IMP是函数指针,指向方法的地址。SELIMP是一一对应的关系,因此我们可以通过修改对应关系达到运行时方法交换的目的。

创建 SEL 对象两种方法
  • 使用@selector()创建
  • 使用NSSelectorFromString()创建
获取方法 IMP 指针两种方法
  • - (IMP)methodForSelector:(SEL)aSelector; 类方法
  • + (IMP)instanceMethodForSelector:(SEL)aSelector; 实例方法

4、复制对象

copy拷贝为不可变对象,mutableCopy拷贝为可变对象。虽然copy对静态对象只是引用计数加1,但是并不影响我们对复制前后的对象进行使用。需要注意的是对于容器对象而言,这两个方法只是复制了容器本身,对容器中包含的对象只是简单的指针引用,并没有深层复制。

// 复制人物
- (instancetype)copyWithZone:(NSZone *)zone
{
    Person *person = [[Person alloc] init];
    person.firstName = self.firstName;
    person.lastName = self.lastName;
    return person;
}

// 使用复制
- (void)useCopy
{
    // 两个源数组
    NSArray *sourceArrayI = [NSArray arrayWithObjects:@"I", @"II", nil];
    NSMutableArray *sourceArrayM = [NSMutableArray arrayWithObjects:@"M", @"MM", nil];
    
    // 两个copy
    NSArray *copyArrayI = [sourceArrayI copy];
    NSArray *copyArrayM = [sourceArrayM copy];
    
    // 两个mutableCopy
    NSMutableArray *mutableArrayI = [sourceArrayI mutableCopy];
    NSMutableArray *mutableArrayM = [sourceArrayM mutableCopy];
    
    // 对象拷贝
    Person *aPerson = [[Person alloc] init];
    Person *copyPerson = [aPerson copy];
    NSLog(@"源对象为:%@",aPerson);
    NSLog(@"copy对象为:%@",copyPerson);
    NSLog(@"copy对象的姓名为:%@", [copyPerson fullName]);
    NSLog(@"sourceArrayI为:%p",sourceArrayI);
    NSLog(@"sourceArrayM为:%p",sourceArrayM);
    NSLog(@"copyArrayI为:%p",copyArrayI);
    NSLog(@"copyArrayM为:%p",copyArrayM);
    NSLog(@"mutableArrayI为:%p", mutableArrayI);
    NSLog(@"mutableArrayM为:%p", mutableArrayM);
}

输出结果为:

2020-10-20 17:10:50.942504+0800 BasicGrammarDemo[27278:4928170] 源对象为:<Person: 0x600002515320>
2020-10-20 17:10:50.942579+0800 BasicGrammarDemo[27278:4928170] copy对象为:<Person: 0x600002515340>
2020-10-20 17:10:50.942638+0800 BasicGrammarDemo[27278:4928170] copy对象的姓名为:LuoMei Bai

2022-02-16 21:30:52.354940+0800 OCDemo[30024:1349421] sourceArrayI为:0x600003232da0
2022-02-16 21:30:52.355093+0800 OCDemo[30024:1349421] sourceArrayM为:0x600003c494d0
2022-02-16 21:30:52.355205+0800 OCDemo[30024:1349421] copyArrayI为:0x600003232da0
2022-02-16 21:30:52.355325+0800 OCDemo[30024:1349421] copyArrayM为:0x600003233560
2022-02-16 21:30:52.355434+0800 OCDemo[30024:1349421] mutableArrayI为:0x600003c49800
2022-02-16 21:30:52.355545+0800 OCDemo[30024:1349421] mutableArrayM为:0x600003c49c20

5、获取Class

// 获取类
Class curClass = [student class];

// 获取父类
Class supClass = [student superclass];

6、判断方法

在OC的世界中,除了NSProxy类以外,所有的类都是NSObject的子类。在Foundation框架下,NSObjectNSProxy两个基类,定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类,这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中,声明了所有OC对象的公共方法。在NSObject协议中,有以下5个方法,是可以从Runtime中获取信息,让对象进行自我检查。

- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
  • class方法返回对象的类;
  • isKindOfClass:isMemberOfClass:方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
  • respondsToSelector:检查对象能否响应指定的消息;
  • conformsToProtocol:检查对象是否实现了指定协议类的方法;

NSObject的类中还定义了一个方法- (IMP)methodForSelector:(SEL)aSelector;这个方法会返回指定方法实现的地址IMP

a、声明两个继承关系的类
@interface Person : NSObject

- (void)run;

@end

@interface Student : Person

@end

@implementation Person

- (void)run
{
    NSLog(@"滚 能换一种说法吗? 蹿吧,孩儿。能文明一点吗?去吧,皮卡丘。能高大上一点吗?奔跑吧,兄弟。能再上档次点吗?世界这么大,你怎么不去看看。能有点诗意吗?你来人间一趟,你要看看太阳。能有点鼓动性吗?奔涌吧,后浪!");
}

@end

@implementation Student

@end

b、使用判断方法
- (void)useJudgment
{
    // 初始化对象
    Person *person = [Person new];
    Student *studentXie = [Student new];
    Student *studentFan = studentXie;

    // 判断对象是否是个代理
    if ([studentXie isProxy])
    {
        NSLog(@"student对象是个代理");
    }

    // 判断两个对象是否相等
    if ([studentXie isEqual:studentFan])
    {
        NSLog(@"studentXie对象与studentFan对象相等,两位同学可能是同一个人");
    }

    // 判断对象是否是指定类
    if ([person isKindOfClass:[Person class]])
    {
        NSLog(@"person对象是Person类,即人是人类");
    }

    // 判断对象是否是指定类或子类
    if ([studentXie isKindOfClass:[Person class]])
    {
        NSLog(@"studentXie对象是Person类的子类,谢同学再颓废没有生气也是个活生生的人呀");
    }

    // 判断某个类是否是另一个类的子类
    if ([Student isSubclassOfClass:[Person class]])
    {
        NSLog(@"Student类是Person类的子类");
    }

    // 判判断对象是否遵从协议
    if ([studentXie conformsToProtocol:@protocol(NSObject)])
    {
        NSLog(@"studentXie对象遵循NSObject协议");
    }

    // 判断类是否遵从给定的协议
    if ([Student conformsToProtocol:@protocol(NSObject)])
    {
        NSLog(@"Student类遵循NSObject协议");
    }

    // 判断对象是否能够调用给定的方法
    if ([studentXie respondsToSelector:@selector(run)])
    {
        NSLog(@"student对象可以调用run方法");
    }

    // 判断实例是否能够调用给定的方法
    if ([Student instancesRespondToSelector:@selector(run)])
    {
        NSLog(@"Student类可以调用run方法");
    }
}
输出结果
2020-10-20 16:46:46.061658+0800 BasicGrammarDemo[26898:4909800] studentXie对象与studentFan对象相等,两位同学可能是同一个人
2020-10-20 16:46:46.061769+0800 BasicGrammarDemo[26898:4909800] person对象是Person类,即人是人类
2020-10-20 16:46:46.061831+0800 BasicGrammarDemo[26898:4909800] studentXie对象是Person类的子类,谢同学再颓废没有生气也是个活生生的人呀
2020-10-20 16:46:46.061902+0800 BasicGrammarDemo[26898:4909800] Student类是Person类的子类
2020-10-20 16:46:46.061988+0800 BasicGrammarDemo[26898:4909800] studentXie对象遵循NSObject协议
2020-10-20 16:46:46.062055+0800 BasicGrammarDemo[26898:4909800] Student类遵循NSObject协议
2020-10-20 16:46:46.062127+0800 BasicGrammarDemo[26898:4909800] student对象可以调用run方法
2020-10-20 16:46:46.062196+0800 BasicGrammarDemo[26898:4909800] Student类可以调用run方法

7、重写对象的系统方法

a、重写系统方法
@interface Animal : NSObject <NSCopying>

@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSString *age;

@end

@implementation Animal

// 重写isEqual:
- (BOOL)isEqual:(id)object
{
    // 自身
    if (self == object)
    {
        return YES;
    }
    
    // 类型相同
    if ([object class] == [Animal class])
    {
        // 转化
        Animal *target = (Animal *)object;
        
        // 设置判断标准
        BOOL result = ([self.name isEqualToString:target.name] && (self.age == target.age));
        return result;
    }
    return NO;
}

// 重写hash
- (NSUInteger)hash
{
    
    NSUInteger nameHash = (self.name == nil ? 0 : [self.name hash]);
    NSUInteger ageHash = (self.age == nil ? 0 : [self.age hash]);
    
    return nameHash * 31 + ageHash;
}

// 重写description
- (NSString *)description
{
    return [NSString stringWithFormat:@"name:%@,age:%@", self.name,self.age];
}

// 重写copyWithZone
- (id)copyWithZone:(NSZone *)zone
{
    Animal *new = [[[self class] allocWithZone:zone] init];
    new.name = self.name;
    new.age = self.age;
    return new;
}

@end

b、调用方式
- (void)useCover
{
    Animal *animal = [[Animal alloc] init];
    animal.name = @"xiejiapei";
    animal.age = @"23";
    NSLog(@"重写了description后,源对象为:%@",[animal description]);
    NSLog(@"源对象hash为:%lu",(unsigned long)[animal hash]);
    
    Animal *copyAnimal = [animal copy];
    NSLog(@"copy对象为:%@",copyAnimal);
    
    if ([copyAnimal isEqual:animal])
    {
        NSLog(@"两个对象相等");
    }
}

输出结果为:

2020-11-03 10:05:35.037203+0800 FoundationDemo[93106:3989340] 重写了description后,源对象为:name:xiejiapei,age:23
2020-11-03 10:05:35.037308+0800 FoundationDemo[93106:3989340] 源对象hash为:3770145254414366752
2020-11-03 10:05:35.037378+0800 FoundationDemo[93106:3989340] copy对象为:name:xiejiapei,age:23
2020-11-03 10:05:35.037432+0800 FoundationDemo[93106:3989340] 两个对象相等

8、SharedApplication

- (void)useSharedApplication
{
    // 获得UIApplicationDelegate对象
    [[UIApplication sharedApplication] delegate];
    
    // 获得UIWindow对象
    [[UIApplication sharedApplication] keyWindow];
    
    // 打开设置界面
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
    
    // 远程的控制相关
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    
    // 不让手机休眠
    [UIApplication sharedApplication].idleTimerDisabled = YES;
   
    // 后台剩余时间
    NSTimeInterval remainTime = [[UIApplication sharedApplication] backgroundTimeRemaining];
    NSLog(@"剩余时间 = %f",remainTime);
    
    // 后台刷新的状态
    [[UIApplication sharedApplication] backgroundRefreshStatus];
    
    // 开启/关闭任务
    [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"task" expirationHandler:^{
    }];
    [[UIApplication sharedApplication] endBackgroundTask:1];
}

9、NSProxy

我们会利用runtime消息转发机制创建一个动态代理。利用这个动态代理来转发消息。这里我们会用到两个基类的另外一个神秘的类,NSProxy

NSProxy类和NSObject同为OC里面的基类,但是NSProxy类是一种抽象的基类,无法直接实例化,可用于实现代理模式。它通过实现一组经过简化的方法,代替目标对象捕捉和处理所有的消息。NSProxy类也同样实现了NSObject的协议声明的方法,而且它有两个必须实现的方法。

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");

另外还需要说明的是,NSProxy类的子类必须声明并实现至少一个init方法,这样才能符合OC中创建和初始化对象的惯例。Foundation框架里面也含有多个NSProxy类的具体实现类。NSDistantObject类定义其他应用程序或线程中对象的代理类。NSProtocolChecker类定义对象,使用这对象可以限定哪些消息能够发送给另外一个对象。


二、头部声明

1、常量和宏

const NSString *constString = @"谢佳培";
NSString *const stringConst = @"甜的";

// 常用宏定义的颜色、字体字号
#define ROW_SIZE 20
#define Blue  [UIColor colorWithRed:(0x##r)/255.0 green:(0x##g)/255.0 blue:(0x##b)/255.0 alpha:1]

// 宏定义获取rootViewController
#define RootVC [UIApplication sharedApplication].delegate.window.rootViewController

// 宏定义获取当前的界面
#define TopVC ([RootVC isKindOfClass:[UITabBarController class]]?[((UITabBarController *)RootVC).selectedViewController topViewController]:RootVC)
  • 宏在预编译时处理(宏在编译开始之前就会被替换),而const会在编译时被处理
  • #define宏没有类型,宏不做任何类型检查,不会报编译错误,只是替换,而const常量有具体的类型,会编译检查,会报编译错误
  • 宏能定义一些函数和方法,而const不能
  • 使用大量宏,容易造成编译时间久,因为每次都需要重新替换
  • 宏定义时不分配内存,变量定义时分配内存
  • 预编译期间进行宏替换,分配内存,再进行宏替换,又一次分配内存,内存中有若干个拷贝。const doulbe Pi —— double i=Pi; 此时为Pi分配内存,以后不再分配!只有一份拷贝(因为是全局的只读变量,存在静态区)

开发过程中的经常需要使用一些全局的常量,便于方法之间的参数传递和类型确定等,经常使用到的有宏定义,const常量以及枚举等,我们经常使用宏定义来进行全局常量的定义,但是是不是所有的全局常量宏定义都是最好的选择呢?宏定义是我们最经常使用的全局常量定义方法,使用非常便捷,在使用过程中需要注意以下事项:

  • 宏定义是预编译处理,在程序开始编译之前就已经完成
  • 宏定义只是进行字符替换,并没有优先级限制,比如#define add(x,y)x + y。在使用时如果使用了add(3,4)*add(3,4),宏定义替换之后的结果是3+4*3+4=19,而不是(3+4)*(3+4) =49,一定要注意!!!
  • 宏定义不做类型的语法检查,宏定义中宏的所有参数都是没有类型的,可能会存在重复定义的问题,如果重复定义了相同名称的宏,程序只是会给出警告,而不会影响编译,所以如果你定义了同名称两个宏,而替换方式不一样的话,是很难发现的。
  • 宏定义不是变量定义,所以并不分配内存
  • 宏定义只是字符串替换,结尾不需要使用使用分号结束
const

const是变量修饰符号,在定义全局变量的时候,应该优先考虑static加上const来替换宏定义常量,原因有以下几点:

  • 当项目比较大的时候,过多的宏定义会使项目的编译变得缓慢;
  • const修饰变量在编译期间会进行语法检查,可以防止意外的类型错误,以及重复定义,可以使定义更加安全;
  • const修饰变量时,该变量不允许改变,可以防止常量被意外修改;
  • 在外部需要使用的地方,只需要使用extern关键字扩大变量的作用域,使得变量的访问更加灵活。

对于const的使用,需要注意以下事项:

  • const修饰普通变量,表示该变量为常量,使用过程中不允许修改;
  • const修饰指针类变量;含有地址的变量时,以*分为左右两部分,左侧部分含有const修饰符号时,内容不能修改;右侧部分有const修饰时,该变量地址不能改变,如果两侧都有const修饰,则地址和内容都不可变;
  • const可以修饰形参,被修饰的参数在方法内部不能被修改;
  • const修饰的变量的作用域,可以通过extern进行扩展,不需要进行初始化和分配空间,只是告诉编译器该变量会在其他地方进行定义。

2、枚举

  • typedef是类型替换,直接参与编译,而define只是简单的文本替换
  • define写在方法/函数中则作用域从写的地方开始有效,直至使用#undef(不写此指令则后面一直有效),typedef写在方法/函数中则作用域只在此方法/函数中有效

block取别名MyBlock

typedef void(^MyBlock) (int a,int b);
C语言格式
// 给NSTimeInterval取别名为MyTime
typedef NSTimeInterval MyTime;

// c语言格式,给Person结构体取别名为MyPerson
// 使用:MyPerson p = {"jack"};
typedef struct Person {
    char *name;
} MyPerson;

// c语言格式,给Gender枚举取别名为MyGender
// 使用:MyGender g = Man;
typedef enum Gender {
    Man,
    Woman
} MyGender;
OC语言格式

对于互斥(不能同时具有两种状态)表示的多种整型变量,一般推荐使用NS_ENUM来表示,使用很简洁,也很方便。比如文本在竖直方向的对齐状态。这种表示的优点是可以将多种实用普通整型数字不易区分的状态,用易于区分的字符串表示,见名知意,不易出错,方便表示。

typedef NS_ENUM(NSInteger, NumberType)
{
     NumberTypeInt = 0,
     NumberTypeFloat = 1,
     NumberTypeDouble = 2
};
NumberType type = NumberTypeInt;

但是这种表示方法的不足之处就是各种状态之间不能并存,但是有些时候我们需要使用多种状态同时存在,比如选择了一个通信录里的联系人需要获取的信息类型。

如果你需要同时选定姓名和联系方式,只需要定义_enums = APContactFieldFirstName|APContactFieldLastName|APContactFieldPhones就可以了,
那么问题来了,对于这种可以同时选择多个属性的枚举类型,如何确定某个枚举值(比如APContactFieldFirstName)是不是包含在选定枚举(比如_enums)中呢?
只需要将(APContactFieldFirstName)&(_enums)运算,如果其值为真,则包含,否则为假。

typedef NS_OPTIONS(NSUInteger, TMEnumTest)
{
    TMEnumTestOne     = 0,          // 0
    TMEnumTestTwo     = 1 << 0,     // 1
    TMEnumTestThree   = 1 << 1,     // 2
    TMEnumTestFour    = 1 << 2,     // 4
};
TMEnumTest test = TMEnumTestTwo | TMEnumTestThree; 


typedef NS_OPTIONS(NSUInteger, LifeRoleOptions)
{
    LifeRoleOptionsFather =   1UL << 0,
    LifeRoleOptionsSon = 1UL << 1,
    LifeRoleOptionsHusband = 1UL << 3,
};
LifeRoleOptions lifeRole = LifeRoleOptionsFather | LifeRoleOptionsSon;
使用方式
- (void)useTypedef
{
    // 添加TMEnumTestFour到test中
    test += TMEnumTestFour;
    // 将TMEnumTestThree从test中去除
    test -= TMEnumTestThree;
    // 判断 TMEnumTestFour枚举 是否被包含
    if (test & TMEnumTestFour)
    {
        NSLog(@"数字是:四");
    }
    // 判断 TMEnumTestThree枚举 是否被包含
    if (test & TMEnumTestThree)
    {
        NSLog(@"数字是:三");
    }
    
    if (lifeRole & LifeRoleOptionsFather)
    {
        NSLog(@"人生角色:父亲");
    }
    
    if (lifeRole & LifeRoleOptionsHusband)
    {
        NSLog(@"人生角色:丈夫");
    }
}

输出结果

2020-10-20 15:24:20.763915+0800 BasicGrammarDemo[25511:4841344] 数字是:四
2020-10-20 15:24:20.764021+0800 BasicGrammarDemo[25511:4841344] 人生角色:父亲

3、属性关键字

类型
  • atomic:系统默认,声明的属性保证赋值和获取是线程安全的,不保证添加和移除是线程安全的(如数组)
  • noatomic:如果属性经常被使用的话通常声明为此属性关键字
  • retain/strong
  • assign/unsafe_unretained:修饰基本数据类型,如intBool。修饰对象类型时候,不改变其引用计数。对象释放后,会产生悬垂指针,继续访问会产生内存泄露
  • weak:不改变被修饰对象的引用计数,对象释放后,自动置为nil
  • copy浅拷贝和深拷贝的区别:是否开辟了新的内存空间,是否影响了引用计数。可变对象copy 后是NSArray 不可以调用add delete等方法。只有不可变对象的copy是浅拷贝,其他都是深拷贝

属性声明
//默认关键字
@property (atomic, strong, readwrite) NSString *firstName;

@property (readonly, getter=isShit) BOOL shit;

@property (nonatomic, strong) NSArray *vcTitles;
@property (nonatomic, strong) NSArray<NSArray *> *pickerData;


@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) personBlock runFinishBlock;

@property (nonatomic, weak) id<WorkProtocol> delegate;
@property (nonatomic, weak) id<CountToolDataSource> dataSource;

@property (nonatomic, assign) int age;
@property (nonatomic, assign) BOOL bodyTextChanged;
@property (nonatomic, assign) NSTimeInterval created;

使用演示
@interface PersonClass : NSObject

@property (nonatomic, strong) NSObject *objStrong;
@property (nonatomic, weak) NSObject *objWeak;
@property (nonatomic, assign) NSObject *objAssign;
@property (nonatomic, copy) NSString *objCopy;

@end

// 使用属性关键字
- (void)useAttributeKey
{
    PersonClass *aPerson = [[PersonClass alloc] init];
    aPerson.objStrong = [[NSObject alloc] init];
    
    // 系统会警告,但不会报错
    aPerson.objWeak = [[NSObject alloc] init];
    aPerson.objAssign = [[NSObject alloc] init];
    
    NSMutableString *testStr = [[NSMutableString alloc] initWithString:@"我知道什么呢?"];
    aPerson.objCopy = testStr;
    [aPerson setObjCopy:testStr];
    [testStr appendString:@"什么都知道"];
    
    // 正确的方式
    NSLog(@"objStrong %@", aPerson.objStrong);
    
    // weak会释放为null
    NSLog(@"objWeak %@", aPerson.objWeak);
    
    // Assign会崩溃
    // NSLog(@"objAssign %@", aPerson.objAssign);
    
    // Copy后,原字符串改变对新字符串无影响
    NSLog(@"testStr %@", testStr);
    NSLog(@"objCopy %@", aPerson.objCopy);
}
输出结果
2020-10-20 15:38:17.996696+0800 BasicGrammarDemo[25777:4854926] objStrong <NSObject: 0x60000006c1b0>
2020-10-20 15:38:17.996794+0800 BasicGrammarDemo[25777:4854926] objWeak (null)
2020-10-20 15:38:17.996872+0800 BasicGrammarDemo[25777:4854926] testStr 我知道什么呢?什么都知道
2020-10-20 15:38:17.996936+0800 BasicGrammarDemo[25777:4854926] objCopy 我知道什么呢?

三、基本数据类型

1、Null/nil

// 默认值: NO/0/nil
Person *aPerson = nil;
aPerson.age // 0

// 用在不能在数组和词典对象中放入nil ,又确实需要一个特殊的对象来表示空值
+ (NSNull *) null 
Nil

nil的定义是null pointer to object-c object,指的是一个OC对象指针为空,本质就是(id)0,是OC对象的字面0值。不过这里有必要提一点就是OC中给空指针发消息不会崩溃的语言特性,原因是OC的函数调用都是通过objc_msgSend进行消息发送来实现的,相对于C和C++来说,对于空指针的操作会引起Crash的问题,而objc_msgSend会通过判断self来决定是否发送消息,如果selfnil,那么直接返回,所以不会出现问题。

Class class = [NSString class];
if (class != Nil) {
    NSLog(@"class name: %@", class);
}·
NSNull

NSNull包含了唯一一个方法+(NSNull)null[NSNull null]是一个对象,用来表示零值的单独的对象。NSNull主要用在不能使用nil的场景下,比如NSMutableArray是以nil作为数组结尾判断的,所以如果想插入一个空的对象就不能使用nilNSMutableDictionary也是类似,我们不能使用nil作为一个object,而要使用NSNull

NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
NSString *nameOne = @"Allen";
NSString *nameTwo = [NSNull null]; //not use nil
NSString *nameThree = @"Tom";
[dictionary setObject:nameOne forKey:@"nameOne"];
[dictionary setObject:nameTwo forKey:@"nameTwo"];
[dictionary setObject:nameThree forKey:@"nameThree"];
NSLog(@"names: %@", dictionary);
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:nameOne];
[array addObject:nameTwo];
[array addObject:nameThree];
NSLog(@"names : %@", array);

2、BOOL

BOOL hidden = YES;
BOOL bigger = 0 > 1;

if (-1)
{
    NSLog(@"not 0 = YES");
}

NSObject *boolObj = [[NSObject alloc] init];
if (boolObj)
{
    NSLog(@"not nil = YES");
}

输出结果为:

2020-10-21 11:40:25.715805+0800 BasicGrammarDemo[31446:5105441] not 0 = YES
2020-10-21 11:40:25.715873+0800 BasicGrammarDemo[31446:5105441] not nil = YES

3、NSNumber

// 用语法糖进行声明,可转换为多种数据类型
NSNumber *intNumber = @(-1);
NSNumber *boolNumber = @(YES);
NSNumber *charNumber = @('A');
NSLog(@"int(-1)值:%@,bool(YES)值:%@ ,char(A)值:%@", intNumber, boolNumber, charNumber);
NSLog(@"字面A的charValue值:%d,stringValue值:%@,intValue值:%d", charNumber.charValue, charNumber.stringValue, charNumber.intValue);

输出结果为:

2020-10-21 11:44:24.847241+0800 BasicGrammarDemo[31501:5108050] int(-1)值:-1,bool(YES)值:1 ,char(A)值:65
2020-10-21 11:44:24.847298+0800 BasicGrammarDemo[31501:5108050] 字面A的charValue值:65,stringValue值:65,intValue值:65

4、NSData

NSString *dataString = @"XieJiaPei";
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"string(XieJiaPei) 转化为 data:%@", data);

NSData *zeroData = [NSData data];
NSLog(@"空数据为:%@", zeroData);

NSMutableData *appendData = [zeroData mutableCopy];
[appendData appendData:data];
NSLog(@"在空数据后追加数据后结果为:%@", appendData);

输出结果为:

2020-10-21 13:44:31.226384+0800 BasicGrammarDemo[32605:5160936] string(XieJiaPei) 转化为 data:{length = 9, bytes = 0x5869654a6961506569}
2020-10-21 13:44:31.226465+0800 BasicGrammarDemo[32605:5160936] 空数据为:{length = 0, bytes = 0x}
2020-10-21 13:44:31.226546+0800 BasicGrammarDemo[32605:5160936] 在空数据后追加数据后结果为:{length = 9, bytes = 0x5869654a6961506569}

5、CG类型

- (void)useCG
{
    // 状态栏高度
    CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
    NSLog(@"状态栏高度:%f",statusBarHeight);
    
    // 如何把一个CGPoint存入数组里
    CGPoint point = CGPointMake(0, 0);
    NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:NSStringFromCGPoint(point), nil];
    point = CGPointFromString(array[0]);
    NSLog(@"如何把一个CGPoint存入数组里: %@",array);
}

输出结果为:

2020-10-30 13:40:44.198593+0800 FoundationDemo[42880:1608020] 状态栏高度:44.000000
2020-10-30 13:40:44.198740+0800 FoundationDemo[42880:1608020] 如何把一个CGPoint存入数组里: (
    "{0, 0}"
)

四、字符串

1、范围

a、NSMakeRange
NSRange range = NSMakeRange(2, 4);
NSString *fullString = @"abcdefghijk";
NSString *subString = [fullString substringWithRange:range];
NSLog(@"原字符串为abcdefghijk,其(2,4)范围内的子字符串为:%@", subString);

输出结果为:

2020-10-21 13:52:18.515144+0800 BasicGrammarDemo[32785:5169598] 原字符串为abcdefghijk,其(2,4)范围内的子字符串为:cdef

b、substringToIndex
NSString *intString = @"456789";
NSString *charString = @"abcdef";
NSString *chineseString = @"中文哈哈哈哈";

NSString *subIntString = [intString substringToIndex:2];
NSString *subCharString = [charString substringToIndex:2];
NSString *subChineseString = [chineseString substringToIndex:2];

NSLog(@"substringToIndex:2,456789的子串为:%@,abcdef的子串为:%@,中文哈哈哈哈的子串为:%@", subIntString,subCharString,subChineseString);

输出结果为:

2020-10-21 13:57:36.238543+0800 BasicGrammarDemo[32871:5174143] substringToIndex:2,456789的子串为:45,abcdef的子串为:ab,中文哈哈哈哈的子串为:中文

c、substringFromIndex
NSString *subFromString = [chineseString substringFromIndex:2];
NSLog(@"原字符串为中文哈哈哈哈,FromIndex:2范围内的子字符串为:%@", subFromString);

输出结果为:

2020-10-21 14:14:07.313580+0800 BasicGrammarDemo[33183:5188958] 原字符串为中文哈哈哈哈,FromIndex:2范围内的子字符串为:哈哈哈哈

2、格式

a、stringWithFormat
NSString *stringWithFormat = [NSString stringWithFormat:@"字符串为: %@, 两位小数的浮点数为: %1.2f",@"XieJiaPei", 31415.9265];
NSLog(@"带格式的字符串 = %@", stringWithFormat);

输出结果为:

2020-10-21 14:04:51.764665+0800 BasicGrammarDemo[33017:5180791] 带格式的字符串 = 字符串为: XieJiaPei, 两位小数的浮点数为: 31415.93

b、stringByAppendingFormat
NSNumber *number = @12345;
NSDictionary *dictionary = @{@"date": [NSDate date]};
NSString *baseString = @"Test: ";
NSString *stringByAppendingFormat = [baseString stringByAppendingFormat:@"数字: %@, 字典: %@", number, dictionary];
NSLog(@"追加格式字符串 = %@", stringByAppendingFormat);

输出结果为:

2020-10-21 14:09:38.901710+0800 BasicGrammarDemo[33102:5185253] 追加格式字符串 = Test: 数字: 12345, 字典: {
    date = "2020-10-21 06:09:38 +0000";
}

3、拷贝

NSString *intString = @"456789";
NSString *charString = @"abcdef";
NSString *chineseString = @"中文哈哈哈哈";

NSString *copyIntString = [intString copy];
NSString *copyCharString = [charString copy];
NSString *copyChineseString = [chineseString copy];

NSLog(@"复制后的字符串,%@,%@,%@", copyIntString,copyCharString,copyChineseString);

输出结果为:

2020-10-21 14:01:51.381827+0800 BasicGrammarDemo[32960:5177962] 复制后的字符串,456789,abcdef,中文哈哈哈哈

4、替换

NSString *chineseString = @"中文哈哈哈哈";

NSString *replaceChineseString = [chineseString stringByReplacingOccurrencesOfString:@"哈" withString:@"好"];
NSLog(@"原字符串为中文哈哈哈哈,替换后为:%@", replaceChineseString);

NSString *pureChineseString = [chineseString stringByReplacingOccurrencesOfString:@"哈" withString:@""];
NSLog(@"原字符串为中文哈哈哈哈,用替换的方式进行删除后为:%@", pureChineseString);

输出结果为:

2020-10-21 14:18:03.822172+0800 BasicGrammarDemo[33238:5192253] 原字符串为中文哈哈哈哈,替换后为:中文好好好好
2020-10-21 14:18:03.822240+0800 BasicGrammarDemo[33238:5192253] 原字符串为中文哈哈哈哈,用替换的方式进行删除后为:中文

5、比较

NSString *abc = @"abc";
NSString *aBc = @"aBc";
NSString *aB = @"aB";
NSComparisonResult abcOrder = [abc compare:aBc];
NSComparisonResult aBOrder = [aB compare:aBc];
NSLog(@"abc compare:aBc的结果为:%ld,即abc > aBc",(long)abcOrder);
NSLog(@"aB compare:aBc的结果为:%ld,即aB < aBc",(long)aBOrder);

// 不区分大小写进行比较
NSLog(@"abc和aBc不区分大小写进行比较:%ld",(long)[abc caseInsensitiveCompare:aBc]);

输出结果为:

2020-10-21 14:22:18.913094+0800 BasicGrammarDemo[33347:5197161] abc compare:aBc的结果为:1,即abc > aBc
2020-10-21 14:22:18.913165+0800 BasicGrammarDemo[33347:5197161] aB compare:aBc的结果为:-1,即aB < aBc
2021-03-14 13:40:26.201521+0800 FoundationDemo[30079:5574788] abc和aBc不区分大小写进行比较:0

6、路径

a、hasPrefix / hasSuffix
if ([aBc hasSuffix:@"Bc"])
{
    NSLog(@"aBc的后缀为Bc");
}

if ([aBc hasPrefix:@"aB"])
{
    NSLog(@"aBc的前缀为aB");
}

输出结果为:

2020-10-21 14:25:09.571763+0800 BasicGrammarDemo[33434:5201268] aBc的后缀为Bc
2020-10-21 14:25:09.571853+0800 BasicGrammarDemo[33434:5201268] aBc的前缀为aB

b、stringByAppendingPathComponent
NSString *homePath = NSHomeDirectory();// 能够访回当前用户的主目录
NSString *workPath = [homePath stringByAppendingPathComponent:@"LuckCoffee"];
NSLog(@"瑞幸咖啡的存储路径为:%@", workPath);

输出结果为:

2020-10-21 14:28:37.083043+0800 BasicGrammarDemo[33522:5205708] 瑞幸咖啡的存储路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/8D07F1D9-24C5-45A2-8AA2-3AB9EF752D6F/data/Containers/Data/Application/202BCFAD-50E8-499B-833C-93426D5EAB1E/LuckCoffee

c、lastPathComponent/pathExtension
NSString *filePath = @"/tmp/image/cat.tiff";
NSLog(@"路径为:/tmp/image/cat.tiff,最后一个部分为:%@,文件的扩展名为:%@",[filePath lastPathComponent],[filePath pathExtension]);

输出结果为:

2020-10-21 15:14:28.535638+0800 BasicGrammarDemo[34137:5237242] 路径为:/tmp/image/cat.tiff,最后一个部分为:cat.tiff,文件的扩展名为:tiff

7、字符串转变

a、componentsSeparatedByString / componentsJoinedByString
// 分割
NSArray *pathArray = [homePath componentsSeparatedByString:@"/"];
NSLog(@"NSHomeDirectory的路径为:%@", homePath);
NSLog(@"路径转变为数组后为:%@", pathArray);

// 拼接
NSString *joinPath = [pathArray componentsJoinedByString:@"/"];
NSLog(@"将数组拼接成为路径:%@",joinPath);

输出结果为:

2020-10-21 15:25:32.289578+0800 BasicGrammarDemo[34365:5248464] NSHomeDirectory的路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/8D07F1D9-24C5-45A2-8AA2-3AB9EF752D6F/data/Containers/Data/Application/E1A549FF-396C-402B-934A-35BB04F60A1A
2020-10-21 15:25:32.289679+0800 BasicGrammarDemo[34365:5248464] 路径转变为数组后为:(
    "",
    Users,
    xiejiapei,
    Library,
    Developer,
    CoreSimulator,
    Devices,
    "8D07F1D9-24C5-45A2-8AA2-3AB9EF752D6F",
    data,
    Containers,
    Data,
    Application,
    "E1A549FF-396C-402B-934A-35BB04F60A1A"
)
2020-10-21 15:25:32.289762+0800 BasicGrammarDemo[34365:5248464] 将数组拼接成为路径:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/8D07F1D9-24C5-45A2-8AA2-3AB9EF752D6F/data/Containers/Data/Application/E1A549FF-396C-402B-934A-35BB04F60A1A

b、initWithData
NSString *name = @"XieJiaPei";

// 将NSString转化为NSData
NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];

// 用存储在data中的二进制数据来初始化NSString对象
NSString *dataName = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"用存储在data中的二进制数据来初始化NSString对象,结果为:%@",dataName);

输出结果为:

2020-10-21 15:14:28.536003+0800 BasicGrammarDemo[34137:5237242] 用存储在data中的二进制数据来初始化NSString对象,结果为:XieJiaPei

c、把NSArray集合转换为格式字符串
NSArray *array = @[@"xie",@"jia",@"pei",@"fan",@"yi",@"cheng",@"lin",@"feng",@"mian"];
NSMutableString *result = [NSMutableString stringWithString:@"["];
for (id object in array)
{
    [result appendString:[object description]];
    [result appendString:@","];
}
// 去掉字符串最后的两个字符
[result deleteCharactersInRange:NSMakeRange(result.length - 2, 2)];
[result appendString:@"]"];
NSLog(@"把NSArray集合转换为字符串:%@",result);

输出结果为:

2020-10-30 16:49:02.894530+0800 FoundationDemo[46760:1747913] 把NSArray集合转换为字符串:[xie,jia,pei,fan,yi,cheng,lin,feng,mia]

8、字符串属性

// 字符串对象内部使用Unicode 编码,返回C语言字符串的指针
NSLog(@"Unicode 编码:%s",[name UTF8String]);

// 小写与大写
NSLog(@"原字符串为:XieJiaPei,小写结果为:%@",[name lowercaseString]);
NSLog(@"原字符串为:XieJiaPei,大写结果为::%@",[name uppercaseString]);
NSLog(@"原字符串为:XieJiaPei,首字母变为大写结果为::%@",[name capitalizedString]);

// 分别被用来把NSString类型的字符串转为float、int、NSinteger和BOOL类型的数值
NSLog(@"原字符串为:414.5678,floatValue值为:%f",[@"414.5678" floatValue]);
NSLog(@"原字符串为:512,intValue值为:%d",[@"512" intValue]);
NSLog(@"原字符串为:1024,integerValue值为:%ld",(long)[@"1024" integerValue]);
NSLog(@"原字符串为:OK,boolValue值为:%ld",(long)[@"2" boolValue]);

输出结果为:

2020-10-21 15:16:56.468275+0800 BasicGrammarDemo[34195:5239924] Unicode 编码:XieJiaPei
2020-10-21 15:16:56.468392+0800 BasicGrammarDemo[34195:5239924] 原字符串为:XieJiaPei,小写结果为:xiejiapei
2020-10-21 15:16:56.468487+0800 BasicGrammarDemo[34195:5239924] 原字符串为:XieJiaPei,大写结果为::XIEJIAPEI
2020-10-21 15:16:56.468600+0800 BasicGrammarDemo[34195:5239924] 原字符串为:XieJiaPei,首字母变为大写结果为::Xiejiapei
2020-10-21 15:16:56.468715+0800 BasicGrammarDemo[34195:5239924] 原字符串为:414.5678,floatValue值为:414.567810
2020-10-21 15:16:56.468827+0800 BasicGrammarDemo[34195:5239924] 原字符串为:512,intValue值为:512
2020-10-21 15:16:56.468971+0800 BasicGrammarDemo[34195:5239924] 原字符串为:1024,integerValue值为:1024
2020-10-21 15:16:56.469081+0800 BasicGrammarDemo[34195:5239924] 原字符串为:OK,boolValue值为:1

9、可变字符串

// 随着字符串的变化而自动扩展内存,所以capacity不需要非常精密
NSMutableString* mutableString = [[NSMutableString alloc] initWithCapacity:20];

// 在原来的字符串尾部添加,返回void,不产生新字符串
[mutableString appendString:@"所谓平庸"];

// 在原来的字符串尾部添加格式化字符串
[mutableString appendFormat:@"是在于认清%i个人的限度,而安于这个限度。",1];

// 插入字符串
[mutableString insertString:@"," atIndex:4];

// 替换
[mutableString replaceCharactersInRange:NSMakeRange(2, 2) withString:@"幸福"];

NSLog(@"可变字符串为:%@",mutableString);

输出结果为:

2020-10-21 15:29:30.122108+0800 BasicGrammarDemo[34466:5253526] 可变字符串为:所谓幸福,是在于认清1个人的限度,而安于这个限度。

10、富文本

下划线
NSString *singleLineText = @"Single Line";

// 下划线
NSAttributedString *singleLineTextAttr = [[NSAttributedString alloc] initWithString:singleLineText attributes:@{NSUnderlineStyleAttributeName: @(1)}];

UILabel *singleLineLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 100, 100, 50)];
singleLineLabel.attributedText = singleLineTextAttr;
[self.view addSubview:singleLineLabel];

五、集合

1、NSSet

a、集合
NSSet *set = [NSSet setWithObjects:@"xie",@"jia",@"pei", nil];
NSSet *newSet = [NSSet setWithSet:set];
NSSet *differenceSet = [NSSet setWithObjects:@"xie",@"liu",@"ying", nil];
NSSet *deepCopySet = [[NSSet alloc] initWithSet:set copyItems:YES];
NSLog(@"如果是深拷贝,所有元素必须符合NSCoping协议,拷贝后集合为:%@",deepCopySet);

NSLog(@"集合中元素个数:%lu",(unsigned long)[set count]);

set = [set setByAddingObject:@"love"];
NSLog(@"将添加单个元素后生成的新集合赋给set:%@",set);

set = [set setByAddingObjectsFromSet:newSet];
NSLog(@"添加多个元素,相当于并集:%@",set);

if ([set intersectsSet:differenceSet])
{
    NSLog(@"有交集");
}

if ([set isSubsetOfSet:differenceSet])
{
    NSLog(@"有子集");
}

if ([set containsObject:@"xie"])
{
    NSLog(@"集合中包含元素xie");
}

NSLog(@"随取元素:%@",[set anyObject]);

NSSet *filteredSet = [set objectsPassingTest:^BOOL(id  _Nonnull obj, BOOL * _Nonnull stop){
    return ([obj isEqualToString:@"xie"]);
}];
NSLog(@"过滤集合:%@",filteredSet);

输出结果为:

2021-03-14 14:36:14.281893+0800 FoundationDemo[30601:5621429] 如果是深拷贝,所有元素必须符合NSCoping协议,拷贝后集合为:{(
    xie,
    jia,
    pei
)}
2021-03-14 14:36:14.282051+0800 FoundationDemo[30601:5621429] 集合中元素个数:3
2021-03-14 14:36:14.282181+0800 FoundationDemo[30601:5621429] 将添加单个元素后生成的新集合赋给set:{(
    jia,
    xie,
    pei,
    love
)}
2021-03-14 14:36:14.282299+0800 FoundationDemo[30601:5621429] 添加多个元素,相当于并集:{(
    jia,
    xie,
    pei,
    love
)}
2021-03-14 14:36:14.282429+0800 FoundationDemo[30601:5621429] 有交集
2021-03-14 14:36:14.282536+0800 FoundationDemo[30601:5621429] 集合中包含元素xie
2021-03-14 14:36:14.282633+0800 FoundationDemo[30601:5621429] 随取元素:jia
2021-03-14 14:36:14.282741+0800 FoundationDemo[30601:5621429] 过滤集合:{(
    xie
)}

b、可变集合
// 添加
NSArray *array = @[@"xie",@"jia",@"pei"];
NSMutableSet *set = [NSMutableSet setWithCapacity:10];
[set addObjectsFromArray:array];
NSSet *newSet = [NSSet setWithSet:set];

// 删除
[set removeObject:@"xie"];
NSLog(@"删除元素后,新set为:%@",set);

// 并集
[set unionSet:newSet];
NSLog(@"set取并集为:%@",set);

// 交集
[set intersectsSet:newSet];
NSLog(@"set取交集为:%@",set);

// 差集
[set minusSet:newSet];
NSLog(@"set取差集为:%@",set);

输出结果为:

2020-11-03 09:37:00.269452+0800 FoundationDemo[92558:3964819] 删除元素后,新set为:{(
    jia,
    pei
)}
2020-11-03 09:37:00.269550+0800 FoundationDemo[92558:3964819] set取并集为:{(
    xie,
    jia,
    pei
)}
2020-11-03 09:37:00.269630+0800 FoundationDemo[92558:3964819] set取交集为:{(
    xie,
    jia,
    pei
)}
2020-11-03 09:37:00.269690+0800 FoundationDemo[92558:3964819] set取差集为:{(
)}

c、带重复次数的集合
NSCountedSet *set = [NSCountedSet setWithObjects:@"xie",@"jia", nil];
[set addObject:@"xie"];
[set addObject:@"pei"];
NSLog(@"带有重复次数的set为:%@",set);
NSLog(@"xie这个字符串出现的次数为:%lu",(unsigned long)[set countForObject:@"xie"]);

输出结果为:

2020-11-03 09:40:29.104474+0800 FoundationDemo[92640:3968983] 带有重复次数的set为:<NSCountedSet: 0x6000009692c0> (xie [2], jia [1], pei [1])
2020-11-03 09:40:29.104555+0800 FoundationDemo[92640:3968983] xie这个字符串出现的次数为:2

2、NSDictionary

a、创建字典并取值
PersonClass *person = [[PersonClass alloc] init];

// 通过语法糖创建字典
NSDictionary *dict = @{@"name": @"XieJiaPei", @"age": @22, @4: @5, person: @"FanYiCheng"};
NSLog(@"原始字典为:%@",dict);

// 从字典中取值
NSLog(@"字典中的姓名为:%@,年龄为:%@,倘若没有Key值为:%@",dict[@"name"],[dict objectForKey:@"age"],dict[@"noKey"]);

// 通过初始化方法创建字典
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: @"XieJiaPei",@"name",@22,@"age",person,@"FanYiCheng",nil];
NSLog(@"key-value对数量: %lu",(unsigned long)[dictionary count]);
NSLog(@"allKey: %@",[dictionary allKeys]);
NSLog(@"allValues: %@",[dictionary allValues]);
NSLog(@"key不同,值相同,这种情况的所有key为: %@",[dictionary allKeysForObject:@"XieJiaPei"]);

输出结果为:

2020-10-30 17:34:07.613893+0800 FoundationDemo[47518:1783828] 原始字典为:{
    age = 22;
    "<PersonClass: 0x600002828300>" = (null);
    name = XieJiaPei;
    4 = 5;
}
2020-10-30 17:34:07.613982+0800 FoundationDemo[47518:1783828] 字典中的姓名为:XieJiaPei,年龄为:22,倘若没有Key值为:(null)
2020-10-30 17:34:07.614057+0800 FoundationDemo[47518:1783828] key-value对数量: 3
2020-10-30 17:34:07.614140+0800 FoundationDemo[47518:1783828] allKey: (
    name,
    age,
    FanYiCheng
)
2020-10-30 17:34:07.614212+0800 FoundationDemo[47518:1783828] allValues: (
    XieJiaPei,
    22,
    "<PersonClass: 0x600002828980>"
)
2020-10-30 17:34:07.614295+0800 FoundationDemo[47518:1783828] key不同,值相同,这种情况的所有key为: (
    name
)

b、遍历字典
PersonClass *person = [[PersonClass alloc] init];
NSDictionary *dict = @{@"name": @"XieJiaPei", @"age": @22, @4: @5, person: @"FanYiCheng"};

// 使用指定代码块来迭代执行该集合中所有key-value对
[dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop)
{
    NSLog(@"key的值为:%@,value的值:%@", key, obj);
}];

输出结果为:

2020-10-30 17:40:27.704477+0800 FoundationDemo[47616:1788769] key的值为:age,value的值:22
2020-10-30 17:40:27.704543+0800 FoundationDemo[47616:1788769] key的值为:<PersonClass: 0x600001435f80>,value的值:FanYiCheng
2020-10-30 17:40:27.704610+0800 FoundationDemo[47616:1788769] key的值为:name,value的值:XieJiaPei
2020-10-30 17:40:27.704690+0800 FoundationDemo[47616:1788769] key的值为:4,value的值:5

c、写入文件
// 我发现要写入plist文件,key必须为string类型
NSDictionary *dict = @{@"name": @"XieJiaPei", @"age": @22, @"4": @5};

NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"jia.plist"];

// 将路径转换为本地url形式
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];

// writeToURL 的好处是,既可以写入本地url也可以写入远程url,苹果推荐使用此方法写入plist文件
if ( [dict writeToURL:fileUrl atomically:YES] )
{
    NSLog(@"成功写入文件,路径为:%@",filePath);
}

NSDictionary *dictionaryFromFile = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"从文件中读取到的字典为:%@",dictionaryFromFile);

输出结果为:

2020-10-30 17:58:34.469548+0800 FoundationDemo[47972:1807344] 成功写入文件,路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/4E4809CA-E567-4D3A-8ADE-790075200303/data/Containers/Data/Application/62175E05-6FFD-4EEF-A872-3C08B6FBACB4/Documents/jia.plist
2020-10-30 17:58:34.469723+0800 FoundationDemo[47972:1807344] 从文件中读取到的字典为:{
    4 = 5;
    age = 22;
    name = XieJiaPei;
}

d、字典排序
NSDictionary *numberDict = @{@"Xie": @22, @"Liu": @18, @"Zou": @19};

// compare:
NSArray *newKeys = [numberDict keysSortedByValueUsingSelector:@selector(compare:)];
NSLog(@"集合元素自身的排序方法(compare:),字典通过比较值将key排序后为:%@", newKeys);

// block:
NSDictionary *stringDict = @{@"Xie": @"apple", @"Liu": @"Banana", @"Zou": @"watermelon"};
newKeys = [stringDict keysSortedByValueUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2)
{
    if ([obj1 length] > [obj2 length])
    {
        return NSOrderedDescending;
    }
    if ([obj1 length] < [obj2 length])
    {
        return NSOrderedAscending;
    }
    return NSOrderedSame;
}];
NSLog(@"通过代码块排序,定义比较大小的标准:字符串越长越大,排序后key为:%@", newKeys);

输出结果为:

2020-10-30 18:10:00.621205+0800 FoundationDemo[48152:1816838] 集合元素自身的排序方法(compare:),字典通过比较值将key排序后为:(
    Liu,
    Zou,
    Xie
)
2020-10-30 18:10:00.621368+0800 FoundationDemo[48152:1816838] 通过代码块排序,定义比较大小的标准:字符串越长越大,排序后key为:(
    Xie,
    Liu,
    Zou
)

e、字典过滤
NSDictionary *numberDict = @{@"Xie": @22, @"Liu": @18, @"Zou": @19};

NSSet *keySet = [numberDict keysOfEntriesPassingTest:^BOOL(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop)
{
    return ([obj intValue] < 20);
}];
NSLog(@"真羡慕,你俩都好小呀:%@",keySet);

输出结果为:

2020-10-30 18:13:51.339440+0800 FoundationDemo[48212:1821809] 真羡慕,你俩都好小呀:{(
    Liu,
    Zou
)}

f、可变字典
NSDictionary *numberDict = @{@"Xie": @22, @"Liu": @18, @"Zou": @19};
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:numberDict];

mutableDict[@"Xie"] = @"love Liu";
NSLog(@"覆盖值后,字典为:%@",mutableDict);

mutableDict[@"marray"] = @"baby";
NSLog(@"添加新元素后,字典为:%@",mutableDict);

[mutableDict addEntriesFromDictionary:@{@"Lin":@21}];
NSLog(@"添加另外一个字典后,字典为:%@",mutableDict);

[mutableDict removeObjectForKey:@"Xie"];
NSLog(@"删除元素后,字典为:%@",mutableDict);

输出结果为:

2020-10-30 18:19:20.392259+0800 FoundationDemo[48302:1826112] 覆盖值后,字典为:{
    Liu = 18;
    Xie = "love Liu";
    Zou = 19;
}
2020-10-30 18:19:20.392358+0800 FoundationDemo[48302:1826112] 添加新元素后,字典为:{
    Liu = 18;
    Xie = "love Liu";
    Zou = 19;
    marray = baby;
}
2020-10-30 18:19:20.392443+0800 FoundationDemo[48302:1826112] 添加另外一个字典后,字典为:{
    Lin = 21;
    Liu = 18;
    Xie = "love Liu";
    Zou = 19;
    marray = baby;
}
2020-10-30 18:19:20.392513+0800 FoundationDemo[48302:1826112] 删除元素后,字典为:{
    Lin = 21;
    Liu = 18;
    Zou = 19;
    marray = baby;
}

3、NSArray

a、创建数组
NSArray *aArray = @[@1, @2, @3];
NSArray<NSString *> *bArray = @[@"2", @"4", @"3", @"1"];
NSArray *cArray = [NSArray arrayWithObjects:@1, @"XieJiaPei", person, nil];
NSArray *dArray = [NSArray arrayWithObjects:@1, @"XieJiaPei", nil, @5, person, nil];
NSLog(@"原始数组A为:%@",aArray);
NSLog(@"原始数组B为:%@",bArray);
NSLog(@"原始数组A为:%@",cArray);
NSLog(@"原始数组C为:%@",dArray);

输出结果为:

2020-10-21 16:48:29.731149+0800 BasicGrammarDemo[1505:46009] 原始数组A为:(
    1,
    2,
    3
)
2020-10-21 16:48:29.731213+0800 BasicGrammarDemo[1505:46009] 原始数组B为:(
    2,
    4,
    3,
    1
)
2020-10-21 16:48:29.731286+0800 BasicGrammarDemo[1505:46009] 原始数组A为:(
    1,
    XieJiaPei,
    "<PersonClass: 0x600002dfa140>"
)
2020-10-21 16:48:29.731345+0800 BasicGrammarDemo[1505:46009] 原始数组C为:(
    1,
    XieJiaPei
)

NSArray是不是单例?这个问题乍一看好像很可笑,数组当然不是单例!但是真的所有数组都不是单例吗?下边我们来看一组有意思的验证:

NSArray *array1 = [NSArray alloc];
NSArray *array2 = [NSArray alloc];
NSArray *array3 = [NSArray alloc];
NSLog(@"arr1 == %p, arr2 == %p, arr3 == %p", array1, array2, array3);

array1 = [array1 init];
array2 = [array2 init];
array3 = [array3 init];
NSLog(@"arr1 == %p, arr2 == %p, arr3 == %p", array1, array2, array3);

输出结果为:

2022-02-14 10:21:45.777974+0800 OCDemo[56683:9544074] arr1 == 0x7fff8a4b3b90, arr2 == 0x7fff8a4b3b90, arr3 == 0x7fff8a4b3b90
2022-02-14 10:21:45.778151+0800 OCDemo[56683:9544074] arr1 == 0x7fff8a349a10, arr2 == 0x7fff8a349a10, arr3 == 0x7fff8a349a10

从上述验证中可以发现:使用alloc方法创建出来的NSArray对象指向了同一个内存空间,虽然看起来怪怪的,但是它真的是单例;init初始化之后的对象变成了另一个空间地址,而且所有对象都指向了同一个空间地址,说明对于类簇为了提升效率节约不必要的内存消耗,apple还是做了很多处理和优化的。


b、从数组中取值
PersonClass *person = [[PersonClass alloc] init];
NSArray *cArray = [NSArray arrayWithObjects:@1, @"XieJiaPei", person, nil];

NSString *index1IncArray = [cArray objectAtIndex:1];
NSNumber *index0IncArray = cArray[0];
NSLog(@"数组C中下标0的值为:%@,下标1的值为:%@",index0IncArray,index1IncArray);

NSArray *array = @[@"xie",@"jia",@"pei",@"fan",@"yi",@"cheng",@"lin",@"feng",@"mian"];
NSArray *newArray = [array objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 3)]];
NSLog(@"获取索引从3~6的元素组成的新集合:%@",newArray);

NSLog(@"获取元素在集合中的位置:%lu",(unsigned long)[array indexOfObject:@"xie"]);

array = [array arrayByAddingObjectsFromArray:newArray];
NSLog(@"向array数组的最后追加另一个数组的所有元素:%@",array);

array = [array subarrayWithRange:NSMakeRange(5, 3)];
NSLog(@"索引为6~9处的所有元素:%@",array);

输出结果为:

2020-10-21 16:48:29.765873+0800 BasicGrammarDemo[1505:46009] 数组C中下标0的值为:1,下标1的值为:XieJiaPei

2020-10-30 15:08:34.786959+0800 FoundationDemo[45297:1678179] 获取索引从3~6的元素组成的新集合:(
    pei,
    fan,
    yi
)
2020-10-30 15:08:34.787054+0800 FoundationDemo[45297:1678179] 获取元素在集合中的位置:0
2020-10-30 15:08:34.787135+0800 FoundationDemo[45297:1678179] 向array数组的最后追加另一个数组的所有元素:(
    xie,
    jia,
    pei,
    fan,
    yi,
    cheng,
    lin,
    feng,
    mian,
    pei,
    fan,
    yi
)
2020-10-30 15:08:34.787201+0800 FoundationDemo[45297:1678179] 索引为6~9处的所有元素:(
    cheng,
    lin,
    feng
)

c、给数组排序
sortedArrayUsingSelector:

该排序方法需要数组对象自身实现compare:方法,而有些类系统已经实现了compare:方法,比如NSStringNSNumber等,所以对于系统类的简单排序来说非常方便;对于自定义类数组的排序来说就需要在自定类中实现compare:方法。

NSArray<NSString *> *bArray = @[@"2", @"4", @"3", @"1"];

NSArray *sortedArray = [bArray sortedArrayUsingSelector:@selector(compare:)];
NSLog(@"集合元素自身的排序方法(compare:),数组B排序后为:%@", sortedArray);
sortedArrayUsingComparator:

该方法需要传入一个cmptr匿名函数用来做比较函数,使用起来要比单独定义比较函数(方法)方便很多;sortedArrayWithOptions:usingComparator:多出了一个NSSortOptions枚举类型的变量,这个枚举类型的变量有两个不同的枚举值:NSSortConcurrent可以利用多核cpu的特性使排序并发执行;NSSortStable可以保证排序算法的稳定性(相等的值在排序前后顺序不发生变化) 。

NSArray *sortedArrayByBlock = [bArray sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2)
{
    if ([obj1 integerValue] > [obj2 integerValue])
    {
        return NSOrderedDescending;
    }
    if ([obj1 intValue] < [obj2 intValue])
    {
        return NSOrderedAscending;
    }
    return NSOrderedSame;
}];
NSLog(@"通过代码块排序,数组B排序后为:%@", sortedArrayByBlock);
sortedArrayUsingFunction:context:

方法需要传入一个C语言的函数comparator,由于是C语言的函数,所以可以写在任意对排序方法可见的位置。第三个参数是任意的指针类型,可以使得排序函数内部可以依赖一些外部的参数。

NSArray *sortedArrayByFunction = [bArray sortedArrayUsingFunction:intSort context:nil];
NSLog(@"通过函数排序,数组B排序后为:%@", sortedArrayByFunction);

NSInteger intSort(id num1, id num2, void *context)
{
    int v1 = [num1 intValue];
    int v2 = [num2 intValue];
    if (v1 < v2)
        return NSOrderedAscending;
    else if (v1 > v2)
        return NSOrderedDescending;
    else
        return NSOrderedSame;
}

输出结果为:

2020-10-30 15:19:30.831961+0800 FoundationDemo[45456:1686606] 集合元素自身的排序方法(compare:),数组B排序后为:(
    1,
    2,
    3,
    4
)
2020-10-30 15:19:30.832052+0800 FoundationDemo[45456:1686606] 通过函数排序,数组B排序后为:(
    1,
    2,
    3,
    4
)
2020-10-30 15:19:30.832179+0800 FoundationDemo[45456:1686606] 通过代码块排序,数组B排序后为:(
    1,
    2,
    3,
    4
)

d、让数组过滤数据

使用LIKEBETWEENBEGINSWITHENDSWITHCONTAINSIN等谓词来筛选(大小写不影响结果,习惯上用大写)。

NSArray<NSString *> *bArray = @[@"2", @"4", @"3", @"1"];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF matches %@", @"[3-9]+"];
NSArray *filteredArray = [bArray filteredArrayUsingPredicate:predicate];
NSLog(@"数组B过滤数据(只留下3-9之间的数据)后为:%@", filteredArray);

输出结果为:

2020-10-21 16:53:27.058488+0800 BasicGrammarDemo[1615:51718] 数组B过滤数据(只留下3-9之间的数据)后为:(
    4,
    3
)

使用>,<,>=,<=等运算符号来筛选:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name.length > 5"];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];

e、可变数组
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:@[@"xie",@"jia",@"pei",@"fan",@"yi",@"cheng"]];

// 向集合最后添加一个元素
[mutableArray addObject:@"liu"];
// 向集合尾部添加多个元素
[mutableArray addObjectsFromArray:@[@"ying",@"chi"]];
NSLog(@"向集合最后位置添加元素后数组为:%@",mutableArray);

// 指定位置插入一个元素
[mutableArray insertObject:@"GuanYu" atIndex:1];
// 指定位置插入多个元素
[mutableArray insertObjects:@[@"ZhangFei",@"ZhaoYun"] atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]];
NSLog(@"指定位置插入元素后数组为:%@",mutableArray);

// 删除最后一个元素
[mutableArray removeLastObject];
// 删除指定索引处的元素
[mutableArray removeObjectAtIndex:5];
// 删除3~6处元素
[mutableArray removeObjectsInRange:NSMakeRange(2, 3)];
NSLog(@"删除元素后数组为:%@",mutableArray);

// 替换索引为2处的元素
[mutableArray replaceObjectAtIndex:2 withObject:@"MaChao"];
NSLog(@"替换可变数组中的数据后为:%@",mutableArray);

输出结果为:

2020-10-30 17:00:55.532047+0800 FoundationDemo[46981:1758054] 向集合最后位置添加元素后数组为:(
    xie,
    jia,
    pei,
    liu,
    ying,
    chi
)
2020-10-30 17:00:55.532186+0800 FoundationDemo[46981:1758054] 指定位置插入元素后数组为:(
    xie,
    ZhangFei,
    ZhaoYun,
    GuanYu,
    jia,
    pei,
    liu,
    ying,
    chi
)
2020-10-30 17:00:55.532264+0800 FoundationDemo[46981:1758054] 删除元素后数组为:(
    xie,
    ZhangFei,
    liu,
    ying
)
2020-10-30 17:00:55.532336+0800 FoundationDemo[46981:1758054] 替换可变数组中的数据后为:(
    xie,
    ZhangFei,
    MaChao,
    ying
)

f、数组的拷贝
NSArray *shallowCopyArray = [bArray copy];
NSLog(@"原数组为不可变数组——使用copy——结果数组为不可变数组,比较是否相等的结果为:%@", shallowCopyArray == bArray ? @"相等" : @"不相等");
NSLog(@"原数组为不可变数组——使用copy——结果数组为不可变数组,比较元素是否相等的结果为:%@", shallowCopyArray[3] == bArray[3] ? @"相等" : @"不相等");

NSMutableArray *mutableCopyArray = [bArray mutableCopy];
NSLog(@"原数组为不可变数组——使用mutableCopy——结果数组为可变数组,比较是否相等的结果为:%@", mutableCopyArray == bArray ? @"相等" : @"不相等");

NSArray *copyArray = [mutableCopyArray copy];
NSLog(@"原数组为可变数组——使用copy——结果数组为不可变数组,比较是否相等的结果为:%@", copyArray == mutableCopyArray ? @"相等" : @"不相等");

NSMutableArray *anotherMutableCopyArray = [mutableCopyArray mutableCopy];
NSLog(@"原数组为可变数组——使用mutableCopy——结果数组为可变数组,比较是否相等的结果为:%@", anotherMutableCopyArray == mutableCopyArray ? @"相等" : @"不相等");

输出结果为:

2020-10-21 17:16:15.239156+0800 BasicGrammarDemo[2026:73856] 原数组为不可变数组——使用copy——结果数组为不可变数组,比较是否相等的结果为:相等
2020-10-21 17:16:15.239212+0800 BasicGrammarDemo[2026:73856] 原数组为不可变数组——使用copy——结果数组为不可变数组,比较元素是否相等的结果为:相等
2020-10-21 17:16:15.239280+0800 BasicGrammarDemo[2026:73856] 原数组为不可变数组——使用mutableCopy——结果数组为可变数组,比较是否相等的结果为:不相等
2020-10-21 17:16:15.239336+0800 BasicGrammarDemo[2026:73856] 原数组为可变数组——使用copy——结果数组为不可变数组,比较是否相等的结果为:不相等
2020-10-21 17:16:15.239401+0800 BasicGrammarDemo[2026:73856] 原数组为可变数组——使用mutableCopy——结果数组为可变数组,比较是否相等的结果为:不相等

g、数组的遍历

一般的遍历方式包括for循环NSEnumeratorfor in。比较特殊的遍历方式enumerateObjectsUsingBlock是通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成。这几个遍历方式中for in性能最好,for循环较低, 多线程遍历方式是性能最差的。

使用for in快速快速遍历
  • 该遍历方法是按照元素的放入顺序依次遍历元素
  • 可以直接获取到对应的元素,使用方便
  • 在可变数组遍历过程中不能对数组进行插入删除等更新操作,否则会出现异常闪退问题.
for (NSString *string in bArray)
{
    NSLog(@"使用快速遍历,字符串为:%@",string);
}
使用枚举器遍历
  • 按照元素在数组中的顺序进行遍历;
  • 可以同时获取下标和对应的元素;
  • 可以随时终止查找,只需要将*stop=true
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"array[%@]:%@",@(idx), obj);
    if ([obj isEqualToString:@"five"]) {
        *stop = YES;
    }
}];

以上顺序和逆序遍历还可以使用不带block回调的枚举器来实现,如下:

NSEnumerator *enumerator = [bArray objectEnumerator];
id object;
while (object = [enumerator nextObject])
{
    NSLog(@"使用顺序枚举器,数值为:%@",object);
}
NSEnumerator *reverseEnumerator = [bArray reverseObjectEnumerator];
while (object = [reverseEnumerator nextObject])
{
    NSLog(@"使用逆序枚举器,数值为:%@" , object);
}

输出结果为:

2020-10-30 15:26:21.535811+0800 FoundationDemo[45562:1691741] 使用快速遍历,字符串为:2
2020-10-30 15:26:21.535861+0800 FoundationDemo[45562:1691741] 使用快速遍历,字符串为:4
2020-10-30 15:26:21.535928+0800 FoundationDemo[45562:1691741] 使用快速遍历,字符串为:3
2020-10-30 15:26:21.535992+0800 FoundationDemo[45562:1691741] 使用快速遍历,字符串为:1

2020-10-30 15:26:21.536155+0800 FoundationDemo[45562:1691741] 使用顺序枚举器,数值为:2
2020-10-30 15:26:21.536255+0800 FoundationDemo[45562:1691741] 使用顺序枚举器,数值为:4
2020-10-30 15:26:21.536402+0800 FoundationDemo[45562:1691741] 使用顺序枚举器,数值为:3
2020-10-30 15:26:21.536514+0800 FoundationDemo[45562:1691741] 使用顺序枚举器,数值为:1

2020-10-30 15:26:21.536622+0800 FoundationDemo[45562:1691741] 使用逆序枚举器,数值为:1
2020-10-30 15:26:21.536748+0800 FoundationDemo[45562:1691741] 使用逆序枚举器,数值为:3
2020-10-30 15:26:21.536884+0800 FoundationDemo[45562:1691741] 使用逆序枚举器,数值为:4
2020-10-30 15:26:21.536991+0800 FoundationDemo[45562:1691741] 使用逆序枚举器,数值为:2
并发遍历
  • 该选项可以利用多核CPU的特性进行并发遍历,速度会比较快
  • 该遍历不是线程安全的,所以设置*stop=true没有实际意义
  • 该操作主线程会等待遍历操作完成之后继续进行后边的操作
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"thread:%@, array[%@]:%@", [NSThread currentThread], @(idx), obj);
    if ([obj isEqualToString:@"five"]) {
        *stop = YES;// 无意义
    }
}];
其他遍历方法

还可以使用dispatch_apply函数来模拟遍历,但其实质只是一个重复指定次数的操作,并且可以指定完成操作的队列类确定是否需要开启并发遍历。在多数情况下,我们会使用并发队列来将耗时操作放在dispatch_apply函数中来实现,以利用多核cpu的特性来提升性能。

dispatch_queue_t queue = dispatch_queue_create("com.xiejiapei", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(array.count, queue, ^(size_t iteration) {
    //此处插入耗时操作
});

还可以使用ReactiveCocoa创建信号使用subscribeNext:方法进行遍历。


h、写入文件
NSArray<NSString *> *bArray = @[@"2", @"4", @"3", @"1"];

NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentPath stringByAppendingFormat:@"/%@",@"xie.plist"];
if ( [bArray writeToFile:filePath atomically:YES] )
{
    NSLog(@"成功写入文件,路径为:%@",filePath);
}

NSArray *arrayFromFile = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@"从文件中读取到的数组为:%@",arrayFromFile);

输出结果为:

2020-10-30 16:40:54.419610+0800 FoundationDemo[46618:1741321] 成功写入文件,路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/4E4809CA-E567-4D3A-8ADE-790075200303/data/Containers/Data/Application/27126598-B9C0-4156-BC10-D3EAC6697F7F/Documents/xie.plist
2020-10-30 16:40:54.419790+0800 FoundationDemo[46618:1741321] 从文件中读取到的数组为:(
    2,
    4,
    3,
    1
)

I、整体调用方法
- (void)performSelector
{
    PersonClass *person = [[PersonClass alloc] init];
    NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:@[person,person,person,person]];
    
    // 对集合元素整体调用方法
    [mutableArray makeObjectsPerformSelector:@selector(printPetPhrase:) withObject:@"锤子"];
    NSLog(@"对集合元素整体调用方法:%@",mutableArray);
    
    // 迭代集合内指定范围内元素,并使用该元素来执行代码块
    [mutableArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)] options:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
         NSLog(@"正在处理第%ld个元素:%@" , idx , obj);
    }];
}

- (void)printPetPhrase:(NSString *)petPhrase
{
    NSLog(@"你的口头禅是什么呀?%@",petPhrase);
}

输出结果为:

2020-10-30 17:22:03.303271+0800 FoundationDemo[47347:1775321] 你的口头禅是什么呀?锤子
2020-10-30 17:22:03.303360+0800 FoundationDemo[47347:1775321] 你的口头禅是什么呀?锤子
2020-10-30 17:22:03.303433+0800 FoundationDemo[47347:1775321] 你的口头禅是什么呀?锤子
2020-10-30 17:22:03.303490+0800 FoundationDemo[47347:1775321] 你的口头禅是什么呀?锤子
2020-10-30 17:22:03.303692+0800 FoundationDemo[47347:1775321] 正在处理第2个元素:<PersonClass: 0x6000031ac880>
2020-10-30 17:22:03.303758+0800 FoundationDemo[47347:1775321] 正在处理第1个元素:<PersonClass: 0x6000031ac880>

J、数组去除重复元素
法一

利用NSSet特性, 放入集合自动去重。这种方法更快,利用NSSet不会添加重复元素的特性。

NSArray *selected = [deleteDuplicateSet allObjects];

不过去重的数组没有进行排序,如果需要排序,可以使用NSSortDescriptor类。

NSSet *deleteDuplicateSet = [NSSet setWithArray:self.switchAlbumSelectList];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
NSArray *selected = [deleteDuplicateSet sortedArrayUsingDescriptors:sortDescriptors];
法二

利用NSDictionaryAllKeysAllValues)方法可以将NSArray中的元素存入一个字典,然后利用AllKeys或者AllValues取得字典的所有键或值,这些键或值都是去重的。结果为无序的,也就是说不包含原有顺序,可自行加入排序算法。

NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
for (NSNumber *n in originalArr) 
{
    [dict setObject:n forKey:n];
}
NSLog(@"%@",[dictM allValues]);
法三

通过valueForKeyPath去重只需一行代码。

NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSArray *result = [originalArr valueForKeyPath:@"@distinctUnionOfObjects.self"];
法四

开辟新的内存空间,判断是否存在,若不存在则添加到数组中,得到最终结果的顺序不发生变化。

// 只能通过这种方式去重,而不能使用字典、Set,因为要保证批量上传的顺序按照选择时候的顺序
NSMutableArray *selected = [NSMutableArray array];
for (UCARPhotoModel *photo in self.switchAlbumSelectList)
{
  if (![selected containsObject:photo])
  {
    [selected addObject:photo];
  }
}

4、可变数组的实现原理

a、Demo演示
NSArray *placeHoldler = [NSArray alloc];
NSArray *arr1 = [[NSArray alloc] init];
NSArray *arr2 = [[NSArray alloc] initWithObjects:@0, nil];
NSArray *arr3 = [[NSArray alloc] initWithObjects:@0, @1, nil];
NSArray *arr4 = [[NSArray alloc] initWithObjects:@0, @1, @2, nil];
NSLog(@"placeHoldler:%s",object_getClassName(placeHoldler));
NSLog(@"arr1:%s",object_getClassName(arr1));
NSLog(@"arr2:%s",object_getClassName(arr2));
NSLog(@"arr3:%s",object_getClassName(arr3));
NSLog(@"arr4:%s",object_getClassName(arr4));
NSMutableArray *mutablePlaceHoldler = [NSMutableArray alloc];
NSMutableArray *mutableArr1 = [[NSMutableArray alloc] init];
NSMutableArray *mutableArr2 = [[NSMutableArray alloc] initWithObjects:@0, nil];
NSMutableArray *mutableArr3 = [[NSMutableArray alloc] initWithObjects:@0, @1, nil];
NSMutableArray *mutableArr4 = [[NSMutableArray alloc] initWithObjects:@0, @1, @2, nil];
NSLog(@"placeHoldler:%s",object_getClassName(mutablePlaceHoldler));
NSLog(@"mutableArr1:%s",object_getClassName(mutableArr1));
NSLog(@"mutableArr2:%s",object_getClassName(mutableArr2));
NSLog(@"mutableArr3:%s",object_getClassName(mutableArr3));
NSLog(@"mutableArr4:%s",object_getClassName(mutableArr4));
NSLog(@"placeHoldler地址:%p",placeHoldler);
NSArray *anotherPlaceHoldler = [NSArray alloc];
NSLog(@"anotherPlaceHoldler地址:%p",anotherPlaceHoldler);

输出结果为:

2020-07-23 17:52:33.886193+0800 Demo[76977:21942936] placeHoldler:__NSPlaceholderArray
2020-07-23 17:52:33.886604+0800 Demo[76977:21942936] arr1:__NSArray0
2020-07-23 17:52:33.886698+0800 Demo[76977:21942936] arr2:__NSSingleObjectArrayI
2020-07-23 17:52:33.886760+0800 Demo[76977:21942936] arr3:__NSArrayI
2020-07-23 17:52:33.886829+0800 Demo[76977:21942936] arr4:__NSArrayI

2020-07-23 17:52:33.886917+0800 Demo[76977:21942936] placeHoldler:__NSPlaceholderArray
2020-07-23 17:52:33.886975+0800 Demo[76977:21942936] mutableArr1:__NSArrayM
2020-07-23 17:52:33.887035+0800 Demo[76977:21942936] mutableArr2:__NSArrayM
2020-07-23 17:52:33.887205+0800 Demo[76977:21942936] mutableArr3:__NSArrayM
2020-07-23 17:52:33.887332+0800 Demo[76977:21942936] mutableArr4:__NSArrayM

2020-07-23 18:12:14.146565+0800 Demo[77022:21953656] placeHoldler地址:0x10dd56738
2020-07-23 18:12:14.146780+0800 Demo[77022:21953656] anotherPlaceHoldler地址:0x10dd56738

不管是NSArray,还是NSMutableArrayalloc之后的得到都是__NSPlacrholderArray。当我们创建一个空数组时,得到的是__NSArray0。当NSArray只有一个元素时,得到的是__NSSingleObjectArrayI。当NSArray的元素个数大于1个的时候, 得到 __NSArrayINSMutablearray 无论元素个数有多少返回的都是__NSArrayMplaceHolderanotherPlaceHoldler的内存地址一样,说明是一个单例,该类内部只有一个isa指针,init后被新的实例换掉了。


b、数组的数据结构
NSArrayI
__NSArrayI
{
    // 数组的元素个数,调用[array count]时,返回的就是_userd的值
    NSInterger _userd;
    
    // 当做id_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了
    id_list[0];
}
NSSingleObjectArrayI
__NSSingleObjectArrayI
{
    //因为只有在创建只包含一个对象的不可变数组时,才会得到__NSSingleObjectArrayI对象,所以其内部结构更加简单
    id object;
}
__NSArrayM
__NSArrayM
{
    //当前对象数目 [NSMutablearray count]
    NSUInterger _used;
    
    //对象数组的起始偏移
    NSUInterger _offset;
    
    //已分配的_list大小,能存储的对象个数,不是字节数
    int_size: 28;
    int_unused: 4;
    
    //修改标记
    uint32_t _mutations;
    
    //是个循环数组,并且在增删操作时会动态地重新分配以符合当前的存储需求
    id *_list;
}

c、数组插入删除的原理

数组是一段连续的内存空间, 所以当在下标0处插入一个元素时,需要移动其后面所有的元素。同样的移除第一个元素,需要进行相同的操作。

当数组非常大时,就有问题了。NSMutableArray使用环形缓冲区,这个数据结构相对简单,只是比常规数组复杂点。环形缓冲区的内容能在到达任意一段时绕向另一端,如果我们在中间进行插入和删除,只会移动最少的一边元素。


Demo

Demo在我的Github上,欢迎下载。
BasicsDemo

参考文献

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

推荐阅读更多精彩内容