重点掌握
3 类对象和方法
- 对象就是一个物体
- 类的独特存在就是一个实例,对实例进行操作叫做方法。方法可以应用于类或者实例本身
- @interface(接口) 部分用于描述类和类的方法。也可以为类申明实例变量。
- implementation 部分用于描述数据(类对象的实例变量存储的数据),并实现在接口中申明方法的实际代码。也可以添加实例变量。
- program 部分的程序代码实现了程序的预期目的 (main函数)
- “-”表示实例方法,“+”表示类方法。实例方法可以对类进行操作
- alloc [Fraction alloc] alloc方法保证对象的所有实例变量都变成初始化状态,但是却并不意味着对该对象进行了适当的初始化,从而在创建对象之后还要进行初始化 [Fraction init]
- init 方法用于初始化类的实例变量。也就是说myFraction= [Fraction init] 表示要在这里初始化一个特殊的Fraction对象,因此,它没有发送给类,而是是发送给了类的实例。init方法也可以放回一个值,即被初始化的对象。将返回的值存到Fraction的变量myFraction中
- Fraction *myFraction;“ * ”表明myFraction是Fraction对象的引用(指针)。变量myFraction实际上并不储存Fraction数据,只是存储了一个引用(内存地址),表明对象数据在内存中的位置。Fraction *myFraction=[ [Fraction alloc] init] alloc返回存储数据的位置,并赋给变量myFraction
- 实例方法总可以直接访问它的实例变量,而类方法不行。因为类方法只处理本身,并不处理任何实例
- setter 设置实例变量的方法通常称为设置方法。设置方法不会返回任何值。主要目的是将方法参数设为对应的实例变量的值,在这种情况下不需要返回值
- getter 检索实例变量的方法称为取值方法。 目的是获取存储在对象中的实例变量的值,并通过程序返回发送出去。因此取值方法必须返回实例的值作为return的参数
- accessor 设置方法和取值方法通常称为访问器
atomic
- atomic 的意思就是setter/getter这个函数,是一个原语操作。如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,可以保证数据的完整性。
- atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。
- 如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。
nonatomic
- 关于nonatomic是线程不安全的,当有多个线程同时修改属性name的值的时候,拿到的结果可能不是自己想要的,因为当属性设置nonatomic的话是允许多个线程是可以同时修改name的值。
4 数据类型和表达式
- retain(strong), copy 把一个对象赋值给一个属性变量,当这个对象变化了,如果希望属性变量变化就使用strong属性,如果希望属性变量不跟着变化,就是用copy属性。把一个对象a赋值给一个属性变量b,如果这个变量b的属性为strong时,a和b的地址相同,也就是浅拷贝,所以它们是同一个对象。如果把对象a赋值给一个属性为copy的变量c,此时c的地址和a不同,copy是深复制,产生了一个新对象,所以它们地址不同。
- 深拷贝copy,浅拷贝strong。两者对内存计数的影响都是一样的,都会增加内存引用计数,都需要在最后的时候做处理。深拷贝是产生一个新的对象(地址不同),浅拷贝不产生新对象(地址是一样的)
什么时候使用copy
- 用于指针变量,setter方法进行copy操作,与retain处理流程一样,先旧值release,再copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
- 用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
/** 适用于集合类型(collection type)*/
@property (copy, nonatomic) NSString *name;
-
strong
:强引用类型,被定义了该类型的属性会被其所指对象持有,为这种属性设置新值的时候,set方法先保留新值,并释放掉旧值,然后再把新值赋值上去
/** 适用于对象类型,并且被持有*/
@property (strong, nonatomic) UIView *view;
-
weak
:爱恨交织的weak!弱引用类型,被定义了该类型的属性不会被持有,在set的时候,既不保留新值也不会释放旧值,和assign类似,不同的是在该属性所指对象被销毁之后,属性值也会被置为nil
weak 和 assgin
- weak属性的变量是不为其所属对象持有的,并且在该变量被销毁之后,此weak变量的值会自动被赋值为nil。而assign属性一般是对C基本数据类型成员变量的声明,当然也可以用在对象类型成员变量上,只是其代表的意义只是单纯地拷贝所赋值变量的值。即如果对某assign成员变量B赋值某对象A的指针,则此B只是简单地保存此指针的值,且并不持有对象A,也就意味着如果A被销毁,则B就指向了一个已经被销毁的对象,如果再对其发送消息会引发崩溃
/** 弱引用类型,对象释放后置为nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;
代理为什么用weak修饰?
- weak:指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制
- 如果用strong:该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用
-
unsafe_unretained
:这个跟assign就更像了,但不同的是它适用于对象类型.同样的不会被持有,但与weak不同是,所指对象被销毁之后不会把属性置为nil
/** 弱引用类型,对象释放后不会置为nil*/
@property (unsafe_unretained, nonatomic) UIView *view;
-
assign
:适用于基本数据类型,其set方法只会进行简单的赋值操作NSInteger,CGFloat...
/** 基本数据类型*/
@property (assign, nonatomic) NSInteger age;
- Int 类型的变量只能用于保存整型值,也就是不包含小数位数的值
- float 类型的变量可以存储浮点数
- double 和 char 类型一样,前者范围大约是后者的两倍。char 类型可以存储单个字符
- 常量 它们的总成
- char charVar = ‘a’
- float 类型的变量可以存储包含小数位的值
- 限定词 long 、longlong、short、unsigned、signed
- long变量的具体范围也可以由具体的计算机系统决定 %li 将用十进制显示 long int 的值
- 如果把限定词long放在int 声明前,那声明的整型变量在某些计算机上具有扩展的值域
- 如果把限定词short放在int 声明前,表示声明特定的变量来存储相当小的整数 (用short是对内存空间的考虑,可以节约空间)
- unsigned 放在 int 前,表示存储的是正数的整数变量
- id 类型可以存储任何类型的对象,是多态和动态绑定的基础
- count += 10 等价于 count = count + 10 、 "-" 同理
strong / weak / unsafe_unretained 的区别?
- weak只能修饰OC对象,使用weak不会使计数器加1,对象销毁时修饰的对象会指向nil
- strong等价与retain,能使计数器加1,且不能用来修饰数据类型
- unsafe_unretained等价与assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误
5 循环结构
- break 在执行循环过程中,有时候希望发生特定的条件就立刻退出循环。无论是for、 while、 do。循环内break之后的语句将被跳过,并且该循环的执行也将终止,从而转去执行循环外的其他语句。如果在一组嵌套循环中执行break语句,仅会退出执行break语句的最内层循环。记住加“ ;”
- continue 它和break类似。但是它并不会使循环结束。执行continue时,会跳过该语句之后直到循环结尾处的所有语句。通常用来根据某个条件绕过循环中的一组语句。
6 选择结构
- “ <>”引用系统文件 “ “” ”引用本地文件
- @synthesize:@synthesize age = _age。自动生成age的set和get方法的实现部分,并且会访问_age这个成员变量
- @synthesize 合成存取法 设值方法和取值方法统称为 存取方法 。它还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myButton = xxx;那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。在实际的项目中,我们一般这么写.m文件
@synthesize myButton;
这样写了之后,那么编译器会自动生成myButton的实例变量,以及相应的getter和setter方法。
注意:_myButton这个实例变量是不存在的,因为自动生成的实例变量为myButton而不是_myButton。
所以现在@synthesize的作用就相当于指定实例变量,
如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton。
如果没写@synthesize myButton;那么生成的实例变量就是_myButton。
所以跟以前的用法还是有点细微的区别。
@synthesize 合成实例变量的规则,有以下几点:
如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
如果这个成员已经存在了就不再生成了.
如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
如果是 @synthesize foo = _foo; 就不会生成成员变量了.
在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?
- 同时重写了 setter 和 getter 时
- 重写了只读属性的 getter 时
- 使用了 @dynamic 时
- 在 @protocol 中定义的所有属性
- 在 category 中定义的所有属性
- 重载的属性 当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar
@synthesize和@dynamic分别有什么作用?
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
使用@property 指令,就不需要在实现部分声明相应的实例变量
局部变量 只有在局部变量所在的方法运行时才存在,并且只能在此方法中访问
static 关键字可以使局部变量保留多次调用一个方法所得的值。static int hitCount = 0 ,声明hitCount是一个静态变量,和其他基本数据类型的局部变量不同,静态变量的初始值为0,所以前面显示的初始化时多余的。此外,它们只在程序开始执行初始化一次,并且在多次调用方法时保存这些数值。
-(int)showPage {
static int pageCount = 0;
++pageCount;
return pageCount;
}
在上面 定义了static变量,这个变量在编译期会对这个变量进行初始化赋值,也就是说这个变量值要么为nil,要么在编译期就可以确定其值,一般情况下,只能用NSString或者基本类型 ,并且这个变量只能在cellForRowAtIndexPath 访问
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
- static int pageCount = 0 将pageCount 设置为局部静态变量和实例变量之间的区别。前一种,pageCount能记录调用showPage方法的所有对象打印页面的数目。后一种情况,pageCount变量可以计算每个对象打印的页面数目,因为每个对象都有自己的pageCount副本
- 继承通常用于扩展一个类。
- 分类:分类机制允许你以模块的方式向现有类定义添加新方法。(不必经常给同一接口和实现文件增加新定义。当你希望对没有源代码访问权限的类添加新定义时)
- 扩展类 (自己看到书上的方法总结:比如一个矩形类Rectangle,没有中间坐标点的位置属性,所以专门写了一个坐标点的类XYPoint,定义坐标中点的属性。然后在矩形类里面引入坐标点的类 XYPoint *origin)
Rectangle :NSObject;
XYPoint :NSObject;
@implementation Rectangle {
XYPoint *origin;
}
- @class 指令提高了效率(个人看视频理解就是,在.h文件中引用@class。表示你这个类有可能用到@class的这个类。但是不知道会用到它具体的什么方法,所以在.m文件的时候要导入import 这个引用的类)
#import
与@class二者的区别在于:
import会链入该头文件的全部信息,包括实体变量和方法等;而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑。在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
在编译效率方面考虑,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用@class则不会。
如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。 在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import在@class中声明的类进来.
7 类
- 子类中直接使用父类的实例变量,必须先在接口部分声明,而不像书中其他例子在实现部分声明。父类实现部分声明和合成的实例变量是私有的,子类中并不能直接访问,需要明确定义或合成取值方法,才能访问实例变量的值
8 继承
- 类的每个实例都拥有自己的实例变量,即使这些实例变量是继承来的。 ClassA -> ClassB ->ClassC 。对象ClassC与对象ClassB具有完全不同的实例变量。
- 在默认情况下,合成的设值方法只是简单的复制对象指针,而不是对象本身。
- 覆写方法。就是在子类中定义一个和父类名称一样,返回类型一样,传入参数一样的方法。
- 当定义一个子类时,不仅可以添加新的方法来有效的扩展类的定义,还可以添加新的实例变量。
- 抽象类:为了更容易创建子类。等价的称抽象超类
9 多态、动态类型和动态绑定
- 多态 (相同的名称,不同的类)能够使来自不同类的对象定义相同名称的方法。 可以让你开发一组类,这组类中的每一个类都能响应相同的方法名。每个类的定义都封装了响应特定方法所需要的代码。多态允许添加新类,这些新类能响应相同的方法名。
- 动态类型 能使程序直到执行时才确定对象所属的类。
- 动态绑定 则能使程序直到执行时才能确定实际要调用的对象方法
- id类型 先判定对象所属的类,然后在运行时确定需要动态调用的方法,而不是在编译的时候。
id dataValue = [ [Fraction alloc] init];
[dataValue setReal: 10.0 andImaginary: 2.5];
- 这些代码在编译时,不会产生警告信息,因为编译器在处理源文件时并不知道存储在dataValue中的对象类型(即编译器并不知道Fraction对象早已存在于这个变量中)。直到运行包含这些代码的程序时,会崩溃。
- [Fraction setReal: andImaginary] : unrecognized selector sent to instance 0x103f00
- 当程序运行时,系统首先检查存储在dataValue中的对象类型,因为在dataValue中存储有Fraction 。所以运行时系统检查并查找定义在Fraction类中的 setReal: andImaginary 方法。因为未找到这个方法,所以程序会崩溃
- 静态类型 将一个变量定义为特定类的对象时,使用的是静态类型。“静态”指的是对存储在变量中对象的类型进行显示声明。这样存储在这种形态中的对象的类型是预定义的,也就是静态的。使用静态类型时,编译器尽可能确保变量的用法在程序中始终保持一致。编译器能够通过检查来确定应用于对象的方法是由该类定义的还是由该类继承的,否则它将显示警告信息。
- 有一些方法可以调用变量指定的方法,在这种情况下,编译器不会进行检查
- 使用静态类型的另一个原因是它能够提高程序的可读性。
- 使用动态类型来调用一个方法,需要注意以下规则:如果在多个类中实现名称相同的方法,那么每个方法必须要符合各个参数的类型和返回值类型。这样编译器才能为消息表达式生成正确的代码
- class-object是一个类对象(通常是由class方法产生)。selector是一个SEL类型的值(通常是由@selector指令产生的)
- respondsToSelector方法广泛用于实现委托(delegation)的概念。比如系统需要你在类中实现一个或多个方法用来响应某些事件和为了提供一些信息。为了让系统能够检查你确定实现了特定的方法,使用respondsToSelector 判断是否可以将事件的处理委托给你的方法。如果你没有实现这个方法,它会自己处理事件,按定义的默认行为来执行
- 使用@try处理异常:可以测试使程序异常终止的条件并处理这些情况,可能要记录一条消息并完全终止程序,或者采取其他的正确措施
int main(int argc, const char * argv[]) {
@autoreleasepool {
Fraction *f = [[Fraction alloc] init];
@try {
[f noSuchMethod];
} @catch (NSException *exception) {
[NSLog(@"Caught %@%@"),[exception name], [exception reason]];
} @finally {
}
}
return 0;
}
- 上面代码当出现异常时, @catch块被执行。包含异常信息的NSException对象作为参数传递给这个块。
- @finally块包含是否执行抛出异常的@try块中的语句代码。
- @throw指令允许你抛出自己的异常。可以使用该指令抛出特定的异常或者在@catch块内抛出带你进入类似如下代码块的异常
@throw
10 变量和数据类型
- 枚举数据类型可以为只存储一些值得链表这种数据类型定义名称。OC 语言的typedef 语句允许你为内置或派生的数据类型指定自己的名称。
- 对象的初始化 即创建对象的新实例,然后对它初始化
- 常见的编程习惯是类中所有的初始化方法都以Init开头。
- (id)init {
self = [super init];
if (self) {
//初始化代码
}
return self;
}
- 上面是覆写init方法的标准“模块”。首先会调用父类初始化方法。执行父类的初始化方法,使得继承的实例变量能够正常初始化。
- 必须将父类init方法的执行结果赋值给self,因为初始化过程改变了对象在内存中的位置(意味着引用将要改变)
- 如果父类的初始化过程成功,返回的值将是非空的,通过if语句可以验证。注释说明可以在这个代码块的位置放入自定义的初始化代码。通常可以在这个位置创建并初始化实例变量。
- 如果你的类中包含多个初始化方法,其中一个就应该是指定的(designated)初始化方法,并且其他所有的初始化方法都应该使用这个方法。通常,它是最复杂的初始化方法(一般是参数最多的初始化方法)。通过创建指定的初始化方法,可以把大部分初始化代码集中到单个方法中,然后人要想从该类派生子类,都可以重载这个指定的初始化方法,以便保证正确的初始化新的实例
- 为了使用指定的初始化规则,你需要修改Fraction类的init方法。这一点尤其是作为子类特别重要
- 注意:init被定义为返回id类型。子类的对象有可能并不等同于父类,为保持一致
- 程序开始执行时,向所有类发送Initialize调用方法。如果存在一个类及相关的子类,则父类首先得到这条消息,该消息只向其发送初始化消息。这样做的目的是程序开始时能够执行到所有类的初始化工作。列如你可能希望在开始时初始化与类相关的静态变量。
- @proterted 这个指令后面的实例变量可被该类以及该类的任何子类中定义的方法直接访问。在接口部分定义的实例变量默认是这种作用域
- @private 只能被当前类的对象方法中直接访问。在实现部分定义的实例变量默认是这种作用域。
但是要是@private在父类里面声明了成员变量,子类可以通过set方法访问(父类要有这个成员变量的set和get方法)。因为是继承,所以相当于子类也拥有这个@private成员变量。 - @public 这个指令后面的实例变量可被该类中定义的方法直接访问,也可被其他类或模块中定义的方法直接访问
- @package 对于64位映像,可以在实现该类的映像中任何地方访问这个实例变量。。。。只要处在同一个框架中,就能直接访问对象的成员变量
- 所谓的直接访问,就是比如父类有个成员变量_age。直接访问就是直接使用_age,如果_age是私有的private。要想在子类中访问,就要调用set方法
- 将实例变量声明为public并不是良好的编程习惯,因为违背了数据封装的思想(一个类需要隐藏它的实例变量)
- 在实现部分显示的声明的实例变量(或者使用@synthesize指令隐性声明的实例)是私有的,这就意味着并不能在子类中通过名字直接获取到实例变量 。在子类中只能使用继承的存取方法获取到实例变量的值
- 外部变量是可被其他任何地方或函数访问和更改其值得变量。在需要访问外部变量的模块中,变量声明和普通方式一样,只是需要在声明前加上关键字 exten 。这就告知系统,要访问其他文件中定义的全局变量。
extern int gMoveNumber
- 使用外部变量必须遵循下面的原则:1.变量必须定义在源文件的某个位置。即在所有的方法和函数之外声明的变量,并且前面不加关键字 extern(可以选择为这个变量赋值)
int gMoveNumber
- 确定外部变量的第二种方式是在所有函数之外声明变量,在声明前加 extern ,同时显示的为变量指派初始值
extern int gMoveNumber = 0;
- 但是编译器会出现警告,提示你已将变量声明为extern,并同时为变量赋值。这是因为使用关键字 extern 表明这条语句是变量的声明,而不是定义。(声明不会引起分配变量的存储空间,而定义会引起变量存储空间的分配)。前面的例子强行将声明当做定义处理。所以违背了这个原则
- extern声明的变量,如果有很多方法都访问,只在文件的开始进行一次 extern 声明。如果只有几个方法要访问该这个变量,就在方法中进行extern声明
- 如果变量定义在包含访问该变量的文件中,那么不需要进行单独extern声明
- 用static定义的变量,放在方法外,就不是 外部变量。只是全局变量
static int gCount;
-------
+ (int) count {
extern int gCount;
return gCount;
}
- static作用 ------修饰局部变量:1.延长局部变量的生命周期,程序结束才会销毁。2.局部变量只会生成一份内存,只会初始化一次。3.改变局部变量的作用域。------修饰全局变量:1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改。2.避免重复定义全局变量
- extern作用 ------只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量
- extern工作原理: ------先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
- 声明gCount为静态。其实并不需要声明 extern ,这样只是为了阅读该方法的人明白,他访问的变量是定义在该方法之外。程序开始运行时gCount 的值会自动置为0(如果要将类作为整体进行任何特殊的初始化,例如,将其它静态变量的值设置为一些非零值,可以重载继承类 initialize方法)
- 枚举 枚举数据类型的定义以关键字 enum 开头,之后是枚举数据类型的名称,然后是标识符序列(包含一对花括号内),它们定义了可以给该类型指派的所有允许值
enum flag { false, true }
enum direction { up, down, left = 10, right }
- 上述例子,up = 0; down = 1; left = 10; right = 11; 因为left = 10 ,所以后面的要递增
- OC编译器实际上是将枚举标识符作为整型常量来处理
- 在代码块中定义的枚举数据类型的作用域限于块的内部。在程序开始及所有块之外定义的枚举数据类型对于该文件是全局的。
- 定义枚举数据类型时,必须确保枚举标识符与定义在相同作用域之内的变量名和其他标识符不同
- typedef 允许为数据类型另外指派一个名称
typedef int Count
Count j, n;
- 上面第一句代码就是相当于把 Count 看成 int 。第二句代码就 相当于声明了2个 int 类的变量。
11 分类和协议
- 分类
- 类扩展 创建一个未命名的分类 且在括号 “ ()”之间不能指定名字 。未命名分类中声明的方法需要在主实现区域实现,而不是在分离的实现区域实现。
- 未命名分类非常有用,因为它们的方法都是私有的。如果只是想供类本身使用,用未命名分类比较合适
- 分类可以覆写该类中的另一个方法,但是这样不好。因为覆写了以后,再也不能访问原来的方法。如果真的要覆写,最好是创建子类。如果要在子类中覆写方法,仍然可以通过向super发送消息来引用父类的方法。
- 如果喜欢,可以拥有许多分类。
- 通过使用分类添加新方法来扩展类,不仅会影响这个类,还会影响它的所有子类。
- 协议是多个类共享的一个方法列表。协议提供一种方式,用指定的名称定义一组多少有点相关的方法。
- 协议列出一组方法,有些可以选择实现,有些必须实现。如果决定实现特定协议的所有方法,也就意味着要遵守( confirm to)或者采用 ( adopt )这项协议,可以定义协议中所有方法必须实现的,也可以都是选择实现的
- 定义协议很简单:只要使用 @protocol 指令,后面跟上你给出的协议名称。然后和处理接口部分一样,声明一些方法。 @end 指令之前所有的方法都是协议的一部分。
Fraction.h
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
- 如果你的类采用 NSCopying 协议,则必须实现名为 copyWithZone 的方法。通过 @interface 行的一对尖括号列出协议名称。告诉编译器,你采用一个协议
@interface Fraction : NSObject <NSCopying>
- 如果自己定义了自己的协议,可以不必有自己实现它,但是要告诉其他程序员。如果采用这项协议,则必须实现这项方法。这些方法可以从超类继承。也就是说,一个类遵守 NSCopying 协议,则它的子类也遵守NSCopying协议
- @optional 指定 表示指令之后写出的方法都是可选的。协议方法默认为 optional 修饰
- @requierd 指令来列出需要的方法。
- 协议不引用任何类,它是无类的(classless) 任何类都可以遵守 某个协议,并不仅仅是 XX的子类。
- 使用conformsToProtocol方法检查一个对象是否遵循某项协议。使用@protocol指令获取一个协议名称,并产生Protocol对象。
id currentObject;
-----
if ([currentObject containsObject:@protocol(Drawing)] == YES) {
------
}
- 通过在类型名称之后的尖括号中添加协议名称,可以借助编译器来检查变量的一致性。告诉编译器currentObject将包含遵守Drawing协议。如果向 currentObject 指派静态类型的对象,这个对象不遵守Drawing协议,编译器会警告 “waring: class 'XXX' does not implement the 'Drawing' protocol”。
id <Drawing> currentObject;
- 定义一项协议时,可以扩展现有协议的定义。
@protocol Drawing3D <Drawing>
- 说明协议Drawing3D也采用 Drawing 协议。因此任何采用 Drawing3D 协议的类都必须实现Drawing3D和 Drawing 协议的方法
- 分类也可以采用一项协议
@interface Fraction (Stuff) <NSCopying, NSCoding>
- 和类名一样,协议名必须是唯一的
- 协议也是一种两个类之间的接口定义。定义了协议的类可以看做是将协议定义的方法代理给了实现他们的分类。这样,类的定义可以更为通用,因为具体动作由代理类来承担,来响应某些事件或者定义某些参数。
- 非正式协议(抽象协议 abstract) 实际上是一个分类,列出了一组方法,但是没有实现它们。每个人(或者几乎每个人人)都继承相同的跟对象,因此,非正式分类通常是为根类定义的。
- 非正式协议的类自己并不实现这些方法,并且选择实现这些方法的子类需要在它的接口部分重新声明这些方法,同时还要实现这些方法中的一个或多个。和正式协议不同,编译器不提供有关非正式协议的帮助,这里没有遵守协议或者由编译器测试这样的概念
- 如果一个对象采用正式协议,则它必须遵守协议中的所有信息。如果一个对象采用非正式协议,则它可能不需要采用此协议的所有方法,具体取决于这项协议。
- optional 指令可以取代非正式协议使用
- 合成对象 你已经学习了通过派生子类和分类等技术可以扩展类定义的几种方式。还有一项技术可以定义一个类包含其他类的一个或多个对象。这个新类的对象就是所谓的合成对象,因为它是由其他对象组成的。(就是父类中有些方法子类不适合,或者不适用、还有可能改变父类以后。子类也改变,依赖父类,导致子类不能正常工作。所以就要创建子类的代替方式,定义一个新类,包含要扩展类的实例变量以及适合子类分方法)
12 预处理
- 预处理使用 “ # ” 标记。这个符号必须是一行的第一个非空字符
-
define 给符号名称指定程序常量
- 预处理名称不是变量。因此,不能为它赋值,除非替代指定值的结果实际上是一个变量。
- 预处理定义包含在程序的一行中。如果需要第二行,那么上一行最后一个字符必须是反斜线符号。表示还有后续。
-
undef 语句 使一些已经定义的名称成为未定义。
13 基本的C语言特性
函数、结构、指针、联合和数组。。。。
- 在函数中(方法)定义的变量称为自动布局变量。因为每次调用函数都是自动创建的。而且是局部的。
- 块 (blocks)是C语言的扩展 。也有返回值。定义在函数或者方法内部,并能够访问在函数或者方法范围内块之外的任何变量。一般来说,这些变量能够访问,但是不能够改变这些变量的值。有一个特殊的块修改器(由块前面含有两个下划线的字符组成)能够修改块内变量的值。
- 块的其中一个优势在于能够让系统分配给其他处理器或应用的其他线程执行
- 声明block 用copy。原因是在代码块里面我可能会使用一些本地变量。而block一开始是放在栈上的,只有copy后才会放到堆上。
- 默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使其在堆中保留一份, 相当于一直强引用着, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained. 以下是示例代码及其说明, 读者可以试着打印出不同情况下block的内存情况
- 翻译过来的。block使用weak的话,当对象被赋值以后,有可能被释放掉。
void (^printMessage)(void) =
^(void) {
NSLog(@"------");
} ;
- 块是以插入字符 “ ^ ”开头为标识。后面跟的一个括号表示块所需要的参数列表。
- 上面的方法是将块赋给一个变量 printMessage (只要变量声明正确)等号左边表示printMessage指向一个没有参数和返回值的块指针。需要注意的是,赋值语句是以分号终止
- 执行一个变量引用的块,和函数调是一样的 printMessage();
- 块能定义为全局或者局部的。
void (^printMessage)(int) =
^(int n) {
int i, triangularNumber = 0;
for (i = 1; i<= n; ++i) {
triangularNumber += i;
}
} ;
printMessage(10);
- 指向块的指针变量printMessage 以int 作为参数,并没有返回值
int (^gcd) (int, int) =
^(int u, int v) {
int temp;
while (v != 0) {
temp = u % v;
u = v;
v = temp;
}
return u;
};
- 块可以访问在其范围内定义的变量,变量的值同时作为块中定义的值。
int foo = 10;
void (^printFoo)(void) =
^(void) {
NSLog(@"----");
foo = 20;
};
foo = 15;
printFoo();
- 块printFoo 可以访问本地变量foo 。注意:打印的结果为10,而不是15。因为变量在定义块的同时已具有值了,而不是在块执行的时候 (不可以在块的内部修改变量 ---但是我记得可以的,只需要在块中加锁还是什么来着)
- 如果在定义本地变量之前插入_ _block修改器 _ _ block int foo = 10;此时打印的结果有两个,第一个为15,第二个为20.第一次显示的是调用块时foo的值,第二次验证块中能否将foo 值改变为20
结构
struct data {
int month;
int day;
int year;
};
struct data today;
today.month = 9;
today.day = 25;
today.year = 2011;
struct data today01 = {7,8,9};
- 结构的初始化和数组类似,例如上面最后一句。
typedef struct data today01;
- typedef提供了声明变量更简便的方式,不需要使用 struct 关键字
struct data {
int month;
int day;
int year;
} todayDate, purchaseDate;
- 定义了data结构,同时声明了变量todayDate, purchaseDate的数据类型
struct data {
int month;
int day;
int year;
} todayDate = {7,8,9};
- 定义了data结构,同时也将变量todayDate, purchaseDate赋值
struct data {
int month;
int day;
int year;
} todayDate [100];
- 定义了100个元素的数组datas ,每个元都包含年月日三个整型成员结构。因为没有为这个结构提供名称,所以定义同类型变量唯一方式就是再次显示地定义这个结构
指针
- 指针可以高效的表示复杂的数据结构,更改作为参数传递给函数和方法的值,并且更准确、高效的处理数组。
- & scanf 调用的时候使用&位运算符 在OC中是 一元预算符(又称地址运算符)用来得到变量指针
int count = 10, x;
int *intPtr;
intPtr = &count;
x = *intPtr;
- 变量count和x以常规方式声明为整型变量。变量intPtr声明为“int 指针”类型。然后对变量count应用地址预算符。它的作用就是创建该变量的指针。接着程序将该指针赋值给变量intPtr。x = *intPtr 告知编译器表达式 intPtr 是指向整型数据的值。而且前面的程序语句中已经将intPtr设为指向整型变量的count指针,所以count的值可以使用表达式intPtr间接访问
指针和结构
struct data {
int month;
int day;
int year;
};
struct data todaysDate;
struct data *datePtr;
datePtr = &todaysDate;
(*datePtr).day = 21;
datePtr->dat = 21;
试图生命周期
loadView -> viewDidLoad -> viewWillAppear -> viewDidAppear -> viewWillDisappear -> viewDidDisappear
当一个视图控制器被创建,并在屏幕上显示的时候。 代码的执行顺序
- alloc 创建对象,分配空间
- init (initWithNibName) 初始化对象,初始化数据
- loadView 从nib载入视图 ,通常这一步不需要去干涉。除非你没有使用xib文件创建视图
- viewDidLoad 载入完成,可以进行自定义数据以及动态创建其他控件
- viewWillAppear 视图将出现在屏幕之前,马上这个视图就会被展现在屏幕上了
- viewDidAppear 视图已在屏幕上渲染完成
当一个视图被移除屏幕并且销毁的时候的执行顺序,这个顺序差不多和上面的相反
- viewWillDisappear 视图将被从屏幕上移除之前执行
- iewDidDisappear 视图已经被从屏幕上移除,用户看不到这个视图了
- dealloc 视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放
A push B 的生命周期
A_viewDidLoad -> A_viewWillAppear -> A_viewDidAppear -> A_viewWillDisappear -> B_viewDidLoad ->
B_viewWillAppear -> A_viewDidDisappear -> B_viewDidAppear
B 回到 A (viewDidLoad 只加载一次)
B_viewWillDisappear -> A_viewWillAppear -> B_viewDidDisappear -> A_viewDidAppear
+load 和 +initialize 的区别是什么?
+load
- 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息
load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类 - load 方法不会被类自动继承
+initialize
也是在第一次使用这个类的时候会调用这个方法
load 在启动程序的时候就会调用,而initialize是在类被使用的时候(初始化)调用
什么是 Protocol,Delegate 一般是怎么用的?
- 协议是一个方法签名的列表,在其中可以定义若干个方法,遵守该协议的类可以实现协议里的方法,在协议中使用@property只会生成setter和getter方法的声明
- delegate用法:成为一个类的代理,可以去实现协议里的方法
为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?
- 如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃.因为向野指针发送了消息
- 实现自动remove:通过自释放机制,通过动态属性将remove转移给第三者,解除耦合,达到自动实现remove
强引用和弱引用
在强引用中,有时会出现循环引用的情况,这时就需要弱引用来帮忙(__weak)。
强引用持有对象,弱引用不持有对象。
强引用可以释放对象,但弱引用不可以,因为弱引用不持有对象,当弱引用指向一个强引用所持有的对象时,当强引用将对象释放掉后,弱引用会自动的被赋值为nil,即弱引用会自动的指向nil。
----------------------------
用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
-
如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
NSLog------NSStringFromClass([self class]) = Son
NSLog------NSStringFromClass([super class]) = Son
上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *xxx 这个对象。
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
这也就是为什么说“不推荐在 init 方法中使用点语法”,如果想访问实例变量 iVar 应该使用下划线( _iVar ),而非点语法( self.iVar )。
点语法( self.iVar )的坏处就是子类有可能覆写 setter 。
面试题
- 在ios第一版中,我们为输出口同时声明了属性和底层实例变量,那时,属性是oc语言的一个新的机制,并且要求你必须声明与之对应的实例变量
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
最近,苹果将默认编译器从GCC转换为LLVM(low level virtual machine),从此不再需要为属性声明实例变量了。
如果LLVM发现一个没有匹配实例变量的属性,它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为输出口声明实例变量。
例如:
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
在MyViewController.m文件中编译器也会自动的生成一个实例变量_myButton
那么在.m文件中可以直接的使用_myButton实例变量,也可以通过属性self.myButton.都是一样的。
注意这里的self.myButton其实是调用的myButton属性的getter/setter方法
如果点表达式出现在等号 = 左边,该属性名称的setter方法将被调用。如果点表达式出现在右边,该属性名称的getter方法将被调用。"
所以在oc中点表达式其实就是调用对象的setter和getter方法的一种快捷方式, 例如:
dealie.blah = greeble 完全等价于 [dealie.blah setBlah:greeble];
简单介绍以下几个宏:
-
__VA_ARGS__
是一个可变参数的宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉,否则会编译出错。 -
__FILE__
宏在预编译时会替换成当前的源文件名 -
__LINE__
宏在预编译时会替换成当前的行号 -
__FUNCTION__
宏在预编译时会替换成当前的函数名称
重新定义系统的NSLog,OPTIMIZE 是release 默认会加的宏
#ifndef __OPTIMIZE__
#define NSLog(...) NSLog(__VA_ARGS__)
#else
#define NSLog(...){}
#endif
直接自己写#define,当release版本的时候把#define 注释掉即可
#define IOS_DEBUG
#ifdef IOS_DEBUG
#define NSLog(...) NSLog(__VA_ARGS__)
#endif
这种方式需要修改项目的配置,使得在debug编译的时候,编译DLog的宏,产生详细的日志信息,而release的时候,不产生任何控制台输出相比而言,还是第一种比较方便
#ifdef DEBUG
# define DLog(format, ...) NSLog((@"[文件名:%s]" "[函数名:%s]" "[行号:%d]" format), __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...);
#endif
这个DEBUG在哪设置呢,
在 "Target > Build Settings > Preprocessor Macros > Debug" 里有一个"DEBUG=1"。
设置为Debug模式下,Product-->Scheme-->SchemeEdit Scheme
设置Build Configuration成Debug时,就可以打印nslog了。
设置Release,发布app版本的时候就不会打印了,提高了性能
%@ 对象
%d,%i 整型 (%i的老写法)
%hd 短整型
%ld , %lld 长整型
%u 无符整型
%f 浮点型和double型
%0.2f 精度浮点数,只保留两位小数
%x: 为32位的无符号整型数(unsigned int),打印使用数字0-9的十六进制,小写a-f;
%X: 为32位的无符号整型数(unsigned int),打印使用数字0-9的十六进制,大写A-F;
%o 八进制
%zu size_t
%p 指针地址
%e float/double (科学计算)
%g float/double (科学技术法)
%s char * 字符串
%.*s Pascal字符串
%c char 字符
%C unichar
%Lf 64位double
%lu sizeof(i)内存中所占字节数
isMemberOfClass 和 isKindOfClass 联系与区别
- 联系 两者都能查看一个对象是否是某个类的成员
- 区别 isKindOfClass可以查看一个对象是否是一个类的派生出来的类的成员
@interface CatA : NSObject
@end
@interface CatB : CatA
@end
@implementation CatC : NSObject
CatA *catA = [[CatA alloc] init];
CatB *catB = [[CatB alloc] init];
//第一组
[catA isKindOfClass:[A class]] = YES
[catA isMemberOfClass:[A class]] = YES
//第二组
[catB isKindOfClass:[CatB class]] = YES
[catB isMemberOfClass:[CatB class]] = YES
//第三组
[catB isKindOfClass:[CatA class] = YES
[catB isMemberOfClass:[CatA class] = NO
@end
上面代码可以知道,CatB是CatA的子类,分析三组数据:
一、第一组
第一组数据catA,是类CatA的一个实例,根据isMemberOfClass和isKindOfClass的定义(一个对象是否是某个类的实例),毋庸置疑答案是YES。
一、第二组
第二组数据catB,是类CatB的一个实例,同第一组数据结果一样都是YES。
一、第三组(重头戏)
第三组我们已经知道CatB是CatA的子类,也就是说CatB是CatA的派生类,并且catB是类CatB的实例对象,符合isKindOfClass定义的第二条, 所以[catB isKindOfClass:[CatA class] = YES;不符合isMemberOfClass的定义所以NO。
结论:
- [类的实例 isMemberOfClass] = YES [类的实例 isMemberOfClass] = YES ;
- [子类实例 isMemberOfClass 父类 ] = NO [子类实例 isKindOfClass 父类 ] = YES
@protocol 和 category 中如何使用 @property
- 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
- category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:
objc_setAssociatedObject
objc_getAssociatedObject
runtime 如何实现 weak 属性
要实现 weak 属性,首先要搞清楚 weak 属性的特点:
- weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
那么 runtime 如何实现 weak 变量的自动置nil?
- runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
再命令编译运行以后,在$ clang -rewrite-objc test.m 会把test.m 变成test.app C语言文件
使用static const 与 #define
-
使用static const修饰变量和宏定义的比较
相同点
- 都不能再被修改
- 一处修改,其它都改了
不同点
- static const修饰变量只有一份内存
- 宏定义,只是简单的替换,每次使用都需要创建一份内存
-
结论
使用static const修饰更加高效,在同一个文件内可以使用static const取代
#define
// static const修饰变量只有一份内存
static const CGFloat ZMJRed = 0.4;
// 宏定义,只是用0.4替换ZMJRed,每次使用都需要创建一份内存
#define ZMJRed 0.4
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
不用中间变量,用两种方法交换A和B的值
A = A+B;
B = A - B;
A = A - B;
内存黄金法则:
- 谁alloc,谁release!(包括new);
- 谁retain,谁release!
- (retain) 引用计数+1
- (release) 引用计数-1
- 谁copy,谁release!
内存分区:
1)、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2)、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。(ios中alloc都是存放在堆中)
3)、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。注意:全局区又可分为未初始化全局区:.bss段和初始化全局区:data段。
4)、常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5)、代码区—存放函数体的二进制代码。
集合类对象的copy与mutableCopy
- [immutableObject copy] // 浅复制
- [immutableObject mutableCopy] //单层深复制
- [mutableObject copy] //单层深复制
- [mutableObject mutableCopy] //单层深复制
对非集合类对象的copy操作:
- [immutableObject copy] // 浅复制
- [immutableObject mutableCopy] //深复制
- [mutableObject copy] //深复制
- [mutableObject mutableCopy] //深复制