如何做好App
来源:互联网 发布:手机淘宝 我的尺码 编辑:程序博客网 时间:2024/06/04 21:15
由于我们家app需要大量跳转到外部,所以想写一个工具类来解决这个问题:
首先,我们需要的是
1.打电话,弹出一个提示框,是否要拨打电话。
2.打开外部浏览器。
3.打开到系统设置的更新页面
4.打开到系统设置的wifi选择页面
5.打开到系统设置的声音页面
代码写好了 原地址:https://github.com/sunqichao/sqcjump ,接口部分
typedef enum : NSInteger{
//打开设置中的图片设置
SQCJumpTophotos,
//打开设置中的语音助手
SQCJumpToSiri,
//打开设置中的软件更新
SQCJumpToSoftWareUpdate,
//打开设置中的wifi设置页
SQCJumpToWiFi,
//打开设置中的声音设置页
SQCJumpToSounds
}SQCJumpType;
@interface SQCJumpOut : NSObject
+ (BOOL)jumpToOSDetailSet:(SQCJumpType)type;
/**
* 打电话 两种方式,一种是弹出一个alert提示是否要打电话,一种是直接打电话
*
* @param phone 电话号码
* @param isAppear 是否弹出alert
*
* @return
*/
+ (BOOL)callWithNumber:(NSString *)phone appearAlert:(BOOL)isAppear;
/**
* 打开外部浏览器
*
* @param URL 地址
*
* @return
*/
+ (BOOL)openWebSite:(NSString *)URL;
phpMyAdmin错误 缺少 mysqli 扩展。请检查 PHP 配置 的解决方案
phpMyAdmin 缺少 mysqli 扩展。请检查 PHP 配置 的解决方案:缺少 mysqli 扩展。请检查 PHP 配置。
打开你的php.ini->一般在C:WINDOWS目录下。找到
代码如下:
;extension=php_msql.dll;extension=php_mssql.dllextension=php_mysql.dllextension=php_mysqli.dll
需要开启哪个扩展,就把这一行前面的分号去掉就行,注意要重启Apache
或者IIS解决步骤:1.看看php的ext目录中是否有php_mysqli.dll文件如果有,继续下面的步骤。没有的话重新装个php2.打开php.ini,找到
代码如下:
;extension=php_mysqli.dll
把前面的分号";"去掉,改为
代码如下:
extension=php_mysqli.dll
3.找到
代码如下:
;extension_dir="./ext"
把前面的分号";"去掉,改为(相对于php的安装路径的ext目录)
代码如下:
extension_dir = "e:\php\ext"
e:php为php的存放目录
4.把php.ini拷贝到c:windows下5.把libmysql.dll复制到Windows下面的System32目录(这个很关键)6.重新启动IIS(不是网站)
cmd下运行 iisreset /RESTART
如果上面方法还不行,可参考下面方法操作
第一步,输出 phpinfo() 看看 MySQL 模块是否成功装载,如果成功装载说明你见鬼了,否则看第二步,
IIS下phpinfo()无mysql模块是何原因?原先用的php是.msi安装的,后来全部重来了一次,换成压缩包形式安装的。又照着配置了php.ini,复制到windows,复制libmysql.dll到system32。之后测试成功。。。第二步,查看
extension_dir 的设置是否正确第三步,查看是否把 libmysql.dll 文件复制到 c:windowssystem32 下
1、没有正确安装Mysql数据库,在系统服务中Mysql相关的服务没有启动 (请查看正确安装Mysql的方法)
2、在系统的 system32(C:windowssystem32) 目录下缺少
libmysql.dll文件,解决方法是找到php目录下的libmysql.dll,并将libmysql.dll复制到C:windowssystem32目录中,然后重新启动Web服务。
3、在C:windows目录下的php.ini文件中,没有将“;extension=php_mysql.dll”中的前面一个“;”去掉,所以不能使用相应功能,解决方法是打开php.ini文件
4、Mysql目录没有读取权限,正确的目录权限如下:administrator 完全控制system
完全控制user 读取加运行其他的用户权限全部删除(也可保留,但安全性不高,建议删除),然后重启MYsql服务和Web服务。
当你打开phpmyadmin时可能会报这样的一个错误,
缺少 mysqli 扩展。请检查 PHP 配置。 <a href="Documentation.html#faqmysql"
target="documentation"><img class="icon"
src="./themes/original/img/b_help.png" width="11" height="11" alt="文档"
title="文档" /></a>
你可以按照以下这几种方法进行检查:
1.检查php.ini ;extension=php_mysqli.dll是不是已经启用 也就是去掉前面的;
2.检查php.ini extension_dir 地址是不是指向了php目录的ext目录下这步是关键
我就是处在这一步的问题上 我把 extension_dir = "e:\www\php54\ext" 设置成了自己的目录 就可以了
3.检查lib_mysql.dll有没有拷贝到windows目录下 这个dll文件有多种形式的
有没加下划线的libmysql_d.dll 因此要注意自己看
4.检查php安装目录ext目录下,php_mysqli.dll文件是不是存在 网上也说明 最好使用zip包解压
不会出现文件丢失
5.查看php详细页面<?php phpinfo();
?>,看看mysql和mysqli是不是已经启动这一步就是检查你有没有开启mysqli的没有开启的话
你查找mysqli关键字是无法再php详细页面看到的
最后要注意!!!!重启Apache
原文地址:http://www.cppblog.com/colorful/archive/2012/04/29/173122.html
最近刚开始接触Linux,在虚拟机中装了个Ubuntu,当前的版本是Ubuntu 11.10,装好后自然少不了安装一些软件,在设置了软件的源后,就开始了 sudo apt-get install,结果出现了下面的Unable to locate package错误:
- E: Unable to locate package mysql-server
- E: Unable to locate package mysql-client
这叫一个郁闷啊,出师不利,不带这么吓唬刚玩Ubuntu的小朋友吧~于是赶紧找资料,又回顾下前面的操作,最后发现问题出在执行sudo apt-get install之前更换了软件源,但是却忘了update下了,于是执行下面的命令:
- sudo apt-get update
等上面命令执行完后,再执行sudo apt-get install就可以了!其实错误信息已经很明确了,Unable to locate packet就是无法找到包嘛,那还不赶紧sudo apt-get update下!
附另一篇文章:
原文地址:http://blog.chinaunix.net/uid-22002627-id-3475650.html
碰到这个问题后找到这个帖子就转了过来 当用apt-get更新软件包时常出现错误提示Unable to locate package update, 尤其是在ubuntu server上,解决方法是: 先更新apt-get #sudo apt-get update 执行完后,问题就解决了。 继续更新: #sudo apt-get upgrade 然后就可以安装apache: #sudo apt-get install apache2 等就可以了 安装mysql命令:sudo apt-get install mysql-server mysql-client
今天上传新版本遇到这个问题,
- The u option must have a non-empty value"
- "The password option must have a non-empty value"
正确的解决方案是升级XCode 到7.1,然后再上传就可以了
本文主要是说一些iOS9适配中出现的坑,如果只是要单纯的了解iOS9新特性可以看瞄神的开发者所需要知道的 iOS 9 SDK 新特性。9月17日凌晨,苹果给用户推送了iOS9正式版,随着有用户陆续升级iOS9,也就逐渐的衍生出了一系列的问题,笔者也在赶忙为自己维护的App做适配,本文写的一些坑基本都是亲身体验了。
一、NSAppTransportSecurity
iOS9让所有的HTTP默认使用了HTTPS,原来的HTTP协议传输都改成TLS1.2协议进行传输。直接造成的情况就是App发请求的时候弹出网络无法连接。解决办法就是在项目的info.plist 文件里加上如下节点:
NSAppTransportSecurity - NSAllowsArbitraryLoads
这个子节点的意思是:是否允许任性的加载?! 设为YES的话就将禁用了AppTransportSecurity转而使用用户自定义的设置,这个问题就解决了。
上面说是苹果限制了HTTP协议,但是也并不是说所有的HTTPS都能完美适配iOS9了。
举个栗子,从app内起webView加载https的网页。新建个项目写几行起网页的代码
- (void)loadView{
UIWebView *web = [[UIWebView alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.view = web;
}
- (void)viewDidLoad {
[
super
viewDidLoad];
UIWebView *web = (UIWebView *)self.view;
//董铂然
NSURL *url = [NSURL URLWithString:@
"https://github.com/"
];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[web loadRequest:request];
}
中间的url就是我们想要加载的https地址,用https://baidu.com/ 和 https://github.com/ 分别试一下,结果不同
github的网页能打开,百度的网页打不开,下面打印了一行log
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
原因是苹果的官方资料说首先必须要基于TLS 1.2版本协议。然后证书的加密的算法还需要达到SHA256或者更高位的RSA密钥或ECC密钥,如果不符合,请求将被中断并返回nil.
在浏览器中是可以直接查看这个网站的加密算法的,先点绿锁再点证书信息。
从右边两张图可以看出,github带RSA加密的SHA-256符合苹果的要求,所以才可以展示。
针对百度的情况可以在info.plist中配置如下,如果网站引用的比较多应该是需要针对每个网站进行配置。
NSAppTransportSecurity,NSExceptionDomains,NSIncludesSubdomains,NSExceptionRequiresForwardSecrecy,NSExceptionAllowInsecureHTTPLoads 写在下面便于复制。
其中的ForwardSecrecy理解为超前的密码保护算法,在官方资料里有写,一共是11种。配置完毕百度可以访问。
二、Bitcode
bitcode的理解应该是把程序编译成的一种过渡代码,然后苹果再把这个过渡代码编译成可执行的程序。bitcode也允许苹果在后期重新优化我们程序的二进制文件,有类似于App瘦身的思想。
用了xcode7的编译器编译之前没问题的项目可能会出现下列报错。
XXXX’ does not contain bitcode. You must rebuild it
with
bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode
for
this
target.
for
architecture arm64
问题的原因是:某些第三方库还不支持bitcode。要不然是等待库的开发者升级了此项功能我们更新库,要不就是把这个bitcode禁用。
禁用的方法就是找到如下配置,选为NO.(iOS中bitcode是默认YES,watchOS中bitcodes是不让改的必须YES。)
三、设置信任
这一条只和企业级应用或inhose 有关,和AppStore渠道的应用无关。
在iOS8只是弹出一个窗问你是否需要让手机信任这个应用,但是在iOS9却直接禁止,如果真的想信任需要自己去手动开启。类似于Mac系统从未知开发者处下载的dmg直接打不开,然后要到系统偏好设置的安全性与隐私手动打开。 下图展示左边iOS8,右边iOS9
用户需要去 设置---》通用---》描述文件 里面自行添加信任。
这种问题的处理方法也就两种:1.提前周知暂时不要升级iOS9 2.大多是公司员工使用的企业级应用,群发一个指导邮件。
四、字体
iOS8中,字体是Helvetica,中文的字体有点类似于“华文细黑”。只是苹果手机自带渲染,所以看上去可能比普通的华文细黑要美观。iOS9中,中文系统字体变为了专为中国设计的“苹方” 有点类似于一种word字体“幼圆”。字体有轻微的加粗效果,并且最关键的是字体间隙变大了!
所以很多原本写死了width的label可能会出现“...”的情况。
iOS8
iOS9 蛋疼
上面这两张图也可以直观的看出同一个界面,同一个label的变化。
所以为了在界面显示上不出错,就算是固定长度的文字也还是建议使用sizetofit 或者ios向上取整 ceilf() 或者提前计算
CGSize size = [title sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
五、URL scheme
URL scheme一般使用的场景是应用程序有分享或跳其他平台授权的功能,分享或授权后再跳回来。
在iOS8并没有做过多限制,但是iOS9需要将你要在外部调用的URL scheme列为白名单,才可以完成跳转
如果iOS9没做适配 会报如下错误
canOpenURL: failed
for
URL :
"mqzone://qqapp"
- error:
"This app is not allowed to query for scheme mqzone"
具体的解决方案也是要在info.plist中设置 LSApplicationQueriesSchemes 类型为数组,下面添加所有你用到的scheme
六、statusbar
这个还好只是报一个警告,如果就是不管他,也不会出现问题。
: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
以前我们为了能够实时的控制顶部statusbar的样式,可能会在喜欢使用
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]
[[UIApplication sharedApplication]setStatusBarHidden:YES];
但是这么做之前需要将 info.plist 里面加上View controller-based status bar appearance BOOL值设为NO,就是把控制器控制状态栏的权限给禁了,用UIApplication来控制。但是这种做法在iOS9不建议使用了,建议我们使用吧那个BOOL值设为YES,然后用控制器的方法来管理状态栏比如。
- (UIStatusBarStyle)preferredStatusBarStyle
{
return
UIStatusBarStyleLightContent;
}
点进头文件可以验证刚才说法:
@property(readwrite, nonatomic,getter=isStatusBarHidden) BOOL statusBarHidden NS_DEPRECATED_IOS(2_0, 9_0,
"Use -[UIViewController prefersStatusBarHidden]"
);
七、didFinishLaunchingWithOptions
如果运行的时候报下列错误,那就是你的didFinishLaunchingWithOptions写的不对了
***** Assertion failure
in
-[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UIApplication.m:3294**
iOS9不允许在didFinishLaunchingWithOptions结束了之后还没有设置window的rootViewController。 也许是xcode7的编译器本身就不支持。
解决的方法当然就是先初始化个值,之后再赋值替换掉
UIWindow *window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
window.rootViewController = [[UIViewController alloc]init];
八、tableView
虽然现在的iOS9已经推送正式版了,但是iOS9使用时还是会感觉到App比以前更加卡顿了,tableView拖动时卡顿显示的最为明显。 并且之前遇到一个bug,原本好的项目用xcode7一编译,tableView刷新出了问题 ,[tableView reloadData]无效 有一行cell明明改变了但是刷新不出来。 感觉可能是这个方法和某种新加的特性冲突了,猜测可能是reloadData的操作被推迟到下一个RunLoop执行最终失效。
解决的方法是,注释[tableView reloadData],改用局部刷新,问题居然就解决了。
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
暂时遇到这些问题,感觉iOS9的出现让所有iOS开发都是菊花一紧,希望苹果这种大刀阔斧做改变,特立独行的风格发展下去以后别和government 产生矛盾,然后公司倒闭 导致开发人员失业,也许是我想多了。预祝所有的iOS都能及时的做好适配改完bug,下个版本一上线,所有问题都解决。
最近提交版本的时候出现以下提示:
Could not make parent directory for: /Users/xxx/.itmstransporter/softwaresupport/bin/Frameworks/ITunesSoftwareService.framework/Resources/ITunesSoftwareServiceConfiguration.xml
Could not configure software support.An exception has occurred: /Users/xxx/.itmstransporter/softwaresupport/bin/Frameworks/ITunesSoftwareService.framework/Resources/ITunesSoftwareServiceConfiguration.xml (No such file or directory)
ERROR ITMS-90168: "The binary you uploaded was invalid."
解决方法如下:
打开终端,输入以下指令:
- $ cd ~/.itmstransporter
- $ rm update_check*
- $ mv softwaresupport softwaresupport.bak
- $ cd UploadTokens
- $ rm *.token
down vote
I encountered the same problem today with the same exact error message when trying to submit our app (using Xcode 7 beta 5) but instead of the instabug.bundle
bit, it was for me TencentOpenApi_IOS_Bundle.bundle
.
I solved the problem by finding the named bundle in the project then - just as the error message suggests - edited the Info.plist
that is in the bundle by removing the CFBundleExecutable
key. The CFBundlePackageType
key was already set to BNDL
so I didn't touch it.
After these changes I did Product > Clean and then had no problem submitting the app to the App store.
I hope this helps.
今天升级Xcode 7.0 bata
发现网络访问失败。
输出错误信息
<code style="padding: 0px; color: inherit; white-space: inherit; background-color: transparent;"><span style="font-family:Comic Sans MS;font-size:18px;">The resource could not be loaded because the App Transport Security policy requires the <span class="hljs-operator"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">use</span> <span class="hljs-keyword" style="color: rgb(133, 153, 0);">of</span> a secure <span class="hljs-keyword" style="color: rgb(133, 153, 0);">connection</span>.</span></span></code>
Google后查证,iOS9引入了新特性App Transport Security (ATS)
。详情:App Transport Security (ATS)
新特性要求App内访问的网络必须使用HTTPS
协议。
但是现在公司的项目使用的是HTTP
协议,使用私有加密方式保证数据安全。现在也不能马上改成HTTPS
协议传输。
最终找到以下解决办法:
- 在Info.plist中添加
NSAppTransportSecurity
类型Dictionary
。 - 在
NSAppTransportSecurity
下添加NSAllowsArbitraryLoads
类型Boolean
,值设为YES
参考:
- App Transport Security support aka apps on iOS 9 don't work #4560
- 711_networking_with_nsurlsession.pdf
以前的项目 放到Xcode7中运行时可能会遇到以下错误:You must rebuild it with bitcode enabled (Xcodesetting ENABLE_BITCODE)
未来Watch应用须包含Bitcode,iOS不强制,但Xcode7默认会开启Bitcode。
如何适配?
方法一:更新library使包含Bitcode,否则会出现以下中的警告;
(
null
): URGENT: all bitcode will be dropped because
‘/Users/myname/Library/Mobile Documents/com~apple~CloudDocs/foldername/appname/GoogleMobileAds.framework/GoogleMobileAds(GADSlot+AdEvents.o)‘
was built without bitcode. You must rebuild it
with
bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode
for
this
target. Note: This will be an error
in
the future.
方法二:关闭Bitcode
工程设置中 buildingsetting 中搜索bitcode 选择no
解决问题!!!!
原文地址:http://blog.csdn.net/wzzvictory/article/details/18413519 感谢原作者
一、NULL
1、声明位置
2、定义
- #undef NULL
- #ifdef __cplusplus
- # if !defined(__MINGW32__) && !defined(_MSC_VER)
- # define NULL __null
- # else
- # define NULL 0
- # endif
- #else
- # define NULL ((void*)0)
- #endif
- # define NULL ((void*)0)
因此,NULL本质上是:(void*)0
3、用处及含义
4、示例
- charchar *string = NULL;
二、nil
1、声明位置
2、定义
- #ifndef nil
- # if __has_feature(cxx_nullptr)
- # define nil nullptr
- # else
- # define nil __DARWIN_NULL
- # endif
- #endif
- # define nil __DARWIN_NULL
- #ifdef __cplusplus
- #ifdef __GNUG__
- #define __DARWIN_NULL __null
- #else /* ! __GNUG__ */
- #ifdef __LP64__
- #define __DARWIN_NULL (0L)
- #else /* !__LP64__ */
- #define __DARWIN_NULL 0
- #endif /* __LP64__ */
- #endif /* __GNUG__ */
- #else /* ! __cplusplus */
- #define __DARWIN_NULL ((void *)0)
- #endif /* __cplusplus */
- #define __DARWIN_NULL ((void *)0)
3、用处及含义
4、示例
- NSString *string = nil;
- id anyObject = nil;
三、Nil
1、声明位置
2、定义
- #ifndef Nil
- # if __has_feature(cxx_nullptr)
- # define Nil nullptr
- # else
- # define Nil __DARWIN_NULL
- # endif
- #endif
3、用处及含义
4、示例
- Class anyClass = Nil;
四、NSNull
1、声明位置
2、定义
- @interface NSNull : NSObject <NSCopying, NSSecureCoding>
- + (NSNull *)null;
- @end
3、用处及含义
4、示例
我们通常初始化NSArray对象的形式如下:- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",nil];
- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",nil,@"foogry"];
- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",[NSNull null],@"foogry"];
五、总结
从前面的介绍可以看出,不管是NULL、nil还是Nil,它们本质上都是一样的,都是(void *)0,只是写法不同。这样做的意义是为了区分不同的数据类型,比如你一看到用到了NULL就知道这是个C指针,看到nil就知道这是个Objective-C对象,看到Nil就知道这是个Class类型的数据。
在Mac上配置adb命令
在Mac OS中使用adb命令时,应进行变量配置,步骤如下:
一、终端中输入 cd ~
二、输入touch .bash_profile 回车
touch:如果没有,则创建文件,如果有,更新一下文件时间
三、输入open -e .bash_profile
open:打开文件
回车后会在TextEdit中打开这个文件(如果未配置过环境变量,应该是个空白文件)。在文件中加如以下内容
export PATH=${PATH}:/Users/sunqichao/worksoft/android-sdk-mac_x86/platform-tools
这是我机器上的路径,具体个人机器路径,可右键点击platform-tools--》显示简介 查看
原文地址:http://www.cocoachina.com/swift/20150608/12025.html
一、解决问题
Swift项目需要使用封装好的Objective-c组件、第三方类库,苹果提供的解决方案能够处理日常大部分需求,但还不能称之为完美,混编过程中会遇到很多问题。本文将Swift兼容Objective-c的问题汇总,以帮助大家更好的使用Swift,内容列表如下:
1. Swift调用Objective-c代码
2. Objective-c调用Swift代码
3. Swift兼容Xib/Storyboard
4. Objective-c巧妙调用不兼容的Swift方法
5. 多Target编译错误解决
6. 第三方类库支持
二、基础混合编程
Swift与Objective-c的代码相互调用,并不像Objective-c与C/C++那样方便,需要做一些额外的配置工作。无论是Swift调用Objective-c还是Objective-c调用Swift,Xcode在处理上都需要两个步骤:
2.1 Swift调用Objective-c代码
Xcode对于Swift调用Objective-c代码,除宏定义外,其它支持相对完善。
2.1.1 使用Objetvie-c的第一步
告诉Xcode、哪些Objective-c类要使用,新建.h头文件,文件名可以任意取,建议采用“项目名-Bridging-Header.h”命令格式。
Tips
Swift之IOS项目,在Xcode6创建类文件,默认会自动选择OS X标签下的文件,这时一定要选择iOS标签下的文件,否则会出现语法智能提示不起作用,严重时会导致打包出错。
2.1.2 第二步,Target配置,使创建的头文件生效
设置Objective-C Bridging Header时,路径要配置正确,例如:创建的名为“ILSwift-Bridging-Header.h”文件,存于ILSwift项目文件夹的根目录下,写法如下:
ILSwift/ILSwift-Bridging-Header.h
当然,在新项目中,直接创建一个Objective-c类,Xcode会提示:
直接选择Yes即可,如果不小心点了其它按钮,可以按照上面的步骤一步一步添加。
2.2 Objective-c调用Swift代码
2.2.1 Objective-c调用Swift代码两个步骤
第一步告诉Xcode哪些类需要使用(继承自NSObject的类自动处理,不需要此步骤),通过关键字@objc(className)来标记
import UIKit
@objc(ILWriteBySwift)
class ILWriteBySwift {
var
name: String!
class func newInstance() -> ILWriteBySwift {
return
ILWriteBySwift()
}
}
第二步引入头文件,Xcode头文件的命名规则为
$(SWIFT_MODULE_NAME)-Swift.h
示例如下:
#import "ILSwift-Swift.h"
Tips
不清楚SWIFT_MODULE_NAME可通过以下步骤查看
2.2.2找不到$(SWIFT_MODULE_NAME)-Swift.h
1.遇到此问题可按以下步骤做常规性检查
确定导入SWIFT_MODULE_NAME)-Swift.h头文件的文件名正确
SWIFT_MODULE_NAME)-Swift.h在clean后没有重新构建,执行Xcode->Product->Build
2.头文件循环
在混合编程的项目中,由于两种语言的同时使用,经常会出现以下需求:在Swift项目中需要使用Objectvie-c写的A类,而A类又会用到Swift的一些功能,头文件的循环,导致编译器不能正确构建$(SWIFT_MODULE_NAME)-Swift.h,遇到此问题时,在.h文件做如下处理
//删除以下头文件
//#import "ILSwift-Swift.h"
//通过代码导入类
@class ILSwiftBean;
在Objevtive-c的.m文件最上面,添加
#import "ILSwift-Swift.h"
出现Use of undecalared identifier错误或者找不到方法,如下:
引起的原因有以下几种可能:
使用的Swift类不是继承自NSObject,加入关键字即可
SWIFT_MODULE_NAME)-Swift.h没有实时更新,Xcode->Product->Build
此Swift文件中使用了Objective-c不支持的类型或者语法,如private
出现部分方法找不到的问题,Xcode无智能提示:
此方法使用了Objective-c不支持的类型或者语法
苹果官方给出的不支持转换的类型
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
三、Xib/StoryBoard支持
Swift项目在使用Xib/StoryBoard时,会遇到两种不同的问题
Xib:不加载视图内容
Storyboard:找不到类文件
3.1 Xib不加载视图内容
在创建UIViewController时,默认选中Xib文件,在Xib与类文件名一致时,可通过以下代码实例化:
let controller = ILViewController()
运行,界面上空无一物,Xib没有被加载。解决办法,在类的前面加上@objc(类名),例如:
import UIKit
@objc(ILViewController)
class ILViewController: UIViewController {
}
Tips:
StoryBoard中创建的UIViewController,不需要@objc(类名)也能够保持兼容
3.2 Storyboard找不到类文件
Swift语言引入了Module概念,在通过关键字@objc(类名)做转换的时候,由于Storboard没有及时更新Module属性,会导致如下两种类型错误:
3.2.1 用@objc(类名)标记的Swift类或者Objective-c类可能出现错误:
2015-06-02 11:27:42.626 ILSwift[2431:379047] Unknown class _TtC7ILSwift33ILNotFindSwiftTagByObjcController in Interface Builder file.
解决办法,按下图,选中Module中的空白,直接回车
3.2.2 无@objc(类名)标记的Swift类
2015-06-02 11:36:29.788 ILSwift[2719:417490] Unknown class ILNotFindSwiftController
in
Interface Builder file.
解决办法,按下图,选择正确的Module
3.产生上面错误的原因: 在设置好Storyboard后,直接在类文件中,添加或者删除@objc(类名)关键字,导致Storyboard中 Module属性没有自动更新,所以一个更通用的解决办法是,让Storyboard自动更新Module,如下:
3.3 错误模拟Demo下载
为了能够让大家更清楚的了解解决流程,将上面的错误进行了模拟,想动手尝试解决以上问题的同学可以直接下载demo
四、Objective-c巧妙调用不兼容的Swift方法
在Objective-c中调用Swift类中的方法时,由于部分Swift语法不支持转换,会遇到无法找到对应方法的情况,如下:
import UIKit
enum HTTPState {
case
Succed, Failed, NetworkError, ServerError, Others
}
class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void
in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void
in
callback(state: HTTPState.Succed)
})
})
}
}
对应的$(SWIFT_MODULE_NAME)-Swift.h文件为:
SWIFT_CLASS(
"_TtC12ILSwiftTests13ILHTTPRequest"
)
@interface ILHTTPRequest : NSObject
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
从上面的头文件中可以看出,方法requestLogin使用了不支持的Swift枚举,转换时方法被自动忽略掉,有以下两种办法,可以巧妙解决类似问题:
4.1 用支持的Swift语法包装
在Swift文件中,添加一个可兼容包装方法wrapRequestLogin,注意此方法中不能使用不兼容的类型或者语法
import UIKit
enum HTTPState: Int {
case
Succed = 0, Failed = 1, NetworkError = 2, ServerError = 3, Others = 4
}
class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void
in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void
in
callback(state: HTTPState.Succed)
})
})
}
class func wrapRequestLogin(userName: String, password: String, callback: (state: Int) -> (Void)) {
self.requestLogin(userName, password: password) { (state) -> (Void)
in
callback(state: state.rawValue)
}
}
}
对应的$(SWIFT_MODULE_NAME)-Swift.h文件为:
SWIFT_CLASS(
"_TtC12ILSwiftTests13ILHTTPRequest"
)
@interface ILHTTPRequest : NSObject
+ (void)wrapRequestLogin:(NSString * __nonnull)userName password:(NSString * __nonnull)password callback:(void (^ __nonnull)(NSInteger))callback;
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
此时,我们可以在Objective-c中直接使用包装后的方法wrapRequestLogin
4.2 巧妙使用继承
使用继承可以支持所有的Swift类型,主要的功能在Objective-c中实现,不支持的语法在Swift文件中调用,例如,ILLoginSuperController做为父类
@interface ILLoginSuperController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
- (IBAction)loginButtonPressed:(id)sender;
@end
////////////////////////////////////////////////////////////////
@implementation ILLoginSuperController
- (IBAction)loginButtonPressed:(id)sender
{
}
@end
创建Swift文件,继承自ILLoginSuperController,在此Swift文件中调用那些不支持的语法
import UIKit
class ILLoginController: ILLoginSuperController {
override func loginButtonPressed(sender: AnyObject!) {
ILHTTPRequest.requestLogin(self.userNameField.text, password: self.passwordField.text) { (state) -> (Void)
in
//具体业务逻辑
}
}
}
五、多Target编译错误解决
在使用多Target时,会出现一些编译错误
5.1 Use of undeclared type
此类错误,是因为当前运行的Target找不到必须编译文件。将文件添加到Target即可,如下支持ILSwiftTests Target,选中ILSwiftTests前的复选框即可
5.2 does not have a member named
此类错误可能由于如下两种原因引起,解决办法同上:
1.此方法来自父类,父类文件没有加入到当前Target
2.此方法来自扩展,扩展没有加入到当前Target
Tips
如果检查发现,所有的类文件都已经准确添加到Target中,但编译还是不通过,此时着重检查桥接文件是否正确设置,是否将相应的头文件加入到了桥接文件中。如无特别要求,建议将所有Target的桥接文件全都指向同一文件。关于桥接文件的设置,请参考2.1
六、第三方类库支持
Swift项目取消了预编译文件,一些第三方Objective-c库没有导入必要框架(如UIKit)引起编译错误
6.1 Cocoapods找不到.o文件
在使用了Cocoapods项目中,会出现部分类库的.o文件找不到,导致此种错误主要是以下两种问题:
类库本身存在编译错误
Swift没有预编译,UIKit等没有导入
将此库文件中的代码文件直接加到项目中,编译,解决错误。
6.2 JSONModel支持
在Swift中可以使用JSONModel部分简单功能,一些复杂的数据模型建议使用Objevtive-c
import UIKit
@objc(ILLoginBean)
public class ILLoginBean: JSONModel {
var
userAvatarURL: NSString?
var
userPhone: NSString!
var
uid: NSString!
}
Tips
在Swift使用JSONModel框架时,字段只能是NSFoundation中的支持类型,Swift下新添加的String、Int、Array等都不能使用
6.3 友盟统计
Swift项目中引入友盟统计SDK会出现referenced from错误:
解决办法,找到Other Linker Flags,添加-lz
七、综述
现在大部分成熟的第三方框架都是使用Objective-c写的,开发时不可避免的涉及到两种语言的混合编程,期间会遇到很多奇怪的问题。因为未知才有探索的价值,Swift的简洁快速,能够极大的推进开发进度。所以从今天开始,大胆的开始尝试。
本文转载,原文地址:http://www.cocoachina.com/ios/20150609/12072.html
原文 Grand Central Dispatch Tutorail for Swift: Part 1/2
原文作者:Bjrn Olav Ruud
译者:Ethan Joe
尽管Grand Central Dispatch(以下简称为GCD)已推出一段时间了,但并不是所有人都明白其原理;当然这是可以理解的,毕竟程序的并发机制很繁琐,而且基于C的GCD的API对于Swift的新世界并不是特别友好。
在接下来的两节教程中,你将学习GCD的输入 (in)与输出 (out)。第一节将解释什么是GCD并了解几个GCD的基础函数。在第二节,你将学习几个更加进阶的GCD函数。
Getting Started
GCD是libdispatch的代名词,libdispatch代表着运行iOS与OS X的多核设备上执行并行代码的官方代码库。它经常有以下几个特点:
GCD通过将高代价任务推迟执行并调至后台运行的方式来提升App的交互速度。
GCD提供比锁与多线程更简单的并发模型,以此来避免一些由并发引起的Bug。
为了理解GCD,你需要明白一些与线程、并发的相关的概念。这些概念间有着细微且模糊的差别,所以在学习GCD前请简略地熟悉一下这些概念。
连续性 VS 并发性
这些术语用来描述一些被执行的任务彼此间的关系。连续性执行任务代表着同一时间内只执行一个任务,而并发性执行任务则代表着同一时间内可能会执行多个任务。
任务
在这篇教程中你可以把每个任务看成是一个闭包。 事实上,你也可以通过函数指针来使用GCD,但在大多数情况下这明显有些麻烦。所以,闭包用起来更简单。
不知道什么是Swift中的闭包?闭包是可被储存并传值的可调用代码块,当它被调用时可以像函数那样包含参数并返回值。
Swift中的闭包和Objective-C的块很相近,它们彼此间是可以相互交替的。这个过程中有一点你不能做的是:用Objective-C的块代码去交互具有Swift独有属性属性的闭包,比如说具有元组属性的闭包。但是从Swift端交互Objective-C端的代码则是毫无障碍的,所以无论何时你在文档中看到到的Objective-C的块代码都是可用Swift的闭包代替的。
同步 VS 异步
这些术语用来描述当一个函数的控制权返回给调用者时已完成的工作的数量。
同步函数只有在其命令的任务完成时才会返回值。
异步函数则不会等待其命令的任务完成,即会立即返回值。所以,异步函数不会锁住当前线程使其不能向队列中的下一位函数执行。
值得注意的是---当你看到一个同步函数锁住(block)了当前进程,或者一个函数是锁函数(blocking function)或是锁运算(block operation)时别认混了。这里的锁(blocks)是用来形容其对于自己线程的影响,它跟Objective-C中的块(block)是不一样的。再有一点要记住的就是在任何GCD文档中涉及到Objective-C的块代码都是可以用Swift的闭包来替换的。
临界区
这是一段不能被在两个线程中同时执行的代码。这是因为这段代码负责管理像变量这种若被并发进程使用便会更改的可共享资源。
资源竞争
这是一种软件系统在一种不被控制的模式下依靠于特定队列或者基于事件执行时间进行运行的情况,比如说程序当前多个任务执行的具体顺序。资源竞争可以产生一些不会在代码排错中立即找到的错误。
死锁
两个或两个以上的进程因等待彼此完成任务或因执行其他任务而停止当前进程运行的情况被称作为死锁。举个例子,进程A因等待进程B完成任务而停止运行,但进程B也在等待进程A完成任务而停止运行的僵持状态就是死锁。
线程安全性
具有线程安全性的代码可以在不产生任何问题(比如数据篡改、崩溃等)的情况下在多线程间或是并发任务间被安全的调用。不具有线程安全性的代码的正常运行只有在单一的环境下才可被保证。举个具有线性安全性的代码示例let a = ["thread-safe"]。你可以在多线程间,不产生任何bug的情况下调用这个具有只读性的数组。相反,通过var a = ["thread-unsafe"]声明的数组是可变可修改的。这就意味着这个数组在多线层间可被修改从而产生一些不可预测的问题,对于那些可变的变量与数据结构最好不要同时在多个线程间使用。
上下文切换
上下文切换是当你在一个进程中的多个不同线程间进行切换时的一种进程进行储存与恢复的状态。这种进程在写多任务App时相当常见,但这通常会产生额外的系统开销。
并发 VS 并行
并发和并行总是被同时提及,所以有必要解释一下两者间的区别。
并发代码中各个单独部分可以被"同时"执行。不管怎样,这都由系统决定以何种方式执行。具有多核处理器的设备通过并行的方式在同一时间内实现多线程间的工作;但是单核处理器设备只能在同一时间内运行在单一线程上,并利用上下文切换的方式切换至其他线程以达到跟并行相同的工作效果。如下图所示,单核处理器设备运行速度快到形成了一种并行的假象。
并发 VS 并行
尽管你会在GCD下写出使用多线程的代码,但这仍由GCD来决定是否会使用并发机制。并行机制包含着并发机制,但并发机制却不一定能保证并行机制的运行。
队列
GCD通过队列分配的方式来处理待执行的任务。这些队列管理着你提供给GCD待处理的任务并以FIFO的顺序进行处理。这就得以保证第一个加进队列的任务会被首个处理,第二个加进队列的任务则被其次处理,其后则以此类推。
连续队列
连续队列中的任务每次执行只一个,一个任务只有在其前面的任务执行完毕后才可开始运行。如下图所示,你不会知道前一个任务结束到下一个任务开始时的时间间隔。
连续队列
每一个任务的执行时间都是由GCD控制的;唯一一件你可以确保的事便是GCD会在同一时间内按照任务加进队列的顺序执行一个任务。
因为在连续队列中不允许多个任务同时运行,这就减少了同时访问临界区的风险;这种机制在多任务的资源竞争的过程中保护了临界区。假如分配任务至分发队列是访问临界区的唯一方式,那这就保证了的临界区的安全。
并发队列
并发队列中的任务依旧以FIFO顺序开始执行。。。但你能知道的也就这么多了!任务间可以以任何顺序结束,你不会知道下一个任务开始的时间也不会知道一段时间内正在运行任务的数量。因为,这一切都是由GCD控制的。
如下图所示,在GCD控制下的四个并发任务:
并发队列
需要注意的是,在任务0开始执行后花了一段时间后任务1才开始执行,但任务1、2、3便一个接一个地快速运行起来。再有,即便任务3在任务2开始执行后才开始执行,但任务3却更早地结束执行。
任务的开始执行的时间完全由GCD决定。假如一个任务与另一个任务的执行时间相互重叠,便由GCD决定(在多核非繁忙可用的情况下)是否利用不同的处理器运行或是利用上下文切换的方式运行不同的任务。
为了用起来有趣一些,GCD提供了至少五种特别的队列来对应不同情况。
队列种类
首先,系统提供了一个名为主队列(main queue)的特殊连续队列。像其他连续队列一样,这个队列在同一间内只能执行一个任务。不管怎样,这保证了所有任务都将被这个唯一被允许刷新UI的线程所执行。它也是唯一一个用作向UIView对象发送信息或推送监听(Notification)。
GCD也提供了其他几个并发队列。这几个队列都与自己的QoS (Quality of Service)类所关联。Qos代表着待处理任务的执行意图,GCD会根据待处理任务的执行意图来决定最优化的执行优先权。
QOS_CLASS_USER_INTERACTIVE: user interactive类代表着为了提供良好的用户体验而需要被立即执行的任务。它经常用来刷新UI、处理一些要求低延迟的加载工作。在App运行的期间,这个类中的工作完成总量应该很小。
QOS_CLASS_USER_INITIATED:user initiated类代表着从UI端初始化并可异步运行的任务。它在用户等待及时反馈时和涉及继续运行用户交互的任务时被使用。
QOS_CLASS_UTILITY:utility类代表着长时间运行的任务,尤其是那种用户可见的进度条。它经常用来处理计算、I/O、网络通信、持续数据反馈及相似的任务。这个类被设计得具有高效率处理能力。
QOS_CLASS_BACKBROUND:background类代表着那些用户并不需要立即知晓的任务。它经常用来完成预处理、维护及一些不需要用户交互的、对完成时间并无太高要求的任务。
要知道苹果的API也会使用这些全局分配队列,所以你分派的任务不会是队列中的唯一一个。
最后,你也可以自己写一个连续队列或是并发队列。算起来你起码最少会有五个队列:主队列、四个全局队列再加上你自己的队列。
以上便是分配队列的全体成员。
GCD的关键在于选择正确的分发函数以此把你的任务分发至队列。理解这些东西的最好办法就是完善下面的Sample Project。
Sample Project
既然这篇教程的目的在于通过使用GCD在不同的线程间安全地调用代码,那么接下来的任务便是完成这个名为GooglyPuff的半成品。
GooglyPuff是一款通过CoreImage脸部识别API在照片中人脸的双眼的位置上贴上咕噜式的大眼睛且线程不安全的App。你既可以从Photo Library中选择照片,也可以通过网络从事先设置好的地址下载照片。
GooglyPuff Swift Start 1
将工程下载至本地后用Xcode打开并编译运行。它看起来是这样的:
GooglyPuff
在工程中共有四个类文件:
PhotoCollectionViewController:这是App运行后显示的首个界面。它将显示所有被选照片的缩略图。
PhotoDetailViewController:它将处理将咕噜眼添加至照片的工作并将处理完毕的照片显示在UIScrollView中。
Photo:一个包含着照片基本属性的协议,其中有image(未处理照片)、thumbnail(裁减后的照片)及status(照片可否使用状态);两个用来实现协议的类,DownloadPhoto将从一个NSURL实例中实例化照片,而AssetPhoto则从一个ALAsset实例中实例化照片。
PhotoManager:这个类将管理所有Photo类型对象。
使用dispatch_async处理后台任务
回到刚才运行的App后,通过自己的Photo Library添加照片或是使用Le internet下载一些照片。
需要注意的是当你点击PhotoCollectionViewController中的一个UICollectionViewCell后,界面切换至一个新的PhotoDetailViewController所用的时间;对于那些处理速度较慢的设备来说,处理一张较大的照片会产生一个非常明显的延迟。
这种情况下很容易使UIViewController的viewDidLoad因处理过于混杂的工作而负载;这么做的结果便在view controller出现前产生较长的延迟。假如可能的话,我们最好将某些工作放置后台处理。
这听起来dispatch_async该上场了。
打开PhotoDetailViewController后将viewDidLoad函数替换成下述代码:
override func viewDidLoad() {
super
.viewDidLoad()
assert(image != nil,
"Image not set; required to use view controller"
)
photoImageView.image = image
// Resize if neccessary to ensure it's not pixelated
if
image.size.height <= photoImageView.bounds.size.height &&
image.size.width <= photoImageView.bounds.size.width {
photoImageView.contentMode = .Center
}
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
// 1
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(dispatch_get_main_queue()) {
// 2
self.fadeInNewImage(overlayImage)
// 3
}
}
}
在这里解释一下上面修改的代码:
你首先将照片处理工作从主线程(main thread)移至一个全局队列(global queue)。因为这是一个异步派发(dispatch_async的调用,闭包以异步的形式进行传输意味着调用的线程将会被继续执行。这样一来便会使viewDidLoad更早的在主线程上结束执行并使得整个加载过程更加流畅。与此同时,脸部识别的过程已经开始并在一段时间后结束。
这时脸部识别的过程已经结束并生成了一张新照片。当你想用这张新照片来刷新你的UIImageView时,你可以向主线程添加一个新的闭包。需要注意的是--主线程只能用来访问UIKit。
最后,你便用这张有着咕噜眼的fadeInNewImage照片来刷新UI。
有没有注意到你已经用了Swift的尾随闭包语法(trailing closure syntax),就是以在包含着特定分配队列参数的括号后书写表达式的形式了向dispatch_async传递闭包。假如把闭包写出函数括号的话,语法会看起来更加简洁。
运行并编译App;选一张照片后你会发现view controller加载得很快,咕噜眼会在很短的延迟后出现。现在的运行效果看起来比之前的好多了。当你尝试加载一张大得离谱的照片时,App并不会在view controller加载时而延迟,这种机制便会使App表现得更加良好。
综上所述,dispatch_async将任务以闭包的形式添加至队列后立即返回。这个任务在之后的某个时间段由GCD所执行。当你要在不影响当前线程工作的前提下将基于网络或高密度CPU处理的任务移至后台处理时,dispatch_asnyc便派上用场了。
接下来是一个关于在使用dispatch_asnyc的前提下,如何使用以及何时使用不同类型队列的简洁指南:
自定义连续队列(Custom Serial Queue): 在当你想将任务移至后台继续工作并且时刻监测它的情况下,这是一个不错的选择。需要注意的是当你想从一个方法中调用数据时,你必须再添加一个闭包来回调数据或者考虑使用dispatch_sync。
主队列(Main Queue[Serial]):这是一个当并发队列中的任务完成工作时来刷新UI的普遍选择。为此你得在一个闭包中写入另一个闭包。当然,假如你已经在主线程并调用一个面向主线程的dispatch_async的话,你需要保证这个新任务在当前函数运行结束后的某个时间点开始执行。
并发队列(Concurrent Queue):对于要运行后台的非UI工作是个普遍的选择。
获取全局队列的简洁化变量
你也许注意到了dispatch_get_global_queue函数里的QoS类的参数写起来有些麻烦。这是因为qos_class_t被定义成一个值类型为UInt32且最后还要被转型为Int的结构体。我们可以在Utils.swift中的URL变量下面添加一些全局的简洁化变量,以此使得调用全局队列更加简便。
var
GlobalMainQueue: dispatch_queue_t {
return
dispatch_get_main_queue()
}
var
GlobalUserInteractiveQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
}
var
GlobalUserInitiatedQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
}
var
GlobalUtilityQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)
}
var
GlobalBackgroundQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
回到PhotoDetailViewController中viewDidLoad函数中,用简洁变量代替dispatch_get_global_queue和dispatch_get_main_queue。
dispatch_async(GlobalUserInitiatedQueue) {
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(GlobalMainQueue) {
self.fadeInNewImage(overlayImage)
}
}
这样就使得派发队列的调用的代码更加具有可读性并很轻松地得知哪个队列正在被使用。
利用dispatch_after实现延迟
考虑一下你App的UX。你的App有没有使得用户在第一次打开App的时候不知道该干些什么而感到不知所措呢?: ]
假如在PhotoManager中没有任何一张照片的时候便向用户发出提醒应该是一个不错的主意。不管怎样,你还是要考虑一下用户在App主页面上的注意力:假如你的提醒显示得过快的话,用户没准在因为看着其他地方而错过它。
当用户第一次使用App的时候,在提醒显示前执行一秒钟的延迟应该足以吸引住用户的注意力。
在PhotoCollectionViewController.swift底部的showOrHideBarPrompt函数中添加如下代码:
func showOrHideNavPrompt() {
let delayInSeconds = 1.0
let popTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(delayInSeconds * Double(NSEC_PER_SEC)))
// 1
dispatch_after(popTime, GlobalMainQueue) {
// 2
let count = PhotoManager.sharedManager.photos.count
if
count > 0 {
self.navigationItem.prompt = nil
}
else
{
self.navigationItem.prompt =
"Add photos with faces to Googlyify them!"
}
}
}
当你的UICollectionView重载的时候,viewDidLoad函数中的showOrHideNavPrompt将被执行。解释如下:
你声明了一个代表具体延迟时间的变量。
你将等待delayInSeconds变量中设定的时间然后向主队列异步添加闭包。
编译并运行App。你会看到一个在很大程度上吸引用户注意力并告知他们该做些什么的细微延迟。
dispatch_after就像一个延迟的dispatch_async。你仍旧在实时运行的时候毫无操控权并且一旦dispatch_after返回后你也无法取消整个延迟任务。
还在思考如何适当的使用dispatch_after?
自定义连续队列(Custom Serial Queue):当你在自定义连续队列上使用dispatch_after时一定要当心,此时最好不要放到主队列上执行。
主队列(Main Queue[Serial]):这对于dispatch_after是个很好的选择;Xcode对此有一个不错的自动执行至完成的样板。
并发队列(Concurrent Queue):在自定义并发队列上使用dispatch_after时同样要当心,即便你很少这么做。此时最好放到主队列上执行。
单例和线程安全
单例,不管你love it还是hate it,他们对于iOS都是非常重要的。: ]
一提到单例(Singleton)人们便觉得他们是线程不安全的。这么想的话也不是没有道理:单例的实例经常在同一时间内被多线程所访问。PhotoManager类便是一个单例,所以你要思考一下上面提到的问题。
两个需要考虑的情况,单例实例初始化时和实例读写时的线程安全性。
先考虑第一种情况。因为在swift是在全局范围内初始化变量,所以这种情况较为简单。在Swift中,当全局变量被首次访问调用时便被初始化,并且整个初始化过程具有原子操作性。由此,代码的初始化过程便成为一个临界区并且在其他线程访问调用全局变量前完成初始化。Swift到底是怎么做到的?其实在整个过程中,Swift通过dispatch_once函数使用了GCD。若想了解得更多的话请看这篇Swift官方Blog。
在线程安全的模式下dispatch_once只会执行闭包一次。当一个在临界区执行的线程--向dispatch_once传入一个任务--在它结束运行前其它的线程都会被限制住。一旦执行完成,它和其他线程便不会再次在此区域执行。通过let把单例定义为全局定量的话,我们就可以保证这个变量的值在初始化后不会被修改。总之,Swift声明的所有全局定量都是通过线程安全的初始化得到的单例。
但我们还是要考虑读写问题。尽管Swift通过使用dispatch_once确保我们在线程安全的模式下初始化单例,但这并不能代表单例的数据类型同样具有线程安全性。举个例子,假如一个全局变量是一个类的实例,你仍可以在类内的临界区操控内部数据,这将需要利用其他的方式来保证线程安全性。
处理读取与写入问题
保证线程安全性的实例化不是我们处理单例时的唯一问题。假如一个单例属性代表着一个可变的对象,比如像PhotoManager 中的photos数组,那么你就需要考虑那个对象是否就有线程安全性。
在Swift中任何用let声明的变量都是一个只可读并线程安全的常量。但是用var声明的变量都是值可变且并线程不安全的。比如Swift中像Array和Dictionary这样的集合类型若被声明为值可变的话,它们就是线程不安全的。那Foundation中的NSArray线程是否安全呢?不一定!苹果还专门为那些线程非安全的Foundation类列了一个清单。
尽管多线程可以在不出现问题的情况下同时读取一个Array的可变实例,但当一个线程试图修改实例的时候另一个线程又试图读取实例,这样的话安全性可就不能被保证了。
在下面PhotoManager.swift中的addPhoto函数中找一找错误:
func addPhoto(photo: Photo) {
_photos.append(photo)
dispatch_async(dispatch_get_main_queue()) {
self.postContentAddedNotification()
}
}
这个写取方法修改了可变数组的对象。
再来看一看photos的property:
private
var
_photos: [Photo] = []
var
photos: [Photo] {
return
_photos
}
当property的getter读取可变数组的时候它就是一个读取函数。调用者得到一份数组的copy并阻止原数组被不当修改,但这不能在一个线程调用addPhoto方法的同时阻止另一个线程回调photo的property的getter。
提醒:在上述代码中,调用者为什么不直接得到一份photos的copy呢?这是因为在Swift中,所有的参数和函数的返回值都是通过推测(Reference)或值传输的。通过推测进行传输和Objective-C中传输指针是一样的,这就代表着你可以访问调用原始对象,并且对于同一对象的推测后其任何改变都可以被显示出来。在对象的copy中通过值结果传值且对于copy的更改都不对原是对象造成影响。Swift默认以推测机制或结构体的值来传输类的实例。
Swift中的Array和Dictionary都是通过结构体来实现的,当你向前或向后传输这些实例的时候,你的代码将会执行很多次的copy。这时不要当心内存使用问题,因为这些Swift的集合类型(如Array、Dictionary)的执行过程都已被优化,只有在必要的时候才会进行copy。对于来一个通过值传输的Array实例来说,只有在被传输后才会进行其第一次修改。
这是一个常见的软件开发环境下的读写问题。GCD通过使用dispatch barriers提供了一个具有读/写锁的完美解决方案。
在使用并发队列时,dispatch barriers便是一组像连续性路障的函数。使用GCD的barrier API保证了被传输的闭包是在特定时间内、在特定队列上执行的唯一任务。这就意味着在派发的barrier前传输的任务必须在特定闭包开始执行前完成运行。
当闭包到达后,barrier便开始执行闭包并保证此段时间内队列不会再执行任何其他的闭包。特定闭包一旦完成执行,队列便会返回其默认的执行状态。GCD同样提供了具有同步与异步功能的barrier函数。
下面的图式描述了在多个异步任务中的barrier函数的运行效果:
dispatch barrier
需要注意的是在barrier执行前程序是以并发队列的形式运行,但当barrier一旦开始运行后,程序便以连续队列的形式运行。没错,barrier是这段特定时间内唯一被执行的任务。当barrier执行结束后,程序再次回到了普通的并发队列运行状态。
对于barrier函数我们做一些必要的说明:
自定义连续队列(Custom Serial Queue):在这种情况下不是特别建议使用barrier,因为barrier在连续队列执行期间不会起到任何帮助。
全局并发队列(Global Concurrent Queue):谨慎使用;当其他系统也在使用队列的时候,你应该不想把所有的队列都垄为自己所用。
自定义并发队列(Custom Concurrent Queue):适用于涉及临界区及原子性的代码。在任何你想要保正设定(setting)或初始化具有线程安全性的情况下,barrier都是一个不错的选择。
从上面对于自定义并发序列解释可以得出结论,你得写一个自己的barrier函数并将读取函数和写入函数彼此分开。并发序列将允许多个读取过程同步运行。
打开PhotoManager.swift,在photos属性下给类文件添加如下的私有属性:
private let concurrentPhotoQueue = dispatch_queue_create(
"com.raywenderlich.GooglyPuff.photoQueue"
, DISPATCH_QUEUE_CONCURRENT)
通过dispatch_queue_create函数初始化了一个名为concurrentPhotoQueue的并发队列。第一个参数是一个逆DNS风格的命名方式;其描述在debugging时会非常有用。第二个参数设定了你的队列是连续性的还是并发性的。
很多网上的实例代码中都喜欢给dispatch_queue_create的第二个参数设定为0或NULL。其实这是一种过时的声明连续分派队列的方法。你最好用你自己的参数设定它。
找到addPhoto函数并代替为以下代码:
func addPhoto(photo: Photo) {
dispatch_barrier_async(concurrentPhotoQueue) {
// 1
self._photos.append(photo)
// 2
dispatch_async(GlobalMainQueue) {
// 3
self.postContentAddedNotification()
}
}
}
你的新函数是这样工作的:
通过使用你自己的自定义队列添加写入过程,在不久后临界区执行的时候这将是你的队列中唯一执行的任务。
向数组中添加对象。只要这是一个barrier属性的闭包,那么它在concurrentPhotoQueue队列中绝不会和其他闭包同时运行。
最后你推送了一个照片添加完毕的消息。这个消息应该从主线程推送因为它将处理一些涉及UI的工作,所以你为这个消息以异步的形式向主线程派发了任务。
以上便处理好了写入方法的问题,但是你还要处理一下photos的读取方法。
为了保证写入方面的线程安全行,你需要在concurrentPhotoQueue队列中运行读取方法。因为你需要从函数获取返回值并且在读取任务返回前不会运行任何其他的任务,所以你不能向队列异步派发任务。
在这种情况下,dispatch_sync是一个不错的选择。
dispatch_sync可以同步传输任务并在其返回前等待其完成。使用dispatch_sync跟踪含有派发barrier的任务,或者在当你需要使用闭包中的数据时而要等待运行结束的时候使用dispatch_sync。
谨慎也是必要的。想象一下,当你对一个马上要运行的队列调用dispatch_sync时,这将造成死锁。因为调用要等到闭包B执行后才能开始运行,但是这个闭包B只有等到当前运行的且不可能结束的闭包A执行结束后才有可能结束。
这将迫使你时刻注意自己调用的的或是传入的队列。
来看一下dispatch_sync的使用说明:
自定义连续队列(Custome Serial Queue):这种情况下一定要非常小心;假如一个队列中正在执行任务并且你将这个队列传入dispatch_sync中使用,这毫无疑问会造成死锁。
主队列(Main Queue[Serial]):同样需要小心发生死锁。
并发队列(Concurrent Queue):在对派发barrier执行同步工作或等待一个任务的执行结束后需要进行下一步处理的情况下,dispatch_sync是一个不错的选择。
依旧在PhotoManager.swift文件中,用以下代码替换原有的photos属性:
var
photos: [Photo] {
var
photosCopy: [Photo]!
dispatch_sync(concurrentPhotoQueue) {
// 1
photosCopy = self._photos
// 2
}
return
photosCopy
}
分布解释一下:
同步派发concurrentPhotoQueue使其执行读取功能。
储存照片数组至photosCopy并返回。
恭喜--你的PhotoManager单例现在线程安全了。不管现在是执行读取还是写入功能,你都可以保证整个单例在安全模式下运行。
队列可视化
还不能完全理解GCD的基础知识?接下来我们将在一个简单的示例中使用断点和NSLog功能确保你进一步理解GCD函数运行原理。
我将使用两个动态的GIF帮助你理解dispatch_async和dispatch_sync。在GIF的每步切换下,注意代码断点与图式的关系。
dispatch_sync重览
override func viewDidLoad() {
super
.viewDidLoad()
dispatch_sync(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog(
"First Log"
)
}
NSLog(
"Second Log"
)
}
dispatch_sync
分布解释:
主队列按顺序执行任务,下一个将要被执行的任务便是实例化包含viewDidLoad的UIViewController。
主队列开始执行viewDidLoad。
dispatch_sync闭包添加至全局队列并在稍后被执行。在此闭包完成执行前主队列上的工作将被暂停。回调的闭包可以被并发执行并以FIFO的顺序添加至一个全局队列。这个全局队列还包含添加dispatch_sync闭包前的多个任务。
终于轮到dispatch_sync闭包执行了。
闭包执行结束后主队列开始恢复工作。
viewDidLoad函数执行结束,主队列开始处理其他任务。
dispatch_sync函数向队列添加了一个任务并等待任务完成。 其实dispatch_async也差不多,只不过它不会等待任务完成便会返回线程。
dispatch_async重览
override func viewDidLoad() {
super
.viewDidLoad()
dispatch_async(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog(
"First Log"
)
}
NSLog(
"Second Log"
)
}
dispatch_async
主队列按顺序执行任务,下一个将要被执行的任务便是实例化包含viewDidLoad的`UIViewControl。
主队列开始执行viewDidLoad。
dispatch_async闭包添加至全局队列并在稍后被执行。
向全局队列添加dispatch_async闭包后viewDidLoad函数继续运行,主线程继续其剩余的任务。与此同时全局队列是并发性的处理它的任务的。可被并发执行的闭包将以FIFO的顺序添加至全局队列。
通过dispatch_async添加的闭包开始执行。
dispatch_async闭包执行结束,并且所有的NSLog语句都已被显示在控制台上。
在这个例子中,第二个NSLog语句执行后第一个NSLog语句才执行。这种情况并不是每次都会发生的--这取决于硬件在给定的时间内所处理的工作,并且你对于哪个语句会先被执行一无所知且毫无控制权。没准“第一个”NSLog就会作为第一个log出现。
Where to Go From Here?
在这篇教程中,你学会了如何让你的代码具有线程安全性和如何在CPU高密度处理多个任务的时候获取主线程的响应。
你可以从这里下载GooglyPuff的完整代码,在下一节教程中你将会继续在这个工程中进行修改。
假如你打算优化你的App,我觉得你真的该使用Instruments中的Time Profile. 具体教程请查看这篇How To Use Instruments。
首先要有一款翻墙软件,
1.到http://developer.android.com/下载android studio
2.下载成后按照官方推荐的配置安装,需要翻墙才能下载各种工具。
3.安装好后打开一个已存在并且编译能过的项目,然后run,但是我遇到的情况是没有识别出我的android的手机,设备列表是空的,报错是google usb driver not compatible with mac os
4.设置adb的环境变量
1 cd ~:进入用户目录2 vi .bash_profile:创建.bash_profile文件
3 export PATH=${PATH}:/xxx/android-sdk-macosx/tools:/xxx/android-sdk-macosx/platform-tools:导入tools和platform-tools的路径,/xxx/android-sdk-macosx/tools–它指向android SDK的tools目录
4 执行如下命令:source ./.bash_profile :使文件生效
5 重启terminal:执行adb进行测试
5.需要将vid找到
- -----------------
- Spreadtrum phone:
- Product ID: 0x5d04
- Vendor ID: 0x1782
- ...
- Serial Number: T619
- ...
- -----------------
2. 终端进入/Users/user/.android,应该有一个adb_usb.ini文件,如果没有就创建,然后将上面的Vender ID “0x1782”单独一行加入到该文件,然后退出。
- # ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.
- # USE 'android update adb' TO GENERATE.
- # 1 USB VENDOR ID PER LINE.
- 0x1782
3. 重启adb server进程。
- adb kill-server
- adb start-server
- -MacBook-Pro:.android user$ adb devices
- List of devices attached
self.orderCost.text = [NSStringstringWithFormat:@"%.1f元",self.order.cost.floatValue];
%.1f 表示小数点一位,%.2f 表示小数点2位,依次类推.格式定义
The format specifiers supported by the NSString formatting methods and CFString formatting functions follow the IEEE printf specification; the specifiers are summarized in Table 1. Note that you can also use the “n$” positional specifiers such as %1$@ %2$s. For more details, see the IEEE printf specification. You can also use these format specifiers with the NSLog function.
平台依赖
Mac OS X uses several data types—NSInteger, NSUInteger,CGFloat, and CFIndex—to provide a consistent means of representing values in 32- and 64-bit environments. In a 32-bit environment, NSInteger and NSUInteger are defined as int and unsigned int, respectively. In 64-bit environments, NSInteger and NSUInteger are defined as long and unsigned long, respectively. To avoid the need to use different printf-style type specifiers depending on the platform, you can use the specifiers shown in Table 2. Note that in some cases you may have to cast the value.
The following example illustrates the use of %ld to format an NSInteger and the use of a cast.
2
printf("%ld\n", (long)i);
In addition to the considerations mentioned in Table 2, there is one extra case with scanning: you must distinguish the types for float and double. You should use %f for float, %lf for double. If you need to use scanf (or a variant thereof) with CGFloat, switch to double instead, and copy the double to CGFloat.
2
3
4
double tmp;
sscanf (str, "%lf", &tmp);
imageWidth = tmp;
It is important to remember that %lf does not represent CGFloat correctly on either 32- or 64-bit platforms. This is unlike %ld, which works for long in all cases.
5.1之后的跳转设置都变了,最新的有效方法
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
//如果点击打开的话,需要记录当前的状态,从设置回到应用的时候会用到
[[UIApplication sharedApplication] openURL:url];
}
下面是这个类的一些功能:
1.设置icon上的数字图标
//设置主界面icon上的数字图标,在2.0中引进, 缺省为0
[UIApplicationsharedApplication].applicationIconBadgeNumber = 4;
2.设置摇动手势的时候,是否支持redo,undo操作
//摇动手势,是否支持redo undo操作。
//3.0以后引进,缺省YES
[UIApplicationsharedApplication].applicationSupportsShakeToEdit =YES;
3.判断程序运行状态
//判断程序运行状态,在2.0以后引入
/*
UIApplicationStateActive,
UIApplicationStateInactive,
UIApplicationStateBackground
*/
if([UIApplicationsharedApplication].applicationState ==UIApplicationStateInactive){
NSLog(@"程序在运行状态");
}
4.阻止屏幕变暗进入休眠状态
//阻止屏幕变暗,慎重使用,缺省为no 2.0
[UIApplicationsharedApplication].idleTimerDisabled =YES;
(慎重使用本功能,因为非常耗电)
5.显示联网状态
//显示联网标记 2.0
[UIApplicationsharedApplication].networkActivityIndicatorVisible =YES;
6.在map上显示一个地址
NSString* addressText =@"1 Infinite Loop, Cupertino, CA 95014";
// URL encode the spaces
addressText = [addressTextstringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSString* urlText = [NSStringstringWithFormat:@"http://maps.google.com/maps?q=%@", addressText];
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:urlText]];
7.发送电子邮件
NSString *recipients =@"mailto:first@example.com?cc=second@example.com,third@example.com&subject=Hello from California!";
NSString *body =@"&body=It is raining in sunny California!";
NSString *email = [NSStringstringWithFormat:@"%@%@", recipients, body];
email = [emailstringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:email]];
8.打电话到一个号码
// Call Google 411
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"tel://8004664411"]];
9.发送短信
// Text to Google SMS
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"sms://466453"]];
10.打开一个网址
// Lanuch any iPhone developers fav site
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"http://itunesconnect.apple.com"]];
在java中使用extends关键字完成类的继承关系,操作格式如下:
class 父类{}
class 子类 extends 父类{}
继承父类的功能,扩展子类的所有功能。
- class Person{
- private String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class student extends Person{
- //此处不添加任何代码
- }
- public class ExtDemo02{
- public static void main(String args[]){
- Student stu = new Student();
- stu.setName("张三");
- set.setAge(30);
- System.out.println(stu.getName()+stu.getAge());
- }
- }
子类有时候也称之为派生类。
继承的进一步研究
子类对象的实例化过程
实例化子类对象:
1.先调用父类的构造->调用父类构造
2.再调用子类构造->调用子类构造
实例:
InstanceDemo.java
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
//输出:父类Person中的构造
子类Student中的构造
实际上此时隐含在Student的构造方法的第一行有以下代码:
super(); //调用父类的构造函数
即代码相当于:
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- super();
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
注意点:在方法覆写时必须考虑到权限问题,即:被子类覆写的方法不能拥有比父类方法更加严格的访问权限。
访问权限一共有四种,现在只讲三种:
●private:只能被类内访问,权限最第
●default:相当于什么都不声明
●public:最大的访问权限
●大小关系:private<default<public
如果降低了访问权限时如Person使用public而Student使用default时,会产生以下错误:
print() in Student cannot override print() in Person; attempting to assign weaker access privileges; was public void print()
//表示错误的,降低了权限
子类会自动调用覆写的方法,但此时如果非要调用父类中的方法的话,使用super()关键字。
如:Student构造方法调用父类的方法可以进行以下修改:
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- public void print(){
- System.out.println("just a moment!");
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- super.print();
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
---------------------------------------------------------
问题:如果现在在父类中使用private 关键字声明了一个方法,那么在子类中使用default权限算是覆写吗?
答 :不是覆写.
------------------------------------------------------
- class Person{
- private String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- private void print(){
- System.out.println("Person------>void print()");
- }
- }
- class Student extends Person{
- private String school;
- void print(){
- System.out.println("Student-------->void print()");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- stu.print();
- }
- }
输出:Student---------->void print()
此时,方法没有被覆写,而是相当于在子类中又重新定义了一个新的方法出来。
3.3 属性的覆写
在子类中声明了父类同名的属性。
- class Person{
- public String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- private void print(){
- System.out.println("Person------>void print()");
- }
- }
- class Student extends Person{
- public String name;
- void print(){
- System.out.println("父类中的属性:"+super.info);
- System.out.println("子类中的属性:"+this.info);
- }
- }
- public class OverrideDemo{
- public static void main(String args[]){
- Student stu = new Student();
- stu.print();
- }
- }
3.4方法的覆写与的方法的重载
overloading overrideing
方法名称相同参数的类型或个数不同 方法名称参数的类型返回值类型全部相同
对权限没有要求 被覆写的方法不能拥有更严格的权限
发生在一个类中 发生在继承类中
3.5 super关键字
表示从子类调用父类中的指定操作,例如调用属性,方法,构造等。因为在子类实例化的时候会默认调用父类中的无参构造,如果现在希望调用有参构造方法,可以使用super方法传递参数。
- class Person{
- private String name;
- private int age;
- public Person(String name,int age){
- this.name = name;
- this.age = age;
- System.out.println("父雷Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(String name,int age,String school){
- super(name,age);
- this.school = school;
- System.out.println("子类student中的构造方法");
- }
- }
- public class OverrideDemo{
- public static void main(String args[]){
- Student stu = new Student("张三",30,"山东大学");
- }
- }
当子类调用无参构造方法时,父雷中不存在无参构造方法,此时就会报错。因为子类会在无参构造方法第一行隐含调用super方法,解决方法为:父类定义一个无参构造方法,或使用super调用父类存在参数的方法。
不管任何时候,子类实例化时候先去调用父类中的构造方法,默认调用无参构造。
总结:
使用 super 调用无参方法
1.建立含无参构造的类。
2.建立含无参构造子类,继承父类。
3.子类中构造第一行添加或不添加super
使用 super 调用含参数的方法
1.建立含参构造的类。
2.建立含参子类(覆写构造函数),继承父类。
3.子类中构造第一行添加super(内部参数同父类构造方法)
this与super的区别
this super
属性访问:访问本类中的属性,如果本类没有这些属性则从父类中查找。 属性访问:访问父类中的属性
方法:访问本类中的方法,如果本类没有则从父类中查找。 方法:直接访问父类中的方法
调用构造:调用本类构造,必须放在构造方法首行。 调用构造:调用父类构造,必须放在子类构造方法的首行。
特殊:表示当前对象。 特殊:无此概念
对于this和super本身都可以调用构造方法,而且调用的时候都必须放在构造方法首行,所以两个关键字不能同时出现。
疑问:如果在构造方法中(子类)使用了this,那么是不是就不调用父类中的构造方法?
答:子类中不可能同时都调用this()方法,所以默认仍会调用父类的构造方法。
简单单向链表
- class Node{
- private String data; //存储当前节点内容
- private Node next=null; //存储下一下节点
- public Node(String data){
- this.setDate(data);
- }
- public void setDate(String data){
- this.data = data;
- }
- public void setNext(Node next){
- this.next = next;
- }
- public String getDate(){
- return this.data;
- }
- public Node getNext(){
- return this.next;
- }
- }
- public class LinkDemo01
- {
- public static void main(String args[]){
- Node n1 = new Node("节点-A");
- Node n2 = new Node("节点-B");
- Node n3 = new Node("节点-C");
- Node n4 = new Node("节点-D");
- n1.setNext(n2);
- n2.setNext(n3);
- n3.setNext(n4);
- printNode(n1);
- }
- public static void printNode(Node node){
- System.out.println(node.getDate());
- if(node.getNext()!=null){
- printNode(node.getNext());
- }
- }
- }
单向链表整合内部类
- class Link
- {
- class Node
- {
- private String data;
- private Node next=null;
- public Node(String data){
- this.setData(data);
- }
- public void setData(String data){
- this.data = data;
- }
- public void setNext(Node next){
- this.next = next;
- }
- public String getData(){
- return this.data;
- }
- public Node getNext(){
- return this.next;
- }
- public void add(Node node){
- if(this.next==null){
- this.next = node;
- }else{
- this.next.add(node);
- }
- }
- public void print(){
- if(this.next==null){
- System.out.println(this.getData());
- }else{
- System.out.println(this.getData());
- this.next.print();
- }
- }
- public boolean search(String data){//内部搜索方法
- if(data.equals(this.data)){
- return true;
- }else{//向下继续判断
- if(this.next!=null){
- return this.next.search(data);
- }else{
- return false;
- }
- }
- }
- public void delete(Node previous,String data){
- if(data.equals(this.data)){
- previous.next = this.next;//空出当前节点
- }else{
- if(this.next!=null){
- this.next.delete(this,data); //继续查找
- }
- }
- }
- }
- private Node root; //根节点
- public void addNode(String data){
- Node newNode = new Node(data); //创建新节点
- if(this.root==null){
- this.root = newNode;
- }else{
- this.root.add(newNode);
- }
- }
- public void printNode(){
- if(this.root!=null){
- this.root.print();//调用Node类中的输出操作
- }
- }
- public boolean contains(String name){
- return this.root.search(name); //调用Node类的查找方法
- }
- public void deleteNode(String data){
- if(this.contains(data)){ //判断节点是否存在
- if(this.root.getData().equals(data)){
- this.root = this.root.next; //修改根节点
- }else{
- this.root.next.delete(root,data); //把下一个节点的前节点和数据一起传入进去
- }
- }
- }
- }
- public class LinkDemo02
- {
- public static void main(String args[]){
- Link l = new Link();
- l.addNode("节点-A");
- l.addNode("节点-B");
- l.addNode("节点-C");
- l.addNode("节点-D");
- //增加之后的内容
- l.printNode();
- //判断是否包含节点
- System.out.println(l.contains("节点-X"));
- l.deleteNode("节点-B");
- //删除之后的内容
- l.printNode();
- }
- }
总结:
1.类的职能不同,LinkDemo01是基础链表类,而LinkDemo02的内部类为基础链表类,外部类为链表操作类。
2.基础链表类中存在一个this.next指向下一个链表对象。
构造方法私有化及单态模式
构造方法封装
类的封装性不光体现在对属性的封装上,实际上方法也是可以被封装的,当然在方法封装中也包含了对构造方法的封装。例如:以下的代码,就是对构造方法进行了封装。- class Singleton{
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo02{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = new Singleton() ; // 错误,无法实例化对象
- }
- };
- class Singleton{
- Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo03{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- }
- };
正常情况下,instance属性只能通过Singleton类的实例化对象才可以进行调用,如果在没有实例化对象的时候依然可以取得instance,则就需要将instance声明成static访问类型,因为使用static声明的变量,可以直接使用类名称进行访问。
- class Singleton{
- static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo04{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = Singleton.instance ; // 取得实例化对象
- s1.print() ; // 调用方法
- }
- };
- class Singleton{
- private static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- public static Singleton getInstance(){ // 通过静态方法取得instance对象
- return instance ;
- }
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo05{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = Singleton.getInstance() ; // 取得实例化对象
- s1.print() ; // 调用方法
- }
- };
程序的意义?
以上的代码不如直接实例化操作来的直接,为什么还要这样去做呢?
如果现在产生了本类的三个对象。
- class Singleton{
- private static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- public static Singleton getInstance(){ // 通过静态方法取得instance对象
- return instance ;
- }
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo05{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- Singleton s2 = null ; // 声明对象
- Singleton s3 = null ; // 声明对象
- s1 = Singleton.getInstance() ; // 取得实例化对象
- s2 = Singleton.getInstance() ; // 取得实例化对象
- s3 = Singleton.getInstance() ; // 取得实例化对象
- s1.print() ; // 调用方法
- s2.print() ; // 调用方法
- s3.print() ; // 调用方法
- }
- };
不管外部声明了多少个Singleton的对象,但是最终结果都是通过getInstance()方法取得的实例化对象,也就是说,此时S1、S2、S3实际上都使用了一个对象的引用:instance
那么这样的设计在设计模式上来讲,属于单态设计模式(单例设计模式):Singleton
如果现在不希望一个类产生过多的对象的话,则就必须使用单态设计模式,而且使用单态设计模式在以后的JAVA学习中会经常碰到,因为在JAVA的支持的类库中,大量的采用了此种设计模式。
所谓的单态就是在入口处(构造方法)限制了对象的实例化操作。
单态设计模式的意义
实际上这样的应用,读者应该早就能有所了解了,读者应该都会很清楚在windows中有一个回收站的程序,除了桌面上的回收站之外,每个硬盘上都有一个回收站,实际上每个硬盘的回收站和桌面上的回收站都是同一个,那么也就是说在整个操作系统上只有一个回收站实例,各个地方只是引用此实例而已。
总结:
单例模式步骤:
1.将类中的构造方法私有化。2.在内部通过静态私有化属性实例化对象。
3.创建公共函数返回静态私有化属性。
由于我们家app需要大量跳转到外部,所以想写一个工具类来解决这个问题:
首先,我们需要的是
1.打电话,弹出一个提示框,是否要拨打电话。
2.打开外部浏览器。
3.打开到系统设置的更新页面
4.打开到系统设置的wifi选择页面
5.打开到系统设置的声音页面
代码写好了 原地址:https://github.com/sunqichao/sqcjump ,接口部分
typedef enum : NSInteger{
//打开设置中的图片设置
SQCJumpTophotos,
//打开设置中的语音助手
SQCJumpToSiri,
//打开设置中的软件更新
SQCJumpToSoftWareUpdate,
//打开设置中的wifi设置页
SQCJumpToWiFi,
//打开设置中的声音设置页
SQCJumpToSounds
}SQCJumpType;
@interface SQCJumpOut : NSObject
+ (BOOL)jumpToOSDetailSet:(SQCJumpType)type;
/**
* 打电话 两种方式,一种是弹出一个alert提示是否要打电话,一种是直接打电话
*
* @param phone 电话号码
* @param isAppear 是否弹出alert
*
* @return
*/
+ (BOOL)callWithNumber:(NSString *)phone appearAlert:(BOOL)isAppear;
/**
* 打开外部浏览器
*
* @param URL 地址
*
* @return
*/
+ (BOOL)openWebSite:(NSString *)URL;
phpMyAdmin错误 缺少 mysqli 扩展。请检查 PHP 配置 的解决方案
phpMyAdmin 缺少 mysqli 扩展。请检查 PHP 配置 的解决方案:缺少 mysqli 扩展。请检查 PHP 配置。
打开你的php.ini->一般在C:WINDOWS目录下。找到
代码如下:
;extension=php_msql.dll;extension=php_mssql.dllextension=php_mysql.dllextension=php_mysqli.dll
需要开启哪个扩展,就把这一行前面的分号去掉就行,注意要重启Apache
或者IIS解决步骤:1.看看php的ext目录中是否有php_mysqli.dll文件如果有,继续下面的步骤。没有的话重新装个php2.打开php.ini,找到
代码如下:
;extension=php_mysqli.dll
把前面的分号";"去掉,改为
代码如下:
extension=php_mysqli.dll
3.找到
代码如下:
;extension_dir="./ext"
把前面的分号";"去掉,改为(相对于php的安装路径的ext目录)
代码如下:
extension_dir = "e:\php\ext"
e:php为php的存放目录
4.把php.ini拷贝到c:windows下5.把libmysql.dll复制到Windows下面的System32目录(这个很关键)6.重新启动IIS(不是网站)
cmd下运行 iisreset /RESTART
如果上面方法还不行,可参考下面方法操作
第一步,输出 phpinfo() 看看 MySQL 模块是否成功装载,如果成功装载说明你见鬼了,否则看第二步,
IIS下phpinfo()无mysql模块是何原因?原先用的php是.msi安装的,后来全部重来了一次,换成压缩包形式安装的。又照着配置了php.ini,复制到windows,复制libmysql.dll到system32。之后测试成功。。。第二步,查看
extension_dir 的设置是否正确第三步,查看是否把 libmysql.dll 文件复制到 c:windowssystem32 下
1、没有正确安装Mysql数据库,在系统服务中Mysql相关的服务没有启动 (请查看正确安装Mysql的方法)
2、在系统的 system32(C:windowssystem32) 目录下缺少
libmysql.dll文件,解决方法是找到php目录下的libmysql.dll,并将libmysql.dll复制到C:windowssystem32目录中,然后重新启动Web服务。
3、在C:windows目录下的php.ini文件中,没有将“;extension=php_mysql.dll”中的前面一个“;”去掉,所以不能使用相应功能,解决方法是打开php.ini文件
4、Mysql目录没有读取权限,正确的目录权限如下:administrator 完全控制system
完全控制user 读取加运行其他的用户权限全部删除(也可保留,但安全性不高,建议删除),然后重启MYsql服务和Web服务。
当你打开phpmyadmin时可能会报这样的一个错误,
缺少 mysqli 扩展。请检查 PHP 配置。 <a href="Documentation.html#faqmysql"
target="documentation"><img class="icon"
src="./themes/original/img/b_help.png" width="11" height="11" alt="文档"
title="文档" /></a>
你可以按照以下这几种方法进行检查:
1.检查php.ini ;extension=php_mysqli.dll是不是已经启用 也就是去掉前面的;
2.检查php.ini extension_dir 地址是不是指向了php目录的ext目录下这步是关键
我就是处在这一步的问题上 我把 extension_dir = "e:\www\php54\ext" 设置成了自己的目录 就可以了
3.检查lib_mysql.dll有没有拷贝到windows目录下 这个dll文件有多种形式的
有没加下划线的libmysql_d.dll 因此要注意自己看
4.检查php安装目录ext目录下,php_mysqli.dll文件是不是存在 网上也说明 最好使用zip包解压
不会出现文件丢失
5.查看php详细页面<?php phpinfo();
?>,看看mysql和mysqli是不是已经启动这一步就是检查你有没有开启mysqli的没有开启的话
你查找mysqli关键字是无法再php详细页面看到的
最后要注意!!!!重启Apache
原文地址:http://www.cppblog.com/colorful/archive/2012/04/29/173122.html
最近刚开始接触Linux,在虚拟机中装了个Ubuntu,当前的版本是Ubuntu 11.10,装好后自然少不了安装一些软件,在设置了软件的源后,就开始了 sudo apt-get install,结果出现了下面的Unable to locate package错误:
- E: Unable to locate package mysql-server
- E: Unable to locate package mysql-client
这叫一个郁闷啊,出师不利,不带这么吓唬刚玩Ubuntu的小朋友吧~于是赶紧找资料,又回顾下前面的操作,最后发现问题出在执行sudo apt-get install之前更换了软件源,但是却忘了update下了,于是执行下面的命令:
- sudo apt-get update
等上面命令执行完后,再执行sudo apt-get install就可以了!其实错误信息已经很明确了,Unable to locate packet就是无法找到包嘛,那还不赶紧sudo apt-get update下!
附另一篇文章:
原文地址:http://blog.chinaunix.net/uid-22002627-id-3475650.html
碰到这个问题后找到这个帖子就转了过来 当用apt-get更新软件包时常出现错误提示Unable to locate package update, 尤其是在ubuntu server上,解决方法是: 先更新apt-get #sudo apt-get update 执行完后,问题就解决了。 继续更新: #sudo apt-get upgrade 然后就可以安装apache: #sudo apt-get install apache2 等就可以了 安装mysql命令:sudo apt-get install mysql-server mysql-client
今天上传新版本遇到这个问题,
- The u option must have a non-empty value"
- "The password option must have a non-empty value"
正确的解决方案是升级XCode 到7.1,然后再上传就可以了
本文主要是说一些iOS9适配中出现的坑,如果只是要单纯的了解iOS9新特性可以看瞄神的开发者所需要知道的 iOS 9 SDK 新特性。9月17日凌晨,苹果给用户推送了iOS9正式版,随着有用户陆续升级iOS9,也就逐渐的衍生出了一系列的问题,笔者也在赶忙为自己维护的App做适配,本文写的一些坑基本都是亲身体验了。
一、NSAppTransportSecurity
iOS9让所有的HTTP默认使用了HTTPS,原来的HTTP协议传输都改成TLS1.2协议进行传输。直接造成的情况就是App发请求的时候弹出网络无法连接。解决办法就是在项目的info.plist 文件里加上如下节点:
NSAppTransportSecurity - NSAllowsArbitraryLoads
这个子节点的意思是:是否允许任性的加载?! 设为YES的话就将禁用了AppTransportSecurity转而使用用户自定义的设置,这个问题就解决了。
上面说是苹果限制了HTTP协议,但是也并不是说所有的HTTPS都能完美适配iOS9了。
举个栗子,从app内起webView加载https的网页。新建个项目写几行起网页的代码
- (void)loadView{
UIWebView *web = [[UIWebView alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.view = web;
}
- (void)viewDidLoad {
[
super
viewDidLoad];
UIWebView *web = (UIWebView *)self.view;
//董铂然
NSURL *url = [NSURL URLWithString:@
"https://github.com/"
];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[web loadRequest:request];
}
中间的url就是我们想要加载的https地址,用https://baidu.com/ 和 https://github.com/ 分别试一下,结果不同
github的网页能打开,百度的网页打不开,下面打印了一行log
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
原因是苹果的官方资料说首先必须要基于TLS 1.2版本协议。然后证书的加密的算法还需要达到SHA256或者更高位的RSA密钥或ECC密钥,如果不符合,请求将被中断并返回nil.
在浏览器中是可以直接查看这个网站的加密算法的,先点绿锁再点证书信息。
从右边两张图可以看出,github带RSA加密的SHA-256符合苹果的要求,所以才可以展示。
针对百度的情况可以在info.plist中配置如下,如果网站引用的比较多应该是需要针对每个网站进行配置。
NSAppTransportSecurity,NSExceptionDomains,NSIncludesSubdomains,NSExceptionRequiresForwardSecrecy,NSExceptionAllowInsecureHTTPLoads 写在下面便于复制。
其中的ForwardSecrecy理解为超前的密码保护算法,在官方资料里有写,一共是11种。配置完毕百度可以访问。
二、Bitcode
bitcode的理解应该是把程序编译成的一种过渡代码,然后苹果再把这个过渡代码编译成可执行的程序。bitcode也允许苹果在后期重新优化我们程序的二进制文件,有类似于App瘦身的思想。
用了xcode7的编译器编译之前没问题的项目可能会出现下列报错。
XXXX’ does not contain bitcode. You must rebuild it
with
bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode
for
this
target.
for
architecture arm64
问题的原因是:某些第三方库还不支持bitcode。要不然是等待库的开发者升级了此项功能我们更新库,要不就是把这个bitcode禁用。
禁用的方法就是找到如下配置,选为NO.(iOS中bitcode是默认YES,watchOS中bitcodes是不让改的必须YES。)
三、设置信任
这一条只和企业级应用或inhose 有关,和AppStore渠道的应用无关。
在iOS8只是弹出一个窗问你是否需要让手机信任这个应用,但是在iOS9却直接禁止,如果真的想信任需要自己去手动开启。类似于Mac系统从未知开发者处下载的dmg直接打不开,然后要到系统偏好设置的安全性与隐私手动打开。 下图展示左边iOS8,右边iOS9
用户需要去 设置---》通用---》描述文件 里面自行添加信任。
这种问题的处理方法也就两种:1.提前周知暂时不要升级iOS9 2.大多是公司员工使用的企业级应用,群发一个指导邮件。
四、字体
iOS8中,字体是Helvetica,中文的字体有点类似于“华文细黑”。只是苹果手机自带渲染,所以看上去可能比普通的华文细黑要美观。iOS9中,中文系统字体变为了专为中国设计的“苹方” 有点类似于一种word字体“幼圆”。字体有轻微的加粗效果,并且最关键的是字体间隙变大了!
所以很多原本写死了width的label可能会出现“...”的情况。
iOS8
iOS9 蛋疼
上面这两张图也可以直观的看出同一个界面,同一个label的变化。
所以为了在界面显示上不出错,就算是固定长度的文字也还是建议使用sizetofit 或者ios向上取整 ceilf() 或者提前计算
CGSize size = [title sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
五、URL scheme
URL scheme一般使用的场景是应用程序有分享或跳其他平台授权的功能,分享或授权后再跳回来。
在iOS8并没有做过多限制,但是iOS9需要将你要在外部调用的URL scheme列为白名单,才可以完成跳转
如果iOS9没做适配 会报如下错误
canOpenURL: failed
for
URL :
"mqzone://qqapp"
- error:
"This app is not allowed to query for scheme mqzone"
具体的解决方案也是要在info.plist中设置 LSApplicationQueriesSchemes 类型为数组,下面添加所有你用到的scheme
六、statusbar
这个还好只是报一个警告,如果就是不管他,也不会出现问题。
: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
以前我们为了能够实时的控制顶部statusbar的样式,可能会在喜欢使用
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]
[[UIApplication sharedApplication]setStatusBarHidden:YES];
但是这么做之前需要将 info.plist 里面加上View controller-based status bar appearance BOOL值设为NO,就是把控制器控制状态栏的权限给禁了,用UIApplication来控制。但是这种做法在iOS9不建议使用了,建议我们使用吧那个BOOL值设为YES,然后用控制器的方法来管理状态栏比如。
- (UIStatusBarStyle)preferredStatusBarStyle
{
return
UIStatusBarStyleLightContent;
}
点进头文件可以验证刚才说法:
@property(readwrite, nonatomic,getter=isStatusBarHidden) BOOL statusBarHidden NS_DEPRECATED_IOS(2_0, 9_0,
"Use -[UIViewController prefersStatusBarHidden]"
);
七、didFinishLaunchingWithOptions
如果运行的时候报下列错误,那就是你的didFinishLaunchingWithOptions写的不对了
***** Assertion failure
in
-[UIApplication _runWithMainScene:transitionContext:completion:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UIApplication.m:3294**
iOS9不允许在didFinishLaunchingWithOptions结束了之后还没有设置window的rootViewController。 也许是xcode7的编译器本身就不支持。
解决的方法当然就是先初始化个值,之后再赋值替换掉
UIWindow *window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
window.rootViewController = [[UIViewController alloc]init];
八、tableView
虽然现在的iOS9已经推送正式版了,但是iOS9使用时还是会感觉到App比以前更加卡顿了,tableView拖动时卡顿显示的最为明显。 并且之前遇到一个bug,原本好的项目用xcode7一编译,tableView刷新出了问题 ,[tableView reloadData]无效 有一行cell明明改变了但是刷新不出来。 感觉可能是这个方法和某种新加的特性冲突了,猜测可能是reloadData的操作被推迟到下一个RunLoop执行最终失效。
解决的方法是,注释[tableView reloadData],改用局部刷新,问题居然就解决了。
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
暂时遇到这些问题,感觉iOS9的出现让所有iOS开发都是菊花一紧,希望苹果这种大刀阔斧做改变,特立独行的风格发展下去以后别和government 产生矛盾,然后公司倒闭 导致开发人员失业,也许是我想多了。预祝所有的iOS都能及时的做好适配改完bug,下个版本一上线,所有问题都解决。
最近提交版本的时候出现以下提示:
Could not make parent directory for: /Users/xxx/.itmstransporter/softwaresupport/bin/Frameworks/ITunesSoftwareService.framework/Resources/ITunesSoftwareServiceConfiguration.xml
Could not configure software support.An exception has occurred: /Users/xxx/.itmstransporter/softwaresupport/bin/Frameworks/ITunesSoftwareService.framework/Resources/ITunesSoftwareServiceConfiguration.xml (No such file or directory)
ERROR ITMS-90168: "The binary you uploaded was invalid."
解决方法如下:
打开终端,输入以下指令:
- $ cd ~/.itmstransporter
- $ rm update_check*
- $ mv softwaresupport softwaresupport.bak
- $ cd UploadTokens
- $ rm *.token
down vote
I encountered the same problem today with the same exact error message when trying to submit our app (using Xcode 7 beta 5) but instead of the instabug.bundle
bit, it was for me TencentOpenApi_IOS_Bundle.bundle
.
I solved the problem by finding the named bundle in the project then - just as the error message suggests - edited the Info.plist
that is in the bundle by removing the CFBundleExecutable
key. The CFBundlePackageType
key was already set to BNDL
so I didn't touch it.
After these changes I did Product > Clean and then had no problem submitting the app to the App store.
I hope this helps.
今天升级Xcode 7.0 bata
发现网络访问失败。
输出错误信息
<code style="padding: 0px; color: inherit; white-space: inherit; background-color: transparent;"><span style="font-family:Comic Sans MS;font-size:18px;">The resource could not be loaded because the App Transport Security policy requires the <span class="hljs-operator"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">use</span> <span class="hljs-keyword" style="color: rgb(133, 153, 0);">of</span> a secure <span class="hljs-keyword" style="color: rgb(133, 153, 0);">connection</span>.</span></span></code>
Google后查证,iOS9引入了新特性App Transport Security (ATS)
。详情:App Transport Security (ATS)
新特性要求App内访问的网络必须使用HTTPS
协议。
但是现在公司的项目使用的是HTTP
协议,使用私有加密方式保证数据安全。现在也不能马上改成HTTPS
协议传输。
最终找到以下解决办法:
- 在Info.plist中添加
NSAppTransportSecurity
类型Dictionary
。 - 在
NSAppTransportSecurity
下添加NSAllowsArbitraryLoads
类型Boolean
,值设为YES
参考:
- App Transport Security support aka apps on iOS 9 don't work #4560
- 711_networking_with_nsurlsession.pdf
以前的项目 放到Xcode7中运行时可能会遇到以下错误:You must rebuild it with bitcode enabled (Xcodesetting ENABLE_BITCODE)
未来Watch应用须包含Bitcode,iOS不强制,但Xcode7默认会开启Bitcode。
如何适配?
方法一:更新library使包含Bitcode,否则会出现以下中的警告;
(
null
): URGENT: all bitcode will be dropped because
‘/Users/myname/Library/Mobile Documents/com~apple~CloudDocs/foldername/appname/GoogleMobileAds.framework/GoogleMobileAds(GADSlot+AdEvents.o)‘
was built without bitcode. You must rebuild it
with
bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode
for
this
target. Note: This will be an error
in
the future.
方法二:关闭Bitcode
工程设置中 buildingsetting 中搜索bitcode 选择no
解决问题!!!!
原文地址:http://blog.csdn.net/wzzvictory/article/details/18413519 感谢原作者
一、NULL
1、声明位置
2、定义
- #undef NULL
- #ifdef __cplusplus
- # if !defined(__MINGW32__) && !defined(_MSC_VER)
- # define NULL __null
- # else
- # define NULL 0
- # endif
- #else
- # define NULL ((void*)0)
- #endif
- # define NULL ((void*)0)
因此,NULL本质上是:(void*)0
3、用处及含义
4、示例
- charchar *string = NULL;
二、nil
1、声明位置
2、定义
- #ifndef nil
- # if __has_feature(cxx_nullptr)
- # define nil nullptr
- # else
- # define nil __DARWIN_NULL
- # endif
- #endif
- # define nil __DARWIN_NULL
- #ifdef __cplusplus
- #ifdef __GNUG__
- #define __DARWIN_NULL __null
- #else /* ! __GNUG__ */
- #ifdef __LP64__
- #define __DARWIN_NULL (0L)
- #else /* !__LP64__ */
- #define __DARWIN_NULL 0
- #endif /* __LP64__ */
- #endif /* __GNUG__ */
- #else /* ! __cplusplus */
- #define __DARWIN_NULL ((void *)0)
- #endif /* __cplusplus */
- #define __DARWIN_NULL ((void *)0)
3、用处及含义
4、示例
- NSString *string = nil;
- id anyObject = nil;
三、Nil
1、声明位置
2、定义
- #ifndef Nil
- # if __has_feature(cxx_nullptr)
- # define Nil nullptr
- # else
- # define Nil __DARWIN_NULL
- # endif
- #endif
3、用处及含义
4、示例
- Class anyClass = Nil;
四、NSNull
1、声明位置
2、定义
- @interface NSNull : NSObject <NSCopying, NSSecureCoding>
- + (NSNull *)null;
- @end
3、用处及含义
4、示例
我们通常初始化NSArray对象的形式如下:- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",nil];
- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",nil,@"foogry"];
- NSArray *arr = [NSArray arrayWithObjects:@"wang",@"zz",[NSNull null],@"foogry"];
五、总结
从前面的介绍可以看出,不管是NULL、nil还是Nil,它们本质上都是一样的,都是(void *)0,只是写法不同。这样做的意义是为了区分不同的数据类型,比如你一看到用到了NULL就知道这是个C指针,看到nil就知道这是个Objective-C对象,看到Nil就知道这是个Class类型的数据。
在Mac上配置adb命令
在Mac OS中使用adb命令时,应进行变量配置,步骤如下:
一、终端中输入 cd ~
二、输入touch .bash_profile 回车
touch:如果没有,则创建文件,如果有,更新一下文件时间
三、输入open -e .bash_profile
open:打开文件
回车后会在TextEdit中打开这个文件(如果未配置过环境变量,应该是个空白文件)。在文件中加如以下内容
export PATH=${PATH}:/Users/sunqichao/worksoft/android-sdk-mac_x86/platform-tools
这是我机器上的路径,具体个人机器路径,可右键点击platform-tools--》显示简介 查看
原文地址:http://www.cocoachina.com/swift/20150608/12025.html
一、解决问题
Swift项目需要使用封装好的Objective-c组件、第三方类库,苹果提供的解决方案能够处理日常大部分需求,但还不能称之为完美,混编过程中会遇到很多问题。本文将Swift兼容Objective-c的问题汇总,以帮助大家更好的使用Swift,内容列表如下:
1. Swift调用Objective-c代码
2. Objective-c调用Swift代码
3. Swift兼容Xib/Storyboard
4. Objective-c巧妙调用不兼容的Swift方法
5. 多Target编译错误解决
6. 第三方类库支持
二、基础混合编程
Swift与Objective-c的代码相互调用,并不像Objective-c与C/C++那样方便,需要做一些额外的配置工作。无论是Swift调用Objective-c还是Objective-c调用Swift,Xcode在处理上都需要两个步骤:
2.1 Swift调用Objective-c代码
Xcode对于Swift调用Objective-c代码,除宏定义外,其它支持相对完善。
2.1.1 使用Objetvie-c的第一步
告诉Xcode、哪些Objective-c类要使用,新建.h头文件,文件名可以任意取,建议采用“项目名-Bridging-Header.h”命令格式。
Tips
Swift之IOS项目,在Xcode6创建类文件,默认会自动选择OS X标签下的文件,这时一定要选择iOS标签下的文件,否则会出现语法智能提示不起作用,严重时会导致打包出错。
2.1.2 第二步,Target配置,使创建的头文件生效
设置Objective-C Bridging Header时,路径要配置正确,例如:创建的名为“ILSwift-Bridging-Header.h”文件,存于ILSwift项目文件夹的根目录下,写法如下:
ILSwift/ILSwift-Bridging-Header.h
当然,在新项目中,直接创建一个Objective-c类,Xcode会提示:
直接选择Yes即可,如果不小心点了其它按钮,可以按照上面的步骤一步一步添加。
2.2 Objective-c调用Swift代码
2.2.1 Objective-c调用Swift代码两个步骤
第一步告诉Xcode哪些类需要使用(继承自NSObject的类自动处理,不需要此步骤),通过关键字@objc(className)来标记
import UIKit
@objc(ILWriteBySwift)
class ILWriteBySwift {
var
name: String!
class func newInstance() -> ILWriteBySwift {
return
ILWriteBySwift()
}
}
第二步引入头文件,Xcode头文件的命名规则为
$(SWIFT_MODULE_NAME)-Swift.h
示例如下:
#import "ILSwift-Swift.h"
Tips
不清楚SWIFT_MODULE_NAME可通过以下步骤查看
2.2.2找不到$(SWIFT_MODULE_NAME)-Swift.h
1.遇到此问题可按以下步骤做常规性检查
确定导入SWIFT_MODULE_NAME)-Swift.h头文件的文件名正确
SWIFT_MODULE_NAME)-Swift.h在clean后没有重新构建,执行Xcode->Product->Build
2.头文件循环
在混合编程的项目中,由于两种语言的同时使用,经常会出现以下需求:在Swift项目中需要使用Objectvie-c写的A类,而A类又会用到Swift的一些功能,头文件的循环,导致编译器不能正确构建$(SWIFT_MODULE_NAME)-Swift.h,遇到此问题时,在.h文件做如下处理
//删除以下头文件
//#import "ILSwift-Swift.h"
//通过代码导入类
@class ILSwiftBean;
在Objevtive-c的.m文件最上面,添加
#import "ILSwift-Swift.h"
出现Use of undecalared identifier错误或者找不到方法,如下:
引起的原因有以下几种可能:
使用的Swift类不是继承自NSObject,加入关键字即可
SWIFT_MODULE_NAME)-Swift.h没有实时更新,Xcode->Product->Build
此Swift文件中使用了Objective-c不支持的类型或者语法,如private
出现部分方法找不到的问题,Xcode无智能提示:
此方法使用了Objective-c不支持的类型或者语法
苹果官方给出的不支持转换的类型
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
三、Xib/StoryBoard支持
Swift项目在使用Xib/StoryBoard时,会遇到两种不同的问题
Xib:不加载视图内容
Storyboard:找不到类文件
3.1 Xib不加载视图内容
在创建UIViewController时,默认选中Xib文件,在Xib与类文件名一致时,可通过以下代码实例化:
let controller = ILViewController()
运行,界面上空无一物,Xib没有被加载。解决办法,在类的前面加上@objc(类名),例如:
import UIKit
@objc(ILViewController)
class ILViewController: UIViewController {
}
Tips:
StoryBoard中创建的UIViewController,不需要@objc(类名)也能够保持兼容
3.2 Storyboard找不到类文件
Swift语言引入了Module概念,在通过关键字@objc(类名)做转换的时候,由于Storboard没有及时更新Module属性,会导致如下两种类型错误:
3.2.1 用@objc(类名)标记的Swift类或者Objective-c类可能出现错误:
2015-06-02 11:27:42.626 ILSwift[2431:379047] Unknown class _TtC7ILSwift33ILNotFindSwiftTagByObjcController in Interface Builder file.
解决办法,按下图,选中Module中的空白,直接回车
3.2.2 无@objc(类名)标记的Swift类
2015-06-02 11:36:29.788 ILSwift[2719:417490] Unknown class ILNotFindSwiftController
in
Interface Builder file.
解决办法,按下图,选择正确的Module
3.产生上面错误的原因: 在设置好Storyboard后,直接在类文件中,添加或者删除@objc(类名)关键字,导致Storyboard中 Module属性没有自动更新,所以一个更通用的解决办法是,让Storyboard自动更新Module,如下:
3.3 错误模拟Demo下载
为了能够让大家更清楚的了解解决流程,将上面的错误进行了模拟,想动手尝试解决以上问题的同学可以直接下载demo
四、Objective-c巧妙调用不兼容的Swift方法
在Objective-c中调用Swift类中的方法时,由于部分Swift语法不支持转换,会遇到无法找到对应方法的情况,如下:
import UIKit
enum HTTPState {
case
Succed, Failed, NetworkError, ServerError, Others
}
class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void
in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void
in
callback(state: HTTPState.Succed)
})
})
}
}
对应的$(SWIFT_MODULE_NAME)-Swift.h文件为:
SWIFT_CLASS(
"_TtC12ILSwiftTests13ILHTTPRequest"
)
@interface ILHTTPRequest : NSObject
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
从上面的头文件中可以看出,方法requestLogin使用了不支持的Swift枚举,转换时方法被自动忽略掉,有以下两种办法,可以巧妙解决类似问题:
4.1 用支持的Swift语法包装
在Swift文件中,添加一个可兼容包装方法wrapRequestLogin,注意此方法中不能使用不兼容的类型或者语法
import UIKit
enum HTTPState: Int {
case
Succed = 0, Failed = 1, NetworkError = 2, ServerError = 3, Others = 4
}
class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void
in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void
in
callback(state: HTTPState.Succed)
})
})
}
class func wrapRequestLogin(userName: String, password: String, callback: (state: Int) -> (Void)) {
self.requestLogin(userName, password: password) { (state) -> (Void)
in
callback(state: state.rawValue)
}
}
}
对应的$(SWIFT_MODULE_NAME)-Swift.h文件为:
SWIFT_CLASS(
"_TtC12ILSwiftTests13ILHTTPRequest"
)
@interface ILHTTPRequest : NSObject
+ (void)wrapRequestLogin:(NSString * __nonnull)userName password:(NSString * __nonnull)password callback:(void (^ __nonnull)(NSInteger))callback;
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
此时,我们可以在Objective-c中直接使用包装后的方法wrapRequestLogin
4.2 巧妙使用继承
使用继承可以支持所有的Swift类型,主要的功能在Objective-c中实现,不支持的语法在Swift文件中调用,例如,ILLoginSuperController做为父类
@interface ILLoginSuperController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
- (IBAction)loginButtonPressed:(id)sender;
@end
////////////////////////////////////////////////////////////////
@implementation ILLoginSuperController
- (IBAction)loginButtonPressed:(id)sender
{
}
@end
创建Swift文件,继承自ILLoginSuperController,在此Swift文件中调用那些不支持的语法
import UIKit
class ILLoginController: ILLoginSuperController {
override func loginButtonPressed(sender: AnyObject!) {
ILHTTPRequest.requestLogin(self.userNameField.text, password: self.passwordField.text) { (state) -> (Void)
in
//具体业务逻辑
}
}
}
五、多Target编译错误解决
在使用多Target时,会出现一些编译错误
5.1 Use of undeclared type
此类错误,是因为当前运行的Target找不到必须编译文件。将文件添加到Target即可,如下支持ILSwiftTests Target,选中ILSwiftTests前的复选框即可
5.2 does not have a member named
此类错误可能由于如下两种原因引起,解决办法同上:
1.此方法来自父类,父类文件没有加入到当前Target
2.此方法来自扩展,扩展没有加入到当前Target
Tips
如果检查发现,所有的类文件都已经准确添加到Target中,但编译还是不通过,此时着重检查桥接文件是否正确设置,是否将相应的头文件加入到了桥接文件中。如无特别要求,建议将所有Target的桥接文件全都指向同一文件。关于桥接文件的设置,请参考2.1
六、第三方类库支持
Swift项目取消了预编译文件,一些第三方Objective-c库没有导入必要框架(如UIKit)引起编译错误
6.1 Cocoapods找不到.o文件
在使用了Cocoapods项目中,会出现部分类库的.o文件找不到,导致此种错误主要是以下两种问题:
类库本身存在编译错误
Swift没有预编译,UIKit等没有导入
将此库文件中的代码文件直接加到项目中,编译,解决错误。
6.2 JSONModel支持
在Swift中可以使用JSONModel部分简单功能,一些复杂的数据模型建议使用Objevtive-c
import UIKit
@objc(ILLoginBean)
public class ILLoginBean: JSONModel {
var
userAvatarURL: NSString?
var
userPhone: NSString!
var
uid: NSString!
}
Tips
在Swift使用JSONModel框架时,字段只能是NSFoundation中的支持类型,Swift下新添加的String、Int、Array等都不能使用
6.3 友盟统计
Swift项目中引入友盟统计SDK会出现referenced from错误:
解决办法,找到Other Linker Flags,添加-lz
七、综述
现在大部分成熟的第三方框架都是使用Objective-c写的,开发时不可避免的涉及到两种语言的混合编程,期间会遇到很多奇怪的问题。因为未知才有探索的价值,Swift的简洁快速,能够极大的推进开发进度。所以从今天开始,大胆的开始尝试。
本文转载,原文地址:http://www.cocoachina.com/ios/20150609/12072.html
原文 Grand Central Dispatch Tutorail for Swift: Part 1/2
原文作者:Bjrn Olav Ruud
译者:Ethan Joe
尽管Grand Central Dispatch(以下简称为GCD)已推出一段时间了,但并不是所有人都明白其原理;当然这是可以理解的,毕竟程序的并发机制很繁琐,而且基于C的GCD的API对于Swift的新世界并不是特别友好。
在接下来的两节教程中,你将学习GCD的输入 (in)与输出 (out)。第一节将解释什么是GCD并了解几个GCD的基础函数。在第二节,你将学习几个更加进阶的GCD函数。
Getting Started
GCD是libdispatch的代名词,libdispatch代表着运行iOS与OS X的多核设备上执行并行代码的官方代码库。它经常有以下几个特点:
GCD通过将高代价任务推迟执行并调至后台运行的方式来提升App的交互速度。
GCD提供比锁与多线程更简单的并发模型,以此来避免一些由并发引起的Bug。
为了理解GCD,你需要明白一些与线程、并发的相关的概念。这些概念间有着细微且模糊的差别,所以在学习GCD前请简略地熟悉一下这些概念。
连续性 VS 并发性
这些术语用来描述一些被执行的任务彼此间的关系。连续性执行任务代表着同一时间内只执行一个任务,而并发性执行任务则代表着同一时间内可能会执行多个任务。
任务
在这篇教程中你可以把每个任务看成是一个闭包。 事实上,你也可以通过函数指针来使用GCD,但在大多数情况下这明显有些麻烦。所以,闭包用起来更简单。
不知道什么是Swift中的闭包?闭包是可被储存并传值的可调用代码块,当它被调用时可以像函数那样包含参数并返回值。
Swift中的闭包和Objective-C的块很相近,它们彼此间是可以相互交替的。这个过程中有一点你不能做的是:用Objective-C的块代码去交互具有Swift独有属性属性的闭包,比如说具有元组属性的闭包。但是从Swift端交互Objective-C端的代码则是毫无障碍的,所以无论何时你在文档中看到到的Objective-C的块代码都是可用Swift的闭包代替的。
同步 VS 异步
这些术语用来描述当一个函数的控制权返回给调用者时已完成的工作的数量。
同步函数只有在其命令的任务完成时才会返回值。
异步函数则不会等待其命令的任务完成,即会立即返回值。所以,异步函数不会锁住当前线程使其不能向队列中的下一位函数执行。
值得注意的是---当你看到一个同步函数锁住(block)了当前进程,或者一个函数是锁函数(blocking function)或是锁运算(block operation)时别认混了。这里的锁(blocks)是用来形容其对于自己线程的影响,它跟Objective-C中的块(block)是不一样的。再有一点要记住的就是在任何GCD文档中涉及到Objective-C的块代码都是可以用Swift的闭包来替换的。
临界区
这是一段不能被在两个线程中同时执行的代码。这是因为这段代码负责管理像变量这种若被并发进程使用便会更改的可共享资源。
资源竞争
这是一种软件系统在一种不被控制的模式下依靠于特定队列或者基于事件执行时间进行运行的情况,比如说程序当前多个任务执行的具体顺序。资源竞争可以产生一些不会在代码排错中立即找到的错误。
死锁
两个或两个以上的进程因等待彼此完成任务或因执行其他任务而停止当前进程运行的情况被称作为死锁。举个例子,进程A因等待进程B完成任务而停止运行,但进程B也在等待进程A完成任务而停止运行的僵持状态就是死锁。
线程安全性
具有线程安全性的代码可以在不产生任何问题(比如数据篡改、崩溃等)的情况下在多线程间或是并发任务间被安全的调用。不具有线程安全性的代码的正常运行只有在单一的环境下才可被保证。举个具有线性安全性的代码示例let a = ["thread-safe"]。你可以在多线程间,不产生任何bug的情况下调用这个具有只读性的数组。相反,通过var a = ["thread-unsafe"]声明的数组是可变可修改的。这就意味着这个数组在多线层间可被修改从而产生一些不可预测的问题,对于那些可变的变量与数据结构最好不要同时在多个线程间使用。
上下文切换
上下文切换是当你在一个进程中的多个不同线程间进行切换时的一种进程进行储存与恢复的状态。这种进程在写多任务App时相当常见,但这通常会产生额外的系统开销。
并发 VS 并行
并发和并行总是被同时提及,所以有必要解释一下两者间的区别。
并发代码中各个单独部分可以被"同时"执行。不管怎样,这都由系统决定以何种方式执行。具有多核处理器的设备通过并行的方式在同一时间内实现多线程间的工作;但是单核处理器设备只能在同一时间内运行在单一线程上,并利用上下文切换的方式切换至其他线程以达到跟并行相同的工作效果。如下图所示,单核处理器设备运行速度快到形成了一种并行的假象。
并发 VS 并行
尽管你会在GCD下写出使用多线程的代码,但这仍由GCD来决定是否会使用并发机制。并行机制包含着并发机制,但并发机制却不一定能保证并行机制的运行。
队列
GCD通过队列分配的方式来处理待执行的任务。这些队列管理着你提供给GCD待处理的任务并以FIFO的顺序进行处理。这就得以保证第一个加进队列的任务会被首个处理,第二个加进队列的任务则被其次处理,其后则以此类推。
连续队列
连续队列中的任务每次执行只一个,一个任务只有在其前面的任务执行完毕后才可开始运行。如下图所示,你不会知道前一个任务结束到下一个任务开始时的时间间隔。
连续队列
每一个任务的执行时间都是由GCD控制的;唯一一件你可以确保的事便是GCD会在同一时间内按照任务加进队列的顺序执行一个任务。
因为在连续队列中不允许多个任务同时运行,这就减少了同时访问临界区的风险;这种机制在多任务的资源竞争的过程中保护了临界区。假如分配任务至分发队列是访问临界区的唯一方式,那这就保证了的临界区的安全。
并发队列
并发队列中的任务依旧以FIFO顺序开始执行。。。但你能知道的也就这么多了!任务间可以以任何顺序结束,你不会知道下一个任务开始的时间也不会知道一段时间内正在运行任务的数量。因为,这一切都是由GCD控制的。
如下图所示,在GCD控制下的四个并发任务:
并发队列
需要注意的是,在任务0开始执行后花了一段时间后任务1才开始执行,但任务1、2、3便一个接一个地快速运行起来。再有,即便任务3在任务2开始执行后才开始执行,但任务3却更早地结束执行。
任务的开始执行的时间完全由GCD决定。假如一个任务与另一个任务的执行时间相互重叠,便由GCD决定(在多核非繁忙可用的情况下)是否利用不同的处理器运行或是利用上下文切换的方式运行不同的任务。
为了用起来有趣一些,GCD提供了至少五种特别的队列来对应不同情况。
队列种类
首先,系统提供了一个名为主队列(main queue)的特殊连续队列。像其他连续队列一样,这个队列在同一间内只能执行一个任务。不管怎样,这保证了所有任务都将被这个唯一被允许刷新UI的线程所执行。它也是唯一一个用作向UIView对象发送信息或推送监听(Notification)。
GCD也提供了其他几个并发队列。这几个队列都与自己的QoS (Quality of Service)类所关联。Qos代表着待处理任务的执行意图,GCD会根据待处理任务的执行意图来决定最优化的执行优先权。
QOS_CLASS_USER_INTERACTIVE: user interactive类代表着为了提供良好的用户体验而需要被立即执行的任务。它经常用来刷新UI、处理一些要求低延迟的加载工作。在App运行的期间,这个类中的工作完成总量应该很小。
QOS_CLASS_USER_INITIATED:user initiated类代表着从UI端初始化并可异步运行的任务。它在用户等待及时反馈时和涉及继续运行用户交互的任务时被使用。
QOS_CLASS_UTILITY:utility类代表着长时间运行的任务,尤其是那种用户可见的进度条。它经常用来处理计算、I/O、网络通信、持续数据反馈及相似的任务。这个类被设计得具有高效率处理能力。
QOS_CLASS_BACKBROUND:background类代表着那些用户并不需要立即知晓的任务。它经常用来完成预处理、维护及一些不需要用户交互的、对完成时间并无太高要求的任务。
要知道苹果的API也会使用这些全局分配队列,所以你分派的任务不会是队列中的唯一一个。
最后,你也可以自己写一个连续队列或是并发队列。算起来你起码最少会有五个队列:主队列、四个全局队列再加上你自己的队列。
以上便是分配队列的全体成员。
GCD的关键在于选择正确的分发函数以此把你的任务分发至队列。理解这些东西的最好办法就是完善下面的Sample Project。
Sample Project
既然这篇教程的目的在于通过使用GCD在不同的线程间安全地调用代码,那么接下来的任务便是完成这个名为GooglyPuff的半成品。
GooglyPuff是一款通过CoreImage脸部识别API在照片中人脸的双眼的位置上贴上咕噜式的大眼睛且线程不安全的App。你既可以从Photo Library中选择照片,也可以通过网络从事先设置好的地址下载照片。
GooglyPuff Swift Start 1
将工程下载至本地后用Xcode打开并编译运行。它看起来是这样的:
GooglyPuff
在工程中共有四个类文件:
PhotoCollectionViewController:这是App运行后显示的首个界面。它将显示所有被选照片的缩略图。
PhotoDetailViewController:它将处理将咕噜眼添加至照片的工作并将处理完毕的照片显示在UIScrollView中。
Photo:一个包含着照片基本属性的协议,其中有image(未处理照片)、thumbnail(裁减后的照片)及status(照片可否使用状态);两个用来实现协议的类,DownloadPhoto将从一个NSURL实例中实例化照片,而AssetPhoto则从一个ALAsset实例中实例化照片。
PhotoManager:这个类将管理所有Photo类型对象。
使用dispatch_async处理后台任务
回到刚才运行的App后,通过自己的Photo Library添加照片或是使用Le internet下载一些照片。
需要注意的是当你点击PhotoCollectionViewController中的一个UICollectionViewCell后,界面切换至一个新的PhotoDetailViewController所用的时间;对于那些处理速度较慢的设备来说,处理一张较大的照片会产生一个非常明显的延迟。
这种情况下很容易使UIViewController的viewDidLoad因处理过于混杂的工作而负载;这么做的结果便在view controller出现前产生较长的延迟。假如可能的话,我们最好将某些工作放置后台处理。
这听起来dispatch_async该上场了。
打开PhotoDetailViewController后将viewDidLoad函数替换成下述代码:
override func viewDidLoad() {
super
.viewDidLoad()
assert(image != nil,
"Image not set; required to use view controller"
)
photoImageView.image = image
// Resize if neccessary to ensure it's not pixelated
if
image.size.height <= photoImageView.bounds.size.height &&
image.size.width <= photoImageView.bounds.size.width {
photoImageView.contentMode = .Center
}
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
// 1
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(dispatch_get_main_queue()) {
// 2
self.fadeInNewImage(overlayImage)
// 3
}
}
}
在这里解释一下上面修改的代码:
你首先将照片处理工作从主线程(main thread)移至一个全局队列(global queue)。因为这是一个异步派发(dispatch_async的调用,闭包以异步的形式进行传输意味着调用的线程将会被继续执行。这样一来便会使viewDidLoad更早的在主线程上结束执行并使得整个加载过程更加流畅。与此同时,脸部识别的过程已经开始并在一段时间后结束。
这时脸部识别的过程已经结束并生成了一张新照片。当你想用这张新照片来刷新你的UIImageView时,你可以向主线程添加一个新的闭包。需要注意的是--主线程只能用来访问UIKit。
最后,你便用这张有着咕噜眼的fadeInNewImage照片来刷新UI。
有没有注意到你已经用了Swift的尾随闭包语法(trailing closure syntax),就是以在包含着特定分配队列参数的括号后书写表达式的形式了向dispatch_async传递闭包。假如把闭包写出函数括号的话,语法会看起来更加简洁。
运行并编译App;选一张照片后你会发现view controller加载得很快,咕噜眼会在很短的延迟后出现。现在的运行效果看起来比之前的好多了。当你尝试加载一张大得离谱的照片时,App并不会在view controller加载时而延迟,这种机制便会使App表现得更加良好。
综上所述,dispatch_async将任务以闭包的形式添加至队列后立即返回。这个任务在之后的某个时间段由GCD所执行。当你要在不影响当前线程工作的前提下将基于网络或高密度CPU处理的任务移至后台处理时,dispatch_asnyc便派上用场了。
接下来是一个关于在使用dispatch_asnyc的前提下,如何使用以及何时使用不同类型队列的简洁指南:
自定义连续队列(Custom Serial Queue): 在当你想将任务移至后台继续工作并且时刻监测它的情况下,这是一个不错的选择。需要注意的是当你想从一个方法中调用数据时,你必须再添加一个闭包来回调数据或者考虑使用dispatch_sync。
主队列(Main Queue[Serial]):这是一个当并发队列中的任务完成工作时来刷新UI的普遍选择。为此你得在一个闭包中写入另一个闭包。当然,假如你已经在主线程并调用一个面向主线程的dispatch_async的话,你需要保证这个新任务在当前函数运行结束后的某个时间点开始执行。
并发队列(Concurrent Queue):对于要运行后台的非UI工作是个普遍的选择。
获取全局队列的简洁化变量
你也许注意到了dispatch_get_global_queue函数里的QoS类的参数写起来有些麻烦。这是因为qos_class_t被定义成一个值类型为UInt32且最后还要被转型为Int的结构体。我们可以在Utils.swift中的URL变量下面添加一些全局的简洁化变量,以此使得调用全局队列更加简便。
var
GlobalMainQueue: dispatch_queue_t {
return
dispatch_get_main_queue()
}
var
GlobalUserInteractiveQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
}
var
GlobalUserInitiatedQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
}
var
GlobalUtilityQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)
}
var
GlobalBackgroundQueue: dispatch_queue_t {
return
dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
回到PhotoDetailViewController中viewDidLoad函数中,用简洁变量代替dispatch_get_global_queue和dispatch_get_main_queue。
dispatch_async(GlobalUserInitiatedQueue) {
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(GlobalMainQueue) {
self.fadeInNewImage(overlayImage)
}
}
这样就使得派发队列的调用的代码更加具有可读性并很轻松地得知哪个队列正在被使用。
利用dispatch_after实现延迟
考虑一下你App的UX。你的App有没有使得用户在第一次打开App的时候不知道该干些什么而感到不知所措呢?: ]
假如在PhotoManager中没有任何一张照片的时候便向用户发出提醒应该是一个不错的主意。不管怎样,你还是要考虑一下用户在App主页面上的注意力:假如你的提醒显示得过快的话,用户没准在因为看着其他地方而错过它。
当用户第一次使用App的时候,在提醒显示前执行一秒钟的延迟应该足以吸引住用户的注意力。
在PhotoCollectionViewController.swift底部的showOrHideBarPrompt函数中添加如下代码:
func showOrHideNavPrompt() {
let delayInSeconds = 1.0
let popTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(delayInSeconds * Double(NSEC_PER_SEC)))
// 1
dispatch_after(popTime, GlobalMainQueue) {
// 2
let count = PhotoManager.sharedManager.photos.count
if
count > 0 {
self.navigationItem.prompt = nil
}
else
{
self.navigationItem.prompt =
"Add photos with faces to Googlyify them!"
}
}
}
当你的UICollectionView重载的时候,viewDidLoad函数中的showOrHideNavPrompt将被执行。解释如下:
你声明了一个代表具体延迟时间的变量。
你将等待delayInSeconds变量中设定的时间然后向主队列异步添加闭包。
编译并运行App。你会看到一个在很大程度上吸引用户注意力并告知他们该做些什么的细微延迟。
dispatch_after就像一个延迟的dispatch_async。你仍旧在实时运行的时候毫无操控权并且一旦dispatch_after返回后你也无法取消整个延迟任务。
还在思考如何适当的使用dispatch_after?
自定义连续队列(Custom Serial Queue):当你在自定义连续队列上使用dispatch_after时一定要当心,此时最好不要放到主队列上执行。
主队列(Main Queue[Serial]):这对于dispatch_after是个很好的选择;Xcode对此有一个不错的自动执行至完成的样板。
并发队列(Concurrent Queue):在自定义并发队列上使用dispatch_after时同样要当心,即便你很少这么做。此时最好放到主队列上执行。
单例和线程安全
单例,不管你love it还是hate it,他们对于iOS都是非常重要的。: ]
一提到单例(Singleton)人们便觉得他们是线程不安全的。这么想的话也不是没有道理:单例的实例经常在同一时间内被多线程所访问。PhotoManager类便是一个单例,所以你要思考一下上面提到的问题。
两个需要考虑的情况,单例实例初始化时和实例读写时的线程安全性。
先考虑第一种情况。因为在swift是在全局范围内初始化变量,所以这种情况较为简单。在Swift中,当全局变量被首次访问调用时便被初始化,并且整个初始化过程具有原子操作性。由此,代码的初始化过程便成为一个临界区并且在其他线程访问调用全局变量前完成初始化。Swift到底是怎么做到的?其实在整个过程中,Swift通过dispatch_once函数使用了GCD。若想了解得更多的话请看这篇Swift官方Blog。
在线程安全的模式下dispatch_once只会执行闭包一次。当一个在临界区执行的线程--向dispatch_once传入一个任务--在它结束运行前其它的线程都会被限制住。一旦执行完成,它和其他线程便不会再次在此区域执行。通过let把单例定义为全局定量的话,我们就可以保证这个变量的值在初始化后不会被修改。总之,Swift声明的所有全局定量都是通过线程安全的初始化得到的单例。
但我们还是要考虑读写问题。尽管Swift通过使用dispatch_once确保我们在线程安全的模式下初始化单例,但这并不能代表单例的数据类型同样具有线程安全性。举个例子,假如一个全局变量是一个类的实例,你仍可以在类内的临界区操控内部数据,这将需要利用其他的方式来保证线程安全性。
处理读取与写入问题
保证线程安全性的实例化不是我们处理单例时的唯一问题。假如一个单例属性代表着一个可变的对象,比如像PhotoManager 中的photos数组,那么你就需要考虑那个对象是否就有线程安全性。
在Swift中任何用let声明的变量都是一个只可读并线程安全的常量。但是用var声明的变量都是值可变且并线程不安全的。比如Swift中像Array和Dictionary这样的集合类型若被声明为值可变的话,它们就是线程不安全的。那Foundation中的NSArray线程是否安全呢?不一定!苹果还专门为那些线程非安全的Foundation类列了一个清单。
尽管多线程可以在不出现问题的情况下同时读取一个Array的可变实例,但当一个线程试图修改实例的时候另一个线程又试图读取实例,这样的话安全性可就不能被保证了。
在下面PhotoManager.swift中的addPhoto函数中找一找错误:
func addPhoto(photo: Photo) {
_photos.append(photo)
dispatch_async(dispatch_get_main_queue()) {
self.postContentAddedNotification()
}
}
这个写取方法修改了可变数组的对象。
再来看一看photos的property:
private
var
_photos: [Photo] = []
var
photos: [Photo] {
return
_photos
}
当property的getter读取可变数组的时候它就是一个读取函数。调用者得到一份数组的copy并阻止原数组被不当修改,但这不能在一个线程调用addPhoto方法的同时阻止另一个线程回调photo的property的getter。
提醒:在上述代码中,调用者为什么不直接得到一份photos的copy呢?这是因为在Swift中,所有的参数和函数的返回值都是通过推测(Reference)或值传输的。通过推测进行传输和Objective-C中传输指针是一样的,这就代表着你可以访问调用原始对象,并且对于同一对象的推测后其任何改变都可以被显示出来。在对象的copy中通过值结果传值且对于copy的更改都不对原是对象造成影响。Swift默认以推测机制或结构体的值来传输类的实例。
Swift中的Array和Dictionary都是通过结构体来实现的,当你向前或向后传输这些实例的时候,你的代码将会执行很多次的copy。这时不要当心内存使用问题,因为这些Swift的集合类型(如Array、Dictionary)的执行过程都已被优化,只有在必要的时候才会进行copy。对于来一个通过值传输的Array实例来说,只有在被传输后才会进行其第一次修改。
这是一个常见的软件开发环境下的读写问题。GCD通过使用dispatch barriers提供了一个具有读/写锁的完美解决方案。
在使用并发队列时,dispatch barriers便是一组像连续性路障的函数。使用GCD的barrier API保证了被传输的闭包是在特定时间内、在特定队列上执行的唯一任务。这就意味着在派发的barrier前传输的任务必须在特定闭包开始执行前完成运行。
当闭包到达后,barrier便开始执行闭包并保证此段时间内队列不会再执行任何其他的闭包。特定闭包一旦完成执行,队列便会返回其默认的执行状态。GCD同样提供了具有同步与异步功能的barrier函数。
下面的图式描述了在多个异步任务中的barrier函数的运行效果:
dispatch barrier
需要注意的是在barrier执行前程序是以并发队列的形式运行,但当barrier一旦开始运行后,程序便以连续队列的形式运行。没错,barrier是这段特定时间内唯一被执行的任务。当barrier执行结束后,程序再次回到了普通的并发队列运行状态。
对于barrier函数我们做一些必要的说明:
自定义连续队列(Custom Serial Queue):在这种情况下不是特别建议使用barrier,因为barrier在连续队列执行期间不会起到任何帮助。
全局并发队列(Global Concurrent Queue):谨慎使用;当其他系统也在使用队列的时候,你应该不想把所有的队列都垄为自己所用。
自定义并发队列(Custom Concurrent Queue):适用于涉及临界区及原子性的代码。在任何你想要保正设定(setting)或初始化具有线程安全性的情况下,barrier都是一个不错的选择。
从上面对于自定义并发序列解释可以得出结论,你得写一个自己的barrier函数并将读取函数和写入函数彼此分开。并发序列将允许多个读取过程同步运行。
打开PhotoManager.swift,在photos属性下给类文件添加如下的私有属性:
private let concurrentPhotoQueue = dispatch_queue_create(
"com.raywenderlich.GooglyPuff.photoQueue"
, DISPATCH_QUEUE_CONCURRENT)
通过dispatch_queue_create函数初始化了一个名为concurrentPhotoQueue的并发队列。第一个参数是一个逆DNS风格的命名方式;其描述在debugging时会非常有用。第二个参数设定了你的队列是连续性的还是并发性的。
很多网上的实例代码中都喜欢给dispatch_queue_create的第二个参数设定为0或NULL。其实这是一种过时的声明连续分派队列的方法。你最好用你自己的参数设定它。
找到addPhoto函数并代替为以下代码:
func addPhoto(photo: Photo) {
dispatch_barrier_async(concurrentPhotoQueue) {
// 1
self._photos.append(photo)
// 2
dispatch_async(GlobalMainQueue) {
// 3
self.postContentAddedNotification()
}
}
}
你的新函数是这样工作的:
通过使用你自己的自定义队列添加写入过程,在不久后临界区执行的时候这将是你的队列中唯一执行的任务。
向数组中添加对象。只要这是一个barrier属性的闭包,那么它在concurrentPhotoQueue队列中绝不会和其他闭包同时运行。
最后你推送了一个照片添加完毕的消息。这个消息应该从主线程推送因为它将处理一些涉及UI的工作,所以你为这个消息以异步的形式向主线程派发了任务。
以上便处理好了写入方法的问题,但是你还要处理一下photos的读取方法。
为了保证写入方面的线程安全行,你需要在concurrentPhotoQueue队列中运行读取方法。因为你需要从函数获取返回值并且在读取任务返回前不会运行任何其他的任务,所以你不能向队列异步派发任务。
在这种情况下,dispatch_sync是一个不错的选择。
dispatch_sync可以同步传输任务并在其返回前等待其完成。使用dispatch_sync跟踪含有派发barrier的任务,或者在当你需要使用闭包中的数据时而要等待运行结束的时候使用dispatch_sync。
谨慎也是必要的。想象一下,当你对一个马上要运行的队列调用dispatch_sync时,这将造成死锁。因为调用要等到闭包B执行后才能开始运行,但是这个闭包B只有等到当前运行的且不可能结束的闭包A执行结束后才有可能结束。
这将迫使你时刻注意自己调用的的或是传入的队列。
来看一下dispatch_sync的使用说明:
自定义连续队列(Custome Serial Queue):这种情况下一定要非常小心;假如一个队列中正在执行任务并且你将这个队列传入dispatch_sync中使用,这毫无疑问会造成死锁。
主队列(Main Queue[Serial]):同样需要小心发生死锁。
并发队列(Concurrent Queue):在对派发barrier执行同步工作或等待一个任务的执行结束后需要进行下一步处理的情况下,dispatch_sync是一个不错的选择。
依旧在PhotoManager.swift文件中,用以下代码替换原有的photos属性:
var
photos: [Photo] {
var
photosCopy: [Photo]!
dispatch_sync(concurrentPhotoQueue) {
// 1
photosCopy = self._photos
// 2
}
return
photosCopy
}
分布解释一下:
同步派发concurrentPhotoQueue使其执行读取功能。
储存照片数组至photosCopy并返回。
恭喜--你的PhotoManager单例现在线程安全了。不管现在是执行读取还是写入功能,你都可以保证整个单例在安全模式下运行。
队列可视化
还不能完全理解GCD的基础知识?接下来我们将在一个简单的示例中使用断点和NSLog功能确保你进一步理解GCD函数运行原理。
我将使用两个动态的GIF帮助你理解dispatch_async和dispatch_sync。在GIF的每步切换下,注意代码断点与图式的关系。
dispatch_sync重览
override func viewDidLoad() {
super
.viewDidLoad()
dispatch_sync(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog(
"First Log"
)
}
NSLog(
"Second Log"
)
}
dispatch_sync
分布解释:
主队列按顺序执行任务,下一个将要被执行的任务便是实例化包含viewDidLoad的UIViewController。
主队列开始执行viewDidLoad。
dispatch_sync闭包添加至全局队列并在稍后被执行。在此闭包完成执行前主队列上的工作将被暂停。回调的闭包可以被并发执行并以FIFO的顺序添加至一个全局队列。这个全局队列还包含添加dispatch_sync闭包前的多个任务。
终于轮到dispatch_sync闭包执行了。
闭包执行结束后主队列开始恢复工作。
viewDidLoad函数执行结束,主队列开始处理其他任务。
dispatch_sync函数向队列添加了一个任务并等待任务完成。 其实dispatch_async也差不多,只不过它不会等待任务完成便会返回线程。
dispatch_async重览
override func viewDidLoad() {
super
.viewDidLoad()
dispatch_async(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog(
"First Log"
)
}
NSLog(
"Second Log"
)
}
dispatch_async
主队列按顺序执行任务,下一个将要被执行的任务便是实例化包含viewDidLoad的`UIViewControl。
主队列开始执行viewDidLoad。
dispatch_async闭包添加至全局队列并在稍后被执行。
向全局队列添加dispatch_async闭包后viewDidLoad函数继续运行,主线程继续其剩余的任务。与此同时全局队列是并发性的处理它的任务的。可被并发执行的闭包将以FIFO的顺序添加至全局队列。
通过dispatch_async添加的闭包开始执行。
dispatch_async闭包执行结束,并且所有的NSLog语句都已被显示在控制台上。
在这个例子中,第二个NSLog语句执行后第一个NSLog语句才执行。这种情况并不是每次都会发生的--这取决于硬件在给定的时间内所处理的工作,并且你对于哪个语句会先被执行一无所知且毫无控制权。没准“第一个”NSLog就会作为第一个log出现。
Where to Go From Here?
在这篇教程中,你学会了如何让你的代码具有线程安全性和如何在CPU高密度处理多个任务的时候获取主线程的响应。
你可以从这里下载GooglyPuff的完整代码,在下一节教程中你将会继续在这个工程中进行修改。
假如你打算优化你的App,我觉得你真的该使用Instruments中的Time Profile. 具体教程请查看这篇How To Use Instruments。
首先要有一款翻墙软件,
1.到http://developer.android.com/下载android studio
2.下载成后按照官方推荐的配置安装,需要翻墙才能下载各种工具。
3.安装好后打开一个已存在并且编译能过的项目,然后run,但是我遇到的情况是没有识别出我的android的手机,设备列表是空的,报错是google usb driver not compatible with mac os
4.设置adb的环境变量
1 cd ~:进入用户目录2 vi .bash_profile:创建.bash_profile文件
3 export PATH=${PATH}:/xxx/android-sdk-macosx/tools:/xxx/android-sdk-macosx/platform-tools:导入tools和platform-tools的路径,/xxx/android-sdk-macosx/tools–它指向android SDK的tools目录
4 执行如下命令:source ./.bash_profile :使文件生效
5 重启terminal:执行adb进行测试
5.需要将vid找到
- -----------------
- Spreadtrum phone:
- Product ID: 0x5d04
- Vendor ID: 0x1782
- ...
- Serial Number: T619
- ...
- -----------------
2. 终端进入/Users/user/.android,应该有一个adb_usb.ini文件,如果没有就创建,然后将上面的Vender ID “0x1782”单独一行加入到该文件,然后退出。
- # ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.
- # USE 'android update adb' TO GENERATE.
- # 1 USB VENDOR ID PER LINE.
- 0x1782
3. 重启adb server进程。
- adb kill-server
- adb start-server
- -MacBook-Pro:.android user$ adb devices
- List of devices attached
self.orderCost.text = [NSStringstringWithFormat:@"%.1f元",self.order.cost.floatValue];
%.1f 表示小数点一位,%.2f 表示小数点2位,依次类推.格式定义
The format specifiers supported by the NSString formatting methods and CFString formatting functions follow the IEEE printf specification; the specifiers are summarized in Table 1. Note that you can also use the “n$” positional specifiers such as %1$@ %2$s. For more details, see the IEEE printf specification. You can also use these format specifiers with the NSLog function.
平台依赖
Mac OS X uses several data types—NSInteger, NSUInteger,CGFloat, and CFIndex—to provide a consistent means of representing values in 32- and 64-bit environments. In a 32-bit environment, NSInteger and NSUInteger are defined as int and unsigned int, respectively. In 64-bit environments, NSInteger and NSUInteger are defined as long and unsigned long, respectively. To avoid the need to use different printf-style type specifiers depending on the platform, you can use the specifiers shown in Table 2. Note that in some cases you may have to cast the value.
The following example illustrates the use of %ld to format an NSInteger and the use of a cast.
2
printf("%ld\n", (long)i);
In addition to the considerations mentioned in Table 2, there is one extra case with scanning: you must distinguish the types for float and double. You should use %f for float, %lf for double. If you need to use scanf (or a variant thereof) with CGFloat, switch to double instead, and copy the double to CGFloat.
2
3
4
double tmp;
sscanf (str, "%lf", &tmp);
imageWidth = tmp;
It is important to remember that %lf does not represent CGFloat correctly on either 32- or 64-bit platforms. This is unlike %ld, which works for long in all cases.
5.1之后的跳转设置都变了,最新的有效方法
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
//如果点击打开的话,需要记录当前的状态,从设置回到应用的时候会用到
[[UIApplication sharedApplication] openURL:url];
}
下面是这个类的一些功能:
1.设置icon上的数字图标
//设置主界面icon上的数字图标,在2.0中引进, 缺省为0
[UIApplicationsharedApplication].applicationIconBadgeNumber = 4;
2.设置摇动手势的时候,是否支持redo,undo操作
//摇动手势,是否支持redo undo操作。
//3.0以后引进,缺省YES
[UIApplicationsharedApplication].applicationSupportsShakeToEdit =YES;
3.判断程序运行状态
//判断程序运行状态,在2.0以后引入
/*
UIApplicationStateActive,
UIApplicationStateInactive,
UIApplicationStateBackground
*/
if([UIApplicationsharedApplication].applicationState ==UIApplicationStateInactive){
NSLog(@"程序在运行状态");
}
4.阻止屏幕变暗进入休眠状态
//阻止屏幕变暗,慎重使用,缺省为no 2.0
[UIApplicationsharedApplication].idleTimerDisabled =YES;
(慎重使用本功能,因为非常耗电)
5.显示联网状态
//显示联网标记 2.0
[UIApplicationsharedApplication].networkActivityIndicatorVisible =YES;
6.在map上显示一个地址
NSString* addressText =@"1 Infinite Loop, Cupertino, CA 95014";
// URL encode the spaces
addressText = [addressTextstringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSString* urlText = [NSStringstringWithFormat:@"http://maps.google.com/maps?q=%@", addressText];
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:urlText]];
7.发送电子邮件
NSString *recipients =@"mailto:first@example.com?cc=second@example.com,third@example.com&subject=Hello from California!";
NSString *body =@"&body=It is raining in sunny California!";
NSString *email = [NSStringstringWithFormat:@"%@%@", recipients, body];
email = [emailstringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:email]];
8.打电话到一个号码
// Call Google 411
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"tel://8004664411"]];
9.发送短信
// Text to Google SMS
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"sms://466453"]];
10.打开一个网址
// Lanuch any iPhone developers fav site
[[UIApplicationsharedApplication]openURL:[NSURLURLWithString:@"http://itunesconnect.apple.com"]];
在java中使用extends关键字完成类的继承关系,操作格式如下:
class 父类{}
class 子类 extends 父类{}
继承父类的功能,扩展子类的所有功能。
- class Person{
- private String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class student extends Person{
- //此处不添加任何代码
- }
- public class ExtDemo02{
- public static void main(String args[]){
- Student stu = new Student();
- stu.setName("张三");
- set.setAge(30);
- System.out.println(stu.getName()+stu.getAge());
- }
- }
子类有时候也称之为派生类。
继承的进一步研究
子类对象的实例化过程
实例化子类对象:
1.先调用父类的构造->调用父类构造
2.再调用子类构造->调用子类构造
实例:
InstanceDemo.java
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
//输出:父类Person中的构造
子类Student中的构造
实际上此时隐含在Student的构造方法的第一行有以下代码:
super(); //调用父类的构造函数
即代码相当于:
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- super();
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
注意点:在方法覆写时必须考虑到权限问题,即:被子类覆写的方法不能拥有比父类方法更加严格的访问权限。
访问权限一共有四种,现在只讲三种:
●private:只能被类内访问,权限最第
●default:相当于什么都不声明
●public:最大的访问权限
●大小关系:private<default<public
如果降低了访问权限时如Person使用public而Student使用default时,会产生以下错误:
print() in Student cannot override print() in Person; attempting to assign weaker access privileges; was public void print()
//表示错误的,降低了权限
子类会自动调用覆写的方法,但此时如果非要调用父类中的方法的话,使用super()关键字。
如:Student构造方法调用父类的方法可以进行以下修改:
- class Person{
- private String name;
- private int age;
- public Person(){
- System.out.println("父类Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- public void print(){
- System.out.println("just a moment!");
- }
- }
- class Student extends Person{
- private String school;
- public Student(){
- super.print();
- System.out.println("子类student中构造");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- }
- }
---------------------------------------------------------
问题:如果现在在父类中使用private 关键字声明了一个方法,那么在子类中使用default权限算是覆写吗?
答 :不是覆写.
------------------------------------------------------
- class Person{
- private String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- private void print(){
- System.out.println("Person------>void print()");
- }
- }
- class Student extends Person{
- private String school;
- void print(){
- System.out.println("Student-------->void print()");
- }
- }
- public class InstanceDemo{
- public static void main(String args[]){
- Student stu = new Student();
- stu.print();
- }
- }
输出:Student---------->void print()
此时,方法没有被覆写,而是相当于在子类中又重新定义了一个新的方法出来。
3.3 属性的覆写
在子类中声明了父类同名的属性。
- class Person{
- public String name;
- private int age;
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- private void print(){
- System.out.println("Person------>void print()");
- }
- }
- class Student extends Person{
- public String name;
- void print(){
- System.out.println("父类中的属性:"+super.info);
- System.out.println("子类中的属性:"+this.info);
- }
- }
- public class OverrideDemo{
- public static void main(String args[]){
- Student stu = new Student();
- stu.print();
- }
- }
3.4方法的覆写与的方法的重载
overloading overrideing
方法名称相同参数的类型或个数不同 方法名称参数的类型返回值类型全部相同
对权限没有要求 被覆写的方法不能拥有更严格的权限
发生在一个类中 发生在继承类中
3.5 super关键字
表示从子类调用父类中的指定操作,例如调用属性,方法,构造等。因为在子类实例化的时候会默认调用父类中的无参构造,如果现在希望调用有参构造方法,可以使用super方法传递参数。
- class Person{
- private String name;
- private int age;
- public Person(String name,int age){
- this.name = name;
- this.age = age;
- System.out.println("父雷Person中的构造方法");
- }
- public void setName(String name){
- this.name = name;
- }
- public void setInt(int age){
- this.age = age;
- }
- public String getName(){
- return this.name;
- }
- public int getAge(){
- return this.age;
- }
- }
- class Student extends Person{
- private String school;
- public Student(String name,int age,String school){
- super(name,age);
- this.school = school;
- System.out.println("子类student中的构造方法");
- }
- }
- public class OverrideDemo{
- public static void main(String args[]){
- Student stu = new Student("张三",30,"山东大学");
- }
- }
当子类调用无参构造方法时,父雷中不存在无参构造方法,此时就会报错。因为子类会在无参构造方法第一行隐含调用super方法,解决方法为:父类定义一个无参构造方法,或使用super调用父类存在参数的方法。
不管任何时候,子类实例化时候先去调用父类中的构造方法,默认调用无参构造。
总结:
使用 super 调用无参方法
1.建立含无参构造的类。
2.建立含无参构造子类,继承父类。
3.子类中构造第一行添加或不添加super
使用 super 调用含参数的方法
1.建立含参构造的类。
2.建立含参子类(覆写构造函数),继承父类。
3.子类中构造第一行添加super(内部参数同父类构造方法)
this与super的区别
this super
属性访问:访问本类中的属性,如果本类没有这些属性则从父类中查找。 属性访问:访问父类中的属性
方法:访问本类中的方法,如果本类没有则从父类中查找。 方法:直接访问父类中的方法
调用构造:调用本类构造,必须放在构造方法首行。 调用构造:调用父类构造,必须放在子类构造方法的首行。
特殊:表示当前对象。 特殊:无此概念
对于this和super本身都可以调用构造方法,而且调用的时候都必须放在构造方法首行,所以两个关键字不能同时出现。
疑问:如果在构造方法中(子类)使用了this,那么是不是就不调用父类中的构造方法?
答:子类中不可能同时都调用this()方法,所以默认仍会调用父类的构造方法。
简单单向链表
- class Node{
- private String data; //存储当前节点内容
- private Node next=null; //存储下一下节点
- public Node(String data){
- this.setDate(data);
- }
- public void setDate(String data){
- this.data = data;
- }
- public void setNext(Node next){
- this.next = next;
- }
- public String getDate(){
- return this.data;
- }
- public Node getNext(){
- return this.next;
- }
- }
- public class LinkDemo01
- {
- public static void main(String args[]){
- Node n1 = new Node("节点-A");
- Node n2 = new Node("节点-B");
- Node n3 = new Node("节点-C");
- Node n4 = new Node("节点-D");
- n1.setNext(n2);
- n2.setNext(n3);
- n3.setNext(n4);
- printNode(n1);
- }
- public static void printNode(Node node){
- System.out.println(node.getDate());
- if(node.getNext()!=null){
- printNode(node.getNext());
- }
- }
- }
单向链表整合内部类
- class Link
- {
- class Node
- {
- private String data;
- private Node next=null;
- public Node(String data){
- this.setData(data);
- }
- public void setData(String data){
- this.data = data;
- }
- public void setNext(Node next){
- this.next = next;
- }
- public String getData(){
- return this.data;
- }
- public Node getNext(){
- return this.next;
- }
- public void add(Node node){
- if(this.next==null){
- this.next = node;
- }else{
- this.next.add(node);
- }
- }
- public void print(){
- if(this.next==null){
- System.out.println(this.getData());
- }else{
- System.out.println(this.getData());
- this.next.print();
- }
- }
- public boolean search(String data){//内部搜索方法
- if(data.equals(this.data)){
- return true;
- }else{//向下继续判断
- if(this.next!=null){
- return this.next.search(data);
- }else{
- return false;
- }
- }
- }
- public void delete(Node previous,String data){
- if(data.equals(this.data)){
- previous.next = this.next;//空出当前节点
- }else{
- if(this.next!=null){
- this.next.delete(this,data); //继续查找
- }
- }
- }
- }
- private Node root; //根节点
- public void addNode(String data){
- Node newNode = new Node(data); //创建新节点
- if(this.root==null){
- this.root = newNode;
- }else{
- this.root.add(newNode);
- }
- }
- public void printNode(){
- if(this.root!=null){
- this.root.print();//调用Node类中的输出操作
- }
- }
- public boolean contains(String name){
- return this.root.search(name); //调用Node类的查找方法
- }
- public void deleteNode(String data){
- if(this.contains(data)){ //判断节点是否存在
- if(this.root.getData().equals(data)){
- this.root = this.root.next; //修改根节点
- }else{
- this.root.next.delete(root,data); //把下一个节点的前节点和数据一起传入进去
- }
- }
- }
- }
- public class LinkDemo02
- {
- public static void main(String args[]){
- Link l = new Link();
- l.addNode("节点-A");
- l.addNode("节点-B");
- l.addNode("节点-C");
- l.addNode("节点-D");
- //增加之后的内容
- l.printNode();
- //判断是否包含节点
- System.out.println(l.contains("节点-X"));
- l.deleteNode("节点-B");
- //删除之后的内容
- l.printNode();
- }
- }
总结:
1.类的职能不同,LinkDemo01是基础链表类,而LinkDemo02的内部类为基础链表类,外部类为链表操作类。
2.基础链表类中存在一个this.next指向下一个链表对象。
构造方法私有化及单态模式
构造方法封装
类的封装性不光体现在对属性的封装上,实际上方法也是可以被封装的,当然在方法封装中也包含了对构造方法的封装。例如:以下的代码,就是对构造方法进行了封装。- class Singleton{
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo02{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = new Singleton() ; // 错误,无法实例化对象
- }
- };
- class Singleton{
- Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo03{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- }
- };
正常情况下,instance属性只能通过Singleton类的实例化对象才可以进行调用,如果在没有实例化对象的时候依然可以取得instance,则就需要将instance声明成static访问类型,因为使用static声明的变量,可以直接使用类名称进行访问。
- class Singleton{
- static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo04{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = Singleton.instance ; // 取得实例化对象
- s1.print() ; // 调用方法
- }
- };
- class Singleton{
- private static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- public static Singleton getInstance(){ // 通过静态方法取得instance对象
- return instance ;
- }
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo05{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- s1 = Singleton.getInstance() ; // 取得实例化对象
- s1.print() ; // 调用方法
- }
- };
程序的意义?
以上的代码不如直接实例化操作来的直接,为什么还要这样去做呢?
如果现在产生了本类的三个对象。
- class Singleton{
- private static Singleton instance = new Singleton() ; // 在内部产生本类的实例化对象
- public static Singleton getInstance(){ // 通过静态方法取得instance对象
- return instance ;
- }
- private Singleton(){ // 将构造方法进行了封装,私有化
- }
- public void print(){
- System.out.println("Hello World!!!") ;
- }
- };
- public class SingletonDemo05{
- public static void main(String args[]){
- Singleton s1 = null ; // 声明对象
- Singleton s2 = null ; // 声明对象
- Singleton s3 = null ; // 声明对象
- s1 = Singleton.getInstance() ; // 取得实例化对象
- s2 = Singleton.getInstance() ; // 取得实例化对象
- s3 = Singleton.getInstance() ; // 取得实例化对象
- s1.print() ; // 调用方法
- s2.print() ; // 调用方法
- s3.print() ; // 调用方法
- }
- };
不管外部声明了多少个Singleton的对象,但是最终结果都是通过getInstance()方法取得的实例化对象,也就是说,此时S1、S2、S3实际上都使用了一个对象的引用:instance
那么这样的设计在设计模式上来讲,属于单态设计模式(单例设计模式):Singleton
如果现在不希望一个类产生过多的对象的话,则就必须使用单态设计模式,而且使用单态设计模式在以后的JAVA学习中会经常碰到,因为在JAVA的支持的类库中,大量的采用了此种设计模式。
所谓的单态就是在入口处(构造方法)限制了对象的实例化操作。
单态设计模式的意义
实际上这样的应用,读者应该早就能有所了解了,读者应该都会很清楚在windows中有一个回收站的程序,除了桌面上的回收站之外,每个硬盘上都有一个回收站,实际上每个硬盘的回收站和桌面上的回收站都是同一个,那么也就是说在整个操作系统上只有一个回收站实例,各个地方只是引用此实例而已。
总结:
单例模式步骤:
1.将类中的构造方法私有化。2.在内部通过静态私有化属性实例化对象。
3.创建公共函数返回静态私有化属性。
- 如何做好APP营销
- 如何做好App
- 如何做好app的引导页
- 如何做好app的测试工作?
- 视觉设计:如何做好App的引导页?
- 视觉设计:如何做好App的引导页?
- 汇道科金支招:定制开发商城App软件如何做好用户体验
- APP测试中的头疼脑热:测试人员如何驱动开发做好自测
- 如何通过统计分析工具做好APP的数据分析和运营
- 怎样才能做好app应用推广?
- 如何做好一个网站?
- 如何做好下属
- 如何做好一个软件
- 如何做好SQA?
- 如何做好职业生涯规划?
- 如何做好项目经理
- 如何做好SQA
- 如何做好PL
- 要常看的东东
- Js实现浏览器下标签页间切换触发的事件
- django的404,500错误自定义页面的配置
- CUDA issue:cudaGetDeviceCount()错误
- 北雁计算器
- 如何做好App
- 鸟哥的Linux私房菜(服务器)- 第十二章、网络参数控管者: DHCP 服务器
- android中实现多个apk文件。
- 智慧云停车场的业务
- Java序列化详解
- ]Android ADT SDK API 说明
- 鸟哥的Linux私房菜(服务器)- 第十三章、文件服务器之一:NFS 服务器
- ios面试题(1)
- Reverse Integer