iOS开发学习日志(2)

第一期问题解决

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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值