Objective-C学习笔记

1.引言

   Objective-C是对C的扩展,主要基于面向对象编程,用来开发IOS程序。

2.环境配置

   下载最新版的Xcode,首次下载后要先开启一次Xcode进行初始化。

2.1创建控制台项目

   打开Xcode,点击创建新Project。

   这里我们选择macOS的Command Line Tool

   工程名采用大驼峰进行命名,并填好基本信息,选择Objective-C。

   此时Xcode会预创建main.m文件,并填入Hello World程序。点击command+R便可以进行编译运行。

2.2创建APP项目

   创建APP项目的时候,在选择创建的项目选择ios下的Single View App。

   创建过程中,如果希望能够在真机上进行运行调试,我们需要在Team中填入AppleID账号。

   接下来我们将SceneDelegate.h、SceneDelegate.m、Main.storyboard删除,并在项目配置中(info)找到下面一行删除。

   此外还需要在项目信息中(General),删除预设的Main Interface。

   在AppDelegate.m中,需要改成如下代码,多余部分删除:

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
     return  YES;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    ViewController *vc = [[ViewController alloc] init];
    self.window.rootViewController = vc;
    [self.window makeKeyAndVisible];
     return  YES;
}
@end

3.Objective-C中的OOP

3.1类的定义与实现

   在Objective-C中,采用“@interface”和“@end”定义类结构。常见类定义如下:

@interface ClassName: FatherClass //类名定义采用大驼峰,Objective-C仅支持单继承

{
     //成员变量定义
    Type variable1; //变量名采用小驼峰
    Type variable2;
    ...
}
//不同方法之间空一行
//声明方法时,在-/+与返回类型之间留一个空格
//参数之间不要留空格 星号*之前必须有一个空格
//-代表成员方法,需要通过实例化的对象进行访问
- (Return Type)method1:(Parameter Type)parameter1 addParameter2:(Parameter Type)parameter2

//+代表类方法,可以使用类名进行访问,类似于C/C++中的static
+ (Return Type)method2:(Parameter Type)parameter1 addParameter2:(Parameter Type)parameter2 
...
@end

   定义完类的基本结构后,我们需要在.m文件中定义其类内方法的实现方式,我们按照以下结构来进行实现:

@implementation ClassName
- (Return Type)method1:(Parameter Type)parameter1 addParameter2:(Parameter Type)parameter2
{
    //函数具体实现
    ...
    return returnParamter;
}

+ (Return Type)method2:(Parameter Type)parameter1 addParameter2:(Parameter Type)parameter2 
{
    //函数具体实现
    ...
    return returnParamter;
}
...
@end

   在类中,我们还会定义构造函数和析构函数,主要采用以下的形式定义和调用:

@interface ClassName: FatherClass
{
    //成员变量定义
    ...
}
- (id)init; //不带参数的构造函数

- (id)initWith...:(Parameter Type)parameter //带参数的构造函数

- (void)dealloc; //析构函数,如果有继承,需要调用[super dealloc]
...
@end

@implementation ClassName
...
@end

int  main(int argc, char *argv[]){
    ClassName *myclass1 = [[ClassName alloc] init]; //调用不带参数的构造函数
    ClassName *myclass2 = [[ClassName alloc] initWith...:parameter]; //调用带参数的构造函数
    ...
    [myclass1 release];
    [myclass2 release]; //release方法使得引用计数减1,当引用计数减至0时调用dealloc方法
}

3.2继承

   为了提高代码的复用性,且在需要修改代码时,能够更快的定位到需要修改的代码,我们会采用继承概念来实现类的创建和实现,将多个类之间相同可复用的成员或方法放置在父类中,而在子类中实现其特定的功能,也可以透过重写等操作,修改自定义子类的实现手段。
   需要注意的是,在Objective-C中不支持多继承,如果想要使用类似多继承的特性,可以使用协议或分类来实现。
   在继承中,有两个会涉及到的关键字:self和super。self的作用类似于C/C++中的this指针,用来返回当前对象的指针,在语法中,有两种访问成员的方法,分别是:“self.”和“self->”,其中“self.”是调用get和set方法,“self->”是直接访问成员变量。一般在子类中访问方法时(隐藏传递了参数self),会优先查找自己是否具有该方法,如果没有会向上搜索超类是否有该方法。如果想要访问超类定义的方法,可以利用super关键字来实现优先查找超类的方法。
   在存储有继承链的对象时,采用基地址+偏移量的方式存储,超类会存储在子类前面(结构如下)。通常我们最初始的类都继承自NSObject,NSObject的实例对象是isa,用来保存指向该对象的指针。


3.3复合

   复合是指一个类中包含各种其他的类,比如“车”包含“轮子”和“发电机”等其他类,也可以说是多个类组合形成一个新的类。
   由于在一个类中,经常会复合其他不同的类,我们需要对这些成员设置set、get方法,以便在使用“self.”调用的时候能够找到对应的方法,具体的set函数和get函数的定义方式如下:

- (void)setParameter:(Parameter Type)parameter; //set函数必须set开头 

- (Parameter Type)Parameter; //get函数不要用get前缀

3.4继承和复合的比较:

   继承代表的是“is a”的关系,而复合则是代表“has a”的关系。

4.Foundation Kit快速教程

4.1结构体

(1)NSRange:

   可以透过字段赋值、聚合结构赋值或是“NSMakeRange(location, length)”函数进行赋值。

typedef  struct _NSRange {
    unsigned  int location;
    unsigned  int length;
}NSRange;

(2)NSPoint:

   NSPoint用来表示笛卡尔平面中的一个点。

typedef  struct _NSPoint {`
    float x;
    float y;
}NSPoint;

(3)NSSize:

typedef  struct _NSSize {
    float width;
    float height;
}NSSize;

(4)NSRect:

typedef  struct _NSRect {
    NSPoint origin;
    NSSize size;
}NSPoint;

4.2字符串

(1)NSString:

   创建字符串可以直接赋值也可以透过类方法创建,其中一个按照格式的类方法如下:

+ (id)stringWithFormat:(NSString *)format, ...; //函数后面的...代表可以接受多个以逗号隔开的其他参数
//使用示例
NSString *string = [NSString stringWithFormat:@"Hello World %d!", 1];
//output: Hello World 1!

   在NSString中有成员length记录字符串的大小,可以透过其get函数来取得NSString的长度。

//length的get函数
- (unsigned  int)length;
//使用示例
unsigned  int length = [string length];
unsigned  int length = string.length;

   字符串的比较采用类内定义的方法来比较两个字符串,有两种方法:

- (BOOL)isEqualToString:(NSString *)aString;
//使用示例
NSString *s1 = @"String1";
NSString *s2 = [NSString stringWithFormat:@"String%d", 1];
[s1 isEqualToString:s2];
//output YES
- (NSComparisonResult)compare:(NSString *)string; //这个方法返回是个枚举类型,可以判断字符串比较的大小(升序或降序)
//NSComparisonResult类型定义
typedef  enum _NSComparisonResult {
    NSOderedAscending = -1,
    NSOrderSame,
    NSOrderDescending
}NSComparisonResult;
- (NSComparisonResult)compare:(NSString *)string optiond:(unsigned)mask; //这个方法可以提供掩码选项来进行有条件的比较
//mask可以透过位或操作来添加复合的选项,常用的mask有
//NSCaseInsensitiveSearch 不区分大小写
//NSCLiteralSearch 区分大小写
//NSNumericSearch 比较字符串的字符个数

   字符串匹配分为前缀匹配、后缀匹配和中间匹配,透过以下三个函数实现:

- (BOOL)hasPrefix:(NSString *)aString; //前缀匹配
- (BOOL)hasSuffix:(NSString *)aString; //后缀匹配
- (NSRange)rangeOfString:(NSString *)aString; //中间匹配

(2)NSMutableString:

   这个类是NSString的子类,与NSString不同的是,NSString一旦被创建后,就不可以被修改,而NSMutableString则可以修改,透过以下类方法创建一个新的NSMutableString:

+ (id)stringWithCapacity:(unsigned)capacity; //透过指定字符串长度来创建NSMutableString,这里的大小不是严格的限制,而是建议值

   添加字符可以透过以下几个方法添加:

- (void)appendString:(NSString *)aString;
- (void)appendFormat:(NSString *)format, ...;
- (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc;

   删除指定范围的字符串透过类内定义的方法删除;

- (void)deleteCharactersInRange:(NSRange)range;

4.3集合类

(1)NSArray:

   NSArray是一个Cocoa类,用来储存有序列表(C/C++中的数组)。NSArray里面只能储存Objective-C的对象,不能储存C/C++中的基本数据类型或NSArray的随机指针(也不能存储nil),我们可以主要透过以下方法进行创建:

+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ...; //后面的Object列表以nil结尾

   在NSArray类中,常用的方法如下:

- (unsigned)count; //获得NSArray中对象个数
- (id)objectAtIndex:(unsigned  int)index; //获得特定索引的对象

(2)NSMutableArray:

   与NSString类似的是,NSArray一旦创建后,里面的对象不能修改,所以也同样的产生了一个可变数组—NSMutableArray。利用和NSMutableString一样的方式创建可变数组,透过给定容量来生成新的可变数组。
   可以利用以下函数对NSMutableArray进行函数添加和删除操作:

- (void)addObject:(id)object; //在末尾添加object
- (void)removeObjectAtIndex:(unsigned)index; //删除指定位置的object

(3)NSEnumerator:

   NSEnumerator是枚举器,可以用来访问数组的元素,透过数组的objectEnumerator方法来访问object,可以访问直到遇到nil。需要注意的是,当使用枚举器访问可变数组时,不能通过增加或删除方法对数组容器进行操作。

- (NSEnumerator *)objectenumerator; //NSArray类的方法,获得顺序枚举器
- (NSEnumerator *)reverseObjectenumerator; //NSArray类的方法,获得逆序枚举器
- (id)nextObject; //NSEnumerator类方法,获得数组容器的object

(4)NSDictionary:

   这是一个字典容器,可以透过给定关键字来访问对应的信息。

//创建字典,先传object再传对应的key
- (id)dictionaryWithObjectsAndKeys:(id)firstObject, ...;
//访问object
- (id)objectForKey:(id)key;

(5)NSMutableDictionary:

   与字符串和数组容器类似的,NSDictionary是不可变字典,一样也有可变字典NSMutableDictionary,透过指定容器大小创建,并根据类内方法添加键值对应对象。

//透过容量大小创建可变字典
- (id)dictionaryWithCapacity:(unsigned  int)capacity;
//添加新的键值
- (void)setObject:(id)object forKey:(id)key;
//删除键值
- (void)removeObjectForKey:(id)key;

   对于上面给出的数据结构,可以使用但不要进行继承。

4.4NS中的数值类

(1)NSNumber:

   由于前面提到的集合类,无法存储基本数据类型,因此Cocoa提供了NSNumber来封装这些数据结构,其类内方法定义如下:

//创建NSNumber类
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
//取得原来的数据类型
- (char)charValue;
- (int)intValue;
- (float)floatValue;
- (BOOL)boolValue;
- (NSString *)stringValue;

(2)NSValue:

   NSValue是NSNumber的超类,NSValue可以包装任意类型,同样也能放进NS容器中。

//创建NSValue,value参数通常传入的是想要存储变量的地址,type参数可以利用编译器指令@encode(className)
- (NSValue *)valueWithBytes:(const  void *)value objCType:(const  char *)type;
//取得存储的变量数值,value参数传入的是变量地址
- (void)getValue:(void *)value
//Cocoa提供的转换方法
+ (NSValue *)valueWithPoint:(NSPoint)point;
+ (NSValue *)valueWithSize:(NSSize)size;
+ (NSValue *)valueWithRect:(NSRect)rect;
- (NSPoint)pointValue;
- (NSSize)sizeValue;
- (NSRect)rectValue;

(3)NSNull:

   这个类用来表示空,由于每次创建的NSNull返回的是一样的数值,可以使用==来进行判断。

+ (NSNull *)null;

5.协议

   协议是OC里比较重要的一个概念,透过协议我们可以实现多继承、代理等操作,在iOS的UI编程中,MVC模式下,用户在View中的操作可以透过协议代理的方式通知ViewController,下面是协议的基本定义方法:

@protocol ProtocolName <NSObject>
@required
// Method define
@optional
// Method define
@end

   从上面的定义来看,协议中的方法有两种属性:required和optional,在遵守协议的类中,必须实现required属性的方法(虽然不遵守只会报出警告),而optional属性的方法则可以看需求实现。

6.内存管理

   一个对象有其生命周期,在C/C++中学习过,如果一个堆上的变量,没有被手动释放的话,会有内存泄漏的风险。在Objective-C中,以前需要程序员手动管理内存(MRC),现在经过Apple的更新,已经采用自动管理内存(ARC),不需要程序员自己管理。

  • 引用计数

   MRC中的核心,除了new/delete、alloc/dealloc之外,引用计数也是一个重要的观念。Objective-C中,在创建一个对象的时候,Objective-C会自动将该对象的引用计数设置为1;当一个对象的引用计数为0的时候,会自动调用该类的dealloc方法,我们可以重写dealloc方法来实现自定义的操作。
   为了让程序员能够控制引用计数。Objective-C中提供了以下几个方法来操作引用计数:

- (id)retain; // 引用计数+1
- (void)release; // 引用计数-1
- (unsigned)retainCount; // 获得该对象的引用计数

   当我们在写一个成员的set方法时,需要考虑到对象的销毁时机,以下面的例子举例:

// 假设Car类里面有一个Engine类的成员,考虑以下的set方法
- (void)setEngine:(Engine *)engine
{
    _engine = engine;
}

int  main()
{
    Car *car = [Car new];
    Engine *engine = [Engine new]; // engine引用计数=1
    [car setEngine:engine]; // engine引用计数=1
    [engine release]; // engine引用计数=0,触发dealloc
    ...
    Engine *newEngine = car.engine; // car.engine的引用计数已经归零,被销毁了,访问不到该变量
}
  • 所有权

   为了避免上面的情况,这里引入一个所有权的概念,所有权代表的是:谁持有一个对象,由谁来管理该对象的引用计数,在持有的时候retain,在不使用的时候release。当所有权的概念被遵守的时候,我们可以保证:如果谁还需要使用一个对象,该对象的引用计数一定不为0,如果没有任何地方要使用该对象时,该对象能够被销毁(引用计数变为0)。
   引入所有权的概念后,我们需要对set函数进行修改:

// 假设Car类里面有一个Engine类的成员,考虑以下的set方法
- (void)setEngine:(Engine *)engine
{
    [engine retain];
    [_engine release]; // 先retain再release可以避免当_engine == engine且该对象引用计数=1时被意外销毁
    _engine = engine;
}

int  main()
{
    Car *car = [Car new];
    Engine *engine = [Engine new]; // engine引用计数=1
    [car setEngine:engine]; // engine引用计数=2
    [engine release]; // engine引用计数=1
    ...
    Engine *newEngine = car.engine; // car.engine=engine引用计数=1
    [newEngine retain]; // newEngine引用计数=2
}

// 我们在Car类的dealloc方法也需要将engine的引用次数-1
- (void)dealloc
{
    [_engine release];
    [super dealloc];
}
  • Autorelease

   autorelease透过自动释放池来实现,autorelease的作用是,预先设定在未来某个时刻(autorelease销毁时)向一个对象发送release消息,我们可以透过创建一个autorelease pool并将想要自动释放的对象加入pool中,在pool销毁时,系统会自动向pool中的所有对象发送一次release消息。

// 创建autorelease pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
...
// 将想要自动释放的对象加入pool中
[obj autorelease]; // 此时obj引用计数保持不变,加入自动释放池
...
// 自动释放池销毁
[pool release]; // 此时向池中所有对象发送release消息,如果该对象引用计数变为0,则自动销毁该对象。

7.类别

   当我们在开发过程中,时常会需要在现有的类中添加一些新方法,一般来说我们可以透过直接修改类定义和实现或继承的方式处理,但对于三方SDK或系统类,无法直接修改类定义和实现,又觉得继承子类比较麻烦时,我们可以为该类新增一个类别,只要我们保证类别名唯一,并在使用该方法是引入包含了类别定义的头文件,就可以正常使用新方法。

  • 类别的定义和实现

   类别的定义、实现和一般的类定义类似,只是在类的后面添加"(类别名)"。

// 类别定义,假设为NSString添加一个新的类别
@interface NSString (newCategory)
- (Return Type)newMethod:(Parameter Type)parameter...;
...
@end

// 类别实现
@implementation NSString (newCategory)
- (Return Type)newMethod:(Parameter Type)parameter...
{
    ...
}
...
@end
  • 类别的缺陷

   1. 无法在类别中添加实例变量,一般都是透过方法来实现功能的判断。

   2. 如果类别中的方法和原有的方法重名,系统默认类别有较高的优先级,会直接覆盖掉原有方法。

8.block

   block是一个数据类型,用来表示一段代码,是Objective-C对于闭包的实现,在创建时透过以下的方式创建:

// 按照格式创建
returnType(^blockName)(parameterTypes) = ^(parameters) {
        statements
};
// 利用Objective-C中的自动类型创建
__auto_type blockName = ^(parameters) {
        statements
};
// 调用block
blockName(parameters);

   block具有以下几个特点:
      1. 可以嵌套定义,定义方式和函数定义很像。
      2. 可以定义在方法内部或外部。
      3. 只有调用block的时候才会执行内部代码。
      4. 本质是对象。
   在block里面,我们还需要了解block如何去捕捉外部的变量。在C/C++/OC中主要有几种变量:
      1. 全局变量
      2. 静态全局变量
      3. 静态变量
      4. 局部变量(自动变量)
   我们透过以下代码来查看block对四种变量的捕获:

#import <Foundation/Foundation.h>
int global_i = 1;
static  int static_global_j = 2;
int  main(int argc, const  char * argv[]) 
{
    static  int static_k = 3;
    int val = 4;
    void (^myBlock)(void) = ^{
        global_i ++;
        static_global_j ++;
        static_k ++;
        NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    };
    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    myBlock();
    return  0;
}
// 输出结果
// Block外 global_i = 2,static_global_j = 3,static_k = 4,val = 5
// Block中 global_i = 3,static_global_j = 4,static_k = 5,val = 4

   在上面四种变量之中,对于全局变量、静态全局变量,由于其作用域较广,在block中进行修改后,离开block,其值依然可以保存下去;对于静态变量来说,block里面捕获的是其引用,因此在内部可以对值进行修改;局部变量block捕获是数值,类似于C语言函数中的值传递,因此在block定义的时候已经将局部变量的值捕获进去了,即便外面对局部变量进行修改,也无法影响block内部的数值。
   在Objective-C中,为了防止程序员在block中修改局部变量的值导致结果不符合预期,在编译层面就会报错,提示程序员修改变量类型或传递指针/引用。
   如果我们希望在block中修改局部变量的值呢?可以透过__block关键字来实现对局部变量的捕获即修改。

#import <Foundation/Foundation.h>

int  main(int argc, const  char * argv[]) 
{
    __block int i = 0;
    void (^myBlock)(void) = ^{
        i ++;
        NSLog(@"%d",i);
    };
    myBlock();
    return  0;
}
// 输出:
// 1

   __block关键字的原理是将局部变量进行封装,封装成一个结构体,这个结构体中有指向自己的指针__forwarding,可以透过这个指针来实现对局部变量的访问和修改。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335