ios kvo、kvc、block

443 阅读2分钟

当你写下面代码会发生什么事

least = MIN(*p++, b);
结果是:((p++ <= (b)? (p++): (*p++)))
这个表达式会产生副作用,指针p会做三次自增操作

NSString 用copy 还是 strong修饰

oc中NSString 为不可变字符串是,用copystrong 都只是分配一次内存,但是如果用copy的时候需要先判断字符串是否是不可变字符串,如果不是可变字符串,就不在分配空间,如果是可变字符串在分配空间,这样就会有性能消耗,如果程序用使用的的NSString是不可变的字符串就可以使用strong修饰

@synthesize 和@dynamic 的作用

- @property 有两个对应的词,一个是 @synthesize 一个是@dynamic。如果 @synthesize@dynamic 都没写,那么就默认是@synthesize var = _var;
- @synthesize 的语义是如果你没有手动实现settergetter 方法 那么编译器就会自动为你添加这两个方法
- @dynamic 告诉编译器:属性的settergetter 方法有用户自己实现,不自动生成(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供settergetter方法,编译的时候没有问题,但是当程序运行到 instance。var = someVar 的时候,由于缺少setter 方法会导致程序的崩溃,或者当运行到someVar = var时,由于缺getter方法同样会导致崩溃,编译是没有问题的,运行的时候才回去执行相应的方法,这就是所谓的动态绑定

notificationCenter,KVC,KVO,Delegate 他们之间有什么区别

- KVO:一对多,观察者模式,键值观察机制,他提供了观察某一个属性变化的方法,极大简化了代码
- KVC: 是键值编码,一个对象在调用setValue的时候
    . 检查是否存在相应的keyset方法,存在就调用set方法
    . set方法不存在,就查找_key的成员变是够存在,存在就直接赋值
    . 如果_key没找到,就找同名的key,存在就赋值
    . 如果没有有就调用valueForUndefinedkey和setValue:forUndefindkey.
- Delegate:通常发送者和接受者的关系是一对一的关系
    . 代理的目的就是改变传输控制链,允许一个泪在某些特定时刻通知到其他类,而不需要要获取到哪些类的指针
    . 可以减少框架的复杂度,消息大宋这(sender)告知接收者(receiver)某个时间将要发生,delegate同意然后发送者响应事件,delegate机制是的接收者可以改变发送者的行为
- notification:观察者模式,通常发送者和接收者的关系是间接地多堆垛关系,消息的发送者告知接收者事件已经发生或将要发送,仅此而已,接收者并不能反过来影响发送者的行为
区别:
    - 效率肯定是delegate比notification高
    - delegate方法比notification更加直接,需要关注返回值,所以delegate方法往往包含should这个传神的词,相反,notification最大的特色就是不关心结果,所以往往用did这个词
    - 两个模块之间联系不是很紧密,就用notification,例如多线程之间的传值
    - delegate 只是一种极为简单的回调,且主要用在一个模块中,例如底层功能完成了需要把一些值传到上层去,就事先把上层的函数通过delegate传到底层,然后在calldelegate,他们都在一个模块中,完成一个功能,例如说navgationController 从b界面到a点返回按钮(调用popViewController)方法

block底层实现原理

先看下面这段代码

void test1() {
	int a = 10;
	void (^block)() = ^{ NSLog(@"a is %d", a);
};
	a = 20; 
	block(); // 10
}
void test2() {
	__block int a = 10;
	void (^block)() = ^{ NSLog(@"a is %d", a);
};
	a = 20;
    block(); // 20
}
void test3() {
	static int a = 10;
	void (^block)() = ^{ 
		NSLog(@"a is %d", a);
	};
	a = 20;
    block(); // 20
}
int a = 10;
void test4() {
	void (^block)() = ^{ 
	NSLog(@"a is %d", a);
	};
    a = 20;
    block();//20
}
  • 造成这样的原因:传值和传址。为什么会有传值和传址,把.m编译成c++代码得到.cmp文件,我们来到文件,看到下面代码
struct __test1_block_impl_0 {
	struct __block_impl impl;
	struct __test1_block_desc_0* Desc;
	int a;
	__test1_block_impl_0(void *fp,struct 		__test1_block_desc_0*
Desc,int _a,int flag=0): a(_a){ 
	impl.isa = &_NSConcreteStackBlock; 
	impl.Flags = flags;
	impl.FuncPtr = fp;
	Desc = desc;
	}
};

static void __test1_block_func_0(struct __test1_block_imp_0 *__cself)
{
int a = __cself->a;
	NSLog(a);//这里就是打印a的值,代码太长,而且没有意义
}


void test1() {
	int a = 10;
	void (*block)() = (void (*)())&__test1_block_impl_0((void *))__test1_block_func_0,&__test1_block_desc_0_DATA,a);
	a = 20;
	((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr) ((_block_impl *)block);
}


int main(int argc, const char * argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
	test1(); 
}
return 0; 
}

static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  • 我们看到void test1()中,void(*block)()右边最后面,把a值传进去了,也就是把10这个值传进去了
  • 而且对void(block)简化分析,void(block)() = &_test1_block_impl_0();所以block就是指向结构体的指针
  • 10传入block后,代码最上面创建_test1_block_impl_0结构体中,a = 10
  • 对void test1()zhong 最下面的函数进行简化分析,得到(block)->FuncPtr(block),我们在回到刚才的test1_block_impl_0这个结构体中,impl.FuncPtr = fp;而fp又是传入结构体的第一个参数,而在void(block)()中,传入结构体的第一个参数为test1_block_func_0,也就是说(block)->FuncPtr)(blcok)=>_test1_block_func_0(block);
  • 上一步相当于调用test1_block_func_0()这个函数,我们来看这个函数,有这样一段代码:int a = cself->a;访问block中a的值,传递a;所以10,这种就是传值

我们看test2() 添加了_block会发送什么变化

void test2()
{
__attribute__((_blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),10};
void(*block)() = (void (*)())&__test2_block_impl_0((void *))__test2_block_func_0,&__test2_block_desc_0_DATA, (__Block_byref_a_0 *)&a,570425344);

(a.__forwarding->a) = 20;

((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr) ((_block_impl *)block);
}
int main(int argc, const char * argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test2();
}
return 0; 
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
  • 代码虽然很多看着很复杂,但是我们只需要看我们想知道的,睁大你的眼睛,看到void(*block)()这个函数的最后面,有个&a,这里传的是a的地址,从test2到test4,都是传址,所以a的值d发生了变化,block打印出来的是a的最终值

block注意点

  • 在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针 __weak typeof(self) weakself = self;
  • block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁,需要在block内部再将弱指针重新强引用一下 __strong typeof(self) strongself = weakself;
  • 如果需要在block内部修改外部变量,需要在用__block修饰外部变量