Objective-C 特性相关

分类(Category)

  1. 分类可以做些什么
  • 声明私有方法
  • 分解体积庞大的类文件
  • 把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);
}
  1. 分类有哪些特点
  • 运行时决议:分类的内容如方法列表、属性(通过关联对象实现)等是在运行时(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);
}
  1. 分类中都可以添加那些内容
  • 实例方法
  • 类方法
  • 协议
  • 属性(setter、getter,需要通过关联对象来创建实例变量)
  1. 分类的实现原理
  • 分类的内容如方法列表、属性(通过关联对象实现)等是在运行时(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)

  1. 扩展可以做什么
  • 声明私有属性
  • 声明私有方法
  • 声明私有变量
  1. 扩展的特点
  • 编译时决议
  • 只以声明的形式存在,多数情况下寄生于宿主类的.m中。
  • 不能为系统类添加扩展
    分类和扩展的区别

代理(Delegate)

  1. 代理是什么

-代理模式是一种设计模式

  • iOS当中以@protocol形式体现
  • 传递方式一对一
    代理的工作流程

    协议中即可以定义属性也可以定义方法,由代理方负责实现。
    @optional下的为代理方可以选择实现的方法(默认), @required下的为代理方必须实现的方法。

2.代理的使用中需要注意的问题

一般声明为weak以避免产生循环引用。

通知(NSNotification)

  1. 通知是什么
  • 是使用观察者模式来实现的用于跨层传递消息的机制。
  • 传递方式为一对多
  1. 通知机制是如何实现的

由NofificationCenter维护一个Notification_Map,key为notificationName,value为观察者列表,成员为一个存有观察者的对象指针以及所要执行的SEL方法指针


如何实现简单的通知机制

KVO

1.KVO是什么

  • KVO是Key-value observing的缩写
  • KVO是Objective-C对观察者设计模式的一种实现
  • Apple使用了isa混写(isa-swizzling)来实现KVO
    isa混写

    isa混写:通过addObserver: forKeyPath: options: context:为Class A属性添加观察者对象后,会动态的生成Class NSKVONotifying_A(Class A的子类),并将原本指向A对象的isa指针指向NSKVONotifying_ANSKVONotifying_A会重写Setter方法,重写后的Setter方法负责通知所有观察对象属性的修改。这种改变被观察者A对象的isa指针的方式就成为isa混写
    观察者对象通过实现observeValueForKeyPath: ofObject: change: context:方法来监听的属性修改
  • 通过两个断点及命令行方法po object_getClassName(obj)来查看调用addObserver: forKeyPath: options: context:方法前后obj指针指向的ClassName
    动态的生成Class NSKVONotifying_A
  • 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"];
}
  1. 需要注意的问题
  • 通过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访问自己的成员变量(没有settergetter方法的变量)
相关KVC底层的一些介绍 iOS底层原理探究之----KVC

  1. 这种键值编码技术是否会破坏面向对象的编程方法或者是否会有违面向对象的编程思想

由于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告诉编译器:属性的ivarsettergetter不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供settergetter方法,编译的时候没问题,但是当程序运行到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中基本不使用),weakcopy

ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
对应基本数据类型默认关键字是
atomic, readwrite, assign
对于普通的OC对象
atomic,readwrite, strong

  1. atomic关键字

atomic关键字的线程安全是针对settergetter方法即对象的读写操作来说的,对于对象的操作并不能保证线程安全,如对atomic修饰的NSMutableArray进行增删操作就无法保证是线程安全的。atomic内部是通过@synchronized来实现原子性的
关于@synchronized的文章: 正确使用多线程同步锁@synchronized

//atomic时,setter、getter方法的默认实现
- (void)setAge:(int)age { 
   @synchronized(self){
       _age = age;
   }
}

- (int)age {
   @synchronized(self){
       return _age; 
   }
}  
  1. assing关键字

assing关键字的特点:修饰基本数据类型,如intBOOL等;修饰对象类型时,不改变其引用计数;当其修饰对象被销毁时仍指向原内存地址,从而造成野指针(悬垂指针)。

  1. weak关键字

weak关键字特点:修饰对象,不改变其引用计数;当其修饰对象被销毁时,会自动将指针置nilweak修饰的指针默认值是nil
weak可以自动将空指针置nil原理runtime 维护了一个Weak表weak_table_t用于存储指向某一个对象的所有Weak指针。Weak表其实是一个哈希表key是所指对象的地址valueweak指针的地址的数组。在对象回收的时候,就会在weak表中进行搜索,找到所有以这个对象地址为键值的weak对象,从而置为nil。由于维护了一个weak表,所以对性能有些许影响。

  1. unsafe_unretained关键字

unsafe_unretained关键字特点:修饰基本数据类型,如intBOOL等;修饰对象时,不改变其引用计数;当其修饰对象被销毁时,指针仍指向原内存地址,从而造成野指针(悬垂指针)(基本被assing替代)。

  1. copy关键字
  • copy关键字:修饰对象,对其执行copy操作
  • 浅拷贝:对内存地址的复制,让目标对象指针与源对象指针指向同一片内存空间,不会引起内存空间的分配;会让源对象内存引用计数加1
  • 深拷贝:对内存的复制,让目标对象指针指向一片内容与源对象指针内容相同的内存空间,会引起内存的分配;不会让源对象内存引用计数加1。

区分深拷贝与浅拷贝:
① 是否开辟了新内存空间(深拷贝会开辟新内存空间而浅拷贝不会);
② 是否影响引用计数(深拷贝不会影响引用计数而浅拷贝会影响被拷贝对象的引用计数);

copymutableCopy
① 对可变对象copy操作和mutableCopy操作都是深拷贝;
② 对不可变对象copy操作是浅拷贝mutableCopy操作是深拷贝
copy返回的都是不可变对象mutableCopy返回的都是可变对象

@property(copy)NSMutableArray *array;// 会产生什么问题
①如果赋值过来的是NSMutableArraycopy操作之后变成了NSArray,是深拷贝
②如果赋值过来的是NSArraycopy操作之后是NSArray,是浅拷贝
③不论赋值过来的是NSArray还是NSMutableArraycopy操作之后都是NSArray,如果调用NSMutableArray相关的如addObject:等方法,会引起crash

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

推荐阅读更多精彩内容