iOS学习笔记 - iOS程序设计2

目录

nib管理

Cocoa类

Coaco事件


参考书:ios程序设计 Matt Neuburg


nib管理

nib文件是包含一整块界面绘制的文件,源于文件拓展名.nib(NeXTStep Interface Builder)。现经常使用的是拓展名为.xib格式的文件开发界面,当应用编译的时候,对象的.xib文件会转换(编译)成.nib格式,但是.xib文件仍然称为nib文件。

创建程序有两种方式:1、编写代码;2、绘制界面。两种方式最终结果相同。绘制界面是写代码的一种方式。应用运行的同时加载在nib文件中的绘制界面会转化成指令,用来实例化和初始化nib文件里的对象。同样可以使用代码来实例化和初始化这些对象。

顶层对象

停靠栏显示nib文件的顶层对象。把nib文件想象成被包含的对象,其中的一些对象(即那些表示视图的对象)排列在一个层次结构的容器中,其中没有被其他对象所包含的就是顶层对象。一个视图可以包含其他视图(即成为他的子视图),也可以被其他视图包含(即他的父视图)。

停靠栏
xcode的停靠栏(dock)

一个nib文件可以包含两种不同类型的顶层对象:

  • 占位符(代理对象):表示nib加载是已经存在于应用代码中的对象。代理对象在nib文件中主要是为了让在应用代码中的对象和在nib中实例化的对象之间能够通信。无法创建或者删除一个代理对象,停靠栏对自动填充它们。代理对象显示在停靠栏中的分割线之上(Placeholders)。
  • nib对象:一个通过nib实例化的对象,即它所代表的对象会在代码运行并且nib加载时创建,可以创建新的nib对象。顶层nib对象显示在停靠栏的分割线以下(View)。

nib加载和文件所有者

nib文件只有在应用运行并且加载nib时在有用。如果nib由Info.plist的健"Main nib file base name"指定(NSMainNibFile),应用运行时会自动加载。一般情况下,应用运行时,nib会在需要的时候显示加载。则没有必要加载一个用户可能不会访问的nib文件直到用户明确要求显示它,可使内存使用保持在最低限度。加载nib文件需要时间,所以在启动时加载更少的nib能启动得更快。

在nib加载时,把某个已经存在的实例指定为他的所有者。nib不能在没有所有者的情况下加载,同时所有者必须在nib可以加载前已经存在。nib的所有者可以是任何类的实例。

当nib加载时,他的所有nib中的对象都实例化,即它的顶层nib对象和分层依赖它们的所有深层nib对象。(相同的nib可以多次加载,每次都生成一组全新的实例,参考UITableViewCell)。

插座变量链接

在代码中引用当nib文件加载时从nib对象生成的实例,需要预先在同一个nib文件中的代理对象中设置一个插座变量(outlet)链接。链接时nib文件中一个对象(链接源)的到同一个nib中另一个对象(链接对象)的单向链接。插座变量是一个链接,其名称对应于源对象的实例变量。当nib加载时,目标对象实例化,实例变量的值设置成目标对象。因而源对象接管目标对象的引用,作为其实例变量的一个值。

用链接可以链接nib文件中的任何两个对象,但是代理对象作为链接的源时特殊的,因为它代表一个在nib加载前就已经存在的对象。因而一个从代理对象中的插座变量会导致一个在nib加载前就已经存在的对象最终变成一个直到nib加载后才存在的对象引用--一个实际上是由加载nib文件而实例化的对象。

实例变量和插座变量之间的匹配不是因为相同的名字,而是依赖键值编码。

@implementation MyClass{
    IBOutlet UILabell * theLabel;
}
@end

IBOutlet是没有意义的,是宏定义成一个空字符串,所有它会在编译器看到它之前删除。

链接到代码是假象:在代码中的实例变量和nib中的对象之间没有链接,代码中的实例变量和nib中的对象之间没有同一性。如果一个插座变量正常工作,那么肯定存在两个截然不同的独立东西,即类中的实例变量个nib中的插座变量,有相同的名字,并且来自该类的一个实例。是名字的同一性让两者在运行时nib加载过程中得以匹配。

动作链接

动作(action)是一个Cocoa UIControl接口对象(控件)在用户做了某些动作时自动发出的消息。不同的用户行为会控制对象发出动作消息,称为事件。

动作消息,是在代码中享用用户对戒面中控件操作的方式。除非事先明确和控件约定,否则代码不会接收到任何控件消息。必须告诉控件什么事件触发一个动作消息,动作消息发给哪个实例(target),还有动作消息的名字应该是什么(selector)。有两种方式建立这种约定,即在代码中和在nib中。

对基于nib的实例进行额外初始化

在nib完成加载的时候,它的实例已经构造完成,这些实例已经初始化和配置了通过属性和尺寸检查器(Xcode右侧)指定的所有属性,它的插座变量都已经对应实例变量的值。此时可能需要在初始化过程中加上自定义代码,最常见时通过实现awakeFromNib。awakeFromNib消息会发送给所有刚刚从加载的nib实例化的nib实例,此时对象已经初始化并且配置,其链接都可以使用。

在Mac OS X中,很少或几乎不从awakeFromNibdiaoyongsuper(实际上会抛出异常);在iOS中必须始终调用awakeFroNib中的super。在Mac OS X中nib所有者的awakeFromNib在nib加载时调用,所以这样可能会想一个对象发送多次的awakeFromNib消息;在iOS中,awakeFromNib只会在该对象自己从nib实例化时发送一次,所以至多发送给对象一侧。

Cocoa类

(简略,知识补充)

类别

类别(category):Objective-C的一项语言功能,用它能为已有的类定义额外的方法。于子类不同,在类别中的不能定义新的实例变量。类别支持重写现有方法,但一般来说不应该利用这个能力。类别的定义需要基于某个类进行,定义类别的方法和接口类的定义很相似:需要接口部分以及实现部分,一般会将它们的分别放在独立的.h和.m文件对中。在接口部分和实现部分开头的类名后面使用圆括号指定类别的名字, .h文件需要包含类别所基于的类的头文件(或者定义该类的框架头文件),而.m文件则按照惯例包含对应的头文件。

// [StringCategories.h]
#import <Foundation/Foundation.h>

@interface NSString (MyStringCategories)
- (NSString*) basePictureName;
@end


// [StringCategories.m]
#import "StringCategories.h"

@implementation NSString (MyStringCategories)
- (NSString*) basePictureName{
    return [self stringByAppendingString: @"IO"];
}
@end

类别尤其适合用在NSString这样的类上,因为文档警告说不建议派生NSString。NSString实际上是相关的一组类(称为类簇class cluster)中的一个,这意味着所谓的NSString对象实际上可能是其他类的实例。另外,在类别里也可以定义类方法,可以将工具方法加入到任何合适的类中,从而避免调用它们是产生实例化的开销。由于类对象本身是全局可见的,因此这些方法也就成为全局方法类。

使用类别声明私有方法:创建一个类别,并将它的接口部分放到这个类的实现文件中。

协议

协议是方法声明的列表,没有与之对应的实现。实现协议中声明的方法世纪有遵循它的类负责。协议方法可以是必要的或者可选的(@optional)。

可选方法

非正式协议(informal protocol)不是协议,知识一种想编译器提供方法签名来避免发送消息是编译器报错的方法。有两种方法实现非正式协议:

  • 在NSObject上定义类别,这允许任何对象都能够接受类别中列出的消息。(定义具名的类别接口部分,同时不定义具有相应名字的类别实现部分。)
  • 虽定义协议但是不正式遵循它,只向id类型的对象发送协议中列出的任何消息以此来避免编译器报错。(定义协议,并将其方法显示指定为可选的。)

一些Foundation类

(1)有用的结构体和常量:

NSRange结构体表示范围,由两个整数(NSUInteger)组成,即location和length。

NSNotFound是一个整数常量,表示要求的元素没有找到,不关心NSNotFound实际数值,在检查索引有没有意义是总是直接于NSNotFound本身进行比较。如果查找会返回一个范围为,而要查找的东西又不存在,则返回的NSRange中的location部分将是NSNotFound。

(2)NSString与正则

        //============
        //提取title中的首位两个数值

        //method1
        NSString * title = @"12 by 34";
        NSScanner * scanner = [NSScanner scannerWithString:title];
        int rows, cols;// 存储两个值
        [scanner scanInt:&rows];//获取第一个值
        [scanner scanUpToCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:nil];
        [scanner scanInt:&cols];//获取第二个值
        NSLog(@"rows: %d, cols: %d", rows, cols);

        //method2
        int rowcol[2];// 数组存储两个值
        int* prowcol = rowcol;// 指针遍历数组
        NSError * error = nil;
        NSRegularExpression * r = [NSRegularExpression regularExpressionWithPattern:@"\\d+" options:0 error:&error];// when @"\\d", rowcol[0]=1 and rowcol[1]
        for(NSTextCheckingResult * match in [r matchesInString:title options:0 range:NSMakeRange(0, [title length])]){// [r matchesInString:title options:0 range:NSMakeRange(0, [title length])] return all matches
            *prowcol++ = [[title substringWithRange:[match range]] intValue];
        }
        for(int i = 0; i < 2; i++){
            NSLog(@"index %d: %d", i, rowcol[i]);
        }

(2)NSDate

// NSDate
        NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
        if([[NSLocale availableLocaleIdentifiers] indexOfObject:@"en_US"] != NSNotFound){
            NSLocale * loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
            [dateFormatter setLocale:loc];
        }
        [dateFormatter setDateFormat:@"d MMMM yyyy 'at' h:mm a z"];
        NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

(3)NSNumber:包含数字(包括BOOL)的对象,能在需要对象是用它来保存并传递数字。本身不是数字,因而不能将它用于计算或者需要实际数字的地方,使用intVaue可以提取int(double等以此类推)。

(4)NSArray与filter

        //filter,获取数组中以‘m’或‘M’开头的字符串
        NSArray * array = @[@"Manny", @"Meo", @"mmm", @"Jack", @"Dude", @"Kate"];
        NSPredicate * predicate = [NSPredicate predicateWithFormat:@"self BEGINSWITH[cd] 'm'"];//不区分大小写
        NSArray * arr1 = [array filteredArrayUsingPredicate:predicate];
        NSLog(@"arr1: %@", arr1);(Manny, Meo,mmm)
        
        NSArray * arr2 = [array objectsAtIndexes:[array indexesOfObjectsPassingTest:^BOOL(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            return ([(NSString *)obj rangeOfString:@"m" options:NSCaseInsensitiveSearch].location == 0);
        }]];
        NSLog(@"arr2: %@", arr2);//(Manny, Meo,mmm)

(5)NSNull:只负责提供指向单利对象的指针([NSNull null]),可以在需要对象但不允许使用nil阿德情况下代替nil。

(6)可变和不可变

[NSArray array]生成内容不可变数组,而[NSMutableArray arrat]生成内容可变数组。要检查一个实例是可变还是不可变,不要向它发送class消息。这些不可变类和可变类在内部都实现为类簇,这意味着Cocoa可能会在内部使用另一个秘密的类,它不同于文档中的描述的你使用的类。

    if([NSStringFromClass([n class]) isEqualToString: @"NSCFArray"]) // wrong
    

    if([n respondsToSelector: @selector(addObject:)]) // right

Coaco事件

产生事件的原因

划分为4类(非官方、分类界限不一定确切):

  • 用户事件:用户交互操作会直接触发一个事件。
  • 生命周期事件:由于通知应用程序到达了其生命周期的某一个阶段。
  • 功能事件:CoCoa马上就要做一些工作,但它现在把控制权交给你以方便你提供额外的功能(eg:UIView的drawRect:)。
  • 查询事件:Cocoa向你询问一件事,它之后的行为由你的回答决定(eg:表格视图显示数据需要表格中某一行的单元格数据)。

通知

Cocoa提供NSNotificationCenter的单利,可不正式称为通知中心(notificaiton center),是一种消息发送机制的基础,这个机制称为通知(notification)。通知包括一个NSNotication实例(对象)。他的基本思想(参考例子)就是任何对象【receiver】都可以向通知中心注册要接受某种通知【name: @"HeyDude"】,另一个对象【通知中心或其他对象】能够给通知中心提交要发送的通知对象【noti】(这个过程称为发送通知),通知中心随后会将通知对象【noti】以通知的形式发送给所有已经注册要求接受到这种通知的对象【receiver】

//  main.m

#import <Foundation/Foundation.h>
#import "Receiver.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Receiver * receiver = [[Receiver alloc] init];// init...
        [receiver myMethod];
        // This is myMethod
        // Hey! Dude~ This is <Receiver: 0x102800be0>
        [receiver stop]; // dealloc...
        
    }
    return 0;
}


//
//  Receiver.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Receiver : NSObject

- (void)myMethod;

- (void)stop;

@end

NS_ASSUME_NONNULL_END


//
//  Receiver.m


#import "Receiver.h"

@implementation Receiver{
    __weak id observer;//弱引用暂存观察者令牌,以便后续注销
}

- (id)init{
    self = [super init];
    if(self){
        NSLog(@"init...");
        // add notificaton to NSNotificationCenter
        __weak Receiver * wself = self;//**防止保留循环1
        self->observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"HeyDude" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {//注册通知
            Receiver * sself = wself;//**防止保留循环2
            if(sself){//**防止保留循环3
                NSLog(@"Hey! Dude~ This is %@", sself);
            }
            }];
    }
    return self;
}

- (void)myMethod{
    NSLog(@"This is myMethod");
    // post notification to notification center, then execute the block
    NSNotification * noti = [[NSNotification alloc] initWithName:@"HeyDude" object:nil userInfo:nil];
    [[NSNotificationCenter defaultCenter] postNotification:noti];//发送通知
}

- (void)stop{
    [[NSNotificationCenter defaultCenter] removeObserver:self->observer];//注销通知
//    self->observer = nil;
}

- (void)dealloc{
    NSLog(@"dealloc...");
    NSLog(@"%@", self->observer);
}

@end


通知机制允许对象直接发送消息而不必知道或关心要接受它的具体是什么对象或者有多少个对象。通知减轻了开发者在应用程序架构中确保让实例见关联起来以便收发信息的负担。当对象在概念上相差甚远时,通知是较为轻量的消息传递方法。但是要注意由于消息发送者不关心消息接受对象,导致可能存在消息接受对象(观察者)已经消失而导致错误。在观察者消失之前应手动注销其所有注册过的通知。大多数情况下,最简单的解决方法是在注册为通知接受者的实例的dealloc方法中注销,即使找到另一个更好的地方来注销,也应该在dealloc中做一次注销。

定时器NSTimer

行为类似通知,会在指定时间间隔后发出信号(定时器触发)。一个有效地正在计时的定时器称为已启动的(scheduled)。定时器可能会触发一次,也可能重复(repeating)触发。值触发一次的定时器在触发后自动失效,重复触发的触发器需要发送invalidata使其失效。NSTimer可能导致内存泄漏,参考https://www.cnblogs.com/Ohero/p/4828623.html

委托

委托(delegation)是一种定义对象之间关系的面向对象设计模式。如果一个对象的行为由另一个对象定义或协助完成,那后者就是前者的委托(delegate)。使用委托不必派生现有的类,前者实际上不知道后者属于什么类。

数据源

数据源(data source)就像是委托,但它用于向对象提供要显示的数据。Cocoa中只有UITableView和UIPickerView者两个类有数据源。

响应者链

响应者(responder)是能够直接接收UIEvent的对象。应用程序中的响应者属于一个响应者链,这个链本质上是通过视图的层次关系连接起来的。UIView可以放在另一个UIView(其父视图)内,以此类推最终会到达应用程序的UIWindow(这是一个不具有父视图的UIView)。

推迟责任:一个响应者接收到它无法处理的触摸事件,这个事件可以沿着响应者链一次传递给上一级,直到找到能够处理它的响应者。有两种方式会产生这种情况:(1)响应者没有实现相应的方法(2)响应者将相关方法实现调用为super。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值