分类(Category)
- 分类可以做些什么
- 声明私有方法
- 分解体积庞大的类文件
- 把Framework的私有方法公开
如通过method_exchangeImplementations
方法将私有方法替换为自己的方法+ (void)load{ //通过runtime进行方法交换 /* 如这里通过方法交换将UIVeiw.intrinsicContentSize的getter方法 替换为自定义的intrinsicContentSizeSwizzle方法 */ Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"intrinsicContentSize")); Method method2 = class_getInstanceMethod([self class], @selector(intrinsicContentSizeSwizzle)); method_exchangeImplementations(method1, method2); }
- 分类有哪些特点
- 运行时决议:分类的内容如方法列表、属性(通过关联对象实现)等是在运行时(runtime)动态的添加到宿主类中(最大的特点也是与扩展最主要的区别)
- 可以为系统类添加方法(扩展不能为系统类添加方法)
- 分类添加的方法优先级大于宿主类中的同名方法(原因:通过
runtime
源码得知,分类的方法列表会在运行时动态的插入到宿主类方法列表的前面
,在通过消息机制进行方法查找时,优先找到的是靠前的分类方法,所以会产生“覆盖”的效果)- 多个分类中出现了
同名方法
,谁能生效取决于编译顺序(Build Phases->Compile Sources)
,最后编译
的分类方法生效- 名字相同的分类会引起编译报错
分类重写原类方法时,如何调用原类方法:通过遍历类的方法列表,获取方法在方法列表methods的索引(获取最后一次出现的索引即为主类同名方法的索引),然后调用即可。
- (void)callClassMethod:(NSString *) methodName className:(Class)className{ u_int count; Method *methods = class_copyMethodList([className class], &count); NSInteger index = 0; for (int i = 0; i < count; i++) { SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; if ([strName isEqualToString: methodName]) { index = i; // 先获取原类方法在方法列表中的索引 } } // 调用方法 id obj = [[className alloc] init]; SEL sel = method_getName(methods[index]); IMP imp = method_getImplementation(methods[index]); ((void (*)(id, SEL))imp)(obj,sel); }
- 分类中都可以添加那些内容
- 实例方法
- 类方法
- 协议
- 属性(setter、getter,需要通过关联对象来创建实例变量)
- 分类的实现原理
- 分类的内容如方法列表、属性(通过关联对象实现)等是在运行时(runtime)动态的添加到宿主类中
- 由于分类的方法列表会在运行时动态的插入到宿主类方法列表的前面,在通过消息机制进行方法查找时,优先找到的是靠前的分类方法,所以会产生“覆盖”宿主类方法的效果)
关联对象相关
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object);
关联对象的本质
关联对象由AssociationsManager(静态)管理并在AssociationsHashMap 中存储
所有对象的关联内容都在同一个全局容器
中。
思考:
如果有关联对象,在dealloc中需要对关联对象进行清理吗
不需要的,在dealloc的内部实现中,系统会帮我们清楚关联对象(_object_remove_assocations()
)
扩展(Extension)
- 扩展可以做什么
- 声明私有属性
- 声明私有方法
- 声明私有变量
- 扩展的特点
- 编译时决议
- 只以声明的形式存在,多数情况下寄生于宿主类的.m中。
- 不能为系统类添加扩展
分类和扩展的区别
代理(Delegate)
- 代理是什么
-
代理模式
是一种设计模式
- iOS当中以
@protocol
形式体现- 传递方式
一对一
协议中即可以定义属性
也可以定义方法
,由代理方负责实现。
@optional
下的为代理方可以选择实现的方法(默认),@required
下的为代理方必须实现的方法。
2.代理的使用中需要注意的问题
一般声明为weak以避免产生循环引用。
通知(NSNotification)
- 通知是什么
- 是使用
观察者模式
来实现的用于跨层传递消息的机制。- 传递方式为
一对多
。
- 通知机制是如何实现的
由NofificationCenter维护一个Notification_Map,key为notificationName,value为观察者列表,成员为一个存有观察者的对象指针以及所要执行的SEL方法指针
KVO
1.KVO是什么
- KVO是Key-value observing的缩写
- KVO是Objective-C对
观察者设计模式
的一种实现- Apple使用了isa混写(
isa-swizzling
)来实现KVO
isa混写
:通过addObserver: forKeyPath: options: context:
为ClassA
的属性
添加观察者对象
后,会动态的生成ClassNSKVONotifying_A
(ClassA
的子类),并将原本指向A
对象的isa指针指向NSKVONotifying_A
,NSKVONotifying_A
会重写Setter
方法,重写后的Setter
方法负责通知所有观察对象属性的修改。这种改变被观察者A对象的isa指针的方式就成为isa混写
观察者对象
通过实现observeValueForKeyPath: ofObject: change: context:
方法来监听的属性修改- 通过两个断点及命令行方法
po object_getClassName(obj)
来查看调用addObserver: forKeyPath: options: context:
方法前后obj指针指向的ClassName
- NSKVONotifying_A 对Setter方法的重写
//NSKVONotifying_A重写后的Setter方法 - (void)setValue:(id)obj{ [self willChangeValueForKey:@"keyPath"]; [super setValue: obj] //didChangeValueForKey:方法会触发observeValueForKeyPath: ofObject: change: context:方法回掉,来通知观察者Value发生变化 [self didChangeValueForKey:@"keyPath"]; }
- 需要注意的问题
- 通过KVC设置Value能否生效:可以的,KVC方法会调用Setter方法,进而触发KVO使之生效
[obj setValue:@2 forKey:@"value"];
- 通过成员变量直接赋值value能否生效:不可以的,因为没有调用Setter方法
//可以通过手动添加KVO方法实现 //变量赋值之前添加willChangeValueForKey:方法,赋值之后添加didChangeValueForKey:方法来实现手动添加KVO [self willChangeValueForKey:@"keyPath"]; _value += 1; [self didChangeValueForKey:@"keyPath"];
总结:
- 使用
setter
方法改变值KVO才会生效- 使用
KVC(setValue:forKey:)
改变值KVO才会生效- 成员变量直接修改需要
手动添加
KVO才会生效
KVC
1.KVC 是什么
- KVC是Key-Value coding的缩写(Apple为我们提供的一种
键值编码技术
)- 允许开发者通过
key
直接访问对象的属性方法
或者成员变量
,而不需要调用明确的存取方法。valueForKey:
获取对象中和key同名或相似名称对象的值setValue:forKey:
设置对象中和key同名或相似名称对象的值valueForKey:
与setValue:forKey:
详细的搜索方法可以在KVC官方文档中找到。
+ (BOOL)accessInstanceVariablesDirectly
方法,如果返回NO,可以避免KVC访问自己的成员变量(没有setter
、getter
方法的变量)
相关KVC底层的一些介绍 iOS底层原理探究之----KVC
- 这种键值编码技术是否会破坏面向对象的编程方法或者是否会有违面向对象的编程思想
由于key是没有限制的,所以只要我们知道了对应类的私有成员变量(变量、属性都可以)的名字,就可以通过setValue:forKey对其值进行修改,所以是有违面向对象的编程思想的
属性关键字
@property
的本质是什么?
@property
=ivar
+getter
+setter
;
属性
(property)有两大概念:ivar
(实例变量)、存取方法(access method =getter
+setter
)。
属性
(property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。
@synthesize和@dynamic分别有什么作用?
①@property
有两个对应的词,@synthesize
(默认)和@dynamic
。如果@synthesize
和@dynamic
都没写,那么默认的就是@syntheszie var = _var
;
②@synthesize
的语义是如果你没有手动实现setter
方法和getter
方法,那么编译器会自动为你加上这两个方法。
③@dynamic
告诉编译器:属性的ivar
、setter
和getter
不自动生成。(当然对于readonly
的属性只需提供getter
即可)。假如一个属性被声明为@dynamic var
,然后你没有提供setter
和getter
方法,编译的时候没问题,但是当程序运行到instance.var = someVar
,由于缺少setter
方法会导致程序崩溃;或者当运行到someVar = instance.var
时,由于缺少getter
方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。@interface TestClass() //{ // @private // NSString *_name; //} @property (nonatomic, copy) NSString *name; @end @implementation TestClass @dynamic name; //不会自动生成 _ name 变量和 get、set 方法 - (NSString *)name{ //方法①:可以通过关联对象来实现setter、getter方法 NSString *str = objc_getAssociatedObject(self, @selector(name)); if (str) { return str; } [self setName:name]; return name; //return _name; 方法②:可以通过成员变量来实现setter、getter方法 } - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, @selector(name), @"", OBJC_ASSOCIATION_COPY); //_name = name; } @end
1.属性关键字主要类别
- 读写权限:
reloadonly
只读,readwrite
(默认)可读写- 原子性:
atomic
(默认)原子性,nonatomic
非原子性- 引用计数:
retain
(MRC)/strong
(ARC),assin
(默认)/unsafe_unretained
(MRC中使用较多,ARC中基本不使用),weak
,copy
ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
对应基本数据类型默认关键字是
atomic
,readwrite
,assign
对于普通的OC对象
atomic
,readwrite
,strong
- atomic关键字
atomic
关键字的线程安全是针对setter
、getter
方法即对象的读写操作来说的,对于对象的操作并不能保证线程安全,如对atomic
修饰的NSMutableArray
进行增删操作就无法保证是线程安全的。atomic
内部是通过@synchronized
来实现原子性的
关于@synchronized的文章: 正确使用多线程同步锁@synchronized//atomic时,setter、getter方法的默认实现 - (void)setAge:(int)age { @synchronized(self){ _age = age; } } - (int)age { @synchronized(self){ return _age; } }
- assing关键字
assing
关键字的特点:修饰基本数据类型,如int
,BOOL
等;修饰对象类型时,不改变其引用计数;当其修饰对象被销毁时仍指向原内存地址,从而造成野指针
(悬垂指针)。
- weak关键字
weak
关键字特点:修饰对象,不改变其引用计数;当其修饰对象被销毁时,会自动将指针置nil
;weak
修饰的指针默认值是nil
。
weak
可以自动将空指针置nil原理:runtime
维护了一个Weak表
,weak_table_t
用于存储指向某一个对象的所有Weak
指针。Weak
表其实是一个哈希表
,key
是所指对象的地址
,value
是weak指针的地址的数组
。在对象回收的时候,就会在weak
表中进行搜索,找到所有以这个对象地址为键值的weak
对象,从而置为nil
。由于维护了一个weak
表,所以对性能有些许影响。
- unsafe_unretained关键字
unsafe_unretained
关键字特点:修饰基本数据类型,如int
,BOOL
等;修饰对象时,不改变其引用计数;当其修饰对象被销毁时,指针仍指向原内存地址,从而造成野指针
(悬垂指针)(基本被assing替代)。
- copy关键字
copy
关键字:修饰对象,对其执行copy操作浅拷贝
:对内存地址
的复制,让目标对象指针与源对象指针指向同一片内存空间
,不会引起内存空间的分配;会让源对象内存引用计数加1
。深拷贝
:对内存
的复制,让目标对象指针指向一片内容与源对象指针内容相同
的内存空间,会引起内存的分配
;不会让源对象内存引用计数加1。区分深拷贝与浅拷贝:
① 是否开辟了新内存空间(深拷贝会开辟新内存空间而浅拷贝不会);
② 是否影响引用计数(深拷贝不会影响引用计数而浅拷贝会影响被拷贝对象的引用计数);
copy
与mutableCopy
:
① 对可变对象
的copy
操作和mutableCopy
操作都是深拷贝
;
② 对不可变对象
的copy
操作是浅拷贝
,mutableCopy
操作是深拷贝
;
③copy
返回的都是不可变对象
,mutableCopy
返回的都是可变对象
;@property(copy)NSMutableArray *array;// 会产生什么问题
①如果赋值过来的是NSMutableArray
,copy
操作之后变成了NSArray
,是深拷贝
;
②如果赋值过来的是NSArray
,copy
操作之后是NSArray
,是浅拷贝
;
③不论赋值过来的是NSArray
还是NSMutableArray
,copy
操作之后都是NSArray
,如果调用NSMutableArray
相关的如addObject:
等方法,会引起crash
;
- MRC下如何重写retain修饰变量的setter 方法
@interface Test
@property (nonatomic, retain) id obj;
@end
@implementation Test
- (void)setObj:(id)obj{
/*
这里如果不进行!=判断,如果恰好_obj==obj,并且_obj的引用计数为1,
调用[_obj release]后obj对象会被销毁,obj对象变为nil,
这个时候_obj = [obj retain]相当于_obj = [nil retain],
后续程序对_obj对象进行操作时,会引起crash。
*/
if (_obj != obj){
[_obj release];
_obj = [obj retain];
}
}
@end