84334052 2019-06-27
在OC中,定义一个类的过程和C++类似,先有一个声明,然后再实现相应的函数。不过C++比较自由,既可以像Java一样所有函数都在类内实现,也可以不都在类内实现,将一部分函数或者全部函数转移到类外实现。而在OC中这方面的规定较为严格。即——只能在声明中定义变量(亦或称为属性),只能在类实现中实现方法(函数)。具体使用形式如下:
//Objective-C类的声明 @interface 类名 : 父类名 <实现的协议1,实现的协议2, … > //定义变量、声明方法 @end
从中可以看出,OC和Java一样,不允许多重继承,而多重继承的类似实现方式可以使用协议,也可以使用类目来进行。
一般而言,类的声明写在头文件(h文件)中,因为OC在#import导入文件的时候,只允许导入头文件,所以在头文件中定义的变量(属性)和方法(函数)都是对其他文件可见的。
OC中类的实现用于实现在声明中或者扩展中定义的方法(函数),类目实现用于实现类目声明中定义的方法(函数)。前者的使用形式如下:
//Objective-C类的实现 @implementation 类名 //实现方法,禁止定义变量 @end
值得注意的是,可以实现没有在声明中声明的方法,因为一般只会导入h文件,所以这样的方法是对其他类隐藏的(类似于C++的私有函数),但是这些被隐藏的方法并非是私有的,通过一些途径还可以访问只在实现中实现的方法。但是Apple不建议您这样做。
记住:不能在实现中定义变量。
OC中为了简化继承结构,提出了一种更简单的向现有类添加方法的手段,这被称为Category(类目)。Category是OC语言特有的,并且这种特性让很多其他语言为之眼红。Category可以很方便的向现有的类添加方法,但是注意不可以添加变量。Category不会构造出一个新的子类,所以它不是继承关系,被添加的方法和原来在父类中的方法的地位等同,但是如果出现了覆盖现象,则只能调用Category定义的方法。使用形式为:
//Objective-C的类目声明 @interface 类名 (类目名) //添加方法的声明,禁止添加变量 @end
//Objective-C的类目实现 @implementation 类名 (类目名) //添加方法的实现 @end
请注意,Category并不是一个类,你依然只能使用原有类的名字,但是通过这个类或者这个类的对象可以使用所有Category中的所有方法,就好像他们原本就在类中被声明和实现一样,这无疑是非常方便的。
在OC中,除了可以使用Category向现有的类添加方法之外,还可以使用Extension(扩展),在形式上,Extension就好像一个匿名的Category,但是Extension允许你向类中添加变量和方法,而且Extension不需要单独编写实现,需要实现的方法应该直接写在原有的@implementation中,形式如下:
//Objective-C的扩展声明 @interface 类名 () //添加方法的声明,添加变量 @end
上面添加的方法应该直接在类的实现中实现(参考Objective-C类的实现)
一般而言Extension定义在m文件中,所以Extension中的属性和方法是不对外部和子类公开的,但这并不是绝对的。实际上,因为在导入文件的时候只导入了h文件,并没有导入m文件,所以实际情况是,其他文件中的类和对象根本就找不到m文件中的东西,因为本来它们就只导入了h文件。如果导入m文件的话,也是可以访问Extension中的函数的。但是大多数情况下,系统编写的类或者非开源的类不会公开m文件,也就是隐藏了方法的实现,这样的话Extension中的函数自然就被隐藏了。
在OC中,也存在类似于C++和Java“构造函数”的东西,但是其机制和C++和Java语言有很大不同。OC中建立一个对象要分两个步骤,一个是发送alloc消息分配空间,一个是发送init消息初始化对象。在OC中没有真正的“构造函数”——也就是那个和类名相同的、无返回值的函数。在OC中,方法名以类名开头(首字母小写)的方法一般是静态初始化方法,和Java中的工厂方法比较类似。例如上文中NSString*类型的
+(instancetype)stringWithFormat:
+(instancetype)stringWithCString:encoding:
等。这类函数可以直接返回一个初始化好的对象。而另一类以init开头的方法一般是动态初始化方法,例如NSString *类型的-initWithFormat:、-initWithCString:encoding:等。这类方法必须在先发送过alloc消息的基础上才可以发送,否则会抛出异常。
此外,和C++、Java特别不同的一点是,OC的初始化方法是有返回值的,其返回值就是当前类的对象指针,例如NSString类型的初始化方法将返回NSString类型,在方法末要加上return self。但是在OC继承中,因为OC的初始化方法就是普通方法,所以也会一并继承到子类中,如果不覆盖继承来的初始化方法,那么这个方法就会返回错误的类型——也就是返回一个父类对象指针。Apple为了解决这个问题,引入了instancetype类型,这个类型可以自动返回该类的对象指针。所以我们在自己编写初始化方法的时候,一律使用instancetype类型作为返回值。注意:instancetype类型和id类型是不同的,instancetype类型是当前类的对象指针,而id类型是任意类型的对象指针。
在定义一个属性或者一个变量的时候,可以指定变量的引用计数特性,包括retain、assign、copy、strong、weak、unsafe_unretained,这些属性都和Objective-C的引用计数机制有关。
定义引用计数属性的方式:
//使用property: @property (特性) 类型 属性名; //不使用property: __特性 类型 变量名;
下面详细讲解引用计数机制:OC为了解决C语言内存管理的问题,使用引用计数的方法来管理内存。首先我们知道,在OC中所有的对象都是指针,对象所占用的内存都是动态分配产生的,按照C和C++的原理,这些内存需要用户自己去管理。但是在OC中,只要使用了引用计数,用户就无需使用手动管理内存。我们在程序中申请一片空间用于存放对象的时候,系统会自动为该空间建立引用计数,每有一个指针指向这块空间,该空间的引用计数就加1,当一个指针不再指向该内存空间的时候(改为指向nil或者指向其他内存空间),这块空间的引用计数就降为0,被立即释放,同时指针也不会指向任意位置内存,而是指向nil来保证安全。这就是引用计数机制。在iOS5以及更高版本引入了ARC机制,这样我们可以更方便的管理内存。上述关键字对属性或者变量的引用计数特性的影响如下:
retain:使用引用计数,在函数参数传递时,使用的是指针传递,引用计数+1,引用计数=0的时候对象将释放,这是除NSString*以外的类型的默认设置,只能用于对象。
copy:参数传递时拷贝对象,在函数参数传递时,使用的是值传递,原有对象的引用计数不变,直接为形参建立新的内存空间,引用计数=0的时候对象将释放,这是NSString*类型的默认设置,只能用于对象。
strong:强指针类型,使用引用计数,strong是ARC机制的关键字,本质上和retain没有区别。
weak:弱指针类型,使用引用计数,weak是ARC机制的关键字,和retain类似,但是使用weak指针可以保证不会出现强引用循环现象(如果不能理解的话就随便看看吧)。
**强引用循环:两个strong指针互相指向的时候,这两块内存空间的引用计数都无法降为0,这时候就会发生内存泄漏,这称为强引用循环。将其中一个指针改为weak指针即可解决此问题。unsafe_unretained:不使用引用计数,完全按C和C++的模式管理。
assign:不使用引用计数,值传递,只能用于NSInteger等基本类型。
Objective-C的Protocol(协议)类似于Java的接口,是一些方法声明的集合,在协议中可以规定若干方法,方法分为@required类型(默认为此类型)和@optional类型,前者是必须实现的,后者是可选实现的。一个类可以实现多个协议,但是只能继承一个父类,在实现协议的时候,必须实现协议中规定的@required方法,否则会出现警告。协议实际上可以理解为“多个类之间的约定”。通过实现相同的协议,一个类可以安全的调用另一个类的某些方法,只要他们都位于协议中并且是@requierd的。在UITableView的实现中就用到了这种关系,在UITableView类中会自动调用一些方法来设置UITableView的一些属性,而这些属性应该在UITableView对应的视图控制器中进行设置,所以要将这些方法放入对应的UIViewController类中,如何确保这个类中确实存在这些方法,在调用的时候不会出错呢?只需要规定一个协议,把需要调用的方法放入其中,让使用UITableView的视图控制器去实现即可。这种方式被称为委托设计模式。详见Delegate设计模式
协议的定义:
@protocol 协议名 <父协议> @required //必须实现的方法声明 @optional //可选实现的方法声明 @end
注意:协议中不可以实现方法,只能声明,方法的实现应该交给实现这个协议的类来完成。
协议的实现
@interface 类名 : 父类名 <实现的协议1,实现的协议2, …> … @end
@implementation 类名 //实现协议中规定的方法 @end
使用IBAction的时候,实际上是用到了Taget-Action设计模式。这种设计模式的工作方式是:当某个特定的Event发生的时候,发送者(sender)会向一个指定的目标(target)发送一个之前设定好的IBAction消息。例如当一个UIButton被点击的时候,该按钮就是发送者(sender),通过定义IBAction,将其目标声明为对应的UIViewController,同时UIButton调用了UIViewController中对应的IBAction消息,这就是Target-Action设计模式的工作方式。
在Target-Action设计模式中,针对不同的Event,需要定义不同的IBAction方法。实现类似的功能还可以使用Delegate(委托)设计模式。这种设计模式的原理是,原本的消息发送者将具有一个delegate属性,可以将其他类定义成消息发送者的delegate。在该对象的某个事件发生的时候,因为该对象可能不能自己处理这个事件,所以它就通过“委托”的方式,调用Delegate方法来实现相应的功能,这种设计模式称为Delegate设计模式,是通过协议来实现的。
例如当UITableView的某个单元格被点击的时候,实际上UITableView自身是无法处理这个事件的,单元格被点击之后要做什么应该由其对应的UIViewController来决定,这时候就把UITableView的delegate属性设置为这个视图控制器,并且这个视图控制器来实现UITableView的一个协议(协议中规定了作为一个合格的委托应该实现的方法)。这样在视图控制器类里实现对应的代理方法(委托方法),就完成了这一事件的处理。
OC中所有类的共同父类是NSObject*类型,其中包含了一些基本的方法,例如alloc和init等。
OC的字符串类型:NSString*类型,默认属性是copy,可以直接用赋值语句来创建一个字符串,也可以使用初始化方法创建,一般使用以下几种:
+(instancetype)stringWithFormat:(nonnull NSString *), …
该方法可以用各种类型的数据生成一个NSString*对象
+(instancetype)stringWithCString:(nonnull const char *) encoding:(NSStringEncoding)
该方法可以使用encoding指定的编码,利用一个const char类型的C风格字符串初始化一个NSString对象
还有很多初始化方法,可以根据Xcode的自动提示或者API文档学习,将上述方法中的string改为init,就是对应的动态方法(带“-”号的)。上面的都是静态方法,在实际应用过程中,静态方法的使用更为广泛。
还有一些方法可以将字符串转化为数字,一般使用以下几个:
-(NSInteger)integerValue
该方法可以将字符串转换为整数,如果向含有非数值的字符串发送该消息,则会自动截断非数值部分。
-(int)intvalue
和上面的方法类似,只不过是转化为int类型(NSInteger在64位机上是long)。
-(double)doubleValue
和上面的方法类似,只不过是转化为double类型。
NSString是常量字符串,其值不可改变,可以改变值的字符串类型是NSMutableString类型,但是我们形式上也可以通过赋值“改变”一个NSString字符串的值,但是这种改变实际上是重新申请了一个对象,详见引用计数机制。
OC和Java一样,数组都作为类来出现,这样的好处就是可以使用各种方法,扩展数组的功能。OC中的数组类型也分NSArray和NSMutableArray类型,区别和上面的字符串类型相同,一个是常量数组,一个是可变数组(实际上就是链表)。这里的可变指的是数组的长度,也就是说NSMutableArray的长度是可以随时扩展的,而NSArray的长度和C++一样,不能扩展。下面介绍NSArray*的常用方法:
初始化方法:
+(instancetype)arrayWithObjects:(nonnull id), … , nil
这个方法可以返回一个数组,并通过后面的参数向数组中添加元素
+(instancetype)array
建立一个空数组
-(id)objectAtIndex:(NSUInteger)
返回以给定无符号数为下标的元素,返回值是任意对象的指针
-(id)objectAtIndexes:(nonnull NSIndexSet*)
返回一个元素组(多个元素),NSIndexSet*是一个元素组类型,在介绍UITableView时会介绍。
NSMutableArray*的常用方法:
+(instancetype)arrayWithObjects:(nonnull id), … , nil
这个方法可以返回一个数组,并通过后面的参数向数组中添加元素
-(id)objectAtIndex:(NSUInteger)
返回以给定无符号数为下标的元素,返回值是任意对象的指针
+(instancetype)array
建立一个空数组
-(void)addObject:(nonnull id)
向数组中插入一个元素(添加到末尾)
-(void)removeObjectAtIndex:(NSUInteger)
删除数组中指定的元素