一、block
1)iOS开发的内存分配;
2)block的变量捕获机制:为了保证在block的内部可以正常访问外部的变量;
3)block的类型(__NSGlobalBlock__(存放数据段)、__NSStackBlock__(存放栈内存,出了作用域销毁)、__NSMallocBlock__(存放堆内存,自己控制)
);
4)block在ARC下会自动copy的四种情况;
5)对象auto修饰注意事项;
6)__block修饰变量、对象;
7)block的本质、block在使用过程中的注意事项、block的修饰词copy,block修改NSMutableArray需不需要添加__block修饰;
1-1、iOS内存分配:
栈区:存放函数局部变量
,栈区地址从高到低分配;
堆区:堆区的地址是从低到高分配,存放alloc,malloc
对象;
全局区/静态区(未初始化):初始化的全局变量
和static
修饰的变量,其实就是存放数据段;
全局区/静态区(初始化):未初始化的全局变量
和static
修饰的变量,其实就是存放数据段;
常量:常量字符串
就是放在这里,const
修饰的常量;
代码区:存放编译过
的代码;
1-2、block的变量捕捉分析:
#import <Foundation/Foundation.h>
int globalAge = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int autoAge = 10;
static int staicAge = 20;
//1、定义方法
void(^block)(void) = ^{
NSLog(@"autoAge=%d,staicAge=%d,globalAge=%d",autoAge,staicAge,globalAge);
//输出结果:autoAge=10(值补捉),staicAge=21(地址补捉),globalAge=31(全局变量)
};
autoAge = 11;
staicAge = 21;
globalAge = 31;
//2、调用方法
block();
}
return 0;
}
转化成C++代码分析:
//1-1,调用过程
auto int autoAge = 10;
static int staicAge = 20;
//定义方法
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, autoAge, &staicAge));
//定义方法简化
void(*block)(void)= &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, autoAge, &staicAge);
autoAge = 11;
staicAge = 21;
globalAge = 31;
//调用方法
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//调用方法简化
block->FuncPtr)(block);
//1-2、调用过程
int globalAge = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int autoAge;
int *staicAge;
//方法的初始化(设置类的属性,方法初始值)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _autoAge, int *_staicAge, int flags=0) : autoAge(_autoAge), staicAge(_staicAge) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//1-3、调用过程基本信息
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//1-4、调用过程
//block的方法内部实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int autoAge = __cself->autoAge; // bound by copy
int *staicAge = __cself->staicAge; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_k4_j2sqg6y12zvgxxrm1h1wtpnh0000gn_T_main_101291_mi_0,autoAge,(*staicAge),globalAge);
}
1-2、总结(block变量捕捉)
1)block的变量捕获本质(为了block的内部可以正常的访问block外部的变量
,auto
(自动变量为局部变量,出了作用域内存就销毁掉了,尤其出现在跨方法调用问题上)、static
(static修饰的代码只会运行一次,存放静态区域,存放内存中不会销毁)、全局变量
(存放全局区,运行过程不会销毁));
2)通过c++源码可以看出,static和auto修饰局部变量
,会出现变量捕捉到block内部(auto是值捕捉方式,static是地址捕捉方式);全局变量
不会出现变量捕捉;
1-3、block的类型:(
__NSStackBlock__进行copy操作会存储到__NSMallocBlock__上
)
int height = 172;
void (^blockWholeTest)(void);
//1-3-1、block的类型(__NSGlobalBlock__) 没有访问auto变量(访问了static、全局变量(注意全局的宏不是全局变量只是替换值))的为此类型,放在堆上,需要自己管理
static int age = 10;
void (^blockTest)(void) = ^{
NSLog(@"height=%d",height);
NSLog(@"age=%d",age);
};
blockTest();
NSLog(@"blockTest type is %@",[blockTest class]);
//1-3-2、block的类型(__NSStackBlock__)访问了auto变量 (需要关闭 Automatic Reference Counting),存放在栈上,出了作用域内存回收
auto int length = 18;
void (^blockTest2)(void) = ^{
NSLog(@"length=%d",length);
};
blockTest2();
NSLog(@"blockTest2 type is %@",[blockTest2 class]);
//1-3-3、分析作用域存在的问题:MRC环境下(ARC环境下:系统内部会自动进行copy操作)
void test()
{
//__NSStackBlock__ 存放在栈上,除了作用域,系统回收内存
int age = 27;
blockWholeTest = [^{
//打印出来的值因为内存回收了,所以是个未知值,想要正常访问的话,需要copy操作将block的stack到malloc
NSLog(@"test---block---age=%d",age);
} copy];
[blockWholeTest release];
}
//作用域的问题
test();
blockWholeTest();
1-4:总结:block的存放在
栈区
、有强指针
指向这个block、使用了Cocoa API中的方法含有UsingBlock
的参数、在GCD
中block作为参数的时候;
typedef void(^CWBlock)(void);
CWBlock myblock()
{
//问题1.MRC环境下---这个block存在栈区
int age = 20;
CWBlock block = ^{
NSLog(@"this return block %d",age);
};
return block;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//问题1.这样调用比较危险,因为返回的block是存放在栈区,出了作用域销毁;
CWBlock block = myblock();
block();
NSLog(@"%@",[block class]);
//问题2:将block赋值给__strong指针时,ARC环境下编译器会自动copy操作(局部变量在arc环境下默认是__strong),什么时候赋值给__strong(我自己的理解:只是将^{ NSLog(@"age=%d",age);};赋值给block2了,全局的CWBlock强指针这个block块);
int age = 10;
CWBlock block2 = ^{
NSLog(@"age=%d",age);
};
block2();
//3、使用了Cocoa API中方法名含有UsingBlock的方法参数
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"3",@"6",@"4",nil];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
//4、block作为GCD的方法参数的时候,会自动copy
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"once method");
});
}
return 0;
}
1-5.总结:
1)MRC情况下:Person在执行完[p2 release]就释放
,所以再调用block()就有问题
,想要正常调用block()需要将block从栈区copy到堆区
;
2)ARC情况下:CWBlock强引用block,所以系统会自动
将blockcopy
到堆区;
MRC:
CWBlock block;
{
//auto修饰的对象,值传递
Person *p2 = [[Person alloc] init];
p2.age = 20;
//ARC下需要自动管理内存,优化成下面代码
//block = ^{
// NSLog(@"age=%zd",p2.age);
// } ;
block = [^{
NSLog(@"age=%zd",p2.age);
} copy];
[p2 release];
}
block();
NSLog(@"-------华丽分割线--------");
ARC:
//block的修饰:__weak、__strong类型(ARC的环境下面,会自动copy操作,_Block_object_assign根据修饰词__weak、__strong,作出相应的弱引用,强引用;_Block_object_dispose根据修饰词进行销毁操作,相当于release);
CWBlock block;
{
//auto修饰的对象,值传递
Person *p2 = [[Person alloc] init];
p2.age = 20;
// __weak Person *weakPerson = p2;
block = ^{
//使用weakPerson时候,出了Person作用域就销毁(因为没有strong引用);使用p2出了CWBlock作用域才会销毁;
NSLog(@"age=%zd",p2.age);
// NSLog(@"age=%zd",weakPerson.age);
};
}
NSLog(@"block type %@",[block class]);
NSLog(@"-------华丽分割线--------");
ARC分析:
//对象的内存释放,出了作用域并没有进行释放,因为block持用person对象(因为auto修饰,捕捉了person对象),放block除了他的作用域的时候,block释放了,所以person也才释放
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//捕捉了person对象
Person *p2;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p2, int flags=0) : p2(_p2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
//此句会是block强引用p2对象
p2 = _p2;
}
};
1-6、__block修饰变量、对象注意事项:会生成__Block_byref_age_0、__Block_byref_per_1的结构体;
__block int age = 10;
void(^blockTest)(void) = ^{
age = 20;
NSLog(@"age=%d",age);
};
blockTest();
__block修饰的变量:
//1、定义方法
void(*blockTest)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_age_0 *)&age, 570425344))
//2、生成的__Block_byref_age_0结构体
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
//3、修改age的值
_static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
//这一步修改值
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_k4_j2sqg6y12zvgxxrm1h1wtpnh0000gn_T_main_96f4fb_mi_3,(age->__forwarding->age));
}
__block修饰的对象生成代码:
//源代码
__block Person *per = [[Person alloc] init];
//生成的代码
__main_block_impl_0包含__Block_byref_per_1 *per;
struct __Block_byref_per_1 {
void *__isa;
__Block_byref_per_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *per;
};
1-7、总结:
1)block的本质(是一个OC对象、封装了代码块函数的定义初始化及函数的调用);
2)block在使用过程中的注意事项(想要改变外部的变量:采用__block
修饰变量;防止出现循环引用的问题:ARC环境下面可以采用__weak、__unsafe_unretained、__block
方式解决循环引用问题,MRC:采用__unsafe_unretained、__block
解决循环引用问题);
3)block的修饰词copy(因为原始block的是放在栈区或者全局区,使用copy之后才能放到堆内存
中,程序员控制block的生命周期
);
4)block中修改NSMutableArray不需要
添加__block
修饰();
代码分析3问题:
//ARC内存问题解决方案
//__block Person *per = [[Person alloc] init];
Person *per = [[Person alloc] init];
per.age = 28;
//方案1:最常用的方案
//__weak typeof(Person) *weakPerson = per;
//方案2:与方案1的,在release时刻会将指针置为nil,而此方案不会置为nil;
// __unsafe_unretained typeof(Person) *unsafePerson = per;
//方案3:使用__block修饰对象,在block内部per.block = nil方案解决循环引用;
per.block = ^{
// NSLog(@"%ld",weakPerson.age);
// NSLog(@"%ld",unsafePerson.age);
NSLog(@"%ld",per.age);
per.block = nil;
};
per.block();
//MRC内存解决方案:
采用__unsafe_unretained、__block(__main_block_impl_0 强引用__Block_byref_per_0、__Block_byref_per_0 中的per 弱引用Person对象),所以可以正常释放;
__block Person *per = [[Person alloc] init];
per.age = 28;
per.block = ^{
NSLog(@"%ld",per.age);
};
per.block();
[per release];
源码方案3分析:__main_block_impl_0 强引用__Block_byref_per_0、__Block_byref_per_0 中的per 强引用Person对象、Person对象又强引用block,所以形成三角引用关系
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//1、__main_block_impl_0 强引用__Block_byref_per_0
__Block_byref_per_0 *per; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_per_0 *_per, int flags=0) : per(_per->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_per_0 {
void *__isa;
__Block_byref_per_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
//2、__Block_byref_per_0 中的per 强引用Person对象
Person *per;
};
代码分析4问题:
NSMutableArray *array = [NSMutableArray array];
void(^blockTest)(void) = ^{
//没有修改指针的值,只是使用值
[array addObject:@"1"];
[array addObject:@"2"];
};
blockTest();
二、Runtime
Runtime知识点:
1)Runtime的方法的调用三大过程:消息发送、动态解析、消息转发(objc_msgSend的执行流程:消息发送、动态方法解析、消息转发(都找不到报错:找不到方法);
2)super [self class]、[self superclass]、[super class]、[super superclass];
3)isKindOfClass、isMemberOfClass方法;
4)Runtime的应用:利用关联对象给分类添加属性、获取实例对象的属性和成员变量 、方法交换实现、NSArray,NSDictionary的nil处理;
1-1、class的结构:
1-2、cache_t方法缓存结构:缓存通过方法名&mask生成一个key值,用于存取方法的key(哈希表)
1-3、方法的调用流程总结:1、当对象
调用
一个方法,首先会在通过isa找到类对象,在类对象里面的cache_t的方法里查找
是否有调用过这个方法,有则直接返回方法的地址调用;2、没有则在class_rw_t的结构体里面method_list_t里面查找,有则调用,缓存到cache_t
里面;3、没有的话通过superClass找到父类,在父类的cache_t里面看是否有缓存过这个方法(有的话则缓存到当前调用对象的cache_t
),没有的话,则在class_rw_t的结构体里面method_list_t里面查找,有则调用,缓存到调用类
的cache_t
里面;没有则继续在父类中查找,父类还是没有找到则进入动态解析
过程,动态解析没找到方法实现则进入消息转发,没有报错doesNotRecognizeSelector:
;
1-3-1、消息发送:
//转化成runtime的消息发送 消息接收者:per;消息名称:test
//objc_msgSend(per,selector(test));
Person *per = [[Person alloc] init];
[per test];
1-3-2、消息动态解析:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(otherTest));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
1-3-3、消息转发:
//消息转发阶段--返回消息的接受者(方法为空,走签名的方法)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Student alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型(当返回了方法签名,就会走forwardInvocation方法)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation:封装了方法调用者、方法名、方法参数
//anInvocation.target 方法调用者
//anInvocation.selector 方法名
//[anInvocation getArgument:null atIndex:0] 获取参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//改下target,执行
anInvocation.target = [[Student alloc] init];
[anInvocation invoke];
// [anInvocation invokeWithTarget:[[Student alloc] init]];
}
2-1、代码验证super内部调用逻辑:
[super msg]
源码实现:objc_msgSendSuper({self,
//消息接收者
class_getSuperclass(objc_getClass("CWStudent"))},
//消息接受父类,方法从父类查找
sel_registerName("superclass"))
//执行方法
}
//结构体
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;//消息接收者
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;//方法调用开始从父类查找
/* super_class is the first class to search */
};
/**< NSObject的class、superclass内部实现代码 */
- (Class)class
{
//self消息接受者,即当前实例对象
return object_getClass(self);
}
- (Class)superclass
{
//self为消息接受者,实例对象
return class_getSuperclass(object_getClass(self));
}
- (instancetype)init
{
if (self = [super init]) {
//转化成的消息
NSLog(@"self - class = %@",[self class]);//CWStudent
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self,
sel_registerName("class"));
NSLog(@"self - superclass = %@",[self superclass]);//CWPerson
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self,
sel_registerName("superclass"));
//__rw_objc_super为结构体
NSLog(@"super - superclass = %@",[super class]);//CWStudent
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("CWStudent"))},
sel_registerName("class"));
NSLog(@"super - superclass = %@",[super superclass]);//CWPerson
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("CWStudent"))},
sel_registerName("superclass"));
return self;
}
3-1、源码:
+ (BOOL)isMemberOfClass:(Class)cls {
//判断:当前实例对象的`类对象`是否等于传进来的对象
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
//判断:当前类对象的`元类对象`是否等于传进来的对象
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
//判断:当前、或者父类--的类对象的`元类对象`是否等于传进来的对象
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
//判断:当前、或者父类--的实例对象的`类对象`是否等于传进来的对象
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
3-2、代码实践:
CWStudent *stu = [[CWStudent alloc] init];
CWPerson *per = [[CWPerson alloc] init];
NSLog(@"----------------");
NSLog(@"%d",[stu isKindOfClass:[CWStudent class]]);//1
NSLog(@"%d",[stu isMemberOfClass:[CWPerson class]]);//0
NSLog(@"%d",[per isMemberOfClass:[CWStudent class]]);//0
NSLog(@"%d",[per isMemberOfClass:[CWPerson class]]);//1
NSLog(@"----------------");
NSLog(@"%d",[CWStudent isKindOfClass:[CWPerson class]]);//0
NSLog(@"%d",[CWStudent isKindOfClass:[CWStudent class]]);//0
NSLog(@"%d",[CWPerson isMemberOfClass:[CWPerson class]]);//0
NSLog(@"%d",[CWPerson isMemberOfClass:[CWStudent class]]);//0
NSLog(@"----------------");
NSLog(@"%d",[CWStudent isKindOfClass:object_getClass([CWPerson class])]);//1
NSLog(@"%d",[CWStudent isKindOfClass:object_getClass([CWStudent class])]);//1
NSLog(@"%d",[CWPerson isMemberOfClass:object_getClass([CWPerson class])]);//1
NSLog(@"%d",[CWPerson isMemberOfClass:object_getClass([CWStudent class])]);//0
NSLog(@"%d",[CWPerson isMemberOfClass:[NSObject class]]);//0
//特殊分析:因为CWPerson的元类一直到NSObject的元类(NSObject的元类就是NSObject)
NSLog(@"%d",[CWPerson isKindOfClass:[NSObject class]]);//1
4-1、获取对象的成员变量和属性(查看私有成员变量、重置对象值、字典转模型):
CWPerson *person = [[CWPerson alloc] init];
//获取person类的属性和成员变量
unsigned int count;
Ivar *ivars = class_copyIvarList([person class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
4-2:方法交换实现(统计按钮的点击事件、NSMutableArray,NSMutableDictionary为nil处理):
Method methodTest1 = class_getInstanceMethod([person class], @selector(test1));
Method methodTest2 = class_getInstanceMethod([person class], @selector(test2));
method_exchangeImplementations(methodTest1, methodTest2);
//拦截所有的button点击事件,统一做处理
+ (void)load
{
Method sy_btnMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method cw_btnMethod = class_getInstanceMethod(self, @selector(cw_sendAction:to:forEvent:));
method_exchangeImplementations(sy_btnMethod, cw_btnMethod);
}
- (void)cw_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"%@-%@-%@",self,NSStringFromSelector(action),target);
[self cw_sendAction:action to:target forEvent:event];
}
//类簇--数组添加nil对象崩溃
NSMutableArray *array = [NSMutableArray array];
[array addObject:nil];
//添加的对象为nil处理
+ (void)load
{
Class cls = NSClassFromString(@"__NSArrayM");
Method sy_arrayMethod = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method cw_arrayMethod = class_getInstanceMethod(cls, @selector(cw_insertObject:atIndex:));
method_exchangeImplementations(sy_arrayMethod, cw_arrayMethod);
}
- (void)cw_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return ;
[self cw_insertObject:anObject atIndex:index];
}
//类簇--字典key为nil崩溃
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
dic[nil] = @"cjw";
//key为nil处理
+ (void)load
{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method sy_dicMethod = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method cw_dicMethod = class_getInstanceMethod(cls, @selector(cw_setObject:forKeyedSubscript:));
method_exchangeImplementations(sy_dicMethod, cw_dicMethod);
}
- (void)cw_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (key == nil) return ;
[self cw_setObject:obj forKeyedSubscript:key];
}
三、Runloop
app项目:
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
命令行项目:
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
二个项目的区别:
1、app项目执行UIApplicationMain函数创建一个Runloop,一直在检测app的是否有点击、触摸事件、定时器等,有事件处理事件、没有则处于休眠状态(保持程序的运行状态);
2、命令行项目:相当于执行完NSLog函数就退出;