iOS程式設計基礎-OC(九)-專家級技巧:使用執行時系統API
第九章 專家級技巧:使用執行時系統API
第7章和第8章介紹了OC的動態特性和用於實現這些特性的執行時系統結構;
本章將通過幾個示例程式使你獲得使用執行時系統功能及其API的實踐經驗;
你將使用NSInvocation API建立一個動態代理;
使用NSBundle API動態載入你編寫的框架包;
還將編寫一個廣泛使用執行時系統庫API的程式;
本章之後,您將能夠掌握錯綜複雜的OC執行時系統;
是不是很期待;
9.1 使用可選包擴充套件程式
OC提供了非常多的動態特性,他們提供了這門語言的能力和靈活性;使你能夠在執行程式的過程中修改程式;
第七章介紹了動態載入,還簡要介紹了使用Foundation框架中的NSBundle類管理包的方式;
接下來我們將使用這些特性編寫一個程式,使用可選包擴充套件正在執行的程式;
9.1.1 方法
這個例子實際需要編寫兩個工程;
需要在一個工程中編寫可以使用可選包的程式;
在另一個工程中建立可選包;
具體步驟如下:
1)在工程1中,編寫一個協議和遵循該協議的類;
2)在工程2中,建立一個包含另一個遵守該協議的類的可選包;
3)在工程1中,使用NSBundle類找到並載入這個包(在步驟2中建立的),使用該包中的類建立一個物件,然後呼叫這個物件中的方法;
不是很難理解,我們繼續看;
9.1.2 步驟1:編寫基礎程式碼
工程1我們就是用當前的這個專案;
接下來建立一個協議和遵守這個協議的類;
我們已經知道(第二章)使用協議可以宣告通用的方法和屬性;
將協議與動態型別一起使用可以獲得一種理想的機制;
通過包以動態方式載入的類設定實現的方法;
接下來新建這個協議;
選擇協議模板,這樣就在導航欄窗格中添加了一個名為C9Greeter.h的標頭檔案;
點選可以看到這個C9Greeter協議;之後新增一個方法;
再建一個遵循這個協議的類C9BasicGreeter;
(Code9_1:C9Greeter.h C9BasicGreeter.h C9BasicGreeter.m)
#import <Foundation/Foundation.h>
@protocol C9Greeter <NSObject>
-(NSString *)greeting:(NSString *)salutation;
@end
#import <Foundation/Foundation.h>
#import "C9Greeter.h"
@interface C9BasicGreeter : NSObject
@end
#import "C9BasicGreeter.h"
@implementation C9BasicGreeter
-(NSString *)greeting:(NSString *)salutation{
return [NSString stringWithFormat:@"%@,Wrold",salutation];
}
@end
現在我們來進行測試這段程式碼;
匯入C9BasicGreeter類的標頭檔案;
實現如下程式碼;
id<C9Greeter> greeter1 = [[C9BasicGreeter alloc] init];
NSLog(@"%@",[greeter1 greeting:@"Hello"]);
log:
2017-12-15 15:06:30.574490+0800 精通Objective-C[38928:3729761] Hello,Wrold
我們慢下腳步,來看看這段程式碼:
首先,在遵守協議的時候,我們並沒有通過擴充套件的方式,聲明當前類已經遵循了這個協議;
@interface C9BasicGreeter()<C9Greeter>
@end
而是手動將這個協議的方法在C9BasicGreeter類中進行了實現;
這個裡建立的BasicGreeter物件賦給了一個名為greeter的變數;
這個變數被宣告為id型別並且遵守Greeter協議;
然後,呼叫了物件中的方法greeting:,最後顯示了結果;
使用id<C9Greeter> greeter1宣告的變數,就可以將任何遵守C9Greeter協議的OC物件賦值給他;
接下來要建立另一個遵守C9Greeter協議的類,只不過這次是在可選包中建立;
9.1.3 步驟2:建立一個可選包
新建一個工程;參考下圖:
(Pic9_1)
命名為C9CustomGreeter;這樣我們就建立了一個空包;
我們來看下這個工程的導航欄窗格,如下圖:
(Pic9_2)
1)products分組下邊有一個.bundle檔案,這個就是要新增到你編寫的程式碼和資源中的包;
2)info.plist也是一個必不可少的檔案,含有這個包的配置資訊;
接下來向該包中新增一個實現C9Greeter協議的類;
要做到這一點,首先需要將協議對應的標頭檔案包含到這兒工程中;
我們按住Control點選導航欄窗格,xcode會開啟一個下拉選單,我們可以新增檔案到當前工程;(如下圖)
(Pic9_3)
(Pic9_4)
繼續我們的操作,在可選包工程中新增一個遵守C9Greeter協議的新類:C9CustomGreeter;
(Code9_2:C9CustomGreeter.h C9CustomGreeter.m)
#import <Foundation/Foundation.h>
#import "C9Greeter.h"
@interface C9CustomGreeter : NSObject
@end
#import "C9CustomGreeter.h"
@implementation C9CustomGreeter
-(NSString *)greeting:(NSString *)salutation{
return [NSString stringWithFormat:@"%@,Universe",salutation];
}
@end
通過Product選單中選擇Build選項,編譯了這個包;
這樣我們就建立了一個包;
選擇這個包檔案,可以在右側檔案檢查器顯示出完整的檔案路徑;
當你使用NSBundle類載入這個包的時候,需要用到這個路徑;
9.1.4 步驟3:動態載入包
接下來我們需要通過該包以動態的方式建立一個物件(通過該包中的類),並呼叫這個物件的方法;
在實際測試程式碼執行是前,我們還需要做一些準備:
最主要的就是如何獲取bundle包的地址;
我們將通過main函式的引數argv[],來獲取對應傳入的引數(這個可以參考我的C語言blog中關於這部分的講解),稍後會有介紹;
複製包路徑,按照下圖在工程1中進行配置:
(Pic9_6)
同時在工程目錄下新建一個plist檔案,用來記錄和儲存這個路徑引數;(其他的儲存路徑方式均可)
(Code_main.m)
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "C9Greeter.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString * bundleGreeterPath;
if (argc != 2) {
NSLog(@"沒有獲得包路徑!");
}else{
bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
if (bundleGreeterPath == nil) {
NSLog(@"Bundle not found at this path!");
}else{
NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSMutableDictionary * pathData;
if (!dic) {
pathData = [NSMutableDictionary dictionary];
}else{
pathData = [NSMutableDictionary dictionaryWithDictionary:dic];
}
[pathData setValue:bundleGreeterPath forKey:@"bundleGreeterPath"];
BOOL success = [pathData writeToFile:filePath atomically:YES];
NSLog(@"%d",success);
}
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
接下來,回到當前工程(工程1),執行如下程式碼;
id greeter = [[C9BasicGreeter alloc] init];
NSLog(@"%@",[greeter greeting:@"Hello-1"]);
id greeterB;
NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"%@",dic);
NSString * bundlePath =dic[@"bundleGreeterPath"];
NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];
if (greeterBundle == nil) {
NSLog(@"Bundle not found at this path!");
}else{
NSError * error;
BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
if (!isLoaded) {
NSLog(@"Error = %@",[error localizedDescription]);
}else{
//載入包後使用該包建立一個物件並向這個物件傳送一條訊息
Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
greeterB = [[greeterClass alloc] init];
NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
//使用完以動態方式載入的包,現在解除安裝它
//先釋放所有用包中類建立的物件!
greeterB = nil;
BOOL isUnloaded = [greeterBundle unload];
if (!isUnloaded) {
NSLog(@"Counld not unload bundle!");
}
}
}
這段程式碼雖然看起來很長,我們來分段理解下:
1)獲取包的路徑引數;
2)建立一個NSBundle物件;
3)載入包;
4)獲得包中的類;
5)解除安裝包;
6)設定包路徑引數;
再逐條研究下這些程式碼的邏輯:
1.獲取包的路徑引數
OC中main()函式會設定兩個引數:argc和argv;
使用這些引數能夠在執行程式的過程中向程式傳遞引數;
int main(int argc, char * argv[]);
引數argc設定了程式引數的數量;
argc陣列儲存了以空格分隔的引數值;
argv[0]儲存的是程式的名字,對應的argc的值大於等於1;(如果實在命令列中執行程式 程式名以及其之後空格間隔的內容就是main函式接受的引數,這一部分在我的C語言blog中有詳解);
我們剛剛在scheme中新增argument就是工程2中生成的包的路徑;
這樣這一點對應的程式碼就好理解了;
2.建立一個NSBundle物件
NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];
if (greeterBundle == nil) {
}
根據路徑建立一個NSBundle物件,並驗證該物件是否成功建立;
3.載入包
-(BOOL)loadAndReturnError:(NSError **)error;
NSBundle提供了多個向OC執行時系統中載入包的方法,上邊的就是其中之一,出錯的話也會及時返回,成功則返回BOOL值YES;
4.獲得包中的類
從包中獲取類,然後建立一個物件使用;
這裡也可以使用[greeterBundle principalClass]的方式獲取類greeterClass;
principalClass被用於獲取包的首要類;
首要類用於控制包中含有的其他類;
包的Info.plist檔案舍之類首要類的名字(沒有設定的話,那麼從包中載入的第一個類就是首要類);
(Pic9_7)
這個生成的例項被賦值給了已經宣告的變數greeterB,這個變數在宣告時最好直接宣告為id<C9Greeter> greeterB;
5.解除安裝包
不再需要的包可以解除安裝,以節省系統資源;
6.設定包路徑引數
其實要做的就是查明bundle包的路徑,在程式執行時將這個路徑設定為它的輸入引數;
如何查詢該路徑以及如何設定,前面我們都已截圖說明過,這裡不再贅述;
這裡有必要知道,scheme方案定義了Xcode功能和工作區的建立和測試設定;
建立配置資訊中含有執行目標程式時傳遞的引數;因此在工程1中這裡設定的是包的完整路徑;
說了這麼多一直還沒給大家看log呢;我們來執行下看看:
2017-12-18 14:16:24.005835+0800 精通Objective-C[50675:5339959] Error = The bundle “C9CustomGreeter” couldn’t be loaded because it is damaged or missing necessary resources.
報錯了!
這個問題我大致查了一下,應該是涉及到動態載入更新程式碼的方式,在客戶端是受限的,或者有其他方式;
比如我們將一個模組獨立成一個包,然後從伺服器下載,再到客戶端去使用,會達到動態更新的目的;
這個問題,暫時還沒有更加深入的理解,也在網上同人交流,mark在這,日後清楚之後,再分出一節單獨討論吧;(本段標紅)
為了證明這種方式的可行性,我們新建了一個專案C9DynaLoader,將其作為工程1的替代,將協議和實現協議的類都在這個工程中使用;
在其main.m中執行上述邏輯的測試程式碼如下:
(Code_C9DynaLoader_main.m)
#import <Foundation/Foundation.h>
#import "C9BasicGreeter.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
id greeter = [[C9BasicGreeter alloc] init];
NSLog(@"%@",[greeter greeting:@"Hello-1"]);
NSString * bundleGreeterPath;
if (argc != 2) {
NSLog(@"沒有獲得包路徑!");
}else{
bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
if (bundleGreeterPath == nil) {
NSLog(@"Bundle not found at this path!");
}else{
id<C9Greeter> greeterB;
NSBundle * greeterBundle = [NSBundle bundleWithPath:bundleGreeterPath];
if (greeterBundle == nil) {
NSLog(@"Bundle not found at this path!");
}else{
NSError * error;
BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
if (!isLoaded) {
NSLog(@"Error = %@",[error localizedDescription]);
}else{
//載入包後使用該包建立一個物件並向這個物件傳送一條訊息
Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
greeterB = [[greeterClass alloc] init];
NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
//使用完以動態方式載入的包,現在解除安裝它
//先釋放所有用包中類建立的物件!
greeterB = nil;
BOOL isUnloaded = [greeterBundle unload];
if (!isUnloaded) {
NSLog(@"Counld not unload bundle!");
}
}
}
}
}
return 0;
}
}
目錄結構截圖署截圖說明如下:
(Pic9_8)
(Pic9_9)
和原來的工程1一樣,還是要設定包路徑引數;
執行C9DynaLoader,log如下:
2017-12-18 15:11:32.521433+0800 C9DynaLoader[51745:5414801] Hello, World!
2017-12-18 15:11:32.521733+0800 C9DynaLoader[51745:5414801] Hello-1,Wrold
2017-12-18 15:11:32.530178+0800 C9DynaLoader[51745:5414801] Hello-2,Universe
至此,我們看到:
greeterB物件是通過以動態方式載入的C9CustomGreeter包建立的;這就是通過使用NSBundle類以動態的方式載入包,向正在執行的程式新增程式碼和資源的方式;
相關推薦
iOS程式設計基礎-OC(九)-專家級技巧:使用執行時系統API
第九章 專家級技巧:使用執行時系統API 第7章和第8章介紹了OC的動態特性和用於實現這些特性的執行時系統結構; 本章將通過幾個示例程式使你獲得使用執行時系統功能及其API的實踐經驗; 你將使用NSInvocati
iOS程式設計基礎-OC(六)-專家級技巧:使用ARC
第6章 專家級技巧:使用ARC 本章是第一部分的最後一章; 本章介紹ARC記憶體管理中的細微之處; 如直接橋接物件使用AR
iOS程式設計基礎-OC(七)-執行時系統(續)
第7章 執行時系統 7.4 動態繫結 動態繫結(dynamic binding): 是指在執行程式時(而不是在編譯時)將訊息與方法對應起來的處理過程;
iOS程式設計基礎-OC(七)-執行時系統
第7章 執行時系統 終於到了執行時這一章,讓我們來一步一步揭開它神祕的面紗吧; OC擁有相當多的動態特性,這些特性在執行程式時發揮作用,而不是在編譯或連結程式碼時發
iOS學習筆記56(Runtime)-Objective-C Runtime 執行時之三:方法與訊息
前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL
.NET基礎知識(九)
clear .net基礎 continue 連接數 public 全局 return語句 實現 tar 1、打印出由*號組成的倒三角形的圖案******* 4*2-1 要求: 1、輸入倒三角的行數,行數範圍3-18,對於不在範圍的行數,拋出提示. ***** 3*2-1
11-Linux基礎入門(九)-Linux的通配符
cal echo 微信公眾平臺 當前 技術分享 vertical hub font not 一、概述Linux的通配符和正則表達式是不一樣的,因此代表的意義也是有較大區別的。通配符一般用於用戶命令行bash環境,而Linux正則表達式用於grep、sed、awk場景。符號代
PL/SQL程式設計基礎知識(一)
--PL/SQL變數的宣告和賦值 declare v_ename varchar2(30);--定義變數 begin v_ename:='&請輸入名字';--接受鍵盤輸入 dbms_output.put_line(v_ename); end; --put_line :列印換行
PHP基礎知識(九)
PHP面向物件技術 一、類的定義: 1 <?php 2 class Cat{ 3 //定義成員變數,需使用var關鍵字 4 var $name; 5 var $sex; 6 var $age;
linux基礎篇(九):Redhat7系統中使用指令碼安裝虛擬機器
安裝虛擬機器 要安裝一個虛擬機器,我們需要開啟virt-manager然後一步一步的去設定才能到達安裝頁面,但是有沒有更好的辦法能夠只需要一部到達安裝虛擬機器的頁面呢,下面就給大家展示用指令碼直接到達安裝一個虛擬機器的頁面 安裝虛擬機器我們當然要在真機中操作,每一步都一定要仔細檢查
深度學習基礎系列(九)| Dropout VS Batch Normalization? 是時候放棄Dropout了 深度學習基礎系列(七)| Batch Normalization
Dropout是過去幾年非常流行的正則化技術,可有效防止過擬合的發生。但從深度學習的發展趨勢看,Batch Normalizaton(簡稱BN)正在逐步取代Dropout技術,特別是在卷積層。本文將首先引入Dropout的原理和實現,然後觀察現代深度模型Dropout的使用情況,並與BN進行實驗比對,從原
Atitit web 之道 艾龍著 Atitit web 之道 艾龍艾提拉著v2 saa.docx 1. 第1章 Web程式設計基礎知識 (1) 3 1.1. 1.1 什麼是Web (1) 3 1.2.
Atitit web 之道 艾龍著 Atitit web 之道 艾龍艾提拉著v2 saa.docx 1. 第1章 Web程式設計基礎知識 (1) 3 1.1. 1.1 什麼是Web (1) 3 1.2. 1.2 Web的工作原理 (2) 3 1.3. 1.3 Int
Go程式設計基礎—函式(func)
https://blog.csdn.net/qq_22063697/article/details/74858264 函式是基本的程式碼塊,用於執行一個任務,是構成程式碼執行的邏輯結構。 在Go語言中,函式的基本組成為:關鍵字func、函式名、引數列表、返回值、函式體和返回語句。 函
Python3基礎之(九)if elif else 判斷
一、基本使用 if condition1: true1_expressions elif condition2: true2_expressions elif condtion3: true3_expressions elif ... ... else
Unity Editor 基礎篇(九):EditorUtility編輯器工具
EditorUtility 編輯器工具 轉自:http://blog.csdn.net/liqiangeastsun/article/details/42174339,請檢視原文,尊重樓主原創版權。 這是一個編輯器類,如果想使用它你需要把它放到工程目錄下的Assets/E
深度學習基礎系列(九)| Dropout VS Batch Normalization? 是時候放棄Dropout了
Dropout是過去幾年非常流行的正則化技術,可有效防止過擬合的發生。但從深度學習的發展趨勢看,Batch Normalizaton(簡稱BN)正在逐步取代Dropout技術,特別是在卷積層。本文將首先引入Dropout的原理和實現,然後觀察現代深度模型Dropout的使用情況,並與BN進行實驗比對,從原
【鏈塊技術54期】智慧合約基礎語言(九)——Solidity繼承
原文連結:以太坊智慧合約(九):Solidity繼承 本文主要講解了有關智慧合約繼承的概念、繼承的引數傳遞、重寫函式以及Solidity的繼承中的呼叫關係與多繼承。掌握區塊鏈技術,學習智慧合約。 一、目錄 ☞繼承的概念 ☞繼承的引數傳
iOS開發基礎Swift(04)— 迴圈
迴圈的介紹在開發中經常會需要迴圈常見的迴圈有:for/while/do while.這裡我們只介紹for/while,因為for/while最常見for迴圈的寫法最常規寫法// 傳統寫法 for var
OpenGL程式設計 基礎篇(四)與滑鼠的互動
#include "stdafx.h" #include <cstdlib> #include <gl\glut.h> const int screenWidth = 600; const int screenHeight = 480; class GLintPoint { publ