/*提升启动速度
在 main() 函数执行前,系统主要会做下面几件事情:加载可执行文件(App 的.o 文件的集合);加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。
相应地,这个阶段对于启动速度优化来说,可以做的事情包括:减少动态库加载。每个库本身都有依赖关系,苹果公司建议使用更少的动态库,并且建议在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司建议最多使用 6 个非系统动态库。减少加载启动后不会去使用的类或者方法。+load() 方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换掉。因为,在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。不要小看这 4 毫秒,积少成多,执行 +load() 方法对启动速度的影响会越来越大。控制 C++ 全局变量的数量。
链接器
使用编译器和解释器执行代码的特点,我们就可以概括如下:采用编译器生成机器码执行的好处是效率高,缺点是调试周期长。解释器执行的好处是编写调试方便,缺点是执行效率低
现在苹果公司使用的编译器是 LLVM
首先,你写好代码后,LLVM 会预处理你的代码,比如把宏嵌入到对应的位置。预处理完后,LLVM 会对代码进行词法分析和语法分析,生成 AST 。AST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 IR(中间表示)。最后 AST 会生成 IR,IR 是一种更接近机器码的语言,区别在于和平台无关,通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O。
在真实的 iOS 开发中,你会发现很多功能都是现成可用的,不光你能够用,其他 App 也在用,比如 GUI 框架、I/O、网络等。链接这些共享库到你的 Mach-O 文件,也是通过链接器来完成的。链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的 Mach-O 文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用 dyld 就可以实现动态加载。
包大小
App Thinning 会专门针对不同的设备来选择只适用于当前设备的内容以供下载。比如,iPhone 6 只会下载 2x 分辨率的图片资源,iPhone 6plus 则只会下载 3x 分辨率的图片资源。在苹果公司使用 App Thinning 之前, 每个 App 包会包含多个芯片的指令集架构文件。
Objective-C 是一种简单的计算机语言,设计为可以支持真正的面向对象编程。
Objective-C 通过提供类定义,方法以及属性的语法,还有其他可以提高类的动态扩展能力的结构等,扩展了标准的 ANSI C 语言。类的语法和设计主要是基于 Smalltalk,最早的面向对象编程语言之一。
如果你以前使用过其他面向对象编程语言,那么下面的信息可以帮助你学习 Objective-C 的基本语法。许多传统的面向对象概念,例如封装,继承以及多态,在 Objective-C 中都有所体现。这里有一些重要的不同,但是这些不同在这文章会表现出来,而且如果你需要还有更多详细的信息存在。
如果你从来没有使用任何编程语言编过程序,那么你至少需要在开始之前,对相关概念进行一些基础的了解。对象的使用和对象对象架构是 iPhone 程序设计的基础,理解他们如何交互对创建你的程序非常重要。想了解面向对象概念的,请参看使用 Objective-C 进行面向对象编程。
Objective-C:C的超集
Objective-Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C被描述为盖在C语言上的薄薄一层,因为Objective-C的原意就是在C语言主体上加入面向对象的特性。
Objective-C代码的文件扩展名
| 扩展名 | 内容类型 |
|---|---|
| .h | 头文件。头文件包含类,类型,函数和常数的声明。 |
| .m | 源代码文件。这是典型的源代码文件扩展名,可以包含 Objective-C 和 C 代码。 |
| .mm | 源代码文件。带有这种扩展名的源代码文件,除了可以包含Objective-C和C代码以外还可以包含C++代码。仅在你的Objective-C代码中确实需要使用C++类或者特性的时候才用这种扩展名。 |
当你需要在源代码中包含头文件的时候,你可以使用标准的 #include 编译选项,但是 Objective-C 提供了更好的方法。#import 选项和 #include 选项完全相同,只是它可以确保相同的文件只会被包含一次。Objective-C 的例子和文档都倾向于使用 #import,你的代码也应该是这样的。
语法
Objective-C的面向对象语法源于Smalltalk消息传递风格。所有其他非面向对象的语法,包括变量类型,预处理器(preprocessing),流程控制,函数声明与调用皆与C语言完全一致。但有些C语言语法合法代码在objective-c中表达的意思不一定相同,比如某些布尔表达式,在C语言中返回值为true,但在Objective-C若与yes直接相比较,函数将会出错,因为在Objective-C中yes的值只表示为1。
第一个 Objective-C 程序,基于Xcode 4.3.1:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSLog(@"Hello World!");
}
return 0;
}
消息传递
Objective-C最大的特色是承自Smalltalk的消息传递模型(message passing),此机制与今日C++式之主流风格差异甚大。Objective-C里,与其说对象互相调用方法,不如说对象之间互相传递消息更为精确。此二种风格的主要差异在于调用方法/消息传递这个动作。C++里类别与方法的关系严格清楚,一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能调用一个不存在类别里的方法。但在Objective-C,类别与消息的关系比较松散,调用方法视为对对象发送消息,所有方法都被视为对消息的回应。所有消息处理直到运行时(runtime)才会动态决定,并交由类别自行决定如何处理收到的消息。也就是说,一个类别不保证一定会回应收到的消息,如果类别收到了一个无法处理的消息,程序只会抛出异常,不会出错或崩溃。
C++里,送一个消息给对象(或者说调用一个方法)的语法如下:
obj.method(argument);
Objective-C则写成:
[obj method: argument];
此二者并不仅仅是语法上的差异,还有基本行为上的不同。
这里以一个汽车类(car class)的简单例子来解释Objective-C的消息传递特性:
[car fly];
典型的C++意义解读是"调用car类别的fly方法"。若car类别里头没有定义fly方法,那编译肯定不会通过。但是Objective-C里,我们应当解读为"发提交一个fly的消息给car对象",fly是消息,而car是消息的接收者。car收到消息后会决定如何回应这个消息,若car类别内定义有fly方法就运行方法内之代码,若car内不存在fly方法,则程序依旧可以通过编译,运行期则抛出异常。
此二种风格各有优劣。C++强制要求所有的方法都必须有对应的动作,且编译期绑定使得函数调用非常快速。缺点是仅能借由virtual关键字提供有限的动态绑定能力。Objective-C天生即具备鸭子类型之动态绑定能力,因为运行期才处理消息,允许发送未知消息给对象。可以送消息给整个对象集合而不需要一一检查每个对象的类型,也具备消息转送机制。同时空对象nil接受消息后默认为不做事,所以送消息给nil也不用担心程序崩溃。
字符串
作为C语言的超集,Objective-C 支持 C 语言字符串方面的约定。也就是说,单个字符被单引号包括,字符串被双引号包括。然而,大多数Objective-C通常不使用C语言风格的字符串。反之,大多数框架把字符串传递给NSString对象。NSString类提供了字符串的类包装,包含了所有你期望的优点,包括对保存任意长度字符串的内建内存管理机制,支持Unicode,printf风格的格式化工具,等等。因为这种字符串使用的非常频繁,Objective-C提供了一个助记符可以方便地从常量值创建NSString对象。要使用这个助记符,你需要做的全部事情,是在普通的双引号字符串前放置一个@符号,如下面的例子所示:
NSString* myString = @"My String\n";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];
// 从一个C语言字符串创建Objective-C字符串
NSString* fromCString = [NSString stringWithCString:"A C string"
encoding:NSASCIIStringEncoding];
类
如同所有其他的面向对象语言,类是 Objective-C 用来封装数据,以及操作数据的行为的基础结构。对象就是类的运行期间实例,它包含了类声明的实例变量自己的内存拷贝,以及类成员的指针。Objective-C 的类规格说明包含了两个部分:定义(interface)与实现(implementation)。定义(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。
下图展现了声明一个叫做 MyClass 的类的语法,这个类继承自 NSObject 基础类。类声明总是由 @interface 编译选项开始,由 @end 编译选项结束。类名之后的(用冒号分隔的)是父类的名字。类的实例(或者成员)变量声明在被大括号包含的代码块中。实例变量块后面就是类声明的方法的列表。每个实例变量和方法声明都以分号结尾。
类的定义文件遵循C语言之惯例以.h为后缀,实现文件以.m为后缀。
类声明图
Interface
定义部分,清楚定义了类的名称、数据成员和方法。 以关键字@interface作为开始,@end作为结束。
@interface MyObject : NSObject {
int memberVar1; // 实体变量
id memberVar2;
}
+(return_type) class_method; // 类方法
-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end
方法前面的 +/- 号代表函数的类型:加号(+)代表类方法(class method),不需要实例就可以调用,与C++ 的静态函数(static member function)相似。减号(-)即是一般的实例方法(instance method)。
这里提供了一份意义相近的C++语法对照,如下:
class MyObject : public NSObject {
protected:
int memberVar1; // 实体变量
void * memberVar2;
public:
static return_type class_method(); // 類方法
return_type instance_method1(); // 实例方法
return_type instance_method2( int p1 );
return_type instance_method3( int p1, int p2 );
}
Objective-C定义一个新的方法时,名称内的冒号(:)代表参数传递,不同于C语言以数学函数的括号来传递参数。Objective-C方法使得参数可以夹杂于名称中间,不必全部附缀于方法名称的尾端,可以提高程序可读性。设定颜色RGB值的方法为例:
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/
[myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/
这个方法的签名是setColorToRed:Green:Blue:。每个冒号后面都带着一个float类别的参数,分别代表红,绿,蓝三色。
Implementation
实现区块则包含了公开方法的实现,以及定义私有(private)变量及方法。 以关键字@implementation作为区块起头,@end结尾。
@implementation MyObject {
int memberVar3; //私有實體變數
}
+(return_type) class_method {
.... //method implementation
}
-(return_type) instance_method1 {
....
}
-(return_type) instance_method2: (int) p1 {
....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
....
}
@end
值得一提的是不只Interface区块可定义实体变量,Implementation区块也可以定义实体变量,两者的差别在于访问权限的不同,Interface区块内的实体变量默认权限为protected,宣告于implementation区块的实体变量则默认为private,故在Implementation区块定义私有成员更匹配面向对象之封装原则,因为如此类别之私有信息就不需曝露于公开interface(.h文件)中。
创建对象
Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对象。 init与alloc都是定义在NSObject里的方法,父对象收到这两个信息并做出正确回应后,新对象才创建完毕。以下为范例:
MyObject * my = [[MyObject alloc] init];
在Objective-C 2.0里,若创建对象不需要参数,则可直接使用new
MyObject * my = [MyObject new];
仅仅是语法上的精简,效果完全相同。
若要自己定义初始化的过程,可以重写init方法,来添加额外的工作。(用途类似C++ 的构造函数constructor)
方法
Objective-C 中的类可以声明两种类型的方法:实例方法和类方法。实例方法就是一个方法,它在类的一个具体实例的范围内执行。也就是说,在你调用一个实例方法前,你必须首先创建类的一个实例。而类方法,比较起来,也就是说,不需要你创建一个实例。
方法声明包括方法类型标识符,返回值类型,一个或多个方法标识关键字,参数类型和名信息。下图展示 insertObject:atIndex: 实例方法的声明。声明由一个减号(-)开始,这表明这是一个实例方法。方法实际的名字(insertObject:atIndex:)是所有方法标识关键的级联,包含了冒号。冒号表明了参数的出现。如果方法没有参数,你可以省略第一个(也是唯一的)方法标识关键字后面的冒号。本例中,这个方法有两个参数。
方法声明语法
当你想调用一个方法,你传递消息到对应的对象。这里消息就是方法标识符,以及传递给方法的参数信息。发送给对象的所有消息都会动态分发,这样有利于实现Objective-C类的多态行为。也就是说,如果子类定义了跟父类的具有相同标识符的方法,那么子类首先收到消息,然后可以有选择的把消息转发(也可以不转发)给他的父类。
消息被中括号( [ 和 ] )包括。中括号中间,接收消息的对象在左边,消息(包括消息需要的任何参数)在右边。例如,给myArray变量传递消息insertObject:atIndex:消息,你需要使用如下的语法:
[myArray insertObject:anObj atIndex:0];
为了避免声明过多的本地变量保存临时结果,Objective-C允许你使用嵌套消息。每个嵌套消息的返回值可以作为其他消息的参数或者目标。例如,你可以用任何获取这种值的消息来代替前面例子里面的任何变量。所以,如果你有另外一个对象叫做myAppObject拥有方法,可以访问数组对象,以及插入对象到一个数组,你可以把前面的例子写成如下的样子:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
虽然前面的例子都是传递消息给某个类的实例,但是你也可以传递消息给类本身。当给类发消息,你指定的方法必须被定义为类方法,而不是实例方法。你可以认为类方法跟C++类里面的静态成员有点像(但是不是完全相同的)。
类方法的典型用途是用做创建新的类实例的工厂方法,或者是访问类相关的共享信息的途径。类方法声明的语法跟实例方法的几乎完全一样,只有一点小差别。与实例方法使用减号作为方法类型标识符不同,类方法使用加号( + )。
下面的例子演示了一个类方法如何作为类的工厂方法。在这里,arrayWithCapacity是NSMutableArray类的类方法,为类的新实例分配内容并初始化,然后返回给你。
NSMutableArray* myArray = nil; // nil 基本上等同于 NULL
// 创建一个新的数组,并把它赋值给 myArray 变量
myArray = [NSMutableArray arrayWithCapacity:0];
属性
OC中所有方法均为公有方法,所以OC要实现函数的私有化(在类外部不能直接调用),是通过隐藏函数名(只写实现不写声明)。
方法在父类实现不声明,在子类声明不实现,子类也可调用该父类方法。
如果要通过点语法调用setter或getter函数,需满足:
setter:-(void)set+(首先成员变量去掉””,并且首字母大写):(成员变量的指针类型)
getter:-(成员变量的指针类型)+(去掉””之后的成员变量的变量名)
如果我们自定义(重写)setter方法,@property就不会生成它的setter方法,但是仍然会帮我们生成getter方法和私有的成员变量;
如果我们自定义(重写)getter方法,@property就不会生成它的getter方法,但是仍然会帮我们生成setter方法和私有的成员变量;
如果我们同时自定义(重写)setter和getter方法,@property就不会生成它的setter和getter方法,也不会帮我们生成setter、getter方法和私有的成员变量。
属性修饰符
MRC下 属性修饰符下的强弱引用
只要是OC对象,都要使用强引用,C语言基本类型,可以使用弱引用
assign属性修饰符下默认生成的setter为弱引用(_age = age)
@property (nonatomic,assign)NSInteger age;
retain属性修饰符下默认生成的setter为强引用,可变字符串也用retain
@property (nonatomic,retain)NSMutableArray *arr;
copy属性修饰符下默认生成的setter为强引用,一般用于NSString*
@property (nonatomic,copy)NSString *name;
ARC下 新增的赋值操作(兼容MRC下的修饰符)
strong:强引用,默认,修饰对象指针,会自动retain或release,ARC环境下苹果推荐使用
只要一个对象还有强引用指针指向它,它就永远不能被释放掉
@property (nonatomic,strong) NSMutableArray *arr1;
@property (nonatomic,copy) NSString* name1;
@property (nonatomic,assign) NSInteger age1;
weak:弱引用,修饰对象指针,不修饰C的基本类型,指向的对象消失时,自动变成nil
@property (nonatomic,weak) NSMutableArray *arr2;
unsafe_unretained:相当于weak,当指向的对象消失时,不会自动变成nil 属性是用来代替声明存取方法的便捷方式。属性不会在你的类声明中创建一个新的实例变量。他们仅仅是定义方法访问已有的实例变量的速记方式而已。暴露实例变量的类,可以使用属性记号代替getter和setter语法。类还可以使用属性暴露一些“虚拟”的实例变量,他们是部分数据动态计算的结果,而不是确实保存在实例变量内的。
实际上可以说,属性节约了你必须要写的大量多余的代码。因为大多数存取方法都是用类似的方式实现的,属性避免了为类暴露的每个实例变量提供不同的getter和setter的需求。取而代之的是,你用属性声明指定你希望的行为,然后在编译期间合成基于声明的实际的getter和setter方法。
属性声明应该放在类接口的方法声明那里。基本的定义使用@property编译选项,紧跟着类型信息和属性的名字。你还可以用定制选项对属性进行配置,这决定了存取方法的行为。下面的例子展示了一些简单的属性声明:
@interface Person : NSObject {
@public
NSString *name;
@private
int age;
}
@property(copy) NSString *name;
@property(readonly) int age;
-(id)initWithAge:(int)age;
@end
性的访问方法由@synthesize关键字来实现,它由属性的声明自动的产生一对访问方法。另外,也可以选择使用@dynamic关键字表明访问方法会由程序员手工提供。
@implementation Person
@synthesize name;
@dynamic age;
-(id)initWithAge:(int)initAge
{
age = initAge; // 注意:直接赋给成员变量,而非属性
return self;
}
-(int)age
{
return 29; // 注意:并非返回真正的年龄
}
@end
属性可以利用传统的消息表达式、点表达式或"valueForKey:"/"setValue:forKey:"方法对来访问。
Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
[aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);
为了利用点表达式来访问实例的属性,需要使用"self"关键字:
-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}
类或协议的属性可以被动态的读取。
int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);
for ( i=0; i < propertyCount; i++ ) {
objc_property_t *thisProperty = propertyList + i;
const char* propertyName = property_getName(*thisProperty);
NSLog(@"Person has a property: '%s'", propertyName);
}
快速枚举
比起利用NSEnumerator对象或在集合中依次枚举,Objective-C 2.0提供了快速枚举的语法。在Objective-C 2.0中,以下循环的功能是相等的,但性能特性不同。
// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;
while ( (p = [enumerator nextObject]) != nil ) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚举
for ( int i = 0; i < [thePeople count]; i++ ) {
Person *p = [thePeople objectAtIndex:i];
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚举
for (Person *p in thePeople) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
快速枚举可以比标准枚举产生更有效的代码,由于枚举所调用的方法被使用NSFastEnumeration协议提供的指针算术运算所代替了。
继承特点:
1)子类可以扩展自己特有的属性(包括新增成员变量),但是不能访问父类的私有成员变量。
2)子类可以扩展自己特有的方法,且新方法可以与原方法同名,而不会对父类原方法产生影响。
什么情况下使用继承?
1)扩展类的属性。
2)新扩展的方法与原方法同名,但是还需要使用父类的实现。
分类(category)特点:
1)分类可以在不改变类名和原有类的实现的前提下,进行类的扩展。
2)在方法内部可以访问原有类的成员变量(在头文件中定义的成员变量),不能给原有类增加成员变量(使用@property只能生成setter和getter方法的声明,不能生成方法的实现,也不会生成带“_”的成员变量)。 可以给原有类添加属性:使用Runtime里的运行时关联对象方法来实现属性的setter和getter方法。
3)分类与原有类不能有同名的方法。因为这样会覆盖原类的实现,而无法访问到原来的方法。
4)分类之间也不能有同名的方法。因为最后编译的那个方法会覆盖其他方法。
什么情况下使用分类?
1)针对系统类(例如:NSString、NSArray、NSNumber等),系统本身不提倡使用继承去扩展方法,因为这些类内部实现对继承有所限制,所以应使用分类的方式扩展。
2)分类支持开发人员针对自己构建的类,把相关的方法分组到多个单独的文件中,对于大型而复杂的类,这有助于提高可维护性,并简化单个源文件的管理。
extension
扩展也叫匿名分类,区别是扩展只有.h文件,而实现是写在被扩展类的.m文件中的。因此,扩展的功能可以认为是被扩展类所私有的,其他类无法使用。
扩展的作用:为类增加私有的属性和方法。一般不用于给系统类添加方法。
因为是私有属性和方法,我们一般不另外生成Extension(扩展)的.h文件,而是将.h文件里的内容(声明)直接写在被扩展类的.m文件里。
例如,我们要扩展一个自定义的Person类,不用另外创建Extension的.h文件,而是在Person的.m文件里加入
协议(Protocol)
协议是多个类(或者说对象)之间协商的一个公共接口,提供一系列方法的声明给类们使用;而代理是协议的一个典型应用机制
Protocol用来声明一些方法,或者说,Protocol是由一系列的方法声明组成的。
一个类只要遵守了Protocol,就相当于拥有了Protocol的所有方法声明。
一个Protocol只要遵守了其他Protocol,就相当于拥有了其他Protocol的所有方法声明。
1、创建一个协议,并使被代理方和代理方均遵守这个协议;
2、被代理方声明一个代理属性:@property (nonatomic, weak) id<代理协议> delegate;
3、代理方实现代理方法(即协议里声明的方法);
4、设置代理:被代理方对象名.delegate = 代理方对象名;
5、当被代理方需要代理方做事情的时候,用delegate调用代理方法,通知代理方干活;
6、代理方对象执行代理方法。
协议是一组没有实现的方法列表,任何的类均可采纳协议并具体实现这组方法。
Objective-C在NeXT时期曾经试图引入多重继承的概念,但由于协议的出现而没有实现之。
协议类似于Java与C#语言中的"接口"。在Objective-C中,有两种定义协议的方式:由编译器保证的"正式协议",以及为特定目的设定的"非正式协议"。
非正式协议为一个可以选择性实现的一系列方法列表。非正式协议虽名为协议,但实际上是挂于NSObject上的未实现分类(Unimplemented Category)的一种称谓,Objetive-C语言机制上并没有非正式协议这种东西,OSX 10.6版本之后由于引入@optional关键字,使得正式协议已具备同样的能力,所以非正式协议已经被废弃不再使用。
正式协议类似于Java中的"接口",它是一系列方法的列表,任何类都可以声明自身实现了某个协议。在Objective-C 2.0之前,一个类必须实现它声明匹配的协议中的所有方法,否则编译器会报告错误,表明这个类没有实现它声明匹配的协议中的全部方法。Objective-C 2.0版本允许标记协议中某些方法为可选的(Optional),这样编译器就不会强制实现这些可选的方法。
协议经常应用于Cocoa中的委托及事件触发。例如文本框类通常会包括一个委托(delegate)对象,该对象可以实现一个协议,该协议中可能包含一个实现文字输入的自动完成方法。若这个委托对象实现了这个方法,那么文本框类就会在适当的时候触发自动完成事件,并调用这个方法用于自动完成功能。
Objective-C中协议的概念与Java中接口的概念并不完全相同,即一个类可以在不声明它匹配某个协议的情况下,实现这个协议所包含的方法,也即实质上匹配这个协议,而这种差别对外部代码而言是不可见的。正式协议的声明不提供实现,它只是简单地表明匹配该协议的类实现了该协议的方法,保证调用端可以安全调用方法。
语法
协议以关键字@protocol作为区块起始,@end结束,中间为方法列表。
@protocol Locking
- (void)lock;
- (void)unlock;
@end
这是一个协议的例子,多线程编程中经常要确保一份共享资源同时只有一个线程可以使用,会在使用前给该资源挂上锁 ,以上即为一个表明有"锁"的概念的协议,协议中有两个方法,只有名称但尚未实现。
下面的SomeClass宣称他采纳了Locking协议:
@interface SomeClass : SomeSuperClass <Locking>
@end
一旦SomeClass表明他采纳了Locking协议,SomeClass就有义务实现Locking协议中的两个方法。
@implementation SomeClass
- (void)lock {
// 實現lock方法...
}
- (void)unlock {
// 實現unlock方法...
}
@end
由于SomeClass已经确实遵从了Locking协议,故调用端可以安全的发送lock或unlock消息给SomeClass实体变量,不需担心他没有办法回应消息。
插件是另一个使用抽象定义的例子,可以在不关心插件的实现的情况下定义其希望的行为。
闭包的定义及使用
1. 概念
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
闭包的定义及使用
1. 概念
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
// 闭包表达式
{ (参数列表) -> 返回值类型 in
// 闭包体 (可执行代码语句)
}
// 捕获值
var aa = 10
let setValue: () -> () = {
aa = 88
}
// 定义函数
func doAdd(aClosure: (Int, Int) -> Int, x: Int, y: Int) -> Int {
return aClosure(x, y) // 传入函数的x/y值, 返回闭包的返回值
}
// 调用函数, 在这里写闭包体
let ret = doAdd(aClosure: { (aa, bb) -> Int in
return aa + bb // 闭包体部分: 接收函数的x/y值进行相加
}, x: 10, y: 15)
print(ret)
// 打印 25
KVC
1、概述
KVC(Key Value Coding)即键值编码,能简便地动态读写对象属性,其实现方法是使用字符串来描述需要更改的对象属性。
KVC的操作方法由NSKeyValueCoding协议提供,而NSObject遵循了该协议,所以说,OC中几乎所有的对象都支持KVC操作。
写入操作
setValue:(nullable id) forKey:(NSString *) 用于简单路径
setValue:(nullable id) forKeyPath:(NSString *) 用于复合路径
读取操作
valueForKey:(NSString *) 用于简单路径
valueForKeyPath:(NSString *) 用于复合路径
[person setValue:@"King" forKey:@"name"];
底层实现
写入操作时(例如setValue: forKey:@"A"),方法内部会做以下操作:
检查是否存在相应key的setter方法(setA),如存在则调用setter方法;
如果没有setter方法,就会查找与key相同名称并且带下划线的成员变量(_A),如果有则直接赋值;
如果没有带下划线的成员变量,则搜索与key同名的成员变量(A),有则直接赋值;
如果最后仍没找到,则调用setValue: forUndefinedKey:方法。
读取操作时(例如valueForKey:@"A"),方法内部会做以下操作:
检查是否存在相应key的getter方法(A),如存在则调用getter方法;
如果没有getter方法,就会查找与key相同名称并且带下划线的成员变量(_A),如果有则直接读取;
如果没有带下划线的成员变量,则搜索与key同名的成员变量(A),有则直接读取;
如果最后仍没找到,则调用valueForUndefinedKey:方法。
KVO
1、概述
KVO(Key Value Observing)即键值监听,是一种观察者模式,通过对某个对象的某个属性添加监听,当该属性改变时,会调用相应方法。
KVO的操作方法由NSKeyValueObserving协议提供,而NSObject遵循了该协议,所以说,OC中几乎所有的对象都支持KVO操作。
动画
UIView本身封装了一些常用的动画:基础动画、关键帧动画及转场动画。对于一般的iOS开发基本能满足,如果需要更多自定义的动画效果,那就使用核心动画CoreAnimation
[UIView animateWithDuration:5.0 // 动画持续时间
delay:0 // 延迟0秒执行
usingSpringWithDamping:0.5 // 阻尼,范围0-1,阻尼越接近于0,弹性效果越明显
initialSpringVelocity:1.0 // 弹性复位的速度
options:UIViewAnimationOptionCurveLinear
animations:^{
NSLog(@"block动画开始");
_imageView.center = location;
}
completion:^(BOOL finished) {
if (finished) {
NSLog(@"block动画结束");
}
}];
UIButton : UIControl : UIView : UIResponder : NSObject
UIwindow UIviewController
UIWindow对象充当了App中UI界面的背景(容器/载体), 还有一个作用是分派事件给各种view.
根据Apple官方文档的这段话, 可以延伸为以下两点:
App中任何一个view, 只有添加到相应的window中, 才能显示出来;
触摸事件会被传递到触摸区域内的最上层的window, 非触摸事件会被传递到keyWindow (详见后文), 并由window将事件分发给恰当的view.
重登陆
当我们使用支付宝或者一些金融类App的时候, 会发现当App从后台返回前台的时候, 总是要重新输入密码, 以提高软件安全性. 这个重新输入的密码界面一般是通过新增一个window来实现的, 因为它可能从App中任意一个界面调用出来, 使用VC或者view的话终究不太方便.
下面来简单介绍一下实现过程, 代码比较简单, 就不贴上了:
AppDelegate中监听返回前台的通知applicationWillEnterForegroundNotification;
在通知中实例化一个自定义window并makeKeyAndVisible, window中加入我们的密码界面;
当用户输入正确密码后直接将该window=nil, 系统自动将keyWindow变为上之前的window.
UIViewController : UIResponder : NSObject
UIViewController是专门用来管理view的, 它具有但不限于在view出现或者消失时调用的方法. 从上面的继承关系中我们知道, UIViewController没有继承自UIView, 它不属于视图类, 也不显示任何内容, 没有frame这个概念. 但是UIViewController有一个view属性, 即self.view, 用于显示内容. 在一个App中, view controller并不是必要的, 但是一般我们的项目中至少包含一个子类继承自UIViewController. 比如创建模板App的时候, 系统默认创建了一个ViewController来作为window的rootViewController, 而他的self.view就是我们看到的第一个视图
#pragma mark - 生命周期
// 每次访问属性view(比如controller.view、self.view)而且view为nil时
- (void)loadView {
[super loadView];
NSLog(@"%s", __func__);
}
// view加载完成
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%s", __func__);
self.view.backgroundColor = [UIColor purpleColor];
}
// view准备出现. 一般用法: 改变视图方向、状态栏方向、视图显示样式等
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
// view即将布局其子视图或者其中一个view的bounds发生改变. 例如: 屏幕旋转, 添加一个sub view
- (void)viewWillLayoutSubviews {
NSLog(@"%s", __func__);
}
// view本身布局完成. 注意: 调用此方法时并不代表所有子视图都调整布局完成了, 每个子视图负责调整自己的布局!
- (void)viewDidLayoutSubviews {
NSLog(@"%s", __func__);
}
// view已经出现.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
// 添加一个view, 会再次调用viewWillLayoutSubviews和viewDidLayoutSubviews方法
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[self.view addSubview:view1];
}
// view即将消失
- (void)viewWillDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewWillDisappear:animated];
}
// view已经消失
- (void)viewDidDisappear:(BOOL)animated {
NSLog(@"%s", __func__);
[super viewDidDisappear:animated];
}
// 当没有内存泄漏时, 正常销毁UIViewController对象会调用此方法
- (void)dealloc
{
NSLog(@"%s", __func__);
}
获取windows中 最上层的 且 正在显示 的window
获取window中最上层的view controller
具体代码如下:
// 获取当前正在显示的view controller
- (UIViewController *)getCurrentViewController {
// 获取windows中 最上层的 且 正在显示 的window
UIWindow *topWindow = nil;
NSArray<UIWindow *> *windows = [[UIApplication sharedApplication].windows copy];
for (int i=(int)windows.count-1; i>=0; i--) {
UIWindow *tempWindow = windows[i];
if (tempWindow.hidden == NO) {
topWindow = tempWindow;
break;
}
}
// 获取该window中最上层的view controller
UIViewController *result = topWindow.rootViewController;
while (result.presentedViewController) {
result = result.presentedViewController;
}
if ([result isKindOfClass:[UITabBarController class]]) {
result = [(UITabBarController *)result selectedViewController];
}
if ([result isKindOfClass:[UINavigationController class]]) {
result = [(UINavigationController *)result visibleViewController];
}
return result;
}
UINavigationController : UIViewController : UIResponder : NSObject
UINavigationController使用有序数组(称为导航堆栈)管理其子视图控制器。数组中的第一个视图控制器是根视图控制器 (没有rootViewController属性, 使用viewControllers[0]获取),表示堆栈的底部。数组中的最后一个视图控制器是堆栈中最顶层视图控制器(topViewController)
如图, 导航控制器主要管理四个对象: 子视图控制器, 导航栏, 工具栏, 其delegate对象.
Run Loops
Run Loops (运行循环) 是与线程相关联的基础设施的一部分, 目的是在有工作要做时让线程忙, 而在没有工作时让线程进入睡眠状态.
Run Loops与线程是一一对应的, 其关系是保存在一个全局的 Dictionary 里.
我们不能主动创建RunLoop, 只能通过CFRunLoopGetMain() 和 CFRunLoopGetCurrent()分别获取主线程和子线程的RunLoop.
主线程中, 系统会自动帮我们创建一个RunLoop; 而子线程中的RunLoop采用类似懒加载的机制, 即我们第一次去获取的时候才会创建, 如果不调用CFRunLoopGetCurrent()获取RunLoop, 那么就一直没有.
RunLoop用于需要与线程更多交互的情况. 例如:
使用端口或自定义输入源与其他线程进行通信
在线程上使用计时器
使用任何performSelector方法
保持线程执行定期任务
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。
C: 静态语言. 编译阶段就要决定调用哪个函数, 如果函数未实现就会编译报错.
OC: 动态语言(得益于runtime机制). 运行时才决定调用哪个函数, 只要函数声明过即使没有实现也不会报错.
Swift: 静态语言. 其对象方法的调用基本上是在编译链接时刻就被确定的
swift基本上取消了runtime机制
runtime 其实是一个系统动态共享库, 具有一个公共接口, 该公共接口由头文件中的一组函数和数据结构组成 (纯C语言API). 由于所有的OC代码终将转换成C代码, 使得 runtime 的API调用非常频繁, 所以新版runtime里面对应的实现基本上都是用C++和汇编语言混合来写的, 以便提高系统效率.
CocoaPods 安装
CocoaPods 使用 Ruby 脚本语言编写, 故我们的Mac需要有Ruby环境. 而Mac是自带装有Ruby的, 但是一般版本都比较低, 我们需升级到更新版本.
升级Ruby我们需要用到其版本管理工具RVM, 故我们需先安装RVM. 而安装RVM过程会自动通过Homebrew安装依赖包, Homebrew是一款macOS平台下的软件包管理工具.
所以我们整个安装顺序依次为 Homebrew ~> RVM ~> Ruby.
网络
HTTP(S)
简介
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写, 是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP基于TCP/IP通信协议来传递数据 (HTML文件, 图片文件, 查询结果等), 默认端口号为80。
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。
HTTPS
HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
HTTPS 默认工作在 TCP 协议443端口.
HTTP与HTTPS区别:
HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,HTTPS 比 HTTP 要更耗费服务器资源
TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议.
TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
假设 A 为客户端,B 为服务器端。
首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
B 收到 A 的确认后,连接建立。
UDP
用户数据报协议(UDP,User Datagram Protocol) 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
UDP用户数据报协议,是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证
TCP与UDP差别
这是传输层的两个协议,先说一下传输层的两大功能:
复用:在发送端,多个应用进程公用一个传输层;
分用:在接收端,传输层会根据端口号将数据分给不同的应用进程。
传输层和网络层的区别:
网络层为不同的主机提供通信服务,传输层为不同应用进程提供通信服务。
网络层只对报文头部进行差错检测,而传输层对整个报文进行差错检测。
UDP:
无连接
不可靠(不能保证都送达)
面向报文(UDP数据传输单位是报文,不会对数据进行拆分和拼接操作,只是给上层传来的数据加个UDP头或者给下层来的数据去掉UDP头)
没有拥塞控制,始终以恒定速率发送数据
支持一对一、一对多、多对多、多对一
首部开销小,只有8字节
TCP:
有连接
可靠的
面向字节流
全双工通信,TCP两端既可以作为发送端也可以作为接收端
连接的两端只能是两个端点,即一对一,不能一对多
至少20个字节,比UDP大的多
IP协议
IP协议是网络互联协议(Internet Protocol)的简称,是TCP/IP协议簇中两个重要的协议之一,是TCP/IP协议簇的运作核心。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。
IP是整个TCP/IP协议族的核心,也是构成互联网的基础。IP位于TCP/IP模型的网络层,对上可载送传输层各种协议的信息,例如TCP、UDP等;对下可将IP信息包放到链路层,通过以太网、令牌环网络等各种技术来传送。
IP地址
IP地址 (Internet Protocol Address) 是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的对象来。
IP地址有IPv4和IPv6。
IPv4只有4段数字 (32位地址长度),每一段最大不超过255,只有大约43亿个地址。由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址很快耗尽 (卒于2019年11月26日)。为了扩大地址空间,拟通过IPv6重新定义地址空间。
IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。
随着互联网的飞速发展和互联网用户对服务水平要求的不断提高,IPv6在全球将会越来越受到重视。实际上,并不急于推广IPv6,只需在现有的IPv4基础上将32位扩展8位到40位,即可解决IPv4地址不够的问题。这样一来可用地址数就扩大了256倍
IP路由
互联网是由许多个网络连接所形成的大型网络。如果要在互联网中传送IP信息包,除了确保网络上每个设备都有一个唯一的IP地址之外,网络之间还必须有传送的机制,才能将IP信息包通过一个个的网络传送到目的地。此种传送机制称为IP路由。
各个网络通过路由器相互连接。路由器的功能是为IP信息包选择传送的路径。换言之,必须依靠沿途各路由器的通力合作,才能将IP信息包送到目的地。在IP路由的过程中,由路由器负责选择路径,IP信息包则是被传送的对象。
JSON简介
JSON 即JavaScript Object Notation(JavaScript 对象表示法)。
JSON 是存储和交换文本信息的语法,类似 XML。
JSON 比 XML 更小、更快,更易解析。
JSON 具有自我描述性,更易理解。
JSON 文件的文件类型是 .json,文本的 MIME 类型是 application/json。
socket是一种抽象层编程接口,用于应用程序间通过网络协议进行通信。其实现方式主要有两种:TCP和UDP。
iOS中,根据不同语言有不同的方法来创建socket连接:
C:CFStream。
OC:NSStream。
Swift:Stream。
OC中的NSStream和Swift中的Stream类都是对CFStream的封装,而CFStream是基于TCP协议创建的socket连接,无法创建UDP socket连接
iOS的另外一种原生创建socket方法 —— BSD Socket。伯克利套接字(Berkeley sockets,即BSD Socket)由伯克利大学研发,最终成为网络开发接口的标准
CocoaAsyncSocket是一个异步socket库,提供了macOS、iOS和tvOS版本,其底层基于BSD socket
多线程
本系列文章将讨论iOS中以下几种多线程编程方式:
编程方式 语言 描述 出场率 线程周期
Thread OC/Swift 轻量级, 简单易用 中 手动管理
GCD C 可充分利用设备的多核 高 自动管理
Operation OC/Swift 基于GCD封装, 添加了一些实用方法 高 自动管理
Pthreads C 可跨平台使用 低 手动管理
何为进程?
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程, 是操作系统进行资源分配和调度的一个独立单位, 是应用程序运行的载体. 进程是一种抽象的概念, 从来没有统一的标准定义. iOS中, 一个App就是一个进程.
为何进程可以并行运行?
CPU执行代码都是顺序执行的, 但由于系统可以让各个进程(应用程序)快速地交替执行, 使得各个进程看起来是同时运行的. 只有多核CPU才能真正实现多个进程同时运行.
为何要线程?
随着计算机的发展, 对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了. 上帝说要有线程, 于是就有了线程.
何为线程?
线程是程序执行中一个单一的顺序控制流程, 是CPU调度和分派的基本单位, 它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
线程是进程的一部分, 一个进程中至少有一个线程.
为何线程可以并行运行?
和进程一样, 也是由于CPU在任务调度间快速切换.
主要区别?
进程是操作系统进行资源分配和调度的基本单位; 线程是CPU调度和分派的基本单位.
(void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(changeValue) toTarget:self withObject:nil];
}
- (void)changeValue {
NSLog(@"%s thread:%@, value=%d", __func__, [NSThread currentThread], self.value);
self.value = 10;
// 调用主线程
[self performSelectorOnMainThread:@selector(logValue) withObject:nil waitUntilDone:NO];
}
- (void)logValue {
NSLog(@"%s thread:%@, value=%d", __func__, [NSThread currentThread], self.value);
}
-------------------------------------
Grand Central Dispatch, 强大的中央调度器
任务(Task) 和 队列(Queue)
任务就是将要在线程中执行的代码(块), GCD中为block. 执行任务有两种方式: 同步执行和异步执行
队列是用于装载线程任务的队形结构, 遵循FIFO(先进先出)原则. 队列有两种: 串行队列和并发队列. 另外有两个系统提供的特殊队列: 主队列(串行队列) 和 全局队列(并发队列)
dispatch_queue_t queue = dispatch_queue_create("我是标签", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 这里的任务(代码块)会被添加到队列queue中
});
同步(sync) 和 异步(async)
同步执行会阻塞当前代码, 不具备开启线程的能力.
但不代表同步执行就一定在当前线程执行, 例如在其他线程同步执行到主队列, 最终是在主线程执行的(因为主线程始终存在, 所以我们说没有开启新线程). 除了调用主队列, 同步执行的任务都是在当前线程完成.
// 在其他线程同步执行到主队列
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1 %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2 %@", [NSThread currentThread]);
});
});
异步执行不会阻塞当前线程, 具备开启新线程能力.
但不是说异步执行就一定会开启新线程, 例如异步执行到主队列不会开启新线程, 又例如多个异步执行到相同队列也可能不会开启相应数量的线程.
// 异步执行+全局队列(并发队列)
for (int i=0; i<8; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
-----------------------------------------------------------------------------------------------------
log:
0 <NSThread: 0x600000b76680>{number = 5, name = (null)}
2 <NSThread: 0x600000b75700>{number = 6, name = (null)}
1 <NSThread: 0x600000b75a40>{number = 4, name = (null)}
3 <NSThread: 0x600000b76a00>{number = 3, name = (null)}
4 <NSThread: 0x600000b76680>{number = 5, name = (null)}
5 <NSThread: 0x600000b75700>{number = 6, name = (null)}
6 <NSThread: 0x600000b76a00>{number = 3, name = (null)}
7 <NSThread: 0x600000b75a40>{number = 4, name = (null)}
任务被添加到并发队列的顺序是任意的, 所以最终可能以任意顺序完成, 你不会知道何时开始运行下一个任务, 或者任意时刻有多少 Block 在运行. 这些完全取决于 GCD.
GCD 会根据系统资源控制并行的数量, 所以如果任务很多, 它并不会让所有任务同时执行. 也就是说, 开不开启新线程(或者说开启多少条新线程)由GCD决定, 但会保证不会阻塞当前线程.
主队列(Main Queue) 和 全局队列(Global Queue)
并行队列 DISPATCH_QUEUE_CONCURRENT 串行队列 DISPATCH_QUEUE_SERIAL。
异步执行dispatch_async 同步执行dispatch_sync。
Operation基于GCD封装, 完全面向对象. 对应于GCD, Operation也有任务和队列的概念, 只不过在Operation中任务(block)被称为操作(operation).
Operation是一个抽象类, 代表一个任务. 通常我们使用它的子类NSInvocationOperation或NSBlockOperation来编写任务代码. 当然也可以直接使用Operation, 不过需要重写main方法, 在main里面编写任务代码.
3种创建方式:
NSInvocationOperation (swift不支持): 此类调用选择器方法(selector), 在方法里面编写任务代码.
NSBlockOperation (swift对应BlockOperation): 此类采用block方式, 在block中编写任务代码.
NSOperation (swift对应Operation): 需要重写main方法, 在main里面编写任务代码.
2种执行方式:
不添加到队列, 手动调用operation的start方法.
添加到队列, 系统自动调用start方法
在GCD中, 同步和异步分别对应dispatch_sync和dispatch_async方法.
在Operation中, 没有这种方法
如果不使用队列, operation默认以同步方式执行. 但我们有办法使之异步执行: 新建一个新线程, 然后在新线程里面调用start方法.
如果使用队列, 系统默认以异步方式执行. 但我们可以使用waitUntilAllOperationsAreFinished (operation queue方法)进行等待, 以确保操作完成后才继续往下执行.
队列 (Operation Queue)有两种: 主队列和非主队列 (自定义队列).
主队列通过mainQueue获得, 主队列里的任务都是放到主线程执行 (不包括使用addExecutionBlock:添加的额外操作, 因其可能在其他线程执行).
非主队列 (自定义队列) 即一般 alloc init 出来的队列, 默认在子线程中异步执行. 通多设置最大并发数(maxConcurrentOperationCount)来控制队列是串行还是并发.
添加操作(任务)到队列有四种方式:
addOperation:
添加一个现有的Operation (或者其子类).
addOperations:waitUntilFinished:
可添加多个现有的Operation (或者其子类), 可设置等待所有操作完成后方可继续往下执行.
addOperationWithBlock:
直接添加一个block
addBarrierBlock:
添加栅栏, 顺带一个任务. 等栅栏前的所有任务都执行完, 再执行本栅栏的任务, 起到隔离同步等待的目的.
动态类型
类似于Smalltalk,Objective-C具备动态类型:即消息可以发送给任何对象实体,无论该对象实体的公开接口中有没有对应的方法。对比于C++这种静态类型的语言,编译器会挡下对(void*)指针调用方法的行为。但在Objective-C中,你可以对id发送任何消息(id很像void*,但是被严格限制只能使用在对象上),编译器仅会发出"该对象可能无法回应消息"的警告,程序可以通过编译,而实际发生的事则取决于运行期该对象的真正形态,若该对象的确可以回应消息,则依旧运行对应的方法。
一个对象收到消息之后,他有三种处理消息的可能手段,第一是回应该消息并运行方法,若无法回应,则可以转发消息给其他对象,若以上两者均无,就要处理无法回应而抛出的例外。只要进行三者之其一,该消息就算完成任务而被丢弃。若对"nil"(空对象指针)发送消息,该消息通常会被忽略,取决于编译器选项可能会抛出例外。
虽然Objective-C具备动态类型的能力,但编译期的静态类型检查依旧可以应用到变量上。以下三种声明在运行时效力是完全相同的,但是三种声明提供了一个比一个更明显的类型信息,附加的类型信息让编译器在编译时可以检查变量类型,并对类型不符的变量提出警告。
下面三个方法,差异仅在于参数的形态:
- setMyValue:(id) foo;
id形态表示参数"foo"可以是任何类的实例。
- setMyValue:(id <aProtocol>) foo;
id表示"foo"可以是任何类的实例,但必须采纳"aProtocol"协议。
- setMyValue:(NSNumber*) foo;
该声明表示"foo"必须是"NSNumber"的实例。
动态类型是一种强大的特性。在缺少泛型的静态类型语言(如Java 5以前的版本)中实现容器类时,程序员需要写一种针对通用类型对象的容器类,然后在通用类型和实际类型中不停的强制类型转换。无论如何,类型转换会破坏静态类型,例如写入一个"整数"而将其读取为"字符串"会产生运行时错误。这样的问题被泛型解决,但容器类需要其内容对象的类型一致,而对于动态类型语言则完全没有这方面的问题。
转发
Objective-C允许对一个对象发送消息,不管它是否能够响应之。除了响应或丢弃消息以外,对象也可以将消息转发到可以响应该消息的对象。转发可以用于简化特定的设计模式,例如观测器模式或代理模式。
Objective-C运行时在Object中定义了一对方法:
转发方法:
- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
响应方法:
- (retval_t) performv:(SEL) sel :(arglist_t) args; // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
希望实现转发的对象只需用新的方法覆盖以上方法来定义其转发行为。无需重写响应方法performv::,由于该方法只是单纯的对响应对象发送消息并传递参数。其中,SEL类型是Objective-C中消息的类型。
以下代码演示了转发的基本概念:
Forwarder.h 文件代码:
#import <objc/Object.h>
@interface Forwarder : Object
{
id recipient; //该对象是我们希望转发到的对象。
}
@property (assign, nonatomic) id recipient;
@end
Forwarder.m 文件代码:
#import "Forwarder.h"
@implementation Forwarder
@synthesize recipient;
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
/*
*检查转发对象是否响应该消息。
*若转发对象不响应该消息,则不会转发,而产生一个错误。
*/
if([recipient respondsTo:sel])
return [recipient performv: sel : args];
else
return [self error:"Recipient does not respond"];
}
Recipient.h 文件代码:
#import <objc/Object.h>
// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end
Recipient.m 文件代码:
#import "Recipient.h"
@implementation Recipient
- (id) hello
{
printf("Recipient says hello!\n");
return self;
}
@end
main.m 文件代码:
#import "Forwarder.h"
#import "Recipient.h"
int main(void)
{
Forwarder *forwarder = [Forwarder new];
Recipient *recipient = [Recipient new];
forwarder.recipient = recipient; //Set the recipient.
/*
*转发者不响应hello消息!该消息将被转发到转发对象。
*(若转发对象响应该消息)
*/
[forwarder hello];
return 0;
}
利用GCC编译时,编译器报告:
$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc
main.m: In function `main':
main.m:12: warning: `Forwarder' does not respond to `hello'
$
如前文所提到的,编译器报告Forwarder类不响应hello消息。在这种情况下,由于实现了转发,可以忽略这个警告。 运行该程序产生如下输出:
$ ./a.out
Recipient says hello!
类别 (Category)
在Objective-C的设计中,一个主要的考虑即为大型代码框架的维护。结构化编程的经验显示,改进代码的一种主要方法即为将其分解为更小的片段。Objective-C借用并扩展了Smalltalk实现中的"分类"概念,用以帮助达到分解代码的目的。
一个分类可以将方法的实现分解进一系列分离的文件。程序员可以将一组相关的方法放进一个分类,使程序更具可读性。举例来讲,可以在字符串类中增加一个名为"拼写检查"的分类,并将拼写检查的相关代码放进这个分类中。
进一步的,分类中的方法是在运行时被加入类中的,这一特性允许程序员向现存的类中增加方法,而无需持有原有的代码,或是重新编译原有的类。例如若系统提供的字符串类的实现中不包含拼写检查的功能,可以增加这样的功能而无需更改原有的字符串类的代码。
在运行时,分类中的方法与类原有的方法并无区别,其代码可以访问包括私有类成员变量在内的所有成员变量。
若分类声明了与类中原有方法同名的函数,则分类中的方法会被调用。因此分类不仅可以增加类的方法,也可以代替原有的方法。这个特性可以用于修正原有代码中的错误,更可以从根本上改变程序中原有类的行为。若两个分类中的方法同名,则被调用的方法是不可预测的。
其它语言也尝试了通过不同方法增加这一语言特性。TOM在这方面走的更远,不仅允许增加方法,更允许增加成员变量。也有其它语言使用面向声明的解决方案,其中最值得注意的是Self语言。
C#与Visual Basic.NET语言以扩展函数的与不完全类的方式实现了类似的功能。Ruby与一些动态语言则以"monkey patch"的名字称呼这种技术。
使用分类的例子
这个例子创建了Integer类,其本身只定义了integer属性,然后增加了两个分类Arithmetic与Display以扩展类的功能。虽然分类可以访问类的私有成员,但通常利用属性的访问方法来访问是一种更好的做法,可以使得分类与原有类更加独立。这是分类的一种典型应用—另外的应用是利用分类来替换原有类中的方法,虽然用分类而不是继承来替换方法不被认为是一种好的做法。
Integer.h 文件代码:
#import <objc/Object.h>
@interface Integer : Object
{
@private
int integer;
}
@property (assign, nonatomic) integer;
@end
Integer.m 文件代码:
#import "Integer.h"
@implementation Integer
@synthesize integer;
@end
Arithmetic.h 文件代码:
#import "Integer.h"
@interface Integer(Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end
Arithmetic.m 文件代码:
#import "Arithmetic.h"
@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
self.integer = self.integer + addend.integer;
return self;
}
- (id) sub: (Integer *) subtrahend
{
self.integer = self.integer - subtrahend.integer;
return self;
}
@end
Display.h 文件代码:
#import "Integer.h"
@interface Integer(Display)
- (id) showstars;
- (id) showint;
@end
Display.m 文件代码:
#import "Display.h"
@implementation Integer(Display)
- (id) showstars
{
int i, x = self.integer;
for(i=0; i < x; i++)
printf("*");
printf("\n");
return self;
}
- (id) showint
{
printf("%d\n", self.integer);
return self;
}
@end
main.m 文件代码:
#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"
int
main(void)
{
Integer *num1 = [Integer new], *num2 = [Integer new];
int x;
printf("Enter an integer: ");
scanf("%d", &x);
num1.integer = x;
[num1 showstars];
printf("Enter an integer: ");
scanf("%d", &x);
num2.integer = x;
[num2 showstars];
[num1 add:num2];
[num1 showint];
return 0;
}
利用以下命令来编译:
gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc
在编译时间,可以利用省略#import "Arithmetic.h" 与[num1 add:num2]命令,以及Arithmetic.m文件来实验。程序仍然可以运行,这表明了允许动态的、按需的加载分类;若不需要某一分类提供的功能,可以简单的不编译之。
计算机基础
/*
1.计算机组成
计算机概论和数据表示
硬件 软件 二进制(高电平 低电平)
十进制转p进制 整数部分:除P倒取余 小数部分 乘p正取整
p到10进制 各位数码和其权值相乘的和
ASICC Unicode编码 utf-8编码用1-4个字节解决冗余的问题
存储器和指令系统
主存储器(RAM ROM) 辅助存储器(硬盘)
主存储器的存储单元为一个字节
指针即地址
一个对象占有一片连续的存储单元 第一个字节的地址即为对象的指针
cache解决CPU和内存访问速度不匹配的问题
将大于内存空间的程序运行 使用虚拟存储器技术(系统把当前程序的部分保留在内存 其余放在磁盘上)
CPU 总线 I/O
CPU 包含运算器和控制器
io 输入输出系统
CPU 通过接口和外部设备连接
2.操作系统
管理软件硬件资源
操作系统提供文件存取服务 内存分配服务 处理机调度服务 设备分配和处理服务
特征 并发 共享(被并行的多个进程访问)
进程 是程序在内存中的一次执行过程 是系统资源分配和保护的基本单位 是动态的 程序是静态的 进程切换开销大
线程 是系统调度和分派的基本单位 是进程的一个执行路径
内存管理
程序运行过程 编译成多个目标模块 链接 装入内存
系统为程序分配的内存可能是一片连续或不连续的地址空间
文件管理 io管理
一个硬盘、光盘都称为一卷
3.数据结构
是研究非计算的程序中操作对象的关系和操作的学科
逻辑结构分类 集合 线性结构(线性表 、栈、数组、队、串) 树型结构 图状结构
存储结构 顺序存储(逻辑上相连存储也相连,删除插入速度慢,取值简单)链式存储(逻辑相邻存储器中不相连,用指针指向下一个位置)索引存储 散列存储
栈是只能再一端插入和删除的线性表 后进先出
队列是只能一端进入另一端删除 先进先出
数组是相同数据类型的有限序列 存储在连续的存储单元中
线性表
4.计算机网络
传输层 TCP(连接的端点是套接字 即IP地址+端口 ) UDP(传输前不需要连接 对方收到报文后不需要给出确认)
TCP 三次握手 客户端发送请求到服务端首部里有序列号等信息为第一次握手 不含数据 服务端收到后发送回应给客户端 告诉其已收到请求 为第二次握手
客户端收到服务端信息后
网络层 IP ipv4 ipv6做了拓展 128个比特位
应用层 域名系统DNS
http 超文本传输协议 它本身是面向无连接的 无状态的
URL 协议://主机:端口/路径
HTTPS 是ssl和http构成的 更安全
websocket 基于TCP的双全工的通信
*/
/*
git log
git reset --hard 06aa3443ce77135aad4d69216655586236d39a60
git branch
git checkout -n new
git checkout new
git add .
git commit -m "打包前配置"
git pull
git push
*/
垃圾收集
Objective-C 2.0提供了一个可选的垃圾收集器。在向后兼容模式中,Objective-C运行时会将引用计数操作,例如"retain"与"release"变为无操作。当垃圾收集启用时,所有的对象都是收集器的工作对象。普通的C指针可以以"__strong"修饰,标记指针指向的对象仍在使用中。被标记为"__weak"的指针不被计入收集器的计数中,并在对象被回收时改写为"nil"。iOS上的Objective-C 2.0实现中不包含垃圾收集器。垃圾收集器运行在一个低优先级的后台线程中,并可以在用户动作时暂停,从而保持良好的用户体验。
参考 oc教程