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 *)change context:(void *)context{
    NSLog(@"keyPath = %@",keyPath);
    NSLog(@"object = %@",object);
    NSLog(@"change = %@",change);
    NSLog(@"change = %@",context);
}

- (void)touchesBegan:(NSSet *)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需要遵循协议 并且实现
- (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) 专场动画

你可能感兴趣的:(OC基础)