空空叶博客 学习与开发博客

IOS开发旅程

2018-09-10
kongkongye
ios

包含一些自我的想法,与熟悉的java语言的比较,项目的进化旅程等.

使用什么语言开发IOS应用?

Objective-CSwift,考虑到Objc比较成熟稳定,Swift则相对比较新, 因此在语言上打算先学Objc,等熟悉了再尝试Swift(这也是网上大部分人的推荐).

但对于比较新的,更简洁优雅的,从Objc的经验上催生的Swift,的确应该抱着谦虚的态度去学习.

使用什么IDE(集成开发环境)?

苹果官方出的XCode,这个没什么好说的.

Git支持?

目前XCode新建项目时自带了初始化Git的选项,并且XCode已经集成了Git功能,比如修改的文件会显示M,新增的文件会显示A. 这说明Git是可行并且是官方推荐的. 对于常用简单的git操作,XCode自带的已经可以了,当然复杂的git操作可以用命令行或其它git工具进行.

只是.gitignore文件仍然需要自己建立,至于内容,则直接参考gitignore上的Objective-C.gitignore.

Objc是什么?怎么学?

首先看字面上的意思 – 面向对象的C,即它是C语言的扩展,在其上添加了面向对象(OOP)特性. 事实上,Objc是C语言的严格超集.

至于添加的面向对象特性,可以简单的概括为3点:

  1. 封装: 就是把数据方法打包在一起,对外隐藏具体实现.
  2. 继承: 利用现有类的功能,无需重新编写相同的代码,对现有功能进行扩展.
  3. 多态: 主要包括覆盖重载,覆盖就是子类重新定义父类的方法,重载就是允许存在多个名字相同的函数,但参数不同.

但要注意的是,在Objc里并不存在重载,因为它只认函数名,不认参数类型.

包与命名空间?

不像java有,c++有命名空间,Objc里没有这种机制…(听说这个点是争论比较大的)

但替代的,Objc里通常以前缀来区分.比如官方核心的NS前缀,像NSObject,NSArray等.

前缀冲突怎么办?这个没什么办法,官方建议两个字母作为前缀的类名是为官方的库与框架准备的;第三方库应该使用三个或更多字母来作为前缀

除了类名,方法名也应该加前缀(这个甚至更严重?!),否则…一旦冲突,自己cry吧.

objc有哪些数据类型?

objc是C语言的超集,那么先说说C语言有哪些数据类型:

  1. 基本数据类型
    • 整数
    • char
    • short
    • int
    • long
    • … * 浮点数
    • float
    • double
    • … * bool
  2. 枚举
  3. void: 没有值,通常用于以下情形:
  4. 函数返回为空
  5. 函数参数为空
  6. 指针指向void(类型为void *)
  7. 派生类型
    • 指针pointer
    • 数组
    • 结构struct
    • 共用体union
    • 函数function (指函数返回值类型)

数组与结构类型统称为聚合类型.

然后objc附加了以下的数据类型:

  1. 派生类型
    • NSValue: 这是个可以和各种基本数据类型互转的类
    • NSNumber: 继承自NSValue,数字的包装类
    • NSString: 这是一个类,基本上代替了char与string的使用
    • NSArray: 数组
  2. id类型: 表示任意类型(只表示对象类型,不表示基本数据类型),类型于js语言的var

objc类型封装? objc在c的基础上,对类型作了一些封装(其实就是别名),如intNSInteger,floatCGFloat等. 那么使用原始的类型还是封装的类型更好呢? 推荐使用封装的类型,这样可以屏蔽底层实现细节.

NSIntegerNSNumber的区别? NSInteger是数据类型,而NSNumber是一个类. 比如NSArray里只能放类,因此3这样的基础数据类型是无法放入的,而包装成@3类后即可放入.

ps: 数据类型这块还是比较乱,后面再整理

消息

Objc使用消息(Message)来调用方法,消息格式为: [对象或者类名字 方法名字:参数序列];,其中:

  • 整个消息格式称为消息表达式(Message expression)
  • 对象或类名字称作接收器(Receiver)
  • 方法名字:参数序列称作消息(Message)
  • 方法名字称作选择器(Selector)或关键字(Keyword)

编译器会检查方法是否有效,如果无效会产生一个警告,但并不会阻止程序启动, 因为objc的动态性,它只有在运行期才会去查找并触发消息,这种机制带来了很大的灵活性.

self & super

self类似java里的this,super类似java里的super.

self与super都是id的指针,这两个比较特殊,可用在实例方法或类方法内, 相应的代表实例或类.

假设方法参数的名字与类实例变量名字重复,那么可以使用self->变量名来获取到类实例变量.

变量与方法的私有性

在objc里,变量有修饰符protected(默认),public,private:

  • protected表示在本类与子类内可见
  • public时在其它类内也可见
  • private时只在本类内可见

但是,在objc里,不推荐变量使用public修饰符,即不推荐直接访问其它类的变量(那怎么访问?用方法呗).

但是objc里方法没有这种修饰符,一般我们利用objc的语言特性来实现私有化:

  • 将方法从头文件中删除,只在实现文件中出现
  • 使用Category

类与选择器

类似java里的反射吧,动态地获得指定的类,与动态地调用类的方法.

动态获取指定的类通过Class类型来实现,可以用以下代码来获取Class:

//方式一
Class class = [类或对象 class];
//方式二
Class class = [类或对象 superclass];
//方式三
Class class = NSClassFromString(@"className");

获取到Class后可以当作普通的class来用,如:

Class class = NSClassFromString(@"className");
id obj = [[class alloc]init];

动态调用类的方法通过选择器来实现,可以用以下代码来获取选择器:

//方式一
SEL sel = @selector(方法名);
//方式二
SEL sel = NSSelectorFromString(@"方法名");

可以看到,选择器只与方法的名字与参数序列有关,跟类无关,只要方法名与参数相同,那么就是相同的选择器.

当然,也可以获取选择器的方法名,如以下代码:

NSString *name = NSStringFromSelector(sel);

执行选择器:

//没有参数
[obj performSelector:sel];
//一个参数
[obj performSelector:sel withObject:param1];
//两个参数
[obj performSelector:sel withObject:param1 withObject:param2];
//更多参数
没有默认方法了,要靠其它方式实现,方式比较多也比较复杂,后面再详细研究.

以下代码可以先判断一下指定对象是否有指定的选择器,有的话再执行:

//obj是对象实例,sel是选择器
if ([obj respondsToSelector:sel]) {
    [obj performSelector:sel];
}

函数指针

首先看一下C语言的函数指针:

//方式一
void (*methodName)(int); //常用

void (*methodName)(int x);
//方式二
void methodName(int);

void methodName(int x); //常用

然后是使用:

//声明func1
void func1(int x) {

}

//声明func2
void (*func2)(int);

//赋值(注意!下面两种方式是等效的)
//  方式一
func2 = &func1; //此时func1与func2的关系类似int *与int的关系
//  方式二
func2 = func1; //此时func1与func2的关系类似int与int的关系

//函数调用
//  一般方式
func2(10);
//  指针方式
(*func2)(10);

其实,上面代码中的func1是一个函数指针常量,而func2是一个函数指针变量.

函数指针变量既然是变量,那么也能当作参数使用,如下:

//定义函数指针类型
typedef void(*FuncType)(int);

void func1(FuncType ft, int x) {
  ft(x);
}

void func2(int x) {

}

int main() {
  func1(func2, 10);
  return 0;
}

再来看objc里函数指针的使用:

void (*func)(id, SEL)

可以看到两个参数,id就代表调用方法的对象,SEL就是方法选择器.

再来个复杂点的,方法返回NSInteger,并且有两个NSInteger类型的参数:

NSInteger (*func)(id, SEL, NSInteger, NSInteger)

然后是一个实际的例子:

//定义一个方法
- (NSInteger) addWithNum:(NSInteger)num1 num2:(NSInteger)num2 {
    return num1 + num2;
}

- (void) test {
  SEL sel = @selector(addWithNum:num2:);
  IMP imp = [self methodForSelector:sel];
  //赋值(等号右边是一个强制类型转换)
  NSInteger (*func)(id, SEL, NSInteger, NSInteger) = (NSInteger (*)(id, SEL, NSInteger, NSInteger)) imp;
  //调用
  func(self, sel, 2, 3);
}

上面代码中的IMP是什么东西呢?来看看定义:

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif

可以看到,它就是函数指针类型,并且在新的版本里,默认采用typedef void (*IMP)(void /* id, SEL, ... */ );这个定义. 作用就是让你在使用时必须进行强制类型转换(来安全地使用),否则编译器就会报错. 如果不这么做的话,假设函数调用参数不匹配,那只能在运行期被发现.

新建对象

首先要分配内存,使用下列格式:

id 对象名=[类名 alloc];

然后是初始化,如果初始化时不需要参数,那么可以使用下列格式:

[对象名字 init];

如果需要参数,那么可以调相当的方法,按照objc的约定,通常这些方法以initWith开头

此外,为了代码的简便,可以使用new来代替alloc与无参数的init,即id 对象名=[[类名 alloc]init];id 对象名=[类名 new];是等同的.


上一篇 IOS开发旅程2

下一篇 IOS内存管理

目录