OC基础

关于@property

(1) @property的本质是啥编译器都帮你做了什么事儿详细说明
(2) 关键字assign weak strong copy unsafe_unretained 分别什么时候使用
(3) 关键字atomic nonatomic 区别 atomic安全吗?
(4) 关键字@synthesize 和@dynamic 各自举出使用场景

property的本质可以说是 = ivar + getter + setter;编译器自动帮我们生成了set方法和get方法还有成员变量(_xxx),

property在runtime中是objc_property_t定义如下:

typedef struct objc_property *objc_property_t;

查看类所有属性的方法runtimeapi如下

objc_property_t *properties  =class_copyPropertyList([self class], &count);

而objc_property是一个结构体,包括name和attributes,定义如下:

struct property_t {
    const char *name;
    const char *attributes;
};

而attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

而attributes的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。
例如:我们定义一个string的property@property (nonatomic, copy) NSString *string;,通过 property_getAttributes(property)获取到attributes并打印出来之后的结果为T@"NSString",C,N,V_string

assign修饰基本数据类型变量(assign也可以修饰对象类型)
weak修饰对象类型变量 修饰的对象属于弱引用当对象销毁时指针自动变成nil
unsafe_unretained修饰对象类型变量 ,和weak一样也是弱引用但是有一点不同,对象销毁了了unsafe_unretained修饰的指针还是指向原来的那块内存,如果用户不手动将指针指向nil将会存在野指针
strong修饰对象类型强引用对象.
copy修饰实现NSCopying协议的对象 具体细节下面会有在copy和MutableCopy这一块有详细讲解

atomic nonatomic分别表示原子操作费原子操作 本质的区别其实就是就是atomic在set和get时加了锁代码如下

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }
 
    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }
 
    id oldValue;
    id *slot = (id*) ((char*)self + offset);
 
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
 
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
 
    objc_release(oldValue);
}

我们举个例子这个例子

@property (atomic, assign) int slice;
    
    self.slice = 0; //atomic
    dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            self.slice = self.slice + 1;
        }
        NSLog(@"slice1 = %d",self.slice); //slice1 = 997
    });
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            self.slice = self.slice + 1;
        }
        NSLog(@"slice2 = %d",self.slice); //slice2 = 1521
    });

这个例子就相当于slice是原子性的

@property (nonatomic, assign) int slice;
- (void)setSlice:(int)slice{
    @synchronized (self) {
      _slice = slice;
    }
}

- (int)slice{
    @synchronized (self) {
        return _slice;
    }
}

self.slice = 0; //atomic
    dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            self.slice = self.slice + 1;
        }
        NSLog(@"slice1 = %d",self.slice); //slice1 = 997
    });
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            self.slice = self.slice + 1;
        }
        NSLog(@"slice2 = %d",self.slice); //slice2 = 1521
    });

我们可看出atomic只是对属性的getter/setter方法进行了加锁操作,这种安全仅仅是get/set的读写安全,当时并不能保证我嗯真正的用法安全. 用法安全应该想下面一样

self.slice = 0; //atomic
    dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            @synchronized (self) {
                self.slice = self.slice + 1;
            }
        }
        NSLog(@"slice1 = %d",self.slice); //slice1 = 1906
    });
    dispatch_async(queue, ^{
        for (int i=0; i<1000; i++) {
            @synchronized (self) {
                self.slice = self.slice + 1;
            }
        }
        NSLog(@"slice2 = %d",self.slice); //slice2 = 2000
    });

@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。(当然对于 readonly 的属性只需提供 getter 即可)
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现
例子
正常的属性
@property (atomic, assign) int slice; 系统默认就是@synthesize slice = _slice; 我们的成员变量_slice就是这样来的

什么情况下@synthesize不会自动生成 _ xx(成员变量)
(1)同时重写了 setter 和 getter 时 不会自动生成 _ xx(成员变量) 需要我们自己写上@synthesize slice = _ slice 或者自己在类扩展中声明出xx
2.重写了只读属性的 getter 时 只读属性编译器不会自动生产
xx(成员变量), 解决方案同(1)
3.使用了 @dynamic 时 编译器不会自动生生set get 方法都要自己写 成员变量可以写在类扩展(单独用这样的这种情况不多)
4.在 @protocol 中定义的所有属性 相当于只是提供了get方法和set方法. 谁遵守了协议需要自己实现get和set方法,但是不会自动生成 _ xx(成员变量)
5.在 category 中定义的所有属性()
6.重载的属性(场景 在继承关系中Base类实现了协议 子类要在协议上扩展 要对子类的delegate做@dynamic声明)

关于KVO

(1) KVO的原理
(2) KVO被触发需要什么条件 直接修改成员变量(_变量)会触发KVO吗?通过修改成员变量来修改只读属性会触发吗?通过KVC修改会触发KVO吗?
(3) KVO监听属性person Key值可以@"person"可以@"_person"吗?
(4) KVO被触发过程中你知道都有什么方法会被调用

#import "LCKVOViewController.h"
#import "LCKVOobj.h"
@interface LCKVOViewController ()
@property (nonatomic, strong) LCKVOobj *kvoObj;
@end

@implementation LCKVOViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.kvoObj = [LCKVOobj new];
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
    [self.kvoObj addObserver:self forKeyPath:@"age" options:options context:@"test1"];
    [self.kvoObj addObserver:self forKeyPath:@"_str" options:options context:@"test2"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"keyPath = %@",keyPath);
    NSLog(@"object = %@",object);
    NSLog(@"change = %@",change);
    NSLog(@"change = %@",context);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.kvoObj.age = 10;
    self.kvoObj->_str = @"str"
}

- (void)dealloc{
    [self.kvoObj removeObserver:self forKeyPath:@"age"];
    [self.kvoObj removeObserver:self forKeyPath:@"_PrivateStr"];
}


@interface LCKVOobj : NSObject
{
    @public
    NSString *_str;
}
@property (nonatomic, assign) int age;


@end

KVO的原理是在运行时动态改变了对象的isa指针的指向.举个例子就是kvoObj在添加了KVO观察者之后会动态生成一个LCKVOobj的子类叫做NSKVONotifying_LCKVOobj. kvoObj对象的isa指向的就是NSKVONotifying_LCKVOobj这个新的类并且在新的子类中重写了监听属性的set方法. 我们知道对象的isa指向的其实是类对象但是这个时候调动[kvoObj class]发现返回的还是LCKVOobj 这是因为狡猾的苹果故意在NSKVONotifying_LCKVOobj里面重写了class方法就是啊让使用者无感知. 我们通过runtime的api可以查看NSKVONotifying_LCKVOobj类里面的方法 会发现类里面重写 - (void)setAge:(int)age -(BOOL)_isKvo; -(void)dealloc - (Class)class这几个方法

@implementation NSKVONotifying_LCKVOobj

- (void)setAge:(int)age{
    
    _NSSetIntValueAndNotify();
}

void _NSSetIntValueAndNotify(){
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key{
    [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

@end

NSKVONotifying_LCKVOobj里面的伪代码大概就是这个样子的
从以上原理我们得出一个结论触发kvo是调用了set方法
如果直接修改成员变量是不经过set方法的所有也不会触发KVO
监听属性person 可以用@"person"也可以用@"_ person"作为Key
手动触发kvo我们可以手动调用willChangeValueForKey 和 didChangeValueForKey也会触发KVO例如下面例子
[self.kvoObj willChangeValueForKey:@"_str"];
self.kvoObj->_str = @"str";
[self.kvoObj didChangeValueForKey:@"_str"];

关于KVC

(1) KVC的原理(赋值取值)
(2) KVC可修改成员变量吗, KVC可以修改redonly属性吗?
(3) 比如说通过KVC修改属性person 都有什么字符串可以作为@"_ person"可以作为key修改吗?

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性或者给一个属性赋值
通过KVC怎么KVC赋值的

image.png

网上这个图很好的总结了 kvc是怎么给属性赋值的.
(1)先通过setValue方法给属性赋值,
(2)如果setValue方法没有会通过_setValue方法给属性赋值
(3)如果上面两个方法都没有会调用+(BOOL)accessInstanceVariablesDirectly方法询问是否可以直接修改成员变量,如果返回NO会调用setValue:forUndefinedKey:
并抛出异常NSUnknownKeyException(返回值默认为YES)
(4)如果可以给成员变量赋值就赋值的顺序 是_key、_isKey、key、isKey

redonly的属性也会自动生成带下划线的成员变量_ XX没有不会自动生成set方法而已所以kcv可以修改redonly的属性

如果@"_person"作为修改person属性的过程也可上图一样调用顺序如下
(1)set_person ,(2)_set_person, (3)__person,(4)__isPerson, (5)_person,(6)_isPerson
我们可以看出用@"_person"作为修改person属性是在第五步修改的,如果一之四任何一部拦截了就修改不了了. 比如有个_person的属性这个时候用@"_person"修改的就是_person属性.

关于copy和mutableCopy

(1) NSString 和 NSMutableString,NSArray和NSMutableArray,NSDictionary和NSMutableDictionary 对象分别调用copy和mutableCopy生成的新对象指向的地址分别有什么变化
(2) 加入一个自定的类要实现copy的这个方法应该做怎么做
(3)像NSString NSArray... 这些类的属性为什么建议用copy
(4)copy修饰的属性的set方法怎么写

NSString *str= [NSString stringWithFormat:@"%@",@"字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串字符串"];
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr= [str mutableCopy];
NSLog(@"str = %p",str);
NSLog(@"copyStr =%p",copyStr);    
NSLog(@"mutableCopyStr = %p",mutableCopyStr);
2020-01-10 18:24:32.984808+0800 测试[30339:4542789] str = 0x600002507520
2020-01-10 18:24:32.985030+0800 测试[30339:4542789] copyStr =0x600002507520
2020-01-10 18:24:32.985159+0800 测试[30339:4542789] mutableCopyStr = 0x600001aa30f0


NSMutableString *mutableCopyStr1 = [[NSMutableString alloc]initWithFormat:@"%@",@"copycopycopycopycopycopycopycopycopycopy"];
NSString *str1 = [mutableCopyStr1 copy];
NSMutableString *mutableCopyStr2 = [mutableCopyStr1 mutableCopy];
NSLog(@"mutableCopyStr1 = %p",mutableCopyStr1);    
NSLog(@"str1 =%p",str1);
NSLog(@"mutableCopyStr2 = %p",mutableCopyStr2);
2020-01-10 18:36:48.505848+0800 测试[30388:4550670] mutableCopyStr1 = 0x6000031d0420
2020-01-10 18:36:48.506010+0800 测试[30388:4550670] str1 =0x600002aefa00
2020-01-10 18:36:48.506121+0800 测试[30388:4550670] mutableCopyStr2 = 0x6000031d2760

NSArray *arr = [NSArray array];
NSArray *arr1 = [arr copy];
NSMutableArray *arr3 = [arr mutableCopy];
NSLog(@"arr = %p",arr);
NSLog(@"arr1 =%p",arr1);
NSLog(@"arr3 = %p",arr3);
2020-01-10 18:49:38.432070+0800 测试[30472:4560849] arr = 0x7fff80615170
2020-01-10 18:49:38.432164+0800 测试[30472:4560849] arr1 =0x7fff80615170
2020-01-10 18:49:38.432293+0800 测试[30472:4560849] arr3 = 0x600001bac000

NSMutableArray *mutableArray = [arr mutableCopy];
NSArray *arr4 = [mutableArray copy];
NSMutableArray *arr5 = [mutableArray mutableCopy];
NSLog(@"mutableArray = %p",mutableArray);
NSLog(@"arr4 =%p",arr4);
NSLog(@"arr5 = %p",arr5);
2020-01-10 18:49:38.432422+0800 测试[30472:4560849] mutableArray = 0x600001bad1d0
2020-01-10 18:49:38.432646+0800 测试[30472:4560849] arr4 =0x7fff80615170
2020-01-10 18:49:38.432903+0800 测试[30472:4560849] arr5 = 0x600001bace10

从上面我们可以看出
UNMutable 对象 copy 不会生成新的对象 mutableCopy会 生产新的对象
Mutable 对象不管 copy 还是 mutableCopy 都会生成新的对象
这样设计背后是有他的道理的 因为copy的精髓就是生产一个全新的副本,修改副本或者原来的对象不会互相影响. 举个例子 A copy ->B,A修改不会影响到B,B改变也不会影响到A.
一个不可变的对象copy 只是多了一指向原来内存的指针,不会产生新对象的原因是,原来的对象是不可变的,新的对象也是不可变得A,B都没有变得机会自然不会互相影响,就没必要开辟新的内存空间了

自定义的对象要copy需要遵循<NSCopying>协议 并且实现
- (id)copyWithZone:(nullable NSZone *)zone;

#import "CopyObject.h"

@implementation CopyObject

- (instancetype)initWithName:(NSString *)name age:(int)age obj:(LCObjectCopy *)obj{
    if (self = [super init]) {
        _name = name;
        _age = age;
        _obj = obj;
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone{
    CopyObject *copy = [[self.class alloc] initWithName:self.name age:self.age obj:self.obj];
    //这里要 [self.class alloc] 因为 如果写死 [CopyObject alloc]这个类就不能被继承了
    return copy;
}

@end

需要注意的是如果对象中有其他对象 要想完全深拷贝还要自己实现嵌套对象中的copy方法

- (id)copyWithZone:(NSZone *)zone{
    CopyObject *copy = [[self.class alloc] initWithName:self.name.copy age:self.age obj:self.obj.copy];
    //这里要 [self.class alloc] 因为 如果写死 [CopyObject alloc]这个类就不能被继承了
    return copy;
}

UI事件传递和响应机制

(1) 让一个超出父view响应事件
(2) 事件的拦截

动画

(1) UIView动画
(2) Base动画
(3) 粒子动画
(4) 适量动画
(5) 专场动画

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

推荐阅读更多精彩内容

  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    iOS菜鸟大大阅读 664评论 0 1
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类...
    司马DE晴空阅读 1,231评论 0 7
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    sumrain_cloud阅读 324评论 0 1
  • 给我一份工作,让我忙碌起来!那将是我的生活来源、没有话语、没有欢声。可是我喜欢这样忙碌而又充实的生活。 每...
    馨百年珠宝阅读 225评论 0 0
  • 科学运动·合理饮食,打造完美身材! 除了上班,生活的空闲往往被带孩子、固定三餐所约束,锻炼对于那些有家庭负担的爸爸...
    健美乐阅读 666评论 2 6