第一期问题解决
1.Objective-C和C++在继承上有什么差异?
(1)Objective-C不支持多重继承,而C++语言支持多重继承。
(2)子类继承父类,并且重写父类方法后,父类指针指向子类对象时,OC执行的是子类的方法(不看指针看对象)。而C++执行的是父类方法,不能动态加载重写的方法,因此C++中引入虚函数(virtual)。在父类中的将需要重写的函数前加virtual关键字,那么子类继承父类后该函数自动成为virtual函数,此时将父类指针指向子类对象,可以进行动态绑定,最后执行子类的方法,也就是C++中所说的动态多态。
以上是查阅博客后总结的结论,之后会通过阅读推荐学习资源深入理解OC和C++在继承上的差异。
2.属性(property)的属性修饰符,以及它们之间的差异:
OC中有几个比较重要的属性修饰符:nonatomic、atomic、copy、assign、weak、strong
其中系统默认的修饰符是:atomic、assign、strong
(1)nonatomic、atomic
这两个是关于原子性的修饰符,不设置时默认为atomic。系统会自动为属性初始化setter/getter方法,使用nonatomic、atomic的区别在于:
- atomic系统自动生成的getter/setter方法为原子性操作,执行性能较低
- nonatomic系统自动生成的getter/setter方法为非原子性操作,执行性能较高
加了atomic的属性可以用于多线程,加锁保证了属性在多线程情况下不出现数据错误,但是加锁会消耗更多的系统资源。需要注意的是atomic的锁仅能保证对该属性的读写安全,但是无法保证线程安全,因为很可能存在除了读写之外的其他操作也使用到该属性。
(2)copy、assign、weak、strong
以上四种修饰符都是关于属性生命周期管理的修饰符。
- assign:简单赋值,不更改引用计数。一般用于基础类型的数据(NSInteger)和C语言类型数据(int、float、double、char、bool),是系统默认的修饰符。
- copy:会拷贝传入的对象,即创建一个引用计数为1的新对象,但是内容与传入对象相同,并把新对象赋值给实例变量。常用于NSString、NSArray、NSDictionary、NSSet等。
- strong:强引用,要求变量保留传入的对象,而放弃原有对象,被传入对象引用计数➕1。一个对象只要被至少一个强引用指向,那么它就不会被释放,而没有强引用指向的对象就会被系统释放。其作用类似于C++智能指针中的shared_ptr。
- weak:弱引用,不会增加传入对象的引用计数。类似assign,但不同的是,当它们指向的对象被释放后,weak会被置为nil,但assign不会。所以assign会导致出现野指针,而weak可以避免悬空指针。weak的作用类似于C++中的weak_ptr,当发生循环引用时,很容易发生内存泄漏,此时使用weak修饰符可以避免发生循环引用。
3.initialize和load
OC中有两个特殊的类方法:load和initialize。不主动使用的情况下,这两种方法系统最多调用一次。
(1)load
- 调用时机:load方法在文件被程序加载时调用,与类是否被调用无关,因此load方法总是在main函数之前调用。一个类的load方法是在它所有父类之后执行,类别的load方法是在自己的load方法之后执行。
- 编写时的注意事项:通常在laod方法中进行方法交换(Method Swizzle)(Method Swizzle目前还没有学习到,无法作出更深的解释),load方法不需要使用[super load]来显性调用父类的load方法,只要被添加到Compile Source下就会执行。如果load方法中需要调用其他类的实例对象的属性或方法,必须确保依赖类的load方法执行完毕,因此应当尽量不在load中使用其他类。除非有必要,尽量不在load方法中写代码。
(2)initialize
- 调用时机:initialize在main函数之后,第一次对这个类发消息时调用。如果没有使用该类,即使加载完毕也不会执行该方法。如果子类和父类都实现了initialize方法,那么会先调用父类方法,再调用子类方法;如果子类没有实现initialize方法,那么父类的实现会被执行数次。
- 编写时的注意事项:initialize方法不需要使用[super initialize]来显性调用父类的initialize方法,实际开发中initialize方法一般用于初始化全局变量或静态变量。
《Objective-C高级编程 iOS与OS X多线程和内存管理》学习总结1
Blocks
Blocks是C语言的扩充功能,即:带有自动变量(局部变量)的匿名函数。可以将其与C++语言中的lamda函数进行类比,它们实现了相同的功能。
1.Block模式
(1)Blocks语法(Block Literal Syntax)
^返回值类型 参数列表 表达式
^int (int count) {return count+1;}
- 返回值类型可以省略。此时如果表达式中有return语句就返回该类型,否则返回void类型。
^ (int count) {return count+1;}
- 参数列表可以省略。
^ {printf("Blocks\n");}
(2)Block类型变量
定义C语言函数时,可以将所定义函数的地址赋值给函数指针类型变量中。同样地,在Block语法中,可将Block语法赋值给声明为Block类型的变量。
- Block类型变量的声明如下:
int (^blk)(int);
使用Block语法赋值为Block类型变量:
int (^blk)(int) = ^(int count){return count+1;};
Block类型变量可以作为函数参数传递,也可以在函数中返回Block变量。可以使用typedef来简化Block变量记述方式复杂的问题,即在函数外声明:
typedef int (^blk_t) (int);
//作为函数参数和返回值
//原来的记述方式
void fun(int (^blk) (int)){}
int (^func() (int));
//简化后的记述方式
void func(blk_t blk){}
blk_t func();
- Block类型变量的调用
int result = blk(10);
//在函数中执行Block
int func(blk_t blk, int rate)
{
return blk(rate);
}
总结:Block类型变量C语言其他类型变量一样使用。
(3)截获自动变量值
int main(int argc, const char * argv[]) {
@autoreleasepool {
int dmy = 256;
int val =10;
const char *fmt = "val=%d\n";
void (^blk)(void) = ^{printf(fmt,val);}; //Block变量
val = 2;
fmt = "These values were changed. val=%d\n";
blk(); //执行结果,val=10
return 0;
}
Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。
截获OC对象时,调用变更该对象的方法:
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
id obj = [NSObject alloc] init];
[array addObject:obj];
};
代码可以正确执行,说明使用截获的值不会有任何问题。当向截获的自动变量赋值时:
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
array = [[NSMutableArray alloc] init]; //编译错误!!!
};
可以看出不能向截获对象进行赋值。若要赋值,附加 __block修饰符。
(4)__block说明符
自动变量值截获只能保存执行Block语法瞬间的值,并且保存后不能改写该值。例如执行下面的源代码:
int val = 0;
void (^blk) (void) = ^{val = 1;};
blk();
printf("val=%d\n");
//执行结果:编译错误
error:Variable is not assignable (missing __block type specifier)
从错误提示中可以看出,要在Block语法的表达式赋值给Block语法外声明的自动变量,需要在该自动变量前附加__block说明符。可以将v错误代码改为:
__block int val = 0;
void (^blk) (void) = ^{val = 1;};
blk();
printf("val=%d\n"); //执行结果: val=1
例子中附加__block说明符的自动变量称为__block变量。
2.Block截获对象生命周期
(1)Block存储域
Block和__block变量都是结构体实例。第1节的Block都是存储于栈上,为_NSConcreteStackBlock类。如果在全局变量地方使用Block语法,生成Block为_NSConcreteGlobalBlock类,其存储于数据区域(.data 区)。设置在栈上的Block,所属变量作用域结束,该Block就被废弃。Blocks提供了将Block和__block对象从栈上复制到堆上的方法,可以延长Block和__block对象的生存周期,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
当ARC有效时,大多数情形下编译器能够自动完成将Block和__block从栈上复制到堆上。
- Block作为函数返回值时,编译器会自动生成从栈上复制到堆上的代码。
编译器无法自动完成复制的情况:
- 向方法或函数的参数中传递Block时。
以下方法或函数不用手动复制:
- Cocoa框架的方法且方法名中含有UsingBlock等时,例如使用NSArray类的enumerateObjectUsingBlock实例方法。
- Grand Central Dispatch的API,例如dispatch_async函数。
下面举一个需要手动复制的例子。使用NSArray类的initWithObjects实例方法上传递Block时需要手动复制。
-(id) getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);},nil;
}
id obj = getBlockArray();
typedef void (^blk_t) (void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
//执行异常,应用程序强制退出
getBlockArray()方法在栈上生成两个Block,并传递给NSArray的initWithObjects方法。getBlockArray函数执行结束后,栈上的Block被废弃,因此blk()函数无法正常执行。因此需要手动将Block复制到堆上:
-(id) getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"blk0:%d",val);} copy], //调用copy方法将Block复制到堆上
[^{NSLog(@"blk1:%d",val);} copy],nil;
}
id obj = getBlockArray();
typedef void (^blk_t) (void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
//执行异常,应用程序强制退出
- 不论Block存储在何处,用copy方法复制都不会引起任何问题。因此不确定时使用copy方法即可。
(2)__block变量存储域
- 若一个Block中使用__block变量,当该Block从栈上复制到堆时,其中的__block变量也从栈复制到堆上。
- 多个Block中使用__block变量时,任何一个Block从栈复制到堆时,__block变量也会一并复制到堆并被该Block持有。剩下的Block复制到堆时,将会持有__block变量,增加__block的引用计数。当__block变量的引用计数为0时,将被废弃。
(3)截获对象
先看一行代码:
{
id obj = [[NSMutableArray alloc] init];
}
生成并持有NSMutableArray 类的对象,默认strong修饰符的赋值目标变量作用域结束,对象被废弃。
而在Block语法中使用该变量array的代码:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld\n",[array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
//执行结果
array count = 1
array count = 2
array count = 3
使用copy方法,Block中截获的strong修饰的对象array就被复制到堆上,超出变量作用域,也能继续存在。将Block从栈复制到堆本质上都是调用_Blcok_copy函数。
除以下情形,推荐调用Block的copy实例方法:
- Block作为函数返回值返回
- 将Block赋值给类的带有strong修饰符的id类型或Block类型成员变量
- Cocoa框架的方法且方法名中含有UsingBlock等,或Grand Central Dispatch的API。
(4)__block变量和对象
__block说明符可以指定任何类型的对象。
__block id obj = [[NSObject alloc] init];
//代码等同于
__block id __strong obj = [[NSObject alloc] init];
因为ARC有效时默认__strong修饰符。当__block变量为附有__strong修饰符的id类型或对象类型自动变量,__block变量从栈复制到堆时,
效果和上一节代码中的array对象一样,它们在超出变量作用域时依然存在,因为已经被赋值到堆上并被Block持有。
来看下面几行代码:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id __weak array2 = array;
blk = [^(id obj){
[array2 addObject:obj];
NSLog(@"array2 count = %ld", [array2 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
//执行结果
array count = 0
array count = 0
array count = 0
附有__strong修饰符的array在变量作用域结束时被释放,nil将会赋值给array2.如果同时附加__block和__weak:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
__block id __weak array2 = array;
blk = [^(id obj){
[array2 addObject:obj];
NSLog(@"array2 count = %ld", [array2 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
//执行结果
array count = 0
array count = 0
array count = 0
附加__block并不会影响array的生存周期,因此作用域结束后依然被释放,nil被赋值给array2.