几天前突然在别人的类的.m文件中看到这么一句代码:@synthesize xxxx = _xxxx; 当时愣是没理解啥意思,过后才缓过神来发现原来是把一些类的基础知识忘记了,虽然不用过多去深究以前的一些旧东西,但但是既然遇到了,还是复习一下。
一、类与对象
1.类:类是定义同一类所有属性和方法的蓝图或原型。
2.对象:用来描述客观事物的一个实体,由具体的属性和方法构成。
3.类与对象的关系:类是用来制作无数实体(对象)的工程图纸。
4.类的特征:属性
5.类的行为:方法
二、封装
1.类就是封装,封装了属性与方法。它是一种思想,其核心就是“暴露出必要的内容给外部用(属性方法私有),而对于内部细节,使用者不用去关心”。
我们声明一个Teacher类:
#import <Foundation/Foundation.h>
@interface Teacher : NSObject
{
// @public 公共的
NSString *_name; //成员变量(实例变量) 默认@protected修饰(受保护的)
/* 顺带一提:@protected、@public、@private、@package
* @public 成员变量可以被在任何地方访问。
* @protected 成员变量能被声明它的类和子类访问(默认)
* @private 成员变量只能在声明它的类中访问(默认现在用@Property关键字生成的成员变量是这个)
* @package 一个@package成员变量在实现这个类的可执行文件镜像中实际上是@public的,但是在外面就是@private。
*/
}@end
然而,我们此时在外面根本无法访问到这个受保护的成员变量。
访问该成员变量的方法:
1> 打开上面 “@public”的注释,在外面以"->“方式访问
Teacher *tec = [[Teacher alloc] init];
tec->_name = @"姓名";
2> 封装set、get方法进行间接访问,在.h文件中加上方法声明,在.m文件中实现
-(void)setName:(NSString *)name;
-(NSString *)getName;
-(void)setName:(NSString *)name
{
_name = name;
}
-(NSString *)getName
{
return _name;
}
这样,便可以在外面通过调用方法的方式来间接访问该成员变量,其实下面讲的@property关键字就是帮你解决了这个set、get方法的访问
让其在外面以"."的方式(本质上是调用set和get方法)间接访问。
Teacher *tec = [[Teacher alloc] init];
[tec setName:@"testName"];
NSLog(@"%@",[tec getName]);
总结一下成员变量和成员属性:
①成员变量即实例变量
②成员属性用于间接访问类中的成员变量
③假如你想让类的一个特性私有,只在本类访问,就定义成成员变量;假如你想在类之外访问该类的一个特性,你就将其定义成成员属性。
三、@property和@synthesize关键字
1.在以前,我们经常可以看到这种类的声明
#import <Foundation/Foundation.h>
@interface Teacher : NSObject
{
NSString *_name; //成员变量
}
@property (nonatomic,copy)NSString *name; //成员属性
@end
#import "Teacher.h"
@implementation Teacher
@synthesize name = _name;
@end
这是在之前的编译器特性中:
@property帮我们声明了name属性的set和get方法
@synthesize name = _name则表示对@property声明的属性name实现set和get方法,后面的” = _name“表示实现的时候访问成员变量_name
2.而现在我们为类声明一个属性则通常只需要一句话(我一般都用这种,方便):
@property (nonatomic,copy)NSString *name; //这里指在.h文件中声明
这是在新的编译器特性中:
@property直接帮助我们干了三件事:
①当该name属性的成员变量未被指定时,会生成一个默认为_name的成员变量,但是该属性为@private(私有的),自己写的则是@protected(受保护的)
②声明name属性的set、get方法
③实现name属性的set、get方法
注:如果你想要用自己生成的成员变量,则用之前的那种格式,在.h文件中增加一个成员变量,在.m文件中加上@synasize xxx = _xxx。
成员变量用”_“下划线开头命名的好处:
①区分成员变量和属性
②防止局部变量和成员变量命名冲突
四、关于继承的几句话
1.继承:建立类之间的关系,实现代码的重用性,方便系统扩展。
2.继承是为了避免多个相似的类中相同的成员变量反复定义而延伸的一个特性。
3.继承有单根性与传递性
4.被继承的类称之为父类或基类,继承的类称之为子类或派生类
5.super关键字:self用于访问本类成员,而super则是用于在子类中访问父类成员
五、分类catagory
分类:①在不改变原来类的基础上,为类增加一些方法,来对类进行一个扩展。
②在开发中,我们一般对都是为系统提供的类添加分类。还有就是将自己的类分模块,将实现不同功能的方法写在不同的分类中,一个类可以有无限个分类。
1.分类又称为非正式协议:NSObject类及其子类的一个类别(catagory)。
#import "Teacher.h"
@interface Teacher (Log)
+(void)log;
@end
#import "Teacher+Log.h"
@implementation Teacher (Log)
+(void)log
{
NSLog(@"分类");
}
@end
如上面所写的分类,我们为Teacher类添加了一个分类"Teacher+Log.h",分类中声明并实现了类方法+(void)log;
2.类的延展:匿名分类。
在Teacher.m文件中
#import "Teacher.h"
@interface Teacher () //这种写法就是匿名分类
@property (nonatomic,copy)NSString *nickName; //匿名分类中可以添加属性,但是该属性默认为@private,只能在本类中使用
+(void)log; //声明了一个类方法log
@end
@implementation Teacher
@synthesize name = _name;
//实现该类方法
+(void)log
{
NSLog(@"匿名分类");
}
@end
接着我们调用log方法,出现如下打印
这里证明:分类(非正式协议)会重写本类及其类扩展(匿名分类)的方法。
3.catagory中匿名分类允许添加属性并会自动生成成员变量和set、get方法;但是在非正式协议中,允许你用@property声明一个属性,不会为你提供set、get方法以及成员变量,如果你直接使用的话,造成崩溃。如下
1> 我为Teacher + MyProperty.h分类头文件中增加了一个属性nickName;
#import "Teacher.h"
@interface Teacher (MyProperty)
@property (nonatomic,copy)NSString *nickName;
@end
2>使用(缺少set、get方法导致崩溃)
Teacher *tec = [[Teacher alloc] init];
tec.nickName = @"nickNmae";
NSLog(@"%@",tec.nickName);
// 打印
2016-01-24 16:58:13.656 分类[2002:159549] -[Teacher setNickName:]: unrecognized selector sent to instance 0x100213e10
2016-01-24 16:58:13.658 分类[2002:159549] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Teacher setNickName:]: unrecognized selector sent to instance 0x100213e10'
*** First throw call stack:……
3>如何在非正式协议(分类)中添加属性【oc针对这一现象,提供了一个解决方案:关联对象(Associated Object)】,在分类的.m文件中写上如下代码
#import "Teacher+MyProperty.h"
#import <objc/runtime.h>
static void *strKey = &strKey;
@implementation Teacher (MyProperty)
-(void)setNickName:(NSString *)nickName
{
objc_setAssociatedObject(self, &strKey, nickName, OBJC_ASSOCIATION_COPY);
}
-(NSString *)nickName
{
return objc_getAssociatedObject(self, &strKey);
}
@end
六、协议protocol
1.有非正式协议,自然也有正式协议,protocol便是,用于声明一大堆方法,等待实现,只要某个类遵守了某个协议,那么这个类就拥有了该协议的所有方法声明。(注意:协议只存在.h声明文件)其格式如下:
@protocol Teach <NSObject>
@required;
-(void)teach:(NSString *)text; // 声明了一个teach的方法,并且为必须实现,默认是@optional;
@end
//新建了一个名为Teach.h的协议头文件
2.类可以遵守协议(Teacher遵守teach协议)
3.实现方法
-(void)teach:(NSString *)text
{
NSLog(@"老师教书");
}
4.一个类只能继承自一个父类(继承的单根性),但是一个类可以遵守多份协议.
七、关联对象(Associated Object)--扩展
1.关联对象类似于成员变量,但是它是在运行时被添加的。(用于解决分类中添加属性的问题,上面已经介绍了原因,这里不做介绍)
2.我们可以把关联对象想象成一个OC对象,这个对象通过key连接到一个类的实例变量上。
3.由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。内存管理策略选项值如下:
// 当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象。当我们需要在多个线程中处理访问关联对象的多线程代码时,这就非常有用了
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
/* 如果指定的策略是assign,则宿主释放时,关联对象不会被释放;
而如果指定的是retain或者是copy,则宿主释放时,关联对象会被释放。
我们甚至可以选择是否是自动retain/copy。
*/
4.使用
1> void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)方法:将一个对象连接到其它对象
参数1:源对象,一般传self.
参数2:key,用来表示是哪一属性的key,可能在分类中添加不止一个属性。
常见有三种写法:
① static void *strKey = &strKey;
②
static NSString *strKey = @"strKey";
③ static char strKey;
参数3:关联的对象
参数4:关联策略
self对象将获取一个新的关联的对象anObject,且内存管理策略是自动retain关联对象,当self对象释放时,会自动release关联对象。另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象,这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存。
2> id anObject = objc_getAssociatedObject(self, &myKey)方法 ,获取关联的对象
3> objc_removeAssociatedObjects(self)方法,移除所有关联
5、应用(UIAlertView + Block分类,以自身为delegate监控选项按钮点击事件,以block作为关联对象)
#import <UIKit/UIKit.h>
typedef void(^CompleteBlock) (NSInteger buttonIndex);
@interface UIAlertView (Block)
// 用Block的方式回调,这时候会默认用self作为Delegate
- (void)showAlertViewWithCompleteBlock:(CompleteBlock) block;
@end
#import "UIAlertView+Block.h"
#import <objc/runtime.h>
@implementation UIAlertView (Block)
static char key;
// 用Block的方式回调,这时候会默认用self作为Delegate
- (void)showAlertViewWithCompleteBlock:(CompleteBlock)block
{
if (block) {
//移除所有关联
objc_removeAssociatedObjects(self);
/**
1 创建关联(源对象,关键字,关联的对象和一个关联策略。)
2 关键字是一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字。
3 关联策略表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。
*/
objc_setAssociatedObject(self, &key, block, OBJC_ASSOCIATION_COPY);
//设置delegate
self.delegate = self;
}
[self show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
///获取关联的对象,通过关键字。
CompleteBlock block = objc_getAssociatedObject(self, &key);
if (block) {
///block传值
block(buttonIndex);
}
}
/**
OC中的关联就是在已有类的基础上添加对象参数。来扩展原有的类,需要引入#import <objc/runtime.h>头文件。关联是基于一个key来区分不同的关联。
常用函数:
objc_setAssociatedObject 设置关联
objc_getAssociatedObject 获取关联
objc_removeAssociatedObjects 移除关联
*/
//当然也可以不必这么麻烦,使用继承同样可以轻易做到。
@end
八、类的加载(认识两个方法)
/**
* 这两个方法是在程序运行一开始就被调用的方法.
* 我们可以利用他们在类被使用前,做一些预处理工作.
* 比如我碰到的就是让类自动将自身类名保存到一个NSDictionary中.
*/
//而initialize是在类或者其子类的第一个方法被调用前调用。
+(void)initialize;
//load方法是只要类所在文件被引用就会被调用.
+(void)load;
/**
* 所以如果类没有被引用进项目,就不会有load调用;
* 但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用
*/
九、反射
1.反射:简而言之就是通过类名返回类的对象(使用前导入#import <objc/runtime.h>头文件,今天前面的关联对象也用到这个头文件,好像关于这个runtime要学的东西有点多,复习着一下子扒出了好多要学的新东西,- _ -,先从基础的慢慢来吧)
// 类名返回类对象
+(NSObject *)createBean:(NSString *)className
{
Class tempClass = NSClassFromString(className);
NSObject *obj;
if (tempClass)
{
obj = [[tempClass alloc]init];
}
return obj;
}
2.类名得到属性名集合
+(NSArray *)propertyOfClass:(NSString *)className
{
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:0];
//通过类名获得类的属性
const char *cClassName = [className UTF8String];
id theClass = objc_getClass(cClassName);
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(theClass, &outCount);
for (i = 0; i < outCount; i++)
{
objc_property_t property = properties[i];
NSString *propertyNameString = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[arr addObject:propertyNameString];
}
return arr;
}
3.通过反射(结合KVC),通过类名、数据源、约定,我们就可以实现json字符串转对象。
4.具体之后三方库学习MJExtension中NSObject + MJClass转模型。
十、类簇
1.类簇是Foundation框架中广泛使用的设计模式(抽象工厂模式)。类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。
2.类簇的概念(选自百度百科。。。)
类簇 是一群隐藏在通用接口下的与实现相关的类,使得我们编写的代码可以独立于底层实现(因为接口是稳定的)。
如创建NSString对象时,你得到的可能是NSLiteralString,NSCFString,NSSimpleCString等。即不同的NSString对象调用同一个接口A,接口A的实现可能是不同的。
在Foundation框架中,常见的类簇有NSNumber,NSString,NSArray,NSDictionary等。 想要在类簇中创建子类会困难一些,必须是抽象超类的子类,必须重载超类的原始方法,必须声明自己的数据存储。最方便的是使用组合或者类别来代替子类化。
3.关于重写init方法时候调用父类的init方法:self = [super init]和[super init]
咋看之下,产生了一个疑问,为什么[super init]要赋值给self。今天看了下类簇的概念,终于明白了,由于在有些情况下,init可能会改变返回的对象,这时候必须赋值给self。
4、不推荐继承类簇
当你继承某个类簇的超类之后,子类可以使用父类的方法,结果你去调用该类簇的超类的方法,你会发现程序崩溃了,原因是由于在超类的方法的实现里,你不知道它实际调用的它里面的哪一个子类的方法(这些组合的子类声明和实现都隐藏在该超类的.m文件里面),结果你的类是直接继承自该类簇的超类,也就是说你的类根本就不认识这个方法。