11,22,23,24,25,28暂时不需要看
- Class Prefix在Xcode 7.2创建项目里面已经没有了,或许是因为默认创建的项目类太少,但是自己在创建类的时候还是加上前缀为好,因为OC没有命名空间。
storyboard是多个xib文件集合的描述文件,也采用xml格式。那么storyboard与xib比较,区别在于:
Interface Builder一些介绍:
ViewController对象创建完毕后会收到消息:initWithNibName:bundle:
,所以可以在这里进行一些数据创建和初始化。
initWithNibName
中初始化数据,新版本Xcode创建的默认项目,使用initWithNibName
不会有效果,可以使用initWithCoder
。关于代码自动补全功能:
Party *partyInstance = [[Party alloc] init];
,将多个消息合并在一行的写法叫做嵌套消息发送(nested message send)。for in
中添加或删除对象,否则抛出异常。%@
对应的实参类型是指向任何一种对象的指针,通过覆盖description
方法定义格式化输出。_itemName
为例,存方法名为setItemName:
。itemName
,其它语言大多为:getItemName
。#import
和#include
的差别在于,#import
可以确保不会重复导入同一个文件。item.itemName
编译后和发消息一样,也是调用之前写好的存取方法,所以存取方法名要按照规范书写。Apple官方代码坚持使用点语法存取实例变量。使用存取方法访问实例变量是良好的编程习惯,即使是访问对象自身的实例变量,也应该使用存取方法,但是在初始化方法中例外。
初始化方法
init
开头,Objective-C中命名约定很重要,应该严格遵守。instancetype
,该关键字表示方法的返回类型和调用方法的对象类型相同。instancetype
引入之前,初始化方法返回类型是id
,表示指向任意对象的指针,类似C语言的void*
。instancetype
和id
instancetype
只能用来表示方法返回类型,id
可以用来表示变量和方法参数类型。id
的定义是指向任意对象的指针,所以不需要再加*
,比如:id item
。
- 书中提到的指定初始化方法(designated initializer)不明白,根据书中后面的"其它初始化方法与初始化方法链的理解",指定初始化方法不同在于可以供其它初始化方法调用,但不调用其它初始化方法,通常参数最多。
self
和super
self
为类或对象自身,存在于方法中,是一个隐式(implicit)局部变量,相当于c++的this
。super
为父类,向super
发消息,其实就是向self发消息,但是要求系统再查找方法时跳过当前对象的类,从父类开始查询。初始化方法总结的若干规则:
类方法的声明和实例方法的声明差别在于第一个字符,实例方法为-
,类方法为+
。
stringWithFormat:
),就可以将该类方法称为便捷方法(convenience method)。NSObject
的对象指针,不能保存基本C类型变量。nil
加入数组,可以用NSNull
代替,例如:[items addObject:[NSNull null]];
。NSString *foo = items[0];
或发消息NSString *foo = [items objectAtIndex:0];
,这两种方法效果是相同的。NSMutableArray
中,可以使用下标语法向数组中添加和修改对象,等价于insertObject:atIndex:
和replaceObjectAtIndex:withObject:
发消息。对书中的疑惑:
- 书中意思是说所有类对象都是运行时绑定,但是感觉并不是所有OC对象都是运行时(runtime)绑定,除了书中展示的使用
id
来接收创建的对象时,这时候书中所说的isa
可能就不起作用了?
@import Fundation
告诉编译器需要使用Foundation框架,之后编译器会优化预编译头文件和缓存编译结果过程,文件中不再明确引用框架,编译器会根据@import自动导入相应框架。#import
。新版本的Xcode已经不会自动创建预编译头文件,因为预编译头文件会带来代码复用困难等问题。
- Why isn't ProjectName-Prefix.pch created automatically in Xcode 6?
栈和堆区别是:栈按后进先出规则保存一组帧,堆则包含了大量无序的活动对象,需要通过指针来访问。
发送alloc消息是从堆上分配内存。
图中两个BNRItem对象中包含两个指向BNRItem对象的指针_container和_containerItem互相指向彼此。
当items设置为nil后,没有两个BNRItem对象引用计数减1,但是互相依然强引用,所以内存无法释放,如下图:
__weak BNRItem *_container;
,这样就解决了强引用循环问题,如下图:_name
和一对存取方法,右边只需要一个属性就完成了。如果声明一个名为itemName的
属性,编译器会自动生成实例变量_itemName
、取方法itemName
和存方法setItemName:
。
自定义属性存取方法:
@property (nonatomic, readwrite, strong) NSString *itemName;
nonatomic
和atomic
,通常设为nonatomic
。readwrite
和readonly
,如果时readonly
只会生成取方法。strong
, weak
, copy
, unsafe_unretained
.int
,默认会选用unsafe_unretained
,不做内存管理。copy
特性。因为可变子类有可能被其它引用者修改,所以会复制一个新的对象给属性。viewDidLoad
中添加如下代码:- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. CGRect firstFrame = CGRectMake(160, 240, 100, 150); BNRHypnosisView *firstView = [[BNRHypnosisView alloc] initWithFrame:firstFrame]; firstView.backgroundColor = [UIColor redColor]; [self.view addSubview:firstView]; CGRect secondFrame = CGRectMake(20, 30, 50, 50); BNRHypnosisView *secondView = [[BNRHypnosisView alloc] initWithFrame:secondFrame]; secondView.backgroundColor = [UIColor blueColor]; [firstView addSubview:secondView]; }
Xcode使用lldb调试:
- 与调试器共舞 - LLDB 的华尔兹
- XCODE LLDB TUTORIAL
- lldb不支持点语法,输出视图的
bounds
应输入:p (CGRect)[firstView bounds]
- lldb error: property not found on object of type
frame
和父视图的bounds
结合计算出来的。
drawRect相关:
UIBezierPath相关:
- (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0; UIBezierPath *path = [[UIBezierPath alloc] init]; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)]; [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:2.0 * M_PI clockwise:YES]; } path.lineWidth = 10; [[UIColor lightGrayColor] setStroke]; [path stroke]; }
#import "BNRHypnosisView.h" @interface BNRHypnosisView() @property (strong, nonatomic) UIColor *circleColor; @end @implementation BNRHypnosisView @end
[self setNeedsDisplay];
在xcode 6以上版本创建Empty Application
项目:
Auto Layout相关
创建空项目后,跑起来是iphone4s的屏幕大小。。。???
- 原因:Xcode 6 默认新建的启动页面为 LaunchScreen.xib,系统通过检测是否有这个文件,来判断 app 是否支持 iphone 6 & 6 plus. 如果要支持 iOS 7,还必须添加 Launch Image assets.
- 解决方法:添加Default.png、[email protected]、[email protected]三张图片进来,可以加到Supporting Files里。
- 参考:Xcode 6 新建工程运行在 iOS 7 上下有黑边的问题, iOS Xcode运行时上下黑边的解决办法
outlet
要声明为弱引用:
- 按照书中代码输出UIDatePicker时间,会出现选择时间和输出看起来不一致问题:
- 将输出的
date
改为[date descriptionWithLocale:[NSLocale systemLocale]]
。http://stackoverflow.com/questions/24971367/set-uidatepicker-timezone-to-simulaters-current-timezone
// This will get a pointer to an object that represents the app bundle NSBundle *appBundle = [NSBundle mainBundle]; // Look in the appBundle for the file BNRReminderViewController.xib BNRReminderViewController *rvc = [[BNRReminderViewController alloc] initWithNibName:@"BNRReminderViewController" bundle:appBundle];
BNRReminderViewController *rvc = [[BNRReminderViewController alloc] init];
- 本地通知是在ios4.0之后添加的,但是在ios8之后,在设置通知之前,需要先对通知进行注册,注册需要的通知类型,否则收不到响应类型的通知消息。
- Ask for User Permission to Receive UILocalNotifications in iOS 8
- IOS – 本地通知
viewDidLoad
:
viewDidLoad
中实现。viewWillAppear
:
viewDidLoad
和viewWillAppear
:
viewDidLoad
;如果用户每次看到视图控制器的view时都需要对其进行设置,则选择viewWillAppear
。UIWindow
有一个firstResponder
当用户点击文本框时,UIWindow
将firstResponder
指向该对象,文本框就会成为第一响应者。UITextField
成为第一响应者屏幕就回弹出键盘,一旦第一响应者不是UITextField
键盘就回消失。UITextField
发送becomeFirstResponder
使其成为第一响应者;向对象发送resignFirstResponder
使其失去第一响应者状态。UITextField
对象设置委托,UITextField
对象会在发生事件时向委托发送相应消息,由委托处理事件。在委托中通畅应该将对象自身作为第一个参数,可以根据该参数判断发送该消息的对象。多个对象可能具有相同的委托,一个视图控制器可能有多个UITextField
。
Many of the message it sends to its delegates are informative: “OK, I am done editing!”. Here are some of those:
- (void)textFieldDidEndEditing:(UITextField *)textField; - (void)textFieldDidBeginEditing:(UITextField *)textField;
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField; - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField; - (BOOL)textFieldShouldClear:(UITextField *)textField; - (BOOL)textFieldShouldReturn:(UITextField *)textField;
@interface BNRHypnosisViewController() <UITextFieldDelegate, OtherDelegate> @end
delegate
属性都是弱引用,这是为了避免对象及其委托之间产生强引用循环,这样就会造成内存泄漏。main.m
中的UIApplicationMain
函数创建。// // BNRItemStore.h // Homepwner #import@interface BNRItemStore : NSObject + (instancetype)sharedStore; @end
// // BNRItemStore.m // Homepwner #import "BNRItemStore.h" @implementation BNRItemStore + (instancetype)sharedStore { static BNRItemStore *sharedStore = nil; // Do I need to create a sharedStore? if (!sharedStore) { sharedStore = [[self alloc] initPrivate]; } return sharedStore; } // If a programmer calls [[BNRItemStore alloc] init], let him // know the error of his ways - (instancetype)init { @throw [NSException exceptionWithName:@"Singleton" reason:@"Use +[BNRItemStore sharedStore]" userInfo:nil]; return nil; } // Here is the real (secret) initializer - (instancetype)initPrivate { self = [super init]; return self; } @end
NSUUID *uuid = [[NSUUID alloc] init]; NSString *key = [uuid UUIDString];
// UITextField的委托方法 - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; }
// 顶层视图应该为UIControl才能响应触摸事件 - (IBAction)backgroundTapped:(id)sender { [self.view endEditing:YES]; }
NSCoding
协议,并且实现两个必须的方法:encodeWithCoder:
and initWithCoder:
.@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; - (instancetype)initWithCoder:(NSCoder *)aDecoder; @end
encodeWithCoder:
方法将所有属性编码到(NSCoder *)aCoder
参数中,要固化的属性必须也遵守NSCoding
属性(基础类型除外),无论编码哪种类型的数据,必须有相应的键。initWithCoder:
方法还原之前通过encodeWithCoder:
编码的所有对象,实例代码如下:- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { _itemName = [aDecoder decodeObjectForKey:@"itemName"]; _serialNumber = [aDecoder decodeObjectForKey:@"serialNumber"]; _dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"]; _itemKey = [aDecoder decodeObjectForKey:@"itemKey"]; _valueInDollars = [aDecoder decodeIntForKey:@"valueInDollars"]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.itemName forKey:@"itemName"]; [aCoder encodeObject:self.serialNumber forKey:@"serialNumber"]; [aCoder encodeObject:self.dateCreated forKey:@"dateCreated"]; [aCoder encodeObject:self.itemKey forKey:@"itemKey"]; [aCoder encodeInt:self.valueInDollars forKey:@"valueInDollars"]; }
UIView
遵守NSCoding
协议)。- (NSString *)itemArchivePath { // Make sure that the first argument is NSDocumentDirectory // and not NSDocumentationDirectory NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); // Get the one document directory from that list NSString *documentDirectory = [documentDirectories firstObject]; return [documentDirectory stringByAppendingPathComponent:@"items.archive"]; }
NSSearchPathForDirectoriesInDomains
函数返回值是包含NSString
的NSArray
对象,这是因为对OS X可能有多个目录匹配某组指定查询条件,但iOS上只会有一个匹配目录。上面代码会获取数组第一个也是唯一一个NSString
对象,然后在该字符串后面追加固化文件的文件名。NSString *search = @"Play some \"Abba\""; NSString *escaped = [search stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // escaped is now "Play%20some%20%22Abba%22" // If you need to un-escape a percent-escaped string, NSString has the method: // - (NSString *)stringByRemovingPercentEncoding;
NSJSONSerialization
可以处理JSon数据,例如:NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
关于
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
错误:
- 在iOS 9中,苹果将原http协议改成了https协议,使用 TLS1.2 SSL加密请求数据。使用xcode 7为iOS 9开发应用 如果直接访问"http:// ... "会出现App Transport Security。
- 参见:iOS 9使用HTTP(App Transport Security问题)
NSURLSessionDataTask
在后台线程,当Web服务请求成功后,BNRCoursesViewController
需要调用reloadData
方法重新加载UITableView
对象数据。为了让reloadData
方法在主线程中运行,可以使用dispatch_async
函数,实例代码如下:- (void)fetchFeed { NSString *requestString = @"http://bookapi.bignerdranch.com/courses.json"; NSURL *url = [NSURL URLWithString:requestString]; NSURLRequest *req = [NSURLRequest requestWithURL:url]; NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; self.courses = jsonObject[@"courses"]; dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); }]; [dataTask resume]; }
UIWebView
对象可以显示指定网页内容,这样就可以在应用内直接打开网页。NSURLSession
委托会收到- URLSession:task:didReceiveChallenge:completionHandler:
消息,可以在该消息中发送用户名和密码,完成认证。- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler { NSURLCredential *cred = [NSURLCredential credentialWithUser:@"BigNerdRanch" password:@"AchieveNerdvana" persistence:NSURLCredentialPersistenceForSession]; completionHandler(NSURLSessionAuthChallengeUseCredential, cred); }
NSURLSessionTask
会使用HTTP协议来和Web服务进行通信,发送和接收的数据必须符合HTTP规范,NSURLSessionTask
提供多种方法,来设置HTTP请求的各方面。NSURL *someURL = [NSURL URLWithString:@"http://www.photos.com/upload"]; UIImage *image = [self profilePicture]; NSData *data = UIImagePNGRepresentation(image); NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:someURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:90]; // This adds the HTTP body data and automatically sets the Content-Length header req.HTTPBody = data; // This changes the HTTP Method in the request-line req.HTTPMethod = @"POST"; // If you wanted to set the Content-Length programmatically... [req setValue:[NSString stringWithFormat:@"%d", data.length] forHTTPHeaderField:@"Content-Length"];
NSUserDefaults
类访问。NSUserDefaults
实例代码:NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *greeting = [defaults objectForKey:@"FavoriteGreeting"]; // If the user expresses a preference, you can set the value for that key: NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:@"Hello" forKey:@"FavoriteGreeting"];
NSUserDefaults
会自动将对象存储到对应的plist文件中,支持:NSArray
, NSDictionary
, NSString
, NSData
,NSNumber
。如果存储plist文件不支持文件,可以先归档为NSData类型。Objective-C运行环境会在创建某个类第一个对象之前调用该类的initialize
方法。
声明全局变量,注册默认设置的实例代码:
// At launch time, the first thing that will happen is the registering of the factory settings. // It is considered good style to declare your preference keys as global constants. // Open BNRAppDelegate.h and declare two constant global variables: #importextern NSString * const BNRNextItemValuePrefsKey; extern NSString * const BNRNextItemNamePrefsKey; @interface BNRAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end // In BNRAppDelegate.m, define those global variables and use them to register the factory defaults in +initialize: #import "BNRAppDelegate.h" #import "BNRItemsViewController.h" #import "BNRItemStore.h" NSString * const BNRNextItemValuePrefsKey = @"NextItemValue"; NSString * const BNRNextItemNamePrefsKey = @"NextItemName"; @implementation BNRAppDelegate + (void)initialize { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSDictionary *factorySettings = @{BNRNextItemValuePrefsKey: @75, BNRNextItemNamePrefsKey: @"Coffee Cup"}; [defaults registerDefaults:factorySettings]; } @end
// Set the label's initial alpha messageLabel.alpha = 0.0; [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{ messageLabel.alpha = 1.0; } completion:NULL]; [UIView animateKeyframesWithDuration:1.0 delay:0.0 options:0 animations:^{ [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.8 animations:^{ messageLabel.center = self.view.center; }]; [UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{ int x = arc4random() % width; int y = arc4random() % height; messageLabel.center = CGPointMake(x, y); }]; } completion:^(BOOL finished) { NSLog(@"Animation finished"); }];