类的深入研究和分类

一、分类

(一)分类的基本知识

概念:Category  分类是OC特有的语言,依赖于类。

分类的作用:在不改变原来的类内容的基础上,为类增加一些方法。

添加一个分类:

文件结构图:

在分类中添加一个方法

@interface Person (Test)

- (void)study;

@end

Study方法的实现

- (void)study
{
    NSLog(@"调用了分类的 study 方法");
}

测试程序:

Person *p = [[Person alloc] init];

// 调用分类中的方法
[p study];
// 打印结果
2016-07-30 00:58:48.606 test[28739:6902446] 调用了分类的 study 方法

(二)分类的使用注意

  1. 分类只能增加方法(包括类方法和对象方法),不能增加成员变量;
  2. 在分类方法的实现中可以访问原来类中的成员变量;
  3. 分类中可以重新实现原来类中的方法,但是会覆盖掉原来的方法,导致原来的方法无法再使用(警告);
  4. 方法调用的优先级:分类->原来的类->父类,若包含有多个分类,则最后参与编译的分类优先;
  5. 在很多的情况下,往往是给系统自带的类添加分类,如NSObject和NSString,因为有的时候,系统类可能并不能满足我们的要求。
  6. 在大规模的应用中,通常把相应的功能写成一个分类,可以有无限个分类,对原有类进行扩充,一般分模块写,一个模块一个分类。

(三)分类编程练习

  1. 给NSString类增加一个类方法,计算某个字符串对象中阿拉伯数字的个数;
  2. 给NSString类增加一个对象方法,计算当前字符串对象中阿拉伯数字的个数;

分类中方法的声明

@interface NSString (NumberOfs)

// 为 NSString 类增加一个类方法, 计算某个字符串阿拉伯数字的个数
+ (int)numberOfString:(NSString *)str;

// 为 NSString 类增加一个对象方法, 计算当前对象中的阿拉伯数字的个数
- (int)numberCount;

@end

分类中方法的实现

@implementation NSString (NumberOfs)

+ (int)numberOfString:(NSString *)str
{
    int count = 0;
    for (int i = 0; i < str.length; i++)
    {
        // 去除字符串中的第i个位置的字符给c
        unichar c = [str characterAtIndex:i];
        // 注意这里是字符, 需要使用单引号
        if (c >= '0' && c <= '9')
        {
            count++;
        }
    }
    return count;
}

// 实现对象方法
- (int)numberCount
{
    int count = 0;
    for (int i = 0; i < self.length; i++)
    {
        // 取出字符串中第i个位置的字符给c
        unichar c = [self characterAtIndex:i];
        // 注意这里是字符, 需要使用单引号
        if (c >= '0' && c <= '9')
        {
            count++;
        }
    }
    return count;
}

@end

测试程序:

// 调用分类中实现的类方法
int a = [NSString numberOfString:@"1234qwer890"];
NSLog(@"%d", a);

// 调用分类中实现的对象方法
int b = [@"2134JFIf" numberCount];
NSLog(@"%d", b);

// 打印结果
2016-07-30 01:15:10.736 test[28972:6958370] 7
2016-07-30 01:15:10.737 test[28972:6958370] 4

二、类的深入研究

(一)类的本质

类本身也是一个对象,是class类型的对象,简称“类对象”。

Class类型的定义:

Typedef struct obj class *class;

类名就代表着类对象,每个类只有一个类对象。

利用 class 创建 Person类

利用 Person 创建 Person 类型的对象

Person *p = [[Person alloc] init];

获取内存中的类对象有两种方法:

(1)class c=[p class];//指向类的对象的指针调用class方法

(2)Class c1=[Person class];//使用类名调用class方法

注意:c和c1打印出来的地址相同,class c2=[p class];可以证明所有的对象共用一个类方法。

( 二)类的加载和初始化

// Person 类的声明
@interface Person : NSObject
@end

/// Person类的实现
@implementation Person

- (void)load
{
  NSLog(@"Person + load");
}
- (void)initialize
{
  NSLog(@"Person + initialize");
}
@end

测试程序:

@interface Student : Person

@end

@implementation Student
// 重写类的加载和初始化方法

- (void)load
{
  NSLog(@"Student + initialize");
}
- (void)initialize
{
  NSLog(@"Student + initialize");
}
@end

// 创建一个Person类对象
// 第一次使用Person类
Person *p = [[Person alloc] init];
Person *p1 = [[Person alloc] init];
// 第一次使用Student类
Student *s = [[Student alloc] init];

// 打印结果
2016-07-30 01:24:47.951 test[29139:7007082] Person + load
2016-07-30 01:24:47.953 test[29139:7007082] Student + initialize
2016-07-30 01:24:48.136 test[29139:7007082] Person + initialize
2016-07-30 01:24:48.137 test[29139:7007082] Student + initialize
  1. 当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法,只会调用一次;
  2. 当第一次使用某个类时,就会调用当前类的+initialize方法;
  3. 先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法,最后调用分类的+load方法),先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)。
  4. 注意:在初始化的时候,如果在分类中重写了+initialize方法,则会覆盖掉父类的。
  5. 重写+initialize方法可以监听类的使用情况。

description方法和sel

一、description方法

Description方法包括类方法和对象方法。(NSObject类所包含)

(一)基本知

- description(对象方法)

使用NSLog和@%输出某个对象时,会调用对象的description方法,并拿到返回值进行输出。

+ description(类方法)

使用NSLog和@%输出某个对象时,会调用类对象的description方法,并拿到返回值进行输出,把整个对象一次性打印出来,打印对象使用%@。
使用@%打印对象如(“@%”,P)默认打印输出为<类名:内存地址]] >,虽然字符串也是对象,但字符串在使用@%打印时情况特殊。

// Person 类的声明
@interface Person : NSObject
// 两个成员变量
@property (nonatomic, assign) int age;
@property (nonatomic, strong) NSString *name;

@end

/// Person类的实现
@implementation Person


@end

Person *p = [[Person alloc] init];
// 点语法 本质是调用 set 和 get 方法
p.age = 10;
p.name = @"小明";
// 直接用 NSLog 和 %@ 打印对象
NSLog(@"%@",p);

// 打印结果
2016-07-30 18:40:53.937 test[1728:627451] <Person: 0x7fdf8a41e0b0>

那么应该怎么实现打印对象的所有属性呢?在类的实现中重写description方法。

(二)实现打印对象的所有属性

// Person 类的声明
@interface Person : NSObject
// 两个成员变量
@property (nonatomic, assign) int age;
@property (nonatomic, strong) NSString *name;

@end

/// Person类的实现
@implementation Person

- (NSString *)description
{
    return [NSString stringWithFormat:@"age=%d, name=%@", _age,_name];
}

@end

Person *p = [[Person alloc] init];
// 点语法 本质是调用 set 和 get 方法
p.age = 10;
p.name = @"小明";
// 直接用 NSLog 和 %@ 打印对象
NSLog(@"%@",p);
// 获取Person的“类对象”
Class c = [Person class];
NSLog(@"%@",c);

// 打印结果
2016-07-30 18:47:41.546 test[1795:650822] age=10, name=小明
2016-07-30 18:47:41.546 test[1795:650822] Person

(三)区别

+ description 决定了类对象的输出结果,即类本身
- description 方法决定了实例对象的输出结果,即Person创建的对象。

(四)打印相关补充

// 1. 打印对象地址
Person *p = [[Person alloc] init];
NSLog(@"%@",p);
// 2. 打印当前代码行号
NSLog(@"%d", __LINE__);
// 3. 打印当前文件路径
NSLog(@"%s", __FILE__);
// 4. 打印所属的方法或函数名
NSLog(@"%s", __func__);

// 打印结果
2016-07-30 18:52:14.556 test[1857:670721] age=0, name=(null)
2016-07-30 18:52:14.556 test[1857:670721] 29
2016-07-30 18:52:14.557 test[1857:670721] /Users/kangan/Desktop/test/test/ViewController.m
2016-07-30 18:52:14.557 test[1857:670721] -[ViewController viewDidLoad]

二、SEL

SEL:全称Selector 表示方法的存储位置。

方法在内存中是怎么存储的?

Person *p=[[Person alloc] init];
[p test];

寻找方法的过程:

  1. 首先把test这个方法名包装成sel类型的数据;
  2. 根据SEL数据找到对应的方法地址;
  3. 根据方法地址调用相应的方法。
  4. 注意:在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。

关于_cmd:每个方法的内部都有一个-cmd,代表着当前方法。

注意:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去寻找对应的方法地址,找到方法地址后就可以调用方法。这些都是运行时特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法。

BLOCK和协议

一、BOLCK

(一)简介

BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码。用来封装和保存代码,有点像函数,BLOCK可以在任何时候执行。

BOLCK和函数的相似性:(1)可以保存代码(2)有返回值(3)有形参(4)调用方式一样。

标识符^

void (^MYBlock)(int) = ^(int n)
{
    for (int i = 0 ; i < n; i++)
    {
        NSLog(@"-----------------------");
    }
};

MYBlock(5);

NSLog(@"----------结束-----------");

// 打印结果
2016-07-30 19:03:05.742 test[1928:729541] -----------------------
2016-07-30 19:03:05.743 test[1928:729541] -----------------------
2016-07-30 19:03:05.743 test[1928:729541] -----------------------
2016-07-30 19:03:05.743 test[1928:729541] -----------------------
2016-07-30 19:03:05.743 test[1928:729541] -----------------------
2016-07-30 19:03:05.743 test[1928:729541] ----------结束-----------

(二)基本使用

(1)定义BLOCK变量
int (^SumBlock)(int,int);//有参数,返回值类型为int
void (^MyBlock)();//无参数,返回值类型为空
(2)利用block封装代码
// 利用block封装代码
// 1. 接受两个参数的
int (^Myblock1)(int, int) = ^(int a, int b)
{
    return a + b;
};
// 2. 无参数形式
int (^Myblock2)()=^()
{
    return 10;
};

// 无参数的省形式
int (^Myblock3)() = ^
{
    return 10;
};
(3)Block访问外部变量
  1. Block内部可以访问外部变量;
  2. 默认情况下,Block内部不能修改外部的局部变量
  3. 给局部变量加上__block关键字,则这个局部变量可以在block内部进行修改。
(4)利用typedef定义block类型(和指向函数的指针很像)
Typedef int(^MyBlock)(int ,int);
//以后就可以利用这种类型来定义block变量了。
MyBlock a,b;  
a=^(int a,int b){return a-b;};
MyBlock b2=^(int n1,int n2){return n1*n2;};

二、Protocol(协议

(一)简介

  1. Protocol:就一个用途,用来声明一大堆的方法(不能声明成员变量),不能写实现。
  2. 只要某个类遵守了这个协议,就拥有了这个协议中的所有方法声明。
  3. 只要父类遵守了某个协议,那么子类也遵守。
  4. Protocol声明的方法可以让任何类去实现,protocol就是协议。
  5. OC不能继承多个类(单继承)但是能够遵守多个协议。继承(:),遵守协议(< >)
  6. 基协议:是基协议,是最根本最基本的协议,其中声明了很多最基本的方法。
  7. 协议可以遵守协议,一个协议遵守了另一个协议,就可以拥有另一份协议中的方法声明。

(二)基本使用

创建一个协议

// MyProtocol协议,这个协议遵守基协议<NSObject>
@protocol MyProtocol <NSObject>

// 在协议中可以声明很多有用的方法
// @Property int age; 在协议中不能声明成员变量,只能声明方法,也不能实现方法。
@required
// @required 关键字,主要用于程序之间的交流,要求实现

- (void)pint;
- (void)haha;

@end

遵守协议

// 在类的声明中加入协议的头文件

import "MyProtocol.h"

// Person 类的声明,这个类继承了 NSObject类,遵守 MyProtocol 协议
@interface Person : NSObject <MyProtocol>
@end

完成协议中声明的方法的实现

/// Person类的实现
@implementation Person
// 在这里进行遵守的协议里所声明方法的实现

- (void)pint
{
  NSLog(@"小明");
}
- (void)haha
{
  NSLog(@"haha@");
}
@end

测试程序

Person *p = [[Person alloc] init];
// Person 类对象,调用协议中的方法
[p pint];
[p haha];

// 打印结果
2016-07-30 19:27:08.553 test[2188:802815] 小明
2016-07-30 19:27:08.553 test[2188:802815] haha@
1. 协议的定义
@protocol 协议名称 <NSObject>
//方法声明列表
@end;
2. 如何遵守协议
类遵守协议
@protocol //类名:父类名 <协议名称1,协议名称2>
@end
协议遵守协议
@protocol //协议名称 <其他协议名称>
@end;
3. 协议方法声明中的关键字

(1)required (默认)要求实现,若没有实现则警告但不报错
(2)Optional 不要求实现

4. 定义变量时遵守协议的限制
类名<协议名称> *变量名    NSObject<.Myprotocol> *obj;

id  <协议名称>  变量名   id  <.Myprotocol> obj1;
5. Property中声明的属性也可以做遵守协议的限制
@property (nonatomic ,strong ) 类名<协议名称> *属性名;

@property (nonatomic ,strong ) id<协议名称>  属性名;
6. 补充知识:

协议本身写在.h头文件中,但也可以定义在任何地方。当这个协议只有这个类使用遵守时,一般把协议写在这个类里边,当这个协议需要多个类去实现时,就写在外边单独的文件中。

Foundation框架—结构体

一、基本知识

Foundation—基础框架。框架中包含了很多开发中常用的数据类型,如结构体,枚举,类等,是其他ios框架的基础。

如果要想使用foundation框架中的数据类型,那么包含它的主头文件就可以了。

#import<foundation/foundation.h>

补充:core foundation框架相对底层,里面的代码几乎都是c语言的,而foundation中是OC的。

二、常用的结构体介绍及简单使用

常用的结构体:

// 在Foundation框架中常用的四种结构体
NSRange; // 表示范围
CGPoint; // 表示坐标
CGSize;  // 表示UI元素的尺寸
CGRect   // 一个UI元素的位置和尺寸

(一)NSRang的基本使用

@"xiao ming love oc";
// 要利用 NSRange 表示 xiao 在字符串中的范围,应该怎么表示?
location = 0, length = 4;

{1, 2, 3, 4, 5}
// 要利用NSRange表示后三个数字在数组中的范围,应该怎么表示?
location = 2, length = 3;

创建变量

// 结构体类型的Date定义
struct Date
{
    int year;
    int month;
    int day;
};

// 创建一个 Date 雷雨的结构体变量,在创建的时候为变量赋值
// 第一种方式
struct Date d1 = {2016, 2, 16};
// 第二种方式
struct Date d2 = {.month = 10, .year = 2013, .day = 20};

// 使用 NSRange 创建变量
NSRange r1 = {2, 4}; //这种方式,可读性不好
NSRange r2 = {.location = 2, .length = 4}; // 代码显得比较多余
// 在实践的开发中最常用的是使用 Foundation 自带的函数即
NSRange r3 = NSMakeRange(2, 4);// 工作中使用速度方式,要求掌握

// 最简单的创建字符串的方式
NSString *str = @"xiao ming love oc";

// 查找某个字符串在 str 中的范围
// 如果找不到,length = 0, location = NSNoitFound == -1

NSRange range = [str rangeOfString:@"haha"];
NSLog(@"loc = %ld, lenth = %ld", range.location, range.length);

// 打印结果
2016-07-30 22:23:15.997 test[2461:1027839] loc = 9223372036854775807, lenth = 0

(二)NSPoint/CGPoint的使用

// NSPoint\CGPoint 表示坐标
// 开发中常使用的CGPoint,因为它是跨平台的
// 创建结构体变量
CGPoint p = CGPointMake(20, 20);  // 最常用

(三)NSSize/CGSize的使用

// NSSize\CGSize 表示UI元素的尺寸 (宽度,高度)
// 创建结构体变量
CGSize s = CGSizeMake(100, 50); // 宽度为100, 高度为50

(四)NSRect/CGRect的使用

// CGRect {CGPoint, CGSize}  一个 UI 元素的位置和尺寸

// 第一种方式
CGRect r1 = CGRectMake(0,
                       0,
                       100,
                       100);
// 第二种方式
CGRect r2 = { {0, 0}, {100,90} };
// 第三种方式
CGRect r3 = {p, s};
// 第四种方式
// 使用 CGPointZero 等的,前提是添加CoreGraphics框架
CGRect r4 = {CGPointZero, CGSizeMake(100, 90)};

// CGSizeZero 表示原点

CGPointZero == CGPointMake(0, 0);

常规的使用方式:

在开发中,想要验证结构体中属性的值,应该如何打印?

CGRect myRect(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
    CGRect rect;
    rect.origin.x = x;
    rect.origin.y = y;
    rect.size.width  = width;
    rect.size.height = height;

    return rect;
}

CGRect r = myRect(1, 2, 3, 3);
// 测试打印
NSLog(@"x = %f, y = %f, width = %f, height = %f", r.origin.x, r.origin.y, r.size.width, r.size.height);

// 打印结果
2016-07-30 22:46:40.267 test[2703:1111014] x = 1.000000, y = 2.000000, width = 3.000000, height = 3.000000

利用框架函数把相应的结构体转变成字符串,再直接打印字符串即可

// 利用框架函数将结构体转为字符串

NSString *str1 = NSStringFromCGPoint(CGPointMake(1, 1)); // 把坐标点转换为字符串
NSString *str2 = NSStringFromCGSize(CGSizeZero);         // 把尺寸转换为字符串
NSString *str3 = NSStringFromCGRect(CGRectMake(1, 2, 3, 4)); // 把位置和尺寸转换为字符串的形式

(五)Foundation框架中一些经常用到的结构体函数

// 使用这些 CGPointEqualToPoint, CGRectContainsPoint 等函数的前提是添加CoreGraphics框架
// 1. 比较凉快区域是否相同(x,y)
BOOL b = CGPointEqualToPoint(CGPointMake(10, 10), CGPointMake(10, 10));
// 2. 比较凉快区域是否相同
CGRectEqualToRect(CGRect rect1, CGRect rect2);
// 3. 比较两个尺寸是否相同
CGSizeEqualToSize(CGSize size1, CGSize size2);
// 4. 判断这个点是否在区域范围内
BOOL b2 = CGRectContainsPoint(CGRectMake(50, 50, 100, 50), CGPointMake(60, 60));

NSLog(@"%d", b2);

// 打印结果
2016-07-30 23:19:22.493 test[2846:1144854] 1

补充1:导入框架 Build phase->link
补充2:ios中坐标原点在左上角。

Foundation框架—字符串

一、Foundation框架中一些常用的类

    //字符串型:

    NSString:不可变字符串

    NSMutableString:可变字符串

    //集合型:

    //1)

    NSArray:OC不可变数组

    NSMutableArray:可变数组

    //2)

    NSSetNSMutableSet//3)

    NSDictiorary

    NSMutableDictiorary

    //其它:

    NSDate

    NSObject

二、NSString和NSMutableString的使用与注意

(一)6种创建字符串的形式

// 最简单快速的创建方式(语法糖)
NSString *s1 = @"小明";

// NSString *s2 = [NSString stringWithString:@"hehe"];

// 使用格式
NSString *s3 = [[NSString alloc] initWithFormat:@"my age is %d", 18];

// C字符串转换成OC字符串
NSString *s4 = [[NSString alloc] initWithUTF8String:"xiaoming"];
// 反过来 OC 字符串转成 C 字符串
const char *cs = [s4 UTF8String];

// 从文件读取信息到字符串
// NSUTF8stringEncoding 用到中文就可以用这种编码
NSString *s5 = [[NSString alloc] initWithContentsOfFile:@"xxx/xxx/xx/xx.txt"
                                               encoding:NSUTF8StringEncoding
                                                  error:nil];

// 视频资源路径读取内容到字符串
// NSString *url = [[NSURL alloc] initWithString:@"file:///Users/xxx/Desktop/1.txt"]; //这里有三个斜杠
NSURL *url = [NSURL fileURLWithPath:@"/Users/xx/xx/xx.txt"]; //这里已经说明,所以不需要再包含协议头
NSString *s6 = [[NSString alloc] initWithContentsOfURL:url
                                              encoding:NSUTF8StringEncoding
                                                 error:nil];

(二)使用注意

(1)字符串的导入导出
// 字符串的导出
// 把字符串写入到文件, 若这个文件不存在,则创建一个
[@"xiaoming \n haha" writeToFile:@"/Users/xxx/xx/xx/xxtxt"
                  atomically:YES
                    encoding:NSUTF8StringEncoding
                       error:nil];

// 注意这里如果要换行的话 可以使用 \n
// 文件内筒中每一个换行都有一个 \n ,所以,可以通过统计 \n 的个数来测试代码量

// 把字符串导入到资源位置
NSString *str = @"249809234dsjsdj";
NSURL *url = [NSURL fileURLWithPath:@"/Users/xxx/xxx/xx/xx.txt"];
[str writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil];
// 这里的 atomically 后面可以是 YES 和 NO ,通常使用 YES,这样更安全,若中途写入失败,则不在创建文件
(2)类方法
// 一般都会有一个类方法跟对象方法配对,类方法通常都以类名开头
// 在实际的开发过程中,通常直接使用类方法
[NSURL URLWithString:(nonnull NSString *)];
[NSString stringWithFormat:(nonnull NSString *), ...];
[NSString stringWithContentsOfFile:(nonnull NSString *)
                          encoding:(NSStringEncoding)
                             error:(NSError * _Nullable __autoreleasing * _Nullable)];

(三)NSMutableString的使用与注意

NSMutableString *s1 = [NSMutableString stringWithFormat:@"xiaoming age is 22"];

// 修改 + 添加
// 拼接内容到s1的后面
[s1 appendString:@" haha"];

// 修改-删除
// 获取 is 的范围
// 在开发中,通常把这两个方法连起来使用,一个获取范围,一个进行删除
NSRange range = [s1 rangeOfString:@"is"];
[s1 deleteCharactersInRange:range];

// 不可变字符串
NSString *s2 = [NSString stringWithFormat:@"age is 10"];

NSString *s3 = [s2 stringByAppendingString:@"  xx "];
// 调用这s2这个方法,把s2的内容拷贝一份,加上xx拼接成一个新的字符串返回,s2字符串本身不变
NSLog(@" s1 = %@, s2 = %@", s1, s2);

// 打印结果
2016-07-31 00:16:38.498 test[3361:1273892]  s1 = xiaoming age  22 haha, s2 = age is 10

(四)URL补充内容

url:资源路径

格式:协议头:/路径

网络路径协议头:http

本地文件以file为协议头

ftp为协议头,说明资源是ftp服务器上的。

Foundation框架—集合

一、NSArray和NSMutableArray

(一)NSArray不可变数组

(1)NSArray的基本介绍

NSArray是OC中使用的数组,是面向对象的,以面向对象的形式操纵对象,是不可变数组。

C语言数组有一个缺点即数组中只能存放同种数据类型的元素。

OC数组只能存放OC对象,不能存放非OC对象,如int,结构体和枚举等。

(2)NSArray的创建
// 普通数组的创建
int a = 5;
int ages[10] = {1, 2, 3, 4};

// 创建一个Person对象的数组
Person *p = [[Person alloc] init];
Person *persons[5] = {p, [[Person alloc] init]};

// NSArray 的创建
NSArray *array = [NSArray array];
// 0. 这个array永远是个空数组

NSArray *array2 = [NSArray arrayWithObject:@"ehe"];
// 1. 这个数组只有一个元素,且永远只有一个元素

// nil 是数组元素结束的标记
NSArray *array3 = [NSArray arrayWithObjects:@"xiaoming", @"xiaohong", nil];
// 2. 这个数组中有多个元素,以nil结尾

// 3.快速创建一个NSArray对象
NSArray *array4 = @[@"jack", @"rose", @"134234"];
(3)NSArray的访问
// 获取 NSArray 的元素个数
// 使用 count 方法

[array2 count];
NSLog(@"%ld", array2.count);

// NSArray中元素的访问
// 1. 使用索引
NSLog(@"%@", [array2 objectAtIndex:0]);
// 2. 像c语言数组一样使用下标
NSLog(@"%@", array2[0]);


 (4)NSArray的遍历

数组的遍历有以下几种方式:

首先创建一个数组

Person *p = [[Person alloc] init];
NSArray *array = @[p, @"heh", @"jack"];

第一种方法:使用for循环遍历

// 2. 快速便利
// id obj 代表着数组中的每个元素
int i = 0;
for (id obj in array)
{
    // 找出 obj 元素中在数组中的位置
    NSUInteger i = [array indexOfObject:obj];
    NSLog(@"%ld - %@", i,obj);

    // 结束便利
    if (i == 1)
    {
        break;
    }
}

// 3. 使用block遍历
// 每遍历到一个元素,就会调用一次block
// 并且当前元素和索引位置当做参数传给block
[array enumerateObjectsUsingBlock:
 ^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
    NSLog(@"%ld - %@", idx, obj);

    if (idx == 0)
    {
        // 停止遍历
        *stop = YES;
    }
}];

Block遍历的深入研究:

  
/// 现在此方法会报错了
void ^(myBlock)(id, NSUInteger, BOOL *) = ^(id obj, NSUInteger idx, BOOL *stop)
{
    NSLog(@"%ld - %@", idx, obj);

    if (idx == 0)
    {
        // 停止遍历
        *stop = YES;
    }
};

for (int i = 0; i < array.count; i++)
{
    // 用来标记是否需要停止遍历
    BOOL isStop = NO;
    // 取出元素
    id obj = array[i];
    myBlock(obj, i, &isStop);

    if (isStop)
    {
        break;
    }

}

每拿到一个元素,就传递给obj,就会调用一次block,并且把当前的元素和索引位置当做参数传递给block。

注意1:break只使用在两种场合,即switch和循环结构中。

注意2:stop参数用来停止遍历,其值若为YES,则停止。

(二)NSMutableArray可变数组

(1)NSMutableArray的基本使用

注意:NSMutableArray 继承自 NSArray,几乎拥有 NSArray 的一切方法。

// 创建一个可变数组
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"xiaoming", nil];

// 添加元素
// 添加一个 Person 类对象元素
[array addObject:[[Person alloc] init]];
// 添加一个字符串对象元素
[array addObject:@"xiaoming"];
// 错误写法
// oc数组中得元素不能为空值
//    [array addObject:nil];

// 删除元素
// 删除所有的元素
[array removeAllObjects];
// 删除指定的对象
[array removeObject:@"xiaoming"];
//删除指定索引的元素
[array removeObjectAtIndex:0];

// 打印数组

NSLog(@"%@", array);
NSLog(@"%ld", array.count);

二、NSSet和NSMutableSet

(一)NSSet不可变集合

基本使用:

// 创建一个NSSet
NSSet *s = [NSSet set];
// 因为不可变,所以 s 永远都为空

// 创建一个有多个元素的 NSSet
NSSet *s2 = [NSSet setWithObjects:@"xiaoming",@"haha", nil];

// 随机取出一个元素
NSString *str = [s2 anyObject];

// 打印
NSLog(@"%@", str);

// 打印结果
2016-07-31 01:02:20.904 test[3904:1421519] xiaoming

(二)NSMutableSet可变集合

基本使用:

// 创建一个 NSMutableSet
NSMutableSet *s = [NSMutableSet set];

// 添加元素
[s addObject:@"xiaoming"];

// 删除元素
// 删除指定元素
[s removeObject:(nonnull id)];
// 删除所有元素
[s removeAllObjects];

(三)NSSet和NSArray的对比

(1)共同点:
  1. 都是集合,都能够存放多个对象
  2. 只能存放oc对象,不能存放非oc对象类型(如int等基本数据类型和结构体,枚举等)。
  3. 本身都不可变,都有一个可变的子类。
(2)不同点:

NSArray 有顺序,NSSet 没有顺序

三、NSDictionary 和 NSMutableDictionary

(一)NSDictionary 不可变字典

(1)介绍

现实中的字典:根据索引找到具体的内容

OC 中的 NSDictionary:根据 key 找到 value。里面存储的东西都是键值对。

(2)NSDictionary的创建
// 创建字典
// 第一种方式
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"jack" forKey:@"name"];

// 第二种方式
NSArray *keys = @[@"name", @"address"];
NSArray *objects = @[@"yangeyong", @"南昌"];
NSDictionary *dict1 = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

// 第三种方式
NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:
                      @"xiaoming", @"name",
                      @"北京", @"Address",
                      @"1348494949", @"qq",
                      nil];

// 第三种方式,快速创建
NSDictionary *dic2 = @{@"name" : @"xiaoming",
                       @"address" : @"北京"};

注意:快速创建字典是编译器特性。

(3)NSDictionary的访问
// 基本使用
// 根据 key 获取对应的值
id obj = [dict objectForKey:@"name"];
// 快速访问
id obj1 = dict[@"name"];

// 打印
NSLog(@"%@", obj);
// 返回的是键值对的个数
// count 方法获取当前字典中键值对的个数
NSLog(@"%ld", dict.count);
(4)NSDictionary的遍历
// 字典不允许有相同的 key ,但允许有相同的 value (object)
// 字典是无序的
// 快速创建一个字典
NSDictionary *dict = @
{
    @"adress" : @"北京",
    @"name" : @"jack",
    @"name2" : @"xiaoming"
};

// 字典的便利
// 第一种方式,使用for循环
NSArray *keys = [dict allKeys];
[keys enumerateObjectsUsingBlock:^
(NSString *key, NSUInteger idx, BOOL * _Nonnull stop)
{
    NSString *object = dict[key];

    NSLog(@"%@, %@", key, object);
}];

// 第二种方式,使用block
[dict enumerateKeysAndObjectsUsingBlock:^
(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop)
{
    NSLog(@"%@ - %@", key, obj);
}];

(二)NSMutableDictionary可变字典

(1)NSMutableDictionary的基本使用
// 可变字典的基本使用
// 创建一个空的可变字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];

// 添加键值对
[dict setObject:@"xiaoming" forKey:@"name"];
[dict setObject:@"北京" forKey:@"address"];
[dict setObject:@"rese" forKey:@"name"];

// 删除键值对
// 删除特定键值对
[dict removeObjectForKey:(nonnull id)];
// 删除所有得键值对
[dict removeAllObjects];

// 获取 namekey 对应的值
NSString *str = dict[@"name"];

// 打印数组
NSLog(@"%@", @[@"XIAOMING", @"ROSE"]);
// 打印字典
NSLog(@"%@", dict);

// 打印结果

2016-07-31 01:33:36.466 test[4260:1513182] (
    XIAOMING,
    ROSE
)
2016-07-31 01:33:36.466 test[4260:1513182] {
}

(2)NSMutableDictionary的使用注意
// 字典的使用注意
NSMutableDictionary *dict = @{@"name" : @"j"};  //这样会报警
[dict setObject:@"xiaoming" forKey:@"name"];

注意:这种快速创建的方式只适用于不可变字典。